Master-detail sessions pattern (both SPAs)
Both frontends implement session browsing with the same master-detail shell.
They share NO code (separate Vite apps, API clients, stores, i18n) — consistency
is by mirrored structure. When editing one /sessions page, mirror the other.
Pages:
- Admin: frontend/src/pages/SessionsPage.tsx
- Operator: frontend-app/src/pages/SessionsPage.tsx
- Detail panes: frontend/src/components/metadata/SessionDetailPanel.tsx,
frontend-app/src/components/sessions/SessionDetailPanel.tsx (both { sessionId }).
Shell (identical Tailwind structure)
- Page root:
max-w-[1600px] mx-auto, thenflex flex-col lg:flex-row gap-4. - Master (list):
lg:w-96 lg:shrink-0, andselected ? 'hidden lg:block' : 'block'— belowlgthe list hides once a session is selected. - Detail:
min-w-0 flex-1; renders<SessionDetailPanel sessionId={selected} />when a session is selected, else a "select a record/session" empty state. - Below
lgin the selected state, alg:hidden"Back to list" control clears the selection.
Scope + URL params
useSearchParams drives everything except facility. Params:
device (device id or all), status (all|active|closed),
mode (all|continuous|sampling), session (selected id), page.
Changing any scope param resets page. Selecting a row sets ?session=<id>.
Facility scope:
- Admin: from useFacilityFilterStore (facilityId/setFacilityId), a sticky
cross-page store — NOT a URL param. The facility "Open sessions" entry point
(FacilitiesPage) calls setFacilityId(id) then navigate('/sessions').
- Operator: single-facility via useFacilityId(); no facility selector.
Device entry point: /sessions?device=<id> (a plain link; the page reads the
device URL param). In device === 'all' mode each list row shows a per-device
color chip from deviceColor(id) (src/lib/deviceColor.ts, duplicated per app
by design).
Per-app differences (intentional)
- Response typing: admin
PaginatedResponse<IngestSession>(camelCase,.data); operatorMeasurementListResponse(snake_case,.items). Do not unify. - Operator copy must pass
jargon-lint(no "session"/"stream"/"세션"/"스트림" in displayed text) — use "measurement"/"record" wording. Admin has no such rule. - Operator detail pane reuses
DeviceTimelineChart(uPlot); admin reuses its richer per-type chart components (StreamChart/AccelChart/…).
The width fix (admin detail pane)
SessionDetailPanel charts render as a single-column flex flex-col gap-4
stack (full-width charts) with only compact single-sample cards collected into a
grid grid-cols-2 sm:grid-cols-3. No col-span-full/3-col grid — that was the
source of the old edge-to-edge / lonely-1/3-cell layout. The bounded detail
column (flex-1 inside max-w-[1600px]) caps chart width.