Desktop users need to see automatic context compaction as a transient transcript state instead of a stray stdout bubble or large card. The UI now collapses prior visible content into a compact timeline divider, shows the compacting phase, then updates the same divider with the completed summary affordance.
Constraint: CLI compaction emits both status and synthetic transcript artifacts that must be normalized before rendering
Rejected: Keep the existing small pill marker | it did not show the in-progress state and looked like chat content
Confidence: high
Scope-risk: moderate
Directive: Do not render local compact stdout as a user-visible chat message
Tested: cd desktop && bun run test -- src/components/chat/MessageList.test.tsx src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: bun run check:desktop
Not-tested: full root bun run verify after the final UI iteration
Desktop transcript messages already expose copy controls in the shared action row. The fork affordance was rendered as its own zero-height hover row, which left the icon below Copy and made the action feel detached from the message toolbar. Moving the fork button into the existing action bar keeps the controls aligned while preserving the original branch-session API path.
Constraint: Desktop message actions should stay visually grouped for both user and assistant messages
Rejected: Keep a separate fork-only hover row | it reproduces the two-line layout regression shown in the desktop UI
Confidence: high
Scope-risk: narrow
Directive: Keep future per-message actions inside MessageActionBar unless they need a different interaction surface
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run test src/i18n/index.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Not-tested: Full live desktop backend fork-click E2E
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.
The detached worktree contains the completed desktop navigation status change. Local main has advanced independently, so this merge records the integration while preserving existing unrelated worktree edits.
Constraint: Local main has unrelated dirty src/ changes that must remain untouched
Rejected: Fast-forward merge | local main and the detached worktree commit diverged after fed9b6c9
Confidence: high
Scope-risk: narrow
Directive: Do not treat the unrelated dirty src/ files as part of this desktop UI merge
Tested: cd desktop && bun run test src/components/layout/TabBar.test.tsx src/components/layout/Sidebar.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: real desktop session in /private/tmp/cc-haha-real-status-fsip8S using gpt-5.5 Sub2API-ChatGPT; running markers appeared in sidebar and tab, then cleared after CCH_STATUS_REAL_DONE
Multiple desktop sessions can run at the same time, so navigation surfaces need a compact shared signal instead of making users open each conversation to check progress. The sidebar and tab bar now derive running state from both persisted tab status and live chat state, while session rows keep worktree and updated-time metadata aligned on the right.
Constraint: Reuse existing tab/chat state without changing session persistence or server APIs
Rejected: Add another session status field | duplicated state would drift from chatStore and tabStore
Confidence: high
Scope-risk: narrow
Directive: Keep sidebar and tab running indicators sourced from the same state rules
Tested: cd desktop && bun run test src/components/layout/TabBar.test.tsx src/components/layout/Sidebar.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: real desktop session in /private/tmp/cc-haha-real-status-fsip8S using gpt-5.5 Sub2API-ChatGPT; running markers appeared in sidebar and tab, then cleared after CCH_STATUS_REAL_DONE
A desktop session can receive a successful TaskStop result without a matching task_notification bookend. The transcript already proves the background command was stopped, but the desktop store kept the local_bash task record in running state, leaving the card spinner active after the assistant turn had ended.
This change derives the stopped background-task update from successful TaskStop/KillShell tool results and upserts the transcript task card through the existing background task merge path. Failed TaskStop results are ignored so an unsuccessful stop cannot hide still-running work.
Constraint: Some persisted and live task-stop paths only surface as ordinary tool_result messages.
Rejected: Change composer/run-button state | that would couple input availability to background task bookkeeping and miss the stuck task card.
Confidence: high
Scope-risk: narrow
Directive: Keep TaskStop result handling gated on non-error results; failed stops must not mark background tasks terminal.
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Full root verify gate; change is scoped to desktop store and covered by check:desktop.
Local main added desktop branch-from-chat, attribution headers, sidecar packaging changes, and provider model normalization. The merge keeps those mainline changes while preserving ChatGPT Official provider metadata, OpenAI OAuth runtime env, provider-load guards, and model catalog behavior.
Constraint: Current worktree was detached; created feat/chatgpt-official-oauth-provider before merging to preserve the OAuth commit line.
Rejected: Rebase the OAuth line over main | the branch already contains many reviewed commits and a merge makes the integration point explicit.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/providers.test.ts src/server/__tests__/settings.test.ts src/server/__tests__/conversation-service.test.ts src/server/__tests__/proxy-transform.test.ts src/utils/__tests__/providerManagedEnvCompat.test.ts src/services/api/client.test.ts src/services/openaiAuth/fetch.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/providerStore.test.ts src/components/controls/ModelSelector.test.tsx --testNamePattern "ChatGPT|OpenAI OAuth|Providers tab|ChatGPTOfficialLogin|ModelSelector|providerStore"
Tested: git diff --cached --check
Desktop users need the same branch-from-here workflow that the CLI already exposed, so the branch creation logic now lives in a shared transcript utility and the desktop app routes completed message actions through the server API. The UI hydrates transcript ids after live completions so newly generated turns can be branched immediately without a refresh.
Constraint: Source sessions must remain unmodified while branch sessions inherit the active transcript chain and persistence metadata.
Rejected: Keep a desktop-only branch implementation | it would drift from CLI /branch semantics and duplicate transcript filtering rules
Confidence: high
Scope-risk: moderate
Directive: Do not remove the post-completion transcript hydration without a real-model desktop E2E for just-finished messages
Tested: bun run verify; Chrome Web UI E2E with real gpt-5.5 provider on ports 45678/45679
Not-tested: Provider-specific behavior beyond the configured Sub2API-ChatGPT route
Settings needed a faster way to find local skills, and the plugin overview was wasting space by forcing summary cards into a tall right rail. Keep skill discovery client-side over the already-loaded list and flatten plugin metrics into a compact header so release notes can point issue #513 at the exact user-facing fix.
Fixes#513
Constraint: Settings skills data is already loaded locally through /api/skills
Rejected: Add server-side skill search | unnecessary API surface for a small local filter
Confidence: high
Scope-risk: narrow
Directive: Keep Settings extension browsing read-only unless an install/edit flow is explicitly added
Tested: bun run check:desktop
Related: #513
The structured Agent fallback formatter used indexed string access to capitalize grouped result labels, which fails under the desktop TypeScript build because the first character may be undefined. Use charAt so the formatter remains equivalent while satisfying noUncheckedIndexedAccess.
Constraint: desktop build runs tsc with strict indexed access checks
Rejected: Non-null assertion on normalized[0] | keeps the fragile indexed access pattern
Confidence: high
Scope-risk: narrow
Directive: Prefer string helpers over indexed access in build-sensitive UI formatting code
Tested: cd desktop && bun run build
Not-tested: Full macOS packaging script after the frontend build failure point
Some providers return the final Agent answer as a text block containing structured JSON instead of Markdown. The desktop result fallback now parses that text-wrapped shape and formats plain and grouped result arrays into readable Markdown before display.
Constraint: Historical sessions can store Agent tool_result content as [{ type: 'text', text: '{...}' }] rather than a direct object
Rejected: Treat provider-specific JSON output as a model bug only | existing transcripts still need readable display
Confidence: high
Scope-risk: narrow
Directive: Agent result display must normalize text-wrapped structured payloads before falling back to raw text
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t "formats structured agent fallback"
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t agent
Not-tested: Full desktop gate per request to avoid broad gate/test runs
Agent fallback output can arrive as structured result arrays when no terminal task report is available. The desktop card now formats that shape into a readable Markdown list instead of exposing raw transport JSON in the preview or result modal.
Constraint: Some Agent executions still only provide structured tool_result content without a terminal Markdown report
Rejected: Keep pretty-printed JSON as the fallback | still exposes implementation shape instead of user-facing results
Confidence: high
Scope-risk: narrow
Directive: Structured Agent result arrays are display data; keep them readable before falling back to raw serialization
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t "formats structured agent fallback"
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t agent
Not-tested: Full desktop gate per request to avoid broad gate/test runs
Agent task notifications carry the user-facing Markdown report, while some Agent tool results contain structured intermediate payloads. The UI now keeps the terminal report as the modal/preview source when it exists, and falls back to raw tool output only when there is no report.
Constraint: Background Agent runs can emit structured tool_result payloads before the terminal task notification carries the readable Markdown report
Rejected: Convert every structured Agent payload into display Markdown | risks inventing summaries and still lets raw transport data compete with the real report
Confidence: high
Scope-risk: narrow
Directive: Do not let raw Agent tool_result content override terminal task result when both are present
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t "prefers the terminal task report"
Tested: cd desktop && bun run test -- run src/components/chat/MessageList.test.tsx -t agent
Not-tested: Full desktop gate per request to avoid broad gate/test runs
Desktop sessions can receive task tools, background-agent lifecycle events, and child tool stream events in the same turn. The UI now keeps root Agent cards separate from task-management tools, preserves parent links when later stream events omit them, and surfaces completed background-agent result text from task notifications.
Constraint: Existing desktop session history can contain task-notification XML and live WS events with incomplete parent metadata.
Rejected: Render local_agent lifecycle messages as standalone cards | duplicates the Agent tool card and makes ownership unclear.
Confidence: high
Scope-risk: moderate
Directive: Do not collapse Agent and TaskCreate/TaskUpdate root tools into one generic group without checking multi-agent screenshots.
Tested: SKIP_INSTALL=1 PRESERVE_TAURI_TARGET=1 ./desktop/scripts/build-macos-arm64.sh
Tested: Real gpt-5.5 desktop E2E sessions under /tmp/cc-haha-subagent-e2e-20260519-025454 for 3 subagents, child tools, result dialog, and background Bash completion.
Not-tested: Full bun run verify gate.
The desktop release workflow expects app version files, Cargo.lock, and release-notes/vX.Y.Z.md to agree with the tag. Bump the desktop package and Tauri metadata to 0.2.8 and add the release note that will be used as the GitHub Release body.
Constraint: The release pipeline reads release-notes/v0.2.8.md from the tagged commit.
Confidence: high
Scope-risk: narrow
Directive: Keep desktop/package.json, Cargo.toml, tauri.conf.json, Cargo.lock, and release-notes/vX.Y.Z.md aligned before tagging.
Tested: Not rerun for this metadata-only release prep commit; previous push gate passed for the workflow fix immediately before this commit.
Not-tested: Full release workflow and packaged artifact smoke after tagging.
Provider presets and custom entries can leave haiku, sonnet, or opus blank when the main model is the intended fallback. Normalize those mappings at both the Settings UI and ProviderService boundaries so persisted provider config and generated runtime env stay consistent.
Constraint: Users may configure only a main model for custom providers.
Rejected: Keep blank secondary model slots | runtime env would still receive empty ANTHROPIC_DEFAULT_* values.
Confidence: high
Scope-risk: narrow
Directive: Keep UI and ProviderService normalization aligned for provider model mapping changes.
Tested: Not run per maintainer request.
Not-tested: Local test suite skipped by request.
Bulk tab actions reused a direct disconnect path instead of the single-tab running-session confirmation path. The tab bar now collects running sessions before closing any selected tab set, prompts when active work would be affected, and applies the user's stop-or-keep choice consistently across close all, close others, close left, and close right.
Constraint: Desktop users can run multiple sessions concurrently and must not lose running work through a bulk tab action without confirmation
Rejected: Patch only Close All | the neighboring bulk actions had the same direct disconnect bypass
Confidence: high
Scope-risk: narrow
Directive: Keep bulk tab closing routed through the same running-session policy as single tab closing
Tested: cd desktop && bun run test src/components/layout/TabBar.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Manual desktop UI click-through
The workspace long-file preview test regularly crosses ten seconds under V8 coverage instrumentation even though the behavior passes. Raising its explicit timeout keeps the coverage lane aligned with the runtime it already observes.
Constraint: PR verification runs the full desktop test suite under coverage, which adds substantial highlighting overhead for this fixture.
Rejected: Change the component rendering path | the failure was a coverage-mode test timeout, not a product regression.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/workspace/WorkspacePanel.test.tsx --run --coverage --coverage.reporter=json-summary --coverage.reporter=lcov --coverage.reportsDirectory=/tmp/cc-haha-workspace-panel-coverage --testTimeout=20000
Tested: bun run check:native
Tested: bun run check:coverage
Tested: git diff --check
Feishu onboarding previously expected users to find the template bot flow in docs before the desktop form made sense. Surface the documented OpenClaw creation link and the two required setup steps directly in the unconfigured Feishu settings state, while hiding the prompt once saved credentials exist.
Constraint: The documented one-click template URL is the source of truth for bot creation.
Rejected: Add a full wizard | this flow only needs a short external creation link plus credential fields.
Confidence: high
Scope-risk: narrow
Directive: Keep this prompt short; detailed Feishu menu setup belongs in docs, not the settings form.
Tested: cd desktop && bunx vitest run src/pages/AdapterSettings.test.tsx src/i18n/index.test.tsx
Tested: cd desktop && bun run lint
Tested: git diff --check
Tested: bun run check:desktop
Not-tested: Live Feishu developer console account flow.
Manual authorization links now behave as transient recovery state: a fresh login clears older URLs, a successful copy clears the exposed URL before polling, and logged-in status defensively drops any stale manual link.
Constraint: OAuth authorization URLs should not survive beyond the active recovery action.
Rejected: Keep the copy button after a successful copy | that leaves an expired authorization URL visible after later auth state changes.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/hahaOpenAIOAuthStore.test.ts
Tested: cd desktop && bun run test -- src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/hahaOpenAIOAuthStore.test.ts src/__tests__/generalSettings.test.tsx --testNamePattern "ChatGPT|OpenAI OAuth|Providers tab|ChatGPTOfficialLogin|hahaOpenAIOAuthStore"
Tested: cd desktop && bun run lint
Tested: git diff --check
When the desktop shell cannot open a browser, the ChatGPT OAuth component keeps the transient authorization URL available for copy and starts polling after that explicit fallback action.
Constraint: OAuth authorization URLs are transient UI state and must not be persisted.
Rejected: Tell users to find the URL in server logs | the desktop flow does not expose those logs as the recovery surface.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/hahaOpenAIOAuthStore.test.ts
Tested: cd desktop && bun run test -- src/components/settings/ChatGPTOfficialLogin.test.tsx src/stores/hahaOpenAIOAuthStore.test.ts src/__tests__/generalSettings.test.tsx --testNamePattern "ChatGPT|OpenAI OAuth|Providers tab|ChatGPTOfficialLogin|hahaOpenAIOAuthStore"
Tested: cd desktop && bun run lint
Tested: git diff --check
Settings now mounts the ChatGPT OAuth status controls only after providers have loaded and ChatGPT Official is confirmed active. This keeps token-refreshing status reads scoped to the selected built-in provider.
Constraint: OAuth status reads can refresh tokens as a side effect
Rejected: Mount ChatGPT OAuth status unconditionally | it repeats the old official OAuth status misfire pattern
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx --testNamePattern "ChatGPT|official OAuth|Providers tab"
Tested: git diff --check
The desktop app now has a dedicated OpenAI OAuth API client and store mirroring the existing Claude Official flow. The component uses the existing browser-open plus polling pattern and never exposes token material to the UI.
Constraint: OAuth status responses must not return token bodies
Rejected: Reuse Claude OAuth store | the status shape and endpoint differ
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/stores/hahaOpenAIOAuthStore.test.ts
Tested: git diff --check
ChatGPT Official uses a real provider id, so desktop UI consumers cannot keep treating only null as the official lane. The model selector now exposes the GPT catalog for that built-in provider, and Settings renders a separate active card instead of folding it into Claude Official state.
Constraint: Claude Official remains represented by activeId null.
Rejected: Reuse the Claude Official card for ChatGPT Official | it hides the active ChatGPT provider and shows the wrong login surface.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/controls/ModelSelector.test.tsx
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx --testNamePattern "Providers tab"
Tested: git diff --check
Desktop receives task lifecycle events for local subagents in addition to the visible Agent tool call. Rendering those events as standalone transcript cards exposed internal task types such as local_agent and duplicated the Agent surface. The store now keeps agent lifecycle state for Agent tool notifications without inserting separate transcript background cards, while non-agent background work remains visible with user-facing labels.
The long workspace preview test now has a wider timeout so coverage mode does not fail a behaviorally passing case.
Constraint: CLI task events use internal task_type values such as local_agent and local_bash.
Rejected: Rename local_agent in the standalone card | still leaves duplicate Agent and task cards for one subagent.
Confidence: high
Scope-risk: moderate
Directive: Do not render local_agent or remote_agent as standalone transcript cards unless the Agent tool card no longer exists.
Tested: bun run verify passed=8 failed=0 skipped=2
Not-tested: Live provider-backed desktop session smoke.
When ChatGPT Official is active, the model API now returns the OpenAI Codex catalog and reads or writes the selected GPT model from cc-haha managed settings. Desktop provider activation also recognizes the built-in provider id and resets the current model to the OpenAI default instead of looking for a saved provider record.
Constraint: ChatGPT Official is a built-in provider and is not present in providers.json providers[].
Rejected: Reuse the four-slot provider model list | it hides GPT-5.5 and misrepresents the OpenAI catalog as Anthropic slot names.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/settings.test.ts
Tested: bun test src/server/__tests__/providers.test.ts --test-name-pattern "ChatGPT Official|getProviderForProxy"
Tested: bun test src/server/__tests__/conversation-service.test.ts --test-name-pattern "ChatGPT Official|session-scoped provider"
Tested: cd desktop && bun run test src/stores/providerStore.test.ts
Tested: git diff --check
The desktop status store retained the last non-default server verb until idle, so a one-time worktree startup label could leak into later thinking or execution bubbles. The fix treats default Thinking and verb-less status updates as a reset for transient status text while preserving explicit non-default labels.
Constraint: Server-side duplicate worktree startup guards are still valid and should remain the source of launch truth.
Rejected: Suppress the label in StreamingIndicator | stale state would still leak to any future consumer of statusVerb.
Confidence: high
Scope-risk: narrow
Directive: Keep statusVerb transient; default Thinking or missing verbs must not preserve prior special labels.
Tested: cd desktop && bun run test -- src/stores/chatStore.test.ts --run
Tested: cd desktop && bun run lint
Tested: bun test src/server/__tests__/conversations.test.ts -t worktree --timeout 30000
Tested: git diff --check
The desktop provider settings editor has its own JSON scrubber for provider-managed env. It now removes the ChatGPT Official OAuth marker and token-file path along with the Anthropic provider env keys, so editing another provider cannot keep stale OpenAI OAuth runtime markers in previewed settings JSON.
Constraint: Desktop JSON preview cleanup is separate from server managed-env filtering.
Rejected: Rely only on server activation cleanup | the settings editor can preview and persist user-edited JSON before the server rebuilds provider env.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun test src/lib/__tests__/providerSettingsJson.test.ts
Tested: bun test src/server/__tests__/providers.test.ts --test-name-pattern "ChatGPT Official|getProviderForProxy"
Tested: bun test src/server/__tests__/conversation-service.test.ts --test-name-pattern "ChatGPT Official|session-scoped provider"
Tested: git diff --check
Activating the built-in ChatGPT provider now writes a dedicated OpenAI OAuth runtime environment instead of clearing provider state. The runtime points the CLI at the desktop-managed OpenAI token file, avoids Anthropic auth/base-url variables, reports auth from the OpenAI token file, and prevents Claude OAuth injection when ChatGPT Official is active.
Constraint: Default provider sessions read cc-haha managed settings, while session-scoped provider selections inject env directly from the desktop host.
Rejected: Treat ChatGPT Official as a generic OpenAI proxy provider | proxy routing would require API-key shaped auth and would leak it into the wrong runtime path.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/providers.test.ts
Tested: bun test src/server/__tests__/conversation-service.test.ts
Tested: bun test src/services/openaiAuth/storage.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts src/server/__tests__/haha-openai-oauth-api.test.ts
Tested: git diff --check
The notification plugin can return an object whose unregister method
expects its original listener object as this. Calling the extracted
method directly drops that context and produces an unhandled rejection
when the browser shell cleans up listeners.
Constraint: The desktop frontend also runs in browser-like dev shells where Tauri plugin APIs may be partially present
Rejected: Ignore cleanup errors | it leaves noisy unhandled rejections during local UI smoke tests
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- desktopNotifications
Not-tested: Native notification click behavior on every desktop platform
The previous portable-mode settings were hard to discover and mostly
explained through environment variables. Users could not pick a target
folder from the desktop UI or clearly understand which config directory
would become active after switching modes.
This moves the control to a lower-priority storage section, makes the
active directory source explicit, supports choosing a portable data
folder, and prepares the desktop app for a controlled restart so the
new storage location takes effect.
Constraint: Directory picking and relaunch require the Tauri desktop runtime
Rejected: Keep environment-variable-only setup | users could not discover or validate the target path from the app
Confidence: high
Scope-risk: moderate
Tested: cd desktop && bun run test -- generalSettings settingsStore
Tested: bun run check:desktop
Tested: bun run check:native
Tested: bun run check:coverage
Tested: bun run verify
Tested: Maintainer manual desktop UI test
Not-tested: Windows relaunch path beyond existing native coverage
ChatGPT OAuth needs a stable built-in provider identity and a runtime kind that survives provider index normalization. This keeps normal saved providers unchanged while letting the desktop treat openai-official as an active provider without storing secrets in providers.json.
Constraint: providers.json migration must preserve activeId=openai-official even when providers[] is empty
Constraint: ChatGPT Official metadata must stay token-free in the provider index
Rejected: Treat ChatGPT Official as a normal openai_responses provider row | it would require persisting a fake saved provider and would still lose activeId during migration
Confidence: high
Scope-risk: narrow
Directive: Do not teach later OpenAI OAuth work to depend on providers[] containing the built-in provider
Tested: bun test src/server/__tests__/providers.test.ts --test-name-pattern "ChatGPT Official provider metadata"
Tested: bun test src/server/__tests__/providers.test.ts
Tested: cd desktop && bun run lint
Tested: git diff --check
The goal command now uses a session Stop hook as the durable completion mechanism and removes the separate evaluator path. Completion output is persisted as local command output so CLI streams, transcripts, and desktop history all see the same completed state.
Desktop keeps active goals in a compact header strip and lets completed goals render in the conversation flow, which avoids the old duplicate pinned card behavior while preserving visibility for running goals.
Constraint: /goal state has to survive transcript replay and desktop session restore.
Rejected: Keep the legacy evaluator alongside Stop hooks | duplicate completion paths made desktop state drift and hid the final card.
Confidence: high
Scope-risk: moderate
Directive: Keep goal completion events on the local_command transcript path unless desktop history restore is changed at the same time.
Tested: bun test src/goals/goalState.test.ts src/query/stopHooks.test.ts src/commands/goal/goal.test.tsx src/commands/headless.test.ts src/server/__tests__/ws-memory-events.test.ts src/server/__tests__/sessions.test.ts
Tested: bun run check:server
Tested: bun run check:desktop
Tested: real /tmp /goal CLI run 98fb4bb2-52da-4850-bc8e-e5e1cc0e4c51 with deepseek-v4-pro and WebUI screenshot verification
Not-tested: Remote release workflow
Bring back the narrow, previously validated sidebar recovery and Windows x64 sidecar compatibility fixes that were present on worktree branches but absent from the canonical local main checkout. The broader worktree branch is intentionally not merged because it would replay unrelated history and risk dropping current main work.
Constraint: The local main worktree already contains unrelated Settings and portable storage edits, so only the recovery hunks are staged.
Rejected: Merge 1506086927/main wholesale | its history includes broad rollback/replay commits that would remove current main changes.
Confidence: high
Scope-risk: narrow
Directive: Recover future worktree fixes by cherry-picking or staging narrow hunks; do not merge drifted worktree branches wholesale.
Tested: cd desktop && bun run test -- --run src/components/layout/Sidebar.test.tsx scripts/build-sidecars.test.ts
Not-tested: Full bun run verify due unrelated dirty Settings/portable-storage worktree changes.
Remote main carries portable-mode, legacy Windows workdir recovery, and Feishu path-safety fixes while local main carries terminal shell, update proxy, slash-command, prompt-draft, AskUserQuestion, background-work, and shell-env changes. This merge keeps both lines by layering portable Bash-path defaults underneath the desktop terminal shell preference and preserving both update-proxy and app-mode settings state.
Constraint: Local main and origin/main diverged after v0.2.7 and both lines contain release-relevant desktop/runtime fixes
Rejected: Prefer either side's terminal settings wholesale | would drop either Windows portable Bash support or explicit desktop startup-shell support
Confidence: medium
Scope-risk: moderate
Directive: Keep portable Bash path as the system-default terminal fallback; explicit desktop startup-shell settings should continue to override it
Tested: cd desktop && bun run test -- --run src/pages/TerminalSettings.test.tsx src/stores/settingsStore.test.ts
Tested: cd desktop/src-tauri && cargo test terminal -- --nocapture
Tested: bun test src/server/__tests__/sessions.test.ts -t stale worktree
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: Manual Windows packaged-app terminal/portable smoke
This brings the detached worktree fix for desktop background-task visibility into the local main branch while preserving the main branch updates that landed after the worktree base.
Constraint: The source worktree was detached from an older main ancestor
Rejected: Fast-forward merge | local main has newer commits and cannot fast-forward to the detached worktree commit
Confidence: high
Scope-risk: narrow
Tested: bun run verify
Not-tested: No live provider baseline was run because this was a desktop UI state fix and the PR gate used non-live checks
The CLI can emit a completed assistant turn while local background bash or task work continues. The desktop transcript and header now keep that background activity visible without converting the composer into a global stop state.
Constraint: Preserve CLI turn semantics where message_complete means the current assistant turn is done, not every background task is finished
Rejected: Treat background tasks as composer-active generation | this blocks follow-up input even though the CLI keeps the session responsive
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/components/chat/ChatInput.test.tsx src/pages/ActiveSession.test.tsx
Tested: bun run check:desktop
Tested: bun run verify
The updater proxy fix was developed in a detached worktree based on an older local main commit. Merge it into the canonical local main while preserving the feature commit and the main branch's intervening desktop work.
Constraint: Local main contains four commits after the worktree base, so a fast-forward is not available
Rejected: Rebase detached worktree onto main | unnecessary history rewriting for a single verified feature commit
Confidence: high
Scope-risk: moderate
Directive: Do not push this local integration unless explicitly requested
Tested: cd desktop && bun run test -- --run src/__tests__/generalSettings.test.tsx src/stores/updateStore.test.ts src/stores/settingsStore.test.ts src-tauri/tauri-config.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Not-tested: Full coverage gate intentionally skipped after maintainer said it was not needed
Desktop update downloads currently depend on the updater HTTP stack and can miss the proxy users already rely on for GitHub access. The change enables OS proxy discovery in the native updater client and adds a narrow manual proxy fallback scoped only to update checks and package downloads.
Constraint: Tauri updater carries the proxy from check through download, so changing proxy settings after a check must discard the pending update and re-check before install
Rejected: Reuse provider proxy settings | provider traffic and updater traffic have different scope and persistence risks
Confidence: high
Scope-risk: moderate
Directive: Keep update proxy settings scoped to app updates; do not mix them with model/provider proxy configuration without a separate migration decision
Tested: cd desktop && bun run test -- --run src/__tests__/generalSettings.test.tsx src/stores/updateStore.test.ts src/stores/settingsStore.test.ts src-tauri/tauri-config.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Not-tested: Full coverage gate intentionally skipped after maintainer said it was not needed
The add-to-chat popover was keyed off the mouse-up location, so broad selections could cover the selected text and stale popovers could remain visible. Shared placement and dismissal logic now anchors the action to the selected range, prefers the space above the selection like Codex App, and clears stale selections on outside clicks.
Constraint: Desktop chat and workspace previews share the same selection affordance and must keep their existing add-reference behavior.
Rejected: Keep cursor-based placement | it can hide selected text and makes multi-line selections unpredictable.
Confidence: high
Scope-risk: moderate
Directive: Keep future selection actions range-anchored and outside-click dismissible across chat and workspace surfaces.
Tested: bun run check:desktop
Tested: Browser verification on http://127.0.0.1:5174 showed the popover above selection, no overlap, and outside click clearing it.
Not-tested: Native packaged Tauri window.
Related: https://github.com/NanmiCoder/cc-haha/issues/484
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
Bring the detached worktree fix into local main so desktop sessions keep legacy custom slash commands visible across live CLI updates.
Constraint: Local main contains an additional local commit, so this is a real merge rather than a fast-forward.
Confidence: high
Scope-risk: moderate
Directive: Preserve both skill and legacy command fallback behavior when changing slash command session state.
Tested: bun test src/server/__tests__/sessions.test.ts -t "slash-commands"
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: Full verify remains blocked by unrelated/flaky coverage lane failures observed before merge.
Related: 010a4bf9
The desktop command list could be replaced by a partial live CLI update after a turn, and the server fallback only knew about skills. Keep the client list stable while refreshing from the authoritative session endpoint, and include legacy .claude/commands entries in that endpoint.
Constraint: Claude Code custom slash commands still use .claude/commands/*.md alongside newer skill commands.
Rejected: Only union client-side updates | would still miss custom commands before CLI init and lose argument hints from the authoritative API.
Confidence: high
Scope-risk: moderate
Directive: Keep session slash command fallback aware of both skills and legacy command directories.
Tested: bun test src/server/__tests__/sessions.test.ts -t "slash-commands"
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Not-tested: bun run verify remains red due unrelated/flaky coverage lane failures outside this change.
Related: https://github.com/NanmiCoder/cc-haha/issues/495
AskUserQuestion used one free-text value for every tab, so typing a custom response on one question leaked into the next and option selection could erase another question's answer. Store free-text answers per question index and submit the per-question answer map so mixed custom and option replies stay intact.
Constraint: Multiple questions share one AskUserQuestion component instance
Rejected: Keep a single free-text fallback | it cannot distinguish which question owns the custom reply
Confidence: high
Scope-risk: narrow
Directive: Keep custom text and option state scoped to a question index when changing this component
Tested: Reproduced in Web UI on 127.0.0.1:1420 with server 127.0.0.1:4678 and deepseek-v4-pro in /private/tmp
Tested: cd desktop && bun run test -- AskUserQuestion.test.tsx --run
Tested: bun run verify
Not-tested: none
The desktop terminal defaulted to COMSPEC on Windows, which commonly lands on cmd.exe and makes the settings terminal feel broken for users who expect PowerShell. This change adds a desktop-only terminal startup-shell setting in Settings, keeps the existing system default untouched unless the user opts in, and resolves the selected shell inside the Tauri PTY spawn path so docked terminals, terminal tabs, and the settings terminal stay aligned.
Constraint: Existing terminal sessions and non-Windows defaults must keep their current behavior unless a Windows user explicitly changes the setting
Rejected: Reuse settings.defaultShell | that setting already controls CLI ! commands and would couple unrelated shell semantics
Rejected: Flip Windows default to pwsh automatically | too risky for users whose current COMSPEC-based terminal already works
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep desktop terminal shell selection separate from CLI shell routing unless both paths are redesigned together
Tested: cd desktop && bun run test -- src/stores/settingsStore.test.ts src/pages/TerminalSettings.test.tsx
Tested: cd desktop/src-tauri && cargo test desktop_terminal_shell_resolution -- --nocapture
Tested: bun run check:desktop
Tested: bun run check:native
Not-tested: Real interactive spawn on a physical Windows machine
The contributor PR adds useful Windows terminal and portable mode support, but it also reintroduced an older General settings zoom block and left the new native settings paths without enough regression coverage. This commit keeps the feature direction intact while removing the duplicate UI, making invalid bash paths fail at save time, and covering the portable cache and app-mode paths with focused tests.
Constraint: This commit lands directly on the contributor PR branch to avoid a long review-comment loop.
Rejected: Ask the contributor to rework the PR from scratch | the remaining issues are narrow and maintainable by us.
Confidence: high
Scope-risk: moderate
Directive: Keep future portable-mode changes covered at the native boundary and the desktop store boundary.
Tested: cd desktop && bun run test src/pages/TerminalSettings.test.tsx src/__tests__/generalSettings.test.tsx src/stores/settingsStore.test.ts
Tested: bun test src/utils/__tests__/cachePaths.test.ts
Tested: cd desktop/src-tauri && cargo test
Tested: cd desktop && bun run lint
Tested: cd desktop/src-tauri && cargo check
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Manual Windows packaged-app portable-mode smoke; to be covered before a future release.