auth-yes/tasks/complete/2026-0826.06.gem.feat.ui.sessions-and-events-final-polish-2305.ph6.md

252 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 `<h1>` 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 `<h2>Event Passes</h2>` from `SessionsPage.tsx`. The
section header is exclusively rendered inside `EventCockpitDeck.tsx` as
`<h2>Events</h2>` alongside the view toggle.
3. **Sessions Section:**
- Retain `<h2>Sessions</h2>` 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: `<h2>Events</h2>`.
- 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 `<details>` 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 ]`
- 160d: Green `[ 28d left ]`
- 212 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 ·`
<span style="background:rgba(34,197,94,0.15);color:#16a34a;padding:2px 6px;border-radius:4px;font-size:0.75rem;font-weight:600;">Active
7h 54m</span>.
- **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: `<h2>Delegate Session</h2>` 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
```