Skip to content

Switch-Hook Lifecycle, Persistent Terminal Sessions and Worktree Creation UX#23

Merged
liam-russell merged 3 commits into
mainfrom
large-repo-updates
May 12, 2026
Merged

Switch-Hook Lifecycle, Persistent Terminal Sessions and Worktree Creation UX#23
liam-russell merged 3 commits into
mainfrom
large-repo-updates

Conversation

@marktimmins

Copy link
Copy Markdown
Collaborator

Switch-Hook Lifecycle, Persistent Terminal Sessions, and Worktree Creation UX

This PR introduces three related improvements: richer worktree-switch hook behaviour, persistent terminal sessions that survive tab switches, and a more polished worktree creation flow.

Switch Hook Enhancements

Switch hooks (before_worktree_switch / after_worktree_switch) gain two new options:

  • Run once per session (switchOncePerSession): when enabled, the hook fires the first time you switch to a given worktree in an app session and is skipped on subsequent switches. A global in-memory set tracks which (workspace, trigger, hook, worktree) keys have already fired; the key is cleared when the worktree is deleted.
  • Auto-run contexts (switchRunOnCreate / switchRunOnDelete): controls whether the hook fires automatically as part of worktree creation or deletion flows, independently of the once-per-session gate.

Switch hooks are also now executed at workspace load for the initially-active worktree (using the 'load' source, which always bypasses the once-per-session guard), and after worktree creation (using 'create', which respects switchRunOnCreate).

Two new workspace DB migrations (006, 007) add the backing columns with safe defaults so existing hook definitions continue working unchanged.

Persistent Terminal Sessions

TerminalContainer now writes its session list, active session ID, and layout to a per-CWD cache (terminal-session-cache.svelte.ts) and restores it on mount. Sessions are no longer torn down when you switch worktrees — switching back to a worktree resumes exactly where you left it. A new workspace-terminals.svelte.ts module manages global per-workspace terminal state (active tab, active terminal path, initialized paths, pending launch requests) so that state is consistent across page-level re-renders.

The layout also auto-promotes to grid when two or more hook sessions are active in the same container.

Hook environment variables are now passed as a structured envVars map in the hook-terminal-launch event (rather than inlined as shell assignments inside the command string), which decouples env injection from command construction and prevents quoting issues across shells.

Worktree Creation UX

  • A placeholder spinner row appears in the worktree list while creation is in progress, so the loading indicator is scoped to the new entry rather than the currently active worktree.
  • The Changes view and file watcher are suspended for the new path during creation to avoid git status / index.lock races on Windows.
  • If a hook terminal opens during creation, the terminal tab is automatically focused after the operation completes.
  • The create modal now auto-focuses the branch name input on open (via tick()).
  • "Reveal in Finder / Explorer" is updated to use revealItemInDir instead of openPath.
  • open_in_editor returns a human-readable editor name instead of the raw command string.

Windows Robustness

delete_managed_worktree now falls back to std::fs::remove_dir_all on Windows when git worktree remove --force fails with "Permission denied", then lets git worktree prune clean up the leftover metadata. This handles the case where a file watcher or shell process is still holding a handle to the directory.

closeAllTerminals is replaced with closeTerminalsForPath so teardown is scoped to a specific worktree path rather than killing all sessions globally.


Release Notes

This release adds smarter worktree-switch hook controls, persistent terminal sessions, and a smoother worktree creation experience.

  • Switch hooks can now be set to run only on the first switch to a worktree per session (Run once per session), avoiding repeated setup scripts when toggling between worktrees
  • Switch hooks now support per-context auto-run rules: choose whether they fire automatically on worktree create, delete, or both
  • Switch hooks run automatically at startup for the active worktree and after a new worktree is created
  • Terminal sessions now persist when switching between worktrees — returning to a worktree resumes your previous shell exactly where you left it
  • A placeholder row appears in the worktree list while a new worktree is being created so the spinner is scoped to the new entry
  • The worktree create modal now auto-focuses the branch name field on open
  • "Reveal in Finder / Explorer" now highlights the folder itself rather than opening it
  • On Windows, worktree deletion now recovers gracefully when a file handle briefly blocks git worktree remove

@marktimmins marktimmins changed the title Made changes to UI when worktrees are created and fixed some bugs in … Switch-Hook Lifecycle, Persistent Terminal Sessions and Worktree Creation UX May 12, 2026
@marktimmins

Copy link
Copy Markdown
Collaborator Author

@copilot please address & fix the failing check on this PR

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends SproutGit’s hook and terminal infrastructure to support richer worktree-switch hook behavior (once-per-session + per-context auto-run), passes hook environment variables as structured data, and improves worktree creation UX while making terminal sessions more persistent across UI state changes.

Changes:

  • Add switch-hook options (switchOncePerSession, switchRunOnCreate, switchRunOnDelete) with new workspace DB migrations and a new backend command to run switch hooks with source context.
  • Introduce terminal session persistence primitives (frontend cache + PTY reattach support) and pass envVars through the hook-terminal-launch event into terminal spawning.
  • Refine workspace UI flows: creation placeholder row/spinner, watcher + Changes view suspension to avoid Windows races, modal autofocus, and updated reveal behavior; update E2E selectors accordingly.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/routes/workspace/+page.svelte Worktree creation placeholder UX, watcher/Changes gating during create, switch-hook execution on load/create/delete, and terminal lifecycle changes.
src/routes/+layout.svelte Applies a global “autocomplete=off” policy to form/text-like inputs via focus + DOM mutation observation.
src/lib/workspace-terminals.svelte.ts Adds a global per-workspace terminal snapshot store (active tab/path, initialized paths, launch requests).
src/lib/terminal-session-cache.svelte.ts Adds an in-memory cache for TerminalContainer sessions/layout keyed by CWD.
src/lib/sproutgit.ts Extends hook/terminal types to include switch-hook flags and hook terminal envVars; adds runWorktreeSwitchHooks and closeTerminalsForPath.
src/lib/components/WorkspaceHooksModal.svelte UI and form normalization for new switch-hook flags; displays badges for these settings.
src/lib/components/ToastContainer.svelte Moves toast stack to bottom-left.
src/app.css Adjusts toast slide animations to match new toast position/direction.
src/lib/components/TerminalPanel.svelte Adds env var support, PTY reattach, optional “preserve on unmount”, and retry for initial command send.
src/lib/components/TerminalContainer.svelte Persists/restores session state, tracks PTY ids, improves add-session UX, and supports env vars per session.
src/lib/components/TerminalDock.svelte New global dock concept for rendering terminal containers across workspaces (not yet wired in).
src-tauri/src/terminal.rs Adds validated env var injection to terminal spawn; tracks initial CWD per PTY; adds close_terminals_for_path.
src-tauri/src/lib.rs Registers new Tauri commands (run_worktree_switch_hooks, close_terminals_for_path).
src-tauri/src/hooks.rs Adds switch-hook flags, once-per-session gating, source-aware filtering, structured env_vars in terminal launch events, and run_worktree_switch_hooks.
src-tauri/src/git/operations.rs Improves Windows worktree delete robustness with fs fallback and clears once-per-session switch-hook keys on delete.
src-tauri/src/editor.rs Returns a friendly editor display name instead of the raw command string.
src-tauri/src/db.rs Registers workspace migrations 006/007 for new hook columns.
src-tauri/migrations/workspace/006_hook_switch_once_per_session.sql Adds switch_once_per_session column with safe default.
src-tauri/migrations/workspace/007_hook_switch_auto_run_contexts.sql Adds switch_run_on_create / switch_run_on_delete columns with safe defaults.
e2e/specs/daily-workflow.spec.ts Updates assertions/selectors to match new terminal tab labeling and removal of the operation header.

Comment thread src/routes/workspace/+page.svelte
Comment thread src/routes/workspace/+page.svelte Outdated
Comment thread src/routes/workspace/+page.svelte
Comment thread src/lib/components/TerminalContainer.svelte
Comment thread src/lib/terminal-session-cache.svelte.ts
Comment thread src/lib/components/TerminalDock.svelte
Comment thread src-tauri/src/terminal.rs
Comment thread src-tauri/src/hooks.rs

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor

@copilot please address & fix the failing check on this PR

Addressed in commit 3d9e648: fixed the failing Rust CI check by removing the Clippy let_and_return lint in src-tauri/src/hooks.rs with no behavior change. No UI changes were made in this fix, so there is no screenshot to provide.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https (dns block)
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libaddr2line-3367f26bd486b29d.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libgimli-7aeefe72516b1ebd.rlib e230aede/build-script-build /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/librustc_demangle-789fb9c0cb1c7158.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libstd_detect-b16e5cb5eba3e0fd.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libhashbrown-70305b2ec0766fa6.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/librustc_std_workspace_alloc-76b5fe9328c1063f.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_/home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libminiz_oxide-2b6a8d2f6e1dc71b.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-u�� /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libminiz_oxide-2b6a8d2f6e1dc71b.rlib /home/REDACTED/.rustup/toolchains/stable-x86_64-REDACTED-linux-gnu/lib/rustlib/x86_64-REDACTED-linux-gnu/lib/libadler2-39ffdbc27c978ccc.rlib 64-u�� 64-REDACTED-linux-gnu/lib/libminiz_oxide-2b6a8d2f6e1dc71b.rlib 64-REDACTED-linux-gnu/lib/libadler2-39ffdbc27c978ccc.rlib -y --no-install/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/deps/zerovec_derive/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/build/thiserror-5450ec67f6b19510/rustc7RCeOV/symbols.o credential.helpecc 8aa98-cgu.0.rcgu.o 8aa98-cgu.1.rcgu.o .o (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@marktimmins

Copy link
Copy Markdown
Collaborator Author

@copilot apply changes based on the comments AND RESPONSES in this thread

Copilot AI commented May 12, 2026

Copy link
Copy Markdown
Contributor

@copilot apply changes based on the comments AND RESPONSES in this thread

Implemented in b43d988. I applied the actionable review items and incorporated your thread responses: terminal persistence across project/workspace switches remains unchanged (not treated as a bug), while I fixed the switch/delete initiating-path bug, path-equality issues in terminal path tracking/dock/cache, backend terminal path normalization for close-by-path, and added focused switch-hook helper tests. No visible UI change from these fixes, so screenshot is not applicable.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • dl.google.com
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https (dns block)
    • Triggering command: /usr/lib/apt/methods/https /usr/lib/apt/methods/https target/debug/depcat-file target/debug/dep/var/lib/apt/lists/_etc_apt_apt-mirrors.txt_dists_noble-security_universe_cnf_Commands-amd64 target/debug/deps/thiserror_impl/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/deps/libtarget_lexicon-289316e768f6348a.rlib targ�� target/debug/deps/thiserror_impl/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/depupdate-fonts-dir target/debug/deps/thiserror_impl/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/dep--x11r7-layout target/debug/deps/thiserror_impl/home/REDACTED/work/SproutGit/SproutGit/src-tauri/target/debug/depType1 target/debug/depdpkg-deb target/debug/dep--control target/debug/dep/tmp/apt-dpkg-install-DaAh0T/5-xfonts-encodings_1%3a1.0.5-0ubuntu2_all.deb target/debug/dep/var/lib/dpkg/tmp.ci targ�� target/debug/deps/thiserror_impl-82edd9eb2bce6772.thiserror_impl.459b10779fb3d191-cgu.13.rcgu.o target/debug/deps/thiserror_impl-82edd9eb2bce6772.thiserror_impl.459b10779fb3d191-cgu.14.rcgu.o /dist/node-gyp-bin/sudo utGit/src-tauri/apt-config utGit/src-tauri/shell utGit/src-tauri/EtcDir utGit/src-tauri/Dir::Etc (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@liam-russell liam-russell merged commit 920ea2b into main May 12, 2026
25 of 29 checks passed
@liam-russell liam-russell deleted the large-repo-updates branch May 12, 2026 02:27
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.

4 participants