Skip to content

Side pane review fixes: animation, topic scope, dormancy, side chat safety, terminal links, CI gate - #4

Merged
RoxyAsahi merged 215 commits into
pr/side-pane-workbenchfrom
claude/project-thread-1zv9nc
Oct 7, 2026
Merged

RoxyAsahi merged 215 commits into
pr/side-pane-workbenchfrom
claude/project-thread-1zv9nc

Conversation

@RoxyAsahi

@RoxyAsahi RoxyAsahi commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

Requested by Roxy · project thread

Before:

  • Collapsing the side pane and reopening it within 240ms could leave it collapsed, with the expand button hidden.
  • With a group selected, "close all" closed, and so deleted, the side chats of other conversations.
  • Once about eight side chats were open, switching away from a browser or plan tab put it to sleep at once, and the browser reloaded every time. A browser tab also reloaded after five minutes hidden or five minutes collapsed.
  • Closing a side chat silently deleted its whole child topic.
  • Clicking a button inside a side chat reply while another reply was streaming started a second send and lost the stop button. With the composer idle, the same click overwrote the typed draft.
  • Typing Chinese into the plan filter was interrupted on every keystroke.
  • OSC 8 links in the terminal opened through a confirm dialog and window.open in a bare window.
  • Ctrl+C with a selection interrupted the shell shared with the AI.
  • The side pane tests ran in no CI job.

After: each of these behaves as in ZCode/DSH. Reopening mid-animation continues from the current width. Topic tabs only show, and only close, inside their own conversation. Side chats don't count toward the live-view limit. Browser tabs only sleep when that limit is exceeded, and the current tab stays awake while the pane is collapsed. Closing a side chat that has messages asks first. A message button respects the busy gate and leaves the draft alone. The filter waits for the input method's composition to end. Terminal links open http(s) only, in the side browser. Ctrl+C copies the selection. npm run test:side-pane runs in the Chat Kernel and UI job.

How: each fix has a JSDOM behaviour test that fails on the old code. Existing tests that opened topic tabs without a current conversation, or that relied on collapsing the pane to put a tab to sleep, now set the parent or switch tabs first. Tab-id lookup and tab-strip arrow-key focus were left to #3, which fixes the same thing. This branch merges cleanly with #3, and the combined test:side-pane passes 451/451 locally.

The findings and the order of the remaining fixes are in the project's review report (sidebar/side-pane-adversarial-review-20261007.md).

🤖 Generated with Claude Code

https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG

claude added 13 commits October 7, 2026 00:22
Code viewer tab ids carry the file path. On Windows the backslashes are
read as CSS escapes, so the selector never matched and keyboard focus
fell to <body> after cycling to, closing, or leaving the menu of such a
tab; a path containing a quote made the selector throw and the mount fail.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
… literal colour

check:ui-system was red on the side pane branch. tool-presentation.css had
101 selectors outside .vcp-ui-scope, an !important on the reduced-motion
rule and two fixed 11px sizes; components.css (from upstream lioensky#234) fell back
to a hard-coded indigo because --vcp-ui-primary is not a token.

Every tool presentation selector now sits under .vcp-ui-scope (the chat
messages container already carries it, and the file is alone in its layer,
so the uniform specificity bump changes no cascade). The reduced-motion rule
reuses the shimmer selector instead of !important, the 11px sizes use
--vcp-ui-text-xs, and the pinned window button uses the accent token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCGGgmHsCToooXTgSXpmq9
Before, a provider that threw during mount (a corrupt restored payload,
a failed lazy import, a bug) left its tab selected over a blank pane,
with the error only in the console. The view now says the tab could not
open, shows the error, and offers a retry; showing the tab again still
retries as before, and closing the tab removes the error view.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
…wn with it

initWorkspaceSidePane ran inside the main startup try block ahead of the
filter manager and setupEventListeners, so any exception while building
the side pane skipped the rest of startup and left the send button and
the other main window bindings dead. It now runs in its own try/catch
and reports the failure as a toast.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
A file:// page in the side browser runs with Electron's default file
privileges, so its script could fetch other local files (SSH keys,
VCPChat settings) and post them out. Like DeepSeek Harness
(browser-guests.ts onBeforeRequest) and ZCode (isAllowedBrowserUrl),
the side browser now only opens http, https and about pages, and its
session cancels file: and other non-web subresource requests.

Any page could also start downloads on its own, and each one opened the
system browser. A download now reaches the system browser only right
after a real input on that page, one per input, the same rule popups use.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
…sible focus ring

- Arrow keys re-rendered the strip and then focused the removed button,
  while activateTab moved focus into the panel, so each press threw a
  keyboard user out of the tablist. Keyboard switches now activate
  without moving focus and refocus the new button by id, as Radix Tabs
  (used by ZCode) does.
- While the new-tab page or hidden notifications was active, no tab had
  tabindex=0, so the strip had no Tab stop. The first tab now gets it.
- Delete closes the focused closable tab.
- Tabs and header buttons removed the outline with no replacement; they
  now get the same focus ring as the tab menu items.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
render() rebuilds the whole page, and it only put focus back on filter
inputs and page tabs. Toggling a section or a batch, opening filters,
picking a contributor, or any background refresh dropped focus to
<body>. Interactive buttons now carry a data-focus-key that render()
records and restores, the imperative equivalent of React keeping keyed
nodes in ZCode and DSH. Clearing the filters lands on the search box.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
Shift+F10 on a change row opened the menu at (0,0) with focus left on
the row, no arrow navigation, and Escape dropped focus. Like the Radix
ContextMenu in ZCode's GitPaneChangeCard, the menu now opens beside the
row when there is no pointer, takes focus, moves with the arrow keys,
Home and End, and Escape, Tab or choosing an item returns focus to the
row. Icon ligature text is hidden from screen readers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
The button called openPythonAttachmentInTextEditor, which only accepts
.py file URLs on Windows, so it failed silently for every file. Sending
the path to open-external-link instead would hand model-supplied paths
(.bat, .lnk) to shell.openExternal. The button now reveals workspace
files through the workspace-checked git:reveal-path and shows the path
for anything else.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
…keys stop

Each arrow, Home or End press wrote settings over IPC, about 30 writes a
second while an arrow key was held. The width still applies on every
press; the save now waits until the keys stop for 400 ms (the debounced
onLayoutChange ZCode's resizable.tsx uses), and runs at once when focus
leaves the handle or the pane is torn down. Pointer drags save on
release as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
…focus hold under real use

- Reopening while the close animation runs no longer leaves the pane collapsed with
  its expand button hidden; a repeated open no longer jumps back to zero width.
- With no current conversation (group selected, early startup) topic tabs of other
  conversations stay off the strip, so "close all" can no longer delete their side chats.
- Keep views (side chats) no longer take places under the live-view limit; browser tabs
  sleep only for the limit, not for being hidden; collapsing the pane no longer puts the
  current tab to sleep.
- Arrow keys on the tab strip keep focus on the tab; tab ids with Windows paths or
  quotes are looked up without building selectors.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
The 430-odd side pane, side chat, git and browser tests were not in any workflow;
test:side-pane gathers them and the Chat Kernel and UI job runs it. The PTY-backed
terminal handler tests need a real PowerShell and stay out until a Windows job exists.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
…en and re-show in constant time

Measured in the real Electron app (headless, 6000-line JS file): opening went
from 1215ms to 330ms and switching back to the tab from 752ms to ~110ms with no
long task. Before, focusing the re-shown view forced a full style and layout
pass over ~60k highlight nodes; now only the chunks in the viewport take part.

Gutter and code are split into 200-line blocks with content-visibility:auto and
an intrinsic height from the fixed 18px line height, so numbers stay aligned.
highlight.js still highlights the whole text once; its output is split per line
with spans that cross lines closed and reopened, so multi-line comments and
template strings keep their colours.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017bCbRB3E9yajQ3HbdMrjiJ
@RoxyAsahi RoxyAsahi self-assigned this Oct 7, 2026
claude added 7 commits October 7, 2026 00:42
File paths shown as changed files come from the model's tool-call
arguments, and the viewer read any of them through get-text-content,
so a prompt-injected path such as ~/.ssh/id_rsa showed its content, one
click from being inserted back into the chat. Like the workspace-bound
previews in ZCode and DeepSeek Harness, a path outside every registered
workspace now shows where it is and is only read after the user clicks
to read it. Workspace files are read as before; windows without the
workspace service keep the old behaviour.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
git status runs whatever command a repository's .git/config names in
core.fsmonitor. A project unzipped with a prepared .git folder could
therefore run code as soon as the Git page read its status. Every git
call now passes -c core.fsmonitor=false, as DeepSeek Harness does in
scripts/change-scope.ts.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
… literal colour

check:ui-system was red on the side pane branch. tool-presentation.css had
101 selectors outside .vcp-ui-scope, an !important on the reduced-motion
rule and two fixed 11px sizes; components.css (from upstream lioensky#234) fell back
to a hard-coded indigo because --vcp-ui-primary is not a token.

Every tool presentation selector now sits under .vcp-ui-scope (the chat
messages container already carries it, and the file is alone in its layer,
so the uniform specificity bump changes no cascade). The reduced-motion rule
reuses the shimmer selector instead of !important, the 11px sizes use
--vcp-ui-text-xs, and the pinned window button uses the accent token.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCGGgmHsCToooXTgSXpmq9
…pect the busy gate, the filter accepts input-method typing

- Closing a side chat that has messages deletes its child topic, so it now asks first
  (also for close others / close all); declining keeps the tab.
- A second send while a reply streams is refused, so the running reply keeps its stop
  button. A message button no longer overwrites or sends a typed draft, references or
  attachments.
- The plan history filter no longer rebuilds the page while an input method composes, so
  Chinese input works; the search runs once composition ends.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
…ection copies

- xterm's default OSC 8 handler showed a confirm and called window.open from the main
  window, which opened a bare window on the default session. The tab now passes a
  linkHandler that only opens http(s) through the side browser, as ZCode does.
- Ctrl/Cmd+C with a selection copies it instead of sending ^C to the PTY shared with the
  AI tools; without a selection Ctrl+C still interrupts.
- Ctrl/Cmd+Alt+B and Ctrl+PageUp/PageDown drive the side pane only and are no longer
  written to the shell as escape codes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
…fix branch

claude/project-thread-20fk2r (PR #3) already finds tabs by id without selectors and keeps
arrow-key focus on the strip, with its own tests. Drop the duplicate here so the two
branches do not conflict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
Keeps the change clear of the focus-restore lines the parallel fix branch edits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
@RoxyAsahi RoxyAsahi changed the title Side pane review fixes: animation, topic scope, dormancy, tab focus, CI gate Side pane review fixes: animation, topic scope, dormancy, side chat safety, terminal links, CI gate Oct 7, 2026
claude added 4 commits October 7, 2026 00:48
PR #4 adds the same tab lookup as findByTabId(container, selector,
tabId). Use the same signature, text and keyboard comments so the two
branches merge without a semantic clash, and rename this branch's
keyboard test so the files don't collide.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCGGgmHsCToooXTgSXpmq9

RoxyAsahi commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner Author

I merged this into integration branch #2 at 7b4d765 (merge 8c303f6), after #3. Tab lookup and strip focus now come from #3, which uses your signature. Your ArrowLeft-from-new-tab fix and the test:side-pane CI step both carried over. Every gate passes on the merged branch, including test:side-pane at 451/451. I also checked where your OSC 8 links meet #3's browser hardening: the linkHandler only passes http(s) through to the side browser, so file: links never reach it. Nothing to change.


Generated by Claude Code

Choosing an item hid the focused menu button and dropped focus to body;
closing a background tab left nothing to return to. Tab now closes the
menu like Radix Menu (ZCode uses Radix ContextMenu), and after an action
focus goes back to the tab, or to the active tab if that one was closed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
claude added 29 commits October 7, 2026 06:34
…start

The 20ms hide timer runs from the second openTab, so on a loaded machine
the probe slept before h.busy.add ran. Under 12-way parallel runs it
failed 23 of 48; with the busy mark first, 0 of 48. CI runs this file
through test:side-pane since 8d2d429.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
30f09c5 typed `cd '…'` without Enter so a foreground vim or password
prompt would not receive it. But AI commands are written into the same
PTY without clearing the input line (PowerShellExecutor writes
`${wrappedCommand}\r`), so a jump left unsubmitted is joined to the next
AI command. Under a real PTY on Linux, terminal-handlers.test.js "several
views share one session" fails with `cd '/tmp/…'echo vcp-shared-…` ->
`bash: cd: too many arguments`; on Windows the end marker never prints
and the AI call waits for its timeout. Restoring `\r` makes that test
pass again (only the Windows-only command-run case still fails here).

Curly-quote doubling and the line-break refusal stay. The duplicate
quoting test in terminal-command-run-watch.test.js is folded into
terminal-cd-command.test.js.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
…scing

a1bc4c3 added two whole-worktree `git diff --numstat` runs to every
status read. On a VCPChat clone with 600 changed files that took getStatus
from 45ms to 220ms (status 40ms, numstat 181ms), and the cost was paid by
all nine internal callers: push and branch-switch prechecks, branch
listing and the status panel summary, which runs its own
`diff --numstat HEAD` on top. Counts are now read for getStatus (the Git
page) and for the statuses handed back to the renderer after a write.
ZCode reads numstat with status too (gitCliRepo.ts), so it stays there.

9f9a449's per-repository coalescing is removed. A caller that arrives
while a read is in flight waits for the next read, so the common pair
(status panel plus Git page) still ran two reads, just serially (434ms vs
about 220ms for one). Out-of-order writes back to the Git page are
already handled by 7d962eb's sequence number.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
17c29af skips re-extraction for a message id whose content length is
unchanged, so a message edited to the same length that now contains a
file write never got its file list. The cache now holds the content
string itself (the same string the history already holds) and compares
by identity, which is as cheap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
5a8e8fe resets the address bar to the page URL on blur. Chromium also
blurs the focused element when the whole window loses focus, so Alt-Tab
to copy a URL from another app cleared what was typed. The reset now
skips blurs where the document itself has no focus.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
Docs and contracts
- Provider contract in SIDE_PANE_ARCHITECTURE.md now matches the code:
  mount context (scope, occurrence, restoredState), every handle method
  the controller calls, and a lifecycle section with the invariants
  (one mount per id, zero view resources while dormant, what never
  sleeps, suspend/resume follows visibility, failure paths).
- Type-level requestClose, dormancy and load documented; 'detach' and
  isBusy examples describe what the code actually does.
- Stale comments fixed (tab strip, launcher, tab utils, controller maps,
  lazy provider list, nonexistent gitSideProvider.js, eager example).

Duplication
- Menu arrow/Home/End handling shared via moveMenuFocus (4 copies; the
  browser copy had drifted: ArrowUp with nothing focused skipped the
  last item).
- Controller visibility delivery and "focus unchanged" rule each in one
  helper.
- Tool output and trajectory search hints live only in their tab types.

Behaviour
- Terminal restart uses the app confirm dialog (native confirm blocks
  the window); a second click waits on the same dialog.
- Plan detail focus() moves focus into the tab instead of only reloading.

Dead code
- Unused toolOutputTabId / modelTrajectoryTabId exports, side chat
  handle setVisible, an unreachable branch and an unused local in
  side-pane-state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
…egistered

The chat and utility preloads are loaded by many windows (voice, assistant,
notes, forum...), and each side pane handler module checked the caller on
its own. ProjectForge's file revert and project delete checked nothing, and
workspaces only after the last fix. A single policy table now lists, per
preload domain, the app pages allowed to call (and the read-only channels
open to other windows). main.js applies it at registration: the domain
activator rejects a wrong window before loading the domain, and the directly
registered workspaces, ProjectForge and source modules get a guarded ipcMain.
Module-level checks stay as a second layer, as DSH asserts the sender per
privileged handler. A contract test calls every channel of every policy
domain from another window, a webview and a subframe, and checks main.js
routes every side pane domain through the policy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
ae9a99b coalesces terminal:data into one IPC per 16ms window, but the
window opened on the first chunk, so every key echo reached the screen
about 16ms late. The first chunk after a quiet period now goes out
immediately and opens the window; chunks inside it are batched as
before, and a window that had output opens the next one. ZCode forwards
every chunk (terminalService.ts); flood throughput keeps the batching.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
…harp

CI installs with --omit=optional, and loading the real fileManager pulls in
sharp, which failed the Chat Kernel and UI contracts job. The test only needs
the reference resolver registration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
collectAgentDraftConfig falls back to currentSelectedItemRef for an empty
name, and while switching Agents that ref already points at the next
Agent. Clearing A's name, switching to B and back, then saving renamed A
to B. The draft kept on switch now drops an empty name, so the restored
form uses the name on disk. settingsManager.js is a guarded boundary
file; its hash and rationale are updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KnTGLLVUgUjQG3f4cWBHJw
# Conflicts:
#	docs/contracts/generated/chat-event-graph.json
…n fixed

Conflicts follow #14: side chats keep dormancy 'keep'. Batch W's
close-confirm and pending-mount rules stay for tabs that still sleep.

# Conflicts:
#	docs/contracts/generated/chat-event-graph.json
#	modules/renderer/sideChatSurfaceOwner.js
…nd duplicates

- Content asserts (exact Chinese copy, class names, markup, icon ligatures) are
  replaced by state: data-status / data-state / data-empty-reason / data-reason /
  data-action hooks, aria state, status type/code, and round-tripped user values.
- Fixed-duration sleeps that raced debounces or timers now use node:test
  mock.timers (negative checks tick past the debounce) or the shared
  tests/helpers/wait-for.mjs; per-file waitFor copies are gone.
- Tests coupled to internals (resource labels, private options, storage keys,
  internal call order, monkeypatched queue internals) assert observable effects.
- Duplicates are folded into the surviving test; the vacuous dual-stream test
  now registers a real main-chat route.
- New test for the restore guard: side chats restored after the user moved to
  another tab stay in the background (previously untested).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012AP71nAxrCFEpH7st6asf9
…er owner

Every VCPLog toast registered its transitionend listener on the main
window's DOM listener owner, which lives for the whole session and only
released registrations on dispose. When the 500ms fallback removed the toast
before transitionend fired (or the toast was dismissed by the notification
page), the owner kept the listener and with it the detached toast forever:
about 27 DOM nodes and 3 listeners per notification in a real Electron run.

The owner now drops registrations that have finished (fired timeouts, fired
once listeners), its timer cancel closure no longer shares scope with the
callback, the toast's transitionend listener lives on the toast itself, and
toast timers are left to fire and no-op instead of being cleared from outside.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SADzqzusVz3S3z7qBUSy8M
…tes removed

Side chat sleep tests stay deleted per #14. Batch W's close-confirm
dormancy test now advances #15's mock clock instead of sleeping.
If the reply finished on its own while a slow interrupt request was
pending, the failed interrupt deleted it as if it were an empty
placeholder (side chat and main chat). Now only a still-streaming
placeholder is removed. If upstream accepts the interrupt but never
sends a terminal, the side chat stops locally after 3s and saves the
partial as cancelled instead of staying busy.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016hW799u5Ji5k9khDAwu688
…to claude/project-thread-1zv9nc

# Conflicts:
#	modules/ui-system/side-pane/git/git-view.js
#	modules/ui-system/side-pane/modelTrajectorySideProvider.js
States now go error, then loading, then empty (as ZCode GitPane does),
so a load in progress or a failed read is never shown as "nothing here":
- Git: no "还没有工作区 / 添加工作区" before the workspace list arrives
  or when it fails; the error has a retry.
- Code viewer picker: a failed workspace read says so; the file list
  error has an in-place retry.
- Trajectory: no "请先选择智能体和话题" flash while subscribing.
- Tool output: a failed command list shows the error with a retry
  instead of "还没有命令记录"; a rejected output read lands on the error
  bar instead of hanging on "加载中…"; a failed reload during a running
  command keeps the output on screen.
- Side chat: the intro stays hidden while history loads; the history
  retry works from the keyboard.

Layout and feedback:
- Git diff: an open diff keeps the previous text while an edited file
  is refetched instead of collapsing to one line; failed diffs are not
  cached and have a retry.
- Plan: retry buttons switch to "正在重试…" at once; a background
  refresh with a filter swaps results in place; filter errors retry.
- Code viewer: reloading a shown file keeps it until the new text
  arrives; its loading icon actually spins.

Docs: dormancy notes follow #14 (side chats stay mounted).
Tests for the new cases use waitFor instead of fixed sleeps.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
…t send owner tests in test:workbench

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UCGGgmHsCToooXTgSXpmq9
- Plan detail: a focus target from the status panel is applied once and
  then cleared from the saved tab, so restart or wake no longer jumps back.
- Side chat restore keeps the saved createdAt, so lists keep their order.
- A local-only side chat draft that cannot be read now warns the user
  instead of silently restoring blank.
- Width persistence logs a {success:false} settings save result.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
The trajectory tab sleeps after 5 minutes hidden. Waking it reset every
collapsed row and jumped the view to the bottom. It now captures its
expansion overrides, commands and scroll position, and restores them
when the remount still shows the same conversation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DFRovoZHMzeheXKkLNWKG
@RoxyAsahi
RoxyAsahi merged commit 2eb0892 into pr/side-pane-workbench Oct 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants