← All stories

BRANCH · ef-064-leave-behind-invitations

Leave-Behind Invitations

EF-064 Persona: Event-day staff + remaining-guest Stage: Day-of (split-party check-in) Roots in: native-app-shell Matrix=Absent — ships as gap probe

Split-party check-in: a guest arrives with multiple invitations (some for friends not yet arrived). Staff checks in the present guest while leaving the remaining invitations active for the friends to use later. Tracks the remaining ledger per group. Matrix=Absent — this story describes the desired contract.

Happy path (desired)

  1. Staff scans guest QR for a multi-invitation group.

    Confirmation screen shows: "[Guest name], party of 4 — 1 arriving, 3 invitations remaining." Inline picker: how many checking in now? Defaults to 1.

  2. Confirm checks in N of M.

    Server marks N attendees confirmed-and-checked-in. Remaining (M-N) invitations stay active. Audit log row: partial_check_in with N + M.

  3. Friend arrives later with the same group QR.

    Friend re-scans. Confirmation screen: "Party of 4 — 1 already checked in, 3 remaining." Same picker. Continues until all are checked in.

  4. Group fully checked in — QR retires.

    Once 4/4 checked in, scanning the QR again shows "All party members checked in." No further action available.

Failure modes (desired contract)

Parity gap — feature absent

Trigger: matrix=Absent.

Visible "EF-064 leave-behind invitations not yet implemented" panel on the access-type config admin page. Staff-side: scanning a multi-invitation QR currently triggers single-check-in only (no party-of-N logic). Until the feature ships, the gap-panel asserts the absence.

Group QR overscan

Trigger: staff tries to check in 5 of a party-of-4.

Picker max-value enforced client-side AND server-side. Submit returns 422 EXCEEDS_PARTY_SIZE. Harness: 4-person party, attempt 5, banner visible.

Two-staff race on same group QR

Trigger: party of 4 — 2 staff devices both trying to check in different counts simultaneously.

Server-side row-level lock on the group's remaining counter. First commit wins; second sees decremented remaining count + adjusts. Harness: 2 devices submit (3 + 2) within 100ms, server final state is one consistent value (e.g., first lands as 3-checked, second sees 1 remaining + commits 1, total 4).

Group QR with 0 remaining

Trigger: scanning a fully-checked-in group's QR.

Confirmation screen replaced with "All party members checked in" + dismissable. No action available. Harness: stub fully-checked group, scan, message visible.

Audit per partial check-in

Trigger: party-of-4 checks in 2-then-1-then-1 across 3 staff scans.

3 audit rows: partial_check_in(2/4 → 4/4), partial_check_in(3/4 → 4/4), partial_check_in(4/4 → 4/4 + group-complete). Harness: 3 scans, 3 audit rows reconstruct full timeline.

Group revoked mid-event

Trigger: organizer revokes a group invitation while 1 of 4 has already checked in.

Already-checked-in attendees stay confirmed (they're already inside). Remaining invitations transition revoked. Future scans show "Group invitation revoked — contact organizer." Harness: revoke after 1/4 check-in, scan, revoked message visible.

Offline partial check-in

Trigger: device offline; staff partial-checks-in 2/4.

Local row records partial check-in with idempotency-key (deviceId + groupId + scanTimestamp + count). Replay preserves the count. Harness: offline 2/4 scan, reconnect, server matches local state.

Anonymous remaining attendees

Trigger: party-of-4 only has 1 named attendee (the inviter); 3 are unnamed +1s.

Check-in flow handles named (look up by name) AND unnamed (just decrement count) attendees. Audit row records "anonymous +1" for unnamed check-ins. Harness: party with 3 unnamed, full check-in completes without requiring names.

Capacity tracking on group invitations

Trigger: event has capacity 100; current confirmed = 96; group QR scanned for party of 5.

Server checks remaining capacity vs requested check-in count. If insufficient (96 + 5 > 100), surface "Only 4 spots remaining — check in 4 of 5?" Harness: stub 96/100, scan party-of-5, banner offers 4-of-5 partial.

Cross-event group QR

Trigger: group QR from a different event scanned at this event.

Same anti-probing as EF-061 — generic "QR is for a different event" banner, no leak. Harness: cross-event group QR, generic banner.

Stable test attributes

leave-behind-gap-panelAccess-type config + check-in tabVisible until feature ships
group-checkin-confirmationAfter group QR scanShows party-of-N + remaining count
group-checkin-count-pickerIn confirmationHow many checking in now
group-fully-checked-in-messageIf 0 remainingDismissable info
group-exceeds-party-banner422 EXCEEDS_PARTY_SIZE"Cannot exceed party size"
group-revoked-bannerIf group revoked"Contact organizer"
group-capacity-partial-offerIf capacity insufficient"Check in 4 of 5"

Agent test plan

Probe list
- gap-panel-visible: matrix=Absent, panel visible
- (when shipped) group-checkin-confirmation: scan, picker shows party-of-N
- (when shipped) overscan-422: party-of-4, attempt 5, EXCEEDS_PARTY_SIZE
- (when shipped) two-staff-race-resolved: 2 devices, total never exceeds party size
- (when shipped) zero-remaining-info: fully-checked, scan, info visible
- (when shipped) audit-per-partial: 3 partial check-ins, 3 audit rows
- (when shipped) revoked-mid-event: revoke after partial, future scans show revoked
- (when shipped) offline-partial-replays: offline + reconnect, local-vs-server match
- (when shipped) anonymous-attendees: party with unnamed +1s, check-in completes
- (when shipped) capacity-partial-offer: stub low capacity, offer partial
- cross-event-group-qr: anti-probing banner