Skip to content

Fix/post merge followups#5

Merged
jeroenniesen merged 2 commits into
mainfrom
fix/post-merge-followups
May 11, 2026
Merged

Fix/post merge followups#5
jeroenniesen merged 2 commits into
mainfrom
fix/post-merge-followups

Conversation

@jeroenniesen
Copy link
Copy Markdown
Contributor

No description provided.

jeroenniesen and others added 2 commits May 11, 2026 20:47
…ck timer, dashboard polish

Follow-up on the apple-ux-pass branch addressing issues found during in-browser
testing, plus a small new feature.

Server
- AppDbContext: query filters for Event / Room / ScheduleItem now also exclude
  rows whose DeletedAtUtc is set. Before this fix, deletes were soft-deletes but
  the list/get endpoints kept returning the same rows, so the UI showed
  "deleted" items reappearing. Callers that need to see tombstones must use
  IgnoreQueryFilters() (audit, undo, purge jobs).

Hub / connection
- useTimerHub: move hub instantiation back inside the effect (was hoisted into
  useMemo). With React 18 StrictMode the same hub object survived the dev-mode
  double-mount, so .start() ran twice on the same connection and rejected with
  "Cannot start a HubConnection that is not in the 'Disconnected' state."
  A fresh per-mount hub fixes it.
- useEventRoomSnapshots: catch each per-room resync individually and run the
  hub at LogLevel.Critical so expected per-room Forbidden rejections on the
  dashboard (RoomOperator scoped to a subset, Viewer with no hub access) don't
  surface as red errors. Presence fetch falls back to empty on Forbidden.
- TimerHub: accept an optional LogLevel so consumers can opt down to Critical
  for read-only dashboards.

Operator console
- Stop is a one-click Button again. The hold-to-confirm pattern was too strict
  for the normal "speaker finished, end the session" flow; Skip and Reset
  remain hold-to-confirm in the More menu where the destructive risk lives.
- OperatorHero gains an empty-state "Quick timer" CTA when the room has no
  current item and no upcoming item.
- New QuickTimerSheet: title + MM:SS duration + 5/10/15/30 min presets. Creates
  a one-off schedule item (scheduledStartUtc=now, preRoll=0) and starts it via
  hub.startItem. Lets operators run an ad-hoc countdown without authoring a
  schedule first.

Misc dashboard
- Picks up the surrounding edits already on the branch: presence pill on
  LobbyCard, Live · N/M rooms running in EventIdentity, Show mode entry,
  RehearsalControls + useRehearsalClock + lib/rehearsal, PreflightPanel +
  lib/preflight, useToast moved to its own toastContext file, plus the
  matching new tests.

Verification
- tsc clean, 73/73 tests pass (4 new — preflight + rehearsal), build succeeds.
- Manual: delete a schedule item now actually removes it; Stop ends the session
  on a single click; signing in on a fresh tab no longer fires StrictMode
  "cannot start HubConnection" errors; Quick timer starts immediately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…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 1ced63b 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