The workspace panel already had the attachment model for both files and directories, but directory rows lacked the same right-click path and there was no low-risk handoff from the panel into the active composer text. This keeps context attachment as the stable source of file content while adding a small composer insertion event for inline citations.
Constraint: Keep the existing attachment pipeline unchanged so file and directory contents still flow through the proven workspace reference path
Rejected: Build a rich inline chip editor immediately | larger composer and history-synchronization surface than these issues require
Confidence: high
Scope-risk: moderate
Directive: Do not replace workspace attachments with inline text-only mentions without rechecking model-content generation and history restore behavior
Tested: cd desktop && bunx vitest run src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bunx vitest run src/components/chat/ChatInput.test.tsx
Tested: bun run check:desktop
Not-tested: Live provider response quality with inline citation wording
The active-session composer used an absolute toolbar overlay and textarea bottom padding to reserve space. When the input grew and scrolled, the caret could render inside that reserved overlay area, making it appear to pass through the controls.
Move the non-hero toolbar back into normal layout flow and remove the oversized textarea bottom padding so the text area and controls occupy separate boxes.
Constraint: Active-session and hero composer variants share the component, so keep the change scoped to the non-hero toolbar path.
Rejected: Increase textarea bottom padding | preserves the overlapping layout that caused the caret artifact.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce an absolutely positioned toolbar over the active-session textarea without validating multiline caret rendering.
Tested: cd desktop && bun run test src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: Browser DOM check confirmed textarea and toolbar no longer overlap.
Desktop non-session tabs unmount the active chat composer, so component-local draft refs disappeared before users returned to the session. Move the draft into per-session chat state and save it at session-switch and unmount boundaries, then clear it after submit.
Constraint: Settings and terminal tabs replace ActiveSession in ContentRouter.
Rejected: Persist drafts in localStorage | would add unnecessary persistence migration surface for a runtime-only draft.
Confidence: high
Scope-risk: narrow
Directive: Keep composer draft state scoped per session; do not move it back into component-only refs without covering unmount and remount.
Tested: bun run test src/components/chat/ChatInput.test.tsx --run
Tested: bun run check:desktop
Tested: bun run verify
Drag-drop support made the composer panel clip overflow so the
stateful slash and file popovers still opened but were hidden above
the panel. Restore visible overflow on the drop target while keeping
the drop overlay and attachment flow intact, then lock both active
and empty composer surfaces with regression coverage.
Constraint: Drag-drop attachments must continue using the existing path-only composer pipeline
Rejected: Move slash and file menus outside the composer tree | broader portal refactor not needed for this regression
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce overflow clipping on composer panels without proving bottom-full popovers still render
Tested: cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: Browser smoke at http://127.0.0.1:1421/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456 for / and @src popovers
Tested: bun run check:desktop
A user-requested drag target should reuse the existing attachment pipeline instead
of creating a second upload path. The composer now handles browser DataTransfer
drops and Tauri native drag-drop events, while keeping desktop attachments
path-only so large files are not serialized into chat payloads. The fallback
/goal metadata is also aligned with the existing desktop command surface because
the desktop gate exercises that menu while validating composer behavior.
Constraint: Desktop attachments must remain path-only to avoid inflated websocket payloads
Rejected: Read dropped desktop files with FileReader | would reintroduce large inline data payloads
Confidence: high
Scope-risk: moderate
Directive: Do not replace the Tauri drag-drop path conversion with data URLs without rerunning payload-size regression coverage
Tested: cd desktop && bun run test -- --run src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun run check:desktop
Tested: agent-browser overlay and dropped-chip smoke at http://127.0.0.1:1421/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456
Tested: bun run verify
Not-tested: Finder-to-packaged-.app manual drag smoke
The desktop composer had treated /goal lifecycle arguments as standalone slash-command rows. That made the UI look like /goal had second-level commands even though the CLI contract is one /goal command with arguments.
Constraint: Keep /goal status/pause/resume/complete/clear usable as CLI arguments.
Rejected: Keep pseudo subcommands in the picker | it contradicts the slash command model and confuses objective entry.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
New desktop sessions populated slash suggestions from the session endpoint before the CLI had emitted init metadata. That endpoint only scanned user and project skill directories, while the plugin settings view and global skills API already saw enabled plugin skills such as superpowers. The session endpoint now reuses the global skill listing and merges it with any cached CLI slash commands, and both composer surfaces rank command-name matches before broad description matches so /su surfaces superpowers first.
Constraint: New sessions need plugin skills before the first real user turn starts the CLI.
Rejected: Start or restart a hidden CLI process on plugin enable | heavier than needed and still misses the REST slash-command fallback path.
Confidence: high
Scope-risk: moderate
Directive: Keep session slash commands and /api/skills on the same skill discovery path when changing plugin skill loading.
Tested: bun test src/server/__tests__/skills.test.ts src/server/__tests__/plugins.test.ts src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test --run src/components/chat/composerUtils.test.ts src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Not-tested: Manual desktop click-through in the packaged app.
Desktop file attachment selection was inlining every selected file as a data URL before sending the websocket message. Large multi-file sends could inflate the renderer request body by tens of megabytes before the server had a chance to materialize uploads.
Route Tauri file selection through the native dialog so desktop sends absolute paths, while preserving browser fallback data URLs for H5. Cover both active-session and draft-session composers plus a payload-size regression case.
Constraint: Browser/H5 cannot rely on local absolute file paths, so the existing FileReader fallback remains for non-Tauri runtimes.
Rejected: Raise websocket/body limits | would keep renderer memory pressure and still send file bytes unnecessarily.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce FileReader data URLs for Tauri desktop file-picker attachments without measuring websocket payload size.
Tested: cd desktop && bun run test src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: /tmp complex-project reproduction showed 12x3MB old inline payload at 50,333,174 bytes versus path-only payload around 1-2KB
Not-tested: Full desktop lint/build due unrelated existing Sidebar.tsx type errors in the dirty worktree
Related: https://github.com/NanmiCoder/cc-haha/issues/444
The active empty-session composer used the same launch controls as the new-session surface but rendered them outside the glass panel, creating an inconsistent gap between the prompt box and project/branch/worktree selectors. Embed the desktop hero launch row in the composer panel and keep the existing mobile and post-message context-chip paths unchanged.
Constraint: Preserve the existing new-session composer layout and shared RepositoryLaunchControls behavior.
Rejected: Adjust outer margins only | would keep the active empty-session DOM structurally different from the already-correct new-session composer.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: agent-browser DOM check confirmed the launch row is inside chat-input-panel with 1px bottom gap
Not-tested: Full bun run verify gate
Goal commands already had local CLI metadata, but desktop and SDK init consumers only received a name list. Keep the legacy slash_commands string array intact and add explicit metadata so clients can render descriptions and usage hints without breaking older readers.
Constraint: Existing SDK/system init consumers may depend on slash_commands staying string-only.
Rejected: Replace slash_commands with objects | would risk breaking older SDK and desktop consumers.
Confidence: high
Scope-risk: moderate
Directive: Keep slash_commands as the compatibility name list unless all SDK consumers have migrated.
Tested: bun test src/server/__tests__/websocket-handler.test.ts src/utils/messages/systemInit.test.ts src/commands/headless.test.ts src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts
Tested: cd desktop && bun test src/components/chat/composerUtils.test.ts
Tested: bun run check:server
Tested: bun run check:desktop
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.
Desktop @ file search was recursively walking the filesystem with a local skip list, which let Python and Node generated directories leak into results and diverged from CLI behavior. Route candidate discovery through the same git-first model: tracked files, untracked files with exclude-standard, and ripgrep fallback for non-git folders, then derive selectable directories from those candidates.
Constraint: Desktop picker must select both files and directories without surfacing ignored project artifacts.
Constraint: No new dependencies; reuse the existing git, ripgrep, settings, and ignore utilities.
Rejected: Maintain a hardcoded directory denylist | it would drift from CLI and miss project-specific ignore rules.
Rejected: Full recursive readdir scanning | it ignores git index semantics and makes large dependency trees visible.
Confidence: high
Scope-risk: moderate
Directive: Keep desktop @ file candidate discovery aligned with src/hooks/fileSuggestions.ts before changing ranking or ignore behavior.
Tested: bun test src/server/__tests__/filesystem.test.ts
Tested: cd desktop && bun run test -- FileSearchMenu.test.tsx ChatInput.test.tsx
Tested: cd desktop && bun run lint && bun run test -- --run && bun run build
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Not-tested: Native Windows/Linux manual UI smoke; path handling relies on cross-platform git/ripgrep wrappers and normalized relative paths.
The desktop chat composer is a single mounted component under the active session view, so switching tabs reused the same local draft state. The fix keeps unsent composer input and attachments keyed by session tab and restores the matching draft when the active tab changes.
Constraint: Must preserve unsent drafts while isolating them per session tab
Rejected: Force-remount ChatInput with a React key | wider UI lifecycle change and more risk to composer substate
Confidence: high
Scope-risk: narrow
Directive: Do not make composer draft state global without preserving per-session isolation
Tested: cd desktop && bun run test -- ChatInput.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: agent-browser reproduced the leak on an unmodified baseline and verified the fixed UI keeps history tab textarea empty while restoring the new tab draft
Not-tested: Windows 11 packaged release manual smoke
The mobile H5 shell was spending one row on the drawer button and another row on session metadata, which left less space for the actual chat on phones. The mobile shell now shares the drawer row with the active session title and metadata, while the session body suppresses its duplicate header on H5.
The mobile composer also restores horizontal breathing room and safe-area bottom padding so the input panel no longer sits flush against the viewport edge.
Constraint: Keep desktop/Tauri layout unchanged and scope the change to browser H5 mobile behavior.
Rejected: Keep the separate mobile menu row | it preserves the exact waste reported in phone screenshots.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce a second mobile session title row without checking a phone-sized H5 viewport.
Tested: cd desktop && bun run test src/components/layout/AppShell.test.tsx src/pages/ActiveSession.test.tsx src/components/chat/ChatInput.test.tsx --run
Tested: git diff --check
Tested: agent-browser 393x852 H5 layout smoke with screenshot /tmp/h5-layout-verify/mobile-h5-layout.png
Not-tested: cd desktop && bun run lint, blocked by pre-existing Settings.tsx responseLanguage/responseLangDraft errors outside this change
H5 browser use needs to keep local desktop WebUI testing open while giving remote phones a usable chat-first surface. This change scopes browser auth by origin and target host, hides non-chat desktop chrome on H5 mobile, compacts the empty-session composer, and unifies mobile selectors behind a full-width bottom sheet with an explicit close action.
Constraint: Local browser development may bypass H5 auth only for loopback or private LAN server hosts; public or remote hosts still require an H5 token.
Constraint: Mobile H5 scope is chat-first; settings and scheduled-task surfaces stay hidden instead of fully redesigned.
Rejected: Reuse desktop popovers on mobile | hover/anchored dropdown behavior leaves key actions hard to dismiss and cramped on narrow screens.
Rejected: Let any localhost origin bypass remote auth | it would let arbitrary local pages access public H5 servers without a token.
Confidence: medium
Scope-risk: moderate
Directive: Keep future H5 selector popups on MobileBottomSheet unless a selector needs a reviewed custom mobile interaction.
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx src/lib/desktopRuntime.test.ts src/components/shared/RepositoryLaunchControls.test.tsx src/components/controls/PermissionModeSelector.test.tsx src/components/controls/ModelSelector.test.tsx src/components/shared/DirectoryPicker.test.tsx src/__tests__/pages.test.tsx
Tested: bun test src/server/__tests__/h5-access-auth.test.ts src/server/middleware/cors.test.ts
Tested: Chrome mobile viewport smoke for context, model, permission, project, branch, and worktree bottom sheets with no horizontal overflow.
Not-tested: Full bun run verify quality gate.
The browser H5 surface now switches to a phone-oriented shell: the sidebar becomes a closed-by-default drawer, chat stays primary, workspace and terminal panels stay off the mobile chat surface, composer controls use larger touch targets, and mobile menus avoid desktop-only widths and keyboard hints. Desktop and Tauri sidebar behavior remain on the existing store-driven path.
Constraint: H5 is personal/team browser access layered on the existing desktop web UI, so the normal desktop app must keep its current layout behavior.
Rejected: Share the global sidebarOpen default for mobile first paint | it can flash the drawer open before effects run on a phone.
Confidence: high
Scope-risk: moderate
Directive: Keep mobile-only layout branching behind useMobileViewport() && !isTauriRuntime() unless a future task explicitly redesigns the native desktop shell.
Tested: cd desktop && bunx vitest run src/hooks/useMobileViewport.test.tsx src/components/layout/AppShell.test.tsx src/components/layout/Sidebar.test.tsx src/pages/ActiveSession.test.tsx src/components/chat/ChatInput.test.tsx src/components/controls/PermissionModeSelector.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
The footer project chip previously kept showing only the source project and selected branch even after a session launched inside an isolated CLI worktree. That made an active worktree session look identical to a normal checkout, especially while agent tools were already operating under .claude/worktrees. The server now returns explicit worktree metadata from the session launch intent, and the desktop chip preserves the user-facing project and branch while appending the isolated worktree slug.
Constraint: CLI worktree branches are internal implementation details and should not replace the user-selected branch label.
Rejected: Show only the actual worktree git branch | it would expose worktree-desktop-* internals and make the business branch harder to read.
Confidence: high
Scope-risk: narrow
Directive: Keep normal checkout footer rendering unchanged; only add worktree identity when session metadata says isolation is enabled.
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern git-info
Tested: cd desktop && bun run test -- src/components/shared/ProjectContextChip.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
The chat footer previously read git-info from the persisted session file and refreshed only on tab changes. Repository launches can update the active CLI cwd after the composer has already rendered, so the footer could keep showing an older selected branch even while the running model was operating in the correct checkout.
Constraint: The active CLI process is the source of truth for a live session cwd
Rejected: Derive the footer branch only from persisted session metadata | metadata can lag behind startup and does not prove the current running cwd
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/sessions.test.ts --test-name-pattern "git-info should prefer"
Tested: cd desktop && bun run lint
Tested: git diff --check
Empty ActiveSession launch controls now use both loaded chat messages and persisted session messageCount, so historical sessions do not briefly fall back into new-session launch mode while message history is still loading. Regression coverage also locks the first-message branch launch flows for direct checkout and isolated worktree sessions.
Constraint: Historical session lists already know messageCount before chat history finishes loading
Rejected: Wait for history fetch before rendering composer controls | it keeps the wrong new-session affordance visible during reopen
Confidence: high
Scope-risk: narrow
Directive: Branch/worktree launch controls are only for messageCount-zero sessions; reopened history must render repository context chips
Tested: bun run test -- --run src/components/chat/ChatInput.test.tsx src/pages/ActiveSession.test.tsx src/__tests__/pages.test.tsx
Tested: bun run check:desktop
Tested: agent-browser /tmp repo direct branch launch on /private/tmp/cc-haha-repo-e2e-IP9mkb produced session 9089ae96-5dd6-4ea6-b88e-376ab081ca24 on workDir /private/tmp/cc-haha-repo-e2e-IP9mkb
Tested: agent-browser /tmp repo isolated worktree launch produced session 9ec657bd-e503-48b3-b52f-36e210fc5d64 on workDir /private/tmp/cc-haha-repo-e2e-IP9mkb/.claude/worktrees/desktop-main-9ec657bd
Not-tested: root bun run verify; desktop surface was covered with check:desktop and live browser smoke
Empty active session tabs were still using the old directory-only picker, so sessions created from the sidebar lost the branch and worktree controls even when their workDir was a Git repository. Reuse the launch controls below the existing composer and replace only the empty session when the user changes repository launch options.
Constraint: Sidebar-created sessions land in ActiveSession, not the EmptySession landing view
Rejected: Reshape the whole session layout | the intended surface is only the controls below the composer
Confidence: high
Scope-risk: narrow
Directive: Keep repository launch controls shared between EmptySession and empty ActiveSession composer flows
Tested: bun run test -- --run src/__tests__/pages.test.tsx src/components/chat/ChatInput.test.tsx
Tested: bun run check:desktop
Tested: agent-browser sidebar new-session smoke at http://127.0.0.1:4790/ with screenshot /tmp/cc-haha-after-sidebar-new-session-controls.png
Not-tested: root bun run verify remains blocked by pre-existing root coverage gate outside desktop
The desktop composer now treats draft and not-yet-running sessions as pending
until a real CLI context snapshot exists. Context inspection has a lightweight
context-only path so the composer and /context panel do not wait on unrelated
usage or MCP status work, and desktop boot no longer blocks the first shell
render on tab restoration.
Constraint: Empty composer state has no live CLI context to inspect until a session starts
Rejected: Display 0% before live context exists | it implied one user message consumed the fixed prompt/tool baseline
Confidence: high
Scope-risk: moderate
Directive: Do not show numeric context usage without a real context snapshot
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: bun run check:server
Tested: Browser reload at http://127.0.0.1:5174 with cleared open-tabs showed pending context and no /inspection request
Provider model context windows now feed the composer-level context indicator, so users can see current usage without opening slash panels. The indicator refreshes when the session runtime model changes, keeps a stable loading placeholder, and marks estimate fallback data during reconnect or compaction windows.
Constraint: The desktop UI must use the real session inspection API and session-scoped runtime selection instead of mocked or static context values.
Rejected: Keep context details only in /context or /status | the user explicitly wanted an always-visible composer indicator.
Rejected: Reuse stale context after model switches | different provider windows make identical percentages misleading.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the runtimeSelectionKey refresh path without real multi-provider browser verification.
Tested: cd desktop && bun run test -- pages.test.tsx
Tested: bun run check:desktop
Tested: KEEP_ARTIFACTS=1 desktop/scripts/e2e-context-usage-live-agent-browser.sh
Not-tested: Live browser E2E with every configured provider in the committed script; separate agent-browser sweep verified four providers locally.
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
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
Desktop slash commands now separate local UI panels from CLI turn execution. The session inspector exposes status, usage, and context data from the active session, including transcript and context fallbacks, so /status, /cost, and /context can render structured desktop UI instead of raw terminal text. The inspector and help surfaces now use the existing desktop i18n catalogs for English and Chinese labels.
Constraint: Desktop read-only slash commands must not spawn duplicate CLI processes or depend on submitting a normal user turn.
Rejected: Render raw CLI command text in chat | it keeps terminal-specific layout constraints and does not fit the desktop panel UX.
Confidence: high
Scope-risk: moderate
Directive: Keep /status, /cost, and /context routed through the local inspector unless the CLI exposes a structured interactive command protocol.
Tested: cd desktop && bun run lint
Tested: cd desktop && bun test src/components/chat/composerUtils.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run build
Tested: agent-browser smoke test on http://127.0.0.1:2024/ for /context localized inspector
Tested: git diff --check
Not-tested: Full packaged Tauri desktop build.
Desktop had no reliable handling for common local slash commands, so
commands like /clear, /help, /context, /cost, and /compact either looked
unresponsive or lost their CLI-specific side effects. This routes desktop
commands by behavior: local panels stay local, stateful clear resets the
session transcript, and CLI-local outputs are rendered in chat.
Constraint: Preserve existing CLI semantics for prompt, local, local-jsx, and compact slash commands
Rejected: Send every slash command through the normal chat path | local commands need desktop UI or session state side effects
Confidence: high
Scope-risk: moderate
Directive: Keep hidden aliases such as /plugins out of the visible command list unless they become canonical CLI commands
Tested: cd desktop && bun run test src/components/chat/composerUtils.test.ts src/stores/chatStore.test.ts --run
Tested: cd desktop && bun run test src/__tests__/pages.test.tsx --run -t "ActiveSession routes /plugin|ActiveSession routes /help|EmptySession slash picker includes dynamic skills"
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: agent-browser E2E for /help, /plugin, /plugins, /cost, /clear, /context, /compact, /mcp, and normal text input
Not-tested: Root tsc project because it currently includes generated Tauri target assets and extracted native files
The desktop model picker now stores a session-scoped provider/model selection instead of relying on the global active provider. That selection is replayed on connect, passed into the CLI startup path, and preserved across turns until the user changes it again.
To make that true end-to-end, the server now restarts the session process when runtime selection changes, injects provider-scoped env for third-party providers, and routes proxy traffic by provider id. The selector UI was also tightened so provider grouping stays visible while the actual model choice remains readable.
Constraint: Different providers can expose the same model id, so chat runtime selection cannot be derived from model id alone
Constraint: A desktop session reuses one CLI subprocess across turns, so runtime changes must restart that process to take effect
Rejected: Keep using Settings active provider as the chat selector | conflates defaults with live session state and breaks overlapping models
Rejected: UI-only runtime switching without server restart | later turns would continue using the old CLI subprocess configuration
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep provider defaults and session runtime overrides separate, and preserve provider-scoped proxy routing when extending model selection surfaces
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/stores/chatStore.test.ts
Tested: cd desktop && bun run test src/__tests__/generalSettings.test.tsx
Tested: bun test src/server/__tests__/conversation-service.test.ts
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: bun -e "await import('./src/server/services/titleService.ts'); await import('./src/server/ws/handler.ts')"
Not-tested: Real third-party provider round-trip from the desktop UI against a live upstream account
Desktop plugin details now route into the shared Skills, Agents, and MCP management surfaces instead of maintaining separate read-only drilldowns. This also extends the desktop/server skill aggregation so plugin-provided skills appear in the shared list, groups MCP entries by source, and preserves detail-view back navigation based on where the user entered the page.
The implementation keeps plugin detail as the high-level capability hub while pushing real inspection into the existing management pages. Disabled plugins no longer expose false navigation paths into shared views, and the agent-browser regression script was expanded to exercise the new end-to-end flows.
Constraint: Shared Agents data only includes enabled plugin agents, so disabled plugins cannot deep-link into agent detail
Rejected: Keep duplicating full Skills/Agents/MCP detail inside Plugin detail | creates divergent UI flows and stale data paths
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: If a detail view can be opened from multiple entry points, keep the return target in store state rather than hardcoding a single back destination
Tested: desktop vitest for plugins/skills/agents/mcp; desktop tsc --noEmit; desktop vite build; server skills API test; agent-browser web regression on plugin->skill/mcp and plugin->agent back navigation
Not-tested: packaged desktop app regression after rebuilding the Tauri bundle
Merged the desktop MCP management work into local main and retained
existing plugin settings affordances while resolving router and settings-tab
conflicts. The resulting flow keeps MCP settings global-only for speed and
uses slash-command cards to route users into concrete MCP or skill targets.
Constraint: local main already had plugin settings and API routes that had to remain available
Rejected: Favor the worktree version wholesale during merge | would have dropped local plugin tab and router support on main
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep slash-command entry flows and settings tabs additive during future merges; do not collapse MCP and plugin navigation into one another
Tested: Conflict resolution review during merge; source commit 66edded validated with bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- pages.test.tsx mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Re-running full desktop/manual verification from the merged main worktree after merge completion
Related: 66edded
Desktop MCP management now has a working server API, a global-only settings surface,
and slash-command entry points that surface MCP and skills from the composer before
routing users into the right settings view.
Constraint: Project-scoped MCP browsing in settings was too slow and noisy because it scanned multiple workdirs
Rejected: Keep project MCP aggregation on the settings homepage | duplicated entries and poor responsiveness
Rejected: Route /mcp directly on Enter without an intermediate card | removed the user's ability to choose a specific target first
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep settings focused on global MCP; add project-scoped MCP affordances in the chat-context slash surfaces instead of re-expanding the settings homepage
Tested: bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- pages.test.tsx mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Manual IAB verification after this final commit/merge cycle
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
The desktop app previously lost project-level skills in two places: the
settings browser only queried user skills, and the slash-command picker for a
fresh session depended on CLI init state that does not exist before the first
turn. This change makes the skills APIs cwd-aware, falls back to project skill
loading before CLI init, syncs sidebar session deletion with open tabs, and
aligns the empty-session composer styling so the pre-message session UI stays
consistent.
Constraint: Fresh desktop sessions need slash-command discovery before the CLI websocket emits system/init
Constraint: Only repository code should be committed; build artifacts, installs, and /tmp smoke fixtures stay out of git
Rejected: Rebuild slash-command listings from full getCommands() in the sessions API | introduced auth-gated command dependencies unrelated to local skill discovery
Rejected: Unify EmptySession and ChatInput into one full component now | higher regression risk for normal chat interactions than a visual-only hero variant
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep pre-message skill discovery keyed to session workDir so project-level .claude/skills remain visible before the first turn
Tested: bun test src/server/__tests__/sessions.test.ts; cd desktop && bun run test -- --run src/__tests__/skillsSettings.test.tsx src/components/layout/Sidebar.test.tsx src/__tests__/pages.test.tsx src/pages/ActiveSession.test.tsx; cd desktop && bun run lint
Not-tested: Manual desktop smoke test against the rebuilt .app bundle in the local GUI
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
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
Instead of close tab → delete session → create session → open tab (which
causes visual flicker and tab jumping), create the new session first then
replace the tab's sessionId in-place via a new replaceTabSession method.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Settings and Scheduled Tasks now open as special tabs in the TabBar,
matching IDE conventions (closeable, icon-prefixed, persistent)
- New Session directly creates a session with the current workDir
- ChatInput shows DirectoryPicker before first message, locks after
- Remove connection status from StatusBar (unnecessary noise)
- ContentRouter routes by tab type instead of activeView
- Settings page no longer has a back-button header
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Fix FileSearchMenu and slash command popups appearing incorrectly in
EmptySession by adding relative positioning to container and absolute
positioning to slash menu. Prevent IME composition Enter from triggering
message send in both ChatInput and EmptySession.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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.
- Input @ in composer to open file search popup showing project root
- Filter files/directories by typing, navigate with keyboard or mouse
- Click directory to enter subdirectory, type / to navigate deeper
- Enter selects item and inserts path text at cursor, popup closes
- Parse filter path (e.g. @src/components/) to auto-navigate and search
- Desktop: filesystem browse/search API supports includeFiles and search
- Web: directory tree browser with real-time search filtering
The desktop app now keeps the composer stable while turns are active,
reduces low-signal tool noise in the transcript, restores project context
under the composer after session creation, and relies on the CLI's own
permission requests instead of injecting broader desktop-side Bash asks.
This also brings in the supporting desktop app source tree and the server
routes/session metadata needed for git info, filesystem browsing, session
resume, slash commands, and SDK-backed permission bridging so the UI can
operate as a coherent feature instead of a partial patch.
Constraint: Desktop transcript needs to stay usable during long multi-tool sessions without hiding file-change diffs
Constraint: Permission prompts must mirror CLI behavior closely enough that read-only commands do not get desktop-only prompts
Rejected: Keep rendering Read/Bash bodies inline | too noisy and unlike the intended transcript model
Rejected: Commit only the touched desktop files | would leave the newly introduced desktop app incomplete in git history
Confidence: medium
Scope-risk: broad
Reversibility: messy
Directive: Treat non-writing tools as summary-first transcript events; do not re-expand them by default without validating the UX against long sessions
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run
Tested: bun test src/server/__tests__/conversations.test.ts
Not-tested: Manual visual regression against the exact screenshots in a live desktop session
Not-tested: Full root TypeScript check (repository still has unrelated extracted-native parse failures)