Skip to content

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, then flex flex-col lg:flex-row gap-4.
  • Master (list): lg:w-96 lg:shrink-0, and selected ? 'hidden lg:block' : 'block' — below lg the 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 lg in the selected state, a lg: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); operator MeasurementListResponse (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.