334 Commits

Author SHA1 Message Date
程序员阿江(Relakkes)
5d02f4c4e7 feat(desktop): subscribe preview bridge events into browser store
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>
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)
3077f1569d feat(desktop): mount BrowserSurface in right panel with TabBar toggle
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
0ac56a6b11 feat(desktop): add BrowserSurface with bounds sync and address bar wiring
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
bdfedcd2e7 feat(desktop): add BrowserAddressBar component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
53724a6652 feat(desktop): add computeWebviewBounds helper
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 17:40:45 +08:00
程序员阿江(Relakkes)
3f4d731cc7 fix: scope permission mode to sessions
Permission mode is a per-session runtime choice, while scheduled tasks cannot rely on a human approval loop. This removes the General settings permission surface, persists session permission metadata, and forces scheduled tasks to run with bypass permissions. Runtime permission changes now handle both directions across bypass boundaries, including startup/prewarm races, by persisting first and restarting only when the CLI launch mode must change.

Constraint: Scheduled tasks must be able to execute without a human standing by for authorization.

Constraint: The CLI only honors bypass permissions when launched with the skip-permissions flag, so switching to or from bypass requires a restart.

Rejected: Keep a global General permission default | it can leak across sessions and scheduled runs in ways the user cannot reason about.

Confidence: high

Scope-risk: broad

Directive: Do not reintroduce a global UI permission selector without proving it cannot affect unrelated sessions or automations.

Tested: bun test src/server/__tests__/conversations.test.ts -t "permission" --timeout 30000 (10 pass, 0 fail)

Tested: bun run check:server (858 pass, 0 fail)

Tested: cd desktop && bun run check:desktop (760 tests plus production build passed)

Tested: desktop/scripts/build-macos-arm64.sh and codesign verification passed

Tested: Real DeepSeek smoke validated plan, bypass, and scheduled task permission behavior

Not-tested: Did not repeat the full DeepSeek smoke after the final startup-race hardening; mock WebSocket permission regression and full server gate were rerun after that change.

Fixes #632
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)
990733cf2c Merge remote-tracking branch 'origin/main' 2026-05-26 00:11:06 +08:00
Relakkes Yang
c373f8cf45 fix: preserve Windows drive-root project identity
Normalize realpath results for Windows drive roots so D:\ does not round-trip to drive-relative D: and resolve back into the current repository.

Also tighten Sidebar hidden-project matching so a drive root does not match every child project on the same drive.

Tested: bun test src/server/__tests__/windows-drive-path.test.ts src/server/__tests__/sessions.test.ts src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test -- --run src/components/layout/Sidebar.test.tsx
Tested: cd desktop && bun run build:windows-x64
Tested: bun run check:desktop
Not-tested: bun run check:server (failed on existing/environment failures including the pre-existing untracked FileReadTool test)
Confidence: high
Scope-risk: narrow
2026-05-25 23:23:15 +08:00
程序员阿江(Relakkes)
76a4ca5d30 fix: preserve desktop terminal sessions across panel moves
Users can be typing in the docked terminal when they hide the panel or promote it into a tab. The old component-owned lifecycle treated those UI moves as terminal teardown and spawned a fresh shell afterward.

This moves terminal ownership into a small runtime registry keyed by panel or tab identity, keeps docked terminals mounted while hidden, and transfers the runtime id to the terminal tab when promoted. Closing the owning tab or session still releases the PTY.

Constraint: Keep native PTY behavior unchanged and fix this in the desktop React lifecycle layer.
Rejected: Persist terminal tabs through localStorage | runtime PTYs are process-local and should not be restored after app restart.
Confidence: high
Scope-risk: moderate
Directive: Do not tie terminal process lifetime to panel visibility; only explicit owning-surface close/restart should destroy it.
Tested: cd desktop && bun run test -- src/pages/TerminalSettings.test.tsx src/pages/ActiveSession.test.tsx src/components/layout/ContentRouter.test.tsx src/stores/tabStore.test.ts
Tested: bun run check:desktop
Not-tested: Manual Tauri window PTY smoke.
2026-05-25 22:33:22 +08:00
程序员阿江(Relakkes)
21954e5f5c fix: keep directory picker menu within viewport
The shared directory picker is used from right-aligned controls such as the MCP target project selector. Its portal menu previously used the trigger's raw left coordinate, so a trigger near the window edge could render the 400px menu partly off-screen.

Clamp the fixed-position menu against the viewport while preserving the existing width where space allows, and cover the right-edge case with a focused component test.

Constraint: DirectoryPicker is shared by MCP settings, task prompts, adapter settings, and launch controls.
Rejected: Special-case the MCP form layout | the overflow comes from the shared portal positioning and would remain in other right-aligned uses.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/components/shared/DirectoryPicker.test.tsx
Tested: bun run check:desktop
Not-tested: Manual browser smoke; local agent-browser automation hung during startup/command evaluation.
2026-05-25 20:10:24 +08:00
程序员阿江(Relakkes)
bda7d86a05 fix: streamline workspace file menu paths
Workspace file-tree actions should default to project-relative paths because users usually operate inside the selected work directory. The menu now keeps the common copy action relative, preserves absolute copying as an explicit secondary action, and removes the duplicate inline citation entry in favor of Add to chat.

Constraint: Desktop workspace context menu should stay compact and avoid duplicate chat-reference actions
Rejected: Keep Copy path as absolute | makes the common selected-project workflow copy a less useful path
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bunx vitest run src/components/workspace/WorkspacePanel.test.tsx
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Manual browser smoke; component interaction tests cover the changed menu behavior
2026-05-25 19:57:32 +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)
fe8aac6b83 fix: normalize Windows drive-root sessions (#601)
Windows folder selection can surface drive roots as D:, which Node treats as drive-relative instead of the absolute D:\ root. Normalize drive-root inputs before resolving launch directories, filesystem access roots, transcript metadata, and workspace paths. Use path.relative containment checks so drive roots and child projects remain distinct in session grouping and file access.

Constraint: Windows drive-root inputs may arrive as C:, D:, or any other single-letter drive prefix
Rejected: Special-case only D: | every Windows drive letter has the same drive-relative semantics
Confidence: high
Scope-risk: moderate
Directive: Keep Windows drive-root normalization centralized; do not reintroduce string-prefix containment checks for workspace roots
Tested: bun test src/server/__tests__/windows-drive-path.test.ts src/server/__tests__/filesystem.test.ts src/server/__tests__/sessions.test.ts src/server/__tests__/workspace-service.test.ts
Tested: cd desktop && bun run test -- --run src/components/layout/Sidebar.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Not-tested: Real Windows desktop smoke on a physical Windows machine
Related: #601
2026-05-25 18:16:17 +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
程序员阿江(Relakkes)
89f66a1c25 fix: align bypass permission path with active tab
The bypass-permission confirmation was resolving its displayed workspace from the session list's activeSessionId, while the chat composer sends runtime updates to the currently active tab. When those two UI states diverged after tab restores or project switches, the confirmation could show a parent or stale workspace path even though the chat was operating in another project.

Constraint: Desktop tabs and session-list selection can diverge during restore and multi-tab workflows.
Rejected: Thread workDir through every chat-composer call site | the selector already has the active tab context needed for uncontrolled usage.
Confidence: high
Scope-risk: narrow
Directive: Keep uncontrolled permission-mode actions keyed to the same active tab used for set_permission_mode.
Tested: cd desktop && bun run test -- src/components/controls/PermissionModeSelector.test.tsx --run
Tested: cd desktop && bun run lint
Not-tested: Live Windows desktop packaging smoke.
2026-05-25 16:09:31 +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
071ee48e2b
Merge pull request #586 from yjjheizhu/feat/hide-official-providers-when-not-logged-in
feat: hide official provider sections when OAuth not logged in
2026-05-24 01:53:28 +08:00
派大星
12ccd2b611 fix: use bottom-anchored positioning for above-trigger dropdown in ModelSelector
When the dropdown opens above the trigger button, use CSS bottom instead
of top to prevent a gap between the dropdown and the button when content
is shorter than maxHeight.

Co-Authored-By: claude-opus-4-6 <noreply@anthropic.com>
2026-05-24 00:53:28 +08:00
派大星
aa1321a02b feat: hide official provider sections in ModelSelector when OAuth not logged in
Only show Claude Official and ChatGPT Official model groups in the
runtime-scoped ModelSelector dropdown when the corresponding OAuth
session is active. This prevents users from selecting official models
they cannot actually use.

- Import useHahaOAuthStore and useHahaOpenAIOAuthStore into ModelSelector
- Fetch OAuth status on mount
- Pass login flags to buildProviderChoices to conditionally include sections
- Add test case verifying hidden sections when not logged in

Co-Authored-By: claude-opus-4-6 <noreply@anthropic.com>
2026-05-24 00:30:15 +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
程序员阿江(Relakkes)
91224cbd20 Merge Writer preview fix into local main
Bring the detached-worktree desktop Writer preview patch into the canonical local main checkout so long pending Write calls show visible progress from main.

Constraint: Local main had advanced beyond the detached worktree base, so this landing keeps an explicit merge boundary.
Confidence: high
Scope-risk: moderate
Directive: Preserve bounded pending Writer rendering; do not replace it with live diff rendering without performance evidence.
Tested: bun run check:desktop in detached worktree before merge.
Not-tested: Post-merge desktop gate before this merge commit was created.
2026-05-23 19:14:32 +08:00
程序员阿江(Relakkes)
6b1561cd1c fix: stream pending Write content as Writer previews
Long Write tool inputs can spend minutes streaming the file body before the tool executes, so the desktop transcript now decodes the pending content field into a lightweight Writer preview. The preview intentionally uses a bounded plain-text window while the tool is pending, then keeps the existing full diff rendering once the Write call completes.

Constraint: Write.content arrives as streaming tool input before the filesystem write executes.
Rejected: Render a live DiffViewer for every input delta | repeated diff and syntax work would reintroduce long-session jank.
Confidence: high
Scope-risk: moderate
Directive: Keep pending Writer rendering lightweight and bounded; reserve full diff rendering for completed Write calls.
Tested: cd desktop && bun run test src/components/chat/chatBlocks.test.tsx -- --runInBand
Tested: cd desktop && bun run test src/stores/chatStore.test.ts -- --runInBand
Tested: bun run check:desktop
Not-tested: Live provider long Write smoke after the UI patch.
2026-05-23 19:14:22 +08:00
程序员阿江(Relakkes)
d327b7fb30 fix: preserve pending user questions across reconnects
Desktop AskUserQuestion waits on the SDK permission bridge, and a transient renderer disconnect could previously let the short no-client cleanup kill the CLI. That turned a live user question into an AbortError and left history restore showing a stale interactive card.

Track live SDK permission requests, replay them to reconnecting clients, and give sessions waiting on user input a longer cleanup grace. Render aborted terminal results as completed history instead of an unanswered prompt.

Constraint: SDK permission requests are live in-memory control messages until answered or aborted.

Rejected: Only de-dupe history cards in MessageList | would not keep the CLI alive or restore the active permission request.

Rejected: Disable disconnect cleanup globally | would leak abandoned CLI processes for ordinary disconnected sessions.

Confidence: high

Scope-risk: moderate

Reversibility: clean

Directive: Do not shorten pending-permission cleanup without testing AskUserQuestion reconnect and abort paths.

Tested: bun run check:desktop

Tested: bun run check:server

Tested: git diff --check

Not-tested: Live Sub2API provider reconnect repro.
2026-05-23 19:06:59 +08:00
程序员阿江(Relakkes)
d116067d9c fix: virtualize long desktop transcripts
Large desktop sessions were still paying too much render and paint cost on tab switch, while the previous content-visibility approach could defer WebView painting enough to show white gaps during scroll. This moves long transcripts to a measured dynamic-height window, keeps small transcripts fully mounted, and caches completed Markdown parses so remounted assistant messages do not pay repeated parse cost.

Constraint: Tauri/WebView must not depend on content-visibility for transcript correctness because deferred offscreen paint can surface as blank regions

Rejected: Keep content-visibility:auto on every chat item | it improves synthetic DOM cost but matches the user's white-gap failure mode

Rejected: Fixed-height transcript virtualization | variable-height chat rows and tool groups can create spacer gaps and broken scroll positions

Confidence: high

Scope-risk: moderate

Directive: Do not reintroduce deferred-paint transcript optimization without testing the real Tauri/WebView scroll path on large sessions

Tested: bun run check:desktop

Tested: Chrome DevTools real-session smoke for 055015e4-6093-418e-9944-19bba25ecb69, switch trace max RunTask about 158ms and scroll blankSamples=0 across 34 sampled positions

Not-tested: Packaged Tauri binary smoke on WebKit/WebView2
2026-05-23 19:03:51 +08:00
程序员阿江(Relakkes)
385435a04d Merge desktop long-session performance fix into local main
Bring the validated detached-worktree desktop performance patch into the canonical local main checkout so long-session tab and scroll fixes are available from the main working copy.

Constraint: Local main had advanced independently beyond the detached worktree base, so fast-forward was not the correct landing shape.
Confidence: high
Scope-risk: moderate
Directive: Verify ancestry for detached-worktree landings because main may advance after the feature worktree is created.
Tested: bun run check:desktop in detached worktree before merge.
Not-tested: Post-merge desktop gate before this merge commit was created.
2026-05-23 17:56:39 +08:00
程序员阿江(Relakkes)
0231e7b61d fix: reduce desktop jank for long sessions
Long transcripts were triggering avoidable layout work during tab switches and auto-scroll because MessageList read scroll geometry while the browser still had a large message tree to lay out. The tab bar also subscribed to full chat session state, so streaming payload churn could invalidate tab chrome even when running status did not change.

This keeps variable-height transcript rows fully mounted, adds browser offscreen rendering hints, scrolls to bottom through clamped large offsets instead of synchronous geometry reads, and narrows TabBar's chat-store subscription to running-state ids.

Constraint: Prior fixed-height virtualization left blank spacer gaps for variable-height chat rows.
Rejected: Reintroduce fixed-height virtualization | would regress existing long transcript rendering behavior.
Confidence: high
Scope-risk: moderate
Directive: Do not replace this with fixed row-height virtualization without measured row heights and scroll-restore coverage.
Tested: bun run check:desktop
Tested: Chrome DevTools trace on real session 055015e4-6093-418e-9944-19bba25ecb69 with INP 84ms to 43ms and ForcedReflow insight removed.
Not-tested: Packaged Tauri WebView runtime beyond the local web UI trace.
2026-05-23 17:56:16 +08:00
程序员阿江(Relakkes)
a6d9adbc38 fix: prevent long tool streams from looking stuck
Desktop already receives tool_use start and input deltas before the final tool_use_complete event, but the chat transcript only rendered a visible tool card after the complete event. This makes long Write payloads look frozen while the model is still generating the tool input. The store now upserts a pending tool_use message on stream start, updates a lightweight input preview from deltas, and resolves that same message when the complete event arrives.

Constraint: Long Write calls spend most perceived time streaming JSON tool input, not executing the filesystem write

Rejected: Show only the global active tool label | it does not anchor progress in the transcript where the user is looking

Confidence: high

Scope-risk: moderate

Directive: Keep pending tool_use messages keyed by toolUseId so the complete event updates in place instead of duplicating cards

Tested: bun run check:desktop

Not-tested: Live provider smoke with a real 10k-word Write stream
2026-05-23 17:08:25 +08:00
程序员阿江(Relakkes)
5fcbdf322a Merge worktree label cleanup into local main
Bring the detached worktree fix into the canonical local checkout so desktop worktree sessions hide internal branch labels while preserving git-info metadata separation.

Constraint: User requested local main integration, not push or PR
Confidence: high
Scope-risk: narrow
Tested: Clean local main worktree before merge; feature commit contains server and desktop verification evidence
Not-tested: Full quality gate after merge
2026-05-23 16:16:50 +08:00
程序员阿江(Relakkes)
89ec7d66c9 fix: hide internal worktree branch labels
Desktop worktree sessions should show the source project and worktree marker without surfacing implementation refs like worktree-desktop-* as user-facing branch state. The git-info response now keeps launch branch metadata separate from worktree identity, and the desktop chip hides branch and slug labels in isolated worktree mode.

Constraint: Git worktrees need an internal branch for isolated execution, but that ref is product plumbing rather than useful UI context
Rejected: Show both launch branch and worktree slug | duplicated noisy identifiers and confused the session location
Confidence: high
Scope-risk: narrow
Tested: bun test src/server/__tests__/sessions.test.ts -t "git-info"; cd desktop && bun run test src/components/shared/ProjectContextChip.test.tsx; bun run check:server; bun run check:desktop; git diff --check
Not-tested: Live desktop screenshot smoke
2026-05-23 16:16:30 +08:00
程序员阿江(Relakkes)
853e3db6ec fix: keep AskUserQuestion state aligned with CLI answers
The CLI transcript already records each AskUserQuestion once, but the desktop bridge dropped the structured toolUseResult answers from both history replay and live WebSocket tool results. That made answered question cards look unresolved and allowed stale cards to reappear during the gap before the next permission request.

Constraint: Desktop should render the CLI question stream directly without inventing separate question lifecycle state.
Rejected: Hide all previous AskUserQuestion cards after submit | would remove resolved answer history instead of preserving transcript context.
Confidence: high
Scope-risk: moderate
Directive: Preserve toolUseResult metadata when adapting CLI transcript entries; AskUserQuestion answer state depends on the structured answers object, not the prose tool_result string.
Tested: bun test src/server/__tests__/sessions.test.ts --timeout 30000
Tested: bun test src/server/__tests__/ws-memory-events.test.ts --timeout 30000
Tested: cd desktop && bun run test src/stores/chatStore.test.ts
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx
Tested: bun run check:server
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: git diff --check
Not-tested: Full bun run verify and live desktop browser smoke.
2026-05-23 15:58:15 +08:00
程序员阿江(Relakkes)
6b62b6c633 Merge code rendering compatibility fix into local main
Bring the detached worktree fix back to the local main branch so modern WebUI keeps Shiki rendering while legacy WebKit can fall back before loading Shiki.

Constraint: Local main already contains unreleased commits ahead of origin/main.
Confidence: high
Scope-risk: moderate
Tested: Worktree commit passed focused CodeViewer test, desktop lint, desktop build, startup bundle scan, and real WebUI Shiki smoke before merge.
Not-tested: Re-running the full desktop gate after merge.
2026-05-22 17:55:45 +08:00
程序员阿江(Relakkes)
4fce6014a6 fix: preserve Shiki code rendering with legacy fallback
Code blocks should keep the richer Shiki rendering in modern WebUI while avoiding the old WebKit startup crash path by loading Shiki lazily and falling back to Prism only when the runtime cannot safely parse or execute it.

Constraint: Issue #461 is triggered by older Safari/WebKit parsing modern RegExp syntax during startup.
Rejected: Replace Shiki entirely with Prism | visual quality regressed for normal WebUI usage.
Confidence: high
Scope-risk: moderate
Directive: Keep Shiki out of the startup path unless macOS 12 WebView compatibility is re-verified.
Tested: cd desktop && bun run test -- --run src/components/chat/CodeViewer.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: Real WebUI smoke on http://127.0.0.1:1420/ verified six code blocks rendered with data-highlight-engine=shiki.
Not-tested: Physical macOS 12 WebView runtime.
Related: https://github.com/NanmiCoder/cc-haha/issues/461
2026-05-22 17:55:33 +08:00
程序员阿江(Relakkes)
861d88225c fix: improve AskUserQuestion desktop rendering
Desktop AskUserQuestion could show duplicate unresolved cards when restored history and a live permission request overlapped. The custom response field was also single-line, making long restored-context answers hard to read or edit.

This keeps the active unresolved AskUserQuestion card aligned with the live permission request, preserves resolved history, and switches custom responses to a multiline editor with Ctrl/Cmd+Enter submission.

Constraint: Desktop issue reports #542 and #543 both hit the AskUserQuestion chat surface.
Rejected: Hide all AskUserQuestion history while pending | resolved answers should remain visible in the transcript.
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test src/components/chat/AskUserQuestion.test.tsx
Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: cd desktop && bun run build
Tested: bun run check:desktop
Not-tested: Live Windows desktop manual restore flow from the issue attachment.
2026-05-21 17:20:39 +08:00
程序员阿江(Relakkes)
e437199b81 fix: Surface API retry progress in desktop chat
CLI retry events already include retry attempts, delay, and upstream status, but
the desktop WebSocket translation dropped api_retry system messages. The desktop
chat now keeps the current retry state in session state and renders a compact
active-turn indicator with attempt count, HTTP status, and countdown.

Constraint: Desktop users need visible feedback during provider retry waits after auth or network failures.
Rejected: Add retry rows to the transcript | retry heartbeats would clutter chat history instead of describing the active turn.
Confidence: high
Scope-risk: moderate
Directive: Keep api_retry as active-turn state; do not persist retry heartbeats into transcript history without a product decision.
Tested: bun test src/server/__tests__/ws-memory-events.test.ts
Tested: cd desktop && bun run test src/stores/chatStore.test.ts src/components/chat/MessageList.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:server
Tested: Browser smoke with temp CLAUDE_CONFIG_DIR and mock OpenAI-compatible 503 endpoint, screenshot /tmp/cc-haha-retry-ui.png
Not-tested: Live ChatGPT logout retry path against OpenAI production auth.
2026-05-20 18:48:36 +08:00
程序员阿江(Relakkes)
dffe83b83a Merge branch 'feat/chatgpt-official-oauth-provider' 2026-05-19 23:35:09 +08:00
程序员阿江(Relakkes)
a4aba9ec5a Show context compaction as transcript state
Desktop users need to see automatic context compaction as a transient transcript state instead of a stray stdout bubble or large card. The UI now collapses prior visible content into a compact timeline divider, shows the compacting phase, then updates the same divider with the completed summary affordance.

Constraint: CLI compaction emits both status and synthetic transcript artifacts that must be normalized before rendering

Rejected: Keep the existing small pill marker | it did not show the in-progress state and looked like chat content

Confidence: high

Scope-risk: moderate

Directive: Do not render local compact stdout as a user-visible chat message

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

Tested: bun test src/server/__tests__/ws-memory-events.test.ts

Tested: bun run check:desktop

Not-tested: full root bun run verify after the final UI iteration
2026-05-19 23:13:01 +08:00
程序员阿江(Relakkes)
8bd122c967 Keep message fork controls inline
Desktop transcript messages already expose copy controls in the shared action row. The fork affordance was rendered as its own zero-height hover row, which left the icon below Copy and made the action feel detached from the message toolbar. Moving the fork button into the existing action bar keeps the controls aligned while preserving the original branch-session API path.

Constraint: Desktop message actions should stay visually grouped for both user and assistant messages

Rejected: Keep a separate fork-only hover row | it reproduces the two-line layout regression shown in the desktop UI

Confidence: high

Scope-risk: narrow

Directive: Keep future per-message actions inside MessageActionBar unless they need a different interaction surface

Tested: cd desktop && bun run test src/components/chat/MessageList.test.tsx --pool forks --poolOptions.forks.singleFork=true

Tested: cd desktop && bun run test src/i18n/index.test.tsx --pool forks --poolOptions.forks.singleFork=true

Tested: cd desktop && bun run lint

Tested: bun run check:desktop

Not-tested: Full live desktop backend fork-click E2E
2026-05-19 23:11:03 +08:00
程序员阿江(Relakkes)
abd782494e fix: keep desktop composer toolbar out of multiline input
The active-session composer used an absolute toolbar overlay and textarea bottom padding to reserve space. When the input grew and scrolled, the caret could render inside that reserved overlay area, making it appear to pass through the controls.

Move the non-hero toolbar back into normal layout flow and remove the oversized textarea bottom padding so the text area and controls occupy separate boxes.

Constraint: Active-session and hero composer variants share the component, so keep the change scoped to the non-hero toolbar path.
Rejected: Increase textarea bottom padding | preserves the overlapping layout that caused the caret artifact.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce an absolutely positioned toolbar over the active-session textarea without validating multiline caret rendering.
Tested: cd desktop && bun run test src/components/chat/ChatInput.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: Browser DOM check confirmed textarea and toolbar no longer overlap.
2026-05-19 22:26:18 +08:00
程序员阿江(Relakkes)
318bf336b0 Expose active session state across desktop navigation
Multiple desktop sessions can run at the same time, so navigation surfaces need a compact shared signal instead of making users open each conversation to check progress. The sidebar and tab bar now derive running state from both persisted tab status and live chat state, while session rows keep worktree and updated-time metadata aligned on the right.

Constraint: Reuse existing tab/chat state without changing session persistence or server APIs
Rejected: Add another session status field | duplicated state would drift from chatStore and tabStore
Confidence: high
Scope-risk: narrow
Directive: Keep sidebar and tab running indicators sourced from the same state rules
Tested: cd desktop && bun run test src/components/layout/TabBar.test.tsx src/components/layout/Sidebar.test.tsx --pool forks --poolOptions.forks.singleFork=true
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Tested: real desktop session in /private/tmp/cc-haha-real-status-fsip8S using gpt-5.5 Sub2API-ChatGPT; running markers appeared in sidebar and tab, then cleared after CCH_STATUS_REAL_DONE
2026-05-19 22:13:54 +08:00