Keep one ward access object per desk so the idle lock fires on signed-in desks (WF-22)

Merged
BI
1d ago
+107-2

Bug

On signed-in ward desks (shared mode, any non-executive role), the 15-minute idle lock (WardIdleLock) never fires. An unattended desk stays open.

Cause

Every shared poll (about every 3 s) parses a fresh access object that is equal in value. WardFlowConnection in ward-flow-provider.tsx builds parseStoredAccess(JSON.stringify(shared.access ?? null)) on each render and passes it to an inner <WardShellAuthorityProvider>. That shadows the outer provider. React Compiler memoises on object identity, so a new object comes out each time. WardIdleLock's effect depends on access, so it clears and restarts the 15-minute timer on every poll.

5ea825c6b (28 Oct) fixed the same problem for the outer provider in ward-workspace-host.tsx only. The inner provider (from 1e5cdfa29, 27 Oct) was not covered. Executive desks are not affected because they have no inner provider.

Fix

WardShellAuthorityProvider (shell/ward-shell-public-context.tsx) now keys its context value on JSON.stringify(access) with useMemo. This applies the same JSON-key idea as 5ea825c6b at the one place both providers pass through. The object stays the same while the role and place are equal, and a real change still hands over a new object.

The fix lives in the context file, not in ward-flow-provider.tsx, on purpose. Open PRs #1605 and #1608 both edit ward-flow-provider.tsx (other lines). This way the PR overlaps no one's files.

Also adds a What's new line, because staff will see desks lock again.

Test evidence (local, at b154aaec8 on main 72d99b1cf)

All data is synthetic.

  • New regression test: tests/ward-idle-lock-shared-poll.dom.test.tsx
    • It re-renders the provider with a fresh, equal access every 3 s for idle time + 3 s, then expects the lock dialog.
    • It also checks that the object identity holds for equal access and changes for different access.
    • Command: node scripts/run-vitest.mjs run tests/ward-idle-lock-shared-poll.dom.test.tsx
    • With the fix: 2 passed.
    • With the context file restored to main: 2 failed (no dialog; Object.is mismatch).
  • Related suites: every test file that references WardIdleLock, WardShellAuthorityProvider, useWardShellAuthority, ward-shell-public-context, ward-access-store or idleLock.
    • Command: node scripts/run-vitest.mjs run <those 159 files>
    • Result: 159 files, 1391 tests passed.
  • Lint: eslint --max-warnings 0 on the two touched TS files. Exit 0.
  • Typecheck: tsc -p tsconfig.typecheck.json --noEmit. Exit 0. The pre-commit hook's lint and typecheck also passed.
  • Script budgets: WARD_GATE_BUILD=1 next build --webpack, then node scripts/check-script-budget.mjs. Every route is under its budget, and no budget changed. For example, /ward-flow is 572.7 / 574.7 KB and /ward-flow/ed is 533.1 / 534.3 KB.

Hosted CI is not yet observed.