Desktop muted CLI output until the user turn was fully enqueued, which dropped early local_command events for typed slash commands such as /goal. The transcript still recorded the goal, but the live desktop client missed the goal event and active-goal indicator. This forwards only the current typed command lifecycle through the pre-turn mute gate while keeping unrelated startup chatter and stale command output suppressed.
Constraint: Desktop must show /goal creation immediately while preserving the pre-turn mute gate for SDK startup noise
Rejected: Forward all pre-turn local command output | would reintroduce stale or unrelated CLI chatter into fresh desktop turns
Confidence: high
Scope-risk: narrow
Directive: Do not widen this gate without verifying /cost, /context, /goal, and reconnect startup behavior
Tested: bun test src/server/__tests__/ws-memory-events.test.ts src/server/__tests__/conversations.test.ts -t 'local command output|/cost|/context|goal|pre-turn mute'
Tested: bun run check:server
Not-tested: live desktop manual smoke after rebuilding/restarting the app
The goal flow needs to drive the agent loop and remain visible after desktop reconnects, so this change treats /goal output as a structured lifecycle signal across CLI, server, and desktop history restoration.
Constraint: The current TypeScript CLI does not share Codex's Rust app-server thread-goal database, so persistence is reconstructed from the existing session transcript.
Rejected: UI-only goal badges | would leave the CLI self-loop unable to recover active or completed goals after process restart.
Rejected: Add a new persisted store | larger migration surface than needed for the existing transcript-backed session model.
Confidence: high
Scope-risk: moderate
Directive: Keep future goal state changes mirrored in transcript-visible local command output or introduce a real migration-backed store.
Tested: bun test src/goals/goalEvaluator.test.ts src/goals/goalState.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx
Tested: bun run verify
Not-tested: Live provider baseline with a real model-driven goal loop.
Desktop plugin enable and disable actions are written by the server process, while an already-running CLI owns its own settings cache. Reloading only the plugin caches left enabledPlugins stale after a disable-then-enable cycle, so the slash command list stayed empty until a new conversation spawned a fresh CLI.
Reset the settings cache at the active plugin refresh boundary before resolving enabled plugins. The regression test simulates an external desktop enable toggle after a cached disabled read and verifies the plugin slash command is restored in the same refresh.
Constraint: Desktop server and active CLI are separate processes with independent in-memory settings caches
Rejected: Restart the CLI on every plugin toggle | heavier user-visible lifecycle change and unnecessary once the reload handoff invalidates settings
Confidence: high
Scope-risk: narrow
Directive: Keep reload_plugins as the handoff that rereads settings before plugin command discovery
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: bun run check:server
Sending from history must move the transcript to the new turn even if session state has not yet advanced from idle. The previous guard tied the jump to chatState, so a user_text tail that landed before thinking/streaming would be skipped permanently because the tail id no longer changed.
Constraint: The same MessageList path serves desktop and H5 chat.
Rejected: Wait for chatState to become thinking before scrolling | that misses updates where the message id is already recorded.
Confidence: high
Scope-risk: narrow
Directive: New user_text tail messages are intentional navigation events and must override historical scroll position.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
iPhone Chrome uses the iOS WebKit viewport model, so using scrollIntoView on the transcript sentinel can ask the browser to scroll ancestor containers, including the page viewport. That is risky with the fixed bottom composer and can expose blank page space when the latest button is clicked. This keeps all latest-message movement scoped to the chat scroll element and follows post-render height changes while streaming.
Constraint: H5 and desktop share MessageList, but the H5 browser runs through iOS WebKit viewport behavior.
Rejected: Keep bottomRef.scrollIntoView with container options | container support is not reliable enough and the default still targets all scrollable ancestors.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce transcript scrollIntoView without real iPhone Chrome verification.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
The Settings zoom slider follow-up stops app-wide zoom from being applied continuously while the pointer is dragging, then commits the chosen value on release. This keeps the local main checkout aligned with the verified worktree fix.
Constraint: The slider was resizing the UI under the pointer when live zoom applied on every drag event
Confidence: high
Scope-risk: narrow
Directive: Keep pointer-drag zoom as local preview plus release commit; keyboard zoom remains live through the shared app zoom state
Tested: bun run check:desktop
Tested: agent-browser Settings smoke confirms drag preview keeps cssZoom at 1 until release, then commits 1.23
Dragging the Settings zoom slider was applying app-wide zoom on every range change. That resized the Settings panel and the range control under the pointer, creating visible flicker and a poor drag feel.
The slider now keeps drag movement local to the Settings preview and commits the shared app zoom only when the drag is released or when keyboard/non-pointer changes occur. The control step is also reduced to one percent so adjustments feel less coarse.
Constraint: App-wide zoom changes resize the control currently being dragged
Rejected: Throttle live app zoom during drag | still resizes the target under the pointer and can flicker on slower WebViews
Confidence: high
Scope-risk: narrow
Directive: Do not apply app-wide zoom continuously during pointer drag; keep live resizing to keyboard shortcuts and final slider commit
Tested: bun run check:desktop
Tested: agent-browser Settings smoke confirms drag preview keeps cssZoom at 1 until release, then commits 1.23
Bring the DingTalk terminal-card fix from the diagnostic worktree onto
local main while preserving the newer main-only plugin refresh commits.
Constraint: The worktree was based two commits behind local main
Rejected: Reset main to the worktree HEAD | would drop newer local main commits
Confidence: high
Scope-risk: narrow
Directive: Keep this merge as the integration point for the IM completion regression fix
Tested: bun test adapters/common/__tests__/message-buffer.test.ts adapters/dingtalk/__tests__/stream-state.test.ts adapters/dingtalk/__tests__/ai-card.test.ts
Tested: bunx tsc -p adapters/tsconfig.json --noEmit
Tested: bun run check:adapters
Tested: cd desktop && bun run build
Tested: bun run check:native
Not-tested: bun run check:coverage is blocked by existing desktop pages.test.tsx sidebar expectation unrelated to IM
DingTalk AI cards can receive all intermediate text before the final
message_complete event. In that case the message buffer is empty, but
the platform card still needs an explicit terminal update to leave the
inputing state.
Constraint: DingTalk AI cards expose completion separately from buffered text delivery
Rejected: Rely on MessageBuffer.complete only | an empty buffer cannot finalize an active card
Confidence: high
Scope-risk: narrow
Directive: Keep IM terminal-state updates independent from whether a text buffer has pending content
Tested: bun test adapters/common/__tests__/message-buffer.test.ts adapters/dingtalk/__tests__/stream-state.test.ts adapters/dingtalk/__tests__/ai-card.test.ts
Tested: bunx tsc -p adapters/tsconfig.json --noEmit
Tested: bun run check:adapters
Tested: cd desktop && bun run build
Tested: bun run check:native
Not-tested: bun run check:coverage is blocked by existing desktop pages.test.tsx sidebar expectation unrelated to IM
Plugin enablement already has a live CLI reload control path, so desktop now applies plugin changes by refreshing the active session instead of waiting for a future conversation startup. The server forwards reload_plugins to the active CLI session, refreshes session slash-command cache, and notifies the client. The desktop plugin store automatically reloads after mutating plugin state, and the empty-session composer refetches skills when plugin capabilities change.
Constraint: Existing CLI exposes reload_plugins as the supported hot-refresh mechanism for commands, agents, plugins, and MCP state.
Rejected: Start a hidden replacement CLI process | higher cost, extra process lifecycle risk, and less precise than the existing control channel.
Confidence: high
Scope-risk: moderate
Directive: Keep plugin refresh routed through reload_plugins unless the CLI control contract is removed or changed.
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: cd desktop && bun run test --run src/stores/pluginStore.test.ts src/__tests__/pluginsSettings.test.tsx src/pages/EmptySession.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: bun run check:desktop is blocked by a pre-existing color-mix compatibility failure in desktop/src/theme/globals.css.
H5 browsers can lag behind smooth scroll animations when streaming text grows, and sending a new prompt should always return the reader to the live turn even if they were browsing history. The message list now writes the scroll container directly while preserving the existing opt-out for manual history reading during assistant streaming.
Constraint: H5 and desktop share MessageList behavior
Rejected: Force-scroll on every streaming update | would break reading historical content during long replies
Confidence: high
Scope-risk: narrow
Directive: Preserve manual history reading except when a new user prompt is appended
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Not-tested: Full check:desktop remains blocked by the existing vite-config color-mix assertion in globals.css
The local main checkout already had a merge in progress for the Settings zoom synchronization fix. The conflict content in globals.css was resolved in the working tree, so this commit records that existing merge before landing the chat scroll fix.
Constraint: main had an existing MERGE_HEAD before the chat scroll landing
Confidence: medium
Scope-risk: narrow
Directive: Do not conflate this merge commit with the chat scroll fix; it only completes the pre-existing Settings zoom merge
Tested: Not run in this step
Not-tested: Settings zoom regression suite after recording the existing merge
The sidebar now groups conversations by project, so the embedded project picker in the search box duplicated the main navigation model and hid state inside a compact icon. Remove that picker and its store state, leaving search as plain text filtering and keeping the project header menu focused on organization and sorting.
The archive-all entry was also hidden because a broad destructive action does not belong in the lightweight project menu.
Constraint: Project grouping is now the primary project navigation surface.
Rejected: Keep the embedded project picker hidden in place | it would leave dead filtering state and a stale recovery path.
Rejected: Keep archive-all in the header menu | broad destructive actions are too risky for this surface.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce broad session deletion or hidden project filters into the sidebar header without a dedicated reviewed management flow.
Tested: cd desktop && bun run test src/components/layout/Sidebar.test.tsx src/stores/sessionStore.test.ts src/components/layout/TabBar.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Settings and keyboard zoom controls were able to diverge because the slider only committed on release and captured pointer events around the native range control. The slider now commits through the shared zoom store as it changes, while the range keeps its native drag behavior.
The full desktop gate also enforces Safari 15 compatibility for startup CSS, so the remaining memory theme color-mix tokens were replaced with static equivalents.
Constraint: macOS 12 Safari 15 WebView cannot rely on CSS color-mix in startup-critical desktop CSS
Rejected: Commit Settings zoom only on pointer release | leaves shortcut state and slider state temporarily out of sync
Confidence: high
Scope-risk: narrow
Directive: Keep Settings slider, reset, and keyboard shortcuts routed through the shared uiZoom/appZoom state
Tested: bun run check:desktop
Tested: agent-browser Settings zoom smoke against local Vite plus server
Not-tested: Packaged Tauri app manual drag on macOS hardware
The detached worktree carried the verified desktop Thinking fixes, while local main already had one newer memory-path commit. This merge brings the provider/runtime Thinking behavior into main without rewriting the existing main history.
Constraint: Local main is the checked-out integration branch and was ahead of the worktree base
Rejected: Rebase the detached worktree onto main | unnecessary history rewrite for a local integration request
Rejected: Fast-forward main | impossible because main already had newer local commits
Confidence: high
Scope-risk: moderate
Directive: Keep c866adc9 as the feature commit for detailed implementation context
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run verify before integration on the feature commit
Not-tested: Full verify after the merge commit; merge was conflict-free except automatic conversationService integration
Desktop sessions must treat the global Thinking toggle as the source of truth across active CLI sessions, title generation, and provider-compatible request bodies. DeepSeek-style streams can start with reasoning blocks before text, so the WebSocket bridge now keeps the UI in thinking state until text content actually starts.
Constraint: DeepSeek and MiniMax use Anthropic-compatible surfaces but expose thinking behavior through provider-specific request and stream shapes
Rejected: Keep provider-specific disabled-thinking env defaults | stale defaults kept overriding the user's global Thinking setting
Rejected: Treat every non-tool stream block as text | reasoning-only startup blocks created a blank streaming gap before the visible thinking bubble
Confidence: high
Scope-risk: moderate
Directive: Do not change thinking stream state transitions without replaying reasoning-before-text provider events
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:server
Tested: bun run verify
Not-tested: Live DeepSeek provider smoke with real quota
Desktop memory settings already resolves project memory through the current sanitized project directory, while spawned CLI sessions could compute an older path variant and fail to read indexed memory files. Pin the child runtime to the same memory directory and let memory preview links open related markdown files directly from the rendered panel.
Constraint: Existing memory files may already live under both legacy underscore and current hyphenated project directories.
Rejected: Symlink or merge memory directories | it could mix stale legacy memory entries into the active project index.
Confidence: high
Scope-risk: moderate
Directive: Keep Settings memory discovery and spawned CLI memory context on the same project identity before changing either sanitizer.
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/memory.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- MemorySettings MarkdownRenderer
Tested: cd desktop && bun run build
Tested: Browser smoke on local Vite/server for memory preview link navigation
Not-tested: bun run check:desktop still has pre-existing vite-config color-mix guard failure in desktop/src/theme/globals.css
The session sidebar already uses project grouping, so the change adds header-level project actions instead of replacing project row controls. Sorting and organization choices are persisted through the existing desktop UI preferences file and mirrored into local cache for startup continuity.
Constraint: Keep the existing project tree, drag ordering, pinning, hiding, and Finder project-row actions intact.
Rejected: Replacing project row actions with the header menu | row-level Finder and per-project session actions are still useful and already covered by tests.
Confidence: high
Scope-risk: moderate
Directive: Do not store sidebar organization in global Claude settings; keep it under cc-haha desktop-ui preferences.
Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx
Tested: bun test src/server/__tests__/desktop-ui-preferences.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Sidebar project ordering, pinning, and hidden-project state must survive
browser access to the same local server, so the UI now stores these
preferences under the cc-haha config directory and keeps localStorage as
a migration/cache fallback.
Constraint: Browser and H5 localStorage is isolated from the Tauri WebView
Rejected: Keep sidebar project preferences only in localStorage | browser sessions would not share state
Rejected: Reuse cc-haha/settings.json | provider and H5 access settings should stay separate
Confidence: high
Scope-risk: moderate
Directive: Keep sidebar hide/remove semantics non-destructive; do not delete transcript files for project removal
Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx --run
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/desktop-ui-preferences.test.ts
Tested: bun run check:persistence-upgrade
Tested: bun run check:server
The desktop sidebar needed a project-first navigation model that keeps worktree sessions attached to their source project instead of scattering them as separate paths. The UI now renders sessions under project groups with project-level actions and persisted ordering, while the server exposes a logical project root for worktree transcripts.
Constraint: Existing memory directory tree work must remain separate from the session sidebar behavior.
Rejected: Group by raw transcript projectPath | worktree paths fragment the same repository into multiple sidebar projects.
Confidence: high
Scope-risk: moderate
Directive: Keep future sidebar grouping keyed by projectRoot before projectPath so isolated worktrees stay under their source repository.
Tested: cd desktop && bun run test -- src/components/layout/Sidebar.test.tsx
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: git diff --check
The memory resource tree should keep the project/file hierarchy, but the extra project action surface was not wanted for this page. This removes the pinning state, Finder action, and matching tests/i18n while leaving the tree layout intact.
Constraint: Preserve the existing memory directory tree changes.
Rejected: Revert the whole memory tree redesign | the user explicitly asked to keep the original directory tree work.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/__tests__/memorySettings.test.tsx
Tested: cd desktop && bun run lint
The memory resource tree needed to match the project-first interaction model: projects expand in place, project rows do not show noisy counts or explicit expand affordances, and common project actions live behind the row menu.
Constraint: Must reuse the existing open-target Finder/file-manager flow instead of shelling out from the view.
Rejected: Reuse the session sidebar project filter menu | it is a filter surface, not the memory resource tree.
Confidence: high
Scope-risk: narrow
Directive: Keep memory project actions backed by openTargetStore so platform-specific file managers stay centralized.
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- src/__tests__/memorySettings.test.tsx
Tested: cd desktop && bun run build
Tested: Playwright screenshot smoke of Settings > Memory project menu at /tmp/cc-haha-memory-ui.png
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
Desktop settings writes update settings.json through SettingsService, while plugin
state writes use the shared CLI settings cache. Reset the shared cache after a
successful atomic SettingsService write so later plugin updates merge against the
fresh file instead of an older in-memory snapshot.
Constraint: Desktop general settings and plugin state share ~/.claude/settings.json but use different write paths
Rejected: Move plugin writes onto SettingsService | broader API change than needed for this regression
Confidence: high
Scope-risk: narrow
Directive: Keep cache invalidation aligned across all settings.json write paths
Tested: bun test src/server/__tests__/settings.test.ts -t "cached CLI settings"
Tested: bun test src/server/__tests__/settings.test.ts src/server/__tests__/plugins.test.ts
Tested: cd desktop && bun run test -- generalSettings settingsStore pluginsSettings
Tested: bun run check:server
Tested: git diff --check
Not-tested: Full bun run verify still fails on unrelated dirty desktop changed-line coverage and CSS color-mix gate
The memory settings surface previously split projects and memory files into separate selection regions. This change keeps the desktop settings frame but makes the memory resource area behave like a single resource tree: project folders expand in place, nested memory folders/files remain in the same hierarchy, and visual badges are removed from the tree rows to keep the UI quiet.
Constraint: The desktop settings shell and existing memory store/API contracts stay unchanged.
Rejected: Keep project and file lists as separate panels | the requested interaction needs project folders and their memory files in one tree.
Rejected: Keep file-type badges in tree rows | they made the navigation look noisy and artificial.
Confidence: high
Scope-risk: moderate
Directive: Keep memory navigation tree rows plain; do not reintroduce badge-heavy file rows without a UX pass.
Tested: cd desktop && bun run test src/__tests__/memorySettings.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: local agent-browser screenshot of settings memory tree
The local main branch already contains memory theme tokens that use color-mix, so the Safari compatibility assertion should cover the startup-sensitive zoom slider CSS block instead of the entire stylesheet.
Constraint: Existing main theme tokens intentionally include color-mix for non-zoom surfaces.
Rejected: Remove the Safari compatibility assertion | the zoom slider still needs coverage for WebView-safe CSS.
Confidence: high
Scope-risk: narrow
Tested: bunx vitest run src/__tests__/generalSettings.test.tsx src/theme/globals.test.ts
Not-tested: Full bun run verify
Bring the verified settings zoom interaction cleanup from the desktop worktree onto local main while preserving the existing main history.
Constraint: Local main already contains unrelated desktop commits after the worktree base.
Confidence: high
Scope-risk: narrow
Tested: bunx vitest run src/__tests__/generalSettings.test.tsx src/theme/globals.test.ts
Tested: browser smoke verified dark-theme order and slider visibility at localhost:5174
Not-tested: Full bun run verify after merge
The settings zoom slider is a secondary desktop preference, so it now previews drag movement locally before committing the app zoom on release and sits below system notifications. The shortcut hint also spells out reset behavior, and the dark theme slider thumb uses stronger contrast tokens so it stays visible while dragging.
Constraint: Desktop zoom changes affect the full application surface and should not update continuously during pointer drag.
Rejected: Keep zoom directly under theme selection | the control is low-frequency and was visually dominating General settings.
Confidence: high
Scope-risk: narrow
Tested: bunx vitest run src/__tests__/generalSettings.test.tsx src/theme/globals.test.ts
Tested: browser smoke verified dark-theme order and slider visibility at localhost:5174
Not-tested: Full bun run verify
The detached worktree contains the verified desktop memory activity styling fix. Merge it into local main while preserving the existing main-line desktop changes and keeping the worktree commit as a separate decision record.
Constraint: Local main already diverged from the detached worktree and is ahead of origin
Rejected: Cherry-pick the worktree commit | would lose the explicit worktree merge topology the user requested
Confidence: high
Scope-risk: narrow
Directive: Preserve main-line desktop changes when resolving future memory-theme edits
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run
Tested: cd desktop && bun run test src/components/layout/Sidebar.test.tsx --run
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Memory tool activity inherited the global warm brand treatment, which made the new desktop memory surface feel detached from white and dark themes. The component now uses memory-specific tokens with a quieter teal family, and the sidebar settings dock is made opaque so scrolling sessions cannot show through it.
Constraint: Desktop memory activity must remain readable across light, white, and dark themes without reusing the classic warm brand surface
Rejected: Keep using color-mix with --color-brand | preserves the original warm tint mismatch in white theme
Confidence: high
Scope-risk: narrow
Directive: Keep memory activity colors on memory-specific tokens instead of generic brand tokens
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run
Tested: cd desktop && bun run test src/components/layout/Sidebar.test.tsx --run
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Browser smoke screenshots for light, white, and dark memory activity previews
Copy actions that did not use CopyButton could succeed silently, leaving users unsure whether the clipboard changed. Show localized success toasts for H5 URL, QR link, and workspace path copies, and report a generic copy failure when the shared clipboard helper cannot write.
Constraint: Copy must keep working across macOS, Linux, and Windows Tauri WebViews, so the existing web Clipboard API plus textarea execCommand fallback remains the platform-neutral mechanism.
Rejected: Add native OS-specific clipboard calls | unnecessary surface area when the current web fallback already covers the desktop WebView contexts.
Confidence: high
Scope-risk: narrow
Directive: Copy surfaces that cannot visibly change their own button text should emit a toast on success or failure.
Tested: cd desktop && bun run test -- --run src/__tests__/generalSettings.test.tsx src/components/workspace/WorkspacePanel.test.tsx src/i18n/index.test.tsx
Tested: bun run check:desktop
Tested: bun run verify (passed=10 failed=0 skipped=0)
Not-tested: Manual OS matrix on Linux and Windows; behavior uses platform-neutral browser clipboard APIs inside Tauri.
Bring the focused titlebar-density fix from the Codex worktree into local main. The patch only adjusts the desktop tab strip height baseline and its regression test, leaving existing local workspace edits untouched.
Constraint: Local main has unrelated uncommitted desktop settings/workspace edits that must be preserved
Rejected: Broad staging before merge | would mix unrelated local work into this landing
Confidence: high
Scope-risk: narrow
Directive: Keep this merge limited to the tab bar height patch; do not treat unrelated dirty files as part of this change
Tested: cd desktop && bunx vitest run src/components/layout/TabBar.test.tsx
Tested: bun run check:desktop
Not-tested: Push or PR creation; user asked for local main merge only
The previous tab strip height made session titles and toolbar affordances feel cramped. Increase the shared titlebar height token for tabs, overflow controls, and the native drag gutter so the top chrome reads as a deliberate desktop titlebar while preserving the existing drag, close, and toolbar interactions.
Constraint: Keep the change scoped to the desktop tab strip and avoid changing session state, ordering, or window-control behavior
Rejected: Increase only the outer bar height | tab hit targets and overflow controls would remain visually misaligned
Confidence: high
Scope-risk: narrow
Directive: Keep tab, overflow button, and drag-gutter heights aligned when changing this titlebar again
Tested: cd desktop && bunx vitest run src/components/layout/TabBar.test.tsx
Tested: bun run check:desktop
Tested: Browser smoke on Vite with tab bar measured at about 44px and toolbar controls centered
Not-tested: Native packaged Tauri window rendering on Windows titlebar controls
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
Bring the detached worktree clipboard fix onto local main while preserving main's existing desktop responsiveness work.
Constraint: Local main and the detached worktree diverged from e070c4a0, so this requires a merge commit instead of a fast-forward.
Confidence: high
Scope-risk: narrow
Directive: Keep desktop copy controls on the shared fallback helper when adding new copy surfaces.
Tested: bun run verify (passed=7 failed=0 skipped=3) in /Users/nanmi/.codex/worktrees/ec25/claude-code-haha before merge
Not-tested: Post-merge full gate on local main; merge applies the verified six-file desktop patch onto a clean local main.
Several desktop copy entry points still called navigator.clipboard directly, which can fail or report success inconsistently in Tauri and browser test contexts. Route those copy actions through the existing textarea execCommand fallback helper and cover the fallback paths for startup diagnostics, markdown copy controls, and workspace file paths.
Constraint: Clipboard API is not reliable in every desktop/browser context.
Rejected: Leave direct navigator.clipboard calls in isolated components | repeats the diagnostics copy failure mode and creates inconsistent copy behavior.
Confidence: high
Scope-risk: narrow
Directive: New desktop copy controls should use copyTextToClipboard or CopyButton instead of calling navigator.clipboard directly.
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run test -- --run src/components/markdown/MarkdownRenderer.test.tsx src/components/layout/StartupErrorView.test.tsx src/components/workspace/WorkspacePanel.test.tsx src/__tests__/diagnosticsSettings.test.tsx src/components/chat/MessageList.test.tsx
Tested: bun run check:desktop
Tested: bun run check:native
Tested: bun run check:coverage
Tested: bun run verify (passed=7 failed=0 skipped=3)
Not-tested: Live provider baseline; not required for this desktop-only clipboard fix.
This merge carries the worktree fix into the local main branch so long multi-session desktop usage keeps rendering cost bounded while DeepSeek follows the shared General Settings Thinking toggle. It preserves running-session behavior and keeps legacy DeepSeek managed env from pinning thinking off after upgrade.
Constraint: User requested the worktree result merged into local main without pushing.
Confidence: high
Scope-risk: moderate
Directive: Keep DeepSeek controlled by the global Thinking setting; do not restore provider-specific disabled-thinking defaults without both toggle-state tests.
Tested: bun test src/server/__tests__/conversations.test.ts -t "global Thinking setting control DeepSeek"
Tested: bun test src/server/__tests__/persistence-upgrade.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/title-service.test.ts src/utils/__tests__/thinking.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts
Tested: cd desktop && bun run test -- MessageList.test.tsx ContextUsageIndicator.test.tsx
Tested: bun run check:server
Virtualized chat rendering keeps inactive long-running sessions cheap without disconnecting their CLI process, while DeepSeek now relies on the shared Thinking setting instead of a provider-specific disabled-thinking override. Existing legacy DeepSeek managed env is normalized so old local settings do not keep suppressing reasoning output after upgrade.
Constraint: Multiple desktop tabs must keep live sessions running and remain quick to switch without reconnecting.
Rejected: Pause or disconnect hidden sessions | would delay tab switching and interrupt streaming/tool state visibility.
Rejected: Keep DeepSeek disabled-thinking preset | conflicts with the General Settings Thinking control.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce provider-specific disabled-thinking defaults for DeepSeek without testing both General Settings toggle states.
Tested: bun test src/server/__tests__/conversations.test.ts -t "global Thinking setting control DeepSeek"
Tested: bun test src/server/__tests__/persistence-upgrade.test.ts src/server/__tests__/provider-presets.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/title-service.test.ts src/utils/__tests__/thinking.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts
Tested: cd desktop && bun run test -- MessageList.test.tsx ContextUsageIndicator.test.tsx
Tested: bun run check:server
Desktop startup can fail before React mounts on older WebViews, so an HTML-level watchdog now renders startup diagnostics even when the module bundle never reaches the app code. Persistent provider migration now imports legacy root provider config into cc-haha-owned storage without deleting the old source file, and plugin marketplace cleanup refuses obvious corrupted cache roots or outside paths.
Constraint: User explicitly accepted the current reviewed state for landing despite remaining review concerns.
Constraint: Global ~/.claude state is user-owned and protected; automatic repair must avoid deleting shared config, transcripts, skills, MCP, plugins, OAuth, adapters, and teams.
Rejected: Tell users to delete ~/.claude or ~/.claude/cc-haha | unsafe because it can destroy user-owned Claude state and still may not fix WebView compatibility failures.
Confidence: medium
Scope-risk: moderate
Directive: Do not weaken protected-path checks; future deletion paths should validate real paths and symlink behavior before recursive rm.
Tested: bun test src/utils/plugins/installedPluginsManager.test.ts src/utils/plugins/marketplaceManager.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test -- --run src/main.test.tsx index-html.test.ts vite-config.test.ts src/theme/globals.test.ts
Tested: cd desktop && bun run build
Tested: bun run check:coverage
Not-tested: live macOS 12/Safari 15 WKWebView startup on an affected machine.
Not-tested: H5 diagnostic URL redaction and symlink-escape hardening are known follow-up risks from review.
PR #428 added a General Settings zoom slider after the desktop shortcut work already introduced a native-first app zoom controller. Keeping both paths would create double scaling and stale UI state, so the slider now routes through the existing controller and store state while the app shell no longer applies a second CSS zoom.
Constraint: UI zoom is device-local display state and should not be written into shared user settings.
Rejected: Keep cc-haha-ui-zoom plus AppShell style.zoom | it conflicts with shortcut zoom and multiplies visual scale.
Rejected: Persist zoom through /api/settings/user | it would sync display-specific state across machines.
Confidence: high
Scope-risk: moderate
Directive: Keep app zoom behind desktop/src/lib/appZoom.ts; do not add another storage key or DOM zoom application point without migration and shortcut sync tests.
Tested: cd desktop && bun run test -- --run src/lib/appZoom.test.ts src/hooks/useKeyboardShortcuts.test.tsx src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx src/lib/persistenceMigrations.test.ts src/lib/doctorRepair.test.ts
Tested: bun run check:desktop
Not-tested: Real Windows/Linux desktop runtime smoke.
The empty-session desktop composer rendered repository launch context
as a separate footer, which created a visual gap between the prompt
surface and the project/branch/worktree controls. This keeps the PC
layout in one panel while preserving the existing mobile bottom layout.
Constraint: Mobile H5 composer layout must remain unchanged.
Rejected: Restyle the shared launch bar globally | existing standalone and mobile placements rely on the old chrome.
Confidence: high
Scope-risk: narrow
Directive: Keep the composer placement desktop-only unless mobile layout is intentionally redesigned.
Tested: cd desktop && bun run test src/pages/EmptySession.test.tsx src/components/shared/RepositoryLaunchControls.test.tsx src/components/shared/DirectoryPicker.test.tsx
Tested: bun run check:desktop
Tested: agent-browser screenshots for PC and mobile empty-session composer.
Not-tested: Packaged Tauri runtime smoke.
Settings reported false negatives for resources the runtime could load, because the API paths used narrower discovery rules than the execution path. This keeps the Settings surfaces read-only where appropriate while matching runtime visibility for linked skills, version-constrained plugins, and merged MCP sources.
Constraint: Settings MCP list must not actively connect to servers while loading.
Rejected: Add UI-specific fallbacks | would preserve drift between Settings and runtime loaders
Confidence: high
Scope-risk: moderate
Directive: Keep Settings resource listing backed by the same shared loader semantics as runtime discovery.
Tested: bun test src/server/__tests__/skills.test.ts
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: bun run check:server
Tested: bun run check:coverage
Tested: bun run verify
Not-tested: Live external MCP connector account state