Workspace file preview is a local desktop surface, so image files should not be blocked by the text preview byte cap. Large text files now return a bounded preview instead of an unusable too-large state, while binary files remain explicitly unsupported.
Constraint: Avoid unbounded text payloads that can freeze the desktop renderer
Rejected: Remove all read limits | large generated files can still overload JSON transport and syntax rendering
Confidence: high
Scope-risk: moderate
Directive: Keep image preview detection before text-size limiting so local images remain renderable
Tested: bun test src/server/__tests__/workspace-service.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run build
Not-tested: Full packaged macOS smoke after this commit
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
Closing the desktop window should not tear down the local server when users expect the app to keep serving background work. The Tauri layer now owns that lifecycle: close hides the main window, tray actions restore or quit, and true quit paths still stop sidecars.
Constraint: Issue #213 asks for Windows close-to-background behavior, and macOS should follow the same desktop lifecycle.
Rejected: Frontend-only close handling | native window close events and OS menu actions need one Tauri-owned path.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the explicit quit marker unless all updater, tray, and app-exit paths are rechecked.
Tested: cargo fmt --manifest-path desktop/src-tauri/Cargo.toml --check
Tested: cargo check --manifest-path desktop/src-tauri/Cargo.toml
Tested: cargo test --manifest-path desktop/src-tauri/Cargo.toml
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Windows cargo check on this macOS machine; x86_64-pc-windows-msvc cross-check stopped in ring because the host lacks Windows C/MSVC headers.
Related: https://github.com/NanmiCoder/cc-haha/issues/213
The release note is now the GitHub Release source of truth, and the desktop version files have been moved together so the tag-triggered workflow can package the same version consistently.
Constraint: release-desktop.yml reads release-notes/v0.1.8.md from the tagged commit
Rejected: Push tag without a release commit | workflow requires version files and notes in the tag
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the release notes filename aligned with the tag before pushing future releases
Tested: bun run scripts/release.ts 0.1.8 --dry
Tested: bun run scripts/release.ts 0.1.8
Not-tested: Remote GitHub Actions build before pushing the tag
Session inspection was coupling quick status and usage rendering to live context control requests, so a slow get_context_usage path could leave the desktop inspector stuck on a loading state. The desktop panel now loads basic inspection data first, renders a transcript-based context estimate immediately, and only asks for live context details as a background refinement.
The translation hook now returns a stable function per locale so effects that depend on translation do not reset and re-fetch after every render.
Constraint: Third-party provider sessions may not expose reliable live context capabilities through the control request path.
Rejected: Keep waiting on live get_context_usage for the context tab | it recreates the user-visible loading stall.
Rejected: Hardcode provider-specific context windows | provider capabilities are not always known from the desktop session record.
Confidence: high
Scope-risk: moderate
Directive: Do not make the inspector first render depend on live CLI control requests without a transcript or cached fallback.
Tested: bun test src/server/__tests__/conversations.test.ts -t "structured session inspection|Sonnet 4.6 transcript usage"
Tested: cd desktop && bun run test -- --run src/i18n/index.test.tsx
Tested: cd desktop && bun run build
Tested: git diff --check
Tested: agent-browser verified /context renders transcript estimate without Loading context data
Not-tested: Provider-specific true context-window discovery for every third-party vendor
Third-party Anthropic-compatible providers can expose Claude model names without supporting every first-party runtime capability. Desktop sessions now preserve provider-specific capability overrides, keep selected provider runtime state across reconnects and restarts, and keep the provider dialog behavior aligned with the updated presets.
Constraint: Custom ANTHROPIC_BASE_URL endpoints must not be treated as first-party Anthropic for adaptive thinking support.
Rejected: Rename provider default Sonnet models | that would hide the compatibility issue instead of fixing runtime capability detection.
Confidence: high
Scope-risk: moderate
Directive: Do not enable Claude first-party capability defaults for custom Anthropic-compatible base URLs without provider verification.
Tested: bun test src/utils/__tests__/thinking.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/components/shared/Modal.test.tsx --run
Tested: git diff --check
Provider setup now keeps API keys hidden by default while still allowing an explicit reveal, and the About page links users directly to release notes and issue feedback. Provider preset copy was also adjusted so promotional text stays intentional and current.
Constraint: Reuse the existing settings page structure and provider preset schema
Confidence: high
Scope-risk: moderate
Reversibility: clean
Tested: cd desktop && bun run test -- generalSettings.test.tsx
Not-tested: Full desktop lint and production build
Provider setup now carries the metadata needed for sponsored and local Anthropic-compatible providers, while keeping provider URLs and API keys editable in the desktop form. The desktop UI also exposes API-key links, optional sponsor copy, full key visibility, and local no-key presets that can be activated into runtime settings.
Constraint: Local LM Studio and Ollama integrations require Anthropic-compatible root URLs rather than OpenAI /v1 URLs
Rejected: Keep local providers at the top of the preset list | user requested them immediately before Custom
Confidence: high
Scope-risk: moderate
Directive: Do not change local provider base URLs to /v1 without rechecking Anthropic compatibility docs
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/__tests__/generalSettings.test.tsx --run
Tested: git diff --cached --check
Tested: Web UI provider creation plus real sessions for LM Studio, Ollama, JiekouAI, and Shengsuanyun
Not-tested: Production packaged Tauri build
The desktop terminal already supported independent PTY sessions, but it only lived inside settings. This change promotes it to a first-class tab workflow so users can open multiple host terminals without leaving the chat-oriented desktop layout.
Constraint: Tauri terminal sessions are process-backed and must stay mounted while switching tabs.
Rejected: Reuse the settings terminal as a navigated page only | it cannot support multiple independent terminal tabs.
Rejected: Hide inactive xterm panes with display none | xterm lost visible output after tab switches during E2E.
Confidence: high
Scope-risk: moderate
Directive: Keep inactive terminal panes mounted and avoid display none unless xterm repaint behavior is reverified.
Tested: bun run test src/components/layout/ContentRouter.test.tsx src/components/layout/Sidebar.test.tsx src/components/layout/TabBar.test.tsx src/pages/TerminalSettings.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: ./scripts/build-macos-arm64.sh
Tested: Computer Use E2E against build-artifacts/macos-arm64 app for multiple terminals, command output retention, tab switching, and terminal cleanup
Not-tested: Intel macOS package
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
The model selector reused the light-theme primary fixed surface for selected rows, which made dark mode render a pale block with poor text contrast. This gives the selector its own selected-row tokens so dark mode can keep a low-luminance surface while light mode preserves the existing look.
Constraint: Keep the fix scoped to desktop theme tokens and the existing selector component
Rejected: Change primary-fixed globally | too broad because other components rely on that token semantics
Confidence: high
Scope-risk: narrow
Directive: Do not point selected model rows back to primary-fixed without checking dark-mode contrast
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Not-tested: Browser screenshot capture blocked by an existing Chrome DevTools profile instance
The context tab needs both absolute token counts and percent-of-window values to stay scannable. Category rows now show the percent next to each token count, and the free-space summary card also reports remaining percent.
Constraint: Keep the compact terminal-inspired layout while restoring the numeric detail users rely on.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: agent-browser smoke test on http://127.0.0.1:2024/ for /context percentages
Not-tested: Packaged Tauri build.
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 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 local Apple Silicon packaging script produced app bundles that could
fail strict bundle validation because the outer .app had no sealed
resources. The fix shallow-signs only the copied canonical app bundle and
checks that the claude-sidecar code-signature hash stays unchanged.
Constraint: GitHub Actions release packaging must remain on the existing tauri-action path
Constraint: macOS Keychain ACLs are sensitive to sidecar code-signature hashes
Rejected: Set macOS signingIdentity in release CI | Tauri re-signs nested sidecars and can change Keychain caller identity
Confidence: high
Scope-risk: narrow
Directive: Do not deep-sign claude-sidecar without proving existing Keychain ACLs still work after upgrade
Tested: bash -n desktop/scripts/build-macos-arm64.sh
Tested: SKIP_INSTALL=1 desktop/scripts/build-macos-arm64.sh
Tested: codesign --verify --deep --strict --verbose=2 desktop/build-artifacts/macos-arm64/Claude\ Code\ Haha.app
Tested: open -n desktop/build-artifacts/macos-arm64/Claude\ Code\ Haha.app and /health returned ok
Not-tested: GitHub Actions release workflow, intentionally unchanged
Desktop slash command handling needed a few follow-up guards after adding
CLI parity. Bare /clear remains a local desktop reset, but /clear with
arguments is rejected instead of silently discarding user text. Clear also
resets cached slash commands, compact boundaries preserve CLI-provided text,
and the help panel now discloses when the More section is truncated.
Constraint: Preserve the desktop /clear fast path without accepting accidental arguments
Rejected: Let /clear arguments fall through to the CLI | the CLI command still clears context and would keep the footgun
Confidence: high
Scope-risk: narrow
Directive: Keep desktop-local slash command guards in sync with CLI command argument semantics
Tested: cd desktop && bun run test src/stores/chatStore.test.ts src/__tests__/pages.test.tsx --run -t "clears local desktop chat state|ActiveSession routes /help"
Tested: bun test src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full pages.test.tsx because unrelated locale-sensitive assertions are already present in that file
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 release workflow reads release-notes/v0.1.6.md from the tagged commit, so this commit aligns version metadata, lockfile state, and release copy before creating the v0.1.6 tag.
Constraint: GitHub Actions packaging is triggered by pushed v*.*.* tags, not by a plain main push
Constraint: The release script expects release-notes/vX.Y.Z.md to exist in the tagged commit
Rejected: Push main without a tag | that would not start the desktop release workflow
Confidence: high
Scope-risk: narrow
Directive: Keep version files, Cargo.lock, release notes, and the release tag aligned for desktop releases
Tested: bun run scripts/release.ts 0.1.6 --dry
Tested: git diff --cached --check
Not-tested: Remote GitHub Actions run before pushing the tag
Desktop sessions could show MCP configuration in the UI while the CLI SDK child process still started cold on the first user message. Start eligible desktop chat sessions as soon as their websocket is connected, keep init metadata cached but muted, and wait briefly for MCP startup in SDK print mode so turn one sees the tools.
Constraint: Desktop wraps the existing CLI SDK bridge rather than owning MCP startup directly.
Rejected: Prewarm every restored tab | synthetic tabs such as settings would start unnecessary CLI subprocesses.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the synthetic-tab guard without validating restored settings and scheduled-task tabs in the browser.
Tested: bun test src/server/__tests__/conversations.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- chatStore.test.ts mcpSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: agent-browser E2E new session prewarm plus first message reuse with mock CLI
Not-tested: Full desktop Vitest suite still has pre-existing English-vs-Chinese copy assertion failures.
Plugin actions refresh the selected plugin detail after mutating enabled state. The detail component can enter its loading branch while a plugin is still selected, so every hook must run before that early return.
Constraint: React hook order must stay identical across the detail and loading render paths.
Rejected: Clear the selected plugin before reload | would hide the current context and add UI churn during short refreshes.
Confidence: high
Scope-risk: narrow
Directive: Keep PluginDetail hooks before loading and empty-state returns when adding detail-derived memoization.
Tested: cd desktop && bun run test src/__tests__/pluginsSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Full desktop Vitest suite has pre-existing locale assertion failures unrelated to this plugin fix.
Desktop sessions can switch provider and model while a CLI subprocess is already alive, so the server now serializes runtime restarts and marks provider-managed launches to prevent stale settings env from overriding the selected provider. Provider settings also write API key env consistently and clear stale managed keys before syncing.
This includes the related desktop/docs brand asset refresh and keeps the desktop locale default in Chinese, with tests updated to match the current provider semantics.
Constraint: Session-scoped model selection must win over cc-haha/settings.json and inherited ANTHROPIC_* values.
Rejected: Store the selected model as a global provider activeModel | chat runtime selection is per session.
Confidence: high
Scope-risk: moderate
Directive: Do not remove CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST without validating Desktop provider switching against stale settings env.
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversations.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/providers-real.test.ts
Tested: cd desktop && bun run test src/stores/settingsStore.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
Not-tested: Full desktop production package/signing.
The detached worktree implementation adds a Settings terminal for command-based setup flows. The merge keeps the target branch's current adapter/runtime dependencies while adding the new portable-pty backend, xterm frontend, UTF-8 output handling, and host-shell environment propagation.
Constraint: Target branch had newer desktop dependency changes, including anyhow in the Tauri crate.
Constraint: Existing uncommitted work in the main worktree had to stay out of this merge commit.
Rejected: Fast-forward the checked-out branch under a dirty worktree | would risk mixing unrelated local changes into the branch state.
Confidence: high
Scope-risk: moderate
Directive: Treat the terminal as host-shell integration; keep PATH and UTF-8 locale handling together when changing startup behavior.
Tested: cargo fmt --check
Tested: cargo test --lib
Tested: cargo check
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test src/pages/TerminalSettings.test.tsx
Tested: git diff --check
Not-tested: Rebuilt macOS DMG from the target branch after merge.
Some setup flows end in a shell command instead of a natural-language install path, so Settings now exposes a host PTY terminal backed by portable-pty and xterm. The terminal inherits the user's login-shell environment, forces a UTF-8 locale when needed, and preserves split UTF-8 output so Chinese paths render correctly.
Constraint: Desktop GUI apps do not inherit the user's interactive shell PATH on macOS.
Constraint: Command output may split UTF-8 characters across PTY reads.
Rejected: Use Tauri shell commands only | users need an interactive PTY for copy-pasted install flows.
Rejected: Ask users to edit shell profiles | terminal setup should work out of the box.
Confidence: high
Scope-risk: moderate
Directive: Keep terminal startup tied to host environment checks; do not bundle runtimes to solve PATH issues.
Tested: cargo fmt --check; cargo test --lib; cargo check; cd desktop && bun run lint; cd desktop && bun run test src/pages/TerminalSettings.test.tsx; cd desktop && bun run build; cd desktop && bun run build:macos-arm64
Tested: Computer Use verified npm is available in the built macOS app terminal and Chinese output renders after UTF-8 decoding fix.
Not-tested: Native Windows/Linux package runtime validation.
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
The Install Center now spells out the two supported terminal flows after the
bundled launcher is available: keep using `claude` when the official Claude
Code CLI is already installed, or use `claude-haha` otherwise. It also shows
copyable example commands so users understand that Skills, Plugins, and MCP
configuration is shared between both command names.
Constraint: The worktree contains unrelated icon, provider-label, and other UI edits, so this commit stages only the Install Center hint copy and its focused test
Rejected: Leave the launcher status card without command guidance | users would still not know when to use `claude` versus `claude-haha`
Rejected: Duplicate the same explanation in a new settings section | it adds UI weight without improving discoverability over the existing CLI card
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep the examples aligned with the real supported command surface; if launcher behavior changes, update both zh/en copy and the InstallCenter test together
Tested: cd desktop && bun x vitest run src/components/settings/InstallCenter.test.tsx; cd desktop && bun run lint
Not-tested: Full desktop production build and manual Settings page click-through after this copy-only follow-up
The desktop app already shipped a bundled sidecar, but only desktop-managed
sessions could see it. This change installs a `claude-haha` launcher into the
user bin directory, wires PATH setup so new terminals can resolve it, and keeps
desktop installer sessions aligned on the same bundled sidecar resolution path.
The desktop install surface now also reports whether the launcher is ready or
still waiting on a terminal restart.
Constraint: The worktree already contains unrelated icon, docs, and UI changes, so this commit stages only the bundled CLI launcher slice
Rejected: Tell users to install the official Claude CLI separately | it breaks the desktop out-of-box install story
Rejected: Keep the bundled CLI reachable only inside desktop-managed shells | system terminals would still be unable to call the packaged runtime
Rejected: Symlink directly into the app bundle instead of copying to user bin | moving or replacing the app bundle would leave a stale launcher behind
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Proxy-backed non-Anthropic providers still depend on the desktop server; do not assume this launcher makes every provider fully standalone
Tested: bun test src/server/__tests__/desktop-cli-launcher.test.ts src/server/__tests__/settings.test.ts; bun test src/server/__tests__/conversation-service.test.ts; bun test src/utils/shell/bashProvider.test.ts; cd desktop && bun x vitest run sidecars/launcherRouting.test.ts src/components/settings/InstallCenter.test.tsx; cd desktop && bun run lint; cd desktop && bun run build; cargo check --manifest-path desktop/src-tauri/Cargo.toml
Not-tested: Manual packaged desktop app install plus real Terminal/iTerm/PowerShell invocation on fresh macOS and Windows machines
Desktop chat currently reuses one status channel for assistant streaming and
background task progress. When a task update arrived mid-turn, the store
flushed the in-flight markdown into a completed message, which caused the
remaining deltas to render as a second bubble and broke markdown styling.
Keep the reply in streaming state until the turn actually becomes idle, and
lock the behavior with a regression test that injects task progress between
text deltas.
Constraint: Background task progress currently shares the same status channel as assistant streaming
Rejected: Retune markdown renderer styles | would hide the symptom without preserving message integrity
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Do not flush streaming assistant text on non-idle status transitions unless the turn is actually ending
Tested: bun run test src/stores/chatStore.test.ts; bun run lint
Not-tested: Manual desktop UI verification with a live agent session
The Settings terminal added a full xterm plus Tauri PTY stack for a job
that is better handled by dedicated install and configuration flows. This
change removes the Settings tab, frontend terminal wiring, Tauri terminal
commands, and the terminal-only dependencies so the desktop settings
surface stays narrower and less fragile.
Constraint: The worktree already contains unrelated desktop icon and UI changes, so this commit stages only the terminal-removal slice
Rejected: Keep a hidden or runtime-only terminal stub | it would still preserve the heavy cross-layer maintenance surface
Rejected: Remove only the Settings tab and leave the Tauri PTY backend | that would leave dead code and unused dependencies behind
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: If future install workflows need more power, prefer Settings-native actions and runtime refresh over reintroducing a general shell tab
Tested: bun x vitest run src/__tests__/generalSettings.test.tsx src/__tests__/skillsSettings.test.tsx src/__tests__/pluginsSettings.test.tsx src/__tests__/agentsSettings.test.tsx src/__tests__/mcpSettings.test.tsx src/components/settings/InstallCenter.test.tsx; bun run lint; bun run build; cargo check --manifest-path desktop/src-tauri/Cargo.toml
Not-tested: Manual packaged desktop app click-through after removing the Settings terminal tab
Settings needed a real shell for plugin, MCP, and skill setup without relying on
a globally installed Claude CLI. Add an xterm.js terminal backed by portable-pty,
wire it into the Tauri desktop runtime, and move shell restart handoff to the
new session before old PTY teardown so the UI is less likely to stall behind
child shutdown.
Constraint: The desktop app must inject the bundled CLI into the shell environment instead of requiring a separate global install
Constraint: Restart teardown cannot block the frontend-facing Tauri command path
Rejected: Keep terminal setup inside installer chat only | that flow cannot replace an interactive shell
Rejected: Wait for old PTY shutdown before adopting the new session | it keeps restart vulnerable to hung child teardown
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Preserve new-session handoff before old-session cleanup when changing terminal lifecycle or restart logic
Tested: bunx vitest run src/__tests__/terminalPanel.test.tsx src/components/settings/TerminalPanel.restart.test.tsx; bun run lint; cargo check
Not-tested: Full packaged-app command echo and repeated manual restart behavior still need additional runtime verification
The desktop Settings flow needed a real shell for bundled CLI setup, but the
restart path could hang behind old PTY teardown. Wire an xterm.js panel to a
portable-pty backend, inject the bundled CLI into the shell bootstrap, and
switch to the new session before cleaning up the previous one so restart work
stays off the frontend critical path.
Constraint: The desktop app must ship its own CLI entrypoint instead of depending on a global Claude install
Constraint: PTY teardown must not block the Tauri invoke path
Rejected: Reuse the install chat for arbitrary shell commands | it does not provide a real interactive PTY
Rejected: Close the old PTY before adopting the new session | it keeps restart vulnerable to hung child shutdown
Confidence: medium
Scope-risk: moderate
Reversibility: clean
Directive: Keep PTY teardown off the invoke path and preserve session handoff ordering when changing terminal lifecycle code
Tested: bunx vitest run src/__tests__/terminalPanel.test.tsx src/components/settings/TerminalPanel.restart.test.tsx; bun run lint; cargo check
Not-tested: End-to-end command echo inside the packaged desktop terminal still needs follow-up runtime verification
The desktop settings and sidebar still had deletion flows that either used browser-native confirms or deleted immediately. This change consolidates destructive confirmations behind a shared dialog component, applies it to provider deletion, plugin uninstall, adapter unbind, and sidebar session deletion, and adds regression coverage so delete actions require an explicit second confirmation before mutating state.
Constraint: Other in-progress desktop work in the tree had to stay out of this commit
Constraint: Existing MCP and task confirmations needed to keep their current behavior
Rejected: Leave confirmations embedded per-page with browser dialogs | inconsistent UX and easy to regress
Rejected: Add confirmations only to provider deletion | leaves other destructive desktop flows unsafe
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any new desktop delete, uninstall, or unbind action should use the shared ConfirmDialog instead of browser-native dialogs or one-click deletion
Tested: bun run test src/components/layout/Sidebar.test.tsx src/__tests__/generalSettings.test.tsx; bun run lint
Not-tested: Manual desktop click-through of every destructive action after this refactor
The desktop provider flow was still mixing provider-managed state with
`~/.claude/settings.json`, which let unrelated tools leak fields like
`ANTHROPIC_REASONING_MODEL` and `model` back into the active provider path.
This change moves the provider JSON editor onto `~/.claude/cc-haha/settings.json`,
routes provider settings through dedicated `/api/providers/settings` endpoints,
and makes model reads/writes under an active provider use the managed cc-haha
settings instead of the global user settings file.
Constraint: Active provider model selection must be isolated from legacy ~/.claude/settings.json
Rejected: Keep merging provider JSON with settingsApi.getUser() | external tools can reintroduce unrelated model fields into the provider flow
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Any future provider-model UI or runtime change should read/write cc-haha managed settings first, not the global user settings file
Tested: bun test src/server/__tests__/provider-presets.test.ts src/server/__tests__/settings.test.ts; cd desktop && bun run lint
Not-tested: Manual desktop provider modal interaction after the cc-haha settings API switch
The provider picker had two separate preset definitions and some defaults had
started to drift from the model IDs vendors document for Claude Code /
Anthropic-compatible usage. This change moves built-in presets to one
server-side JSON source, serves that source through the existing presets API,
and makes the desktop settings page consume the API instead of a duplicated
frontend constant. The preset defaults were then corrected against vendor docs
so the built-in values match documented model IDs and casing.
Constraint: Built-in provider defaults must match official vendor Claude Code or Anthropic docs
Rejected: Keep duplicated frontend and backend preset lists | values and casing drifted independently
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Update src/server/config/providerPresets.json and rerun provider-presets tests before changing built-in provider defaults again
Tested: bun test src/server/__tests__/provider-presets.test.ts; cd desktop && bun run lint
Not-tested: Manual desktop settings UI interaction after fetching presets from the API
The project filter and session search were competing for the same narrow vertical lane in the desktop sidebar. This change merges them into a single search surface, keeps project scoping available through an embedded trigger, and trims the surrounding control chrome so the sidebar reads like one cohesive tool instead of stacked widgets.
Constraint: Sidebar filtering behavior and existing project dropdown logic had to remain intact
Constraint: This commit must exclude unrelated in-progress desktop and server changes in the worktree
Rejected: Keep separate project and search controls with smaller spacing | still wastes vertical space and keeps the visual split
Rejected: Move project filtering into a second modal flow | adds friction to a frequent navigation task
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep project scoping embedded in the search lane unless session discovery changes materially
Tested: bun run test src/components/layout/ProjectFilter.test.tsx src/components/layout/Sidebar.test.tsx src/__tests__/pages.test.tsx; bun run lint; bun run build
Not-tested: Manual dark-theme screenshot pass after the final integrated layout change
The reconnect action could succeed, but the detail and edit views gave almost no page-level feedback beyond the button spinner. Mirror the reconnect lifecycle into the visible status surfaces so users can tell immediately that the request is in flight and whether it settled.
Constraint: Reconnect feedback must be visible without waiting for the list row to refresh
Rejected: Rely on toast only | too late and too easy to miss while staying on the detail page
Rejected: Add a separate transient banner | duplicates the existing status surfaces instead of updating them
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep reconnect state reflected in the page-level status badge and status field, not just the action button
Tested: cd desktop && bun run test -- mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Manual desktop click-through against a live MCP reconnect
Desktop MCP settings was synchronously probing every configured server from /api/mcp, which made the list page scale with connection latency and surface brittle behavior as installs accumulated more MCPs. Return lightweight snapshot rows from the list API, keep explicit status checks separate, and let the desktop UI refresh status in a constrained background lane while preserving project-aware server identity.
Constraint: MCP list must stay responsive even with many configured servers
Rejected: Probe all servers from the list view without limits | still fans out with server count and can overload slow installs
Rejected: Keep servers permanently unchecked until detail view | misses the desired loading feedback when the MCP page opens
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep /api/mcp as a configuration snapshot endpoint; do not reintroduce per-row live connect work on list load
Tested: bun test src/server/__tests__/mcp.test.ts; cd desktop && bun run test -- mcpSettings.test.tsx; cd desktop && bun x tsc --noEmit --ignoreDeprecations 5.0
Not-tested: Real desktop interaction against an environment with dozens of live MCP servers
The desktop transcript had drifted into multiple competing left edges.
Assistant bubbles, thinking rows, tool cards, permissions, and standalone
results were using different offsets, which made the timeline feel visually
broken even when the underlying data was correct.
This change removes the ad hoc assistant-side indentation and makes the
assistant output lane follow the same content rail as the composer. The
assistant message component still distinguishes short bubble replies from
markdown-heavy document replies, but both now sit on the same shared left
alignment. Supporting chat blocks were updated to use that same rail so the
whole transcript reads as one coherent column.
Constraint: Assistant transcript content must align with the composer rail, not with local per-block offsets
Rejected: Keep tool/thinking blocks on a separate inset lane | creates multiple left edges and keeps the transcript visually inconsistent
Rejected: Fix only final assistant replies | leaves the rest of the assistant-side timeline misaligned
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Treat the composer rail as the canonical left edge for all assistant-side transcript blocks unless the entire transcript layout is redesigned together
Tested: bun run test src/components/chat/MessageList.test.tsx --run; bun run lint; bun run build
Not-tested: Real Tauri runtime screenshot against a live session after this unified alignment change
Two desktop surfaces regressed in ways that made the app harder to read and operate.
The plugin header compressed summary cards and action buttons too early for the available width,
and the rewind/copied affordances in chat were sharing the same horizontal lane as message bubbles,
which visually broke the expected "user on the right, assistant on the left" transcript structure.
This commit keeps both fixes narrow: the plugin header now delays its split layout and lets summary
cards and controls reflow responsively, while chat message actions live inside each message column so
interaction affordances no longer distort the bubble alignment.
Constraint: Desktop settings and transcript layouts must remain readable in narrower window widths
Rejected: Keep message actions inline with the bubble row | action buttons keep stretching the message lane and blur role alignment
Rejected: Hide rewind and copy entirely until a larger redesign | removes the control instead of fixing the regression
Confidence: high
Scope-risk: narrow
Reversibility: clean
Directive: Keep transcript actions inside the message column unless a future redesign also redefines bubble alignment rules
Tested: bun run test src/__tests__/pluginsSettings.test.tsx --run; bun run test src/components/chat/MessageList.test.tsx --run; bun run lint; bun run build; agent-browser verification against local mock desktop session
Not-tested: Real Tauri runtime screenshot on a live backend session after these layout changes
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
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