Tracked followup from Slice C (PIN unlock latency was measured at 4403ms on prod, dominated by
router.refresh() re-running ~20 DAOs in register/[configId]/page.tsx).
Slice I returns the OPEN-phase data inline from the unlock action; the shell transitions state
locally instead of refetching the page.
Promise.allSettled, so
total wall time = max of the round-trips instead of the sum. Per-fetch try/catch
fallback preserved via an unwrap() helper — a single slow/failing DAO never blocks
the others.
New lib/server/open_phase_loader.ts — two loaders
(loadProOpenPhaseBundle, loadStarterOpenPhaseBundle) that fan out
the OPEN-phase fetches in a single Promise.allSettled. Per-fetch
unwrap() + warn-on-reject preserves the "nice-to-have" fallbacks the old
sequential try/catch blocks had.
lib/actions/register.ts — three actions
(unlockRegisterAction, unlockRegisterAsUserAction,
openShiftAction) now return an optional openData field, tier-
discriminated ({ tier: "pro", ... } | { tier: "starter", ... }). A shared
internal loadBundleForSession() helper detects tier via
getCurrentTenant() and dispatches to the right loader. The bundle load runs in
parallel with the audit-log INSERT so the existing critical-path cost stays
roughly the same.
lock-screens.tsx — UnlockResult +
OpenShiftResult types exported. LockScreen.onUnlocked and
PreShiftScreen.onOpened receive the full action result so the parent shell can
transition state locally.
Both lockable shells ((pos-fullscreen)/.../lockable-shell.tsx Pro
+ starter-lockable-shell.tsx) became client state machines keyed on an
override ({phase, cashier, bundle}). On unlock action: setOverride
and render the new phase directly. On lock / close-shift: setOverride(null) +
router.refresh() so the server-driven LOCKED render takes over cleanly. First
page load (cookie already present, mid-shift reload) — override is null, falls back to the
server-rendered props.phase path. No regression.
page.tsx — unchanged. Still SSR-renders OPEN with all data on a mid-shift reload (cookie already present). The new path is invoked only on the unlock transition; first load works exactly as before.openData field is optional; pre-Slice-I callers ignore it.writeActiveCashier still writes via response headers; the action reads its own params for tier dispatch.Critical-path change. Lockable shell is used by every cashier login. Mitigation: kept the old action behavior intact (extending vs replacing); defensive router.refresh() fallback in the shell if the action returns an unexpected shape.
Phase state machine. Moving from server-derived phase to client state introduces edge cases (e.g. session closed externally mid-shift). Today's flow has the same issue (router.refresh only on user action). Won't regress.
Lock action override-clear race. Both onLock and close-shift onDone explicitly call onAfterCloseShift?.() (the setOverride(null) hook from the shell) BEFORE router.refresh() so the next render lands on server-driven locked state without a stale override flicker.
open_phase_loader.ts: Pro + Starter bundle types + loaders exportedopen_phase_loader.ts: uses Promise.allSettled + unwrap() for per-fetch fallbackregister.ts: 3 actions return openData; tier discriminator presentlock-screens.tsx: UnlockResult + OpenShiftResult exports; callback signatures updatedlockable-shell.tsx: override state + bundle spread on OPEN + reset on lock/close-shiftlockable-shell.tsx: same state machine shape; no kitchen-field leakRaw: 01-loader-probe.json · result.json
New: lib/server/open_phase_loader.ts
Modified: lib/actions/register.ts · app/(authed)/pos/_components/lock-screens.tsx · app/(pos-fullscreen)/pos/register/[configId]/lockable-shell.tsx · app/(authed)/pos/_components/starter-lockable-shell.tsx
Push → CF deploy → time the unlock → workspace-visible latency on get-coffee Pro. Pre-Slice-I baseline = 4403ms. Target < 1500ms. Plus regression sweep (51 checks).