Skip to content

Adopt Per-Operator Preferences from rwe-core

Technical Story: Athlete-scoped preferences vs device-scoped localStorage

Store athletes share devices, switch stores, and expect their dismissals, layouts, and flags to follow them — not the browser profile on a shared iPad.

rwe-core keys several dashboard behaviours by operator_id (from useAuth().userInfo):

  • Widget dismiss persistence: localStorage.widgetsLastHiddenByUser[operatorId][widgetId]
  • Per-athlete feature flags via deledge-based overrides (host-level)

whisker-host today persists much user-visible state per device in localStorage:

  • Dashboard layout: dashboard-layout-store (Zustand persist — not scoped by operator_id)
  • Sidebar, theme, language — device-local patterns documented in Risks & improvements — end users

On a shared store device, Athlete A’s dashboard layout or widget dismissals may appear for Athlete B after login switch. rwe-core’s widget dismiss model avoids that for widgets; Whisker does not yet.

This ADR decides what to adopt from rwe-core — not whether to abandon Whisker, but which user-scoping model Whisker must implement before rwe-core migration claims parity.

Related: 0006 — Widget host model (widget dismiss is one instance of this problem).


  • Shared devices in stores — Same hardware, different athletes per shift
  • Athlete continuity — Dismissals and layout should not leak across users
  • Parity with rwe-core migration — Workflows that relied on per-athlete flags must not silently break
  • Storage simplicity — localStorage is easy but wrong scope without keying strategy
  • Future BFF — Server-side preferences may belong on whisker-bff long term

Option A — Keep device-scoped localStorage (Whisker today)

Section titled “Option A — Keep device-scoped localStorage (Whisker today)”

No change; accept that preferences are per-browser.

Option B — Adopt rwe-core’s operator-keyed localStorage pattern

Section titled “Option B — Adopt rwe-core’s operator-keyed localStorage pattern”

Namespace all preference keys by operator_id from useAuth().userInfo (client-side only, same storage medium).

Option C — Server-side preferences via BFF

Section titled “Option C — Server-side preferences via BFF”

Persist per-operator layout, dismissals, flags in a store API; localStorage as cache only.

Operator-keyed localStorage short term; BFF-backed preferences when whisker-bff lands.


  • Good: simplest implementation; no backend.
  • Bad: wrong UX on shared devices; migration blocker from rwe-core; diverges from athlete mental model.
  • Good: matches rwe-core widget dismiss pattern; no backend required for first slice; quick to ship.
  • Bad: still client-only — cleared if storage wiped; no cross-device sync for same athlete on another iPad.
  • Good: true per-athlete profile; cross-device; audit trail possible.
  • Bad: requires BFF/API design, auth, storage, migration — not ready for immediate pilot.
  • Good: closes shared-device gap quickly; leaves room for server profile; aligns with rwe-core interim behaviour.
  • Bad: two-phase implementation; teams must migrate keys once BFF ships.

Chosen option: Option D — hybrid.

Near term (adopt from rwe-core):

  1. Key dashboard layout, widget dismiss state, and similar preferences by operator_id — mirror rwe-core’s widgetsLastHiddenByUser[operatorId][widgetId] pattern.
  2. Clear or swap preference namespaces on login/logout when operator_id changes.

Medium term:

  1. Design server-backed athlete preferences on whisker-bff (or flags service) — resolves risk #10 (per-athlete feature overrides / deledge parity).

Explicitly not deciding here: exact API shape for server preferences; manifest userOverrides vs separate flags service.

Option A is rejected as steady state for store shared devices.


  • Shared iPads behave correctly when athletes hand off.
  • Migration from rwe-core can cite explicit preference parity plan.
  • Reuses proven rwe-core keying without copying entire host.
  • Whisker must audit all persist stores for operator scoping — not only dashboard layout.
  • Client-only prefs still do not sync across devices for one athlete until server phase ships.
  • deledge / feature-flag parity remains a separate workstream (risk #10).

  1. Inventory every localStorage / sessionStorage persist in whisker-host and WCL providers; add operator_id namespace where user-specific.
  2. Implement widget dismiss persistence keyed by operator when 0006 lifecycle work lands.
  3. Block rwe-core migration sign-off for modules that depend on per-athlete flags until risk #10 has a decided approach.