Message action controls stay visible through focus-within for keyboard navigation, but pointer clicks left the copy/fork buttons focused after the mouse moved away. Release pointer focus on message actions so hover controls return to their quiet state while keeping Tab focus behavior intact.
Constraint: Message actions use focus-within for keyboard accessibility and should not remove that path.
Rejected: Remove focus-within reveal | would regress keyboard users who tab to copy or fork.
Confidence: high
Scope-risk: narrow
Directive: Keep pointer-only blur scoped to message action controls; do not remove focus-within without a replacement keyboard affordance.
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run
Tested: bun run check:desktop
Not-tested: Live Tauri manual hover smoke
Related: #642
Large desktop installs can accumulate huge JSONL transcripts, and the sidebar session list previously loaded each requested transcript into memory on cache misses. The main branch cache from #651 handles repeated hot refreshes, so this change keeps that cache and replaces cache-miss list metadata extraction with a sequential streaming summary scan for paginated files only. It also makes Windows sidecar shutdown prefer taskkill /T so stale child processes are less likely to survive app stop paths.
Constraint: Must preserve main's short session-list cache from #651 while reducing cache-miss memory growth.
Rejected: Rely only on the #651 cache | cache expiry, first load, and pagination still hit full JSONL reads.
Rejected: Add a persistent summary index | larger persistence migration risk than needed for this issue.
Confidence: medium
Scope-risk: moderate
Directive: Do not replace the streaming scanner with full readJsonlFile calls without re-running large-transcript pressure tests.
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: bun run check:server
Tested: bun run check:native
Tested: bun run check:desktop
Tested: synthetic pressure test with 140 sessions x 1200 JSONL entries showed lower RSS growth than main baseline.
Not-tested: Windows live taskkill /T process-tree cleanup.
Dark-mode Markdown text was still inheriting Tailwind Typography's default prose color, so normal and italic text could render dark while strong text used the app theme token.
Map markdown prose variables to the desktop theme tokens, move code-viewer hover and line-number styling onto theme tokens, and make Mermaid/Diff rendering follow the active app theme. Write/Edit/tool result and workspace render paths were audited; their core code and diff surfaces already use theme tokens, with the DiffViewer theme handoff tightened here.
Constraint: Mermaid theme customization requires the base theme and concrete color values, so CSS theme tokens are resolved to hex before rendering.
Rejected: Add per-element prose utility overrides only | misses Tailwind Typography defaults for quotes, counters, tables, and future prose elements
Confidence: high
Scope-risk: moderate
Directive: Keep markdown, code, diff, and diagram colors routed through shared desktop theme tokens; do not reintroduce fixed light backgrounds in chat renderers.
Tested: cd desktop && bun run test -- --run src/theme/globals.test.ts src/components/markdown/MarkdownRenderer.test.tsx src/components/chat/CodeViewer.test.tsx src/components/chat/MermaidRenderer.test.tsx src/components/chat/DiffViewer.test.tsx src/components/chat/chatBlocks.test.tsx src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Not-tested: Full Tauri live desktop session with real provider output; browser broad-audit screenshot capture timed out, but computed colors were captured for all three themes.
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.
The desktop session sidebar can poll /api/sessions while long live-model turns are streaming. Re-reading the same paginated JSONL files on every request adds avoidable server pressure, so list metadata is cached briefly and invalidated when session metadata changes.
Constraint: Session transcripts remain the source of truth and can be large during long real-model conversations.
Rejected: Cache transcript parses indefinitely | stale titles and counts would be harder to reason about when external writers touch JSONL files.
Confidence: medium
Scope-risk: moderate
Directive: Keep the list-session cache TTL short unless every transcript writer routes through SessionService.
Tested: bun test src/server/__tests__/sessions.test.ts --timeout 30000
Tested: bun run check:server
Tested: DeepSeek deepseek-v4-pro and Sub2API gpt-5.5 long real-model repro runs for #651
Not-tested: Windows 11 TCP reset reproduction; local macOS runs did not reproduce the exact hang.
Related: #651
Message rows should stay visually quiet until the user needs copy or fork controls. The action row now appears on message hover/focus, uses icon-sized controls, and shows the message timestamp beside the actions with locale-aware recent and historical formats.
Constraint: Desktop chat actions already own copy and fork affordances, so the change keeps that surface instead of adding a separate always-visible timestamp row.
Rejected: Keep text-labeled Copy buttons | the row remained too visually heavy for every message.
Confidence: high
Scope-risk: narrow
Directive: Keep copy, fork, and timestamp as one hover/focus metadata row unless the message layout is redesigned as a whole.
Tested: bun run check:desktop
Not-tested: Live Tauri native packaging
A turn can touch many files; an open-with affordance on every row is noise.
Restrict the per-file 打开方式 pill to rendered-previewable types via a new
isPreviewableChangedFile() predicate. Source rows keep their inline diff toggle.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
圈选标注截图作为引用承载元素位置;输入框文字只放用户修改意见+具体改动(人话),
不再写 selector/nthPath/computedStyles/页面URL/「请在源码中落地」。沿用进输入框+手动发送。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each file row now shows a material-symbol document-type icon, bold filename,
and a type subtitle (e.g. 文档·MD / 代码·TS) alongside a labeled 「打开方式 ⌄」
pill replacing the old open_in_new icon button; diff-expand is preserved.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extend the injection effect in AssistantMessage to also append a ▾ trigger
after inline <code> elements whose text is a browser-localhost or remote URL,
so AI-written run URLs like \`http://localhost:9527/\` get the same open-with
menu that markdown <a> links already receive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each file row in CurrentTurnChangeCard now has a sibling "open with" button
(open_in_new icon) that shows an OpenWithMenu, reusing buildOpenWithItems and
openWithContextForWorkspaceFile. HTML/XHTML files also get the in-app browser
option. Tests and i18n keys (openWith.title) included.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire createPicker into the preview-agent IIFE: enter/exit picker via host messages, bind DOM mousemove/click (capture phase) to picker hover/select, and on click emit a selection event carrying ElementMetadata + annotated screenshot. Route the selection event in previewEvents.ts through buildSelectionComposerText into queueComposerPrefill with the screenshot attachment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Routes preview://event (navigated/ready) from Rust into the browser store via a new subscribePreviewEvents subscriber; adds title field and setNavigated/setReady reducers to BrowserSessionState; BrowserSurface subscribes on mount to replace M1 optimistic nav-state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Wire L1 link routing into assistant message rendering. AssistantMessage now
accepts an optional sessionId and, when present, builds an onLinkClick that runs
handlePreviewLink against the running local server base (getServerBaseUrl) and
the browser/workspace panel stores, preventing default on handled links.
MessageList passes sessionId through in the assistant_text case.
Adds getServerBaseUrl() to desktopRuntime as a synchronous wrapper over the api
client's cached getBaseUrl().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>