Work / Trunk / Wiki / Decisions
2026-10-20-edit-state-and-caching
Decisioncanonicalverified 2026-10-20
DECISION.2026-10-20.EDIT-STATE-AND-CACHINGAn edit on a canvas reads back only its canvas
Asked for and approved by the owner on 2026-10-20.
Decision
An edit on a page no longer reloads the screen: it reads back that page's canvas into a browser store. A page render works out the draft once, and a read writes only when main moved.
- A page render works out the draft once.
editorValuesand the membership check are kept for the length of a render with Reactcache. A tRPC call is not a render, so a write always reads its own transaction. - A read writes only when main moved. A draft on main already at main's
head skips its sync (
syncDraftin@trunk/history). Only a commit changes main's values, and every commit moves main's head. See branch-verify-merge. - An edit on a page reads back only that page's canvas. A Zustand store
(
apps/web/src/stores/canvas-store.ts) holds each open page's drawn elements and edited properties. Canvas hooks calluseCanvasRefresh(), which asksdrawing.canvasfor that page and puts the answer in the store. The frame already redraws itself from the draft. Layers and the drawn inspector read the store. - A full reload stays where the shape of the product changes: a new or deleted page, Review, merge, sync, undo, and anything on a component's canvas, which has no store yet.
- Folder rules are linted.
libandconstantsimport none ofcomponents,hooks,storesorserver;serverimports none ofcomponents,hooksorstores;hooksandstoresimport nocomponents. Types may cross. knip runs inpnpm verify.
Why
Every edit called router.refresh(). The server then ran every loader of the
screen again: one reload of a page in Edit was 212 SQL statements in 17
transactions, about 330 ms on a laptop. Each loader worked out the draft's
values again, about 8 times per render, and syncing the draft wrote to it even
when main had not moved. Reads still go through withMember
(tenant-isolation).
Considered and not done:
- Rust. The heavy work runs in Chromium; the cost was repeated reads.
- React Query or SWR. Reads come from server components; a second cache would be a second copy of the same data.
- Caching the page frame (ETag, LRU). The frame is
no-storeon purpose, and every edit is a new revision, so a cache would rarely hit. - nuqs. The URL helpers are about 150 lines and most of the address is read on the server.
- Server-Sent Events in place of polling. Polling every 1–3 s is a few lines; SSE needs a long-lived database listener.
- Undo in the browser. Undo restores a snapshot of the draft, which covers every kind of change. It stays on the server.
Impact
- One reload of a page in Edit: 212 statements → 113. A rename in Layers is one save and one canvas read, with no reload.
- A new edit hook on a canvas calls
useCanvasRefresh(), neverrouter.refresh(). - A component's canvas keeps its reload: measured on 2026-10-20 it is 87 statements and about 160 ms, and a store would copy most of the component inspector's data into the browser.
- A move to another screen shows a loading screen at once (pages, components, Journey, Review, Compare, Branches, Versions); picking inside one screen does not, so a Styles token or an Analysis section never blanks the panel just clicked.
Status
Active.
Sources
- The plan and its diagram, approved 2026-10-20: https://claude.ai/artifact/ERQXWAVEyQHvs1MXZmZvHS