12 KiB
12 KiB
TASK METADATA
- Target Files:
server/routes/events.tsui/components/SessionsPage.tsxui/components/sessions/EventCockpitDeck.tsxui/components/sessions/WorkshopDrawer.tsxui/components/sessions/EventGuestsDrawer.tsxui/components/sessions/SessionDeck.tsxui/components/sessions/SessionTable.tsxui/components/sessions/SessionsScript.tsxserver/tests/events.test.tsui/ui_scripts.test.ts
- Core Objective: Implement Phase 6 Final Polish & Hardening:
- Unify nomenclature & IA across pages and drawers (
Sessions & Events,Delegate Session,Events,Sessions). - Implement universal ingress credential rotation (rotates both 6-digit PIN and event slug suffix simultaneously).
- Fix expired event extend math bug using
GREATEST(expires_at, NOW()) + interval. - Replace broken compact mode with strictly bounded, 2-Row Compact Cards with inline copy pills and zero text/border collisions.
- Implement standardized natural
ExpiryBadgeformat ([Date] · [Time] · [Urgency Badge]) and symmetrical inverted start-time telemetry in the Guest Drawer. - Upgrade segmented view mode toggles and WAI-ARIA delegation tabs with high-contrast active states.
- Reset drawer state machine on open and replace verbose/mock buttons with
clean
[ OK ].
- Unify nomenclature & IA across pages and drawers (
- Dependencies: None.
- Additional Important Notes: Must remain 100% pure React-free Hono SSR JSX.
All client interactions in
SessionsScript.tsxmust use native vanilla JavaScript DOM APIs.
2. Architectural Considerations & Risks
-
Risks:
- Ingress Rotation Leakage & Active Session Disruption: When rotating
event ingress credentials (
pin_codeandslug), 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. UsingGREATEST(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 Sessiondefault tab) rather than leaving the stale success screen visible.
- Ingress Rotation Leakage & Active Session Disruption: When rotating
event ingress credentials (
-
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)
-
Universal Ingress Rotation (
POST /api/events/:id/rotate-pin):- Update endpoint to rotate BOTH
pin_codeAND the random slug suffix: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.
- Update endpoint to rotate BOTH
-
Resilient Event Extension Math (
POST /api/events/:id/extend):- Fix SQL to calculate new expiry from
GREATEST(expires_at, NOW()):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
- Fix SQL to calculate new expiry from
-
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.
- Ensure query returns
Phase 2: Page Hierarchy, Nomenclature & Heading Cleanup (SessionsPage.tsx)
- Page Title:
- Set top
<h1>inSessionsPage.tsxtoSessions & Eventswith subtitle "Manage logins, mint 1:1 delegated tokens, or launch multi-claim workshop events."
- Set top
- Remove Duplicate Headings:
- Remove redundant
<h2>Event Passes</h2>fromSessionsPage.tsx. The section header is exclusively rendered insideEventCockpitDeck.tsxas<h2>Events</h2>alongside the view toggle.
- Remove redundant
- Sessions Section:
- Retain
<h2>Sessions</h2>heading with subtitle "Direct device logins, passkey authentications, and delegated agent tokens."
- Retain
Phase 3: Bounded 2-Row Compact Cards & Grid Card Polish (EventCockpitDeck.tsx)
-
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".
- Active style:
- Header:
-
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 ].
- Left: Event title with ellipsis
(
- 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).
- Left (Handoff Pills):
- Delete Mock Code: Completely remove the
[ ▸ Details ]button and itsalert(...)placeholder.
- Container: Strictly bounded flex/grid (~70px height),
-
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;).
- Single-line snippet when collapsed with
- Standardize
[ 🔄 Rotate Credentials ]button to invoke multi-field rotation.
- Replace detached CLI
Phase 4: Standardized Natural Expiry & Start-Time Telemetry (SessionsScript.tsx & Drawers)
-
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 ](no142d) >1yr: Green[ 1.2 yrs left ](no420d)- Expired: Red
[ Expired ]
- $<1$h: Amber
- Tooltip:
title="${fullISODate}"on hover/long-press. - Format:
[Date] · [Time] · [ Colored Urgency Badge ].
- Natural Date String:
-
Inverted Symmetrical Start-Time Formatter (
formatNaturalJoinTime(createdAt)):- In
EventGuestsDrawer.tsx, render:- Header: Bold
Seat #[N]+🟢 Active / ⏸️ Pausedbadge +[ ⏸️ 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_seatvisual text (keep in tooltip only).
- Header: Bold
- In
Phase 5: Delegation Drawer Overhaul (WorkshopDrawer.tsx & SessionsScript.tsx)
-
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".
- Tab 1 (
- Drawer Header:
-
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 ].
- Standardize all 3 copy buttons to uniform
-
State Machine Reset on Open:
- In
SessionsScript.tsx:openDelegateDrawer():- Reset
#eventCreateStatetodisplay: blockand#eventHandoffStatetodisplay: none. - Clear form inputs (
eventName,eventSlug,eventPinCode, etc.). - Reset tab selection to
Single Session.
- Reset
- In
4. Verification Plan
Automated Tests
- Universal Credential Rotation Test (
server/tests/events.test.ts):- Create event pass -> Call
POST /api/events/:id/rotate-pin. - Verify response returns new
pinCodeAND newslug. - Verify old PIN and old slug are rejected at
/api/events/joinwith 404. - Verify new PIN and new slug succeed at
/api/events/join. - Verify existing authenticated guest sessions remain valid.
- Create event pass -> Call
- 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/extendwithextendHours: 1. - Verify
expires_at > NOW()(approximatelyNOW() + 1 hour).
- Insert expired event with
- UI Script & Formatter Tests (
ui/ui_scripts.test.ts):- Unit test
formatNaturalExpiryacross all time horizons (<1h, <24h, 28d, 142d ->4.5 mos, 420d ->1.2 yrs, expired). - Test
formatNaturalJoinTimeinverted duration math. - Test drawer state machine reset logic.
- Unit test
Quality Gate Commands
deno fmt --check
deno task lint
deno task check
deno test -A --no-check server/tests/events.test.ts ui/ui_scripts.test.ts