Repository navigation
[FEATURE]: Restore the legacy UI with the persistent left sidebar as an option #48882
Description
Activity
github-actions commented
on Sep 14, 2026 on Sep 14, 2026 – with GitHub ActionsContributorMore actionsThis issue might be a duplicate of existing issues. Please check:
- [UI feedback] Forced V2 interface destroys productivity for multi-project/multi-agent workflows (20+ sessions) #48837: Same request — restore persistent sidebar/old layout for power users managing many sessions
- [FEATURE] : keep legacy layout option #37012: "keep legacy layout option" — directly requests preserving the old two-panel layout
- [FEATURE]New users can't switch back from the new layout — no layout toggle available #39835: No layout toggle available for switching back to the old layout
- Web: no way to revert the new layout, and the new layout is missing workspaces/worktrees #37546: Web UI has no way to revert to the old layout
Please review these issues and consider adding your feedback there instead, or linking them as related.
Reacted by LAO, daudef and 0-SamboNZ-0准备换deepseek-harness,新布局生产力不行
Reacted by kapaandyy, beliefchen398, daudef and 0-SamboNZ-0新UI太难用了,完全没用旧UI好用,新UI操作太反人类
Reacted by 理查德, intlex, diuwu, kapaandyy, beliefchen398, 0-SamboNZ-0, ZenDevMaster and anonymous2100I also agree that the original layout was MUCH easier to use; this is a significant loss for usability!
Reacted by intlex, diuwu, aa2013, kapaandyy, Kevin Riemens, beliefchen398, Igor Chakrygin, Florian Buenzli, drobin04, 0-SamboNZ-0 and 2 more写了个powersell脚本,替换opencode打包后的
asar里面的新布局判断逻辑强制使用旧布局,执行powershell,输入asar的文件夹,通常是:
C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resources
注意替换路径里面的用户名,脚本会自动备份当前的asar,执行完成后重启opencode即可,支持 1.18.30 版本
注意:每次更新opencode后需要重新执行I wrote a PowerShell script that replaces the new layout detection logic inside the packaged
asarof opencode, forcing it to use the old layout. Run PowerShell and enter the asar folder, which is usually:
C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resources
Note: replace the username in the path. The script will automatically back up the current asar. After it finishes, just restart opencode. It supports version 1.18.30.
Note: You need to run it again after each opencode update.
script
generated by ai
Copy and save it as a .ps1 file, then run it.
$ErrorActionPreference = "Stop" # Wait for a key press so the console window does not close before the result is visible. function Wait-Key { Write-Host "" Write-Host "Press any key to exit ..." [void][Console]::ReadKey($true) } # ---------- Step 0: locate app.asar ---------- Write-Host "[1/8] Resolve app.asar path from the folder you provide ..." $inputPath = Read-Host "Enter the opencode install folder" $inputPath = $inputPath.Trim().Trim('"', "'") if ([string]::IsNullOrWhiteSpace($inputPath)) { Write-Host "Error: path cannot be empty." Wait-Key exit 1 } if (-not (Test-Path -LiteralPath $inputPath -PathType Container)) { Write-Host "Error: folder not found: $inputPath" Wait-Key exit 1 } # 只接受文件夹:优先找其下的 resources\app.asar,其次文件夹内直接存在的 app.asar $item = Get-Item -LiteralPath $inputPath $candidates = @( (Join-Path $item.FullName "resources\app.asar"), (Join-Path $item.FullName "app.asar") ) $AsarPath = $null foreach ($candidate in $candidates) { if (Test-Path -LiteralPath $candidate -PathType Leaf) { $AsarPath = $candidate break } } if (-not $AsarPath) { Write-Host "Error: no app.asar found under: $($item.FullName)" Write-Host "Expected at 'resources\app.asar' or 'app.asar'." Wait-Key exit 1 } Write-Host " app.asar: $AsarPath" # ---------- Step 1: prepare equal-length replacement ---------- Write-Host "[2/8] Building equal-length replacement string ..." $target = "if (layoutUpgrade()) return true;" $replacement = "return false;" $pad = " " * ($target.Length - $replacement.Length) $newString = $replacement + $pad # Equal-length invariant must hold, otherwise the ASAR header offsets break. if ($newString.Length -ne $target.Length) { Write-Host "Error: equal-length invariant broken." Wait-Key exit 1 } Write-Host " target len: $($target.Length) / replacement len: $($newString.Length)" # ---------- Step 2: read bytes ---------- Write-Host "[3/8] Reading bytes from $AsarPath ..." $targetBytes = [System.Text.Encoding]::ASCII.GetBytes($target) $newBytes = [System.Text.Encoding]::ASCII.GetBytes($newString) $bytes = [System.IO.File]::ReadAllBytes($AsarPath) Write-Host " read $($bytes.Length) bytes" # Naive scan for a byte sub-sequence; good enough for a short needle. function Find-Bytes { param( [byte[]]$Data, [byte[]]$Pattern, [int]$StartIndex = 0 ) $last = $Data.Length - $Pattern.Length for ($i = $StartIndex; $i -le $last; $i++) { if ($Data[$i] -ne $Pattern[0]) { continue } $ok = $true for ($j = 1; $j -lt $Pattern.Length; $j++) { if ($Data[$i + $j] -ne $Pattern[$j]) { $ok = $false break } } if ($ok) { return $i } } return -1 } # ---------- Step 3: search for target ---------- Write-Host "[4/8] Searching for target string ..." $index = Find-Bytes -Data $bytes -Pattern $targetBytes if ($index -lt 0) { Write-Host " Target string not found; maybe already locked." Wait-Key exit 0 } Write-Host " found at offset $index" # Ensure the match is unique so we never patch the wrong spot. $second = Find-Bytes -Data $bytes -Pattern $targetBytes -StartIndex ($index + 1) if ($second -ne -1) { Write-Host "Error: multiple matches found (offset $second); aborting." Wait-Key exit 1 } # ---------- Step 4: backup ---------- Write-Host "[5/8] Creating backup ..." $bakPath = [System.IO.Path]::ChangeExtension($AsarPath, "asar.bak") if (-not (Test-Path -LiteralPath $bakPath)) { Copy-Item -LiteralPath $AsarPath -Destination $bakPath Write-Host " Backup created: $bakPath" } else { Write-Host " Backup exists, skip: $bakPath" } # ---------- Step 5: patch in memory ---------- Write-Host "[6/8] Applying equal-length replacement ..." $originalLength = $bytes.Length # Overwrite the matched region with the equal-length replacement. [System.Array]::Copy($newBytes, 0, $bytes, $index, $newBytes.Length) if ($bytes.Length -ne $originalLength) { Write-Host "Error: array length changed; aborting before write." Wait-Key exit 1 } # ---------- Step 6: write back ---------- Write-Host "[7/8] Writing patched bytes back ..." [System.IO.File]::WriteAllBytes($AsarPath, $bytes) # ---------- Step 7: verify ---------- Write-Host "[8/8] Verifying patched region ..." $verifyBytes = [System.IO.File]::ReadAllBytes($AsarPath) $verifySlice = $verifyBytes[$index..($index + $newBytes.Length - 1)] $verifyText = [System.Text.Encoding]::ASCII.GetString($verifySlice) if (-not $verifyText.StartsWith($replacement)) { Write-Host "Error: verification failed; unexpected content after write." Wait-Key exit 1 } Write-Host "Success: old layout locked." Wait-Key
Reacted by 0-SamboNZ-0, 理查德, qingfengguoyun, QQQ27, beliefchen398, Meepo268, Axel Po, Kirill Lebedinov, Liu Peng and ANXETYReacted by LAOReacted by Meepo268Keep legacy layout option
If the new layout is used, I never want to use it again.Reacted by 0-SamboNZ-0, kapaandyy, 理查德, qingfengguoyun, ellamas-grupoarpada, Axel Po, tommy-codez, beliefchen398, Magnus Shaffer, ZenDevMaster and 2 moreWe strongly demand that the old layout be restored. Ever since it was changed to the new layout, my colleagues and I have all given up on opencode.
Reacted by 0-SamboNZ-0, 理查德, qingfengguoyun, Axel Po, beliefchen398 and Kirill LebedinovI wrote a PowerShell script that replaces the new layout detection logic inside the packaged
asarof opencode, forcing it to use the old layout.Beautiful! Works perfectly! Thank you for sharing!
Reacted by 理查德 and qingfengguoyun写了个powersell脚本,替换opencode打包后的
asar里面的新布局判断逻辑强制使用旧布局,执行powershell,输入asar的文件夹,通常是:C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resources注意替换路径里面的用户名,脚本会自动备份当前的asar,执行完成后重启opencode即可,支持 1.18.30 版本 注意:每次更新opencode后需要重新执行I wrote a PowerShell script that replaces the new layout detection logic inside the packaged
asarof opencode, forcing it to use the old layout. Run PowerShell and enter the asar folder, which is usually:C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resourcesNote: replace the username in the path. The script will automatically back up the current asar. After it finishes, just restart opencode. It supports version 1.18.30. Note: You need to run it again after each opencode update.script
generated by ai
Copy and save it as a .ps1 file, then run it.
$ErrorActionPreference = "Stop"
Wait for a key press so the console window does not close before the result is visible.
function Wait-Key {
Write-Host ""
Write-Host "Press any key to exit ..."
[void][Console]::ReadKey($true)
}---------- Step 0: locate app.asar ----------
Write-Host "[1/8] Resolve app.asar path from the folder you provide ..."
$inputPath = Read-Host "Enter the opencode install folder"
$inputPath = $inputPath.Trim().Trim('"', "'")if ([string]::IsNullOrWhiteSpace($inputPath)) {
Write-Host "Error: path cannot be empty."
Wait-Key
exit 1
}if (-not (Test-Path -LiteralPath $inputPath -PathType Container)) {
Write-Host "Error: folder not found: $inputPath"
Wait-Key
exit 1
}只接受文件夹:优先找其下的 resources\app.asar,其次文件夹内直接存在的 app.asar
$item = Get-Item -LiteralPath $inputPath
$candidates = @(
(Join-Path $item.FullName "resources\app.asar"),
(Join-Path $item.FullName "app.asar")
)
$AsarPath = $null
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
$AsarPath = $candidate
break
}
}
if (-not $AsarPath) {
Write-Host "Error: no app.asar found under: $($item.FullName)"
Write-Host "Expected at 'resources\app.asar' or 'app.asar'."
Wait-Key
exit 1
}
Write-Host " app.asar: $AsarPath"---------- Step 1: prepare equal-length replacement ----------
Write-Host "[2/8] Building equal-length replacement string ..."
$target = "if (layoutUpgrade()) return true;"
$replacement = "return false;"
$pad = " " * ($target.Length - $replacement.Length)
$newString = $replacement + $padEqual-length invariant must hold, otherwise the ASAR header offsets break.
if ($newString.Length -ne $target.Length) {
Write-Host "Error: equal-length invariant broken."
Wait-Key
exit 1
}
Write-Host " target len: $($target.Length) / replacement len: $ ($newString.Length)"---------- Step 2: read bytes ----------
Write-Host "[3/8] Reading bytes from $AsarPath ..."
$targetBytes = [System.Text.Encoding]::ASCII.GetBytes($target)
$newBytes = [System.Text.Encoding]::ASCII.GetBytes($newString)
$bytes = [System.IO.File]::ReadAllBytes($AsarPath)
Write-Host " read $($bytes.Length) bytes"Naive scan for a byte sub-sequence; good enough for a short needle.
function Find-Bytes {
param(
[byte[]]$Data,
[byte[]]$Pattern,
[int]$StartIndex = 0
)
$last = $Data.Length - $Pattern.Length
for ($i = $StartIndex; $i -le $last; $i++) {
if ($Data[$i] -ne $Pattern[0]) { continue }
$ok = $true
for ($j = 1; $j -lt $Pattern.Length; $j++) {
if ($Data[$i + $j] -ne $Pattern[$j]) {
$ok = $false
break
}
}
if ($ok) { return $i }
}
return -1
}---------- Step 3: search for target ----------
Write-Host "[4/8] Searching for target string ..."
$index = Find-Bytes -Data $bytes -Pattern $targetBytesif ($index -lt 0) {
Write-Host " Target string not found; maybe already locked."
Wait-Key
exit 0
}
Write-Host " found at offset $index"Ensure the match is unique so we never patch the wrong spot.
$second = Find-Bytes -Data $bytes -Pattern $targetBytes -StartIndex ($index + 1)
if ($second -ne -1) {
Write-Host "Error: multiple matches found (offset $second); aborting."
Wait-Key
exit 1
}---------- Step 4: backup ----------
Write-Host "[5/8] Creating backup ..."
$bakPath = [System.IO.Path]::ChangeExtension($AsarPath, "asar.bak")
if (-not (Test-Path -LiteralPath $bakPath)) {
Copy-Item -LiteralPath $AsarPath -Destination $bakPath
Write-Host " Backup created: $bakPath"
}
else {
Write-Host " Backup exists, skip: $bakPath"
}---------- Step 5: patch in memory ----------
Write-Host "[6/8] Applying equal-length replacement ..."
$originalLength = $bytes.LengthOverwrite the matched region with the equal-length replacement.
[System.Array]::Copy($newBytes, 0, $bytes, $index, $newBytes.Length)
if ($bytes.Length -ne $originalLength) {
Write-Host "Error: array length changed; aborting before write."
Wait-Key
exit 1
}---------- Step 6: write back ----------
Write-Host "[7/8] Writing patched bytes back ..."
[System.IO.File]::WriteAllBytes($AsarPath, $bytes)---------- Step 7: verify ----------
Write-Host "[8/8] Verifying patched region ..."
$verifyBytes = [System.IO.File]::ReadAllBytes($AsarPath)
$verifySlice = $verifyBytes[$index..($index + $newBytes.Length - 1)]
$verifyText = [System.Text.Encoding]::ASCII.GetString($verifySlice)
if (-not $verifyText.StartsWith($replacement)) {
Write-Host "Error: verification failed; unexpected content after write."
Wait-Key
exit 1
}Write-Host "Success: old layout locked."
Wait-KeyWorks! Thanks!
Made opencode check for security, and it added timestamped .bak (just in case of repetition):
$ErrorActionPreference = "Stop"
Wait for a key press so the console window does not close before the result is visible.
function Wait-Key {
Write-Host ""
Write-Host "Press any key to exit ..."
[void][Console]::ReadKey($true)
}function Find-Bytes {
param(
[byte[]]$Data,
[byte[]]$Pattern,
[int]$StartIndex = 0
)$last = $Data.Length - $Pattern.Length for ($i = $StartIndex; $i -le $last; $i++) { if ($Data[$i] -ne $Pattern[0]) { continue } $match = $true for ($j = 1; $j -lt $Pattern.Length; $j++) { if ($Data[$i + $j] -ne $Pattern[$j]) { $match = $false break } } if ($match) { return $i } } return -1}
Write-Host "[1/8] Resolve app.asar path from the folder you provide ..."
$inputPath = Read-Host "Enter the opencode install folder"
$inputPath = $inputPath.Trim().Trim([char[]]@([char]34, [char]39))if ([string]::IsNullOrWhiteSpace($inputPath)) {
Write-Host "Error: path cannot be empty."
Wait-Key
exit 1
}if (-not (Test-Path -LiteralPath $inputPath -PathType Container)) {
Write-Host "Error: folder not found: $inputPath"
Wait-Key
exit 1
}$item = Get-Item -LiteralPath $inputPath
$candidates = @(
(Join-Path $item.FullName "resources\app.asar"),
(Join-Path $item.FullName "app.asar")
)$asarPath = $null
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
$asarPath = $candidate
break
}
}if (-not $asarPath) {
Write-Host "Error: no app.asar found under: $($item.FullName)"
Write-Host "Expected at 'resources\app.asar' or 'app.asar'."
Wait-Key
exit 1
}
Write-Host " app.asar: $asarPath"Write-Host "[2/8] Building equal-length replacement string ..."
$target = "if (layoutUpgrade()) return true;"
$replacement = "return false;"
$paddingLength = $target.Length - $replacement.Lengthif ($paddingLength -lt 0) {
Write-Host "Error: replacement is longer than target."
Wait-Key
exit 1
}$newString = $replacement + (" " * $paddingLength)
if ($newString.Length -ne $target.Length) {
Write-Host "Error: equal-length invariant broken."
Wait-Key
exit 1
}
Write-Host " target len: $($target.Length) / replacement len: $ ($newString.Length)"Write-Host "[3/8] Reading bytes from $asarPath ..."
$targetBytes = [System.Text.Encoding]::ASCII.GetBytes($target)
$newBytes = [System.Text.Encoding]::ASCII.GetBytes($newString)
$bytes = [System.IO.File]::ReadAllBytes($asarPath)
Write-Host " read $($bytes.Length) bytes"Write-Host "[4/8] Searching for target string ..."
$index = Find-Bytes -Data $bytes -Pattern $targetBytes
if ($index -lt 0) {
Write-Host " Target string not found; no changes were made."
Wait-Key
exit 0
}
Write-Host " found at offset $index"$second = Find-Bytes -Data $bytes -Pattern $targetBytes -StartIndex ($index + 1)
if ($second -ne -1) {
Write-Host "Error: multiple matches found (second offset: $second); aborting."
Wait-Key
exit 1
}Write-Host "[5/8] Creating timestamped backup ..."
$timestamp = Get-Date -Format "yyyyMMddHHmmss"
$backupPath = "$asarPath.$timestamp.bak"if (Test-Path -LiteralPath $backupPath) {
Write-Host "Error: backup already exists: $backupPath"
Wait-Key
exit 1
}Copy-Item -LiteralPath $asarPath -Destination $backupPath
Write-Host " Backup created: $backupPath"Write-Host "[6/8] Applying equal-length replacement ..."
$originalLength = $bytes.Length
[System.Array]::Copy($newBytes, 0, $bytes, $index, $newBytes.Length)if ($bytes.Length -ne $originalLength) {
Write-Host "Error: array length changed; aborting before write."
Wait-Key
exit 1
}try {
Write-Host "[7/8] Writing patched bytes back ..."
[System.IO.File]::WriteAllBytes($asarPath, $bytes)Write-Host "[8/8] Verifying patched file ..." $verifyBytes = [System.IO.File]::ReadAllBytes($asarPath) if ($verifyBytes.Length -ne $originalLength) { throw "File length changed after write." } $verifySlice = $verifyBytes[$index..($index + $newBytes.Length - 1)] $verifyText = [System.Text.Encoding]::ASCII.GetString($verifySlice) if ($verifyText -ne $newString) { throw "Unexpected content in the patched region." }}
catch {
Write-Host "Error: patch failed:$($ .Exception.Message)"
Write-Host " Restoring backup ..."
try {
Copy-Item -LiteralPath $backupPath -Destination $asarPath -Force
Write-Host " Original file restored."
}
catch {
Write-Host "CRITICAL: automatic restore failed: $($.Exception.Message)"
Write-Host " Restore manually from: $backupPath"
}Wait-Key exit 1}
Write-Host "Success: old layout locked."
Write-Host "Backup: $backupPath"
Wait-KeyReacted by 理查德Thank you so much for your script!!!!!
Reacted by 理查德Guys WTH!
We really need to patch it for old (NORMAL) layout? I can't image who can work with this new dumb's interface.Reacted by daudef, 0-SamboNZ-0 and 理查德I HATE the new layout. I stayed on an old version for quite a while for the old layout, and then forgot one day and updated. HATE IT. PLEASE add a toggle to restore the old layout. The new one is AWFUL.
Reacted by Eugene, daudef, Kyle Anderson, 0-SamboNZ-0 and 理查德The recent change to force the new layout completely breaks the mental model that the previous layout encouraged. I no longer have easy visibility over which one of my projects has active agent activity. I'm left to try to infer the activity from the thread's title, which might not always be relevant or accurate. This adds friction and confusion that is completely unnecessary.
The old layout needs to stay as a configurable option in order to support the workflows we've grown to love as we've used this tool.
Reacted by 0-SamboNZ-0 and 理查德写了个powersell脚本,替换opencode打包后的
asar里面的新布局判断逻辑强制使用旧布局,执行powershell,输入asar的文件夹,通常是:C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resources注意替换路径里面的用户名,脚本会自动备份当前的asar,执行完成后重启opencode即可,支持 1.18.30 版本 注意:每次更新opencode后需要重新执行我编写了一个 PowerShell 脚本,用于替换 opencode 包中
asar部分的新的布局检测逻辑,使其使用旧的布局方式。请运行 PowerShell 脚本,然后进入 ASAR 文件夹,通常该文件夹位于:C:\Users\%Username%\AppData\Local\Programs\@opencode-aidesktop\resources注意:请更换路径中的用户名。该脚本会自动备份当前的 asar 文件。完成后,只需重新启动 opencode 即可。该脚本支持版本 1.18.30。 注意:每次更新 OpenCode 后,都需要重新运行该程序。脚本
由人工智能生成
将文件复制并保存为.ps1 格式的文件,然后运行它。
$ErrorActionPreference = "Stop"
Wait for a key press so the console window does not close before the result is visible.
function Wait-Key {
Write-Host ""
Write-Host "Press any key to exit ..."
[void][Console]::ReadKey($true)
}---------- Step 0: locate app.asar ----------
Write-Host "[1/8] Resolve app.asar path from the folder you provide ..."
$inputPath = Read-Host "Enter the opencode install folder"
$inputPath = $inputPath.Trim().Trim('"', "'")if ([string]::IsNullOrWhiteSpace($inputPath)) {
Write-Host "Error: path cannot be empty."
Wait-Key
exit 1
}if (-not (Test-Path -LiteralPath $inputPath -PathType Container)) {
Write-Host "Error: folder not found: $inputPath"
Wait-Key
exit 1
}只接受文件夹:优先找其下的 resources\app.asar,其次文件夹内直接存在的 app.asar
$item = Get-Item -LiteralPath $inputPath
$candidates = @(
(Join-Path $item.FullName "resources\app.asar"),
(Join-Path $item.FullName "app.asar")
)
$AsarPath = $null
foreach ($candidate in $candidates) {
if (Test-Path -LiteralPath $candidate -PathType Leaf) {
$AsarPath = $candidate
break
}
}
if (-not $AsarPath) {
Write-Host "Error: no app.asar found under: $($item.FullName)"
Write-Host "Expected at 'resources\app.asar' or 'app.asar'."
Wait-Key
exit 1
}
Write-Host " app.asar: $AsarPath"---------- Step 1: prepare equal-length replacement ----------
Write-Host "[2/8] Building equal-length replacement string ..."
$target = "if (layoutUpgrade()) return true;"
$replacement = "return false;"
$pad = " " * ($target.Length - $replacement.Length)
$newString = $replacement + $padEqual-length invariant must hold, otherwise the ASAR header offsets break.
if ($newString.Length -ne $target.Length) {
Write-Host "Error: equal-length invariant broken."
Wait-Key
exit 1
}
Write-Host " target len: $($target.Length) / replacement len: $ ($newString.Length)"---------- Step 2: read bytes ----------
Write-Host "[3/8] Reading bytes from $AsarPath ..."
$targetBytes = [System.Text.Encoding]::ASCII.GetBytes($target)
$newBytes = [System.Text.Encoding]::ASCII.GetBytes($newString)
$bytes = [System.IO.File]::ReadAllBytes($AsarPath)
Write-Host " read $($bytes.Length) bytes"Naive scan for a byte sub-sequence; good enough for a short needle.
function Find-Bytes {
param(
[byte[]]$Data,
[byte[]]$Pattern,
[int]$StartIndex = 0
)
$last = $Data.Length - $Pattern.Length
for ($i = $StartIndex; $i -le $last; $i++) {
if ($Data[$i] -ne $Pattern[0]) { continue }
$ok = $true
for ($j = 1; $j -lt $Pattern.Length; $j++) {
if ($Data[$i + $j] -ne $Pattern[$j]) {
$ok = $false
break
}
}
if ($ok) { return $i }
}
return -1
}---------- Step 3: search for target ----------
Write-Host "[4/8] Searching for target string ..."
$index = Find-Bytes -Data $bytes -Pattern $targetBytesif ($index -lt 0) {
Write-Host " Target string not found; maybe already locked."
Wait-Key
exit 0
}
Write-Host " found at offset $index"Ensure the match is unique so we never patch the wrong spot.
$second = Find-Bytes -Data $bytes -Pattern $targetBytes -StartIndex ($index + 1)
if ($second -ne -1) {
Write-Host "Error: multiple matches found (offset $second); aborting."
Wait-Key
exit 1
}---------- Step 4: backup ----------
Write-Host "[5/8] Creating backup ..."
$bakPath = [System.IO.Path]::ChangeExtension($AsarPath, "asar.bak")
if (-not (Test-Path -LiteralPath $bakPath)) {
Copy-Item -LiteralPath $AsarPath -Destination $bakPath
Write-Host " Backup created: $bakPath"
}
else {
Write-Host " Backup exists, skip: $bakPath"
}---------- Step 5: patch in memory ----------
Write-Host "[6/8] Applying equal-length replacement ..."
$originalLength = $bytes.LengthOverwrite the matched region with the equal-length replacement.
[System.Array]::Copy($newBytes, 0, $bytes, $index, $newBytes.Length)
if ($bytes.Length -ne $originalLength) {
Write-Host "Error: array length changed; aborting before write."
Wait-Key
exit 1
}---------- Step 6: write back ----------
Write-Host "[7/8] Writing patched bytes back ..."
[System.IO.File]::WriteAllBytes($AsarPath, $bytes)---------- Step 7: verify ----------
Write-Host "[8/8] Verifying patched region ..."
$verifyBytes = [System.IO.File]::ReadAllBytes($AsarPath)
$verifySlice = $verifyBytes[$index..($index + $newBytes.Length - 1)]
$verifyText = [System.Text.Encoding]::ASCII.GetString($verifySlice)
if (-not $verifyText.StartsWith($replacement)) {
Write-Host "Error: verification failed; unexpected content after write."
Wait-Key
exit 1
}Write-Host "Success: old layout locked."
Wait-Key太棒了,实测成功回复旧版布局!!!
Reacted by 理查德 and 0-SamboNZ-0I had AI generate a macOS version
restore-old-ui.shReacted by 理查德, 0-SamboNZ-0, aa2013 and fpcxsunReacted by u.wangThe new UI is a significant regression for my workflow. I find it less clear, less efficient, and more difficult to navigate than the previous interface.
The persistent left sidebar was extremely useful for quickly switching between projects, workspaces, and sessions. Removing it makes everyday work slower and more frustrating. The new layout feels especially unsuitable when working with multiple projects or sessions.
What is most disappointing is that users are not even given a choice. Forcing everyone onto the new UI without an option to continue using the legacy interface is not acceptable, particularly while many users clearly prefer the old layout.
Please restore the legacy UI as an optional layout, or at minimum provide a permanent setting to enable the persistent left sidebar. Users should be able to choose the interface that best fits their workflow
Reacted by 理查德, 0-SamboNZ-0, Kyle Anderson, ZenDevMaster, beliefchen398, David Bold and Alberto G. RodriguezNew version 1.18.31 - script doesnt work - problems in UI
New version 1.18.31 - script doesnt work - problems in UI
It's working for me on that version @pav18
What problems are you having?
What problems are you having?
Sorry, you right. My fault - also changed opencode.json with mistake
Reacted by 理查德I just moved to opencode from other companies, not having a unified view of my sessions / projects drives me crazy. Having to ctrl+b to go to the home then click through each project individually to see my sessions is a huge pain. I run multiple sessions across multiple projects.
Old UI was definitely more convenient for me.
The script works for now, but asking an agent to rewrite/adapt it is also possible if necessary.
Reacted by 理查德 and David BoldAlso affected — Desktop v2.0.16 on macOS (Apple silicon, zh-CN UI).
I usually keep a lot of sessions open across several projects. The old two-pane layout (persistent session list on the left, active session on the right) was the most useful part of my workflow: one glance for an overview, one click to switch. In the new top-tab layout, that overview disappears as soon as I have more than a few sessions open; long titles get truncated, and switching always takes extra navigation.
I also noticed
Settings → Appearance → Tabs: Vertical, which is closer to the old workflow — but I'd still love a real persistent left sidebar that lists sessions (ideally grouped by project), with the current session on the right.Please keep a persistent left sidebar available as an option in the new layout. It doesn't have to be the default, and keeping tabs for people who prefer them is perfectly fine. Thanks!
Environment: OpenCode Desktop v2.0.16, macOS (Apple silicon), zh-CN UI.
Reacted by 理查德I strongly agree that the legacy layout should remain available as an officially supported option while OpenCode continues to receive the latest updates.
I regularly switch between several projects and sessions during the day. The previous persistent project and session navigation made it easy to see what was open and move directly to the right context. In the new layout, that overview is less clear and common switches take more clicks, so I spend more time navigating the UI and less time working with the coding agent.
I do not want to downgrade OpenCode to get that workflow back; I still want the latest fixes, provider updates, local LLM improvements, and agent features. A simple setting such as “Use legacy layout” would let users choose the layout that works best for them. If maintaining the full legacy UI is not practical, an always-visible, pinnable project/session sidebar with readable session titles would address the main usability regression.
Reacted by 理查德This is exactly what I wanted too (the classic two-panel sidebar), so while the feature is being built I made a local workaround that restores it on Windows Desktop:
https://github.com/davidlopezsalvador/opencode-legacy-patch
How it works: a same-length patch inside
app.asarmovesoldInterfaceSunsetto 2099, soresolveNewLayoutDesigns(retired, ...)stops hard-coding the new layout, andapp-update.ymlis neutralized so the auto-updater cannot silently wipe the patch.- Tested on 1.18.34 -> 1.18.35 (Electron 42, Windows); applies with the app open in ~2 minutes; one-command rollback (
-Mode restore). - Fail-safe: the script searches the literal dynamically and aborts unless it finds exactly 1 occurrence.
- Caveats: Windows desktop only; updates frozen until you
restore -> update -> patch(script included); unofficial, use at your own risk.
I maintain the repo, feedback welcome. This is only a stopgap until a permanent official toggle ships.
Reacted by 理查德- Tested on 1.18.34 -> 1.18.35 (Electron 42, Windows); applies with the app open in ~2 minutes; one-command rollback (





Feature hasn't been suggested before.
Describe the enhancement you want to request
The recent sidebar redesign (#20242) replaced the classic two-panel layout
(persistent left sidebar + session panel) with a Codex-style unified view.
For me this is not a visual refresh — it removed the single most important
part of my daily workflow, and there is currently no way to go back.
What I want changed / added:
option (a
legacy/classicUI toggle or config flag is enough — itdoesn't have to be the default).
always-visible left sidebar — not hover-only menus or a collapsed rail
that requires extra clicks.
Why this matters (the benefit):
I live inside opencode for hours every day. The left sidebar gives me an
at-a-glance, persistent view of my projects, workspaces, and sessions:
The new interface hides all of this behind a single unified list with
hover/focus interactions. Every session feels slower and more disoriented.
This is not an aesthetic preference — removing the sidebar with no escape
hatch is a functional regression for anyone who relies on it.
Related:
Happy to provide screenshots, my opencode version, and platform details if
that helps. Please don't leave those of us who depend on the left sidebar
behind.