fix(staged): stop project switch from freezing on safe-to-delete git checks#769
Conversation
Add [perf][project-switch] debug logs to measure how long the detail page takes to render after a project switch: - navigation: stamp performance.now() in selectProject and expose msSinceProjectSwitch() so downstream components can report elapsed time - ProjectHome: log when the detail selection resolves and time the safe-to-delete check across visible projects - ProjectSection: time hashtag-item building and note loading, and log mount timing relative to the switch Also picks up a Cargo.lock sync for the doctor crate (nix, wait-timeout). Signed-off-by: Matt Toohey <contact@matttoohey.com>
Sharpen the [perf][project-switch] instrumentation so the logs can be read unambiguously across re-fires and the keyed-block swap: - navigation: replace the bare lastProjectSwitchAt timestamp with a currentSwitch record carrying a monotonic token plus the target project id; expose currentProjectSwitchToken/Target and stamp the end of selectProject's synchronous body to isolate sync work from the unmeasured section swap. - ProjectHome: only the first effect firing per switch token reports elapsed-since-switch; later event-driven re-fires are labelled as such so they don't report a misleading elapsed value. - ProjectSection: log teardown timing in onDestroy and tag mount/destroy logs with the switch token, bracketing the keyed-block swap. Signed-off-by: Matt Toohey <contact@matttoohey.com>
The cosmetic "safe-to-delete" button styling ran an eager $effect that spawned a per-branch `hasUnpushedCommits` git subprocess loop on every project switch. For a project with real local branches it cost ~5s cold, re-fired ~12x with no stale guard, and held the event loop so the keyed-block swap couldn't flush (teardown +9915ms, mount +10864ms). Phase 1 (frontend, ProjectHome.svelte) — the real fix: - Defer updateSafeStatus() off the critical render path via requestIdleCallback (setTimeout fallback), cancelled in teardown, so the switch's section swap flushes immediately and the cosmetic styling settles late. - Add a stale guard (mirroring the sibling hashtag effect) that aborts in-flight work on re-fire/teardown so older loops can't clobber newer state. - Dedupe on a cheap structural signature of only the inputs that affect the result (prState, branchType, repoCount, prHeadSha). Background hydration reassigns the source Maps ~12x per switch without changing these fields; the signature early-return spawns no git work. Trades a small staleness window in the red styling for eliminating the freeze. - Parallelize the check across visible projects via Promise.all instead of sequential for-await. Phase 2 (backend, prs.rs): - Wrap the local-branch path of has_unpushed_commits_impl in spawn_blocking, matching the remote path, so a slow cold git subprocess can't block the Tauri IPC thread. Extract computeSafeToDeleteSignature into projectDeleteSafety.ts with unit tests covering identical-input stability and prHeadSha/repoCount invalidation. Signed-off-by: Matt Toohey <contact@matttoohey.com>
…ed check Resolve review feedback on the safe-to-delete deferral: - Record lastSafeSignature only after updateSafeStatus actually completes (inside the !stale block), not before scheduling. Previously a re-fire or teardown could cancel the pending idle callback after the signature was already stamped, so the next fire computed the same signature and early-returned without rescheduling — dropping the check permanently for that signature. Now a cancelled run leaves the signature unchanged and the next fire reschedules. - Pass a 2000ms timeout to requestIdleCallback so the cosmetic check still runs even while the main thread stays busy through the post-switch hydration window, instead of being deferred indefinitely. Signed-off-by: Matt Toohey <contact@matttoohey.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ed134132e
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const signature = computeSafeToDeleteSignature(projectsSnapshot, branches, repoCounts); | ||
| if (signature === lastSafeSignature) { | ||
| // Inputs relevant to the result are unchanged — skip the git work. | ||
| return; |
There was a problem hiding this comment.
Avoid spawning duplicate safe-delete checks while one is in flight
When branchesByProject or repoCountsByProject is reassigned with the same relevant fields while the first safe-to-delete check is already running, this comparison still misses the duplicate because lastSafeSignature is only assigned after the async Promise.all completes. The cleanup marks the old run stale, but it cannot cancel the already-started commands.hasUnpushedCommits IPC/git subprocesses, so background hydration can still launch overlapping checks for the same signature and reproduce the switch-time git storm this change is trying to avoid. Consider tracking a pending/in-flight signature before scheduling or before starting updateSafeStatus.
Useful? React with 👍 / 👎.
The [perf][project-switch] debug logs added earlier on this branch did their job — they traced the stall to the cosmetic safe-to-delete git check, which is now fixed. Strip the instrumentation back out: - navigation: drop currentSwitch/msSinceProjectSwitch/ currentProjectSwitchToken/currentProjectSwitchTarget and restore selectProject to its original body. - ProjectHome: remove the selection-resolved debug effect and the safe-to-delete timing log (keeping the actual deferral/dedup fix). - ProjectSection: remove the mount/destroy, hashtag-build, and note-load timing logs and their now-unused imports. Signed-off-by: Matt Toohey <contact@matttoohey.com>
Summary
Project switching could freeze because an eager
$effectinProjectHomerecomputed the safe-to-delete result on every change to its source Maps, spawning expensive per-branch git work even when background hydration reassigned the Maps without changing any field the computation actually depends on.Changes
computeSafeToDeleteSignaturetoprojectDeleteSafety.ts, a cheap signature over only the inputs that affect the safe-to-delete result (prState,branchType,repoCount, plusprHeadSha).ProjectHome's effect now skips recomputation — and the git work it spawns — when the signature is unchanged, eliminating the per-switch freeze.navigation.svelte.tsstamps each switch with a monotonic token and exposesmsSinceProjectSwitch/currentProjectSwitchToken/currentProjectSwitchTarget, with[perf][project-switch]debug logs inProjectHome/ProjectSectionto isolate switch latency.prs.rsadjustment.Testing
projectDeleteSafety.test.tscovering the new signature behavior.