Happy path
The organizer lands on /admin/events/:eventId/setup — either via the post-create redirect from EF-008, or by clicking an event in the home dashboard's recent-events card, or from a deep link in a Slack message. Within ~5 seconds they understand what state the event is in, what's left to configure, and what the most useful single action is.
-
Land on the setup hub.
Page H1 is the event's name. Subtitle shows the date range in the event's timezone (with timezone label). Breadcrumb: Workspace › Events › <Event name> › Setup. Document title: "<Event name> · Setup".
-
Read the at-a-glance status row.
A single horizontal row immediately below the H1 shows: capacity / registered / waitlisted / status (draft / published / archived). Each is a concrete number with a color-coded label. The row never wraps to multiple lines; on narrow viewports it scrolls horizontally with the leftmost (capacity) sticky.
-
Scan the configuration sections.
The hub is organized into named sections in a deterministic order: Basics (name, dates, location, description), Access types (ticket types, capacity per tier, transferability), Email designs (invitation, confirmation, declination, reminder, post-event), Audience (invited list, group invite, import), Branding (logo, colors, fonts), Public page (Canvas-driven event site), Schedule (agenda items if a multi-session event), Reports (link out to the report center, scoped to this event). Each section is a card with a status pill and a CTA. Sections that are not applicable to this event type are hidden, not shown disabled.
-
See the next-action highlight.
A right-rail or top-of-content checklist surfaces the single most useful next action — algorithmically determined: if no access type, "Add an access type"; if access type but no email design associated, "Configure invitation email"; if those are done, "Invite guests"; if those are done, "Publish your event page". The checklist is never more than 4 items visible at a time; deeper items collapse into a "Show all checklist items" affordance.
-
Click into a section.
Each section's CTA navigates to a dedicated configuration page (
/admin/events/:eventId/access-types,/admin/events/:eventId/designs, etc.). The setup hub is the home base; sub-pages have a back link to the hub. Sub-page navigation does NOT lose the hub's scroll position (browser back returns to the hub at the same scroll offset). -
Publish.
A persistent Publish event CTA in the page header is disabled when prerequisites are missing (no access type, no public-page slug, etc.) with a tooltip naming what's needed. When all prerequisites are met, the button is primary-styled and clicking it opens a confirm modal. Publish transitions
events.statusfrom draft to published; the URL of the public page becomes live; the hub's status pill updates.
Failure modes
Event not found
Trigger: organizer clicks a stale link to an event that's been deleted (or to a different workspace's event).
The hub does not show "Event not found" generically. If the event is in this workspace and was deleted: "This event was archived on <date> by <user>." with an "Unarchive" affordance for users who have permission. If the event is in another workspace OR was hard-deleted: the no-access page (admin-shell-access trunk's failure mode applies) — the hub never reveals that the event existed in a workspace this user can't see.
Recovery: Unarchive (if applicable), or navigate back via breadcrumb.
Stale config — section data outdated relative to underlying state
Trigger: the organizer has the hub open in two tabs. In tab 1 they add an access type. In tab 2 they refresh after 30 seconds and the access-types section shows the previous count.
Each section card declares its data source (the API endpoint it derived from) and the timestamp of last fetch. After 60 seconds since last fetch, the card shows a subtle "Refresh" button. After 5 minutes, it auto-refetches when the tab gains focus. The user is never silently looking at stale data > 5 min in a focused tab.
Recovery: Manual refresh button; auto-refresh on focus.
Concurrent edit — two organizers editing the same event
Trigger: organizer A is in the access-types page, organizer B (in the same workspace) deletes the access type A is editing.
When A submits their edit, the response is 409 with code RESOURCE_GONE. A's page shows a clear message: "This access type was deleted by <organizer B> while you were editing." with options to (a) view the audit log, (b) re-create with the values A had been editing (form values preserved). A does NOT silently 404 or 500.
Recovery: Re-create from preserved form state, or accept the deletion.
Publish prerequisites missing
Trigger: organizer tries to publish but the event has no access type AND no public-page slug.
The Publish CTA is aria-disabled with a tooltip listing the unmet prerequisites in actionable language ("Add at least one access type · Set a public page URL"). Clicking the disabled button opens a popover repeating the list with deep-links to each blocker's section. The organizer is never confused why publishing didn't work.
Recovery: Click each blocker link, fix, return to the hub, click Publish.
Publish race — already-published event
Trigger: two organizers click Publish near-simultaneously. Server processes the first; second request returns 409.
The second click resolves to "<Other organizer> published this event a moment ago." with a refreshed view of the now-published state — not an error toast that vanishes. The hub state updates to reflect the publish.
Recovery: No action needed; the desired state was achieved.
Unpublish from published — destructive confirmation
Trigger: organizer clicks "Unpublish" on a live event with confirmed registrations.
Unpublish opens a destructive confirmation modal (uses ui-modal with closeOnBackdrop=false). The modal explains what unpublishing does ("the public page becomes inaccessible; existing registrations are kept; new registrations are blocked"), and requires the user to type the event name to confirm. The modal does NOT auto-focus the confirm button.
Recovery: Cancel, or type-to-confirm.
Section-data fetch fails — one card down
Trigger: the email-designs API returns 500. The other sections render fine.
Per admin-shell-access trunk's section-data-500 pattern: the failed card shows a contained error state with a Retry button. Other sections remain interactive. The next-action checklist hides items that depend on the failed section's data, and shows a soft note: "Some setup steps are unavailable while we recover." The organizer can still click into other sections.
Recovery: Per-card retry; the rest of the hub stays alive.
Edge cases
Event in the past
If event.endUtc is past, the status pill reads "Concluded" and the next-action checklist shifts to post-event tasks (review reports, archive). The Publish CTA is replaced with "View reports."
Event with zero registered guests
The hub's audience section shows an explicit empty state with the EF-style "No guests yet" message and a primary CTA to invite. NOT a generic "0" — empty is its own state, not just a number.
Multi-occurrence event series
If the event is one of a series, the hub shows a series-context strip at the top: "This is occurrence 3 of 6 in <Series name>" with prev/next occurrence navigation. Setup happens per-occurrence by default; series-wide actions are gated behind explicit "Apply to all occurrences" toggles.
Browser back from a sub-page
Returning to the hub from a sub-page restores the prior scroll position, the prior section that was expanded if any, and the prior next-action highlight. The user does not lose orientation.
Event copied from another (EF-008 path B aftermath)
The hub shows a one-time information toast: "Copied from <source event> · <N> access types, <M> email designs." with a link to view the audit log. The toast can be dismissed; doesn't reappear on subsequent visits.
Page evaluation
| Surface | Discoverability | Error UX | Layout | Orientation |
|---|---|---|---|---|
| /admin/events/:id/setup | Next-action checklist visible at viewport ≥ 1024px without scrolling. | Per-section card errors isolated; the rest of the hub stays interactive. | Two-column at ≥1024px: sections on the left, checklist on the right. Single column at <1024px with checklist above sections. | H1 is the event name. Breadcrumb visible. document.title reflects "<event name> · Setup". |
| Publish CTA | Always visible in the page header. Disabled with explanatory tooltip when prerequisites unmet. | Click-when-disabled opens a popover listing prerequisites with deep-links to fix. | Stays in the header on scroll (sticky). | Label is "Publish event" in primary state, "Publish event · conditions to meet" in disabled-with-tooltip state. |
| Section cards | Each card has a clear title, a status pill, a one-line summary of state ("3 access types"), and a primary action. | Per-card error replaces the card body with an error message + retry; card title and pill remain so the user can identify which section failed. | Cards reflow into a two-column grid at ≥1280px viewport, single column below. | Card title links to the section's sub-page; whole card is hoverable but only the CTA is clickable for clarity. |
| /admin/events/:id/setup (mobile) | Status row is the first interactive zone after H1 (no scroll required). | Section errors render compactly without breaking layout. | Sections stack vertically. Checklist appears above sections, not below. | Sub-page back-button is a clear top-left arrow. |
Acceptance signals
- URL matches
^/admin/events/[a-z0-9-]+/setup/?$. - H1 contains the event's name (matched against the API-fetched value).
- Status row is visible above the fold at 1280×800 with capacity / registered / waitlisted / status all rendered.
- The next-action checklist has at least one highlighted item OR a "concluded" state if the event is past.
- document.title matches
^.+ · Setup$. - Each visible section card has a status pill AND a CTA AND either a populated state OR an empty state.
- Publish CTA is in the page header; visibility teeth — never hidden via display:none / visibility:hidden / opacity:0 / aria-hidden when the user has permission.
- No console errors at severity ≥
warnfrom Voyage origins. - TTI under 2s on broadband.
- No layout shift > 0.1 in the first 5s.
Stable test attributes
This trunk's contract — every data-test attribute the Aperture admin code MUST expose on the event setup hub. Branches that root in event-setup inherit these without re-declaring.
Visibility teeth. Each attribute must be present AND effectively visible when the relevant state is active. Hiding without removal is a Ratchet violation.
| data-test | Where | Purpose |
|---|---|---|
setup-hub | Page /admin/events/:id/setup | Root container of the hub |
setup-hub-h1 | Inside setup-hub | The event name as page H1 |
setup-hub-breadcrumb | Inside setup-hub | Workspace › Events › ‹name› › Setup |
setup-hub-status-row | Inside setup-hub | At-a-glance row: capacity / registered / waitlisted / status |
setup-hub-status-capacity | Inside setup-hub-status-row | Capacity number + label |
setup-hub-status-registered | Inside setup-hub-status-row | Registered count |
setup-hub-status-waitlisted | Inside setup-hub-status-row | Waitlisted count |
setup-hub-status-pill | Inside setup-hub-status-row | Draft / published / concluded / archived |
setup-checklist | Inside setup-hub | Next-action checklist |
setup-checklist-item | Inside setup-checklist | Each item; multiple instances |
setup-checklist-item-highlighted | Inside setup-checklist | The single suggested next item |
setup-section-basics | Inside setup-hub | Basics card |
setup-section-access-types | Inside setup-hub | Access types card |
setup-section-designs | Inside setup-hub | Email designs card |
setup-section-audience | Inside setup-hub | Audience / invited card |
setup-section-branding | Inside setup-hub | Branding card |
setup-section-public-page | Inside setup-hub | Canvas / public page card |
setup-section-schedule | Inside setup-hub | Schedule card; absent for single-session events |
setup-section-reports | Inside setup-hub | Reports card; deep-links to report center scoped to this event |
setup-section-cta | Inside any setup-section-* | The card's primary action button |
setup-section-status-pill | Inside any setup-section-* | The card's "complete / partial / empty" status indicator |
setup-section-error | Inside any setup-section-* | Per-card error replacement; appears only when that card's data fetch failed |
publish-cta | Page header (sticky) | Publish event button; disabled with tooltip when prerequisites unmet |
publish-prereq-popover | Anchored to publish-cta | Lists unmet prerequisites with deep-links |
publish-confirm-modal | Modal portal | Confirm modal that opens when Publish is clicked AND prerequisites met; uses ui-modal |
archived-event-banner | Top of setup-hub in archived state | "This event was archived…" with unarchive affordance |
concurrent-edit-banner | Top of setup-hub | Shown after a 409 RESOURCE_GONE response on save |
copied-from-toast | Toast root | One-time after EF-008 path B redirect |
Agent test plan
This trunk's setup is consumed as a precondition by every branch story rooted in event-setup. The agent harness chains these probes after the admin-shell-access trunk's setup (since event-setup roots in admin-shell-access), then runs the branch's own probes.
Trunk setup steps
preconditions:
- trunkStoryId: admin-shell-access (auth, shell rendered)
After admin-shell setup:
1. navigate to /admin/events/${fixture.eventId}/setup
2. wait for [data-test=setup-hub]
3. assert acceptance signals: URL match, H1 has event name, status row visible above fold, checklist has highlighted item, document.title ends with " · Setup", no console errors, TTI < 2s, CLS < 0.1
Failure-mode probes
- archived-event: navigate to a known-archived event, assert archived-event-banner visible AND unarchive affordance present
- stale-config: open hub, advance clock 6 minutes, focus tab, assert auto-refetch fired
- concurrent-edit: stub a 409 RESOURCE_GONE on access-type save, assert concurrent-edit-banner with form values preserved
- publish-prereq-missing: load a fresh event with no access type, click publish-cta, assert publish-prereq-popover visible with unmet items listed AND deep-links present
- publish-race: stub publish endpoint to return 409 ALREADY_PUBLISHED, assert hub re-fetches and reflects published state without an error toast
- unpublish-destructive-confirm: click unpublish on a published event, assert ui-modal opens with closeOnBackdrop=false AND requires typing the event name AND submit is not auto-focused
- section-data-500: stub /v1/admin/events/:id/email-designs to 500, assert setup-section-error visible inside setup-section-designs AND other sections still interactive