Skip to content

fix(staged): stop project switch from freezing on safe-to-delete git checks#769

Merged
matt2e merged 5 commits into
mainfrom
slow-project-switch
Jun 5, 2026
Merged

fix(staged): stop project switch from freezing on safe-to-delete git checks#769
matt2e merged 5 commits into
mainfrom
slow-project-switch

Conversation

@matt2e
Copy link
Copy Markdown
Contributor

@matt2e matt2e commented Jun 5, 2026

Summary

Project switching could freeze because an eager $effect in ProjectHome recomputed 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

  • Add computeSafeToDeleteSignature to projectDeleteSafety.ts, a cheap signature over only the inputs that affect the safe-to-delete result (prState, branchType, repoCount, plus prHeadSha). ProjectHome's effect now skips recomputation — and the git work it spawns — when the signature is unchanged, eliminating the per-switch freeze.
  • Keep the safe-to-delete dedup from dropping its only scheduled check (follow-up fix).
  • Add project-switch perf instrumentation: navigation.svelte.ts stamps each switch with a monotonic token and exposes msSinceProjectSwitch / currentProjectSwitchToken / currentProjectSwitchTarget, with [perf][project-switch] debug logs in ProjectHome / ProjectSection to isolate switch latency.
  • Minor prs.rs adjustment.

Testing

  • Updated projectDeleteSafety.test.ts covering the new signature behavior.

matt2e added 4 commits June 5, 2026 14:13
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>
@matt2e matt2e requested review from baxen and wesbillman as code owners June 5, 2026 05:35
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +420 to +423
const signature = computeSafeToDeleteSignature(projectsSnapshot, branches, repoCounts);
if (signature === lastSafeSignature) {
// Inputs relevant to the result are unchanged — skip the git work.
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.

P2 Badge 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>
@matt2e matt2e merged commit 7f05802 into main Jun 5, 2026
5 checks passed
@matt2e matt2e deleted the slow-project-switch branch June 5, 2026 06:43
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