← All stories

BRANCH · ef-040-custom-registration-questions

Custom registration questions

EF-040Persona: OrganizerRoots in: event-setup

Custom registration questions let organizers collect typed answers only when relevant to a guest access type. Matrix evidence: guest patch accepts answer rows at workers/events/src/routes/guests.ts:65 and registration accepts custom fields at workers/events/src/routes/registrations.ts:37, but builder, validation, scoping, and public rendering parity are still partial.

Preconditions

Organizer can edit event setup; fixture includes General Admission and VIP access types plus a published public registration form.

Happy path / Lifecycle

  1. Open registration questions from Event Setup.

    The question list shows draft and published state side by side.

  2. Create a required VIP date question.

    The builder stores label, answer type, required flag, access-type scope, and display order.

  3. Preview and publish.

    Preview shows draft schema; publish promotes a versioned question set for public registration.

Failure modes

Permission denied at the right boundary

Trigger: viewer/support attempts organizer-only operation.

Resolution: the write request returns 403, the editable surface remains closed or read-only, and the response does not leak hidden guest, event, or tenant fields.

Cross-tenant isolation

Trigger: tenant-A user guesses tenant-B resource id.

Resolution: the server returns 404 instead of 403, masks existence, and the UI renders a generic not-found state.

Soft-delete leaves audit trail

Trigger: organizer removes or deactivates the configured object.

Resolution: the row is marked inactive/deleted with actor, timestamp, and prior state preserved in audit.

Archive vs delete distinction

Trigger: organizer chooses between reversible archive and destructive delete.

Resolution: archive stays reversible and copy/export labels it archived; delete requires separate destructive confirmation and changes copy behavior.

Edit lock during publish

Trigger: publish snapshot begins while an edit is open.

Resolution: publish wins; stale save receives a deterministic conflict modal and does not mutate the published snapshot silently.

Audit log row written on every state change

Trigger: organizer saves any state transition.

Resolution: each state mutation writes an audit row with actor, timestamp, entity id, and before/after payload.

Two organizers concurrent

Trigger: two organizers edit the same state from stale versions.

Resolution: the second save gets conflict UI, both sessions refresh to the same final state, and there is no silent overwrite.

Undo window for destructive actions

Trigger: organizer deletes, cancels, or clears the object.

Resolution: a visible undo affordance lasts 10 seconds and restores the exact prior state when used.

Answer-type validation per registered type

Trigger: organizer configures text, select, multi-select, or date and enters incompatible answers.

Resolution: invalid answer shapes are blocked before publish and rejected server-side with field-level errors.

Access-type scoped questions

Trigger: question is scoped to VIP access type and a GA guest opens registration.

Resolution: the question is absent for non-matching access types and present for matching access types without leaking hidden labels.

Preview vs published divergence

Trigger: organizer edits draft questions after public form is already published.

Resolution: preview shows draft, public form stays on published schema until republish; UI labels the divergence explicitly.

Question reorder concurrent edit

Trigger: two organizers reorder the same question list.

Resolution: ordering is versioned and stale reorder opens conflict modal instead of silently shuffling published questions.

Parity gap: builder and public rendering missing

Trigger: matrix marks registration questions partial.

Resolution: the branch carries a visible gap panel until question builder, type validation, scoping, and public rendering parity are implemented.

Stable test attributes

Visibility teeth. Each attribute must be effectively visible when active and must match the agent probes.

data-testWherePurpose
registration-question-question-listsurfacequestion list
registration-question-buildersurfacebuilder
registration-question-type-pickersurfacetype picker
registration-question-scope-pickersurfacescope picker
registration-question-preview-panelsurfacepreview panel
registration-question-publish-ctasurfacepublish cta
registration-question-save-ctasurfacesave cta
registration-question-validation-errorsurfacevalidation error
registration-question-archive-ctasurfacearchive cta
registration-question-delete-ctasurfacedelete cta
registration-question-undo-toastsurfaceundo toast
registration-question-conflict-modalsurfaceconflict modal
registration-question-gap-panelsurfacegap panel
registration-question-guest-hidden-questionsurfaceguest hidden question

Agent test plan

- custom-registration-questions-opens
- custom-registration-questions-saves
- custom-registration-questions-audit-visible
- permission-denied-boundary
- cross-tenant-404
- soft-delete-audit
- archive-delete-distinction
- publish-edit-lock
- audit-row-every-change
- concurrent-organizers-conflict
- destructive-undo-window
- answer-type-validation
- access-type-scoped-question
- preview-published-divergence
- question-reorder-concurrent-edit
- registration-question-gap-probe
- evaluate-custom-registration-questions