Complete the Electron replacement boundary before merging by removing the renderer-side Tauri host fallback, tightening H5/browser access so only desktop navigation is tokenless, and moving desktop release publication to a tag-driven GitHub Actions matrix with a single final publish job.
Constraint: H5/browser capability access must not gain tokenless access through localhost or retired Tauri origins
Constraint: Desktop release artifacts must be built by GitHub Actions from version tags, not treated as local build outputs
Rejected: Keep localhost browser origins trusted for convenience | local browser contexts can access loopback services and must use the H5 token path
Rejected: Publish from each matrix job | partial releases can be created before all platforms finish
Confidence: high
Scope-risk: broad
Directive: Do not reintroduce Tauri origins or localhost browser origins into the trusted desktop origin set without a reviewed security design
Tested: bun test src/server/__tests__/h5-access-policy.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/diagnostics-service.test.ts src/server/middleware/cors.test.ts
Tested: bun test scripts/pr/release-workflow.test.ts scripts/release-update-metadata.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Tested: git diff --check
Not-tested: bun run check:server is blocked by expired quarantine entries server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Electron desktop runs network-sensitive OpenAI OAuth token exchange in the sidecar process, so the sidecar now receives system proxy env derived from Electron's cross-platform proxy resolver and the OAuth token client uses the existing proxy fetch options. General manual proxy settings also document and preserve authenticated proxy URLs.
The macOS fullscreen black-screen path is addressed by avoiding native fullscreen Spaces for app fullscreen toggles and by leaving fullscreen before hiding or closing the window.
Constraint: Electron packaged apps may not inherit shell HTTPS_PROXY env when launched from Finder.
Constraint: Manual authenticated proxies must remain standard HTTP(S) proxy URLs for Bun/undici compatibility.
Rejected: Store proxy username and password as separate fields | would require new secret-storage semantics and migration beyond this bugfix.
Rejected: Use native macOS fullscreen Spaces for the desktop app | reproduced black-screen behavior when hiding/closing from fullscreen.
Confidence: high
Scope-risk: moderate
Directive: Do not remove sidecar proxy env injection without retesting OpenAI OAuth from a packaged app launched outside a shell.
Tested: bun test src/services/openaiAuth/client.test.ts src/server/__tests__/haha-openai-oauth-service.test.ts
Tested: bun test src/server/__tests__/network-settings.test.ts
Tested: cd desktop && bun run test -- src/__tests__/generalSettings.test.tsx --run
Tested: cd desktop && bun run check:electron
Tested: git diff --check
Not-tested: bun run check:server blocked by expired quarantine entries server:cron-scheduler, server:providers-real, server:tasks, server:e2e:business-flow, server:e2e:full-flow
Not-tested: live OpenAI OAuth through a corporate authenticated proxy
Introduce the Electron desktop shell alongside the existing React renderer and local Bun server boundary. The migration keeps the DesktopHost contract explicit across Tauri, Electron, and browser runtimes while adding Electron main/preload services for dialogs, shell, notifications, updates, tray/window lifecycle, terminal, preview WebContentsView, app mode, and release/package validation.
The commit also carries the latest local main desktop command updates, including agent slash entries and hidden-by-default markdown thinking details, so the packaged Electron build matches the current main UX surface.
Constraint: React renderer, local Bun server, REST/WebSocket, and sidecar boundaries must remain reusable during the migration
Constraint: macOS dev packages are ad-hoc signed and cannot prove Developer ID notarization or Gatekeeper release launch
Rejected: Browser-only smoke validation | it cannot exercise native dialogs, keychain prompts, notification behavior, or packaged app startup
Confidence: medium
Scope-risk: broad
Directive: Do not remove Tauri host support until signed Electron release artifacts pass native OS smoke on macOS, Windows, and Linux
Tested: bun run check:desktop
Tested: cd desktop && bun run check:electron
Tested: CSC_IDENTITY_AUTO_DISCOVERY=false bun run electron📦dir
Tested: bun run test:package-smoke --platform macos --package-kind dir --artifacts-dir desktop/build-artifacts/electron
Tested: Computer Use read packaged Electron app window at desktop/build-artifacts/electron/mac-arm64/Claude Code Haha.app
Not-tested: Developer ID signed/notarized Gatekeeper launch
Not-tested: Real OS notification click-to-session action
Not-tested: Windows and Linux packaged app smoke on real hosts
Desktop destructive confirmations had split implementations: shared ConfirmDialog, hand-rolled modals, inline task popovers, and a diagnostics window.confirm path. This adds shared ActionDialog and ConfirmPopover primitives, keeps ConfirmDialog as the simple two-action wrapper, and migrates the known desktop confirmation flows onto those shared surfaces.
Constraint: Running-tab close needs three actions, so the two-button ConfirmDialog API was not enough.
Rejected: Force every confirmation through ConfirmDialog | would either lose the keep-running action or make ConfirmDialog too broad for inline popovers.
Confidence: high
Scope-risk: moderate
Directive: Add new desktop confirmation flows through ActionDialog, ConfirmDialog, or ConfirmPopover instead of hand-rolled overlays.
Tested: cd desktop && bun run test -- AdapterSettings.test.tsx mcpSettings.test.tsx TabBar.test.tsx PermissionModeSelector.test.tsx diagnosticsSettings.test.tsx
Tested: bun run check:desktop
Tested: git diff --check
Tested: Chrome DevTools smoke for provider delete and diagnostics clear-log confirmation dialogs
Not-tested: Agent Browser smoke because the local agent-browser CLI hung on open/snapshot/doctor/close commands.
MCP settings could remount into a full-page spinner even when cached server data was available, and overlapping refreshes could let stale responses replace newer state. Historical chat sessions also had no explicit history-loading state, so slow transcript reads looked like a blank session.
This keeps cached MCP/session lists visible during refresh, ignores stale list responses, clears the selected MCP server when returning to the list, and gives historical sessions explicit loading/error states with de-duped history loads.
Constraint: Fix must stay local-state focused and avoid changing MCP config persistence.
Rejected: Add a broad transcript/session indexing layer | too large for the issue-level stall fix.
Confidence: high
Scope-risk: moderate
Directive: Do not remove the stale-response guards without re-testing rapid settings navigation and session switching.
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: cd desktop && bun run test -- sessionStore.test.ts
Tested: cd desktop && bun run test -- chatStore.test.ts
Tested: cd desktop && bun run test -- ActiveSession.test.tsx
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run check:desktop
Tested: bun run check:server
Not-tested: Manual Windows desktop reproduction for issue #648.
Desktop users have different multiline habits, and issue #631 calls out that Enter-only submission is too easy to trigger. This adds a persisted General setting that keeps Enter-send as the default while allowing Ctrl/Cmd+Enter submission for users who want plain Enter to insert a newline.
Constraint: Preserve existing Enter-to-send behavior as the default
Rejected: Change the global default to Ctrl/Cmd+Enter | would disrupt existing users
Confidence: high
Scope-risk: moderate
Directive: Keep active and empty session composers using the shared send shortcut helper
Tested: cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/__tests__/generalSettings.test.tsx src/stores/settingsStore.test.ts
Tested: bun run check:desktop
Tested: cd desktop && bun run lint
Tested: Browser smoke on http://127.0.0.1:5174/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456
Not-tested: Packaged Tauri app runtime
Related: #631
Tauri's desktop webview injects window.confirm through the dialog confirm command, but the default desktop capability only allowed the message dialog command. Windows release builds could therefore reject confirm at ACL time even though the browser-facing code path looked valid in local web development.
Constraint: Tauri plugin-dialog 2.7 still exposes window.confirm as plugin:dialog|confirm in the injected webview shim.
Rejected: Grant dialog:default | broader than the specific confirm compatibility gap.
Confidence: high
Scope-risk: narrow
Directive: Keep confirm permission covered while the desktop webview exposes browser confirm shims.
Tested: cd desktop && bun run test -- --run src/__tests__/tauriCapabilities.test.ts
Tested: bun run check:desktop
Tested: bun run check:native
Not-tested: Windows release package manual smoke
The MCP settings page now waits for the full initial aggregation pass before
rendering list statistics or the empty state. This covers the extra project
path lookup for user-private MCP entries, so the page does not briefly show
zero servers while the desktop app is still discovering scoped configs.
The focused MCP settings test now asserts the initial loading state and keeps
existing list, edit, toggle, and reconnect flows waiting for that load boundary.
Constraint: MCP settings now loads from multiple project contexts before the first trustworthy list render
Rejected: Keep rendering zero-value stats during discovery | this caused a misleading empty flash on startup
Confidence: high
Scope-risk: narrow
Directive: Do not show MCP empty state until initial project path discovery and server fetch have both completed
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:desktop
Tested: git diff --check
The settings page could create project-scoped MCP servers for an explicit
target path, but subsequent reloads only fetched the active session cwd.
That made newly-added local or project MCP entries disappear when the
target project differed from the active session or was only represented in
the user config.
Load MCP settings from the active project, recent projects, and project
keys that already contain user-private MCP config. Project-scoped rows now
render their project path and use project-aware identity keys so same-name
servers in different projects stay distinct.
Constraint: MCP local scope is keyed by project path in the user config, while project scope still resolves through the request cwd
Rejected: Filesystem scan for every possible .mcp.json | unbounded and too expensive for settings load
Confidence: high
Scope-risk: moderate
Directive: Do not collapse settings MCP fetches back to a single cwd; project/local scopes are cwd-sensitive
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Discovery of project-shared .mcp.json files in directories that are neither recent projects nor active workdirs
Desktop MCP local and project scopes were still anchored to the active session workDir when adding or editing servers, which made a selected session worktree look like the target project. The form now treats the target project as explicit input for local/project scopes, and the server update path can remove the old scoped config before writing to the newly selected directory.
Constraint: Local MCP scope is private user config keyed by project path; project scope writes the selected project's .mcp.json
Rejected: Keep using the active session workDir as the implicit target | it can point at transient session worktrees and hides where config is written
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce implicit active-session cwd writes for MCP create/edit; local and project scopes need an explicit target project
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run check:desktop
Tested: bun run check:server
Tested: bun run quality:gate --mode baseline --allow-live --only 'provider-smoke:*' --provider-model deepseek:main:deepseek-main
Tested: Live filesystem MCP probes with DeepSeek deepseek-v4-pro across local/project scopes; see artifacts/quality-runs/mcp-live-split-2026-05-25T09-15-18-245Z/summary.json
Not-tested: Full bun run verify was not rerun after the live MCP smoke
Related: #585
Desktop MCP settings were using the process startup directory for local
project config and treated new custom servers as user-global. The desktop
API now carries the active cwd into local MCP reads, writes, and enablement
state, and the settings page exposes the same local/project/user scopes that
the CLI supports.
Constraint: Claude Code MCP scope semantics are local, project, and user.
Constraint: Desktop sessions can switch projects inside one long-lived server process.
Rejected: Only clear getProjectPathForConfig cache | still leaves request-scoped desktop API calls tied to getOriginalCwd
Confidence: high
Scope-risk: moderate
Directive: Do not route MCP local config or disabledMcpServers through ambient original cwd in desktop APIs.
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Live provider smoke; the change is config/control-channel scoped.
H5 access broke after switching Wi-Fi: the previously saved private-LAN host
(e.g. 192.168.1.207) was no longer bound to any interface on the current
network (which had moved to 192.168.0.x), but commit da8308de only refreshed
the stale *port* and kept the stale *hostname*. The QR code therefore pointed
at an IP this machine no longer had, and phones got TCP-refused.
Two changes:
1. resolveEffectiveH5PublicBaseUrl now accepts the set of local IPv4 hosts
and, when the stored URL is a plain private-LAN HTTP URL whose hostname
is no longer on any interface, falls back to the auto-discovered URL —
without overwriting the stored value, so switching back to the original
network restores it automatically.
2. H5AccessService.updateSettings now validates the publicBaseUrl on save.
Plain LAN URLs whose host is not on any local interface are rejected with
a 400 + suggested LAN IP. Reverse-proxy URLs (https, custom path, hostname
targets) are accepted unchanged because reachability is owned by the
user's tunnel / nginx / cloudflared setup, not the desktop.
GET /api/h5-access also returns a diagnostics block (storedHostStaleness,
suggestedHost, localInterfaceHosts, effectivePublicBaseUrl) so the desktop
Settings page can render a warning banner with a one-click switch to the
current LAN IP, plus a quieter note for proxy URLs.
Constraint: Reverse-proxy users may legitimately point H5 at a hostname that
is not on this machine's network adapters; we must not regress that path.
Rejected: Reachability-probe arbitrary public URLs from the server | the
desktop cannot reliably round-trip through the user's external tunnel and
should not pretend to validate it.
Rejected: Overwrite the stored host with the auto host | users on multi-
network laptops would lose their pinned WSL/Docker-aware choice on every
Wi-Fi switch.
Confidence: high
Scope-risk: moderate
Directive: Do not narrow validateH5PublicBaseUrl to reject reverse-proxy
URLs without restoring an explicit opt-in path for them.
Tested: bun test src/server/__tests__/h5-access-service.test.ts (21 pass)
Tested: bun test src/server/__tests__/h5-access-api.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/h5-access-policy.test.ts (57 pass)
Tested: bun run check:server (840 pass)
Tested: bun run lint && bun test --run in desktop/ (734 pass, 91 files, +3 new H5 banner / proxy-note / save-error cases)
Not-tested: Real phone scan against a packaged desktop build after a Wi-Fi switch.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Manual H5 LAN hosts should survive auto LAN discovery, because the detected adapter can be a WSL or Docker virtual interface instead of the physical network reachable by a phone. The settings UI now asks for the host or IP separately and reuses the current service port for the normal LAN workflow, while still accepting full URLs for reverse proxy setups.
Constraint: H5 auto discovery may see multiple private IPv4 adapters on Windows hosts.
Rejected: Treat all private URLs as stale auto-discovery output | this overwrites intentional LAN choices.
Rejected: Let users manually type the port for the normal LAN path | the server already owns the bound port and the UI can reuse it.
Confidence: high
Scope-risk: moderate
Tested: bun test src/server/__tests__/h5-access-service.test.ts
Tested: bun test src/server/__tests__/h5-access-api.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/h5-access-policy.test.ts
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Real Windows plus WSL/Docker plus phone LAN smoke.
The WebSocket runtime treated every active provider id as stale unless it existed in the saved custom-provider list. ChatGPT Official is an in-memory built-in provider, so restarting the desktop could activate Claude Official and rewrite the provider index back to null before a new session launched. Keep built-in OpenAI provider ids valid in runtime validation, while preserving the stale custom-provider cleanup path.
This also keeps the General network timeout UI aligned with the authoritative timeout behavior by allowing precise typed values and updating the user-facing hint.
Constraint: ChatGPT Official is a built-in provider id and is not stored in the custom providers array.
Rejected: Persist ChatGPT Official as a synthetic saved provider | it would mix token-backed built-ins with user-managed providers and complicate secret persistence.
Confidence: high
Scope-risk: moderate
Directive: Runtime provider validation must include built-in provider ids as well as saved custom provider ids.
Tested: bun test src/server/__tests__/conversations.test.ts -t "preserve ChatGPT Official"
Tested: bun test src/server/__tests__/conversations.test.ts -t "stale persisted|preserve ChatGPT Official"
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Full bun run verify after the final interruption; user explicitly asked to stop verification and commit directly.
Not-tested: Manual desktop restart with a real ChatGPT OAuth account in the packaged app.
The worktree implementation adds one General settings surface for AI request timeout and proxy configuration. This merge keeps main's newer attribution-signing and ChatGPT Official provider paths while preserving the unified network override across CLI sessions, provider checks, and OpenAI-compatible proxy forwarding.
Constraint: Main had newer attribution and official-provider changes touching the same server integration points
Rejected: Prefer the worktree proxy body verbatim | it would drop main's signed CCH attribution wrapper
Rejected: Keep provider preset timeouts above General | it would violate the single timeout control requirement
Confidence: high
Scope-risk: moderate
Directive: General network settings must remain the final AI request timeout and proxy override for every provider protocol
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/network-settings.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/proxy-network-settings.test.ts
Tested: cd desktop && bun run test -- --run src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx
Not-tested: Full bun run verify after resolving main merge conflicts
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
Settings General now owns AI request timeout and proxy policy so Anthropic-native and OpenAI-compatible provider paths share one user-visible control. The desktop UI persists the network settings, the server proxy and provider checks read them directly, and CLI sessions receive the same timeout/proxy environment.
Constraint: Provider request behavior must be consistent across Anthropic, OpenAI Chat, and OpenAI Responses formats
Rejected: Keep preset API_TIMEOUT_MS precedence | it would make the General timeout control unreliable for some providers
Confidence: high
Scope-risk: moderate
Directive: Do not add protocol-specific AI request timeout controls without preserving General as the final override
Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/network-settings.test.ts src/server/__tests__/providers.test.ts src/server/__tests__/proxy-network-settings.test.ts
Tested: cd desktop && bun run test -- --run src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx
Tested: bun run check:desktop
Tested: bun run check:server
Not-tested: socks5 manual proxy support; manual proxy validation currently accepts HTTP/HTTPS URLs only
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
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.
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
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
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
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
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 goal implementation is being kept in the tree for later redesign, but the CLI and desktop slash-command surfaces should not advertise or route new users into it while the long-running goal experience is incomplete. Existing transcript parsing and goal event rendering remain intact so historical sessions still load.
Constraint: Keep the implementation code available for future iteration while removing discoverable entrypoints.
Confidence: high
Scope-risk: narrow
Directive: Do not re-expose /goal until the persistent goal/runtime design is revisited.
Tested: bun test src/commands/headless.test.ts
Tested: cd desktop && bun run test -- composerUtils.test.ts pages.test.tsx
Tested: NODE_ENV=test ANTHROPIC_API_KEY=dummy bun -e getCommands assertion for hidden goal command
The simplified /goal surface only supports setting a condition and clearing it. Removed subcommand names such as status were still valid free-form objectives, so a user trying the old query flow could overwrite the real goal with a goal named status and make the desktop state look stuck in progress.
Constraint: /goal should stay as /goal <condition> and /goal clear for the prelaunch simplified UX
Rejected: Reintroduce /goal status | it expands the command surface the product direction intentionally removed
Confidence: high
Scope-risk: narrow
Directive: Do not add pseudo subcommands back to the desktop picker unless the CLI command surface is deliberately expanded again
Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
A user-requested drag target should reuse the existing attachment pipeline instead
of creating a second upload path. The composer now handles browser DataTransfer
drops and Tauri native drag-drop events, while keeping desktop attachments
path-only so large files are not serialized into chat payloads. The fallback
/goal metadata is also aligned with the existing desktop command surface because
the desktop gate exercises that menu while validating composer behavior.
Constraint: Desktop attachments must remain path-only to avoid inflated websocket payloads
Rejected: Read dropped desktop files with FileReader | would reintroduce large inline data payloads
Confidence: high
Scope-risk: moderate
Directive: Do not replace the Tauri drag-drop path conversion with data URLs without rerunning payload-size regression coverage
Tested: cd desktop && bun run test -- --run src/lib/composerAttachments.test.ts src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun run check:desktop
Tested: agent-browser overlay and dropped-chip smoke at http://127.0.0.1:1421/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456
Tested: bun run verify
Not-tested: Finder-to-packaged-.app manual drag smoke
The /goal feature has not shipped, so the desktop and CLI paths should only understand the current set/clear protocol instead of carrying transitional transcript formats. This removes Goal created/replaced and old status-block parsing from hydration, WebSocket forwarding, and desktop history mapping.
Constraint: Feature is still pre-release, so historical Goal created/replaced transcript compatibility is unnecessary.
Rejected: Keep old status-block hydration | it preserves a surface that users never received and makes the simplified /goal protocol harder to reason about.
Confidence: high
Scope-risk: narrow
Directive: Do not re-add legacy /goal status-block parsing unless a shipped transcript format requires it.
Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts src/server/__tests__/ws-memory-events.test.ts src/server/__tests__/sessions.test.ts src/server/__tests__/conversations.test.ts
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx
The desktop composer had treated /goal lifecycle arguments as standalone slash-command rows. That made the UI look like /goal had second-level commands even though the CLI contract is one /goal command with arguments.
Constraint: Keep /goal status/pause/resume/complete/clear usable as CLI arguments.
Rejected: Keep pseudo subcommands in the picker | it contradicts the slash command model and confuses objective entry.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/__tests__/pages.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
New desktop installs and settings payloads without an explicit theme now hydrate to the pure white workspace. The General settings selector keeps the other two themes available, but presents pure white first so the default and visible ordering agree.
Constraint: Existing persisted theme choices must continue to win over the default.
Rejected: Remove the warm classic theme | users still need the alternate light appearance.
Confidence: high
Scope-risk: narrow
Directive: Keep the localStorage and user-settings theme fallbacks aligned when changing desktop theme defaults.
Tested: cd desktop && bun run test src/stores/uiStore.test.ts src/stores/settingsStore.test.ts src/__tests__/generalSettings.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Full root bun run verify.
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.
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
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
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 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
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
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
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.
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.
Goal commands already had local CLI metadata, but desktop and SDK init consumers only received a name list. Keep the legacy slash_commands string array intact and add explicit metadata so clients can render descriptions and usage hints without breaking older readers.
Constraint: Existing SDK/system init consumers may depend on slash_commands staying string-only.
Rejected: Replace slash_commands with objects | would risk breaking older SDK and desktop consumers.
Confidence: high
Scope-risk: moderate
Directive: Keep slash_commands as the compatibility name list unless all SDK consumers have migrated.
Tested: bun test src/server/__tests__/websocket-handler.test.ts src/utils/messages/systemInit.test.ts src/commands/headless.test.ts src/goals/goalState.test.ts src/goals/goalEvaluator.test.ts
Tested: cd desktop && bun test src/components/chat/composerUtils.test.ts
Tested: bun run check:server
Tested: bun run check:desktop