auth-yes/tasks/new/2026-0827.04.gem.feat.ui.expired-events-archive-and-guest-telemetry-1042.md

110 lines
4.8 KiB
Markdown

# TASK METADATA
- **Target Files:**
- `server/routes/events.ts`
- `ui/components/sessions/EventCockpitDeck.tsx`
- `ui/components/sessions/EventGuestsDrawer.tsx`
- `ui/components/sessions/SessionsScript.tsx`
- `ui/components/SessionsPage.tsx`
- `server/tests/events.test.ts`
- `ui/ui_scripts.test.ts`
- **Core Objective:** Clean up the Sessions & Events dashboard by archiving
expired passes into a collapsible `<details>` accordion with a 1-click
`[ 🔄 Reopen (+1h) ]` action, and enrich the Guest Drawer with real-time
attendee telemetry and activity logs.
- **Dependencies:** None.
- **Additional Important Notes:**
- Must remain 100% pure React-free Hono SSR JSX.
- Distinguish between naturally expired events (`expires_at < NOW()`,
`is_active = TRUE`, reopenable) vs manually terminated events
(`is_active = FALSE`, permanent archive).
---
## 2. Architectural Considerations & Risks
### Risks
1. **Dashboard Clutter & Information Overload:** As organizers host dozens of
workshops over weeks, active and dead events pile up on the main screen,
pushing active management tools below the fold.
- _Mitigation:_ Partition the SQL query and SSR view into two decks:
`activePasses` (top priority grid/compact cards) and `expiredPasses`
(collapsed under a clean `<details>` drawer showing count
`[ 📁 Expired Passes (N) ]`).
2. **Re-opening State Ambiguity:** If an organizer re-opens an event that
expired 3 days ago, adding 1 hour via `expires_at + interval '1h'` leaves it
in the past.
- _Mitigation:_ Use
`SET expires_at = GREATEST(expires_at, NOW()) + interval '1 hour'` in
PostgreSQL to guarantee the event opens immediately for 60 minutes from the
moment clicked.
3. **Telemetry Performance in Large Guest Drawers:** Querying full session
activity logs for 100+ attendees could slow down
`GET /api/events/:id/attendees`.
- _Mitigation:_ Select only `last_activity_action`, `last_activity_at`, and
`is_agent` joined directly on `sessions` indexed by `user_id`.
### Alternatives
- **Permanent Deletion of Expired Events:** We considered automatically dropping
expired events from the database. However, organizers frequently need to audit
past workshop attendees or re-open events that ran over schedule. Keeping
expired passes in an archived state maintains audit integrity.
---
## 3. Proposed Implementation
### Phase 1: Split Active vs. Expired Events Query (`ui/components/SessionsPage.tsx`)
1. Update `event_passes` query in `SessionsPage.tsx`:
- Split passes into
`activeEvents = eventPasses.filter(e => e.is_active && (!e.expires_at || new Date(e.expires_at) > new Date()))`.
- `expiredEvents = eventPasses.filter(e => !e.is_active || (e.expires_at && new Date(e.expires_at) <= new Date()))`.
2. Pass both arrays into `EventCockpitDeck.tsx`.
### Phase 2: Collapsible Expired Deck & 1-Click Reopen (`ui/components/sessions/EventCockpitDeck.tsx`)
1. **Active Deck:**
- Render active events in standard Grid or 2-Row Compact view.
2. **Archived Deck (`[ 📁 Expired Passes (N) ]`):**
- Render a native `<details>` element below the active deck.
- For each expired pass with `is_active = TRUE`, display an amber/gray card
with an instant `[ 🔄 Reopen (+1h) ]` button.
- For manually ended events (`is_active = FALSE`), show a muted
`[ Terminated ]` badge without a reopen button.
3. **Client Script Handler (`SessionsScript.tsx`):**
- Add `reopenEvent(eventId)` calling `POST /api/events/:id/extend` with
`{ extendHours: 1 }`.
- Dynamically transition the card or reload to place it back into the active
deck.
### Phase 3: Rich Attendee Telemetry in Guest Drawer (`ui/components/sessions/EventGuestsDrawer.tsx`)
1. **Backend Payload Enrichment (`server/routes/events.ts`):**
- In `GET /api/events/:id/attendees`, select:
- `s.last_activity_at`
- `s.last_activity_action` (e.g. `ForwardAuth Ingress (ed-droid)`,
`API Token Read`)
- `s.is_agent`
2. **UI Card Formatting:**
- On each attendee card in the drawer, render:
- **Header:** `Seat #[N]` + `🟢 Active / ⏸️ Paused` badge + inline
`[ 🗑️ Revoke ]`.
- **Line 1 (Inverted Join Time):**
`Joined Today · 2:15 PM · [ Active 1h 45m ]`.
- **Line 2 (Action Telemetry):**
`Last Action: ForwardAuth (ed-droid) · 2m ago · 💻 Web` (with device icon
or agent robot emoji if `is_agent = true`).
### Phase 4: Quality Gates & Verification
1. Unit tests in `server/tests/events.test.ts`:
- Test reopening naturally expired events with
`GREATEST(expires_at, NOW()) + 1h`.
- Test telemetry fields in `/attendees` endpoint.
2. UI syntax tests in `ui/ui_scripts.test.ts`.
3. Run `deno fmt`, `deno task lint`, `deno task check`, and
`deno test -A --no-check`.