Preconditions
Organizer can view the event guest list; fixture guests have outbox, delivered, opened, clicked, bounced, and no-email activity states.
Happy path / Lifecycle
Open guest list columns.
Last Contacted and Last Email Interaction columns appear with sortable timestamps and status pills.
Filter by email state.
Organizer filters for bounced or never-contacted guests and can take row actions such as update email or resend.
Export.
Guest export includes last notified/action fields consistently with the UI.
Failure modes
Permission denied at the right boundary
Trigger: viewer without guest communication permission opens email interaction columns.
Resolution: 403 for detail API or masked columns with no email activity leak.
Cross-tenant isolation
Trigger: tenant A requests tenant B guest email activity.
Resolution: 404, not 403, with no timestamp/status leak.
Soft-delete leaves audit trail
Trigger: organizer clears or suppresses email activity metadata.
Resolution: state is tombstoned with audit row containing prior status and actor.
Archive vs delete distinction
Trigger: event or guest is archived.
Resolution: archived activity remains readable to organizers; deleted activity is unavailable and audit remains.
Edit lock during publish
Trigger: report/export snapshot starts while activity refresh is running.
Resolution: snapshot uses a consistent cursor and UI labels stale data rather than mixing states.
Audit row on every state change
Trigger: activity status is ingested, suppressed, cleared, or corrected.
Resolution: audit row exists with provider event id or outbox id.
Two organizers concurrent
Trigger: one organizer resends while another filters/export activity.
Resolution: last-contacted updates deterministically and both sessions converge after refresh.
Undo window for destructive actions
Trigger: organizer suppresses or clears a guest from contact tracking.
Resolution: 10 second undo restores previous tracking state when no downstream send has occurred.
Incomplete event ingestion gap
Trigger: open/click/bounce webhooks are not fully ingested.
Resolution: UI exposes a gap panel and does not claim complete interaction parity.
Guest-list UI parity gap
Trigger: report/export has fields but guest list lacks columns.
Resolution: story requires visible columns and filters; missing UI remains a failing probe.
Clock ordering ambiguity
Trigger: delivered, opened, and clicked events arrive out of order.
Resolution: latest interaction is ordered by provider event time with deterministic tie-breaker.
Stable test attributes
Visibility teeth. Each attribute must be effectively visible when active.
| data-test | Where | Purpose |
|---|---|---|
email-activity-page | Guest list | Activity-enabled surface |
email-activity-table | Page | Guest table |
email-activity-filters | Toolbar | Status filters |
last-contacted-column | Table | Last contacted |
last-email-interaction-column | Table | Last interaction |
email-interaction-status-pill | Row | Status |
email-activity-export-cta | Toolbar | Export |
email-activity-refresh-cta | Toolbar | Refresh |
email-activity-suppress-cta | Row | Suppress/clear |
email-activity-undo-toast | Toast | Undo suppress |
email-activity-conflict-banner | Page | Snapshot/stale state |
email-activity-gap-panel | Page | Missing ingestion/UI parity |
email-activity-permission-denied | Page | Permission boundary |
Agent test plan
- email-activity-renders
- filter-bounced-guests
- export-includes-last-fields
- permission-denied-boundary
- cross-tenant-404
- soft-delete-audit
- archive-delete-distinction
- publish-edit-lock
- audit-row-every-change
- concurrent-organizers-converge
- destructive-undo-window
- ingestion-gap-visible
- guest-list-ui-gap
- clock-ordering-deterministic