diff --git a/tasks/new/2026-0826.06.gem.feat.ui.sessions-and-events-final-polish-2305.ph6.md b/tasks/new/2026-0826.06.gem.feat.ui.sessions-and-events-final-polish-2305.ph6.md new file mode 100644 index 0000000..0c8b084 --- /dev/null +++ b/tasks/new/2026-0826.06.gem.feat.ui.sessions-and-events-final-polish-2305.ph6.md @@ -0,0 +1,186 @@ +# TASK METADATA + +- **Target Files:** + - `server/routes/events.ts` + - `ui/components/SessionsPage.tsx` + - `ui/components/sessions/EventCockpitDeck.tsx` + - `ui/components/sessions/WorkshopDrawer.tsx` + - `ui/components/sessions/EventGuestsDrawer.tsx` + - `ui/components/sessions/SessionDeck.tsx` + - `ui/components/sessions/SessionTable.tsx` + - `ui/components/sessions/SessionsScript.tsx` + - `server/tests/events.test.ts` + - `ui/ui_scripts.test.ts` +- **Core Objective:** Implement Phase 6 Final Polish & Hardening: + 1. Unify nomenclature & IA across pages and drawers (`Sessions & Events`, `Delegate Session`, `Events`, `Sessions`). + 2. Implement universal ingress credential rotation (rotates both 6-digit PIN and event slug suffix simultaneously). + 3. Fix expired event extend math bug using `GREATEST(expires_at, NOW()) + interval`. + 4. Replace broken compact mode with strictly bounded, 2-Row Compact Cards with inline copy pills and zero text/border collisions. + 5. Implement standardized natural `ExpiryBadge` format (`[Date] Β· [Time] Β· [Urgency Badge]`) and symmetrical inverted start-time telemetry in the Guest Drawer. + 6. Upgrade segmented view mode toggles and WAI-ARIA delegation tabs with high-contrast active states. + 7. Reset drawer state machine on open and replace verbose/mock buttons with clean `[ OK ]`. +- **Dependencies:** None. +- **Additional Important Notes:** Must remain 100% pure React-free Hono SSR JSX. All client interactions in `SessionsScript.tsx` must use native vanilla JavaScript DOM APIs. + +--- + +## 2. Architectural Considerations & Risks + +- **Risks:** + - **Ingress Rotation Leakage & Active Session Disruption:** When rotating event ingress credentials (`pin_code` and `slug`), existing active guest sessions must remain valid and uninterrupted. Only subsequent unauthenticated join attempts using the old PIN, old Direct Link, or old CLI command must be rejected (404 / Invalid Code). + - **Expired Event Extending Edge Cases:** When an organizer extends an event that expired in the past, `expires_at + interval '1 hour'` would leave the expiry in the past. Using `GREATEST(expires_at, NOW()) + interval '${extendHours} hours'` guarantees the new expiration time is set relative to the current moment. + - **DOM Boundary & Overflow Bleed:** The previous compact mode forced elements into a single unconstrained horizontal flex line, causing text collisions and input boxes bleeding off the desktop screen. The new 2-Row Compact Card must enforce strict CSS bounding (`box-sizing: border-box; max-width: 100%; overflow: hidden; text-overflow: ellipsis;`). + - **State Machine Staleness:** Reopening the delegation drawer must unconditionally reset the form back to State 1 (clean creation form with `Single Session` default tab) rather than leaving the stale success screen visible. + +- **Alternatives:** + - *Single-Line vs. 2-Row Compact Cards:* Single-line cards inevitably drop essential copy actions or clip text on viewports under 1200px. A structured 2-Row Compact Card (~70px height) preserves 100% of copy handoffs (PIN, Link, CLI) and key metrics (Seats, Status, Expiry) while preventing all layout collisions. + +--- + +## 3. Proposed Implementation + +### Phase 1: Universal Ingress Credential Rotation & Backend Math Fix (`server/routes/events.ts`) + +1. **Universal Ingress Rotation (`POST /api/events/:id/rotate-pin`):** + - Update endpoint to rotate **BOTH** `pin_code` AND the random slug suffix: + ```typescript + const randPin = Math.floor(100000 + Math.random() * 900000).toString(); + const newPinCode = randPin.substring(0, 3) + "-" + randPin.substring(3); + + // Extract base slug prefix and generate fresh random 4-char suffix + const event = await sqlWrapper.sql`SELECT slug, name FROM event_passes WHERE id = ${eventId}...`; + const baseSlug = event[0].slug.replace(/-[a-z0-9]{4}$/, ""); + const newSuffix = Math.random().toString(36).substring(2, 6); + const newSlug = `${baseSlug}-${newSuffix}`; + ``` + - Update database: `UPDATE event_passes SET pin_code = ${newPinCode}, slug = ${newSlug} WHERE id = ${eventId}...` + - Audit log `event_ingress_rotated`. + - Return `{ success: true, pinCode: newPinCode, slug: newSlug }`. + - Note: Existing active attendee sessions (`username LIKE 'guest_...'`) authenticate via session cookies/Valkey tokens and are unaffected. + +2. **Resilient Event Extension Math (`POST /api/events/:id/extend`):** + - Fix SQL to calculate new expiry from `GREATEST(expires_at, NOW())`: + ```sql + UPDATE event_passes + SET expires_at = GREATEST(expires_at, NOW()) + interval '${extendHours} hours' + WHERE id = ${eventId} AND (created_by = ${user.userId} OR ${isAdmin}) AND is_active = TRUE + RETURNING slug, expires_at + ``` + +3. **Guest Attendee Telemetry (`GET /api/events/:id/attendees`):** + - Ensure query returns `s.id, s.label, s.is_paused, s.created_at, s.expires_at, s.last_activity_at, s.last_activity_action, u.username, u.display_name`. + +--- + +### Phase 2: Page Hierarchy, Nomenclature & Heading Cleanup (`SessionsPage.tsx`) + +1. **Page Title:** + - Set top `

` in `SessionsPage.tsx` to **`Sessions & Events`** with subtitle *"Manage logins, mint 1:1 delegated tokens, or launch multi-claim workshop events."* +2. **Remove Duplicate Headings:** + - Remove redundant `

Event Passes

` from `SessionsPage.tsx`. The section header is exclusively rendered inside `EventCockpitDeck.tsx` as `

Events

` alongside the view toggle. +3. **Sessions Section:** + - Retain `

Sessions

` heading with subtitle *"Direct device logins, passkey authentications, and delegated agent tokens."* + +--- + +### Phase 3: Bounded 2-Row Compact Cards & Grid Card Polish (`EventCockpitDeck.tsx`) + +1. **Section Header & High-Contrast View Toggle:** + - Header: `

Events

`. + - Toggle buttons (`[ πŸ—‚οΈ Grid ]` and `[ πŸ“‹ Compact ]`): + - Active style: `background: var(--primary); color: #ffffff; font-weight: 700; box-shadow: 0 1px 3px rgba(0,0,0,0.3); border-radius: var(--radius-sm);` + - Inactive style: `background: transparent; color: var(--text-muted); opacity: 0.75;` + - Include `aria-pressed="true/false"`. + +2. **2-Row Compact Card Layout (`.compact-view .event-card`):** + - Container: Strictly bounded flex/grid (~70px height), `box-sizing: border-box; overflow: hidden; padding: 0.75rem 1rem;`. + - **Row 1 (Metadata Header):** + - Left: Event title with ellipsis (`max-width: 280px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 700; color: var(--text-primary);`). + - Center/Right: Subtle dot `Β·` + `renderExpiryPill(expiresAt)` + Subtle dot `Β·` + `[ N/Max Seats ]` + `[ Status Badge ]`. + - **Row 2 (1-Click Handoffs & Action Pinned Right):** + - Left (Handoff Pills): `[ PIN: 241-881 (Copy) ]`, `[ Link (Copy) ]`, `[ CLI (Copy) ]` using compact inline pills (`padding: 2px 6px; font-size: 0.75rem;`). + - Right (Action Buttons): Compact `[ πŸ‘₯ Guests (N) ]` and `[ πŸ”„ +1h ]` (or `[ πŸ”„ Reopen (+1h) ]` if expired). + - **Delete Mock Code:** Completely remove the `[ β–Έ Details ]` button and its `alert(...)` placeholder. + +3. **Grid Card Polish:** + - Replace detached CLI `
` box with a unified **Integrated Expanding CLI Component**: + - Single-line snippet when collapsed with `[ Copy ]` and `[ β–Ύ ]` toggle. + - Expands downward into multiline highlighted command block on toggle click. + - Fix double arrow marker bug (`list-style: none;`). + - Standardize `[ πŸ”„ Rotate Credentials ]` button to invoke multi-field rotation. + +--- + +### Phase 4: Standardized Natural Expiry & Start-Time Telemetry (`SessionsScript.tsx` & Drawers) + +1. **Natural Expiry Formatter Helper (`formatNaturalExpiry(expiresAt)`):** + - Natural Date String: `Today` (if $<24$h), `Tomorrow` (if $<48$h), `MMM D` (if same year), `MMM D, YYYY` (if different year). + - Exact Time: `h:mm A` (e.g. `10:39 PM`). + - Scaled Urgency Badge: + - $<1$h: Amber `[ 45m left ]` + - $<24$h: Amber/Green `[ 2h 45m left ]` + - 1–60d: Green `[ 28d left ]` + - 2–12 mos: Green `[ 4.5 mos left ]` (no `142d`) + - $>1$ yr: Green `[ 1.2 yrs left ]` (no `420d`) + - Expired: Red `[ Expired ]` + - Tooltip: `title="${fullISODate}"` on hover/long-press. + - Format: `[Date] Β· [Time] Β· [ Colored Urgency Badge ]`. + +2. **Inverted Symmetrical Start-Time Formatter (`formatNaturalJoinTime(createdAt)`):** + - In `EventGuestsDrawer.tsx`, render: + - **Header:** Bold `Seat #[N]` + `🟒 Active / ⏸️ Paused` badge + `[ ⏸️ Pause ]` + `[ πŸ—‘οΈ Revoke ]`. + - **Line 1:** `Joined Today Β· 2:15 PM Β· ` Active 7h 54m. + - **Line 2:** `Last Action: ${lastAction || 'ForwardAuth Ingress'} Β· ${timeAgo} Β· πŸ’» Web` (or `πŸ“Ÿ CLI`). + - Remove redundant `guest_slug_seat` visual text (keep in tooltip only). + +--- + +### Phase 5: Delegation Drawer Overhaul (`WorkshopDrawer.tsx` & `SessionsScript.tsx`) + +1. **Drawer Nomenclature & WAI-ARIA High-Contrast Tabs:** + - Drawer Header: `

Delegate Session

` with subtitle *"Mint a 1:1 delegated token or launch a multi-seat workshop event."* + - Tabs (`role="tablist"`): + - **Tab 1 (`role="tab"`):** `Single Session` + - Subtitle hint: *For agents, CI/CD, or 1:1 delegation* + - **Tab 2 (`role="tab"`):** `Multi-Claim Event` + - Subtitle hint: *For workshops, teams & guest pools* + - High-Contrast Active State: Solid `var(--primary)` background with white text (`#ffffff`), `aria-selected="true"`. + - Inactive State: Translucent muted background (`opacity: 0.75`), `aria-selected="false"`. + +2. **Handoff State & Button Minimalist Polish:** + - Standardize all 3 copy buttons to uniform `btn-outline` (`Copy PIN`, `Copy URL`, `Copy 1-Liner`). + - Replace `"Dismiss"` with a clean, minimal button **`[ OK ]`**. + +3. **State Machine Reset on Open:** + - In `SessionsScript.tsx:openDelegateDrawer()`: + - Reset `#eventCreateState` to `display: block` and `#eventHandoffState` to `display: none`. + - Clear form inputs (`eventName`, `eventSlug`, `eventPinCode`, etc.). + - Reset tab selection to `Single Session`. + +--- + +## 4. Verification Plan + +### Automated Tests +1. **Universal Credential Rotation Test (`server/tests/events.test.ts`):** + - Create event pass -> Call `POST /api/events/:id/rotate-pin`. + - Verify response returns new `pinCode` AND new `slug`. + - Verify old PIN and old slug are rejected at `/api/events/join` with 404. + - Verify new PIN and new slug succeed at `/api/events/join`. + - Verify existing authenticated guest sessions remain valid. +2. **Expired Event Extend Math Test (`server/tests/events.test.ts`):** + - Insert expired event with `expires_at = NOW() - interval '3 hours'`. + - Call `POST /api/events/:id/extend` with `extendHours: 1`. + - Verify `expires_at > NOW()` (approximately `NOW() + 1 hour`). +3. **UI Script & Formatter Tests (`ui/ui_scripts.test.ts`):** + - Unit test `formatNaturalExpiry` across all time horizons (<1h, <24h, 28d, 142d -> `4.5 mos`, 420d -> `1.2 yrs`, expired). + - Test `formatNaturalJoinTime` inverted duration math. + - Test drawer state machine reset logic. + +### Quality Gate Commands +```bash +deno fmt --check +deno task lint +deno task check +deno test -A --no-check server/tests/events.test.ts ui/ui_scripts.test.ts +```