← All stories

BRANCH · ef-067-wireless-badge-printing

Wireless Badge Printing

EF-067 Persona: Event-day staff Stage: Day-of (native + Bluetooth) Roots in: native-app-shell Matrix=Absent — ships as gap probe

Bluetooth-paired thermal badge printer prints a guest's badge at check-in. Auto-print on confirm OR manual reprint. Badge template configurable per event (name + company + access type pill + optional QR for re-scan). Matrix=Absent — story describes the desired contract.

Happy path (desired)

  1. Staff pairs printer.

    Settings → Printer. App scans Bluetooth devices for supported printers (Brother QL-820NWB, Zebra ZQ620, etc.). Tap to pair, OS-level pairing flow, app remembers across launches.

  2. Organizer designs badge template.

    Admin → Event Settings → Badge Template. Drag-drop placement of: guest name, company, access type pill (color from registry), optional QR (for re-scan), event branding. Preview at actual size. Save.

  3. Auto-print on check-in confirm.

    When check-in confirms (EF-061), the device's paired printer automatically prints the badge. Confirmation screen shows "✓ Checked in. Printing badge..." then "✓ Badge printed" once the printer confirms.

  4. Manual reprint.

    From a previously-checked-in guest's row, tap "Reprint badge." Same flow without the check-in step. Audit log row recorded.

Failure modes (desired contract)

Parity gap — feature absent

Trigger: matrix=Absent.

Visible "EF-067 wireless badge printing not yet implemented" panel on Settings → Printer. Without the feature, no Print tab is rendered (per native-app-shell trunk). Until shipped, the gap-panel asserts the absence.

Printer not paired

Trigger: staff attempts to print but no printer is paired.

Confirmation screen shows "Badge ready — pair printer in Settings to print." Check-in still succeeds (printing is best-effort). Harness: check-in without paired printer, message visible, audit log shows badge_pending.

Printer connection lost mid-print

Trigger: BT signal drops between confirm and printer-confirm.

Print job queued locally, retries on next reconnect. Confirmation says "Badge queued — will print on reconnect." Check-in not blocked. Harness: stub BT loss, queue persists, retry on reconnect.

Out of paper / ribbon

Trigger: printer reports paper-out / ribbon-empty.

UI shows "Printer is out of paper. Replace and tap Retry." Specific error from printer surface. Audit log row: badge_print_failed with reason. Harness: stub paper-out, banner visible, reload + retry succeeds.

Two-staff race on same guest's badge

Trigger: 2 staff devices both check-in same guest concurrently; both auto-print to their respective paired printers.

Idempotency: check-in is single (per EF-061). Printing is local to each device — both badges print on respective printers. The guest gets 2 badges. Acceptable behavior for the safety of physical-pickup at the door. Harness: 2-device test, both badges print, server has 1 check-in row.

Reprint requires permission

Trigger: read-only role tries to reprint.

Reprint requires checkins:reprint ability. Hidden for read-only roles. Harness: stub read-only, button hidden, server POST 403.

Reprint creates audit row

Trigger: staff reprints a guest's badge.

Audit log row: badge_reprinted with operator + guest + timestamp. Tracks how many times a guest has reprinted (potential lost-badge pattern). Harness: reprint, audit row exists.

Badge template change preserves prior prints

Trigger: organizer changes badge template mid-event.

Already-printed badges stay valid. New check-ins use the new template. Reprints use the CURRENT template (so a reprint after a template change reflects the new design). Harness: stub template-change, prior prints unaffected, reprint uses new template.

Badge content for missing fields

Trigger: badge template includes "Company" but a walk-in guest didn't provide one.

Field renders blank (not "{company}" literal, not "(undefined)"). Layout reflows gracefully. Harness: walk-in without company, badge renders without literal token, layout intact.

Cross-tenant printer pairing

Trigger: same physical printer used at two tenants' events.

Pairing is OS-level (the printer itself isn't tenant-aware). App-level pairing is tenant-scoped — if signed into tenant A, only A's print jobs go to that printer. Switching tenants re-prompts pair confirmation. Harness: switch tenants, re-prompt visible.

Stable test attributes

badge-printing-gap-panelSettings → PrinterVisible until feature ships
printer-pair-buttonSettings → PrinterTriggers BT scan
printer-status-pillSettings + tabpaired / disconnected / unsupported
badge-template-editorAdmin → Badge TemplateDrag-drop preview
print-progressConfirmation"Printing badge..." → "Badge printed"
print-pending-bannerIf queued"Will print on reconnect"
print-error-bannerIf printer errorSpecific reason + Retry
reprint-buttonPer guest rowVisible for permitted roles

Agent test plan

Probe list
- gap-panel-visible: matrix=Absent, panel visible
- (when shipped + manual) printer-pairs-survives-relaunch: pair, force-quit, relaunch, still paired
- printer-not-paired-graceful: check-in succeeds, badge-pending message visible
- (when shipped + manual) printer-bt-loss-queues: stub BT loss, queue persists
- (when shipped + manual) printer-out-of-paper: stub error, specific banner + Retry
- two-staff-race-both-print: 2 devices, both badges print, server 1 check-in
- reprint-permission-gated: read-only, button hidden + 403
- reprint-audit-row: reprint, audit row exists
- template-change-preserves-prior: new template doesn't affect already-printed
- (when shipped) missing-field-renders-blank: walk-in without company, no literal token
- cross-tenant-pairing-reprompts: switch tenants, re-prompt
- (when shipped + manual) physical-printer-evidence: signed-build + printer + actual print physical photo