Team evidence · noindex

Scenario matrix

What should happen in every situation?

Scenario matrix

This is the live-event playbook. Expected is what the current Home laser path (processBadgeScan) does, unless a row says otherwise.

Camera scan (SCANQR/useBadgeScanner) and unused DashBoardScreen do not always match. Those mismatches are called out. Do not tell a lead “the app always does X” if they scanned with the camera.

Base URL: selected at launch from src/config/clients.ts
ADNEC https://registration.xporience.com/v2/scan/api · DMG https://dmg-reg.xporience.com/v2/scan/api · Xporience https://reg.xporience.com/v2/scan/api · Terrapinn https://terrapinn.xporience.com/v2/scan/api
Every call is POST to that URL with { operation, EPTOKEN }.

Client selection

ScenarioExpectedMobile actionExpected statusFailure handlingOwner
First launch / upgrade with no stored client and no sessionPicker, no APIGate in Routes after persist rehydrate—Must pick ADNEC, DMG, Xporience, or TerrapinnMobile
Upgrade while already logged in, no stored clientTreat as DMG (old hardcoded URL), persist itInfer LEGACY_CLIENT_ID—Avoid interrupting a live gateMobile
Client already storedSkip pickerRead appStateInfo.selectedClientId—Login / app as todayMobile
Login while ADNEC selectedRequests hit registration hostgetBaseUrl() at request timeSame as login rowsWrong client looks like a login failureMobile host; backend auth
Login while DMG selectedRequests hit dmg-reg hostSameSame as login rowsSameMobile host; backend auth
Login while Xporience selectedRequests hit reg.xporience host; JWT signed with Xporience secretSameSame as login rowsHTML 403 = WAF/UA; JSON 401 Invalid Data = credentials/payloadMobile host + secret; backend auth
Login while Terrapinn selectedRequests hit terrapinn hostSameSame as login rowsSameMobile host; backend auth
Change client while logged out, no unsynced for current clientNew host used after pickLogin “Change”—Synced history kept, filtered by client_id (clients never share counts/queues)Mobile
Change client while current client has unsynced rowsBlockAlert—Cancel (log in and sync) or wipe those unsynced rowsMobile
Legacy scan row with no client_idTreated as DMGresolveBadgeClientId—Shown only on DMGMobile

Login — operation: userLogin

ScenarioExpectedMobile actionExpected statusFailure handlingOwner
Valid credentialsSession created, go to space selectionPOST login JWT (username, password, device_id)HTTP 200 and body code 200 with dataStore user in Redux (storeUserDetails). Clear userToken AsyncStorage key. Do not store a bearer token.Mobile: request + persist user. Backend: authenticate.
Blank username/passwordError, no requestUI validation—“Credentials can not be left blank.”Mobile
Username contains spaceError, no requestUI validation—“Username cannot contain spaces.”Mobile
No internetOffline message, no requestisInternetConnected()—“You’re offline…”Mobile (device network)
Wrong credentials / bad requestError, stay on loginPOST400Show backend messageBackend message; mobile display
Unauthorized / invalid login dataError, stay on loginPOST401Show backend message (e.g. “Invalid Data”)Backend auth; mobile display
Device already logged in elsewhereErrorPOST409Show message, remove userTokenBackend session rule
Server errorError, no sessionPOST500Sanitized message (no AWS/S3 text) via loginErrorUtilsBackend availability
Timeout / DNS / networkError, no sessionPOSTAxios ECONNABORTED / ERR_NETWORKConnection/timeout copyInfrastructure or device
200 but missing dataError, no sessionPOST200 with empty payload“Invalid login response”Contract
Login QR invalidErrorCamera parse JSON or user:pass—“Invalid login QR”Mobile

Login QR is only a credential capture. It still runs the same userLogin API.

Space list — operation: getSpaceList

ScenarioExpectedMobile actionExpected statusFailure handlingOwner
Spaces returnedList shownPOST JWT (event_id, device_id)200 with data arrayCache in component ref, not ReduxBackend data; mobile render
Empty listInfoPOST200 empty“No spaces available”Backend assignment
Invalid space objectsErrorPOST200 but missing space_nameFilter invalid rows; error if none validContract
API failure after loginFallback to current space if already selected, else emptyPOSTNon-200 / throwError toast; may keep previous spaceMixed
User taps a space with unsynced scans for current spaceBlock until those scans syncgenerateLead per offline rowSee sync rows“Please sync all offline badges before changing space”Mobile queue + backend accept
User taps a space, current space queue emptySpace written to Redux userNo extra API—storeUserDetails with space_id, space_name, type, assigned_badgesMobile session

Space list is not persisted as its own store. After kill, space comes back from persisted userDetails.

Badge scan flow (Home laser)

Floor source of truth: processBadgeScan (Zebra DataWedge / Home laser). Camera path can differ — see the matrix below.

Loading diagram…

Tree form (same path):

Zebra / tap scan (DataWedge or Camera)
  └─ Same badge < 1.2s ago? ──yes──► Ignore (hardware dedupe)
  └─ no ──► Badge format OK? ──no──► Invalid Badge (error sound)
  └─ yes ──► Space selected? ──no──► “Please select a space first”
  └─ yes ──► Visitor + already scanned today (same space)?
                └─ yes ──► “Successful Re-checkin” (no second save)
                └─ no ──► Online? ──no──► Success toast + save offline (synced=false)
                           └─ yes ──► generateLead (JWT)
                                        ├─ 200/201 + sync_status=1 ──► Success + save online (synced=true)
                                        ├─ 200/201 + sync_status ≠ 1 / missing ──► Restricted (no save)
                                        ├─ 801 ──► “Select another Space” (no save)
                                        ├─ 403 ──► Restricted (no save)
                                        ├─ 404 ──► Invalid badge (no save)
                                        ├─ 409 ──► Logout
                                        └─ 400/401/408/5xx/timeout/network ──► Success toast + save offline

Badge scan — operation: generateLead (Home laser = source of truth for “what we claim”)

JWT payload: badge_number, event_id, badge_type (Visitor if type === '1' else Conference), visited_date (event timezone), sync_date (device ISO), user_id, space_id, device_id.

ScenarioExpected (Home laser)Camera path (useBadgeScanner)Owner
Empty / too short / URL / non-alphanumeric“Invalid Badge”, no saveSame idea, slightly different charset (@ allowed on Home, not camera)Mobile validation
No space selected (user.data.type missing)“Please select a space first”Different role gate (“Something Went Wrong with this User”)Mobile
Same badge, same day, same space, Visitor (type === '1')“Successful Re-checkin”, no second saveDuplicate check runs after the API call (order differs)Mobile local rule
Conference (type !== '1') same badge againAllowed as a new scanAllowedProduct rule — confirm with backend if this is intended
Same badge within 1.2sIgnored (dedupe)processingRef lockMobile hardware debounce
Device offline / NetInfo says offlineSuccess + save offlineSuccess + save offlineMobile fallback
HTTP 200/201 and data.sync_status === 1Success + save online (synched: true, Valid)SimilarBackend validity; mobile persist
HTTP 200/201 and sync_status missing or not 1“Restricted Badge”, no saveTreat as restricted / no save (verify on camera — trail partly duplicated)Backend validity
HTTP 801“Select another Space” (or backend message), no saveCamera/Dashboard look for 503, not 801Contract mismatch — see risks
HTTP 403“Restricted Badge”, no saveRestricted, no saveBackend
HTTP 404“Invalid badge number”, no saveInvalid, no saveBackend
HTTP 409Logout, clear userTokenLogoutBackend session
HTTP 408 / timeout / network errorSuccess + save offlineCamera: timeout → offline. Dashboard laser: 408 does not saveMixed — do not claim one behavior
HTTP 400 / 401 / 405 / 407 / other 4xx except no-save listError toast + save offlineSimilar but not identical listsMixed
HTTP 5xx (not treated as 801)Error + “Saving offline”503 on camera = no save; other 5xx offlineMixed
Ambiguous / missing statusSuccess + save offline (conservative)VariesMobile

Operator-facing copy “Successfully Checked in” is used for both online-valid and offline-queued scans on the Home path. That is why “the phone said success” is not proof the server stored the lead.

Sync unsynced (header refresh / space switch)

ScenarioExpectedMobile actionOwner
OfflineBlockNetInfoMobile
No unsynced rows for current space“No badge data to refresh” (header) or allow space changeFilter by space + userMobile
Row syncs with 200 + sync_status === 1Move to online array, drop from offlinegenerateLeadBackend accept; mobile arrays
503 during bulk syncKeep in offline queueHeader/space sync look for 503, not 801Contract mismatch
403 / 404 / 408 during bulk syncMark processed with Restricted / Invalid / Timeout, synched: trueRemoved from retry queueIntent unconfirmed — treated as final, not retry
409 mid-bulkSave already-synced rows, then logoutHeader pathBackend session
5xx / 401Keep in offline queueRetry laterBackend / infra

Bulk sync is not a single transaction. Kill mid-loop can leave a mix of synced and unsynced rows. There is no rollback.

Export Excel / send report

ScenarioExpectedAPIOwner
No scans for current spaceError, no fileNoneMobile
Password wrong (excel_security_password from login)“Incorrect password”NoneMobile vs password issued by backend at login
Password QR for excel/send/logout/spaceSame as typing the passwordNoneMobile
ExportXLSX written to app Documents dirNoneMobile filesystem
Send reportDevice mail composer with attachmentNone — react-native-mailMobile + device Gmail. Recipients from login email_id_for_checkin_data

There is no “upload report to server” API in this app.

Logout

ScenarioExpectedOwner
OfflineBlocked: connect and sync firstMobile logoutGuard
Unsynced leads for current spaceBlocked with countMobile
AllowedlogoutUser() clears Redux user/token; badge arrays stay. Auth stack shows because user is nullMobile. Product choice: scans survive logout

How to fill a hole in this matrix

If a status appears that is not in the table:

  1. Capture HTTP status, body code, body message, sync_status.
  2. Do not guess. Add a row here after the incident.
  3. Note which scan path produced it (Home laser vs camera).