feat(windows): custom title bar (Variant A — top-right, single row)#291
feat(windows): custom title bar (Variant A — top-right, single row)#291nothingness0db wants to merge 21 commits into
Conversation
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.
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (25)
📝 WalkthroughWalkthroughThis 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 ChangesVS Code Shell Integration Feature
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
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 liftExtract updater orchestration from handler into the service layer.
latest_beta_endpoint, update-builder composition, and install orchestration are business logic in a handler file. Keepcheck_update/install_updateas thin commands and delegate toservice::updater.As per coding guidelines:
Implement #[tauri::command] handlers in handler/*.rs with thin delegation only—extract state, acquire lock, call service layerandDo 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 liftMove topbar app resolution/launch business logic into the service layer.
list_supported_topbar_apps_*andopen_topbar_app_*currently implement business logic inside the handler module. Keep this file to thin#[tauri::command]entrypoints and delegate toservice::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 layerandDo 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 winConsider using parameter expansion instead of
sedfor 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 asedprocess 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 winAdd 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 winAdd 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 winKeep 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 fromsrc-tauri/crates/infra/src/pty.rshere, 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
⛔ Files ignored due to path filters (2)
.claude/scheduled_tasks.lockis excluded by!**/*.locksrc/generated/types.tsis excluded by!**/generated/**
📒 Files selected for processing (50)
.claude/settings.local.jsonCLAUDE.mdmessages/en.jsonmessages/zh.jsonsrc-tauri/crates/infra/scripts/default_init_fish.fishsrc-tauri/crates/infra/scripts/default_init_pwsh.ps1src-tauri/crates/infra/scripts/shellIntegration-bash.shsrc-tauri/crates/infra/scripts/shellIntegration-rc.zshsrc-tauri/crates/infra/scripts/shellIntegration.fishsrc-tauri/crates/infra/scripts/shellIntegration.ps1src-tauri/crates/infra/src/config.rssrc-tauri/crates/infra/src/filesystem.rssrc-tauri/crates/infra/src/git.rssrc-tauri/crates/infra/src/lib.rssrc-tauri/crates/infra/src/no_window.rssrc-tauri/crates/infra/src/pty.rssrc-tauri/crates/infra/src/shell_detect.rssrc-tauri/crates/infra/src/shell_init.rssrc-tauri/crates/repo/src/project_group.rssrc-tauri/migrations/2026-05-21-000000_add_fk_indexes/down.sqlsrc-tauri/migrations/2026-05-21-000000_add_fk_indexes/up.sqlsrc-tauri/src/handler/shell.rssrc-tauri/src/handler/topbar.rssrc-tauri/src/handler/updater.rssrc-tauri/src/helper.rssrc-tauri/src/lib.rssrc-tauri/tests/common/mod.rssrc-tauri/tests/integration_git.rssrc/App.tsxsrc/features/git/ProjectTopBar.tsxsrc/features/home/HomePage.tsxsrc/features/projects/FileTreePanel.tsxsrc/features/projects/ProfileLayout.tsxsrc/features/projects/components/ProjectTemplatesEditor.tsxsrc/features/projects/hooks.tssrc/features/settings/GlobalTerminalTemplatesSettings.tsxsrc/features/settings/SettingsPage.tsxsrc/features/settings/ShellPicker.tsxsrc/features/terminal/Terminal.tsxsrc/features/terminal/keybindings.tssrc/features/watcher/fileWatcher.test.tssrc/features/watcher/fileWatcher.tssrc/layout/AppSidebar.tsxsrc/layout/WindowControls.tsxsrc/shared/lib/platform.tssrc/shared/lib/queryKeys.test.ts给你/MIGRATION-GUIDE.md给你/PATCH-service-pty.md给你/pty.rs.new给你/shell_init.rs.new
| 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() | ||
| }; |
There was a problem hiding this comment.
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.
| fn setup_2code_home() { | ||
| let home = match std::env::var("HOME") { | ||
| Ok(h) if !h.is_empty() => PathBuf::from(h), | ||
| _ => return, | ||
| }; |
There was a problem hiding this comment.
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.
| aria-label={isFileTreeOpen ? "Close file tree" : "Open file tree"} | ||
| aria-pressed={isFileTreeOpen} |
There was a problem hiding this comment.
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.
| label="Minimize" | ||
| onClick={handleMinimize} | ||
| /> | ||
| <ControlButton | ||
| kind="maximize" | ||
| label={isMaximized ? "Restore" : "Maximize"} | ||
| onClick={handleToggleMaximize} | ||
| isMaximized={isMaximized} | ||
| /> | ||
| <ControlButton kind="close" label="Close" onClick={handleClose} /> | ||
| </HStack> |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
🧩 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\\(" . || trueRepository: 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 || trueRepository: 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 || trueRepository: 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 -nRepository: 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.
|
|
||
| match basename.as_str() { | ||
| "zsh" => ShellType::Zsh, | ||
| "bash" | "sh" => ShellType::Bash, |
There was a problem hiding this comment.
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.
| "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.
| let dir = std::env::temp_dir().join(format!("2code-init-{session_id}")); | ||
| std::fs::create_dir_all(&dir)?; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
❌ Deploy Preview for grand-selkie-225f87 failed. Why did it fail? →
|
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 fromreact-icons/vsc(the same set VS Code uses). Buttons are 36×28 with the Win11 red close-hover (#c42b1c).ProjectTopBarstays a single row — the shrunk control size leaves enough room for the existing topbar action icons on the same line.Screenshot
Test plan
HomePage,SettingsPage,ProjectDetailPagestay draggableSummary by CodeRabbit
New Features
Improvements
Localization
Chores