diff --git a/tasks/new/2026-0827.01.jul.feat.event-controls.attendees-drawer-and-live-controls-0029.md b/tasks/new/2026-0827.01.jul.feat.event-controls.attendees-drawer-and-live-controls-0029.md new file mode 100644 index 0000000..083b751 --- /dev/null +++ b/tasks/new/2026-0827.01.jul.feat.event-controls.attendees-drawer-and-live-controls-0029.md @@ -0,0 +1,123 @@ +# TASK METADATA + +- **Target Files:** `server/db.ts`, `server/routes/events.ts`, + `server/routes/sessions.ts`, `server/auth-session.ts`, `server/middleware.ts` + (if needed for `auth_forward.ts`), + `ui/components/sessions/EventCockpitDeck.tsx`, + `ui/components/sessions/EventAttendeesDrawer.tsx`, + `ui/components/sessions/SessionsScript.tsx` +- **Core Objective:** Implement Phase 4 of the Event & Session Overhaul by + adding live attendee management controls, an expandable EventAttendeesDrawer, + individual session pausing, and operational card controls (Rotate PIN, +5 + Seats). +- **Dependencies:** None. +- **Additional Important Notes:** Client scripts in `SessionsScript.tsx` must + use pure vanilla JavaScript for DOM manipulation. `is_paused` logic must apply + both to Postgres and the Valkey caching layer for immediate `403 Forbidden` + evaluation on active sessions. + +--- + +## 2. Architectural Considerations & Risks + +- **Risks:** + - **Caching Desync:** When pausing a session, updating PostgreSQL is + insufficient; the corresponding Valkey session object must immediately have + `is_paused: true` merged into its JSON string payload to enforce real-time + blocks. + - **Zero-Migration Query Fragility:** The attendee lookup relies on the + deterministic username format `guest__`. If the slug + format changes in the future, this lookup may fail. This is accepted as a + feature to avoid schema migrations for ephemeral associations. + - **Schema Evolution:** We are adding an `is_paused BOOLEAN DEFAULT FALSE` + column to both the `sessions` and `event_passes` tables. The migration logic + in `server/db.ts` needs soft-ignores via `try-catch` to avoid crashing if + run repeatedly. + - **DOM Complexity:** We are introducing an `EventAttendeesDrawer` which + slides out over the interface. Existing strict DOM bindings must not be + broken. Use accessible semantic markup and clear IDs. + +- **Alternatives:** + - _Adding `event_id` to `sessions`:_ While more relational, it introduces a + schema migration and foreign key complexity for an ephemeral guest. + Leveraging the existing deterministic `username` prefix for attendee + querying keeps the schema decoupled and is highly efficient via standard SQL + `LIKE`. + +--- + +## 3. Proposed Implementation + +### Phase 1: Database & Backend Endpoints + +1. **Schema Updates (`server/db.ts`):** + - Inject + `ALTER TABLE sessions ADD COLUMN IF NOT EXISTS is_paused BOOLEAN DEFAULT FALSE`. + - Inject + `ALTER TABLE event_passes ADD COLUMN IF NOT EXISTS is_paused BOOLEAN DEFAULT FALSE`. +2. **Session Pausing Logic (`server/routes/sessions.ts` & Auth/Middleware + Layer):** + - Add `POST /api/sessions/:id/pause` to toggle `is_paused`. + - Update the PostgreSQL `sessions` record. + - Fetch the session from Valkey, merge `is_paused: true|false`, and + re-serialize to Valkey. + - Ensure the edge auth check (`server/auth-session.ts` or forward-auth + middleware) immediately returns `403 Forbidden: Session Paused by Host` if + `is_paused === true`. +3. **Event Operational Controls (`server/routes/events.ts`):** + - Add `POST /api/events/:id/rotate-pin`: Generate a new 6-digit PIN, update + `event_passes.pin_code`, and return the new PIN. Existing sessions remain + untouched. + - Add `POST /api/events/:id/expand`: Accept `{ addSeats }`, execute + `UPDATE event_passes SET max_seats = max_seats + $1 WHERE id = $2`, and + return the updated capacity. +4. **Attendee List Endpoint (`server/routes/events.ts`):** + - Add `GET /api/events/:id/attendees`. + - Perform a deterministic SQL JOIN: + `SELECT s.*, u.username, u.display_name FROM sessions s JOIN users u ON s.user_id = u.id WHERE u.username LIKE ${'guest_' + event.slug + '_%'} AND s.expires_at > NOW() ORDER BY s.created_at DESC`. + +### Phase 2: Event Deck Polish (`ui/components/sessions/EventCockpitDeck.tsx`) + +1. **Countdown & UI Cleanup:** + - Add dynamic `⏳ Xh Ym left` logic or badge for active passes. + - Introduce an accessible `
` expander for the CLI 1-liner handoff + string to clean up the card's visual footprint. +2. **New Controls:** + - Add a `[ 🔄 Rotate PIN ]` button. + - Add a `[ 👥 Manage Attendees (N) ]` button to trigger the new slide-out + drawer, replacing static numbers with actionable links. + - Add a `[ +5 Seats ]` capacity expansion button. + - Ensure `[ End Event ]` retains clear visual destructive styling and + `aria-label`. + +### Phase 3: Slide-Out Attendee Drawer (`ui/components/sessions/EventAttendeesDrawer.tsx`) + +1. **Drawer Component:** + - Create a new reusable drawer + `
` following existing + drawer patterns (e.g., `WorkshopDrawer.tsx`). + - The drawer content container (`#attendeesDrawerContent`) will display the + fetched real-time list of attendees for a targeted event. + - Each row will show `username`, `seat_number`, `time active`, and individual + controls for `[ Pause ]` and `[ Revoke ]`. + +### Phase 4: Client Logic Updates (`ui/components/sessions/SessionsScript.tsx`) + +1. **Action Handlers:** + - Implement `rotatePin(eventId)` utilizing the new backend endpoint and + updating the DOM instantly. + - Implement `expandSeats(eventId, count)` to hit the endpoint and update the + progress bar/capacity max. + - Implement `openAttendeesDrawer(eventId, slug)` to trigger the fetch of + attendees, render the HTML string into `#attendeesDrawerContent`, and slide + the drawer into view. + - Implement `toggleSessionPause(sessionId)` to trigger the pause endpoint and + visually update the attendee row (e.g., strikethrough or badge update). + +### Phase 5: Quality Gates + +1. Run `deno fmt` and `deno task lint` across all changed files. +2. Verify all API behaviors via isolated unit tests if applicable, ensuring + `deno test -A --unstable-ffi` passes. +3. Validate client-side UI script syntax using the hermetic validation present + in `ui/ui_scripts.test.ts`.