Skip to content

Windows 终端体验优化:友好名称、WSL 检测、shell 重构#294

Open
nothingness0db wants to merge 10 commits into
AkaraChen:devfrom
nothingness0db:perf/windows-optimization
Open

Windows 终端体验优化:友好名称、WSL 检测、shell 重构#294
nothingness0db wants to merge 10 commits into
AkaraChen:devfrom
nothingness0db:perf/windows-optimization

Conversation

@nothingness0db
Copy link
Copy Markdown
Contributor

@nothingness0db nothingness0db commented May 27, 2026

改了什么

shell 检测重构
handler/shell.rs 里的 shell 枚举逻辑搬到 infra/shell_detect.rs,handler 只剩 10 行委托代码。新增 supports_integration 字段,cmd.exe 和 WSL 标记为不支持集成,其余标记为支持。

友好名称
shell picker 和终端标签页显示「PowerShell 7」「Command Prompt」「Git Bash」,不再显示完整 exe 路径。标签页的 onTitleChange 回调改为从 shell 配置生成友好名称,忽略 shell 设的工作目录路径。

WSL 发行版检测
wsl -l -q 拿已安装的发行版名,每个发行版单独一条,显示为「Ubuntu (WSL)」。输出是 UTF-16LE,做了 BOM 检测和解码。

Windows 终端修复

  • extract_exe() 处理 Windows 带空格路径(C:\Program Files\PowerShell\7\pwsh.exe
  • setup_2code_home() 给 fish 和 pwsh 创建 notify hook
  • 新增 default_init_fish.fishdefault_init_pwsh.ps1,设置 2code 环境变量和 PATH
  • 启动命令去掉 \x1b[1;1R CPR 前缀,保留 1s sleep 做 PowerShell 同步

和上游的冲突

上游做了 tab 格式化、加了 codex 支持和 notes editor,和我们的改动在不同区域,没有实质冲突。rebase 可以直接合并。

测试

  • shell picker 显示友好名称
  • 终端标签页显示「PowerShell 7」而非路径
  • WSL 显示发行版名
  • 自定义 shell 功能正常
  • macOS / Linux 不受影响

Summary by CodeRabbit

  • New Features

    • Cross-platform shell detection and integration (many shells including Fish, PowerShell, Bash, Zsh, WSL).
    • Shell init scripts for Fish and PowerShell to enable session integrations.
    • Terminal tabs show friendly shell names.
  • Bug Fixes

    • Improved database reliability by tuning SQLite runtime settings to reduce busy failures.
  • Performance

    • Coalesced file-watcher events to reduce redundant work and narrowed query invalidations.
    • Adjusted git polling intervals (longer on Windows); reduced PTY startup delay, larger PTY read buffer, and temp-dir cleanup to avoid accumulation.
  • UI

    • Memoized sidebar components to reduce re-renders.
  • Tests

    • Expanded and updated unit tests around shell handling and utilities.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 27, 2026

Warning

Review limit reached

@nothingness0db, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 59 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6a1b46e-2e4f-4071-b8c3-4ed755f2bbbf

📥 Commits

Reviewing files that changed from the base of the PR and between 711fb53 and e46f34e.

📒 Files selected for processing (31)
  • .claude/commands/opsx/apply.md
  • .claude/commands/opsx/archive.md
  • .claude/commands/opsx/bulk-archive.md
  • .claude/commands/opsx/continue.md
  • .claude/commands/opsx/explore.md
  • .claude/commands/opsx/ff.md
  • .claude/commands/opsx/new.md
  • .claude/commands/opsx/onboard.md
  • .claude/commands/opsx/sync.md
  • .claude/commands/opsx/verify.md
  • .claude/settings.json
  • .claude/skills/dead-code-cleanup/SKILL.md
  • .claude/skills/openspec-apply-change/SKILL.md
  • .claude/skills/openspec-archive-change/SKILL.md
  • .claude/skills/openspec-bulk-archive-change/SKILL.md
  • .claude/skills/openspec-continue-change/SKILL.md
  • .claude/skills/openspec-explore/SKILL.md
  • .claude/skills/openspec-ff-change/SKILL.md
  • .claude/skills/openspec-new-change/SKILL.md
  • .claude/skills/openspec-onboard/SKILL.md
  • .claude/skills/openspec-sync-specs/SKILL.md
  • .claude/skills/openspec-verify-change/SKILL.md
  • .claude/skills/release-changelog.skill
  • .claude/skills/release-changelog/SKILL.md
  • .gitignore
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src-tauri/crates/infra/scripts/shellIntegration-bash.sh
  • src-tauri/crates/infra/src/pty.rs
  • src-tauri/crates/infra/src/shell_init.rs
  • src-tauri/src/handler/shell.rs
  • src/features/terminal/Terminal.tsx
📝 Walkthrough

Walkthrough

Implements cross-platform shell detection and per-shell init script generation, refactors PTY shell-injection and cleanup, updates SQLite pragmas, adds platform-aware Git polling and scoped file-watcher invalidation, and memoizes sidebar components.

Changes

Shell Infrastructure & Initialization

Layer / File(s) Summary
Shell enumeration and detection module
src-tauri/crates/infra/src/shell_detect.rs, src-tauri/crates/infra/src/lib.rs
Implements platform-specific shell enumeration, de-duplication, default marking, and exports AvailableShell + load_available_shells().
Shell command parsing and type detection
src-tauri/crates/infra/src/shell_init.rs
Adds extract_exe() and updates detect_shell_type() to classify shells by extracted executable basename; sh is treated as Unknown.
Home directory setup and shell init scripts
src-tauri/crates/infra/src/shell_init.rs, src-tauri/crates/infra/scripts/default_init_fish.fish, src-tauri/crates/infra/scripts/default_init_pwsh.ps1
Adds setup_2code_home() to create ~/.2code artifacts and embeds Fish/PowerShell init content; new Fish/Pwsh init scripts export _2CODE_* env vars and update PATH.
Shell-specific injection builders
src-tauri/crates/infra/src/shell_init.rs
Refactors into prepare_shell_injection() returning per-session temp dirs and shell-specific ShellInjection variants; Zsh/Bash include project init, Fish/Pwsh skip project init.
Silent process execution utility
src-tauri/crates/infra/src/no_window.rs
Adds command_without_windows_console() as a public alias to silent_command().
Shell init & detection tests
src-tauri/crates/infra/src/shell_init.rs
Expands unit tests for extract_exe, detect_shell_type, and fish/pwsh injection content and cleanup.
SQLite performance configuration
src-tauri/crates/infra/src/db.rs
Adds PRAGMA calls in init_db() (synchronous=NORMAL, busy_timeout=5000, larger cache, temp_store=MEMORY) with error logging.
PTY session creation and cleanup management
src-tauri/crates/service/src/pty.rs
Derives cleanup directory from ShellInjection and removes it when PTY reader exits; reduces Windows pre-injection delay and increases Windows PTY read buffer size.
Tauri shell handler and infra delegation
src-tauri/src/handler/shell.rs
Re-exports AvailableShell from infra and delegates list_available_shells() to infra::shell_detect::load_available_shells() via spawn_blocking.

Frontend Terminal UI & Performance Optimization

Layer / File(s) Summary
Terminal component shell integration
src/features/terminal/Terminal.tsx, src/features/terminal/TerminalTabs.tsx
Adds optional shell prop and friendlyShellName(); TerminalTabs passes defaultShell; title updates prefer friendly shell name when available.
Platform-aware Git query polling
src/features/git/hooks.ts, src/features/projects/hooks.ts
Introduces GIT_POLL_INTERVAL_MS (4s on Windows, 1s elsewhere) and updates Git-related hooks to use it for staleTime and refetchInterval.
File watcher scoped query invalidation
src/features/watcher/fileWatcher.ts, src/features/watcher/fileWatcher.test.ts
Accumulates pending project IDs and invalidates only affected profile git queries and file-tree queries using cached projects data; tests add/reset getQueryData mock.
Sidebar component memoization
src/layout/sidebar/ProfileItem.tsx, src/layout/sidebar/ProjectGroupSection.tsx, src/layout/sidebar/ProjectMenuItem.tsx
Wraps sidebar components with React.memo and updates imports/exports accordingly.
Test namespace tweak
src/shared/lib/queryKeys.test.ts
Adds "browser-apps" key to queryNamespaces expectations.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • AkaraChen/2code#275: Related terminal/PTY shell helper changes and console-suppression helpers.

"🐰
Fish and Pwsh scripts I bring,
Shells detected, bells will ring,
Poll slow on Windows, watchers sing,
Temp dirs cleaned — a tidy thing,
Memoized hops, hooray we spring!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.93% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title in Chinese translates to 'Windows Terminal Experience Optimization: Friendly Names, WSL Detection, Shell Refactoring', which accurately reflects the main objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nothingness0db nothingness0db changed the title feat(shell): friendly shell names, WSL detection, and Windows terminal fixes Windows 终端体验优化:友好名称、WSL 检测、shell 重构 May 27, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/layout/sidebar/ProjectMenuItem.tsx (1)

36-329: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix prefer-arrow-callback lint in ProjectMenuItem
memo(function ProjectMenuItem ...) is flagged; switch to arrow callback form (optionally add displayName).

Proposed fix
-export const ProjectMenuItem = memo(function ProjectMenuItem({
+export const ProjectMenuItem = memo(({
 	project,
 	projectGroups,
 }: {
 	project: ProjectWithProfiles;
 	projectGroups: ProjectGroup[];
-}) {
+}) => {
 	// ...
-});
+});
+
+ProjectMenuItem.displayName = "ProjectMenuItem";
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/layout/sidebar/ProjectMenuItem.tsx` around lines 36 - 329, Replace the
named function passed to memo with an arrow function to satisfy
prefer-arrow-callback: change memo(function ProjectMenuItem({ project,
projectGroups }: { project: ProjectWithProfiles; projectGroups: ProjectGroup[]
}) { ... }) to memo(({ project, projectGroups }: { project: ProjectWithProfiles;
projectGroups: ProjectGroup[] }) => { ... }); after the memo call assign
ProjectMenuItem.displayName = "ProjectMenuItem" so the component keeps a
readable name in devtools; keep the exported identifier export const
ProjectMenuItem as-is and preserve all existing inner logic and hooks.
🧹 Nitpick comments (1)
src/features/watcher/fileWatcher.test.ts (1)

32-33: ⚡ Quick win

Add a test for the new scoped invalidation branch.

Lines 32-33 force getQueryData to undefined, so the suite currently only validates fallback-wide invalidation. Please add one case where cached projects exist and an event includes project_id, then assert invalidation is scoped to affected profile/folder keys.

Proposed test addition
+import { queryKeys } from "`@/shared/lib/queryKeys`";
+
+it("invalidates only affected project/profile queries when projects cache is present", async () => {
+  const channel = await loadWatcher();
+  getQueryDataMock.mockReturnValue([
+    { id: "p1", folder: "/repo/a", profiles: [{ id: "prof-a" }] },
+    { id: "p2", folder: "/repo/b", profiles: [{ id: "prof-b" }] },
+  ]);
+
+  channel.onmessage?.({ project_id: "p2" } as never);
+  vi.advanceTimersByTime(1000);
+
+  expect(invalidateQueriesMock).toHaveBeenCalledWith({
+    queryKey: queryKeys.git.diff("prof-b"),
+  });
+  expect(invalidateQueriesMock).toHaveBeenCalledWith({
+    queryKey: queryKeys.git.diffStats("prof-b"),
+  });
+  expect(invalidateQueriesMock).toHaveBeenCalledWith({
+    queryKey: queryKeys.git.status("prof-b"),
+  });
+  expect(invalidateQueriesMock).toHaveBeenCalledWith({
+    queryKey: queryKeys.git.log("prof-b"),
+  });
+  expect(invalidateQueriesMock).toHaveBeenCalledWith({
+    queryKey: queryKeys.fs.tree("/repo/b"),
+  });
+});

Also applies to: 48-89

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/watcher/fileWatcher.test.ts` around lines 32 - 33, Add a test
case in fileWatcher.test.ts that covers the scoped invalidation branch: instead
of forcing getQueryDataMock to return undefined, have getQueryDataMock return a
cached projects array (include entries with project_id values) and craft an
event object that includes project_id; then call the same handler used elsewhere
in the suite (the test uses getQueryDataMock and invalidateQueriesMock) and
assert invalidateQueriesMock is invoked with the scoped keys for the affected
profile/folder (not the fallback-wide key). Reference getQueryDataMock and
invalidateQueriesMock (and the file watcher handler used in the suite) to locate
where to add the new case and mirror existing test structure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/infra/scripts/shellIntegration-bash.sh`:
- Around line 104-109: The current negated if (! builtin eval
"$VSCODE_PYTHON_BASH_ACTIVATE") records $__vsc_activation_status=$? after the
negation, so it captures the exit status of the if-test instead of the actual
builtin eval failure; change the control flow to run builtin eval non-negated
and capture its exit code in the else path (for example: if builtin eval
"$VSCODE_PYTHON_BASH_ACTIVATE"; then :; else __vsc_activation_status=$?; builtin
printf ... "$__vsc_activation_status"; fi), ensuring you reference
VSCODE_PYTHON_BASH_ACTIVATE, builtin eval and $__vsc_activation_status when
making the change.

In `@src-tauri/crates/infra/src/pty.rs`:
- Around line 131-174: parse_shell_command currently round-trips the input
through split_whitespace(), which breaks quoted args and spaces (e.g., quoted
program paths or distro names like "Ubuntu 24.04"); stop flattening and use a
real tokenizer or keep program+args structural. Replace the split_whitespace
logic in parse_shell_command with a proper shell-aware split (for example using
a crate like shell_words or shlex) to produce Vec<String> tokens that preserve
quotes/escapes, then select the first token as program and the rest as args (or
change callers to accept the Vec<String> directly); remove the manual
path-reconstruction logic that joins tokens and instead rely on the shell-aware
tokens so quoted paths such as "C:\Program Files\PowerShell\7\pwsh.exe" and
multi-word args survive intact.
- Around line 183-195: The Bash and Zsh branches currently drop the parsed
existing_args, causing user flags like `--login`/`-l` to be lost; update the
ShellInjection::Bash and ShellInjection::Zsh handling to include the
existing_args when constructing the CommandBuilder (created via
CommandBuilder::new(program)) instead of discarding them — e.g., append or
extend the CommandBuilder with existing_args before/after adding the
integration-specific flag (`--init-file` for Bash or `-i` for Zsh) so
user-supplied flags are preserved.

In `@src-tauri/crates/infra/src/shell_init.rs`:
- Around line 126-129: The current HOME lookup returns early if HOME is missing,
causing Windows runs to skip wrapper creation; update the logic that sets the
home PathBuf (the match on std::env::var("HOME") that produces `home` and
`PathBuf::from(h)`) to try Windows fallbacks: first try
std::env::var("USERPROFILE"), then if that is missing try concatenating
std::env::var("HOMEDRIVE") and std::env::var("HOMEPATH") (only use the parts
that exist), and only return early if none of these produce a non-empty path;
ensure the final value remains a PathBuf and preserve the existing early-return
behavior when all options are absent.
- Around line 155-162: The current code interpolates notify.to_string_lossy()
into a raw JSON string which breaks on Windows due to backslashes; instead build
the settings object and serialize it with serde_json (or serde_json::json!) so
the path is properly escaped: collect notify.to_string_lossy().to_string() into
a variable and use it as the "command" value inside a Rust data structure or
serde_json::Value for the hooks (refer to the local variables settings and
notify and the JSON structure being written), then call
serde_json::to_string_pretty (or to_string) and write that output to settings;
add the necessary serde_json import and handle serialization errors similarly to
the current write handling.

In `@src-tauri/src/handler/shell.rs`:
- Around line 5-9: The current call to
tauri::async_runtime::spawn_blocking(infra::shell_detect::load_available_shells).await.unwrap_or_default()
swallows join errors and returns an ambiguous empty list; change it to
explicitly handle the Result returned by await, log the join/error (use the
existing logger or e.g. processLogger) when the task fails or panics, and only
then return a sensible default; update the code around the spawn_blocking call
(the invocation of infra::shell_detect::load_available_shells and the
unwrap_or_default usage) to match this explicit error handling and logging so
failures are visible while preserving the fallback behavior.

In `@src/features/terminal/Terminal.tsx`:
- Around line 42-49: The friendlyShellName function currently maps any string
containing "wsl" to "WSL" and only matches "cmd.exe" for Command Prompt; update
friendlyShellName to (1) broaden cmd detection to match "cmd" with or without
".exe" (e.g., check lower.includes("cmd") or use /\bcmd(?:\.exe)?\b/) and (2)
preserve WSL distro labels by extracting a distro token when present (e.g.,
parse patterns like "wsl -d DistroName", "wsl.exe -d DistroName", or common
distro names appearing alongside "wsl") and return `WSL (DistroName)` instead of
collapsing to "WSL"; keep the existing PowerShell/PowerShell Core and Git Bash
checks and fall back to null if no friendly name can be determined.

---

Outside diff comments:
In `@src/layout/sidebar/ProjectMenuItem.tsx`:
- Around line 36-329: Replace the named function passed to memo with an arrow
function to satisfy prefer-arrow-callback: change memo(function
ProjectMenuItem({ project, projectGroups }: { project: ProjectWithProfiles;
projectGroups: ProjectGroup[] }) { ... }) to memo(({ project, projectGroups }: {
project: ProjectWithProfiles; projectGroups: ProjectGroup[] }) => { ... });
after the memo call assign ProjectMenuItem.displayName = "ProjectMenuItem" so
the component keeps a readable name in devtools; keep the exported identifier
export const ProjectMenuItem as-is and preserve all existing inner logic and
hooks.

---

Nitpick comments:
In `@src/features/watcher/fileWatcher.test.ts`:
- Around line 32-33: Add a test case in fileWatcher.test.ts that covers the
scoped invalidation branch: instead of forcing getQueryDataMock to return
undefined, have getQueryDataMock return a cached projects array (include entries
with project_id values) and craft an event object that includes project_id; then
call the same handler used elsewhere in the suite (the test uses
getQueryDataMock and invalidateQueriesMock) and assert invalidateQueriesMock is
invoked with the scoped keys for the affected profile/folder (not the
fallback-wide key). Reference getQueryDataMock and invalidateQueriesMock (and
the file watcher handler used in the suite) to locate where to add the new case
and mirror existing test structure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2808f7ad-eef8-467e-a70c-cf8ef2db1e73

📥 Commits

Reviewing files that changed from the base of the PR and between c634e9c and a95f30c.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (41)
  • package.json
  • src-tauri/.gitignore
  • src-tauri/Cargo.toml
  • src-tauri/capabilities/default.json
  • src-tauri/crates/infra/scripts/default_init_common.sh
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src-tauri/crates/infra/scripts/default_init_zsh.sh
  • src-tauri/crates/infra/scripts/shellIntegration-bash.sh
  • src-tauri/crates/infra/scripts/shellIntegration-env.zsh
  • src-tauri/crates/infra/scripts/shellIntegration-login.zsh
  • src-tauri/crates/infra/scripts/shellIntegration-profile.zsh
  • src-tauri/crates/infra/scripts/shellIntegration-rc.zsh
  • src-tauri/crates/infra/scripts/shellIntegration.fish
  • src-tauri/crates/infra/scripts/shellIntegration.ps1
  • src-tauri/crates/infra/src/config.rs
  • src-tauri/crates/infra/src/db.rs
  • src-tauri/crates/infra/src/git.rs
  • src-tauri/crates/infra/src/lib.rs
  • src-tauri/crates/infra/src/no_window.rs
  • src-tauri/crates/infra/src/pty.rs
  • src-tauri/crates/infra/src/shell_detect.rs
  • src-tauri/crates/infra/src/shell_init.rs
  • src-tauri/crates/service/src/pty.rs
  • src-tauri/src/handler/shell.rs
  • src-tauri/src/handler/topbar.rs
  • src-tauri/src/handler/updater.rs
  • src-tauri/src/lib.rs
  • src/features/git/hooks.ts
  • src/features/projects/components/ProjectTemplatesEditor.tsx
  • src/features/projects/hooks.ts
  • src/features/settings/GlobalTerminalTemplatesSettings.tsx
  • src/features/terminal/Terminal.tsx
  • src/features/terminal/TerminalTabs.tsx
  • src/features/terminal/keybindings.test.ts
  • src/features/terminal/keybindings.ts
  • src/features/watcher/fileWatcher.test.ts
  • src/features/watcher/fileWatcher.ts
  • src/layout/sidebar/ProfileItem.tsx
  • src/layout/sidebar/ProjectGroupSection.tsx
  • src/layout/sidebar/ProjectMenuItem.tsx
💤 Files with no reviewable changes (1)
  • src-tauri/crates/infra/scripts/default_init_common.sh

Comment thread src-tauri/crates/infra/scripts/shellIntegration-bash.sh
Comment thread src-tauri/crates/infra/src/pty.rs
Comment thread src-tauri/crates/infra/src/pty.rs
Comment thread src-tauri/crates/infra/src/shell_init.rs Outdated
Comment thread src-tauri/crates/infra/src/shell_init.rs Outdated
Comment thread src-tauri/src/handler/shell.rs Outdated
Comment thread src/features/terminal/Terminal.tsx
@nothingness0db nothingness0db force-pushed the perf/windows-optimization branch from a95f30c to ce7d71e Compare May 27, 2026 07:43
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/features/terminal/Terminal.tsx (1)

392-447: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add shell to terminalRef dependencies (or otherwise ensure onTitleChange sees the latest shell)

terminalRef is created via useCallback with dependencies [decreaseFontSize, handleTerminalLinkOpen, increaseFontSize, profileId, sessionId, syncTerminalLayout], but it reads shell to compute shellFriendlyName inside term.onTitleChange(...). With shell omitted, the title handler can keep using a stale friendly-name mapping if shell changes for an existing session.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/terminal/Terminal.tsx` around lines 392 - 447, The title-change
handler registered on term via term.onTitleChange uses the outer-scope variable
shell (and computes shellFriendlyName), but terminalRef's useCallback
dependencies do not include shell, causing stale values; update the useCallback
that defines terminalRef to include shell in its dependency array (or
alternatively read shell from a ref like shellRef.current inside the handler),
ensuring term.onTitleChange sees the latest shell value and calling
useTerminalStore.getState().updateTabTitle(profileId, sessionId,
shellFriendlyName) uses the current friendly name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/infra/scripts/default_init_pwsh.ps1`:
- Around line 10-11: Replace the substring check against $env:PATH with an
exact-path-element check: split $env:PATH using [System.IO.Path]::PathSeparator
into components, perform a case-insensitive equality comparison for each element
against $env:_2CODE_BIN, and only prepend
"$env:_2CODE_BIN$([System.IO.Path]::PathSeparator)$env:PATH" when no exact match
is found; update the conditional that currently uses -notlike
"*$env:_2CODE_BIN*" to use this element membership test so partial matches
inside other PATH entries won't block insertion.

In `@src/layout/sidebar/ProfileItem.tsx`:
- Line 13: The component is wrapped with memo using a function expression which
triggers the ESLint prefer-arrow-callback rule; change the memo call to use an
arrow function instead. Replace memo(function ProfileItem({ ... }) { ... }) with
memo((props) => { /* keep same body but use props or destructure inside */ })
and ensure the exported symbol remains ProfileItem (e.g., const ProfileItem =
memo((props) => { ... }); export { ProfileItem };), preserving all existing
props, types, and displayName if set.

In `@src/layout/sidebar/ProjectGroupSection.tsx`:
- Line 21: ESLint flags use of function expressions inside React.memo; replace
the anonymous function passed to memo with an arrow function to satisfy
prefer-arrow-callback. Find the memo(...) calls for ProjectGroupSection,
ProfileItem, and ProjectMenuItem and change memo(function Name({...}) { ... })
to memo((props) => { ... }) or memo(({ ... }) => { ... }) while preserving the
component name (export const ProjectGroupSection = memo((props) => { ... })
etc.), keeping the same props, return value, and any memoization logic.

In `@src/layout/sidebar/ProjectMenuItem.tsx`:
- Line 36: The component is currently wrapped as memo(function
ProjectMenuItem(...)) which is fine but the review points out an attempted
change to an arrow form was written with incorrect syntax; to fix, either keep
the existing named function style export const ProjectMenuItem = memo(function
ProjectMenuItem(props) { ... }) or convert to a correct arrow form by wrapping
the props destructure in an arrow: export const ProjectMenuItem = memo(({ ... })
=> { ... }); update the ProjectMenuItem declaration (and any other memo(...)
usages in this file) to use one of these valid syntaxes so the code compiles and
memo is applied correctly.

---

Outside diff comments:
In `@src/features/terminal/Terminal.tsx`:
- Around line 392-447: The title-change handler registered on term via
term.onTitleChange uses the outer-scope variable shell (and computes
shellFriendlyName), but terminalRef's useCallback dependencies do not include
shell, causing stale values; update the useCallback that defines terminalRef to
include shell in its dependency array (or alternatively read shell from a ref
like shellRef.current inside the handler), ensuring term.onTitleChange sees the
latest shell value and calling
useTerminalStore.getState().updateTabTitle(profileId, sessionId,
shellFriendlyName) uses the current friendly name.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 99010373-3f45-4751-907e-8d224e7312b6

📥 Commits

Reviewing files that changed from the base of the PR and between a95f30c and ce7d71e.

⛔ Files ignored due to path filters (5)
  • src/generated/.typecache is excluded by !**/generated/**
  • src/generated/commands.ts is excluded by !**/generated/**
  • src/generated/events.ts is excluded by !**/generated/**
  • src/generated/index.ts is excluded by !**/generated/**
  • src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (18)
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src-tauri/crates/infra/src/db.rs
  • src-tauri/crates/infra/src/lib.rs
  • src-tauri/crates/infra/src/no_window.rs
  • src-tauri/crates/infra/src/shell_detect.rs
  • src-tauri/crates/infra/src/shell_init.rs
  • src-tauri/crates/service/src/pty.rs
  • src-tauri/src/handler/shell.rs
  • src/features/git/hooks.ts
  • src/features/projects/hooks.ts
  • src/features/terminal/Terminal.tsx
  • src/features/terminal/TerminalTabs.tsx
  • src/features/watcher/fileWatcher.test.ts
  • src/features/watcher/fileWatcher.ts
  • src/layout/sidebar/ProfileItem.tsx
  • src/layout/sidebar/ProjectGroupSection.tsx
  • src/layout/sidebar/ProjectMenuItem.tsx
✅ Files skipped from review due to trivial changes (1)
  • src-tauri/crates/infra/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (10)
  • src/features/terminal/TerminalTabs.tsx
  • src/features/watcher/fileWatcher.test.ts
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src/features/watcher/fileWatcher.ts
  • src-tauri/crates/infra/src/db.rs
  • src-tauri/crates/infra/src/no_window.rs
  • src/features/projects/hooks.ts
  • src-tauri/crates/infra/src/shell_detect.rs
  • src/features/git/hooks.ts
  • src-tauri/crates/infra/src/shell_init.rs

Comment thread src-tauri/crates/infra/scripts/default_init_pwsh.ps1 Outdated
Comment thread src/layout/sidebar/ProfileItem.tsx Outdated
Comment thread src/layout/sidebar/ProjectGroupSection.tsx Outdated
Comment thread src/layout/sidebar/ProjectMenuItem.tsx Outdated
@nothingness0db nothingness0db force-pushed the perf/windows-optimization branch from ce7d71e to 3e4a0ce Compare May 27, 2026 10:11
- shell 检测逻辑从 handler/shell.rs 搬到 infra/shell_detect.rs
- extract_exe() 处理 Windows 带空格路径
- setup_2code_home() 为 fish/pwsh 创建 notify hook
- shell picker 和终端标签页显示友好名称
- WSL 发行版检测 (wsl -l -q)
- git 轮询间隔 Windows 上从 1s 调到 4s
- SQLite PRAGMA 优化
- sidebar 组件 React.memo
@nothingness0db nothingness0db force-pushed the perf/windows-optimization branch from 3e4a0ce to 2889114 Compare May 27, 2026 10:23
@nothingness0db
Copy link
Copy Markdown
Contributor Author

CodeRabbit Review 回复

已修复

  • shell_init.rs HOME fallbacksetup_2code_home() 现在依次尝试 HOMEUSERPROFILEHOMEDRIVE+HOMEPATH
  • shell_init.rs JSON 转义 — 用 serde_json::json! 替代字符串拼接,Windows 反斜杠路径不会破坏 JSON

不修

  • shellIntegration-bash.sh exit status — 这是 VS Code 官方 vendor 脚本,不动上游逻辑
  • pty.rs parse_shell_command 引号处理 — 当前实现覆盖了实际使用的场景(带空格路径),完整 shell-aware split 会过度设计
  • pty.rs Bash/Zsh existing_args — 集成脚本通过 --init-file/-i 注入,用户 flags 在 shell 配置里已经生效,不需要额外透传
  • handler/shell.rs unwrap_or_default — 空列表是合理的 fallback 行为,shell 检测失败不应 crash 应用
  • Terminal.tsx friendlyShellName — 当前匹配覆盖了主要用例,WSL 发行版名由 shell_detect 单独处理
  • ProjectMenuItem.tsx prefer-arrow-callback — 代码风格问题,不在本 PR 范围
  • fileWatcher.test.ts scoped invalidation — 建议合理但属于 nitpick,后续补

@netlify
Copy link
Copy Markdown

netlify Bot commented May 29, 2026

Deploy Preview for grand-selkie-225f87 failed. Why did it fail? →

Name Link
🔨 Latest commit e46f34e
🔍 Latest deploy log https://app.netlify.com/projects/grand-selkie-225f87/deploys/6a1a4b68aa7d9d000858331f

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/features/terminal/Terminal.tsx (1)

45-49: ⚡ Quick win

Friendly names are user-facing tab titles but not localized.

These strings render as tab titles, yet they bypass Paraglide. Brand tokens like "Git Bash"/"WSL" may stay verbatim, but route them through m.* so they participate in the i18n pipeline.

As per coding guidelines: "Use i18n via Paraglide.js with import * as m from '@/paraglide/messages.js' pattern for all user-facing strings."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/terminal/Terminal.tsx` around lines 45 - 49, The user-facing
hardcoded tab title strings in Terminal.tsx (the returns in the function that
maps shell names: the lines returning "PowerShell 7", "Windows PowerShell",
"Command Prompt", "WSL", "Git Bash") must be routed through Paraglide i18n;
import the messages module (import * as m from '`@/paraglide/messages.js`') and
replace those literal return values with message lookups (e.g.,
m.terminal.powerShell7, m.terminal.windowsPowerShell, m.terminal.commandPrompt,
m.terminal.wsl, m.terminal.gitBash or similarly named keys), adding the
corresponding keys to messages.js so these tab titles participate in the i18n
pipeline. Ensure you update any consumers/tests that expect the literal strings
if needed.
src-tauri/crates/infra/src/shell_init.rs (1)

493-534: ⚡ Quick win

Fish/Pwsh tests write into the developer's real $HOME/.2code.

prepare_fish/prepare_pwsh call setup_2code_home(), so these tests create ~/.2code/hooks/notify.sh, ~/.2code/hooks/claude-settings.json, and ~/.2code/bin/claude under the actual home directory of whoever runs the suite (dev or CI). The tests don't assert on those files, so the writes are a pure side effect that pollutes the environment. Consider extracting a setup_2code_home_in(base: &Path) seam so the public function resolves the home dir and the tests can point it at a temp dir.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src-tauri/crates/infra/src/shell_init.rs` around lines 493 - 534, The tests
call prepare_shell_injection (via prepare_fish/prepare_pwsh) which uses
setup_2code_home() and so mutates the real $HOME/.2code; add a seam by
extracting a new helper setup_2code_home_in(base: &Path) that performs the
actual directory/file creation, update setup_2code_home() to resolve the real
home and forward to setup_2code_home_in, and update the tests to create a
tempdir (e.g., via tempfile::tempdir()), call the new setup_2code_home_in or
pass that base into prepare_shell_injection (or provide an overload that accepts
a base path) so the test writes go into the tempdir instead of the real home,
then clean up the tempdir at test end.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src-tauri/crates/infra/src/shell_init.rs`:
- Around line 54-72: The loop in extract_exe incorrectly treats Path::exists()
as a signal the candidate is an executable, which returns true for directories
and truncates paths like "C:\Program Files (x86)\Git\bin\bash.exe"; update the
candidate existence check inside the while loop (the block that currently
branches on lower.ends_with(...) || Path::new(&candidate).exists()) to require a
regular file (use Path::new(&candidate).is_file()) instead of exists(), leaving
the other extension checks intact so the full executable path (e.g., bash.exe)
is still matched on the next iterations; ensure this change is applied where
extract_exe builds candidates so detect_shell_type downstream keeps correct
values.

---

Nitpick comments:
In `@src-tauri/crates/infra/src/shell_init.rs`:
- Around line 493-534: The tests call prepare_shell_injection (via
prepare_fish/prepare_pwsh) which uses setup_2code_home() and so mutates the real
$HOME/.2code; add a seam by extracting a new helper setup_2code_home_in(base:
&Path) that performs the actual directory/file creation, update
setup_2code_home() to resolve the real home and forward to setup_2code_home_in,
and update the tests to create a tempdir (e.g., via tempfile::tempdir()), call
the new setup_2code_home_in or pass that base into prepare_shell_injection (or
provide an overload that accepts a base path) so the test writes go into the
tempdir instead of the real home, then clean up the tempdir at test end.

In `@src/features/terminal/Terminal.tsx`:
- Around line 45-49: The user-facing hardcoded tab title strings in Terminal.tsx
(the returns in the function that maps shell names: the lines returning
"PowerShell 7", "Windows PowerShell", "Command Prompt", "WSL", "Git Bash") must
be routed through Paraglide i18n; import the messages module (import * as m from
'`@/paraglide/messages.js`') and replace those literal return values with message
lookups (e.g., m.terminal.powerShell7, m.terminal.windowsPowerShell,
m.terminal.commandPrompt, m.terminal.wsl, m.terminal.gitBash or similarly named
keys), adding the corresponding keys to messages.js so these tab titles
participate in the i18n pipeline. Ensure you update any consumers/tests that
expect the literal strings if needed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 402886bc-b052-487c-98df-10bf323c48f7

📥 Commits

Reviewing files that changed from the base of the PR and between 3e4a0ce and 711fb53.

⛔ Files ignored due to path filters (5)
  • src/generated/.typecache is excluded by !**/generated/**
  • src/generated/commands.ts is excluded by !**/generated/**
  • src/generated/events.ts is excluded by !**/generated/**
  • src/generated/index.ts is excluded by !**/generated/**
  • src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (19)
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src-tauri/crates/infra/src/db.rs
  • src-tauri/crates/infra/src/lib.rs
  • src-tauri/crates/infra/src/no_window.rs
  • src-tauri/crates/infra/src/shell_detect.rs
  • src-tauri/crates/infra/src/shell_init.rs
  • src-tauri/crates/service/src/pty.rs
  • src-tauri/src/handler/shell.rs
  • src/features/git/hooks.ts
  • src/features/projects/hooks.ts
  • src/features/terminal/Terminal.tsx
  • src/features/terminal/TerminalTabs.tsx
  • src/features/watcher/fileWatcher.test.ts
  • src/features/watcher/fileWatcher.ts
  • src/layout/sidebar/ProfileItem.tsx
  • src/layout/sidebar/ProjectGroupSection.tsx
  • src/layout/sidebar/ProjectMenuItem.tsx
  • src/shared/lib/queryKeys.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src-tauri/crates/infra/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (16)
  • src/shared/lib/queryKeys.test.ts
  • src-tauri/crates/infra/src/db.rs
  • src/features/watcher/fileWatcher.test.ts
  • src/layout/sidebar/ProjectGroupSection.tsx
  • src/features/projects/hooks.ts
  • src/features/terminal/TerminalTabs.tsx
  • src/layout/sidebar/ProfileItem.tsx
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src/layout/sidebar/ProjectMenuItem.tsx
  • src-tauri/crates/service/src/pty.rs
  • src-tauri/crates/infra/src/no_window.rs
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src/features/watcher/fileWatcher.ts
  • src-tauri/src/handler/shell.rs
  • src/features/git/hooks.ts
  • src-tauri/crates/infra/src/shell_detect.rs

Comment thread src-tauri/crates/infra/src/shell_init.rs
@nothingness0db
Copy link
Copy Markdown
Contributor Author

@coderabbitai 这两个 nitpick 有点虚:

  1. friendlyShellName i18n — "PowerShell"/"WSL"/"Git Bash" 都是品牌名,不属于该翻译的 user-facing copy。强行走 Paraglide 反而会鼓励翻译它们。

  2. setup_2code_home() 测试污染 — 函数本身是 idempotent 的(每个文件都先 !exists() 才写),实际只会在第一次测试时创建一次 ~/.2code/{hooks,bin,...},且这些文件就是 2code 自己的运行时产物。为了测试隔离而增加一个 _in(base) seam 收益不大。

Actionable 那个 extract_exePath::exists() 命中目录的 bug 已修 (d4dae16)。

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Tip

For best results, initiate chat on the files or code changes.

@nothingness0db 两点推理都站得住脚,接受:

  1. friendlyShellName i18n — 认同。"PowerShell"、"Git Bash"、"WSL" 是品牌名/专有名词,国际化框架不应介入;强行纳入 Paraglide 反而会给翻译者留下可以改动它们的错误印象。撤回该建议。

  2. setup_2code_home() 测试污染 — 认同。函数以 !exists() 为前提幂等写入,写入的也是应用自身的运行时目录,引入 _in(base) seam 的收益确实不及其带来的复杂度。撤回该建议。

extract_exe 命中目录的 bug 已在 d4dae16 修复,感谢确认。

🐇✨


✏️ Learnings added
Learnt from: nothingness0db
URL: https://github.com/AkaraChen/2code/pull/294

Timestamp: 2026-05-29T08:17:20.549Z
Learning: In the 2code project (src/features/terminal/Terminal.tsx), shell names like "PowerShell", "WSL", "Git Bash", and "Command Prompt" in `friendlyShellName()` are brand names / proper nouns and should NOT be wrapped in i18n (Paraglide). They are not translatable user-facing copy, and routing them through the i18n system would encourage incorrect translations.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


If you found this review helpful, would you consider giving us a shout-out on X?

Thank you for using CodeRabbit!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant