Skip to content

feat: offline mode, member admin, door display config, programmes, room slide-in#4

Merged
jeroenniesen merged 1 commit into
mainfrom
feat/offline-programmes-displays
May 11, 2026
Merged

feat: offline mode, member admin, door display config, programmes, room slide-in#4
jeroenniesen merged 1 commit into
mainfrom
feat/offline-programmes-displays

Conversation

@jeroenniesen
Copy link
Copy Markdown
Contributor

Summary

Five themed features stacked on the apple-ux-pass foundation, plus the README screenshots.

1. Offline mode (display + safe-command queue, ≤5 min outage)

  • lib/offlineQueue.ts — localStorage-backed FIFO queue of operator commands (Pause / Resume / Adjust / SetMessage / ClearMessage). Per-room validators (isApplicable) and optimistic updaters (applyOptimistic).
  • hub/useOfflineCommands.ts — wraps a single-room hub with optimistic-while-offline + silent best-effort drain on reconnect. Toast: Reconnected · N synced, M skipped.
  • TimerHub gains onConnectionChange mapped from SignalR's onreconnecting / onreconnected / onclose. Hooks expose connectionState.
  • LiveIndicator three-state UI (Live · Xs / Reconnecting · N queued / Offline · N queued).
  • Audience surfaces (Speaker / Door / Lobby) hold the last snapshot through reconnects with a non-blocking "Reconnecting · display may drift" banner after 30s. Destructive operator commands are gated by online.
  • StrictMode hub fixuseTimerHub builds the hub inside the effect so the dev-mode double-mount produces two independent connections instead of double-starting one.

2. Member admin (admin password reset + profile + lock)

  • MembersController gains reset-link (token-emailed), temp-password (force-set, audit-logged), profile (display name + email with duplicate guard), lock / unlock (Identity LockoutEnd).
  • PasswordController gains public POST /api/auth/password/reset for the token-emailed reset link, with opaque error mapping (bad userId vs bad token).
  • Frontend row menu, EditMemberSheet, SetTempPasswordSheet (two-phase, copy-to-clipboard once committed), public /reset-password/:userId/:token page, Locked chip on member rows.

3. Door display configuration (per-room)

  • Room.DoorDisplayConfigJson column (opaque to server, 4 KB cap, audit-logged). Migration 20260511195544_DoorDisplayConfig.
  • PublicInfoController returns the config so DoorView reads it without auth.
  • DoorDisplaySettingsSheet (opened from the room slide-in) — orientation toggle (landscape / portrait) + checkboxes for every element. Child checkboxes auto-disable when parent unchecks.
  • DoorPanel renders two layout templates (landscape vs portrait), each respecting the toggles.

4. Programmes (event-level time schedules)

  • New entities Programme + ProgrammeSlot, nullable refs from Room.ProgrammeId and ScheduleItem.ProgrammeSlotId. Migration 20260511202431_Programmes.
  • ProgrammesController — CRUD + nested slot CRUD + reorder. Slot edits accept cascade: "update" | "detach" for re-syncing or detaching linked sessions.
  • RoomFormSheet gains a Programme dropdown. ScheduleItemForm shows a slot picker when the room is bound to a programme; picking a slot fills + locks start + duration. Cascade confirm dialog on slot-timing edits: "Update linked" vs "Detach instead".
  • SetupNav gains a Programmes tab. New /events/:eventId/programmes page.

5. Room slide-in panel (replaces the cramped dropdown)

  • 480px right-anchored drawer with sections: Live state (current item + tabular countdown), Audience displays (inline 64px QR codes for speaker + door, Configure layout button), Operate (control / show mode / schedule), Configure (room settings / door layout / reset code), Danger zone.
  • drawer-panel-enter + drawer-backdrop-enter keyframes via --ease-out.
  • RoomTile shrinks: button + a secondary Details button both open the panel; Control → stays as the primary CTA.

Misc

  • vite.config.tsVITE_API_TARGET env override so devs can choose localhost:5050 (dotnet run) or localhost:8080 (docker compose) without editing the file. .env.local stays gitignored.
  • Soft-delete query filter now applies to Programmes too.
  • test/setup.ts ships a localStorage polyfill so offline-queue tests run under vitest/jsdom regardless of Node experimental-storage state.

README screenshots

  • docs/screenshots/ ships five PNGs: event dashboard, room control surface, room slide-in panel, speaker view, branding setup. New Screenshots section in the README between Status and Docker Compose.

Test plan

  • dotnet build clean
  • tsc -b clean
  • npm test — 17 test files / 88 tests pass (15 new for offline queue helpers)
  • vite build succeeds
  • Manual: signing in works on Docker stack; door display config persists; programmes CRUD works; slot cascade dialog appears when editing a slot's time with linked sessions; room slide-in opens from the tile's and Details buttons; offline-mode operator commands queue and reconcile via Reconnected · N synced, M skipped toast.
  • Manual on staging: same set, plus the public /reset-password/:userId/:token flow with a real reset-link email.

Deploy notes

  • Two new migrations auto-apply on container restart (existing Database__AutoMigrate: true):
    • DoorDisplayConfig — adds Room.DoorDisplayConfigJson (default "{}").
    • Programmes — adds Programmes + ProgrammeSlots tables, plus Room.ProgrammeId and ScheduleItem.ProgrammeSlotId nullable refs.
  • Docker rebuild required: docker compose build app && docker compose up -d app.

🤖 Generated with Claude Code

…om slide-in panel + README screenshots

Five themed features stacked on the apple-ux-pass foundation, plus the
screenshots that go into the README.

1. Offline mode (display + safe-command queue, ≤5 min outage)
   - LocalStorage-backed FIFO queue of operator commands (Pause / Resume /
     Adjust / Set message / Clear message). Optimistic updates while offline;
     silent best-effort drain + "N synced, M skipped" toast on reconnect.
   - SignalR connection state surfaced via TimerHub.onConnectionChange and a
     three-state LiveIndicator (Live · Xs / Reconnecting · N queued / Offline).
   - Audience surfaces (Speaker / Door / Lobby) hold the last snapshot through
     reconnects with a non-blocking "Reconnecting · display may drift" banner
     after 30s. Destructive ops (Stop / Skip / Reset / Start / Quick timer)
     remain gated by `online`.
   - StrictMode hub fix: useTimerHub builds the hub inside the effect so the
     dev-mode double-mount produces two independent connections.

2. Member admin (admin password reset + profile + lock)
   - MembersController gains send-reset-link (token-emailed), force-set
     temporary password (audit-logged), profile edit (display name + email
     with duplicate guard), lock / unlock (Identity LockoutEnd).
   - PasswordController gains the public `POST /api/auth/password/reset` for
     the token-emailed reset link with opaque error mapping for bad token vs
     bad user id.
   - Frontend: ⋯ row menu, EditMemberSheet, SetTempPasswordSheet (two-phase
     with copy-to-clipboard once committed), public /reset-password/:userId/:token
     page, Locked chip on member rows.

3. Door display configuration (per-room)
   - Room gains DoorDisplayConfigJson (opaque to server, 4 KB cap, audit-logged).
   - PublicInfoController returns config so DoorView reads it without auth.
   - New DoorDisplaySettingsSheet (opened from room slide-in): orientation
     toggle (landscape / portrait) + checkboxes for every element (event name,
     room name, logo, now-playing section + speaker + countdown, up-next +
     start time). Child checkboxes auto-disable when their parent unchecks.
   - DoorPanel renders two layout templates (landscape vs portrait), each
     respecting the toggles.

4. Programmes (event-level time schedules)
   - New Programme + ProgrammeSlot entities; nullable refs from Room.ProgrammeId
     and ScheduleItem.ProgrammeSlotId.
   - ProgrammesController CRUD + nested slot CRUD + reorder. Slot edits accept
     `cascade: "update" | "detach"` for re-syncing or detaching linked sessions.
   - RoomFormSheet gains a Programme dropdown. ScheduleItemForm shows a slot
     picker when the room has a bound programme; picking a slot fills + locks
     start + duration. Cascade confirm dialog when editing a slot timing with
     linked sessions: "Update linked" vs "Detach instead".
   - SetupNav gains a "Programmes" tab. New /events/:eventId/programmes page.

5. Room slide-in panel (replaces cramped ⋯ dropdown)
   - 480px right-anchored drawer with sections: Live state (current item +
     tabular countdown), Audience displays (inline 64px QR codes for speaker
     + door, Configure layout button for door), Operate (control / show mode /
     schedule), Configure (room settings / door layout / reset code), Danger.
   - drawer-panel-enter + drawer-backdrop-enter keyframes via --ease-out.
   - RoomTile shrinks: ⋯ button + secondary "Details" button both open the
     panel; Control button stays as the primary CTA.

Misc
   - vite.config.ts gains a VITE_API_TARGET env override so devs can choose
     localhost:5050 (dotnet run) or localhost:8080 (docker compose) without
     editing the file. .env.local stays gitignored.
   - Soft-delete query filters now apply to programmes too.
   - localStorage shim in test/setup.ts so the offline-queue unit tests run
     under vitest/jsdom regardless of Node experimental-storage state.

Screenshots
   - docs/screenshots/ added with 5 PNGs covering the event dashboard, room
     control, room slide-in panel, speaker view, and branding setup. README
     gains a Screenshots section between Status and Docker Compose.

Verification
   - dotnet build clean; 88/88 frontend tests pass (16 new for offline queue +
     temp-password sheet flow); vite build succeeds.
   - Migrations applied on Docker container restart: DoorDisplayConfig (Room
     column) and Programmes (Programme + ProgrammeSlot tables, Room.ProgrammeId,
     ScheduleItem.ProgrammeSlotId).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jeroenniesen jeroenniesen merged commit 4c994e4 into main May 11, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant