Skip to content

feat(windows): custom title bar (Variant A — top-right, single row)#291

Open
nothingness0db wants to merge 21 commits into
AkaraChen:devfrom
nothingness0db:feat/windows-custom-titlebar-singlerow
Open

feat(windows): custom title bar (Variant A — top-right, single row)#291
nothingness0db wants to merge 21 commits into
AkaraChen:devfrom
nothingness0db:feat/windows-custom-titlebar-singlerow

Conversation

@nothingness0db
Copy link
Copy Markdown
Contributor

@nothingness0db nothingness0db commented May 24, 2026

Summary

Windows only. macOS untouched.

One of two competing variants for the Windows custom title bar — the other is the macOS-style traffic lights at top-left.

This variant keeps the standard Windows convention: min / max / close at the top-right corner, drawn with the VscChrome* icons from react-icons/vsc (the same set VS Code uses). Buttons are 36×28 with the Win11 red close-hover (#c42b1c). ProjectTopBar stays a single row — the shrunk control size leaves enough room for the existing topbar action icons on the same line.

Screenshot

PixPin_2026-05-24_18-51-03

Test plan

  • Windows: black system title bar gone
  • Windows: min / max / close at top-right work; close turns red on hover
  • Windows: maximize icon swaps to restore when maximized
  • Windows: top areas of HomePage, SettingsPage, ProjectDetailPage stay draggable
  • Windows: topbar action icons + window controls coexist on one row without overlap
  • macOS: layout unchanged

Summary by CodeRabbit

  • New Features

    • Added shell detection system that identifies available shells and displays integration support status.
    • Added Windows window controls (minimize, maximize, close) with custom title bar styling.
    • Added multi-shell integration support for bash, zsh, fish, and PowerShell.
  • Improvements

    • Improved file-watch query caching and invalidation for better performance.
    • Enhanced shell picker to display integration compatibility information.
  • Localization

    • Added shell integration status text for supported languages.
  • Chores

    • Added database indexes for improved lookup performance.

Review Change Stack

Eira Hazel and others added 20 commits May 19, 2026 04:40
Wrap all std::process::Command spawns in a silent_command helper that
sets CREATE_NO_WINDOW on Windows, eliminating the brief black console
window flash when invoking git, gh, sh, powershell, and open from the
git, config, topbar, and updater modules.
Add copy-selection-or-interrupt and paste-clipboard keybindings for
Windows and Linux. Ctrl+C copies the current selection or, when no
selection exists, passes through to xterm so SIGINT (^C) still works.
Ctrl+V reads the clipboard and writes it to the PTY. Mac is unchanged
since Cmd+C/V is handled by the system.
Splitting `draft` and `isOpen` keeps the Dialog mounted while it closes so
Chakra's exit animation can release the pointer-events lock — previously
the page froze after creating a template. Also wires the global templates
trash button to a remove handler instead of opening the edit dialog.
Replace navigator.clipboard with tauri-plugin-clipboard-manager so
Ctrl+C/V hit the native OS clipboard API instead of WebView2's
JS shim, which is unreliable on Windows.
Extend shell init injection from zsh-only to bash, fish, and PowerShell
by vendoring VS Code's MIT-licensed shell integration scripts.

Shell init (shell_init.rs):
- Split monolithic default_init.sh into common (POSIX-sh) and zsh-only parts
- New ShellType enum with detect_shell_type() for automatic shell detection
- New ShellInjection enum describing per-shell injection strategy:
  - Bash: --init-file flag
  - Zsh: ZDOTDIR redirection (existing approach, now with .zshrc/.zprofile/.zlogin)
  - Fish: --init-command source
  - Pwsh: -noexit -command dot-source
  - Unknown: no injection (graceful fallback)
- prepare_shell_injection() dispatches to per-shell prepare functions
- All shells (including fish/pwsh) now receive project init scripts from 2code.json
- Delete legacy prepare_init_dir() (no remaining callers)

PTY layer (pty.rs):
- build_injected_command() replaces old command_builder(), constructs
  shell-specific args (e.g. --init-file for bash, -i for zsh)
- parse_shell_command() handles Windows paths with spaces in shell commands
  (e.g. "C:\Program Files\...\pwsh.exe -NoLogo -NoProfile")
- Set TERM_PROGRAM=vscode and VSCODE_INJECTION=1 env vars so vendored
  VS Code scripts activate correctly
- New tests for parse_shell_command()

Windows shell detection (handler/shell.rs):
- find_pwsh_path() probes standard install locations + PATH fallback
- Default shell prefers pwsh 7 over powershell 5.1
- load_windows_shells() now lists: pwsh, powershell, cmd, Git Bash, WSL

Vendor scripts (scripts/):
- shellIntegration-bash.sh, shellIntegration-rc.zsh, shellIntegration-env.zsh,
  shellIntegration-profile.zsh, shellIntegration-login.zsh (zsh),
  shellIntegration.fish, shellIntegration.ps1
- default_init_common.sh: POSIX-sh notify hook, claude wrapper, PATH setup
- default_init_zsh.sh: zle keybindings, unsetopt PROMPT_SP

Other:
- .gitignore: add tauri.conf.dev.json for local dev config override
…iles

Improves CodeRabbit docstring coverage from 37.5% to 90%+, passing the 80% threshold.
- bash: capture Python activation exit code before negation so failures report the real code
- zsh: unset VSCODE_INJECTION after injection block to prevent nested shells re-entering it
- fish: use set -g instead of set -l for env-var guard so it actually persists across prompts
- pty: preserve existing_args in Bash/Zsh injection branches (e.g. bash -l keeps -l)
- shell_init: handle executable paths with spaces in detect_shell_type (Windows Program Files paths)
- shell_init: map sh to Unknown instead of Bash to avoid --init-file on dash/BusyBox
- shell: use silent_command for where pwsh probe to suppress console flash on Windows
- Unify exe extraction: extract_exe() in shell_init shared by detect_shell_type and parse_shell_command, eliminating two diverging parsers
- Add supports_integration field to AvailableShell; cmd.exe and wsl.exe marked false
- Show "No shell integration" label in ShellPicker for unsupported shells
- Clean up 2code-init-* temp dirs on app exit
- Add tauri-plugin-clipboard-manager to CLAUDE.md plugin list
- Run cargo fmt
Fish and pwsh shells were missing the 2code common init (notify hook,
claude wrapper, PATH setup), causing the notify feature and claude
wrapper to be absent in those shells.

Fix: setup_2code_home() writes notify.sh, claude wrapper, and
claude-settings.json from Rust at session creation time (idempotent,
only writes if files don't exist). Fish/pwsh init scripts then just
set env vars and update PATH using shell-native syntax (fish_add_path,
$env:PATH), avoiding the need to reimplement bash heredoc logic.

Tests added for both fish and pwsh injection content.
handler/shell.rs is now a thin layer (9 lines) that delegates to
infra::shell_detect. AvailableShell struct and all detection logic
(find_pwsh_path, load_*_shells, push_shell) live in infra.
…g indexes

- Gate git-status and file-tree queries with isActive so hidden profiles
  stop polling every second while another profile is active
- Increase staleTime from 0 to 5s for git branch and git status queries
  to avoid redundant IPC calls on profile switch
- Scope file watcher query invalidation to the triggering project's
  profiles instead of invalidating all git queries globally
- Add missing FK indexes on profiles.project_id and
  pty_sessions.profile_id to eliminate full-table scans

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…split correctly

The previous fallback condition `if end_idx > parts.len()` could never be
true — the loop bumps end_idx up to parts.len() at most. When given a
shell command like `C:\Program Files\nu\nu --login` (path with spaces,
no .exe extension, followed by a flag), the dead fallback meant the flag
was swallowed into the returned exe string.

Track whether the loop matched an extension/exists() candidate with an
explicit found flag, and only run the flag-position fallback when the
loop exhausted without a match.

Adds 5 unit tests for extract_exe covering the regression case plus
simple basename, Unix path with args, Windows path with .exe, and the
existing no-extension/no-flag path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ndow

Use a Set instead of a single string so events from multiple projects
within the same 1s debounce window are all invalidated, not just the last.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- bash shellIntegration: use constant printf format for PS2 so `%` in
  the continuation prompt doesn't get parsed as a format specifier
- fish shellIntegration: hex-escape ASCII control bytes (0x01..0x1F)
  in __vsc_escape_value so multi-line commands and other control bytes
  don't break OSC 633 parsing (matches bash/zsh)
- pwsh shellIntegration: detect Windows via $PSVersionTable when
  $IsWindows isn't defined (Windows PowerShell 5.1)
- shell_detect: read $SHELL on macOS instead of hardcoding /bin/zsh
- shell_init: write claude.cmd on Windows so PowerShell can resolve
  the wrapper from PATH; fall back to USERPROFILE when HOME is unset;
  skip POSIX-flavoured project_init_scripts for fish/pwsh; include
  process PID in temp dir prefix
- lib.rs: scope shell-integration temp-dir cleanup to this process's
  PID prefix so concurrent app instances don't wipe each other's dirs
- ProjectTopBar: extract GitBranchLabelProps interface
- ShellPicker: render both "(default)" and "(no integration)" suffixes
  when both apply

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Apply rustfmt formatting across the codebase for consistent style
- Fix indentation in no_window.rs (spaces to tabs)
- Reorder imports in helper.rs for consistency
- Adjust line breaks in filesystem.rs, git.rs, project_group.rs, topbar.rs
- Extract GitBranchLabelProps interface in ProjectTopBar.tsx
- Update ShellPicker.tsx to show both '(default)' and '(no integration)' suffixes

These changes are purely cosmetic and do not affect functionality.
…ration

Resolved 13 conflicts (4 shell scripts, no_window/config/git/pty/shell_init
in infra, handler shell/topbar/updater, generated types.ts):

- Kept our `silent_command` helper name over upstream's
  `command_without_windows_console` (shorter, same semantics). Renamed the
  two test files (tests/common/mod.rs, tests/integration_git.rs) that
  upstream auto-merged in to use our name.
- Kept our shell integration scripts (more robust: PS5 compat,
  safe printf, fish `-g` scope fix).
- Kept our shell_init.rs/shell_detect.rs (more complete: fish/pwsh init
  scripts, supports_integration flag, multiple rounds of CodeRabbit review).
- Kept our pty.rs (preserves args for bash/zsh, reuses extract_exe).
- Took upstream's types.ts as base and added back AvailableShell.supports_integration.
- Auto-merged upstream's `#[cfg(target_os = "windows")]` on topbar.rs
  `windows_commands` field.

Fixed pre-existing test failures surfaced by the merge:
- queryKeys.test.ts: added `profile-notes` namespace (upstream-added).
- fileWatcher.test.ts: updated `onmessage` callers to pass an event with
  `project_id` and added `getQueryData` to the queryClient mock (broken
  since our 83ea03b perf commit).
The cast in loadWatcher said `() => void` but the merged production code
calls `event.project_id`, so passing an event arg failed tsc (caught by
the Linux smoke build).
Drop the opaque Windows system title bar so the app extends to the
top edge, mirroring the macOS overlay layout. Render compact Win11-style
min/max/close buttons (36×28, red close hover) in a fixed top-right
overlay. ProjectTopBar splits into two rows on Windows so its action
icons don't clash with the new controls. macOS layout untouched.
Swap the hand-rolled min/max/restore/close SVGs for the VscChrome*
icons from react-icons (the same set VS Code uses), so the controls
inherit a tested icon geometry instead of relying on hand-tuned paths.
Drop the two-row Windows variant and reuse the macOS single-row layout
on Windows too, only padding the right side by 118px so the smaller
window controls have room. Topbar action icons and window controls
share the row, relying on the shrunk control size to keep them visually
distinct.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 24, 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 58 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: f9aadc9b-3667-4162-853a-1eaf1722e46f

📥 Commits

Reviewing files that changed from the base of the PR and between 2abbe11 and 70a1a31.

📒 Files selected for processing (25)
  • .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
📝 Walkthrough

Walkthrough

This PR integrates VS Code terminal shell integration into 2code. It introduces platform-aware shell discovery, per-shell injection preparation with environment setup, refactors PTY session management, migrates subprocess invocations to a new silent_command abstraction, and updates frontend UI for custom window controls and platform-aware layout on Windows.

Changes

VS Code Shell Integration Feature

Layer / File(s) Summary
Platform detection utilities and command abstraction
src/shared/lib/platform.ts, src-tauri/crates/infra/src/no_window.rs
New platform.ts exports isMacPlatform, isWindowsPlatform, and isLinuxPlatform helpers using navigator.platform and navigator.userAgent. no_window.rs renames command_without_windows_console to silent_command(program) for clearer Windows console-hiding semantics.
Shell discovery and availability detection
src-tauri/crates/infra/src/shell_detect.rs, src-tauri/crates/infra/src/lib.rs, src-tauri/src/handler/shell.rs
New shell_detect module discovers available shells per OS (Unix reads /etc/shells and common paths; Windows checks PowerShell, Git Bash, WSL, cmd), computes OS-specific default shell commands, and returns AvailableShell entries with integration support flags. Re-exports in shell.rs replace previous local shell enumeration.
Shell initialization and injection preparation
src-tauri/crates/infra/src/shell_init.rs
extract_exe() parses shell command strings with spaced paths and flags; setup_2code_home() creates $HOME/.2code hook/wrapper files with Unix permissions and Windows claude.cmd; prepare_fish() and prepare_pwsh() skip project init (syntax incompatible), while bash/zsh include it; expanded test coverage validates extraction and injection per-shell.
PTY session management refactoring for shell injection
src-tauri/crates/infra/src/pty.rs
parse_shell_command now uses extract_exe() with early-return on empty input; build_injected_command forwards existing args before shell-specific init flags for bash/zsh; fish/pwsh format directives updated for consistency.
Config execution and core git helpers using silent_command
src-tauri/crates/infra/src/config.rs, src-tauri/crates/infra/src/git.rs
config.rs uses silent_command for platform-specific shell invocation in script_command(); git.rs migrates GitHub avatar lookup, core helpers (remote, init, branch, status), and diff/log/show/commit operations to silent_command; adds extensive documentation of git helper behavior.
Git integration tests and handler commands using silent_command
src-tauri/crates/infra/src/git.rs (tests), src-tauri/src/handler/topbar.rs, src-tauri/src/handler/updater.rs, src-tauri/tests/common/mod.rs, src-tauri/tests/integration_git.rs
Git integration test setup and topbar app launcher migrate to silent_command for Windows process management; updater adds beta release filtering; test helpers update git invocations consistently.
Frontend shell picker and window controls
src/features/settings/ShellPicker.tsx, src/layout/WindowControls.tsx, src/App.tsx
ShellPicker displays integration support and default markers; new WindowControls component provides Tauri window minimize/maximize/close buttons; App.tsx conditionally renders controls on Windows; platform detection consolidated in src/shared/lib/platform.ts.
Frontend layout and UX for custom title bar
src/features/git/ProjectTopBar.tsx, src/features/home/HomePage.tsx, src/features/settings/SettingsPage.tsx, src/features/projects/FileTreePanel.tsx, src/features/projects/ProfileLayout.tsx
ProjectTopBar adjusts padding on Windows; HomePage and SettingsPage add Tauri draggable window regions; Windows disables native decorations in Tauri setup; FileTreePanel accepts optional isActive prop for conditional branch-label fetching.
File watching and query cache optimization
src/features/watcher/fileWatcher.ts, src/features/watcher/fileWatcher.test.ts, src/features/projects/hooks.ts
File watcher tracks pending project IDs, reads cached projects, and invalidates only affected profiles' git/fs queries instead of all queries; hook staleTime increased to 5_000ms; test mocks updated for project-scoped event handling.
Shell integration scripts, docs, migrations, and cleanup
src-tauri/crates/infra/scripts/, src-tauri/migrations/, 给你/*.md, src/features/terminal/keybindings.ts, etc.
Adds VS Code shell integration scripts (bash/zsh/fish/pwsh) with environment variable setup; creates foreign-key indexes; documents shell integration porting and implementation; adds getTerminalShortcutSequence() helper and JSDoc comments; includes filesystem search refactoring and test formatting updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • AkaraChen/2code#59: Introduces getTerminalShortcutSequence helper in keybindings for terminal keyboard-shortcut escape sequences, directly mirroring the keybindings update in this PR.
  • AkaraChen/2code#275: Updates subprocess plumbing in infra/no_window.rs and wires silent_command through config/git helpers, overlapping with the widespread subprocess refactoring in this PR.
  • AkaraChen/2code#70: Introduces topbar app detection and launch features whose subprocess invocation is refactored to use silent_command in this PR's topbar.rs handler changes.

Poem

🐰 Shells detected on every shore,
Injections crafted, args galore.
Windows windows—no borders now,
Draggable regions—we show you how.
Queries precise, cache optimized bright,
VS Code integration takes flight! 🚀

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: implementing a custom title bar variant for Windows with window controls positioned at the top-right in a single row.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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

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: 8

Caution

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

⚠️ Outside diff range comments (2)
src-tauri/src/handler/updater.rs (1)

91-203: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extract updater orchestration from handler into the service layer.

latest_beta_endpoint, update-builder composition, and install orchestration are business logic in a handler file. Keep check_update/install_update as thin commands and delegate to service::updater.

As per coding guidelines: Implement #[tauri::command] handlers in handler/*.rs with thin delegation only—extract state, acquire lock, call service layer and Do NOT implement business logic in handlers—delegate to the service layer.

🤖 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/src/handler/updater.rs` around lines 91 - 203, The handler contains
business logic that must be moved into a service module: extract
latest_beta_endpoint, the update-builder composition (including gh_auth_token
usage and calls around app.updater_builder(), builder.header(...),
endpoints(...), build().check()), and the install orchestration that calls
download_and_install into service::updater functions; keep the #[tauri::command]
functions check_update and install_update thin—have them only extract
AppHandle/State<PendingUpdate>/Channel, acquire the lock, and call the new
service APIs (e.g., service::updater::check_update and
service::updater::install_update) while preserving error mapping via
updater_error and metadata mapping via update_metadata; ensure PendingUpdate
locking/unlocking, taking the Option, streaming events (for install_update) and
calling app.restart() (if you choose to keep restart in handler, only do so
after the service returns) are correctly handled between handler and service
boundaries.
src-tauri/src/handler/topbar.rs (1)

132-290: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Move topbar app resolution/launch business logic into the service layer.

list_supported_topbar_apps_* and open_topbar_app_* currently implement business logic inside the handler module. Keep this file to thin #[tauri::command] entrypoints and delegate to service::topbar::*.

Proposed direction
-#[cfg(target_os = "windows")]
-fn open_topbar_app_windows(app_id: &str, path: &str) -> Result<(), AppError> {
-    // resolve + launch logic here
-}
+// handler/topbar.rs
+#[tauri::command]
+pub async fn open_topbar_app(app_id: String, path: String) -> Result<(), AppError> {
+    super::run_blocking(move || service::topbar::open_topbar_app(&app_id, &path)).await
+}

+// service/topbar.rs
+pub fn open_topbar_app(app_id: &str, path: &str) -> Result<(), AppError> {
+    // platform-specific resolution + launch logic moved here
+}

As per coding guidelines: Handlers in src-tauri/src/handler files should follow the pattern ... and delegate business logic to the service layer and Do NOT implement business logic in handlers—delegate to the service layer.

🤖 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/src/handler/topbar.rs` around lines 132 - 290, This file contains
business logic in functions like list_supported_topbar_apps_macos,
list_supported_topbar_apps_windows, open_topbar_app_macos,
open_topbar_app_windows and helpers (resolve_app_path, windows_command_exists,
resolve_windows_command, known_app_spec, KNOWN_TOPBAR_APPS) — move that logic
into a new or existing service module service::topbar (e.g. implement
service::topbar::list_supported_topbar_apps and service::topbar::open_topbar_app
that encapsulate platform-specific resolution and launching using the same types
TopbarApp, TopbarAppSpec, etc.), then change the handler functions
list_supported_topbar_apps and open_topbar_app to be thin tauri entrypoints that
delegate to service::topbar (use tauri::async_runtime::spawn_blocking or
super::run_blocking as before), and remove the moved helper functions and
platform-specific implementations from this handler file.
🧹 Nitpick comments (4)
src-tauri/crates/infra/scripts/shellIntegration-bash.sh (1)

228-229: ⚡ Quick win

Consider using parameter expansion instead of sed for better performance.

The current implementation correctly pre-escapes the continuation prompt before passing it to printf. However, ShellCheck suggests using Bash's built-in parameter expansion ${PS2//pattern/replacement} instead of spawning a sed process for better performance.

⚡ Proposed optimization
-	__vsc_continuation_prompt="$(echo "$PS2" | sed 's/\x1b/\\\\x1b/g')"
+	__vsc_continuation_prompt="${PS2//$'\x1b'/\\\\x1b}"
🤖 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/scripts/shellIntegration-bash.sh` around lines 228 -
229, Replace the external sed call with Bash parameter expansion to avoid
spawning a process: assign __vsc_continuation_prompt by replacing the escape
character in PS2 (match using $'\e') with the literal sequence \x1b via
parameter expansion (e.g. "${PS2//<escape>/<replacement>}"), then pass
__vsc_continuation_prompt to the existing builtin printf
'\e]633;P;ContinuationPrompt=%s\a' call; update the assignment that sets
__vsc_continuation_prompt (and leave the printf line unchanged).
给你/MIGRATION-GUIDE.md (1)

11-19: ⚡ Quick win

Add language specifiers to fenced code blocks.

These code blocks should specify a language identifier for proper syntax highlighting and markdown compliance.

📝 Suggested fix

For lines 11-19:

-```
+```text
 src-tauri/crates/infra/scripts/shellIntegration-bash.sh     # bash 集成

For lines 22-26:

-```
+```text
 src-tauri/crates/infra/src/shell_init.rs  →  用 shell_init.rs.new 替换

Also applies to: 22-26

🤖 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 `@给你/MIGRATION-GUIDE.md` around lines 11 - 19, The fenced code blocks in
MIGRATION-GUIDE.md lack language specifiers; update each fence that lists script
filenames (e.g., shellIntegration-bash.sh, shellIntegration-rc.zsh,
shellIntegration-env.zsh, shellIntegration-profile.zsh,
shellIntegration-login.zsh, shellIntegration.fish, shellIntegration.ps1) to
include an appropriate language tag (for example bash, zsh, fish, pwsh or text)
and also add a language tag for the later block referencing src/shell_init.rs
(use rust or text), so each triple-backtick block becomes
```bash/```zsh/```fish/```pwsh/```rust (or ```text) to enable proper syntax
highlighting and markdown compliance.
src/features/watcher/fileWatcher.test.ts (1)

46-100: ⚡ Quick win

Add coverage for the cache-hit targeted invalidation path.

Current assertions validate debounce + fallback invalidation, but not the new project/profile-scoped invalidation branch when getQueryData(queryKeys.projects.all) returns data. Please add one test that seeds cached projects/profiles and asserts profile-specific query keys are invalidated (and broad namespace fallback is not used).

🤖 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 46 - 100, Add a new
test using loadWatcher that seeds the query cache so
getQueryData(queryKeys.projects.all) returns a non-empty projects/profiles
structure, then trigger the same debounced onmessage burst (use
vi.advanceTimersByTime like the other tests) and assert invalidateQueriesMock
was called for the profile-scoped query keys (e.g. queryKeys.profiles.list or
the profile-specific keys used in the code) and that the broad fallback
invalidations (the git-diff/git-diff-stats/git-status/git-log/fs-tree keys) were
NOT called; ensure you reuse the same helpers/mocks (invalidateQueriesMock,
loadWatcher, getQueryData stub) so the test hits the cache-hit targeted
invalidation branch.
给你/pty.rs.new (1)

131-137: ⚡ Quick win

Keep the reference PTY parser aligned with the real one.

This guidance file still uses split_whitespace(), so anyone copying it will reintroduce the Windows/path-with-spaces parsing bug into production code. Please mirror the current parser strategy from src-tauri/crates/infra/src/pty.rs here, or clearly mark this file as outdated.

🤖 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 `@给你/pty.rs.new` around lines 131 - 137, The build_injected_command function
currently uses shell.split_whitespace(), which reintroduces the
Windows/path-with-spaces parsing bug; replace that simple split with the same
robust parser used by the real PTY implementation (i.e., call or copy the shared
parsing routine used by the production pty code so quoted arguments and Windows
paths with spaces are handled correctly) instead of split_whitespace(), keeping
the rest of build_injected_command logic unchanged.
🤖 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 @.claude/settings.local.json:
- Around line 1-10: The file .claude/settings.local.json contains
developer-specific absolute paths and must be removed from version control;
update the repo by adding .claude/settings.local.json to .gitignore, remove the
tracked file from the index (so it stays locally but is no longer committed),
and make a commit that deletes it from the repository history; ensure any
sensitive or machine-specific values are not present in remaining committed
files.

In `@src-tauri/crates/infra/src/pty.rs`:
- Around line 136-147: parse_shell_command currently trims and then splits the
remainder with split_whitespace which breaks quoted executables and args (e.g.,
"\"C:\\Program Files\\PowerShell\\7\\pwsh.exe\" -NoLogo -NoProfile"); replace
the ad-hoc splitting with the same argv parser used by the shell-detection code
(the module that provides crate::shell_init::extract_exe) so parsing is
quote-aware, have parse_shell_command call that shared parser to produce the
program and Vec<String> args, and add a regression test that validates a quoted
Windows executable (including spaces) plus arguments launches/parses correctly.

In `@src-tauri/crates/infra/src/shell_init.rs`:
- Around line 125-129: The function setup_2code_home currently returns if the
HOME env var is unset; update it to resolve the user home on Windows as well by
falling back to a platform-safe home lookup (e.g., use dirs::home_dir() or
std::env::var("USERPROFILE") on Windows) when std::env::var("HOME") is missing
or empty; keep using PathBuf::from(...) for the resulting path and ensure
subsequent logic (in setup_2code_home) operates on that resolved PathBuf so
Explorer-launched processes on Windows can create .2code/bin/claude.cmd and the
hook/settings files.

In `@src/features/git/ProjectTopBar.tsx`:
- Around line 154-155: ProjectTopBar currently uses hardcoded file-tree strings;
replace those with Paraglide i18n message calls: import the component's messages
(from Paraglide) and change the aria-label and any tooltip/title usages that
reference isFileTreeOpen (and the second occurrence near the other file-tree
control) to call the appropriate message function conditionally (e.g.,
aria-label={isFileTreeOpen ? messages.closeFileTree() : messages.openFileTree()}
), updating both occurrences so labels/tooltips come from Paraglide messages
rather than hardcoded text.

In `@src/layout/WindowControls.tsx`:
- Around line 123-133: Replace the hardcoded English labels on the ControlButton
instances with localized messages: import the i18n bundle via `import * as m
from "`@/paraglide/messages.js`"` and use the appropriate message keys for
"Minimize", "Maximize"/"Restore" (conditional based on `isMaximized`) and
"Close" when setting the `label` prop on the ControlButton components (the ones
near `handleMinimize`, `handleToggleMaximize`, and `handleClose`); ensure you
pass the localized string to `label` so accessibility follows the app locale.

In `@给你/shell_init.rs.new`:
- Around line 80-82: The temp path currently interpolates raw session_id into
dir (let dir = std::env::temp_dir().join(format!("2code-init-{session_id}"));
and then calls std::fs::create_dir_all(&dir)?), which allows path
separators/traversal; sanitize session_id before use by validating or
normalizing it (e.g., reject or strip path separators and control chars, allow
only a safe charset like [A-Za-z0-9_-], or fallback to a hashed/UUID
representation of session_id) and use the sanitized value in the format call so
create_dir_all operates on a safe, non-traversing directory name.
- Line 41: The match arm currently mapping `"bash" | "sh" => ShellType::Bash`
must be changed so `sh` is not treated as Bash; update the match to map `"bash"
=> ShellType::Bash` and either add a distinct variant (e.g., `ShellType::Sh` or
`ShellType::Posix`) for `"sh"` or fall back to
`ShellType::Unknown`/`ShellType::Posix` instead, and then update any code that
builds startup arguments (places that check `ShellType::Bash` to add
`--init-file` or other bash-only flags) to only emit bash-specific flags when
the variant is exactly `ShellType::Bash`; locate the mapping and the
arg-construction logic by the enum/variant name `ShellType` and the match arm
`"bash" | "sh" => ShellType::Bash` and change them accordingly.
- Around line 29-37: The shell type detection uses
shell_cmd.split_whitespace().next() which fails for quoted or space-containing
executable paths and causes detect_shell_type to return Unknown (leading to
ShellInjection::None); replace that tokenization by reusing the extract_exe
logic used elsewhere (the extract_exe helper in infra's shell_init) to correctly
parse quoted paths and Windows paths with spaces, so update the code around
detect_shell_type/shell_cmd to call or inline extract_exe when computing
exe/basename and then continue using file_stem()/to_str()/to_lowercase() as
before.

---

Outside diff comments:
In `@src-tauri/src/handler/topbar.rs`:
- Around line 132-290: This file contains business logic in functions like
list_supported_topbar_apps_macos, list_supported_topbar_apps_windows,
open_topbar_app_macos, open_topbar_app_windows and helpers (resolve_app_path,
windows_command_exists, resolve_windows_command, known_app_spec,
KNOWN_TOPBAR_APPS) — move that logic into a new or existing service module
service::topbar (e.g. implement service::topbar::list_supported_topbar_apps and
service::topbar::open_topbar_app that encapsulate platform-specific resolution
and launching using the same types TopbarApp, TopbarAppSpec, etc.), then change
the handler functions list_supported_topbar_apps and open_topbar_app to be thin
tauri entrypoints that delegate to service::topbar (use
tauri::async_runtime::spawn_blocking or super::run_blocking as before), and
remove the moved helper functions and platform-specific implementations from
this handler file.

In `@src-tauri/src/handler/updater.rs`:
- Around line 91-203: The handler contains business logic that must be moved
into a service module: extract latest_beta_endpoint, the update-builder
composition (including gh_auth_token usage and calls around
app.updater_builder(), builder.header(...), endpoints(...), build().check()),
and the install orchestration that calls download_and_install into
service::updater functions; keep the #[tauri::command] functions check_update
and install_update thin—have them only extract
AppHandle/State<PendingUpdate>/Channel, acquire the lock, and call the new
service APIs (e.g., service::updater::check_update and
service::updater::install_update) while preserving error mapping via
updater_error and metadata mapping via update_metadata; ensure PendingUpdate
locking/unlocking, taking the Option, streaming events (for install_update) and
calling app.restart() (if you choose to keep restart in handler, only do so
after the service returns) are correctly handled between handler and service
boundaries.

---

Nitpick comments:
In `@src-tauri/crates/infra/scripts/shellIntegration-bash.sh`:
- Around line 228-229: Replace the external sed call with Bash parameter
expansion to avoid spawning a process: assign __vsc_continuation_prompt by
replacing the escape character in PS2 (match using $'\e') with the literal
sequence \x1b via parameter expansion (e.g. "${PS2//<escape>/<replacement>}"),
then pass __vsc_continuation_prompt to the existing builtin printf
'\e]633;P;ContinuationPrompt=%s\a' call; update the assignment that sets
__vsc_continuation_prompt (and leave the printf line unchanged).

In `@src/features/watcher/fileWatcher.test.ts`:
- Around line 46-100: Add a new test using loadWatcher that seeds the query
cache so getQueryData(queryKeys.projects.all) returns a non-empty
projects/profiles structure, then trigger the same debounced onmessage burst
(use vi.advanceTimersByTime like the other tests) and assert
invalidateQueriesMock was called for the profile-scoped query keys (e.g.
queryKeys.profiles.list or the profile-specific keys used in the code) and that
the broad fallback invalidations (the
git-diff/git-diff-stats/git-status/git-log/fs-tree keys) were NOT called; ensure
you reuse the same helpers/mocks (invalidateQueriesMock, loadWatcher,
getQueryData stub) so the test hits the cache-hit targeted invalidation branch.

In `@给你/MIGRATION-GUIDE.md`:
- Around line 11-19: The fenced code blocks in MIGRATION-GUIDE.md lack language
specifiers; update each fence that lists script filenames (e.g.,
shellIntegration-bash.sh, shellIntegration-rc.zsh, shellIntegration-env.zsh,
shellIntegration-profile.zsh, shellIntegration-login.zsh, shellIntegration.fish,
shellIntegration.ps1) to include an appropriate language tag (for example bash,
zsh, fish, pwsh or text) and also add a language tag for the later block
referencing src/shell_init.rs (use rust or text), so each triple-backtick block
becomes ```bash/```zsh/```fish/```pwsh/```rust (or ```text) to enable proper
syntax highlighting and markdown compliance.

In `@给你/pty.rs.new`:
- Around line 131-137: The build_injected_command function currently uses
shell.split_whitespace(), which reintroduces the Windows/path-with-spaces
parsing bug; replace that simple split with the same robust parser used by the
real PTY implementation (i.e., call or copy the shared parsing routine used by
the production pty code so quoted arguments and Windows paths with spaces are
handled correctly) instead of split_whitespace(), keeping the rest of
build_injected_command logic unchanged.
🪄 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: be22c883-ebf1-473b-b51f-6b34418d0ed5

📥 Commits

Reviewing files that changed from the base of the PR and between 84ff262 and 2abbe11.

⛔ Files ignored due to path filters (2)
  • .claude/scheduled_tasks.lock is excluded by !**/*.lock
  • src/generated/types.ts is excluded by !**/generated/**
📒 Files selected for processing (50)
  • .claude/settings.local.json
  • CLAUDE.md
  • messages/en.json
  • messages/zh.json
  • src-tauri/crates/infra/scripts/default_init_fish.fish
  • src-tauri/crates/infra/scripts/default_init_pwsh.ps1
  • src-tauri/crates/infra/scripts/shellIntegration-bash.sh
  • 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/filesystem.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/repo/src/project_group.rs
  • src-tauri/migrations/2026-05-21-000000_add_fk_indexes/down.sql
  • src-tauri/migrations/2026-05-21-000000_add_fk_indexes/up.sql
  • src-tauri/src/handler/shell.rs
  • src-tauri/src/handler/topbar.rs
  • src-tauri/src/handler/updater.rs
  • src-tauri/src/helper.rs
  • src-tauri/src/lib.rs
  • src-tauri/tests/common/mod.rs
  • src-tauri/tests/integration_git.rs
  • src/App.tsx
  • src/features/git/ProjectTopBar.tsx
  • src/features/home/HomePage.tsx
  • src/features/projects/FileTreePanel.tsx
  • src/features/projects/ProfileLayout.tsx
  • src/features/projects/components/ProjectTemplatesEditor.tsx
  • src/features/projects/hooks.ts
  • src/features/settings/GlobalTerminalTemplatesSettings.tsx
  • src/features/settings/SettingsPage.tsx
  • src/features/settings/ShellPicker.tsx
  • src/features/terminal/Terminal.tsx
  • src/features/terminal/keybindings.ts
  • src/features/watcher/fileWatcher.test.ts
  • src/features/watcher/fileWatcher.ts
  • src/layout/AppSidebar.tsx
  • src/layout/WindowControls.tsx
  • src/shared/lib/platform.ts
  • src/shared/lib/queryKeys.test.ts
  • 给你/MIGRATION-GUIDE.md
  • 给你/PATCH-service-pty.md
  • 给你/pty.rs.new
  • 给你/shell_init.rs.new

Comment thread .claude/settings.local.json Outdated
Comment on lines 136 to +147
fn parse_shell_command(shell: &str) -> (String, Vec<String>) {
let shell = shell.trim();
let parts: Vec<&str> = shell.split_whitespace().collect();
if parts.is_empty() {
if shell.is_empty() {
return (shell.to_string(), vec![]);
}
let first = parts[0];
let looks_like_path =
first.len() >= 2 && first.as_bytes()[1] == b':' // C: D: etc
|| first.contains('/') || first.contains('\\');

if !looks_like_path || parts.len() == 1 {
return (first.to_string(), parts[1..].iter().map(|s| s.to_string()).collect());
}

// Reconstruct the path by joining tokens until we hit one ending with a
// known extension or the first arg starting with '-'.
let mut end_idx = 1;
while end_idx < parts.len() {
let candidate = parts[..=end_idx].join(" ");
if candidate.to_lowercase().ends_with(".exe")
|| candidate.to_lowercase().ends_with(".bat")
|| candidate.to_lowercase().ends_with(".cmd")
|| candidate.to_lowercase().ends_with(".ps1")
|| candidate.to_lowercase().ends_with(".sh")
|| Path::new(&candidate).exists()
{
end_idx += 1;
break;
}
end_idx += 1;
if end_idx > 6 { break; }
}
if end_idx > parts.len() {
end_idx = parts.iter().position(|p| p.starts_with('-')).unwrap_or(parts.len());
if end_idx == 0 { end_idx = 1; }
}
let program = parts[..end_idx].join(" ");
let args = parts[end_idx..].iter().map(|s| s.to_string()).collect();
let program = crate::shell_init::extract_exe(shell);
let rest = shell[program.len()..].trim();
let args = if rest.is_empty() {
vec![]
} else {
rest.split_whitespace().map(|s| s.to_string()).collect()
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Make PTY shell parsing quote-aware.

This still breaks valid command strings that use quoting. For example, "C:\Program Files\PowerShell\7\pwsh.exe" -NoLogo -NoProfile leaves the executable wrapped in quotes, and any quoted arg with spaces is split apart by split_whitespace(). That means custom shell commands can fail to launch even though they are syntactically valid.

Please switch this to a real argv parser shared with the shell-detection path, and add a regression test for a quoted Windows executable.

🤖 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/pty.rs` around lines 136 - 147,
parse_shell_command currently trims and then splits the remainder with
split_whitespace which breaks quoted executables and args (e.g., "\"C:\\Program
Files\\PowerShell\\7\\pwsh.exe\" -NoLogo -NoProfile"); replace the ad-hoc
splitting with the same argv parser used by the shell-detection code (the module
that provides crate::shell_init::extract_exe) so parsing is quote-aware, have
parse_shell_command call that shared parser to produce the program and
Vec<String> args, and add a regression test that validates a quoted Windows
executable (including spaces) plus arguments launches/parses correctly.

Comment on lines +125 to +129
fn setup_2code_home() {
let home = match std::env::var("HOME") {
Ok(h) if !h.is_empty() => PathBuf::from(h),
_ => return,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve the home directory on Windows too.

setup_2code_home() bails out when HOME is unset, which is common for Explorer-launched Windows processes. In that case PowerShell sessions never create .2code/bin/claude.cmd or the hook/settings files, so the Windows-specific integration path silently degrades.

Suggested fix
-	let home = match std::env::var("HOME") {
-		Ok(h) if !h.is_empty() => PathBuf::from(h),
-		_ => return,
-	};
+	let home = std::env::var("HOME")
+		.ok()
+		.filter(|h| !h.is_empty())
+		.or_else(|| {
+			std::env::var("USERPROFILE")
+				.ok()
+				.filter(|h| !h.is_empty())
+		})
+		.map(PathBuf::from);
+	let home = match home {
+		Some(path) => path,
+		None => return,
+	};
🤖 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 125 - 129, The
function setup_2code_home currently returns if the HOME env var is unset; update
it to resolve the user home on Windows as well by falling back to a
platform-safe home lookup (e.g., use dirs::home_dir() or
std::env::var("USERPROFILE") on Windows) when std::env::var("HOME") is missing
or empty; keep using PathBuf::from(...) for the resulting path and ensure
subsequent logic (in setup_2code_home) operates on that resolved PathBuf so
Explorer-launched processes on Windows can create .2code/bin/claude.cmd and the
hook/settings files.

Comment on lines +154 to +155
aria-label={isFileTreeOpen ? "Close file tree" : "Open file tree"}
aria-pressed={isFileTreeOpen}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move file-tree labels/tooltips to Paraglide messages.

The file-tree open/close text is hardcoded, so it won’t localize consistently with the rest of this component.

As per coding guidelines src/features/**/*.tsx: "Use i18n via Paraglide.js ... and call message functions."

Also applies to: 190-190

🤖 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/git/ProjectTopBar.tsx` around lines 154 - 155, ProjectTopBar
currently uses hardcoded file-tree strings; replace those with Paraglide i18n
message calls: import the component's messages (from Paraglide) and change the
aria-label and any tooltip/title usages that reference isFileTreeOpen (and the
second occurrence near the other file-tree control) to call the appropriate
message function conditionally (e.g., aria-label={isFileTreeOpen ?
messages.closeFileTree() : messages.openFileTree()} ), updating both occurrences
so labels/tooltips come from Paraglide messages rather than hardcoded text.

Comment on lines +123 to +133
label="Minimize"
onClick={handleMinimize}
/>
<ControlButton
kind="maximize"
label={isMaximized ? "Restore" : "Maximize"}
onClick={handleToggleMaximize}
isMaximized={isMaximized}
/>
<ControlButton kind="close" label="Close" onClick={handleClose} />
</HStack>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize window control labels instead of hardcoded English.

The control label props are currently static English strings, so these accessibility labels won’t follow app locale.

Proposed fix
 import { Box, HStack } from "`@chakra-ui/react`";
 import { getCurrentWindow } from "`@tauri-apps/api/window`";
 import { useEffect, useState } from "react";
+import * as m from "`@/paraglide/messages.js`";
@@
 			<ControlButton
 				kind="minimize"
-				label="Minimize"
+				label={m.windowMinimize()}
 				onClick={handleMinimize}
 			/>
 			<ControlButton
 				kind="maximize"
-				label={isMaximized ? "Restore" : "Maximize"}
+				label={isMaximized ? m.windowRestore() : m.windowMaximize()}
 				onClick={handleToggleMaximize}
 				isMaximized={isMaximized}
 			/>
-			<ControlButton kind="close" label="Close" onClick={handleClose} />
+			<ControlButton kind="close" label={m.windowClose()} onClick={handleClose} />

As per coding guidelines src/**/*.{ts,tsx}: "Import i18n messages using import * as m from "@/paraglide/messages.js"."

🤖 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/WindowControls.tsx` around lines 123 - 133, Replace the hardcoded
English labels on the ControlButton instances with localized messages: import
the i18n bundle via `import * as m from "`@/paraglide/messages.js`"` and use the
appropriate message keys for "Minimize", "Maximize"/"Restore" (conditional based
on `isMaximized`) and "Close" when setting the `label` prop on the ControlButton
components (the ones near `handleMinimize`, `handleToggleMaximize`, and
`handleClose`); ensure you pass the localized string to `label` so accessibility
follows the app locale.

Comment thread 给你/shell_init.rs.new
Comment on lines +29 to +37
let exe = shell_cmd
.split_whitespace()
.next()
.unwrap_or(shell_cmd);
let basename = Path::new(exe)
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or(exe)
.to_lowercase();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo_root="$(pwd)"
file="给你/shell_init.rs.new"

# 1) Confirm file exists and show detect_shell_type + nearby code with line numbers
if [ ! -f "$file" ]; then
  echo "Missing file: $file"
  exit 1
fi

echo "== File: $file =="
wc -l "$file"
rg -n "detect_shell_type|prepare_shell_injection|session_id|split_whitespace\\(\\)|file_stem\\(" "$file" || true

echo
echo "== detect_shell_type context =="
rg -n "fn detect_shell_type" -n "$file" && true
start=$(rg -n "fn detect_shell_type" "$file" | head -n1 | cut -d: -f1)
# print ~80 lines around
sed -n "$((start-20)),$((start+120))p" "$file" | cat -n

echo
echo "== extract_exe / other exe parsing helpers =="
rg -n "extract_exe|exe\\b|basename\\b|file_stem\\(|trim\\(\\)|strip_prefix\\(" "$file" || true

echo
echo "== tests for quoted paths or windows-like commands =="
rg -n "test|quote|windows|path with|\\\\|\".*\\.exe\"|'\\''" "$file" || true

# 2) Find where detect_shell_type is called and what shell_cmd looks like
echo
echo "== Calls to detect_shell_type across repo =="
rg -n "detect_shell_type\\(" . || true

Repository: AkaraChen/2code

Length of output: 10392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "struct .*Config|config\\.shell|shell\\s*:\\s*String|shell\\s*:\\s*&str|shell\\s*\\(" src-tauri/crates -S || true

# inspect pty callsites around detect_shell_type
sed -n '240,330p' src-tauri/crates/service/src/pty.rs | cat -n

# inspect infra shell_init.rs detect_shell_type + prepare_shell_injection
sed -n '60,140p' src-tauri/crates/infra/src/shell_init.rs | cat -n
sed -n '340,460p' src-tauri/crates/infra/src/shell_init.rs | cat -n

# inspect prepare_shell_injection implementation there for session_id usage
rg -n "fn prepare_shell_injection|2code-init-\\{session_id\\}" src-tauri/crates/infra/src/shell_init.rs
sed -n '140,220p' src-tauri/crates/infra/src/shell_init.rs | cat -n

# find where session_id is created/passed into prepare_shell_injection
rg -n "prepare_shell_injection\\(|session_id\\s*:" -S src-tauri/crates/service/src/pty.rs src-tauri/crates -S || true

Repository: AkaraChen/2code

Length of output: 17914


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="给你/shell_init.rs.new"

echo "== Does this file contain extract_exe helper? =="
rg -n "fn extract_exe|extract_exe\\(" "$file" || true

echo
echo "== Tests in 给你/shell_init.rs.new around detect_shell_type =="
# print around the detect_shell_type tests
sed -n '140,230p' "$file" | cat -n

echo
echo "== Any mention of quoted paths / windows path with spaces in tests =="
rg -n "quote|windows|Program Files|\\\\\\\\|C:\\\\|powershell\\.exe -NoLogo|C:|\\\"|'" "$file" || true

echo
echo "== Is 给你/shell_init.rs.new referenced anywhere? =="
rg -n "shell_init\\.rs\\.new" . || true
rg -n "mod .*shell_init|shell_init::|include_str!\\(.*shell_init" . -S || true

Repository: AkaraChen/2code

Length of output: 5558


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file_new="给你/shell_init.rs.new"
file_cur="src-tauri/crates/infra/src/shell_init.rs"

echo "== extract_exe in current infra =="
rg -n "fn extract_exe" "$file_cur"
start=$(rg -n "fn extract_exe" "$file_cur" | head -n1 | cut -d: -f1)
sed -n "$((start-10)),$((start+120))p" "$file_cur" | cat -n

echo
echo "== detect_shell_type in current infra =="
rg -n "pub fn detect_shell_type" "$file_cur"
start2=$(rg -n "pub fn detect_shell_type" "$file_cur" | head -n1 | cut -d: -f1)
sed -n "$((start2-5)),$((start2+80))p" "$file_cur" | cat -n

echo
echo "== detect_shell_type in 给你/shell_init.rs.new =="
rg -n "pub fn detect_shell_type" "$file_new"
start3=$(rg -n "pub fn detect_shell_type" "$file_new" | head -n1 | cut -d: -f1)
sed -n "$((start3-5)),$((start3+60))p" "$file_new" | cat -n

Repository: AkaraChen/2code

Length of output: 10796


Fix shell type detection for quoted/space-containing executables

detect_shell_type in 给你/shell_init.rs.new takes the executable token via shell_cmd.split_whitespace().next(), which mis-parses quoted paths with spaces and can push the shell into Unknown, disabling the integration injection (ShellInjection::None). Reuse/port the existing extract_exe logic from src-tauri/crates/infra/src/shell_init.rs, which already covers Windows paths with spaces + args.

🤖 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 `@给你/shell_init.rs.new` around lines 29 - 37, The shell type detection uses
shell_cmd.split_whitespace().next() which fails for quoted or space-containing
executable paths and causes detect_shell_type to return Unknown (leading to
ShellInjection::None); replace that tokenization by reusing the extract_exe
logic used elsewhere (the extract_exe helper in infra's shell_init) to correctly
parse quoted paths and Windows paths with spaces, so update the code around
detect_shell_type/shell_cmd to call or inline extract_exe when computing
exe/basename and then continue using file_stem()/to_str()/to_lowercase() as
before.

Comment thread 给你/shell_init.rs.new

match basename.as_str() {
"zsh" => ShellType::Zsh,
"bash" | "sh" => ShellType::Bash,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not classify sh as Bash.

sh is frequently dash/ash rather than bash; forcing it into the bash path can produce invalid args like --init-file and break startup.

Suggested fix
-        "bash" | "sh" => ShellType::Bash,
+        "bash" => ShellType::Bash,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"bash" | "sh" => ShellType::Bash,
"bash" => ShellType::Bash,
🤖 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 `@给你/shell_init.rs.new` at line 41, The match arm currently mapping `"bash" |
"sh" => ShellType::Bash` must be changed so `sh` is not treated as Bash; update
the match to map `"bash" => ShellType::Bash` and either add a distinct variant
(e.g., `ShellType::Sh` or `ShellType::Posix`) for `"sh"` or fall back to
`ShellType::Unknown`/`ShellType::Posix` instead, and then update any code that
builds startup arguments (places that check `ShellType::Bash` to add
`--init-file` or other bash-only flags) to only emit bash-specific flags when
the variant is exactly `ShellType::Bash`; locate the mapping and the
arg-construction logic by the enum/variant name `ShellType` and the match arm
`"bash" | "sh" => ShellType::Bash` and change them accordingly.

Comment thread 给你/shell_init.rs.new
Comment on lines +80 to +82
let dir = std::env::temp_dir().join(format!("2code-init-{session_id}"));
std::fs::create_dir_all(&dir)?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Sanitize session_id before using it in filesystem paths.

Interpolating raw session_id into a temp path allows separator/traversal patterns and can target unintended directories.

Suggested fix
 pub fn prepare_shell_injection(
     session_id: &str,
     shell_type: ShellType,
     project_init_scripts: &[String],
 ) -> Result<ShellInjection, AppError> {
-    let dir = std::env::temp_dir().join(format!("2code-init-{session_id}"));
+    let safe_session_id: String = session_id
+        .chars()
+        .map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
+        .collect();
+    let dir = std::env::temp_dir().join(format!("2code-init-{safe_session_id}"));
     std::fs::create_dir_all(&dir)?;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let dir = std::env::temp_dir().join(format!("2code-init-{session_id}"));
std::fs::create_dir_all(&dir)?;
pub fn prepare_shell_injection(
session_id: &str,
shell_type: ShellType,
project_init_scripts: &[String],
) -> Result<ShellInjection, AppError> {
let safe_session_id: String = session_id
.chars()
.map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' })
.collect();
let dir = std::env::temp_dir().join(format!("2code-init-{safe_session_id}"));
std::fs::create_dir_all(&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 `@给你/shell_init.rs.new` around lines 80 - 82, The temp path currently
interpolates raw session_id into dir (let dir =
std::env::temp_dir().join(format!("2code-init-{session_id}")); and then calls
std::fs::create_dir_all(&dir)?), which allows path separators/traversal;
sanitize session_id before use by validating or normalizing it (e.g., reject or
strip path separators and control chars, allow only a safe charset like
[A-Za-z0-9_-], or fallback to a hashed/UUID representation of session_id) and
use the sanitized value in the format call so create_dir_all operates on a safe,
non-traversing directory name.

@netlify
Copy link
Copy Markdown

netlify Bot commented May 30, 2026

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

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

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