Desktop users need the same branch-from-here workflow that the CLI already exposed, so the branch creation logic now lives in a shared transcript utility and the desktop app routes completed message actions through the server API. The UI hydrates transcript ids after live completions so newly generated turns can be branched immediately without a refresh.
Constraint: Source sessions must remain unmodified while branch sessions inherit the active transcript chain and persistence metadata.
Rejected: Keep a desktop-only branch implementation | it would drift from CLI /branch semantics and duplicate transcript filtering rules
Confidence: high
Scope-risk: moderate
Directive: Do not remove the post-completion transcript hydration without a real-model desktop E2E for just-finished messages
Tested: bun run verify; Chrome Web UI E2E with real gpt-5.5 provider on ports 45678/45679
Not-tested: Provider-specific behavior beyond the configured Sub2API-ChatGPT route
Desktop sessions can receive task tools, background-agent lifecycle events, and child tool stream events in the same turn. The UI now keeps root Agent cards separate from task-management tools, preserves parent links when later stream events omit them, and surfaces completed background-agent result text from task notifications.
Constraint: Existing desktop session history can contain task-notification XML and live WS events with incomplete parent metadata.
Rejected: Render local_agent lifecycle messages as standalone cards | duplicates the Agent tool card and makes ownership unclear.
Confidence: high
Scope-risk: moderate
Directive: Do not collapse Agent and TaskCreate/TaskUpdate root tools into one generic group without checking multi-agent screenshots.
Tested: SKIP_INSTALL=1 PRESERVE_TAURI_TARGET=1 ./desktop/scripts/build-macos-arm64.sh
Tested: Real gpt-5.5 desktop E2E sessions under /tmp/cc-haha-subagent-e2e-20260519-025454 for 3 subagents, child tools, result dialog, and background Bash completion.
Not-tested: Full bun run verify gate.
Desktop receives task lifecycle events for local subagents in addition to the visible Agent tool call. Rendering those events as standalone transcript cards exposed internal task types such as local_agent and duplicated the Agent surface. The store now keeps agent lifecycle state for Agent tool notifications without inserting separate transcript background cards, while non-agent background work remains visible with user-facing labels.
The long workspace preview test now has a wider timeout so coverage mode does not fail a behaviorally passing case.
Constraint: CLI task events use internal task_type values such as local_agent and local_bash.
Rejected: Rename local_agent in the standalone card | still leaves duplicate Agent and task cards for one subagent.
Confidence: high
Scope-risk: moderate
Directive: Do not render local_agent or remote_agent as standalone transcript cards unless the Agent tool card no longer exists.
Tested: bun run verify passed=8 failed=0 skipped=2
Not-tested: Live provider-backed desktop session smoke.
The add-to-chat popover was keyed off the mouse-up location, so broad selections could cover the selected text and stale popovers could remain visible. Shared placement and dismissal logic now anchors the action to the selected range, prefers the space above the selection like Codex App, and clears stale selections on outside clicks.
Constraint: Desktop chat and workspace previews share the same selection affordance and must keep their existing add-reference behavior.
Rejected: Keep cursor-based placement | it can hide selected text and makes multi-line selections unpredictable.
Confidence: high
Scope-risk: moderate
Directive: Keep future selection actions range-anchored and outside-click dismissible across chat and workspace surfaces.
Tested: bun run check:desktop
Tested: Browser verification on http://127.0.0.1:5174 showed the popover above selection, no overlap, and outside click clearing it.
Not-tested: Native packaged Tauri window.
Related: https://github.com/NanmiCoder/cc-haha/issues/484
Goal sessions were showing stopped background-agent cards because the parent model used TaskStop after reading enough partial review output. That writes a killed task notification, which the desktop then rendered like a failure.
TaskStop and async agent launch guidance now make cancellation an exceptional action rather than a normal cleanup step. Stopped background-agent transcript cards also render as neutral interrupted events instead of error-styled failures.
Constraint: Background agent task notifications must remain truthful; killed tasks still surface as stopped instead of being hidden.
Rejected: Hide stopped notifications in /goal sessions | this would mask real user cancellations and runaway-task stops.
Confidence: medium
Scope-risk: narrow
Directive: Do not encourage parent turns to kill background agents only because partial output was read.
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx -t "renders stopped background agents as neutral transcript events"
Tested: cd desktop && bun run lint
Tested: bun run check:server
Goal runs spawn visible background work while the conversation is active, so the desktop should show those events at their actual transcript position instead of pinning a separate panel above the chat. The UI now renders background task records as inline message events, repairs restored task state from transcript notifications, and keeps later task updates on the original event card.
Constraint: Existing transcripts only persist terminal task notifications, so the server now preserves their timestamps for deterministic restore ordering.
Rejected: Keep the page-level background-agent panel | it occluded the session content and duplicated message-flow information.
Confidence: high
Scope-risk: moderate
Directive: Background task message timestamps intentionally stay fixed after insertion; update the task content without moving the card in the transcript.
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/sessions.test.ts
The fixed-height virtual window introduced after v0.2.6 assumed every rendered chat item was 96px tall. Real desktop messages include markdown, math, code blocks, tool groups, and agent cards with highly variable heights, so scrolling history could replace real content with incorrectly sized blank spacer regions and make tab restoration janky.
Remove that unsafe virtualization layer and keep the existing direct scroll restoration behavior. The regression tests now keep long mixed-height transcripts and tool-call histories mounted so variable-height rows cannot disappear behind fake spacer math again.
Constraint: Desktop chat rows have unbounded variable height from markdown, KaTeX, code, tool, and agent output.
Rejected: Tune the 96px estimate | any constant row height still fails for mixed markdown and tool output.
Rejected: Add measured virtualization now | larger change surface than needed for the production white-gap regression.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce chat virtualization without real row-height measurement and scroll restore coverage.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Goal status previously used a standalone success-card treatment, which made /goal look visually detached from the transcript. Rework the active goal strip and goal event cards to use the same compact memory/tool-result visual language.
Constraint: Preserve /goal state visibility while reducing alert-like chrome.
Rejected: Continue tuning only goal colors | the layout still read as a separate alert instead of transcript state.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx src/theme/globals.test.ts
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Goal status needed to read like persistent agent state, not a success alert. Reuse the Memory surface language and expose goal-specific aliases so the visual treatment follows each supported desktop theme.\n\nConstraint: Must keep /goal visible in both active-session chrome and transcript events.\nRejected: Continue using success tokens | they made the UI look like a warning/success card and clashed with white/dark themes.\nConfidence: high\nScope-risk: narrow\nTested: cd desktop && bun run test -- --run src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx src/theme/globals.test.ts\nTested: cd desktop && bun run lint\nTested: cd desktop && bun run build\nTested: Chrome screenshot fixture for light, white, and dark goal UI tokens.
Goal sessions need to feel like an ongoing agent-loop state, not a large success alert dropped into the chat. The UI now uses compact status and event treatments, and session titles stay anchored to the original goal objective instead of later /goal status commands.
Constraint: /goal history uses local_command transcript entries and later status checks can append fresh AI titles.
Rejected: Keep the existing green cards | they duplicate the objective, consume too much vertical space, and read as one-off notifications instead of durable loop state.
Confidence: high
Scope-risk: narrow
Directive: Goal status UI should remain compact and stateful; do not reintroduce large centered cards for routine lifecycle events.
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Browser preview at 127.0.0.1:5179 with real session 3e9117d5-b792-43c9-bf57-7aec2b124f7e on desktop and mobile viewport
Not-tested: Packaged macOS app rebuild.
The goal flow needs to drive the agent loop and remain visible after desktop reconnects, so this change treats /goal output as a structured lifecycle signal across CLI, server, and desktop history restoration.
Constraint: The current TypeScript CLI does not share Codex's Rust app-server thread-goal database, so persistence is reconstructed from the existing session transcript.
Rejected: UI-only goal badges | would leave the CLI self-loop unable to recover active or completed goals after process restart.
Rejected: Add a new persisted store | larger migration surface than needed for the existing transcript-backed session model.
Confidence: high
Scope-risk: moderate
Directive: Keep future goal state changes mirrored in transcript-visible local command output or introduce a real migration-backed store.
Tested: bun test src/goals/goalEvaluator.test.ts src/goals/goalState.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx
Tested: bun run verify
Not-tested: Live provider baseline with a real model-driven goal loop.
Restored transcripts can still present stale running state while their final markdown and tool output continue to reflow. Resize-driven auto-follow now stays enabled only for live resizable output, so readers can scroll up after completion while active streams still remain pinned.
Constraint: Old H5 sessions can reconnect with stale non-idle UI state even when the transcript has an end_turn.
Rejected: Disable resize following entirely | active H5 streaming would regress when rendered content grows after tokens arrive.
Confidence: high
Scope-risk: narrow
Directive: Do not broaden ResizeObserver auto-follow without a restored completed-session regression test.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Browser smoke on session 85862799-ec6b-43d0-b3b8-6e9fd9130954 kept scrollTop at 320 after content resize.
Sending from history must move the transcript to the new turn even if session state has not yet advanced from idle. The previous guard tied the jump to chatState, so a user_text tail that landed before thinking/streaming would be skipped permanently because the tail id no longer changed.
Constraint: The same MessageList path serves desktop and H5 chat.
Rejected: Wait for chatState to become thinking before scrolling | that misses updates where the message id is already recorded.
Confidence: high
Scope-risk: narrow
Directive: New user_text tail messages are intentional navigation events and must override historical scroll position.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
iPhone Chrome uses the iOS WebKit viewport model, so using scrollIntoView on the transcript sentinel can ask the browser to scroll ancestor containers, including the page viewport. That is risky with the fixed bottom composer and can expose blank page space when the latest button is clicked. This keeps all latest-message movement scoped to the chat scroll element and follows post-render height changes while streaming.
Constraint: H5 and desktop share MessageList, but the H5 browser runs through iOS WebKit viewport behavior.
Rejected: Keep bottomRef.scrollIntoView with container options | container support is not reliable enough and the default still targets all scrollable ancestors.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce transcript scrollIntoView without real iPhone Chrome verification.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
H5 browsers can lag behind smooth scroll animations when streaming text grows, and sending a new prompt should always return the reader to the live turn even if they were browsing history. The message list now writes the scroll container directly while preserving the existing opt-out for manual history reading during assistant streaming.
Constraint: H5 and desktop share MessageList behavior
Rejected: Force-scroll on every streaming update | would break reading historical content during long replies
Confidence: high
Scope-risk: narrow
Directive: Preserve manual history reading except when a new user prompt is appended
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Full check:desktop remains blocked by the existing vite-config color-mix assertion in globals.css
Virtualized chat rendering keeps inactive long-running sessions cheap without disconnecting their CLI process, while DeepSeek now relies on the shared Thinking setting instead of a provider-specific disabled-thinking override. Existing legacy DeepSeek managed env is normalized so old local settings do not keep suppressing reasoning output after upgrade.
Constraint: Multiple desktop tabs must keep live sessions running and remain quick to switch without reconnecting.
Rejected: Pause or disconnect hidden sessions | would delay tab switching and interrupt streaming/tool state visibility.
Rejected: Keep DeepSeek disabled-thinking preset | conflicts with the General Settings Thinking control.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce provider-specific disabled-thinking defaults for DeepSeek without testing both General Settings toggle states.
Tested: bun test src/server/__tests__/conversations.test.ts -t "global Thinking setting control DeepSeek"
Tested: bun test src/server/__tests__/persistence-upgrade.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/title-service.test.ts src/utils/__tests__/thinking.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts
Tested: cd desktop && bun run test -- MessageList.test.tsx ContextUsageIndicator.test.tsx
Tested: bun run check:server
This brings the desktop memory worktree onto local main after main advanced with opener, IM, zoom, and autonomous goal work. The conflict resolution keeps both the selected-chat reference imports and memory event settings entrypoint in MessageList, and keeps both open-targets and memory API routes in the server router.
Constraint: main and feat/desktop-memory-surface diverged across desktop chat and server routing.
Constraint: desktop Vitest runs src-tauri tests under Vitest, so the Tauri config test cannot import bun:test.
Rejected: Fast-forward merge | main carried newer local commits not present on the memory branch.
Confidence: high
Scope-risk: moderate
Directive: Keep chat selection references, memory cards, open-target routes, and memory routes together when touching these files.
Tested: bun run check:desktop
Tested: bun run check:server
Workspace and transcript selections now flow into the composer as lightweight references so users can carry exact snippets into the next prompt without file-only workarounds. The prompt formatter keeps workspace and chat excerpts separate, and the composer avoids sending chat snippets as fake file attachments.
Constraint: Selection references must prepare model context without polluting backend file attachment payloads.
Rejected: Reuse file attachments for chat selections | would leak chat:// pseudo paths into file-oriented payloads.
Confidence: high
Scope-risk: moderate
Directive: Keep chat-selection references prompt-only unless the backend gains a first-class chat context attachment type.
Tested: bun run check:desktop
Tested: bun run verify
Tested: agent-browser UI smoke for workspace, user-message, and assistant-message selections
Not-tested: Strict Chrome extension channel; connection timed out, local browser automation covered the flow.
The CLI already emits memory_saved events and stores Markdown memory files, but the desktop app had no usable surface for seeing or editing those writes. This adds a project memory API, a Settings memory editor, chat memory event cards, and routing from /memory or /context into the memory UI.
Constraint: Memory files live under Claude project storage and must remain plain Markdown editable by users.
Rejected: Only expose raw filesystem links | users need an in-app review and edit flow from chat.
Confidence: high
Scope-risk: moderate
Directive: Keep memory storage project-scoped and preserve unknown Markdown content when editing.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts src/server/__tests__/memory.test.ts
Tested: bun run check:server
Tested: bun run check:desktop
Tested: agent-browser E2E for chat memory card, Open Memory navigation, and responsive Markdown editor layout
Not-tested: Live model auto-memory trigger rate with real provider credentials
Programmatic scroll calls (scrollIntoView) trigger onScroll events mid-animation,
causing updateAutoScrollState to momentarily see a non-bottom position and flash
the button on/off. Guard against this with isProgrammaticScrollingRef.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bring the detached worktree fix into the local main line while preserving the newer main behavior that passes session context into interactive chat message blocks.
Constraint: Local main already contains six unpublished commits on top of origin/main
Constraint: Merge conflict only affected MessageList message rendering around sessionId propagation
Rejected: Overwrite main's MessageBlock call shape | would regress AskUserQuestion and permission interactions that need sessionId
Confidence: high
Scope-risk: moderate
Directive: Keep MessageBlock sessionId propagation when editing chat rendering; AskUserQuestion depends on it
Tested: cd desktop && bun run test -- MessageList.test.tsx
Not-tested: Full verify after merge; previous verify remains blocked by agent-utils global coverage baseline
Chat sessions need predictable navigation when users switch tabs or read history during streaming. This preserves each session's scroll position, defaults fresh sessions to the latest message, and adds a compact jump-to-latest affordance when auto-follow is paused.
The PR gate also exposed that provider-scoped scheduled tasks must force the sdk-cli entrypoint when launched through the sidecar, so the same change records that runtime contract and its regression assertion.
Constraint: Desktop chat should not force-scroll while the user is reading older messages
Constraint: Scheduled task provider env must not inherit stale parent model runtime values
Rejected: Persist scroll positions in localStorage | session scroll is transient UI state and should not survive app restarts
Confidence: high
Scope-risk: moderate
Directive: Do not remove the sdk-cli entrypoint marker from provider-scoped cron tasks without rerunning the sidecar launcher regression
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/cron-scheduler-launcher.test.ts src/utils/__tests__/themeWords.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: bun run verify still fails on pre-existing agent-utils global coverage baseline below threshold
Not-tested: Chrome extension E2E blocked by Codex Chrome Extension communication timeout after plugin diagnostics passed
Desktop question prompts can carry CLI-provided multiSelect flags, so the answer UI now stores selections per question as arrays and only allows multiple checked options when that flag is true. The message renderer also passes the owning session id into permission UI so pending responses go back to the session that produced the prompt instead of whichever tab is active.
Constraint: AskUserQuestion input may arrive as either questions[] or single question shape
Rejected: Make all option questions multi-select | single-select prompts must remain constrained unless the CLI marks them multiSelect
Confidence: high
Scope-risk: narrow
Directive: Do not infer multi-select from option count; honor the explicit multiSelect flag
Tested: cd desktop && bun run test -- src/components/chat/AskUserQuestion.test.tsx src/components/chat/chatBlocks.test.tsx src/components/chat/MessageList.test.tsx
Tested: bun run check:desktop
Tested: Chrome/CDP smoke against /tmp/cc-haha-ask-user-real verified multi-select and single-select behavior
Not-tested: Chrome extension RPC path, which timed out in this environment despite native host checks passing
The chat timeline lost per-turn changed file cards when the visible user message ids did not match persisted transcript ids, and sessions without SDK file-history snapshots had no fallback source for turn changes. Match live cards by stable user-message index when needed and derive checkpoint previews from transcript tool calls when snapshots are absent.
Constraint: Right-side workspace changes already derive from transcript tool calls, so chat checkpoint data must remain consistent with that source.
Rejected: Force a history reload after every turn | would hide the id mismatch but add churn and still not cover transcript-only sessions.
Confidence: high
Scope-risk: moderate
Directive: Keep snapshot checkpoints as the preferred source, and use transcript extraction only as the compatibility fallback.
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: bun test src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test
Tested: git diff --check
Not-tested: Browser E2E against a live model session.
The desktop workspace panel and chat transcript were mixing project-level file state with per-turn session history, which made rewind, file attachment, and changed-file previews brittle across multi-turn and historical sessions. This keeps turn checkpoints durable in the transcript, makes workspace refreshes happen at the right lifecycle points, and hardens long file previews without blocking the UI.
Constraint: Right-side workspace changes are project working-tree state, while chat turn cards are session checkpoint state.
Rejected: Treat every changed-file panel entry as session-local | new sessions must still reveal existing dirty project files.
Confidence: high
Scope-risk: moderate
Directive: Do not couple global workspace status to session checkpoint cards without preserving both product meanings.
Tested: cd desktop && bun run test src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: cd desktop && bun run test
Not-tested: Packaged Tauri runtime smoke test
The chat surface now relies on the current-turn changes card for file rollback, so the older per-message hover rewind affordance was removed to avoid two competing rollback models.
Constraint: Current-turn undo still depends on the existing checkpoint rewind API.
Rejected: Keep both rewind entry points | duplicate rollback affordances make the product harder to explain and test.
Confidence: high
Scope-risk: moderate
Directive: Prefer adding rollback behavior through the current-turn change card instead of restoring per-message hover rewind.
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Browser E2E scripts were updated but not executed in this commit.
Workspace inspection now shares the same diff renderer between chat change cards and panel previews, supports scoped preview-tab closing, confirms checkpoint undo, resolves rewind checkpoints from the prompt cwd, and removes light-only workspace chrome so dark theme remains coherent.
Constraint: Workspace review must keep working for existing transcript sessions and non-git session-derived changes.
Rejected: Keep a separate chat diff preview | it drifted from panel rendering and dark theme behavior.
Confidence: high
Scope-risk: moderate
Directive: Keep chat and workspace diff rendering shared so theme and truncation behavior do not diverge again.
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/components/chat/MessageList.test.tsx src/stores/workspacePanelStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: Browser dark workspace preview at http://127.0.0.1:59468/ with src/App.jsx diff
Not-tested: macOS packaged app build
Workspace inspection was visible only as a side panel, while file restore still lived behind the older per-message rewind affordance. This adds a chat-flow change card for the completed turn, keeps undo routed through checkpoint rewind, and tightens the workspace split layout so narrow and fullscreen windows keep both chat and file preview usable.
Constraint: Current session file changes must work for non-Git directories when transcript tool edits are available
Constraint: Workspace preview must remain right-docked without horizontal overflow across narrow and wide desktop viewports
Rejected: Keep only hover rewind | users could not clearly see what the current turn changed before undoing it
Rejected: Use Git as the only changed-file source | many desktop sessions run in temporary or non-Git directories
Confidence: high
Scope-risk: moderate
Directive: Keep checkpoint rewind as the source of truth for undo semantics; workspace diffs are a preview surface, not the restore authority
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test -- src/components/chat/MessageList.test.tsx src/components/workspace/WorkspacePanel.test.tsx src/stores/workspacePanelStore.test.ts src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun /tmp/cc-haha-layout-e2e/run-layout-e2e.mjs
Not-tested: Native Tauri packaged app window chrome behavior
Expose a workspace inspector for desktop sessions so users can browse the active project, preview files, inspect session-derived changes, and review diffs even when the work directory is not a git repository.
Constraint: The workspace view must work for temporary and non-git project directories.
Rejected: Rely only on git status | non-git sessions would lose the changed-files surface.
Confidence: medium
Scope-risk: moderate
Directive: Keep undo semantics separate from workspace browsing; checkpoint-backed rewind should remain the source of truth for rollback.
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx src/stores/workspacePanelStore.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full desktop packaged app build after this commit
Subagent transcripts are stored in sidecar JSONL files, so the desktop history loader now links completed Agent results back to their sidecar tool activity and namespaces child tool ids before rendering. The chat renderer then keeps parented tool calls and orphaned child results out of the root session stream, while the Agent result preview strips runtime metadata from the user-facing answer.
Constraint: Subagent tool activity may live under ~/.claude/projects/{project}/{session}/subagents instead of the main session JSONL
Rejected: Rely only on adjacent message ordering | interleaved messages can split parent and child tool activity
Rejected: Show final Agent output inside the expanded card | expanded cards should show process activity while the modal shows the final answer
Confidence: high
Scope-risk: moderate
Directive: Do not remove child tool id namespacing without proving multiple subagents cannot emit colliding tool ids
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test -- MessageList.test.tsx chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: agent-browser E2E with two dispatched subagents in /tmp/cc-haha-agent-e2e-q4Xm2s
Not-tested: Live streaming of sidecar subagent tool activity before the sidecar file is persisted
Desktop rewind previously used the visible user-message index as the primary selector. That can drift from the persisted active chain when hidden or non-rendered user messages exist, so the API now prefers a stable user message id and checks the selected prompt text before mutating transcript or files.
Constraint: Desktop UI can hide transcript entries that still exist in the persisted session chain
Rejected: Keep index-only rewind | can target an earlier user message after UI/server chain drift
Confidence: high
Scope-risk: moderate
Directive: Do not remove targetUserMessageId or expectedContent guards without reproducing shifted visible-index sessions
Tested: bun test src/server/__tests__/sessions.test.ts --timeout 20000
Tested: cd desktop && bun run test MessageList.test.tsx chatStore.test.ts -- --run
Tested: desktop/scripts/e2e-rewind-agent-browser.sh
Tested: desktop/scripts/e2e-rewind-complex-agent-browser.sh
Not-tested: Full desktop Vitest suite still has unrelated locale-sensitive failures
The desktop app already had solid session streaming, permission, and tool-rendering flows, but extension setup still forced users into manual forms or external shell work. This change adds an Install Center in Settings that reuses the session chat pipeline for natural-language installs, adds installer-specific guidance for plugin and skill URLs, and includes an agent-browser E2E script for real UI validation.
Constraint: Must reuse the existing session/chat execution path instead of introducing a second install runtime
Constraint: Plugin installs need real CLI commands while skill installs may come from published install commands on third-party pages
Rejected: Separate terminal-only install surface | duplicates session UX and weakens permission/tool visibility
Rejected: Pure form-based installer expansion | too much friction for plugin, MCP, and skill onboarding
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep installer prompts aligned with the actual CLI install surfaces; do not let the installer fall back to slash-command syntax inside Bash
Tested: cd desktop && bun run lint
Tested: Real UI automation via agent-browser for Telegram plugin install flow through Settings > Install, verified Plugins page shows telegram enabled
Tested: Real UI automation via agent-browser for ui-ux-pro-max skill install flow through Settings > Install, verified ~/.claude/skills/ui-ux-pro-max and Skills page visibility
Not-tested: Full e2e-install-center-agent-browser.sh script as a single uninterrupted green run after the latest stability tweaks
The desktop client already had a conversation-level rewind UI concept on the
CLI side, but the web/desktop surface lacked the protocol, session trimming,
and file checkpoint restore path needed to make rewind trustworthy. This change
adds a desktop-specific rewind API, wires the message-level UI affordance and
confirmation modal, enables SDK file checkpointing for desktop sessions, and
covers the restore path with service tests plus a real agent-browser workflow.
Constraint: Desktop sessions run the CLI in SDK/print mode, so file checkpointing had to be enabled explicitly for that path
Constraint: main branch is checked out in a separate worktree, so merge-back must happen from the primary worktree after commit
Rejected: UI-only rewind that only trims local state | would leave persisted transcript and disk state inconsistent after refresh
Rejected: Reuse getLastSessionLog as the sole snapshot source | active rewind must read file-history metadata directly from the session file
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep desktop rewind keyed to persisted user-message order unless the UI model starts carrying stable transcript UUIDs end-to-end
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/sessions.test.ts; desktop MessageList vitest; desktop tsc no-emit; live agent-browser E2E on isolated ports with file edit then rewind
Not-tested: Browser E2E matrix for multi-file and second-edit scenarios is still covered at service-test level rather than full UI level
Desktop chat was rendering AskUserQuestion twice: once as an inline question card and again as a generic permission request. That split also dropped the structured answers on the floor because the websocket permission_response shape only carried allow/deny state.
This change keeps AskUserQuestion on the permission pipeline end-to-end. The desktop websocket contract now carries toolUseId and updatedInput, AskUserQuestion submits answers through permission_response, and the generic permission card is suppressed for that tool so the user sees a single question flow.
Constraint: AskUserQuestion answers must round-trip through updatedInput.answers for the CLI tool contract to complete
Rejected: Leave AskUserQuestion as a plain chat reply in desktop | the tool never receives structured answers and the pending approval UI remains stuck
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep AskUserQuestion bound to the permission-response path unless the desktop protocol grows a separate structured elicitation channel
Tested: desktop lint; vitest src/components/chat/AskUserQuestion.test.tsx src/stores/chatStore.test.ts; bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual desktop click-through against a live plan-mode session after bundling
The desktop transcript renderer was continuing to inline later nested tool calls under an older parent card even after a new user message had been submitted mid-session. That made the session timeline diverge from CLI behavior, where queued user input becomes the boundary for the next tool round.
This change rebuilds the render model around visible message boundaries so nested tool calls only stay attached to a parent within the same contiguous segment. A regression test now covers the interleaved user-message case.
Constraint: Preserve existing grouped tool rendering for contiguous parent-child tool activity
Rejected: Rework the websocket/session pipeline to synthesize extra transcript events | the bug was in render ordering, not transport semantics
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If mid-turn follow-up prompts change again, validate both render ordering and nested child attachment rules together
Tested: bun run test --run src/components/chat/MessageList.test.tsx; bun run lint
Not-tested: Full desktop app manual session playback against a live running tool turn
This introduces a persisted light/dark appearance setting, maps the desktop shell onto semantic theme tokens, and reworks the highest-traffic chat/settings surfaces so the new dark mode is usable without regressing the original light theme.
The same pass tightens markdown rendering for chat replies by improving inline code, table overflow handling, and safe external-link behavior so dark-mode content stays legible in real conversations.
Constraint: Preserve the existing light theme while adding a user-selectable dark theme in Settings > General
Constraint: Avoid introducing new dependencies for styling or markdown handling
Rejected: Replacing the light palette with a single dual-purpose palette | would risk broad visual regressions across the existing desktop UI
Rejected: Implementing dark mode only for shell chrome | leaves chat markdown, diffs, and permission flows visually broken
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: New desktop UI should use semantic theme variables instead of hard-coded color literals so both themes stay aligned
Tested: bun run lint; bun run test; bun run build; browser review of theme switching, provider/permission states, and chat surfaces
Not-tested: Prototype-style pages with remaining hard-coded colors (scheduled/session control mock surfaces) were not fully normalized in this change
The chat UI translated CLI startup failures to a generic localized string and
hid the server-provided detail entirely. That made remote Windows debugging
guesswork even when the backend had already captured the real stderr payload.
This change keeps the localized summary but renders the raw startup detail
underneath when it differs, and adds a regression test covering the Git Bash
missing case.
Constraint: Windows Server repro is happening remotely, so the immediate value is better diagnostics rather than guessing a root cause locally
Rejected: Leave the generic translation only | obscures actionable stderr and slows every remote repro cycle
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: For translated startup errors, preserve backend detail somewhere visible unless it duplicates the summary
Tested: cd desktop && bunx vitest run src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Not-tested: Full desktop interaction flow on Windows Server 2022
Related: GitHub issue #62
This bundles the pending desktop/server team-session fixes with the local adapter recovery changes already in the worktree. The team path now keeps teammate membership stable under concurrent spawns, surfaces real teammate identities in the desktop UI, and allows direct interaction with member transcripts. The adapter changes recover automatically when stale thinking signatures invalidate an existing session.
Constraint: Team config writes can happen concurrently while multiple reviewers spawn in parallel
Constraint: Desktop member views must follow mailbox/transcript semantics rather than hijacking teammate runtime sessions
Rejected: Keep relying on config.json alone for member discovery | in-process teammates can be lost after concurrent writes
Rejected: Open teammate sessionIds as normal desktop sessions | would attach a second CLI instead of the running teammate
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Preserve locked team-file mutation for any future teammate registration path and keep teammate labels sourced from member names before agent types
Tested: bun test src/server/__tests__/teams.test.ts src/server/__tests__/team-watcher.test.ts
Tested: cd desktop && bun run test --run src/stores/chatStore.test.ts src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Manual end-to-end validation against a live Agent Teams run in the desktop app
- security: XSS sanitization with DOMPurify in Markdown/Mermaid/PermissionDialog;
path whitelist in filesystem API; fake keys in test/config files
- perf: fine-grained Zustand selectors in Sidebar/StatusBar/ContentRouter;
50ms throttle on streaming deltas; React.memo + useMemo in MessageList;
useRef for frequent keyboard shortcut state; AbortController 30s timeout
- leaks: WS session TTL timers (5-min cleanup on close); batch splice for
sdkMessages/stderrLines; folderPath validation in cronScheduler
- quality: optimistic update rollback in settingsStore; error state in
providerStore/teamStore; i18n for all hardcoded English strings
- docs: desktop architecture and features docs updated; VitePress nav fixed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This folds together the desktop-side fixes needed before broader rollout.
Session resume no longer deadlocks waiting on init, Mermaid and inline image
output render inside chat, task and sub-agent state stay visible during
execution, local build/release paths are safer, and Feishu/Telegram now expose
lightweight mobile commands (/help, /status, /clear) without adding a new
adapter-specific protocol.
Constraint: Desktop releases must publish updater artifacts from non-draft GitHub releases
Constraint: IM commands need short, phone-friendly responses and low operational complexity
Rejected: Add a dedicated IM command API surface | re-used existing slash commands and session/task REST endpoints to keep adapters thin
Rejected: Wait for task_update push events in WebUI | added low-risk polling because the current frontend ignores that event path
Confidence: medium
Scope-risk: broad
Reversibility: clean
Directive: Keep IM command replies terse and mobile-first, and merge local fallback slash commands when server-provided lists are partial
Tested: cd desktop && bun x vitest run src/components/chat/MermaidRenderer.test.tsx src/components/markdown/MarkdownRenderer.test.tsx
Tested: cd desktop && bun x vitest run src/components/chat/composerUtils.test.ts src/pages/ActiveSession.test.tsx src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "SDK init arrives only after the first user turn" --timeout 60000
Tested: cd adapters && bun test common/ feishu/ telegram/
Tested: cd adapters && bunx tsc --noEmit
Not-tested: Full GitHub Actions release run on all three desktop platforms
Not-tested: Local DMG packaging end-to-end on Apple Silicon
Not-tested: Real Feishu/Telegram device sessions against a live adapter process
Both components now look up state via activeTabId from useTabStore,
reading from sessions[activeTabId] in chatStore. Action calls
(sendMessage, stopGeneration) pass the sessionId as first argument.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The desktop chat view flattened Agent tool activity, which made sub-agent work
hard to follow and separated key evidence from the main conversation. This
change threads parent tool linkage through the server bridge and desktop store,
renders dispatched sub-agents as grouped cards with nested tool activity, and
moves long final outputs into a markdown preview dialog so the main transcript
stays readable on narrow layouts.
Constraint: Existing sessions and live websocket events both needed to preserve parent-child relationships
Rejected: Add brand-new subagent websocket event types | unnecessary protocol expansion when parent linkage already existed upstream
Rejected: Inline full sub-agent markdown in the card body | too cramped for narrow desktop chat layouts
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep Agent card summaries compact; route long-form sub-agent output through the preview dialog unless the main chat layout is widened substantially
Tested: cd desktop && bun run test -- MessageList.test.tsx chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/sessions.test.ts -t should\ reconstruct\ parent\ agent\ tool\ linkage\ from\ parentUuid\ chains
Not-tested: Full end-to-end visual verification against live CLI sessions with sub-agent text/thinking nested inline
Add a lightweight custom i18n system supporting English (default)
and Chinese, with a language switcher in Settings > General.
All 35+ UI components internationalized with ~270 translation keys,
including 189 Chinese spinner verbs and server error code mapping.
The CLI subprocess runs in --print (non-interactive) mode, which disabled
V2 task tools (TaskCreate/TaskUpdate). Only TodoWrite was available, but
the frontend only tracked V2 tool names for task refresh — so the Tasks
bar never appeared for new WebApp sessions.
Changes:
- Set CLAUDE_CODE_ENABLE_TASKS=1 in CLI subprocess env to enable V2 tasks
- Add TodoWrite to TASK_TOOL_NAMES for backward compat with V1 sessions
- Parse TodoWrite input.todos directly into TaskBar state (no disk read)
- Extract last TodoWrite from history on session load for V1 sessions
- Inline completed task summary into message flow when user continues chat
(sticky TaskBar converts to scrollable inline summary)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Tauri sidecar architecture: Rust shell spawns claude-server binary,
dynamic port allocation, health-check wait loop, graceful shutdown
- Fix CORS middleware to accept `tauri://localhost` and `https://tauri.localhost`
origins from Tauri WebView, and add CORS headers to /health endpoint
- Enable native macOS window decorations (traffic lights) with Overlay title bar,
add data-tauri-drag-region on sidebar for window dragging
- Conditionally apply desktop-only padding (44px for traffic lights) vs web (12px)
- Generate brand identity: light-background app icon, horizontal logo, full icon
set (icns/ico/png) for Tauri bundle
- Add brand mark + GitHub link in sidebar, replace mascot SVG with app icon
in EmptySession page
- Update README (zh/en) and docs hero image with new branding
- Add sidecar build scripts and launcher entry points
- Gitignore Rust target/, Tauri gen/, and brand-assets candidates
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>