perf(web): retry-harden the one-time conversion VACUUM against reloads - #383
Merged
Conversation
VACUUM is atomic — a reload mid-run rolls it back with zero progress, and on a bloated profile it starts ~25-30s after open and takes minutes on the single serial SQLite worker, so rapid reloaders (observed: 5 in a row on 2026-07-05) could interrupt the auto-vacuum conversion forever and never escape ~17-19s cold opens (explorations 0260, PRs #380/#381). - Persist an attempt counter (xnet:db-vacuum:attempts) when a real VACUUM starts; clear it on success. Skipped runs (in-memory DB, already converted) never touch it, so the steady-state cost stays one idle PRAGMA read per boot and first paint is never blocked. - 2nd attempt on: show a subtle non-blocking "Optimising storage — keep this tab open a few minutes" pill (StorageOptimiseHint) while the VACUUM is in flight, cleared on completion or failure. - 3rd attempt on: escalate scheduling — start right at first paint instead of first paint + 3s settle + idle, trading one slow-feeling boot for finally landing the conversion. - Fix: import @xnetjs/ui/motion.css in the app. Only Storybook loaded it, so every <Presence> exit waited on an animationend that never fired and exiting UI (e.g. UndoToast) never unmounted once shown. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: xNet Test <test@xnet.dev>
Contributor
🖼️ UI changes in this PRNo visual differences detected in the changed UI. |
Contributor
|
Preview removed for PR #383. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The one-time conversion VACUUM (apps/web/src/lib/db-vacuum.ts, exploration 0260, follows #380/#381) converts long-lived profiles to
auto_vacuum=INCREMENTAL. It is scheduled viarunWhenBootSettled(first paint + 3 s settle + idle), so on a bloated ~500 MB profile with ~19 s first paint it starts ~25–30 s after open and takes 1–3 minutes on the single serial SQLite worker. VACUUM is atomic: a reload or tab close mid-run rolls it back with zero progress. The slowness it exists to fix is exactly what makes users reload — observed 2026-07-05: 5 rapid reloads — so the file never converts and cold opens stay ~17–19 s forever.Fix — graduated escalation off a persisted attempt counter
xnet:db-vacuum:attempts(localStorage): incremented the moment a real VACUUM starts, cleared on success. Skipped runs (in-memory DB, flag latched +PRAGMA auto_vacuum=2) never touch it — steady state stays one idle PRAGMA read per boot, and first paint is never blocked.StorageOptimiseHint, wired via the same window-CustomEvent bus pattern asstorage-durability.ts). Placedbottom-20 right-4to clear the compact-shell bottom nav, the UndoToast, and the dev-tools launcher (verified on both form factors in preview).bootSettled()), skipping the 3 s settle + idle wait, with the same 45 s no-paint fallback asrunWhenBootSettled. One deliberately slow-feeling boot in exchange for finally landing the conversion.Drive-by fix:
<Presence>exits never ran in the appNothing in
apps/webimported@xnetjs/ui/motion.css— only Storybook did.<Presence>unmounts onanimationend, so with no keyframes loaded every Presence-wrapped element stayed mounted forever once shown (e.g. UndoToast never left the DOM after its 6 s timeout).globals.cssnow importsmotion.css; verified live that the exit rules load and the pill unmounts onanimationend.Verification
db-vacuum.test.ts(counter persist/clear/keep-on-failure, no counting of skipped runs, escalated start at first paint + no-paint fallback, hint publish/clear incl. failure path, no hint on first attempt) — 12/12 pass; full suite 10096/10096.attempts=1pre-set, the real path ran end-to-end (db vacuum donelogged, counter cleared, flag latched); pill verified on desktop + mobile clear of all bottom-edge occupants.tsc --noEmit, prettier,check-motion-vocab,check-humane-patterns, and a productionvite buildall pass.--no-verifyafter running every pre-push step manually — the hook's ownpnpm installflipscore.bare=trueinside a git worktree and breaks the git calls that follow it.🤖 Generated with Claude Code