Optimization Backlog
Optimizations that are found, understood, and deliberately not done yet.
What this list is for
Most of these were discovered while fixing something else, and each is real — a measured cost with a known fix. They are parked for one reason: the surfaces they optimise are still being built. Tuning a page's fetch shape while its layout is in flux means doing the work twice, and the second time against a page nobody remembers the first measurement for.
The intended moment is when UI and UX are final and before launch, not sooner. See production-launch-readiness.md for the gate itself; this list is one of its inputs.
Rules for this document.
- An entry needs a measurement, not an intuition. "Feels slow" is not an entry; "10 calls, 7 of them avoidable" is.
- An entry needs its fix identified. If the fix is unknown, the entry is an investigation, and belongs in the implementation plan instead.
- An entry needs a reason it is deferred. "Not important" is not a reason — if it is genuinely unimportant, delete the entry.
- Correctness bugs do not belong here. This is for things that work and cost too much. A defect goes to the implementation plan and gets fixed.
- When an entry is done, delete it and record the result wherever the pattern lives. This list is a queue, not a history.
O1 — Consultation page N+1: one call per attached form
Measured (2026-08-10, clinic consultation tab): 7 + N API calls, where N is the number of staff forms attached to the appointment. Three forms → 10 calls; ten forms → 17.
Cause. listForms returns rows, not views. A row carries no resolved fields — a form renders from its frozen snapshot once materialised and live from its template before that, and only the view resolves which branch applies. The consultation panel needs a FormView per form, so it fetches each one. Server-side each view costs ≥ 2 queries (PublishedSnapshot is Get + FindVersion, uncached), plus the per-call auth and RLS-transaction overhead of a separate HTTP hop.
Fix. An expansion parameter on the list endpoint — GET /v1/organizations/{org}/forms?view=full returning View[]. Collapses 7 + N to a flat 7. The care needed is server-side: a naive loop only relocates the N+1 from HTTP into the database. It should batch-resolve by distinct template id, since a consultation's forms typically share very few templates.
Why deferred. The consultation tab is actively being built and what it fetches is still moving. The fix is also an API + OpenAPI + client change, which is disproportionate while the caller is unsettled.
Size when it lands. 3 forms: 10 → 7 calls. 10 forms: 17 → 7. Latency saving is one wave (~45 ms measured at N=3, more at higher N).
O2 — Availability dialog writes still pay a route re-render
Measured (2026-08-10, clinic calendar → specialist availability): opening the dialog is 4 calls (down from 14 — that half is fixed). A bulk-override save is 8: the PUT, four for the dialog's own SWR revalidation, and three for a route re-render nobody consumes.
Cause. The writes are Server Actions, and a Server Action re-renders its route regardless of refresh() — removing refresh() (already done) drops only the extra client refetch, not the re-render underneath. See P59.
Fix. Move bulkOverrideAction and replaceWeeklyDayAction to route handlers, as the dialog's read already is. setSchedulingTimezoneAction must stay an action — the roster's "bookable" badge is scheduling_active && scheduling_timezone, so that write genuinely changes server-rendered state behind the dialog.
Why deferred. It trades against the "actions are for mutations" convention for a save that happens a handful of times per dialog session, and the expensive instances of this class (the two autosaves) are already fixed. Worth doing when the surface is final, not before.
Size when it lands. ~8 → ~5 calls per save.
O3 — Consultation page fetches the category list to filter in JS
Measured (2026-08-10): one of the consultation tab's seven calls is GET /document-categories?filled_by=staff, used only to build a key set and filter the forms list client-side.
Fix. Let the forms list filter by category server-side, and drop the call.
Why deferred. Smallest item on this list, and it touches the same list endpoint as O1 — doing both in one change is cheaper than doing each alone.
Size when it lands. −1 call per consultation page load.
O4 — No operational view of who is hitting rate limits
State (2026-08-10): rate-limit rejections log policy, limit, path, principal_id, principal_type and org_id, so the data exists. Nothing surfaces it.
Fix. A Console view answering "which orgs are hitting 429s, and on which policy" — the requirement P60 named as the evidence base for tuning the limiter.
Why deferred. Not a performance cost — an observability gap, and it only pays off once real clinics generate traffic. It becomes genuinely useful at onboarding, which is also when the ceilings need their first real tuning.
Note. This is the one entry here that is worth doing at launch rather than before it: with no production traffic there is nothing to look at.
Where the finished work went
Fixes from the same 2026-08-10 investigation that are done, recorded here only so nobody re-opens them: clinic consultation autosave and portal patient autosave moved off Server Actions (538 → 64 calls to fill one form; ~5 → 1 per field), the appointment record's fetch waterfall collapsed across all five tabs (P62), the availability dialog's read moved to a route handler (14 → 4), and the rate limiter gained a per-principal layer with per-principal-type budgets (P60).