218 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
e9e4f3e1e5 fix: dismiss chat selection bubble on scroll (#672)
The chat add-to-session popover is anchored to viewport coordinates, so it can float over unrelated messages after the transcript scrolls. The shared selection-popover dismissal hook now treats captured scroll as a dismissal signal, matching the existing outside-click behavior and clearing the stale text selection.\n\nConstraint: Desktop selection popovers are owned by message/workspace child components, not by the transcript scroll container.\nRejected: Thread scroll state through every selectable message | would re-render message rows during scroll for a global dismissal concern.\nConfidence: high\nScope-risk: narrow\nDirective: Keep selection popover dismissal centralized unless a future menu needs scroll persistence explicitly.\nTested: bun run test -- src/components/chat/MessageList.test.tsx -t "dismisses the selected-message action when the message list scrolls"\nTested: bun run test -- src/components/chat/MessageList.test.tsx\nTested: bun run check:desktop
2026-06-01 16:48:25 +08:00
程序员阿江(Relakkes)
80b2e98f8d fix: preserve composer state for browser screenshots (#678)
Browser screenshot capture reused composer prefill, which replaced local draft text and remained queued after the composer consumed it. The fix keeps rewind-style prefill as replacement while letting Browser screenshots append as one-shot composer attachments.

Constraint: Screenshot events can arrive while the chat composer is unmounted during Settings navigation.

Rejected: Store browser screenshots only in composerDraft | mounted composers would not update immediately.

Confidence: high

Scope-risk: narrow

Directive: Keep Browser screenshot capture on append mode; replacement prefill is for rewind-style composer restoration.

Tested: bun run test -- ChatInput.test.tsx previewEvents.test.ts --run

Tested: bun run check:desktop

Not-tested: Live Windows desktop/Tauri screenshot click
2026-06-01 16:48:03 +08:00
程序员阿江(Relakkes)
ebc78befbe fix: avoid static previews for frontend index HTML
Project entry HTML often only mounts a dev-server app, while built output can be served statically. The desktop open-with policy now only offers the in-app browser for static HTML candidates, and preview-fs rewrites built HTML root-relative assets under the workspace preview path.

Constraint: Frontend project index.html needs a dev server instead of preview-fs static serving
Rejected: Treat every .html file as browser-previewable | project entry HTML can render blank or with missing assets
Confidence: high
Scope-risk: moderate
Tested: cd desktop && bun run check:desktop
Tested: bun run check:server
Tested: git diff --check
Not-tested: bun run verify was intentionally stopped at user request
2026-06-01 03:16:52 +08:00
程序员阿江(Relakkes)
7ca702a253 fix(desktop): simplify changed-file open-with menus
Changed-file rows are easier to scan when previewable files are grouped before source-only files, and the file open-with menu should only expose useful preview, editor, and reveal actions. Output target cards also no longer need a separate copy control now that open/open-with are the primary actions.

Constraint: Keep the existing open-with API and menu components shared across changed files, workspace context menus, and output target cards.
Rejected: Keep system-default open for files | it duplicated less predictable platform behavior and added a low-value final menu item.
Confidence: high
Scope-risk: narrow
Directive: File open-with menus should stay ordered by immediate preview/open usefulness before editor and reveal actions.
Tested: cd desktop && bun run test src/lib/openWithItems.test.ts src/components/workspace/WorkspaceFileOpenWith.test.tsx src/components/chat/AssistantOutputTargetCard.test.tsx src/components/chat/CurrentTurnChangeCard.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
2026-06-01 01:43:36 +08:00
程序员阿江(Relakkes)
14c37d5fc1 fix: polish preview selection affordances
Preview selection turns should disclose the user's requested change on hover
or focus, while the browser capture controls should live in the address
toolbar like the referenced Codex UI. The message keeps a compact element tag
by default and exposes the note through a real tooltip instead of relying on a
native title-only hint.

Constraint: No issue number was provided for this task.
Constraint: Preserve the existing preview bridge protocol and direct-send selection flow.
Rejected: Native title-only hover text | it is too subtle and does not match the requested visible hover affordance.
Rejected: Keep a second preview action toolbar row | it contradicts the referenced right-aligned browser chrome.
Confidence: high
Scope-risk: narrow
Directive: Keep preview capture and picker actions in the browser address chrome unless the reference interaction changes.
Tested: cd desktop && bun run test --run src/components/chat/AttachmentGallery.test.tsx src/components/browser/BrowserSurface.test.tsx src/components/browser/BrowserAddressBar.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Not-tested: Real Tauri webview visual smoke; browser automation startup failed, local Vite HTTP smoke returned 200.
2026-06-01 01:41:54 +08:00
程序员阿江(Relakkes)
eb68791ac5 fix: send preview selections as annotated chat turns
Element selection from the local preview should preserve the visual target
the user picked and send the confirmed request directly to chat. The
screenshot now captures the visible viewport before drawing the marker, and
the selection event sends a model-facing prompt while the chat UI shows only
the annotated image and compact element label.

Constraint: No issue number was provided for this task.
Constraint: Keep the preview-agent protocol shape unchanged.
Rejected: Prefill the composer with the selection note | it keeps the old extra confirmation step and exposes implementation text in the input.
Rejected: Full-document annotation coordinates | body-relative captures drift from the viewport selection users see.
Confidence: high
Scope-risk: moderate
Directive: Do not route confirmed preview selections back through composer prefill without rechecking the Codex-style direct-send flow.
Tested: cd desktop && bun run test --run src/preview-agent/screenshot.test.ts src/lib/previewEvents.test.ts src/lib/selectionComposer.test.ts src/components/chat/AttachmentGallery.test.tsx src/stores/chatStore.test.ts
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build:preview-agent
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Real Tauri webview click-through smoke; in-app Browser automation timed out while local Vite HTTP smoke returned 200.
2026-06-01 01:07:08 +08:00
程序员阿江(Relakkes)
4f0025700f fix(desktop): scope inline image gallery hover
Inline image galleries live inside chat message shells that already use Tailwind group hover for message-level affordances. Reusing the unqualified group selector on image tiles made a parent message hover reveal every fullscreen affordance in the gallery at once. Naming the image tile group keeps the overlay state local to the hovered tile while preserving the existing layout and click behavior.

Constraint: Desktop chat message shells also use generic Tailwind group hover selectors.
Rejected: Track hovered image index in React state | unnecessary runtime state for a selector-scoping issue
Confidence: high
Scope-risk: narrow
Directive: Keep image-tile hover selectors scoped if this gallery remains nested inside message shells.
Tested: cd desktop && bun run test InlineImageGallery.test.tsx --run
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Manual desktop screenshot smoke
2026-06-01 00:44:11 +08:00
程序员阿江(Relakkes)
998e85efab fix: localize synthetic business errors
Synthetic API errors were carrying only English display text, so desktop chat could not distinguish our compatibility guidance from raw upstream failures and recovery logic depended on those English strings.

Add stable business error codes for media, PDF, request-size, prompt-length, and auto-mode errors. Desktop chat now resolves those codes through locale keys and suppresses the stale English fallback for known business errors, while normalizeMessagesForAPI still supports legacy text matching for old transcripts.

Constraint: Raw upstream provider details must remain available for diagnostics but should not drive localized business UX.

Rejected: Localize src/services/api/errors.ts strings directly | that would make persisted transcript text and media recovery depend on the active UI language.

Confidence: high

Scope-risk: moderate

Directive: Add new user-facing synthetic API errors with businessErrorCode values before localizing their display text.

Tested: bun test src/services/api/errors.test.ts tests/mediaRecoveryAndEstimation.test.ts src/server/__tests__/ws-memory-events.test.ts

Tested: cd desktop && bun run test -- run src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx

Tested: bun run check:server

Tested: bun run check:desktop

Not-tested: Manual desktop screenshot smoke for the rendered Chinese error card
2026-06-01 00:36:29 +08:00
程序员阿江(Relakkes)
8e5e86353a fix(desktop): simplify workbench open affordances
Previewable changed-file rows no longer show a redundant workspace chevron when the row already has an Open With control. The unified Workbench now owns the Browser entry point, and opening Browser mode creates a blank address bar instead of navigating to a hard-coded localhost port.

Constraint: Keep explicit preview links and localhost output cards able to open the browser directly with their target URL.
Rejected: Keep the top toolbar browser button | it duplicated the Workbench Browser tab and silently opened localhost:5173.
Rejected: Use localhost:5173 as the blank browser default | it makes arbitrary Browser mode look tied to a dev server.
Confidence: high
Scope-risk: moderate
Tested: cd desktop && /Users/nanmi/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ./node_modules/typescript/bin/tsc --noEmit
Tested: cd desktop && /Users/nanmi/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ./node_modules/vitest/vitest.mjs run src/components/chat/CurrentTurnChangeCard.test.tsx src/stores/browserPanelStore.test.ts src/components/browser/BrowserAddressBar.test.tsx src/components/workbench/WorkbenchPanel.test.tsx src/components/browser/BrowserSurface.test.tsx src/components/layout/TabBar.test.tsx --pool=forks --poolOptions.forks.singleFork=true
Tested: git diff --check -- desktop/src/components/chat/CurrentTurnChangeCard.tsx desktop/src/components/chat/CurrentTurnChangeCard.test.tsx desktop/src/components/browser/BrowserAddressBar.tsx desktop/src/components/browser/BrowserAddressBar.test.tsx desktop/src/components/browser/BrowserSurface.tsx desktop/src/components/browser/BrowserSurface.test.tsx desktop/src/components/layout/TabBar.tsx desktop/src/components/layout/TabBar.test.tsx desktop/src/components/workbench/WorkbenchPanel.tsx desktop/src/components/workbench/WorkbenchPanel.test.tsx desktop/src/stores/browserPanelStore.ts desktop/src/stores/browserPanelStore.test.ts
Not-tested: Full desktop check:desktop gate.
2026-05-31 23:18:20 +08:00
程序员阿江(Relakkes)
fac8ffb0f2 fix(desktop): reserve chat action hover space
Message actions still fade in on hover and focus, but their row now keeps a stable height so transcript content does not jump when the pointer enters or leaves a message.

Constraint: Preserve the existing hover-only visibility and copy/fork interaction model.
Rejected: Keep animating height from zero | it causes the visible transcript reflow this change is fixing.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && /Users/nanmi/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ./node_modules/typescript/bin/tsc --noEmit
Tested: git diff --check -- desktop/src/components/chat/MessageActionBar.tsx desktop/src/components/chat/MessageList.test.tsx
Not-tested: Focused Vitest could not complete in this local shell because the Vitest/esbuild startup process stalled or reported esbuild service stopped.
2026-05-31 23:01:46 +08:00
程序员阿江(Relakkes)
0ab624bff4 fix(desktop): remove hidden chat action hover strip #642
The previous fix moved the hover group onto the message shell, but the action row still occupied layout space while invisible. That invisible row kept expanding the shell hit area, so moving the pointer into the empty metadata strip could still reveal copy and timestamp controls.

Collapse the action row to zero height by default and expand it only while the message shell is hovered or focused. Keep message content spacing separate from the action row so hidden controls do not create a hover target.

Constraint: Copy/fork controls must remain reachable after the bubble reveals them.
Rejected: Use display:none for the action row | would remove the keyboard focus path entirely.
Confidence: high
Scope-risk: narrow
Directive: Keep hidden message metadata rows out of normal hover hit-testing; do not reintroduce invisible layout space around chat bubbles.
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run -t "keeps user actions anchored"
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run
Tested: cd desktop && bun run test src/components/chat/UserMessage.test.tsx --run
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Live Tauri native hover smoke
Related: #642
2026-05-31 21:43:19 +08:00
程序员阿江(Relakkes)
9b3e58b60b perf(desktop): skip off-screen paint on non-virtualized transcript rows (WebKit)
WebKit (Tauri WKWebView) paints complex message DOM ~2x slower than Blink and stutters during scroll. Add content-visibility:auto + contain-intrinsic-size to fully-mounted (non-virtualized) rows so the engine skips off-screen rows without unmounting or re-measuring them.

Measured with Playwright WebKit on the reported laggy 79-message session (main): worst frame 279ms -> 18ms, jank 2 -> 0. Virtualization was ruled out — it made WebKit *worse* (tanstack 12-24fps) due to scroll-time measureElement/re-render cost.

Only non-virtualized rows get the class (no ResizeObserver); virtualized window items stay free of content-visibility to avoid the zero-height measurement regression. Tests guard both directions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
ed574777e3 fix(desktop): make Mermaid diagrams readable at scale #636
Large Mermaid flowcharts were still hard to inspect in the desktop chat surface because ultra-wide SVGs could be squeezed or opened at an unhelpful crop. Normalize the rendered SVG, keep edge strokes visible while preserving author styles, and add fit-first overview rendering for both inline chat cards and the preview modal.

Constraint: Mermaid emits SVG sized for generic web pages, while desktop chat needs readable SVG behavior inside constrained WebView panels.

Rejected: Replace Mermaid with a node-canvas library | it would not be a drop-in renderer for Mermaid syntax and would add unnecessary dependency and migration risk.

Confidence: high

Scope-risk: moderate

Directive: Keep SVG normalization additive; do not override explicit linkStyle or marker paint without adding a regression test.

Tested: bun run check:desktop

Tested: Real desktop frontend visual QA with complex Mermaid screenshots at Fit, 25%, 50%, 75%, 100%, and 125%.

Not-tested: Packaged Tauri WebView smoke.
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
8c5c2a0913 fix(desktop): narrow chat action hover area #642
Message actions were still tied to the full message row, so moving the pointer into blank space beside a short chat bubble kept copy and timestamp controls visible. Move the hover group onto the actual message shell and keep full-width behavior only for document-style assistant replies.

Constraint: Message actions still rely on group-hover and focus-within for pointer and keyboard access.
Rejected: Remove focus-within reveal | would regress keyboard users who tab to copy or fork controls.
Confidence: high
Scope-risk: narrow
Directive: Keep normal bubble hover bounds tied to the message shell; document-layout replies intentionally remain full-width.
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --run
Tested: cd desktop && bun run test src/components/chat/UserMessage.test.tsx --run
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Live Tauri native hover smoke
Related: #642
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
1f9af7e578 feat: scope desktop reasoning effort to sessions
Desktop reasoning effort now follows the selected session runtime instead of the General settings surface. Session metadata records provider, model, and effort so reconnects and prewarm restarts preserve the per-session runtime, while the default effort is max for new sessions.

Constraint: Existing transcript and localStorage runtime data must remain readable without a required migration.
Rejected: Keep effort as a General setting | it made one session change leak into every other session.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce global desktop effort controls without proving multi-session isolation.
Tested: bun run check:server
Tested: bun run check:desktop
Tested: bun run check:native
Tested: bun run check:policy
Tested: bun run check:coverage
Tested: cd desktop && bun run test -- src/components/controls/ModelSelector.test.tsx src/stores/chatStore.test.ts
Tested: bun test src/server/__tests__/settings.test.ts --test-name-pattern "effort"
Tested: bun test src/server/__tests__/conversations.test.ts --test-name-pattern "runtime"
Not-tested: Live provider smoke for domestic providers without local credentials.
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
29586ce384 fix: stabilize empty agentTaskNotifications to prevent scroll re-renders
The "?? {}" fallback allocated a fresh object every render, defeating the React.memo barriers on ToolCallGroup and MessageBlock for sessions without agent task notifications, so every visible tool-call card re-rendered on each scroll frame. Reuse a module-level empty constant, mirroring EMPTY_MESSAGES.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:42:30 +08:00
程序员阿江(Relakkes)
66a0d45627 fix: hold Mermaid rendering until streams finish #636
Streaming assistant output can expose incomplete Mermaid fences before the
model has closed the diagram syntax. Keep those blocks in a generating state
until the assistant message finalizes, then render the completed diagram. Also
preserve Mermaid label content in sanitized SVG output so preview and expanded
views keep node text visible.

Constraint: marked treats unclosed fenced code as a code block during streaming
Rejected: Keep retrying Mermaid render on partial syntax | users still see parse errors while valid output is incomplete
Confidence: high
Scope-risk: narrow
Directive: Do not render parser-backed diagram components from streaming markdown without an explicit pending state
Tested: bun run check:desktop
Not-tested: live provider smoke after Codex host Crashpad renderer/utility dumps were observed
2026-05-31 17:42:09 +08:00
程序员阿江(Relakkes)
fd25e5732f fix: hide message actions after pointer clicks #642
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
2026-05-31 17:42:09 +08:00
程序员阿江(Relakkes)
d9d0d20782 fix: repair desktop theme rendering for markdown #636
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.
2026-05-31 17:42:09 +08:00
程序员阿江(Relakkes)
b483a9929d fix: reveal message actions with timestamps on hover (#642)
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
2026-05-31 17:41:43 +08:00
程序员阿江(Relakkes)
1ca29d8a4a fix(desktop): hide child webview while a fullscreen overlay (image preview) is open 2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
144ee5a779 fix(desktop): open-with trigger toggles its menu (re-click closes; ignore trigger in outside-close) 2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
f17b0828a0 feat(desktop): change-card row opens the workspace diff instead of an inline diff
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
d7d7cffddb feat(desktop): render AI-output videos inline as <video> (mp4/webm/mov)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
f2f230914d feat(desktop): collapse long change-file lists in the turn card (show N more)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
0c24a5da3e feat(desktop): render relative workspace images inline; stop images doubling as cards
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
ac0cb4d0a0 feat(desktop): only previewable change rows (md/html/image) get the open-with pill
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>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
78baace37e fix(desktop): de-duplicate localhost url in output card (hide subtitle when == title)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
95d1ba997a feat(desktop): render message-complete previewable refs as rich cards (drop inline pill)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
f93c94bb09 fix(desktop): inline open-with trigger = clear 打开方式 pill (bigger hit target)
真机验证发现正文里那个像素级小 ▾ 难以点中(富卡片的「打开方式」大按钮则一点就弹)。
改成带边框的「打开方式 ⌄」小药丸:更大的点击区 + 更可发现。事件委托不变(aria-label 保留)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
81ed3e2530 feat(desktop): render changed files as rich cards (icon/type/open-with pill)
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>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
4f03b8f6e6 feat(desktop): show open-with affordance on URLs rendered as inline code
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>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
3f1617d139 feat(desktop): add per-file open-with/preview to the turn changes card
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>
2026-05-31 17:40:46 +08:00
程序员阿江(Relakkes)
0035e7df3f feat(desktop): add open-with menu on previewable links in completed AI messages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
55b9ccc276 feat(desktop): add reusable open-with menu + items builder (IDE/Finder/browser)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
029c2a3f8f feat(desktop): add open-with menu component for links
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
d9ebf4c2f1 feat(desktop): route assistant message links to in-app preview surfaces
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>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
62f1aa71e8 fix: prevent accidental desktop chat sends (#631)
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
2026-05-31 17:39:25 +08:00
程序员阿江-Relakkes
bee3d3286b
Merge pull request #593 from yjjheizhu/feat/i18n-slash-command-descriptions
feat(desktop): i18n slash command descriptions in chat composer
2026-05-27 21:19:45 +08:00
派大星
b2dfeb11cf fix(desktop): prevent raw i18n key from leaking in slash command descriptions
getLocalizedFallbackCommands previously passed a constructed key to t()
without checking if the translation actually resolved. When the i18n
table lacked an entry, t() returned the raw key (e.g.
'slashCmd.clear.description'), which dosubot flagged in PR #593.

Fix: default to the static English description, only override when
t(key) returns a different string. Add two tests covering missing-key
fallback and partial-translation scenarios.

Co-Authored-By: qwen3.6-plus <QwenLM@claude-code-best.win>
2026-05-26 22:52:18 +08:00
程序员阿江(Relakkes)
6e1766abae Revert "fix: session branch visibility and parent-child tree display (#561)"
This reverts commit 1b52da3587325715f06948d5d526f71b00c7d9fb.

The session parent-child tree display in sidebar is visually unappealing
and unnecessary. The branch button behavior during AI response is also
intentional - users should not switch branches mid-conversation.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 19:26:15 +08:00
程序员阿江(Relakkes)
1b2bded1a2 feat: streamline workspace references from the file tree (#590, #597)
The workspace panel already had the attachment model for both files and directories, but directory rows lacked the same right-click path and there was no low-risk handoff from the panel into the active composer text. This keeps context attachment as the stable source of file content while adding a small composer insertion event for inline citations.

Constraint: Keep the existing attachment pipeline unchanged so file and directory contents still flow through the proven workspace reference path
Rejected: Build a rich inline chip editor immediately | larger composer and history-synchronization surface than these issues require
Confidence: high
Scope-risk: moderate
Directive: Do not replace workspace attachments with inline text-only mentions without rechecking model-content generation and history restore behavior
Tested: cd desktop && bunx vitest run src/components/workspace/WorkspacePanel.test.tsx
Tested: cd desktop && bunx vitest run src/components/chat/ChatInput.test.tsx
Tested: bun run check:desktop
Not-tested: Live provider response quality with inline citation wording
2026-05-25 18:13:09 +08:00
程序员阿江(Relakkes)
2db9a210b1 fix: prevent Windows chat content jitter (#603)
Windows WebView2 can report one-pixel content resize oscillations while a thinking or tool response is pinned to the bottom. That fed repeated bottom-scroll corrections back into the message list and made otherwise static chat content visibly move.

Ignore sub-2px content resize deltas before following live content growth, so real message growth still stays pinned without turning tiny layout jitter into scroll movement.

Constraint: Issue #603 reproduces on some Windows WebView2/display paths but not consistently across macOS or all Windows machines
Rejected: Disable bottom-following during thinking | would regress active response visibility when content actually grows
Confidence: medium
Scope-risk: narrow
Directive: Keep ResizeObserver follow behavior tolerant of sub-pixel or one-pixel WebView2 height jitter
Tested: cd desktop && bun run test -- --run src/components/chat/MessageList.test.tsx src/components/chat/virtualHeightCache.test.ts
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: Browser smoke at http://127.0.0.1:17999 loaded Claude Code Companion with no frontend error logs
Not-tested: Live Windows WebView2 runtime retest on reporter machine
Related: #603
2026-05-25 17:33:40 +08:00
派大星
2eaae94d40 feat(desktop): i18n slash command descriptions in chat composer
The slash command menu in ChatInput and EmptySession was hard-coded to
English descriptions, so users running the desktop with locale=zh saw
English copy for the built-in commands the desktop owns (/clear,
/compact, /help, /mcp, /skills, /memory, /plugin, /doctor, ...).

This change:
- Adds slashCmd.<name>.description i18n keys for all 24 built-in slash
  commands in en and zh locales.
- Introduces getLocalizedFallbackCommands(t) so React renders use the
  active locale; the existing FALLBACK_SLASH_COMMANDS constant is kept
  for non-React callers.
- Updates mergeSlashCommands so the localized fallback wins for
  built-in command names while server/team-provided commands (e.g.
  team:lark) still keep their own descriptions.
- Updates composerUtils.test.ts to reflect the new precedence and adds
  a test that built-in commands prefer the localized description even
  when the CLI broadcasts an English one.
- Adds CLAUDE.md and graphify-out/ to .gitignore (local AI assistant
  artifacts that should not be committed).

Verification:
  bun run check:desktop  -> passed (lint + vitest + build, 32.9s)
  changed-line coverage  -> 100% (120/120)

Known environment-only blockers in this clone (unrelated to this diff):
  bun run check:native   -> rustc not installed in this WSL
  bun run check:coverage -> 3 server-area suites hit
                            'error: An internal error occurred (WriteFailed)'
                            while writing very long stdout under WSL.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 13:08:33 +08:00
程序员阿江-Relakkes
98a0b5a7dc
Merge pull request #584 from moyu12-ae/fix/561-session-branch-visibility-and-tree-view
fix: session branch visibility and parent-child tree display (#561)
2026-05-24 01:59:20 +08:00
程序员阿江(Relakkes)
4a55b11c16 perf(desktop): persist virtual height cache across tab switches and defer tab-switch work
Tab switches into a long session paid for everything that was already paid for
the last time the user was on that session. Specifically the per-message height
and metric maps were cleared on every switch, so the new commit could only feed
estimated heights into buildVirtualTranscriptWindow and then chase them with
ResizeObserver callbacks. The first commit also ran getBranchableMessageTargets
and getCompletedTurnTargets — two extra O(N) walks over the messages array —
synchronously, and scrollToBottom('auto') was called inside the switch's
useLayoutEffect, which (on JSDOM and depending on layout state) added a
scrollHeight read to that critical path.

Introduces virtualHeightCache, a small module-level LRU keyed by sessionId that
holds the height and metric maps. MessageList now reads the prior maps on
switch instead of clearing them, so revisiting a session uses real measured
heights from the previous visit and skips the estimate→measure-correction
cascade. tabStore.closeTab calls dropSession so closed tabs don't leak.

The two O(N) branch / completed-turn computations now read from
useDeferredValue(messages), so they run as a low-priority follow-up render
after the first paint instead of blocking the switch commit. The switch's
useLayoutEffect now writes the bottom sentinel directly instead of calling
scrollToBottom, keeping the layout-read path out of the synchronous switch.

Tested: 731/731 desktop vitest suites pass, including 5 new virtualHeightCache
LRU/isolation/restore cases

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:50:49 +08:00
程序员阿江(Relakkes)
b8645b0122 perf(desktop): rework markdown cache and paint off-window spacers via content-visibility
Two issues remained on long transcripts. (1) The markdown parse cache was 48
entries / 1.8MB and keyed by full content string, so a 2.6MB session thrashed
constantly and every streaming chunk evicted finalized history entries because
each delta produced a new key. (2) The virtualization spacers above and below
the active window were single huge divs, leaving the WebView nothing to paint
during reconciliation and producing the literal blank gaps users were seeing.

The markdown cache now keys on `${len}:${fnv1a}` and splits into a 200-entry /
8MB finalized cache plus a small 4-entry streaming cache that cannot evict
finalized parses. AssistantMessage forwards a `streaming` flag (and stops
disabling caching entirely while streaming, so revisiting a settled turn no
longer reparses). The spacers are now broken into ~800px chunks via a
VirtualSpacer component; each chunk uses `content-visibility: auto` with a
`contain-intrinsic-size` pinned to its real height, which is the combination
that lets the WebView paint placeholder boxes even when off-screen paint is
deferred — without restoring the regression the previous content-visibility
rollout caused on in-window items.

Tested: 726/726 desktop vitest suites pass, including 3 new markdown-cache cases
and 1 new spacer-chunk + in-window-no-content-visibility regression guard

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:45:29 +08:00
程序员阿江(Relakkes)
659c63e5ba perf(desktop): memoize chat row subtree to stop window-slide re-renders
When the virtualization window slid during scroll, every visible row reconciled
from scratch because the heavy chat-row subtree had no memo barriers, and
because renderTranscriptItem rebuilt fresh branchAction / toolResult object
literals each render that broke MessageBlock's existing memo.

Wraps AssistantMessage, UserMessage, ToolCallBlock, ToolCallGroup, ToolResultBlock,
and MarkdownRenderer in React.memo, and hoists the per-message branchAction and
toolResult lookups into useMemo'd Maps keyed by message id. Window slides now
keep referentially-stable props for unchanged rows, so reconciliation skips them
and only newly entering rows pay full render cost.

Tested: 722/722 desktop vitest suites pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:41:25 +08:00
moyu12-ae
1b52da3587 fix: session branch visibility and parent-child tree display (#561)
- Fix branch button hidden when AI is responding by decoupling
  branchActionsDisabled from chatState (MessageList.tsx)
- Add sourceSessionId/sourceMessageId to SessionListItem type
  (both server and desktop)
- Scan forkedFrom in listSessions to expose parent-child relationships
- Preserve sourceSessionId/sourceMessageId in branchSession optimistic store
- Add buildSessionTree utility for tree-flattened session list with
  depth and isLastChild markers
- Render forked sessions indented with GitBranch icon in sidebar
- Add comprehensive tests (187 total: 81 desktop + 106 server)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:40:04 +08:00
程序员阿江(Relakkes)
147b744abd perf(desktop): stabilize virtualized transcript scroll and batch height measurements
Long desktop transcripts (797+ messages / 2.6MB) showed 1-2s blank frames while
scrolling. The previous strategy inflated virtualization overscan from 360px to
7200px on every wheel/touch/pointer event and routed scroll-handler state
updates through flushSync, which serialized large React reconciles onto WebKit's
compositor thread and starved paint.

Replaces the dynamic overscan + idle-timer state machine with a single stable
1200px overscan, and removes the flushSync barrier in updateAutoScrollState so
React 18's automatic batching handles the scroll-handler setStates. ResizeObserver
height callbacks now mark a pending bit and flush a single setMeasuredItemsVersion
per animation frame, instead of triggering a list-wide re-render per measured
item. Auto-scroll-on-measure is also removed so measurement feedback no longer
fights an active user scroll.

Tested: 722/722 desktop vitest suites pass

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-23 23:37:15 +08:00