feat: offline mode, member admin, door display config, programmes, room slide-in#4
Merged
Merged
Conversation
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.onConnectionChangemapped from SignalR'sonreconnecting / onreconnected / onclose. Hooks exposeconnectionState.LiveIndicatorthree-state UI (Live · Xs/Reconnecting · N queued/Offline · N queued).online.useTimerHubbuilds 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)
MembersControllergainsreset-link(token-emailed),temp-password(force-set, audit-logged),profile(display name + email with duplicate guard),lock/unlock(Identity LockoutEnd).PasswordControllergains publicPOST /api/auth/password/resetfor the token-emailed reset link, with opaque error mapping (bad userId vs bad token).⋯row menu,EditMemberSheet,SetTempPasswordSheet(two-phase, copy-to-clipboard once committed), public/reset-password/:userId/:tokenpage,Lockedchip on member rows.3. Door display configuration (per-room)
Room.DoorDisplayConfigJsoncolumn (opaque to server, 4 KB cap, audit-logged). Migration20260511195544_DoorDisplayConfig.PublicInfoControllerreturns the config soDoorViewreads 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.DoorPanelrenders two layout templates (landscape vs portrait), each respecting the toggles.4. Programmes (event-level time schedules)
Programme+ProgrammeSlot, nullable refs fromRoom.ProgrammeIdandScheduleItem.ProgrammeSlotId. Migration20260511202431_Programmes.ProgrammesController— CRUD + nested slot CRUD + reorder. Slot edits acceptcascade: "update" | "detach"for re-syncing or detaching linked sessions.RoomFormSheetgains a Programme dropdown.ScheduleItemFormshows 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".SetupNavgains a Programmes tab. New/events/:eventId/programmespage.5. Room slide-in panel (replaces the cramped
⋯dropdown)drawer-panel-enter+drawer-backdrop-enterkeyframes via--ease-out.RoomTileshrinks:⋯button + a secondary Details button both open the panel;Control →stays as the primary CTA.Misc
vite.config.ts—VITE_API_TARGETenv override so devs can chooselocalhost:5050(dotnet run) orlocalhost:8080(docker compose) without editing the file..env.localstays gitignored.test/setup.tsships 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 buildcleantsc -bcleannpm test— 17 test files / 88 tests pass (15 new for offline queue helpers)vite buildsucceeds⋯andDetailsbuttons; offline-mode operator commands queue and reconcile viaReconnected · N synced, M skippedtoast./reset-password/:userId/:tokenflow with a real reset-link email.Deploy notes
Database__AutoMigrate: true):DoorDisplayConfig— addsRoom.DoorDisplayConfigJson(default"{}").Programmes— addsProgrammes+ProgrammeSlotstables, plusRoom.ProgrammeIdandScheduleItem.ProgrammeSlotIdnullable refs.docker compose build app && docker compose up -d app.🤖 Generated with Claude Code