949 Commits

Author SHA1 Message Date
程序员阿江-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)
1464ef538e release: v0.3.1
Prepare the desktop release metadata, concise release notes, and the final terminal help polish for the 0.3.1 release. The release note groups the post-0.3.0 work by user-facing area instead of listing every commit, and the desktop version metadata is aligned for the tag-triggered packaging workflow.

Constraint: GitHub Release body is sourced from release-notes/v0.3.1.md in the tagged commit
Rejected: List every post-0.3.0 commit in the release note | too noisy for this patch release
Confidence: high
Scope-risk: narrow
Tested: cd desktop && bun run test -- src/pages/TerminalSettings.test.tsx --run
Tested: bun run check:desktop
Tested: bun run check:native
Tested: bun run verify (passed=8 failed=0 skipped=2)
Not-tested: Live provider release gate
v0.3.1
2026-05-26 00:52:43 +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)
9a6b0c4e72 fix: compact desktop terminal chrome
The terminal page used a header row, a status row, and an inner host-shell chrome, which reduced usable terminal space and made wheel scrolling fall into terminal scrollback while users were navigating Settings.

This collapses the terminal metadata and actions into one toolbar, removes the nested host-shell title bar, and forwards wheel input to the surrounding scroll container until the terminal is focused.

Constraint: TerminalSettings is shared by Settings, terminal tabs, and docked session terminals.
Rejected: Increase only the panel height | leaves duplicate chrome and scroll handoff unchanged
Confidence: high
Scope-risk: narrow
Directive: Keep terminal process lifetime separate from visual chrome; this change is layout and wheel routing only.
Tested: cd desktop && bun run test -- src/pages/TerminalSettings.test.tsx src/pages/ActiveSession.test.tsx src/components/layout/ContentRouter.test.tsx
Tested: cd desktop && bun run lint
Tested: bun run check:desktop
Not-tested: Manual Tauri window PTY smoke; browser screenshot unavailable in current tool environment
2026-05-25 23:20:28 +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)
9746a6893b docs: restore pre-push gate note
The root contributor guide was simplified without keeping the documented fast pre-push entrypoint, while the quality contract test still requires all contributor docs to name it. Restore the compact note so the policy lane and contributor guidance match again.

Constraint: scripts/pr/quality-contract.test.ts asserts root CONTRIBUTING.md documents bun run quality:push

Rejected: Relaxing the contract test | documentation drift was the cause, not an obsolete policy

Confidence: high

Scope-risk: narrow

Tested: bun test scripts/pr/quality-contract.test.ts

Tested: bun run check:policy
2026-05-25 22:18:30 +08:00
程序员阿江(Relakkes)
fa7e1438e1 fix: avoid empty MCP flash during settings load
The MCP settings page now waits for the full initial aggregation pass before
rendering list statistics or the empty state. This covers the extra project
path lookup for user-private MCP entries, so the page does not briefly show
zero servers while the desktop app is still discovering scoped configs.

The focused MCP settings test now asserts the initial loading state and keeps
existing list, edit, toggle, and reconnect flows waiting for that load boundary.

Constraint: MCP settings now loads from multiple project contexts before the first trustworthy list render
Rejected: Keep rendering zero-value stats during discovery | this caused a misleading empty flash on startup
Confidence: high
Scope-risk: narrow
Directive: Do not show MCP empty state until initial project path discovery and server fetch have both completed
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:desktop
Tested: git diff --check
2026-05-25 20:58:06 +08:00
程序员阿江(Relakkes)
043f0e7a2e docs: 简化贡献指南,补充桌面端手工测试和 PR 影响范围要求 2026-05-25 20:41:07 +08:00
程序员阿江(Relakkes)
c4890bdafc fix: keep project MCP entries visible in settings
The settings page could create project-scoped MCP servers for an explicit
target path, but subsequent reloads only fetched the active session cwd.
That made newly-added local or project MCP entries disappear when the
target project differed from the active session or was only represented in
the user config.

Load MCP settings from the active project, recent projects, and project
keys that already contain user-private MCP config. Project-scoped rows now
render their project path and use project-aware identity keys so same-name
servers in different projects stay distinct.

Constraint: MCP local scope is keyed by project path in the user config, while project scope still resolves through the request cwd
Rejected: Filesystem scan for every possible .mcp.json | unbounded and too expensive for settings load
Confidence: high
Scope-risk: moderate
Directive: Do not collapse settings MCP fetches back to a single cwd; project/local scopes are cwd-sensitive
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
Tested: git diff --check
Not-tested: Discovery of project-shared .mcp.json files in directories that are neither recent projects nor active workdirs
2026-05-25 20:28:00 +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)
82a47d051a fix: require explicit MCP project targets (#585)
Desktop MCP local and project scopes were still anchored to the active session workDir when adding or editing servers, which made a selected session worktree look like the target project. The form now treats the target project as explicit input for local/project scopes, and the server update path can remove the old scoped config before writing to the newly selected directory.

Constraint: Local MCP scope is private user config keyed by project path; project scope writes the selected project's .mcp.json
Rejected: Keep using the active session workDir as the implicit target | it can point at transient session worktrees and hides where config is written
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce implicit active-session cwd writes for MCP create/edit; local and project scopes need an explicit target project
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run check:desktop
Tested: bun run check:server
Tested: bun run quality:gate --mode baseline --allow-live --only 'provider-smoke:*' --provider-model deepseek:main:deepseek-main
Tested: Live filesystem MCP probes with DeepSeek deepseek-v4-pro across local/project scopes; see artifacts/quality-runs/mcp-live-split-2026-05-25T09-15-18-245Z/summary.json
Not-tested: Full bun run verify was not rerun after the live MCP smoke
Related: #585
2026-05-25 17:34:25 +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)
333271b159 fix: make portable plugin skills visible after CLI installs
Portable desktop installs can mutate CLAUDE_CONFIG_DIR from an embedded or external CLI while the desktop server is already running. The server skill surface now rereads plugin-related state before listing skills, and /reload-plugins refreshes plugin skills alongside plugin commands so terminal sessions see the same enabled skills without a restart.

Constraint: Portable mode expects CLI, desktop API, and terminal sessions to share the selected CLAUDE_CONFIG_DIR even when writes happen from separate processes
Rejected: Require a desktop restart after plugin install | leaves the portable install workflow stale and contradicts the existing /reload-plugins handoff
Confidence: high
Scope-risk: moderate
Directive: Keep skill discovery and reload summaries counting plugin skills separately from plugin slash commands
Tested: bun test src/server/__tests__/skills.test.ts
Tested: bun test src/server/__tests__/plugins.test.ts
Tested: bun test src/server/__tests__/sessions.test.ts
Tested: bun run check:server
Tested: CLAUDE_CONFIG_DIR=/tmp portable smoke with anthropics/skills, two cwd /api/skills checks, and DeepSeek skill slash-command calls
Tested: bun run quality:gate --mode baseline --allow-live --provider-model deepseek:main:deepseek-main --only provider-smoke:*
Not-tested: Packaged desktop UI visual smoke
2026-05-25 17:32:15 +08:00
程序员阿江(Relakkes)
3d2b219eda fix: align desktop MCP scopes with CLI
Desktop MCP settings were using the process startup directory for local
project config and treated new custom servers as user-global. The desktop
API now carries the active cwd into local MCP reads, writes, and enablement
state, and the settings page exposes the same local/project/user scopes that
the CLI supports.

Constraint: Claude Code MCP scope semantics are local, project, and user.
Constraint: Desktop sessions can switch projects inside one long-lived server process.
Rejected: Only clear getProjectPathForConfig cache | still leaves request-scoped desktop API calls tied to getOriginalCwd
Confidence: high
Scope-risk: moderate
Directive: Do not route MCP local config or disabledMcpServers through ambient original cwd in desktop APIs.
Tested: bun test src/server/__tests__/mcp.test.ts
Tested: cd desktop && bun run test -- mcpSettings.test.tsx
Tested: bun run check:server
Tested: bun run check:desktop
Not-tested: Live provider smoke; the change is config/control-channel scoped.
2026-05-25 16:09:31 +08:00
程序员阿江(Relakkes)
562523aea6 fix: keep portable desktop CLI installs in the selected config dir
Desktop portable mode depends on CLI-driven installs using the same filesystem root as the app. The installed terminal launcher now carries CLAUDE_CONFIG_DIR into macOS/Linux shell invocations and uses a Windows cmd wrapper so plugin, skill, and MCP operations do not fall back to the native home config. Moved portable bundles also rebase cached plugin install paths when the cache exists under the current config root.

Constraint: Desktop portable bundles are file-system based and must survive zip/unzip relocation.
Rejected: Copy the sidecar binary directly into user bin | it cannot inject the selected portable config directory for future CLI installs.
Rejected: Rewrite plugin install paths unconditionally | missing cache directories should remain visible as broken state instead of being silently retargeted.
Confidence: high
Scope-risk: moderate
Directive: Keep desktop launcher wrappers as env-carrying sh/cmd files; do not revert to raw sidecar copies without portable install verification.
Tested: bun test src/server/__tests__/desktop-cli-launcher.test.ts src/utils/plugins/installedPluginsManager.test.ts
Tested: cargo test portable --manifest-path desktop/src-tauri/Cargo.toml
Tested: bun run check:server
Tested: bun run check:native
Tested: macOS launcher fixture plus Computer Use Finder verification of portable marker output.
Not-tested: Windows physical desktop execution; Windows wrapper behavior is covered by unit test only.
2026-05-25 16:09:31 +08:00
程序员阿江(Relakkes)
49481123f5 fix: recover stale IM session bindings
IM adapters could keep in-process bridge state after desktop session deletion removed the persisted mapping or after the server no longer had the mapped session. The restore path now validates the stored mapping against bridge memory and the server session before reusing it, then clears stale transient state before creating a replacement session.

Constraint: Issue #574 is intermittent and tied to adapter-created sessions; live reporter Telegram credentials are not available locally.
Rejected: Keep separate recovery checks in each IM adapter | duplicates the same stale-state logic across Telegram, Feishu, WeChat, and DingTalk.
Confidence: high
Scope-risk: moderate
Directive: Do not use bridge.hasSession() as the sole restore check; verify adapter-sessions state and server session existence first.
Tested: bun run check:adapters (364 pass, 0 fail)
Tested: cd adapters && bunx tsc --noEmit
Tested: bun test src/server/__tests__/sessions.test.ts src/server/__tests__/websocket-handler.test.ts --timeout 30000 (110 pass, 0 fail)
Tested: git diff --check
Not-tested: Live Telegram bot/provider reproduction on the reporter machine
2026-05-25 16:09:31 +08:00
程序员阿江(Relakkes)
acedb2b5b5 fix: show dotfiles in file lists
File browsing and workspace trees were treating every dot-prefixed entry as hidden, which made project files such as .env.example impossible to select through the desktop file surfaces. The fix keeps normal dotfiles and project folders visible while still hiding VCS metadata directories such as .git.

Constraint: VCS internals should stay out of user-facing file trees even when dotfiles are visible
Rejected: Add a UI toggle for hidden files | issue asks for file lists to include dot-prefixed project entries by default
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce blanket dotfile filtering on filesystem browse, workspace tree, or path completion paths
Tested: bun test src/server/__tests__/filesystem.test.ts src/server/__tests__/workspace-service.test.ts src/utils/suggestions/directoryCompletion.test.ts
Tested: bun run check:server
Not-tested: desktop browser visual smoke
2026-05-25 16:09:31 +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
程序员阿江(Relakkes)
2a937c6cc7 fix(desktop): prevent reconnects from replaying completed replies
Sleep/wake reconnects can resend persisted assistant text as live output
without transcript ids. Treat that output as replay when it already exists
at the hydrated chat tail, and collapse duplicate transcript-backed text
after history refresh so the UI does not append repeated replies or send
new completion notifications for old turns.

Constraint: Keep the fix inside desktop chat state; do not change the WebSocket protocol or transcript storage shape.
Rejected: Add a macOS sleep/wake listener | it would not address replayed stream events from other reconnect/resume paths.
Confidence: high
Scope-risk: narrow
Directive: Do not remove replay dedupe without testing sleep/wake reconnect output against transcript id hydration.
Tested: bunx vitest run src/stores/chatStore.test.ts --testNamePattern "replay|collapses duplicate"
Tested: bun run test -- --run src/stores/chatStore.test.ts
Tested: bun run check:desktop
Not-tested: Real macOS sleep/wake desktop smoke with native notifications.
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
eec809acce
Merge pull request #582 from luoxk/fix/assistant-tool-use-text-interleave
fix: reorder assistant tool_use blocks before sending history
2026-05-24 01:58:38 +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
程序员阿江(Relakkes)
f62411ce24 fix: isolate H5 access test from ambient CLAUDE_H5_AUTO_PUBLIC_URL
The beforeEach hook saved but did not clear the env var, so tests
that expected publicBaseUrl=null got the auto-discovered LAN URL
instead when CLAUDE_H5_AUTO_PUBLIC_URL=1 was set in the environment.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 01:23:41 +08:00
程序员阿江(Relakkes)
63daa34be0 fix: heal stale H5 LAN host and validate saves against local interfaces
H5 access broke after switching Wi-Fi: the previously saved private-LAN host
(e.g. 192.168.1.207) was no longer bound to any interface on the current
network (which had moved to 192.168.0.x), but commit da8308de only refreshed
the stale *port* and kept the stale *hostname*. The QR code therefore pointed
at an IP this machine no longer had, and phones got TCP-refused.

Two changes:

1. resolveEffectiveH5PublicBaseUrl now accepts the set of local IPv4 hosts
   and, when the stored URL is a plain private-LAN HTTP URL whose hostname
   is no longer on any interface, falls back to the auto-discovered URL —
   without overwriting the stored value, so switching back to the original
   network restores it automatically.

2. H5AccessService.updateSettings now validates the publicBaseUrl on save.
   Plain LAN URLs whose host is not on any local interface are rejected with
   a 400 + suggested LAN IP. Reverse-proxy URLs (https, custom path, hostname
   targets) are accepted unchanged because reachability is owned by the
   user's tunnel / nginx / cloudflared setup, not the desktop.

GET /api/h5-access also returns a diagnostics block (storedHostStaleness,
suggestedHost, localInterfaceHosts, effectivePublicBaseUrl) so the desktop
Settings page can render a warning banner with a one-click switch to the
current LAN IP, plus a quieter note for proxy URLs.

Constraint: Reverse-proxy users may legitimately point H5 at a hostname that
is not on this machine's network adapters; we must not regress that path.

Rejected: Reachability-probe arbitrary public URLs from the server | the
desktop cannot reliably round-trip through the user's external tunnel and
should not pretend to validate it.

Rejected: Overwrite the stored host with the auto host | users on multi-
network laptops would lose their pinned WSL/Docker-aware choice on every
Wi-Fi switch.

Confidence: high

Scope-risk: moderate

Directive: Do not narrow validateH5PublicBaseUrl to reject reverse-proxy
URLs without restoring an explicit opt-in path for them.

Tested: bun test src/server/__tests__/h5-access-service.test.ts (21 pass)
Tested: bun test src/server/__tests__/h5-access-api.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/h5-access-policy.test.ts (57 pass)
Tested: bun run check:server (840 pass)
Tested: bun run lint && bun test --run in desktop/ (734 pass, 91 files, +3 new H5 banner / proxy-note / save-error cases)

Not-tested: Real phone scan against a packaged desktop build after a Wi-Fi switch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.3.0
2026-05-24 01:07:46 +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)
2a89331697 release: prepare v0.3.0 — release notes and version bump
Bump version from 0.2.9 to 0.3.0 across desktop/package.json,
tauri.conf.json, and Cargo.toml. Add v0.3.0 release notes covering
desktop long-session performance, streaming tool previews,
AskUserQuestion reliability, multi-client streaming, and H5 network
fixes.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-24 00:25:38 +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)
d1ed077f3e Merge H5 active port fix into local main
Bring the detached worktree fix back to local main so H5 QR and copied LAN links no longer keep stale sidecar ports after app restarts.

Constraint: Local main already had newer unrelated work after the detached worktree base.
Confidence: high
Scope-risk: narrow
Directive: Keep H5 LAN URL host preservation separate from reverse proxy URL preservation.
Tested: bun test src/server/__tests__/h5-access-service.test.ts
Tested: bun test src/server/__tests__/h5-access-api.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/h5-access-policy.test.ts
Tested: bun run check:server
Not-tested: Full desktop packaged build after merge.
2026-05-23 17:09:30 +08:00
程序员阿江(Relakkes)
248a3b4294 fix: keep H5 LAN links on the active server port
H5 access can preserve a manually selected private LAN host, but the desktop sidecar binds a fresh dynamic port on restart. Refresh plain HTTP private-LAN URLs to the current auto-discovered port while leaving reverse proxy URLs intact.

Constraint: Desktop sidecar ports are dynamic across app launches.
Rejected: Preserve the complete private-LAN URL unchanged | this keeps stale ports like 5179 in QR links after restart.
Confidence: high
Scope-risk: narrow
Directive: Do not treat plain private-LAN H5 URLs as reverse proxy URLs unless they carry a custom scheme or path.
Tested: bun test src/server/__tests__/h5-access-service.test.ts
Tested: bun test src/server/__tests__/h5-access-api.test.ts src/server/__tests__/h5-access-auth.test.ts src/server/__tests__/h5-access-policy.test.ts
Tested: bun run check:server
Tested: manual smoke with stored :5179 returning the active :45679 publicBaseUrl
Not-tested: Real phone scan against a packaged desktop build after reinstall.
2026-05-23 17:08:58 +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)
77552375e9 Merge Telegram stream subscription fix into local main
Bring the IM streaming repair from the ea41 worktree into local main so Telegram keeps receiving live content when the desktop opens the same active session. The merged fix also accumulates Telegram thinking deltas instead of replacing the preview with the latest short chunk.

Constraint: Local main already contains independent worktree label cleanup changes, so this merge preserves both histories
Rejected: Cherry-pick the fix | the user asked to merge the worktree back into local main
Confidence: high
Scope-risk: moderate
Directive: Keep multi-client session streaming covered before changing WebSocket output callback ownership
Tested: bun run check:server (828 pass); bun run check:adapters (358 pass); git diff --check
Not-tested: Live Telegram Bot API smoke against the packaged app
2026-05-23 16:20:01 +08:00
程序员阿江(Relakkes)
673523f3fd fix: keep IM session streams subscribed across desktop views
Telegram and desktop can attach to the same session while a turn is streaming. The WebSocket handler now tracks output callbacks per client and broadcasts session messages instead of replacing the previous subscriber. Telegram thinking deltas are accumulated before editing the placeholder so the preview does not collapse to the latest tiny chunk.

Constraint: Desktop and IM adapters may observe the same active session concurrently
Rejected: Keep a single session output callback | a later desktop view can steal Telegram's live content and completion events
Confidence: high
Scope-risk: moderate
Directive: Do not collapse session output callbacks back to one callback without a multi-client streaming regression
Tested: bun run check:server (828 pass); bun run check:adapters (358 pass); git diff --check
Not-tested: Live Telegram Bot API smoke against the packaged app
2026-05-23 16:19:39 +08:00