The release branch has moved substantially past v0.2.6, so this records the desktop version bump and the release note that will be reviewed before the tag is created. The note groups the large post-v0.2.6 range by user-facing areas instead of replaying the raw commit list.
Constraint: The user wants to push the release-prep commit for manual GitHub development testing before any tag is created.
Rejected: Run scripts/release.ts directly | it would create the release commit and annotated tag before review.
Confidence: high
Scope-risk: narrow
Directive: Do not create v0.2.7 tag until manual GitHub validation is complete.
Tested: bun run scripts/release.ts 0.2.7 --dry
Tested: git diff --check
Not-tested: Full release gate or tag-triggered release workflow.
Desktop sessions previously only showed the final local-agent output, so long verification phases looked stalled even while the CLI was still working. The websocket bridge now forwards task start/progress events, and the desktop session state renders them as a compact background-agent panel below the active goal.
Constraint: CLI already emits task lifecycle events; desktop needed to consume the existing stream instead of polling transcript files.
Rejected: Wait for final task notifications only | that keeps the long-running phase invisible.
Confidence: high
Scope-risk: moderate
Directive: Keep task_started/task_progress available as structured desktop notifications when changing CLI event translation.
Tested: bun run verify (passed=8 failed=0 skipped=2)
Tested: bun run check:desktop
Tested: bun run check:server
Tested: bun run check:native
Tested: bun run check:coverage
Not-tested: Post-formatting check:server rerun was intentionally stopped at user request.
Formula-heavy conversations were still expensive after removing the fixed-height virtual chat window because every render repeated KaTeX expansion and HTML post-processing. Keep readable math, but reduce the DOM and CPU cost by rendering KaTeX without the hidden MathML copy, caching repeated formula output, and memoizing enhanced HTML parts outside the JSX render path.
Constraint: H5 streaming scroll follow behavior must remain unchanged
Rejected: Restore fixed-height virtualization | it caused blank gaps for variable-height math and code messages
Rejected: Disable LaTeX rendering | formula readability is a user-facing feature
Confidence: medium
Scope-risk: narrow
Directive: Do not reintroduce estimated-height chat virtualization without measured row heights for math-heavy messages
Tested: cd desktop && bun run test -- MarkdownRenderer.test.tsx MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Not-tested: Manual desktop scroll trace on the user's formula-heavy production conversation
The fixed-height virtual window introduced after v0.2.6 assumed every rendered chat item was 96px tall. Real desktop messages include markdown, math, code blocks, tool groups, and agent cards with highly variable heights, so scrolling history could replace real content with incorrectly sized blank spacer regions and make tab restoration janky.
Remove that unsafe virtualization layer and keep the existing direct scroll restoration behavior. The regression tests now keep long mixed-height transcripts and tool-call histories mounted so variable-height rows cannot disappear behind fake spacer math again.
Constraint: Desktop chat rows have unbounded variable height from markdown, KaTeX, code, tool, and agent output.
Rejected: Tune the 96px estimate | any constant row height still fails for mixed markdown and tool output.
Rejected: Add measured virtualization now | larger change surface than needed for the production white-gap regression.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce chat virtualization without real row-height measurement and scroll restore coverage.
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 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
Drag-drop support made the composer panel clip overflow so the
stateful slash and file popovers still opened but were hidden above
the panel. Restore visible overflow on the drop target while keeping
the drop overlay and attachment flow intact, then lock both active
and empty composer surfaces with regression coverage.
Constraint: Drag-drop attachments must continue using the existing path-only composer pipeline
Rejected: Move slash and file menus outside the composer tree | broader portal refactor not needed for this regression
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce overflow clipping on composer panels without proving bottom-full popovers still render
Tested: cd desktop && bun run test -- --run src/components/chat/ChatInput.test.tsx src/pages/EmptySession.test.tsx
Tested: cd desktop && bun run lint
Tested: Browser smoke at http://127.0.0.1:1421/?serverUrl=http%3A%2F%2F127.0.0.1%3A3456 for / and @src popovers
Tested: bun run check:desktop
Bring the desktop markdown LaTeX renderer back into the local main line
without folding unrelated local release edits into the merge commit.
Constraint: Local main had uncommitted release changes, so they were preserved outside the merge and restored afterward
Confidence: high
Scope-risk: narrow
Directive: Keep future markdown rendering changes covered by focused renderer tests before broad desktop gates
Tested: bun run test --run src/components/markdown/MarkdownRenderer.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: WebUI complex formula screenshot and 390px viewport overflow check
Not-tested: Full desktop gate still has unrelated /goal command hint expectation drift
Desktop assistant responses previously showed LaTeX source for common
model outputs. The markdown renderer now extracts math outside code spans
and fences, renders it through KaTeX, and applies chat-safe layout rules
for inline, block, multiline, matrix, and long-form formulas.
Constraint: Desktop chat already forces long markdown text to wrap, so KaTeX internals must opt out of that wrapping while display blocks handle overflow locally
Rejected: Render all dollar-delimited text blindly | currency and escaped dollar text would become false-positive formulas
Confidence: high
Scope-risk: narrow
Directive: Keep math extraction before marked parsing and preserve code fence protection when extending markdown rendering
Tested: bun run test --run src/components/markdown/MarkdownRenderer.test.tsx
Tested: bun run lint
Tested: bun run build
Tested: WebUI complex formula screenshot and 390px viewport overflow check
Not-tested: Full desktop gate still has unrelated /goal command hint expectation drift
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
This merge carries the set/clear /goal behavior from the Codex worktree into local main while preserving main's memory-style desktop goal cards and compact active-goal banner.
Constraint: Local main already had unrelated version/Tauri/release-note worktree changes and a newer goal UI style.\nRejected: Overwrite main's goal card UI with the worktree version | user wanted the memory-style visual direction already present on main.\nConfidence: high\nScope-risk: moderate\nTested: 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__/conversations.test.ts\nTested: 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\nNot-tested: Full bun run verify on main was not rerun after merge because unrelated dirty release/version files are present in the main worktree.
The previous /goal surface exposed management subcommands that made the CLI and desktop behavior hard to reason about. This keeps the user-facing command close to Claude Code's set/clear interaction while preserving historical transcript hydration and the existing evaluator loop.
Constraint: Desktop must see a live goal event before the model reaches the next tool call.\nRejected: Keep status/pause/resume/complete slash subcommands | requested UX is only set and clear.\nConfidence: high\nScope-risk: moderate\nDirective: Keep /goal user-facing syntax compact; extend internals without expanding the slash picker unless product explicitly asks.\nTested: 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__/conversations.test.ts\nTested: 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\nTested: bun run check:server\nTested: cd desktop && bun run lint\nTested: cd desktop && bun run build\nTested: ./bin/claude-haha -p '/goal smoke verify immediate goal output' --output-format stream-json --include-partial-messages --max-turns 1 --no-session-persistence --verbose emitted immediate system.local_command_output before model stream\nTested: ./bin/claude-haha -p '/goal clear' --output-format stream-json --include-partial-messages --max-turns 1 --no-session-persistence --verbose exited with duration_api_ms=0 and No active goal\nNot-tested: Browser click smoke was blocked by Chrome DevTools transport closing and Computer Use cgWindowNotFound on this machine.
The sticky active-goal strip was wider than the primary transcript column, making it feel detached from the chat surface. Constrain it to the same 860px content measure used by MessageList while preserving mobile side gutters.
Constraint: Keep compact workspace-panel layout full width.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Goal status previously used a standalone success-card treatment, which made /goal look visually detached from the transcript. Rework the active goal strip and goal event cards to use the same compact memory/tool-result visual language.
Constraint: Preserve /goal state visibility while reducing alert-like chrome.
Rejected: Continue tuning only goal colors | the layout still read as a separate alert instead of transcript state.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx src/theme/globals.test.ts
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
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
Goal status needed to read like persistent agent state, not a success alert. Reuse the Memory surface language and expose goal-specific aliases so the visual treatment follows each supported desktop theme.\n\nConstraint: Must keep /goal visible in both active-session chrome and transcript events.\nRejected: Continue using success tokens | they made the UI look like a warning/success card and clashed with white/dark themes.\nConfidence: high\nScope-risk: narrow\nTested: cd desktop && bun run test -- --run src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx src/theme/globals.test.ts\nTested: cd desktop && bun run lint\nTested: cd desktop && bun run build\nTested: Chrome screenshot fixture for light, white, and dark goal UI tokens.
Goal status needed to read like persistent agent state, not a success alert. Reuse the Memory surface language and expose goal-specific aliases so the visual treatment follows each supported desktop theme.\n\nConstraint: Must keep /goal visible in both active-session chrome and transcript events.\nRejected: Continue using success tokens | they made the UI look like a warning/success card and clashed with white/dark themes.\nConfidence: high\nScope-risk: narrow\nTested: cd desktop && bun run test -- --run src/pages/ActiveSession.test.tsx src/components/chat/MessageList.test.tsx src/theme/globals.test.ts\nTested: cd desktop && bun run lint\nTested: cd desktop && bun run build\nTested: Chrome screenshot fixture for light, white, and dark goal UI tokens.
Goal sessions need to feel like an ongoing agent-loop state, not a large success alert dropped into the chat. The UI now uses compact status and event treatments, and session titles stay anchored to the original goal objective instead of later /goal status commands.
Constraint: /goal history uses local_command transcript entries and later status checks can append fresh AI titles.
Rejected: Keep the existing green cards | they duplicate the objective, consume too much vertical space, and read as one-off notifications instead of durable loop state.
Confidence: high
Scope-risk: narrow
Directive: Goal status UI should remain compact and stateful; do not reintroduce large centered cards for routine lifecycle events.
Tested: bun test src/server/__tests__/sessions.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
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Browser preview at 127.0.0.1:5179 with real session 3e9117d5-b792-43c9-bf57-7aec2b124f7e on desktop and mobile viewport
Not-tested: Packaged macOS app rebuild.
Desktop history can only render the original /goal prompt if the session API returns the raw system local_command transcript entries. The prior fix handled frontend rendering, but the server dropped those entries because they do not carry message.role.
This keeps only /goal local command input and goal-like output records in getSessionMessages while leaving other internal command breadcrumbs filtered.
Constraint: Real CLI transcripts store /goal input/output as system local_command records without message.role.
Rejected: Expose all local_command records | would leak unrelated internal command breadcrumbs such as /exit.
Confidence: high
Scope-risk: narrow
Directive: Keep this allowlist goal-specific unless another command explicitly needs visible history restore.
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.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
Tested: cd desktop && bun run lint
Tested: real sessionService.getSessionMessages for 3e9117d5-b792-43c9-bf57-7aec2b124f7e returns initial /goal local_command entries
Not-tested: Packaged app rebuild after this server API fix.
Resumed desktop sessions keep the /goal lifecycle in transcript files, but the CLI status command only checked the in-memory goal map. The desktop history mapper also treated the local command breadcrumb as internal state, so the original /goal prompt disappeared when reopening a session.
This hydrates /goal command state from the current transcript before lifecycle operations and renders historical /goal command breadcrumbs as visible user messages. Query-only negative status output is kept informational so an old broken "No active goal." response cannot erase an earlier completed goal.
Constraint: Goal state is process memory at runtime, while session resume relies on persisted JSONL transcript records.
Rejected: Persist a second goal database | the transcript already contains the authoritative command lifecycle and avoids a new storage migration.
Confidence: high
Scope-risk: narrow
Directive: Treat /goal status output as a query result, not as lifecycle mutation; only explicit clear output should remove restored state.
Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.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
Tested: bun run check:desktop
Tested: real transcript hydrate for 3e9117d5-b792-43c9-bf57-7aec2b124f7e returned Goal: complete
Tested: bun test src/server/__tests__/h5-access-auth.test.ts -t 'blocks remote browser SDK requests even under explicit server auth'
Not-tested: Full check:server completed 726/727 tests; one unrelated H5 auth hook timed out in the full suite and passed on isolated rerun.
Resumed desktop sessions keep the /goal lifecycle in transcript files, but the CLI status command only checked the in-memory goal map. The desktop history mapper also treated the local command breadcrumb as internal state, so the original /goal prompt disappeared when reopening a session.
This hydrates /goal command state from the current transcript before lifecycle operations and renders historical /goal command breadcrumbs as visible user messages. Query-only negative status output is kept informational so an old broken "No active goal." response cannot erase an earlier completed goal.
Constraint: Goal state is process memory at runtime, while session resume relies on persisted JSONL transcript records.
Rejected: Persist a second goal database | the transcript already contains the authoritative command lifecycle and avoids a new storage migration.
Confidence: high
Scope-risk: narrow
Directive: Treat /goal status output as a query result, not as lifecycle mutation; only explicit clear output should remove restored state.
Tested: bun test src/commands/goal/goal.test.tsx src/goals/goalState.test.ts src/goals/goalEvaluator.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
Tested: bun run check:desktop
Tested: real transcript hydrate for 3e9117d5-b792-43c9-bf57-7aec2b124f7e returned Goal: complete
Tested: bun test src/server/__tests__/h5-access-auth.test.ts -t 'blocks remote browser SDK requests even under explicit server auth'
Not-tested: Full check:server completed 726/727 tests; one unrelated H5 auth hook timed out in the full suite and passed on isolated rerun.
The CLI supports /goal status, pause, resume, complete, clear, and --tokens as arguments, but the desktop composer only showed the top-level /goal entry and closed completion after the space. Users could run the commands if they knew them, yet the UI made them look unavailable.
Constraint: Keep CLI semantics unchanged; this is a desktop discovery and completion fix only
Rejected: Add separate CLI slash commands for every goal subcommand | would diverge from the actual command surface
Confidence: high
Scope-risk: narrow
Directive: Keep /goal subcommands as argument completions unless the CLI command model gains first-class nested slash commands
Tested: cd desktop && bun run test -- --run src/components/chat/composerUtils.test.ts src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run test -- --run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx src/pages/ActiveSession.test.tsx
Tested: cd desktop && bun run lint
Not-tested: live desktop manual interaction after restarting the app
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.
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
New desktop sessions populated slash suggestions from the session endpoint before the CLI had emitted init metadata. That endpoint only scanned user and project skill directories, while the plugin settings view and global skills API already saw enabled plugin skills such as superpowers. The session endpoint now reuses the global skill listing and merges it with any cached CLI slash commands, and both composer surfaces rank command-name matches before broad description matches so /su surfaces superpowers first.
Constraint: New sessions need plugin skills before the first real user turn starts the CLI.
Rejected: Start or restart a hidden CLI process on plugin enable | heavier than needed and still misses the REST slash-command fallback path.
Confidence: high
Scope-risk: moderate
Directive: Keep session slash commands and /api/skills on the same skill discovery path when changing plugin skill loading.
Tested: bun test src/server/__tests__/skills.test.ts src/server/__tests__/plugins.test.ts src/server/__tests__/sessions.test.ts
Tested: cd desktop && bun run test --run src/components/chat/composerUtils.test.ts src/pages/EmptySession.test.tsx src/components/chat/ChatInput.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Not-tested: Manual desktop click-through in the packaged app.
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.
Restored transcripts can still present stale running state while their final markdown and tool output continue to reflow. Resize-driven auto-follow now stays enabled only for live resizable output, so readers can scroll up after completion while active streams still remain pinned.
Constraint: Old H5 sessions can reconnect with stale non-idle UI state even when the transcript has an end_turn.
Rejected: Disable resize following entirely | active H5 streaming would regress when rendered content grows after tokens arrive.
Confidence: high
Scope-risk: narrow
Directive: Do not broaden ResizeObserver auto-follow without a restored completed-session regression test.
Tested: cd desktop && bun run test -- MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Browser smoke on session 85862799-ec6b-43d0-b3b8-6e9fd9130954 kept scrollTop at 320 after content resize.
The previous hot-reload fix covered the running CLI process, but empty desktop sessions build their slash skill list through the server-side /api/skills path. If the server had already cached disabled plugin settings, /api/plugins/reload could still reload plugin state from stale settings and /api/skills stayed empty until a real chat spawned a fresh CLI.
Reset the server settings cache at the plugin reload boundary before clearing plugin caches. This keeps the desktop server, active CLI reload, and empty-session skills API aligned on enable-after-disable transitions.
Constraint: EmptySession uses /api/skills, not the active CLI slash-command cache
Rejected: Rely on starting a real CLI session to repopulate skills | delays visibility and makes empty-session slash commands stale
Confidence: high
Scope-risk: narrow
Directive: Plugin reload paths must invalidate settings before resolving enabledPlugins
Tested: bun test src/server/__tests__/skills.test.ts src/server/__tests__/plugins.test.ts
Tested: bun run check:server
Tested: cd desktop && bun run test --run src/pages/EmptySession.test.tsx src/stores/pluginStore.test.ts
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