myProctor myProctor help · admin reference
Help center

The WOW Room in your Pocket & Hand.

myProctor is an iOS and iPadOS app that runs the full lifecycle of proctored screening events across Saudi Arabia — from warehouse prep weeks before event day, through attendance and broadcast operations in-event, to reconciliation afterward. This help center covers every screen, permission, and rule.

myProctor Home dashboard on iPad, Admin role
Home dashboard (iPad, Admin) — greeting banner, Quick Actions strip, live stats, Live Attendance, Recent Broadcasts, and the Warehouse / Inventory rollups at a glance.

Getting started

Sign in, permissions, and your first minute in the app.

Roles & ranks

Ten roles, how rank drives access, city scoping.

Home dashboard

Your role-adaptive landing screen, gesture by gesture.

Attendance

QR check-in/out, OT math, manual fallback, broken logs.

Broadcast Center

Five channels, routing, pin/delete, support lifecycle.

Warehouse pipeline

The ten stages, handovers, and recovery paths.

How this guide is organised

Start here covers orientation — what the app is, how roles work, and how to sign in. Daily use covers the screens most people open every event day. Operations covers the deeper warehouse, labeling, and configuration workflows. Reference is the long tail — the permission matrix, masquerade, error codes, and glossary.

The WOW Room in your Pocket & Hand

myProctor takes the command experience of the WOW Room and makes it portable. The iPhone version keeps it in your pocket on the move; the iPad version puts it in your hand at the venue desk — where it also becomes the common-use check-in terminal. Same command, two form factors, anywhere you need them.

A few things to know

  • Online-only by design. The app depends on Firestore for writes. Offline, you'll see an overlay but won't lose data.
  • All timestamps are Asia/Riyadh. The business schedule, OT calculations, and special-period rules assume KSA time.
  • iOS and iPadOS only. No Android, no web. Built in SwiftUI against Firebase/Firestore with AES-GCM encryption for broadcasts.
  • Self-signup with email and password. No SSO, no Google, no Apple ID — the app uses straight email + password authentication via Firebase Auth. New accounts default to Proctor role with no city assignment until an Admin updates them in Manage Users.
Start here

Getting started.

Two minutes to sign up, two minutes to get an Admin to elevate you. After that, myProctor remembers your device and delivers notifications whenever broadcasts or attendance events involve you.

First sign-up & sign-in

myProctor uses plain email + password authentication backed by Firebase Auth. There is no Google sign-in, no Apple ID, no Enterprise SSO — every user owns their credentials directly.

  1. Install from the App Store or accept the managed install pushed by your MDM. On first launch, grant the permissions the app asks for (see the table below).
  2. Sign up. On the login screen, enter your work email address and a password you'll remember, then tap the green Sign Up button. The app creates your Firebase Auth account and a matching user record in Firestore.
  3. You're auto-assigned the Proctor role with no city. This is the default for every new account. The relevant Admin must edit your access profile in Manage Users → Edit Access to set your real role (Tech, TCA, Manager, etc.) and assign you to a city. Until then your menu is the minimal Proctor menu and you won't see any city-scoped data.
  4. Sign in. On subsequent launches, enter the same email + password and tap the blue Login button. Your Access Profile downloads (typically under 3 seconds) and you land on Home.
  5. Update your Display Name. The app initially shows the local part of your email as your name. Open profile menu → Edit Profile and replace it with your real name. Display Name is editable any time; email is the immutable identifier and cannot be changed once the account exists.
myProctor login screen with Email field, Password field, blue Login button, and green Sign Up button
The login screen — same screen for both flows. Existing users fill in their email + password and tap Login (blue). First-time users fill in their desired email + password and tap Sign Up (green).

Why default to Proctor?

Proctor is the lowest-privilege login role. Auto-assigning it means a brand-new account can authenticate but cannot see any city's data, send broadcasts, touch inventory, or manage users until an Admin explicitly elevates them. The Admin's first task whenever someone signs up is to open Manage Users, find the new entry, and set role + city. Until then the user sits in a sandbox.

myProctor sidebar menu open showing the Admin's full navigation
The sidebar menu — tap the sidebar icon in the top-left of any screen to see every section available to your role. An Admin sees the full list (Home, Manage Users, QR Code Scanner, Generate QR, Attendance, Dashboards, Broadcast Center, Inventory, Warehouse, Settings). Lower ranks see a subset.

Permissions the app asks for

PermissionWhyRequired?
Location (While Using)Stamp your attendance QR with GPS coords (4 decimal places — ~10 m accuracy). Auto-detect your city.Yes
CameraScan QR codes for attendance, inventory, and warehouse enrollment.Yes
NotificationsReceive broadcast pushes, support replies, and handover approvals via APNs / FCM.Recommended
Face ID / Touch IDUnlock the scanner sheet and gated admin actions without re-typing a password.Recommended

Online only

myProctor needs a live connection to Firestore for all writes (attendance, broadcasts, inventory actions). When you lose connectivity, an offline overlay appears globally. No data is lost — actions simply wait until you're back online.

The profile menu

Tap your avatar in the top-right corner of Home to open the profile menu. You'll find three key items:

  • Edit Profile — update your Display Name (initially the local part of your email — change it to your real name) and phone number (must match ^05\d{8}$, e.g. 0551234567). Email is immutable here; it's the identifier the app uses to locate your account in Firestore.
  • Access Profile — read-only view of your role, rank, city scope, and every permission granted to you. Useful when you're not sure why a menu is or isn't showing.
  • Stop Masquerade — only visible when an Admin has masqueraded as you (or vice versa). Instantly returns you to your own Access Profile.
Implementation notes · auth + token flush

Sign-up and sign-in both use Firebase Auth with the email/password provider. createUserWithEmail(_:password:) creates the Auth record and a backing Firestore document at /users/{uid} with default role = "PROCTOR", city = nil, and displayName set to the email's local part. The Admin updates these fields via the Manage Users sheet.

Email is treated as an immutable primary key downstream — QR payloads, attendance records, broadcast routing, and inventory custody all reference the user by their Firebase UID, which is permanently bound to the email at account creation. Display Name is purely cosmetic and can be edited freely via UserProfile.updateDisplayName(_:).

On a successful sign-in, the app flushes any stale FCM token and re-registers to ensure push targeting is correct for the new user. See NotificationBadgeManager.clearAll() and AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken.

Start here

Roles & ranks.

Every menu, every broadcast channel, every warehouse action is gated by two things: your role (what kind of team member you are) and your rank (a numeric weight from 1 to 100). Rank is what drives the can-I-do-this logic throughout the app.

The ten roles

Colors below are authoritative — they come from User.UserRole.roleColors in Swift. Ranks drive default broadcast eligibility and approval chains.

RoleRankCity scopePrimary responsibility
Admin100Assigned cityFull configuration and operations. The only role that can masquerade.
Manager90Region (multi-city)Regional oversight. Can send Password and Executive broadcasts.
TCA50Single venueTest centre administrator. Handles handovers and on-day support routing.
ATCA40Single venueAssistant TCA. Can send Direction broadcasts, help with attendance corrections.
Tech30Assigned cityTechnical support. Receives Tech support tickets and handles inventory enrollment.
Warehouse25Assigned cityWarehouse operator. Handles boxing, labeling, and logistics stages.
Moderator20Assigned eventEvent moderator. Can send General info broadcasts.
Proctor10Assigned venueIn-room proctor. Checks in/out. Submits Support tickets.
Helper5Assigned venueSupport crew for logistics, crowd management.
Student1n/aTest-taker (seed role). Does not use the app.

City scoping

The system is configured for 12 cities (5 of which are currently inactive — they're available to be activated when needed), each with a three-letter authoritative code used throughout the app. Managers have a region (set of cities); everyone else is scoped to exactly one city:

RIY
Riyadh — Active · 500 seats · central region
JED
Jeddah — Active · 500 seats · western region
DAM
Dammam — Active · 400 seats · eastern region
ABH
Abha — Active · 250 seats · southwestern region
MUB
Mubarraz — Active · 250 seats · eastern region
MAD
Medina — Active · 225 seats · western region
RAH
Ras Tanura — Active · 225 seats · eastern region
TBK
Tabuk — Inactive · northwestern region
DHA
Dhahran — Inactive · eastern region
ABQ
Abqiaq — Inactive · eastern region
SYH
Sayhat — Inactive · eastern region
YAN
Yanbu — Inactive · western region

Admins can add new cities from Settings → Cities. The three-letter code is authoritative — it's used in QR payloads, box labels, and seat assignments. Changing it for an existing city will break historic records.

Seeing roles in practice

The Manage Users screen is where Admins (and TCAs, within their venue) see every team member with their role chip and city. Each chip uses the authoritative role color — easy to scan a long list and spot the Managers or TCAs at a glance. Filters let you narrow by city, role, or active status, and a grouped view organises users by city header.

Manage Users flat list
Flat list — every user, sorted alphabetically, with role chip and city.
Manage Users with filters expanded
Filters — search, city, Active-only toggle, grouped-view toggle, and a role quick-filter row.
Manage Users grouped by city
Grouped view — collapsible sections per city (and an "Admins" supra-group that spans cities).
Users Dashboard card grid
Users Dashboard — card grid, color-coded by role, live presence dot in the top-right of each card.

Staffing model

Headcount has two layers. The first is a formula-derived minimum that scales with seat capacity. The second is operational headroom — additional Tech users for multi-venue cities and absence cover, plus floating staff deployed across cities as the event demands. The formula gives the floor; the actual roster always sits above it.

The formula in one breath

For each city: lines = ceil(seats / 20), and each line is managed by 1 Proctor. Add 1 TCA, 1 ATCA, 1 Tech per city. Add 3 Admins and 3 Managers globally (not per city). That's the baseline minimum.

Per-city headcount

Applied to the seven active cities, this gives:

CitySeatsLinesProctorsTCAATCATechPer-city total
Riyadh (RIY)500252511128
Jeddah (JED)500252511128
Dammam (DAM)400202011123
Abha (ABH)250131311116
Mubarraz (MUB)250131311116
Medina (MAD)225121211115
Ras Tanura (RAH)225121211115
Subtotal2,350120120777141

Global leadership

Independent of cities — a single shared layer of leadership oversees the whole operation:

  • 3 Admins — full configuration and operations across all cities
  • 3 Managers — regional oversight and executive broadcasts

Baseline minimum staffing

Theoretical minimum to cover the formula: 147
120 Proctors + 21 per-city staff (7 TCAs · 7 ATCAs · 7 Techs) + 6 leadership (3 Admins · 3 Managers) = 147 staff covering 2,350 seats. This is the floor for the current 7-city configuration.

Dormant capacity — Tabuk and Yanbu

Two of the inactive cities — Tabuk (TBK) and Yanbu (YAN) — historically operated at 200 seats each before being deactivated. If they were reactivated tomorrow at the same capacity, the formula would add the following per city:

  • ceil(200 / 20) = 10 lines → 10 Proctors
  • + 1 TCA + 1 ATCA + 1 Tech = 3 city support staff
  • = 13 staff per dormant city

Reactivating both would add 26 staff total (20 Proctors + 6 city support), bringing the baseline from 147 up to 173. The current operational roster carries the additional headcount needed to handle this re-activation without scrambling for hires — it's reserve capacity, not just floating slack.

Beyond the baseline — additional and floating headcount

On top of both the active baseline and the dormant reserve, real operations need extra capacity for:

  1. Additional Tech users. The baseline assumes one Tech per city, but in practice cities frequently need more. Multi-venue cities (Jeddah currently has 2 venues) need at least one Tech per venue. Inventory, warehouse coordination, and peak-day device-failure surges all consume Tech capacity. Sick-day or shift-rotation cover prevents a city from losing all Tech support if its single assigned Tech is unavailable.
  2. Floating role employees. Staff who aren't bound to a specific city, deployed wherever the event needs them on a given day. Examples: spare Proctors for absence cover, Tech users who travel between nearby cities for surge support, ATCAs lent to a sister city for an unusually large session, or Admins on the ground shadowing operations rather than running them remotely.

Current operational roster

The current roster from the event champion is approximately 185 staff. Decomposing the 38 above the active 7-city baseline:

  • ~26 covers dormant capacity for Tabuk and Yanbu — staff retained so re-activation doesn't trigger a hiring scramble.
  • ~12 covers additional Tech (multi-venue Jeddah, redundancy) and floating role employees (absence cover, surge support).

The exact split between the two extra-Tech vs floating buckets in that ~12 varies event-to-event and is set by operational planning rather than the formula.

Why the math works this way

  • 20 candidates per line is the unit of supervision — one Proctor can attend that many test-takers in their line of sight without missing a hand raise.
  • Ceiling math, not floor — a city with 225 seats gets 12 lines (not 11), even though the last line only holds 5 candidates. You always round up because under-staffing a line is worse than under-utilising one.
  • One TCA + one ATCA per city — the TCA runs the venue's day; the ATCA backs them up and handles attendance corrections so the TCA isn't blocked.
  • One Tech per city — handles inventory and any iPad / app issues raised via Support → Technical broadcasts. For multi-venue cities (Jeddah has 2 venues), the single Tech is mobile and routes between them.
  • Three Admins, three Managers — a global pool, not per city. Allows redundancy across leave / shift / off-day situations without inflating headcount per city. Managers cover regions; Admins can be assigned anywhere they're needed.

Recomputing when seat capacity changes

If the seat capacity of an existing city changes, or a new city is activated, recompute that city's row only:

  1. Update the city's seat capacity in Settings → Cities → Edit.
  2. New lines = ceil(newSeats / 20).
  3. Recruit (or release) Proctors so headcount matches new line count. TCA / ATCA / Tech counts are unaffected by seat changes.
  4. Leadership counts (3 + 3) only change for major operational scope shifts — not per-city seat tweaks.

Role vs permission

A role sets a default permission bundle. An Admin can override individual permissions per user in Manage Users → Edit Access Profile, so two Proctors can have different capabilities. The role determines starting point; permissions determine the final state.

Daily use

Home dashboard.

Home is role-adaptive. An Admin sees a wide view across their city; a Proctor sees their own status and today's directives. Every card is gesture-driven — long-press for context actions, drag to reorder, tap to drill in.

Anatomy of the screen

  1. Greeting banner — time-of-day salutation, your display name in Fraunces italic, your role chip, and the current event status.
  2. Live status strip — your city, live attendance count (e.g. "142/250 · Live"), and a pulsing green dot when data is flowing.
  3. Quick actions — six role-specific shortcuts with role-colored icons and live badge counts (e.g. open support tickets, pending handovers).
  4. Stats cards — "My Hours" (32:15), "Today" (08:42), "Staff" (142/167), "Broken Logs" (count with red pill if >0).
  5. City attendance breakdown (Admin/Manager only) — all cities with role rollups and % present.
  6. First 5 & Recent 5 — first five to check in today, most recent five with timestamps.
  7. Warehouse & Inventory strips — pipeline counts, inventory status bars, role-gated.
Home dashboard scrolled down showing Warehouse KPIs, Inventory Status, and Active Schedule
Scrolled-down view — the Warehouse KPIs grid (Not Staged, Staged, Configured, Enrolling, Ready to Ship, In Venue), Inventory Status (batches + device clear/damaged/malfunction/missing tallies), and the Active Schedule & Cities footer.

Role adaptations

RoleWhat Home shows
AdminFull city dashboard, all stats, punctuality leaderboard, broadcast analytics, warehouse pipeline.
ManagerAll cities in region, city comparison, executive broadcast access.
TCASingle-venue focus, support ticket inbox prioritized, pending handovers.
TechTechnical support inbox, inventory scan shortcut, enrollment recovery.
WarehousePipeline counts, labeling queue, asset/container types.
ProctorPersonal status, today's schedule, direction inbox, support submit button.

Gesture menu

Long-press on most items on Home to get a contextual action sheet. Seven common gestures:

  • Tap — drill into the item (e.g. tap a stats card to see the underlying list).
  • Long press — open the gesture menu with edit / share / pin / remove actions.
  • Draggable items — reorder quick actions. Custom order persists per user.
  • Actionable items — cards with trailing buttons for quick actions (e.g. "Dismiss", "Resolve").
  • Sequential tap — tap-tap-tap to advance through details (e.g. attendance: summary → daily → logs).
  • Resize — pinch on a card to compact or expand (iPad only).
  • More options (⋯) — opens a secondary menu for less-common actions.

Masquerade-aware

When an Admin masquerades as another user, Home fully re-renders with that user's role, permissions, and data — gesture menus included. Very useful for debugging "why can't they see X?".

Implementation notes · HomeDashboardViewModel

HomeDashboardViewModel composes 8 upstream view-models (users, attendance, broadcasts, inventory, warehouse, settings, access profile, masquerade) and merges their outputs through a HomeDashboardCache — a Swift actor for thread-safe access.

AppScheme.warehouseSchemeId = "2026" is centralized here and flows into all pipeline queries. When the scheme changes year-over-year, this is the single point of update.

Daily use

Attendance & hours.

A rolling QR code refreshes every 15 seconds. Staff scan it to check in or out on the common-use iPad at the venue desk. Location is stamped to four decimal places, time to the second, and the app calculates overtime against your business schedule with special-period overrides for Ramadan, weekends, and holidays.

Checking in with QR

  1. Each staff member opens Generate QR on their personal device. A full-screen QR appears with the 15-second countdown visible. The QR encodes UID, location (city + GPS), the action (check-in / check-out), and a timestamp.
  2. They walk up to the common-use iPad at the venue desk. These shared iPads run persistent scanner mode — always on, biometric-locked between scans.
  3. They hold their QR in front of the iPad. The scanner reads the one-shot payload, validates its freshness against the 15-second window, and writes an attendance log.
  4. If the user is late (arrival timestamp past the shift start defined for them), the iPad blocks the immediate write and prompts the user to type a justification. The remark is required; once submitted, the attendance log is written with the remark attached as metadata. TCAs see the remark in the daily summary.
  5. A 60-second buffer prevents accidental double-scans. If you try to check in or out twice within 60 seconds, the second attempt is blocked with a friendly error.

Manual attendance (fallback)

If the camera isn't available — broken lens, denied permission, accessibility need — a user holding the manualSelfAttendance permission can log their own check-in/out through a form instead. The log is flagged manual for audit purposes, but otherwise behaves identically.

Admins and TCAs with attendance permissions can also record attendance on behalf of another user from the All Attendance dashboard — this is how last-minute arrivals or staff without devices get counted.

The five broken-log categories

The app watches your attendance log for anomalies and flags five specific patterns. You'll see the count on the Home stats card and in red pills on the All Attendance view.

CategoryWhat it meansWho can fix
Missing check-inA check-out exists with no matching check-in earlier that day.Self within 3 days · Admin anytime
Missing check-outA check-in exists with no check-out before midnight (Asia/Riyadh).Self within 3 days · Admin anytime
Out-of-orderA check-out timestamp precedes the preceding check-in (clock-skew bug).Admin only
DuplicateTwo identical check-in (or check-out) events within 60 s.Admin only (auto-dedupes on save)
Wrong cityGPS stamp doesn't match the user's assigned city.Admin only — manual review

Hours & overtime

  • Quarter-hour rounding. Worked hours round to the nearest 15 minutes, always down for check-ins (so 07:52 becomes 07:45) and up for check-outs.
  • Daily threshold is set in Settings → Business Schedule (default 7.5 h). Anything beyond threshold counts as OT.
  • Weekend days are 100% OT. Friday and Saturday fall under the special-period "Weekend" bucket.
  • Ramadan applies the Ramadan special-period schedule — typically 6.0-hour daily threshold. Defined in Settings → Special Periods.
  • Lunch block (11:15–12:45 default) does not count toward hours unless explicitly claimed.

Editing attendance

Non-admins can edit their own logs within 3 days of the original timestamp. After that window, only an Admin can edit. Each edit is recorded in the log history with the editor's UID and the reason (free-text, required).

QR freshness

Each generated QR is valid for 15 seconds. After that, the scanner rejects it and prompts the user to regenerate. This prevents screenshot-sharing and keeps the attendance window tight.

Implementation notes · TimestampValidator & location precision

TimestampValidator defaults to 15-second validity (not 15 × 60). Accepts both Date and ISO8601 String inputs. Located at TimestampValidator.swift.

LocationManager.swift uses CLLocationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters and stops updating after the first success. Fallback labels: UNKNOWN CITY, LOCATION DENIED, UNKNOWN LOCATION, ERROR.

Daily use

Broadcast Center.

Five message types, rank-gated sending, encrypted end-to-end, and a two-axis pin/delete model. The Broadcast Center is how the event talks to itself — and how support requests bubble up through the right hierarchy.

The five channels

How city scoping works in the composer

When you open a New Broadcast, the City field is auto-filled with your assigned city. Anyone outside that city won't receive the message. To deliberately broadcast to all cities, clear the City field before tapping Send. To target a different city, replace the value with that city's name. The auto-fill keeps the safe default (your own city) without forcing extra taps; the explicit clear/change is the deliberate broader broadcast.

ChannelCodeSend rankPurpose
PasswordPSWD≥ 90Highest-priority operational broadcast tier, reserved for Admin and Manager. Used for any leadership-level urgent dispatch.
DirectionDIRC≥ 40Crowd-control & staging directions. Admin / TCA / ATCA.
ExecutiveEXEC≥ 90High-priority pinned messages from leadership.
GeneralINFO≥ 20Day-of announcements, timing changes, FYI messages.
SupportSUPRanySender-aware routing — see below.
Broadcast Center inbox with All, Password, Direction, Support, Executive tabs
Broadcast Center — the five-channel filter bar at the top, Pinned messages in a dedicated strip, then Inbox and Outbox. Each card shows the channel badge (INFO, DIRC…), sender, time, view count, and pin icon.

Support routing

Support is the only channel with sender-aware routing. The category is chosen by the sender, but who receives it depends on the sub-type — and in one case, who sent it. All routing is also city-scoped: the composer auto-fills the City field with your assigned city, so a Support message from someone in Riyadh is delivered only to Riyadh recipients. Clear the City field before sending to broadcast to every city.

Support sub-typeRouted toTypical sender
TechnicalAll users with tech role in the sender's cityProctor reports broken iPad
Logistics (default)Admin + Manager of the sender's cityProctor reports missing printouts, supplies, room issues
Administrative (default)Admin + Manager of the sender's cityEscalation, complaint, policy question
Logistics / Administrative
when sent by a Tech user
TCA + ATCA of the sender's city only (not Admin/Manager)Tech needs venue support without escalating to leadership

Composing a broadcast

Tap the + button in the top-right of Broadcast Center to open the New Broadcast sheet. The sheet is adaptive — the "This will notify" preview and the optional Target Audience selectors change as you switch Type.

New Broadcast composer with Password type selected
Password — high-priority operational broadcast. The notify preview shows every staff role receives it; the type-specific caption explains the channel's reserved-for-leadership intent.
New Broadcast composer with Support type selected, Technical subtype
Support → Technical — the second-row segmented control (Technical / Logistics / Admin) appears only for Support, and the notify preview updates to show only TECH will be paged.

Pin & delete — two axes

Pinning and deletion both operate on two axes — personal (just you) and global (everyone who received the message). The sender has a 120-second window in which global delete works; after that, only a personal delete is possible.

  • Pin personal — sticky at the top of your inbox. Visible only to you.
  • Pin global — sticky at the top of everyone's inbox. Available to original sender + Admin.
  • Delete personal — removes from your inbox. Others still see it.
  • Delete global — retracts from everyone's inbox. Sender within 120 s, Admin anytime.

Support lifecycle

Every support ticket moves through four states. State changes are broadcast back to the sender as status updates.

none
Sent, not yet acknowledged. Shows as red dot in recipient inbox.
open
Acknowledged by a responder. Shows as orange dot.
inProgress
Being worked on. The responder has pinned it to their Home.
resolved
Marked done. Sender sees a confirmation toast and an audit log entry.

Message tag format

Every broadcast carries a compact routing tag that encodes how it should be fanned out. The format is versioned:

v1::CATCODE::SCOPE::CITY=X::R=ROLES

Example: v1::DIRC::VENUE::CITY=JED::R=proctor,helper would route a Direction broadcast scoped to a specific venue in Jeddah, delivered to all Proctors and Helpers there.

End-to-end encryption

Message bodies encrypt with AES-GCM. Per-message keys wrap with HKDF-SHA256 using salt "myProctor-broadcast-wrap" and the root key. Messages live 300 days before aging out of the inbox query.

Implementation notes · root key rotation

Root key is currently hardcoded as Data(repeating: 1, count: 32) in BroadcastFireDBService.devRootKey(). Marked TODO for MDM / Keychain / Secure Enclave rotation. See AppEnvironment.broadcastCrypto.

Delivery goes through BroadcastPushAPI which calls a Cloud Function that fans out push notifications to the resolved recipient set. Read receipts are tracked per-user in BroadcastUserStateRecord.

Daily use

Inventory batches.

Every iPad the organization owns lives in Inventory. It has a status, a condition, a custody state, and belongs to exactly one batch at any time. Batches are how devices move from warehouse to venue to user and back.

The four batch modes

Every batch is created in one of four modes. The mode determines who the batch is for, what happens to the iPads while assigned, and the visual chip color you'll see on the Batches screen. The colors below match the SwiftUI Color tokens used by the app.

ModeSwiftUI colorPurposeCap
Student (.studentSeatMapped).blueiPads handed to students for the duration of an assessment session. Each iPad's wallpaper QR and Display Name are bound to a specific seat code (e.g. RIY-A-07); backup iPads (suffix BAK1, BAK2) replace primary units mid-session if needed.20 / batch
Staff (.staffAssigned).greenAn iPad with myProctor installed loaned to a Proctor or Tech who doesn't have the app on their personal iPhone/iPad.unlimited
Special (.specialAssigned).orangeAn iPad with myProctor installed loaned to a TCA, ATCA, Warehouse, Manager, or Admin who doesn't have the app on their personal iPhone/iPad.unlimited
Generic Use (.genericUse).grayAn iPad assigned to a TCA/ATCA for a generic purpose — most commonly running myProctor in Scan Mode as the venue's common-use check-in terminal (Proctors walk up and scan their attendance QR), or running a separate web-app to check in students outside myProctor's scope.unlimited

Batch custody states

Every batch is in exactly one of five custody states at any moment. The state changes are driven by the swipe actions (Hand Out, Hand Back, Cancel) and per-device returns. Colors map to the SwiftUI Color tokens in the source.

StateColorMeaning
In Store (.inStore).blueBatch exists, iPads are bound to it, but custody hasn't moved to the assignee yet. Default state on creation.
With User (.withUser).orangeBatch has been handed out. The assignee has physical possession; the timer for "with user" duration started at hand-out.
Partially Returned (.partiallyReturned).yellowSome — but not all — devices in the batch have been returned via the per-device Return action. The batch as a whole is still active.
Returned to Store (.returnedToStore).greenAll devices handed back. The batch is closed; iPads are free to be added to a new batch.
Cancelled (.cancelled).redBatch voided via Cancel Batch swipe action. iPads released back to the free pool. Stays in History but excluded from active dashboards.
Inventory Batches screen grouped by city with Transfer, Hand Back, Cancel Batch, History actions
Inventory Batches — grouped by city with a collapsible header and per-batch count. Each batch row shows its mode chip (Student/Staff/Special/Generic), the assigned user, their role, and creation time. Long-press to reveal the swipe actions on either side.

Creating a batch (step-by-step)

Batch creation is performed by users with inventoryWrites — typically a Tech, TCA, or ATCA, depending on city and event setup.

  1. Open Inventory from the sidebar menu, or from the Inventory tile on the Home dashboard.
  2. Tap the QR scan icon in the top-right corner of the toolbar. This opens the batch-creation scanner overlay.
  3. Pick the batch mode — Student, Staff, Special, or Generic Use. The choice is permanent for this batch and locks the cap (20 for Student, unlimited for the rest).
  4. Pick the assignee — the user the batch will eventually be handed to. For Student batches this is typically the Proctor running the session; for Staff/Special it's the individual receiving the loaner; for Generic Use it's the TCA/ATCA who will operate the venue terminal.
  5. Scan each iPad's wallpaper QR. Stay on the same scanner overlay and point the camera at each device's wallpaper in turn. The wallpaper QR encodes the device's identity (city, sequence, hardware serial) — see Wallpaper QR format below.
  6. Review the scanned list. Each successful scan adds a row showing the device serial and current condition.
  7. Tap Submit. The batch is created with status In Store; iPads are now bound to this batch but custody hasn't moved yet — they're still in the venue store waiting to be handed out.

The scan validates as you go

If a scanned device is unrecognized (not in the city's enrolled inventory), already belongs to another active batch, or is in Damaged or Missing condition, an inline alert flags the issue and that device is not added. The batch cannot be submitted until every scanned device is recognized, free, and in Clear condition. Resolve the flagged devices first (return the conflicting batch, or replace the damaged unit via Warehouse), then rescan.

Swipe actions on a batch row

Once a batch exists, every action you can take on it is reachable via a swipe on the Batches list. Swipe-left exposes outbound actions; swipe-right exposes inbound and administrative actions. Each button is tinted distinctly so the action is recognizable at a glance:

DirectionActionSF SymbolTint
LeftAdd Deviceplus.circle.indigo
LeftHand Outtray.and.arrow.up.fill.orange
RightTransferarrow.left.arrow.right.circle.blue
RightHand Backarrow.uturn.backward.circle.green
RightCancel Batchxmark.bin.red
RightHistoryclock.arrow.circlepath.gray

Hand Out (swipe left → Hand Out)

Marks the batch as handed to the assigned user. Records the timestamp on the batch and on every iPad inside it. Custody moves from In Store to With User. After this point, the batch appears on the assignee's Home screen.

Add Device (swipe left → Add Device)

Opens the same scanner overlay used during creation, scoped to this existing batch. Use it to (a) scan a fresh iPad into a Student batch that hasn't hit its 20-cap, or (b) hand out a previously held-back iPad without creating a new batch. The cap, mode validation, and condition checks all still apply.

Hand Back (swipe right → Hand Back)

Returns the batch (and all its iPads) to the venue store. Records the return timestamp. The flow prompts the user to mark each device's final condition (Clear / Damaged / Missing) before confirming. Damaged or Missing devices are flagged for Warehouse follow-up; Clear devices become eligible to be added to a new batch the next day.

Transfer (swipe right → Transfer)

Reassigns custody of an active batch from one user to another without the intermediate hand-back step. Useful for shift changes mid-event, or when a Proctor leaves early and another picks up the same student session.

Cancel Batch (swipe right → Cancel Batch)

Voids the batch entirely. All iPads inside are released and become free to be added to another batch. Use this when a batch was created in error, or when an event is cancelled before any session begins. Cancelled batches stay in History but are excluded from active dashboards.

History (swipe right → History)

Opens the batch's full audit trail: created → handed out → handed back → cancelled, etc. Each event shows the actor, timestamp, and from/to custody. See the History screenshot below.

Per-device actions inside a batch

Tap the chevron on a batch row to expand it and see every iPad inside. Each device row has its own actions independent of the batch-level swipe:

  • Edit (per-device) — change the device's condition (Clear / Damaged / Missing) without affecting the rest of the batch. Use this when, for example, a candidate reports a screen issue mid-session and you want to flag that single device without disturbing the others.
  • Replace (per-device) — swap a defective or missing device for a working spare. The replacement scanner opens, you scan the new wallpaper QR, and the new device inherits the old one's slot in the batch (and seat code, in Student batches). The old device is marked Damaged or Missing on the way out.
  • Return (per-device) — return a single device back to store while leaving the rest of the batch with the assignee. Useful when a candidate finishes early or leaves mid-session.

So yes — you can hand-back, replace, or change condition for a single device of a batch without touching the rest of it. The batch-level swipe actions operate on the batch as a whole; the per-device actions operate on one iPad.

Seat math

  • 20 devices per line in a student batch. Lines are alphabetical: A, B, C … Z, then AA, AB, …
  • Lines per city are calculated as ceil(seatCapacity / 20). Riyadh and Jeddah at 500 seats each get 25 lines (A–Y); Dammam at 400 gets 20 lines (A–T); Abha and Mubarraz at 250 each get 13 lines (A–M) where line M holds 10 seats; Medina and Ras Tanura at 225 each get 12 lines (A–L) where line L holds 5 seats.
  • Backup seats use suffix BAK1, BAK2 — not counted in the 20-per-line cap.
  • Seat codes encode as CITYCODE-LETTER-NN, e.g. RIY-A-07.
  • Replacement keeps the seat — if device T9GN9Y37GH at RIY-A-07 fails and you replace it, the new device inherits seat RIY-A-07 and the batch document updates in place. The old device gets a replacement document stub.

Smart search

The Inventory search bar parses 4 inputs: a city code, a batch code, a seat code, or a device serial. The parser auto-detects which and narrows the list instantly. Partial matches work — typing RIY-A narrows to all seats in line A of Riyadh.

Scanner formats

The Inventory scanner reads QR, Code128, Code39, EAN13, EAN8, and UPCE. During a scan, the device-condition cycle is three-state: clear → damaged → missing. Tap the condition pill to advance the state before confirming the scan.

Wallpaper QR format

Each enrolled iPad carries a wallpaper QR generated by the app. The payload format is:

CITYCODE-X-SEQ(SERIAL)
Example: RIY-X-13(T9GN9Y37GH)

The X is a literal segment indicating "cross-batch mobile asset". The sequence is the numeric order in which the device was enrolled in the city. The serial in parentheses is the hardware serial — this is what warehouse scans match against.

Dashboard tabs

The Inventory Dashboard has three top tabs:

  • Logs — chronological list of every inventory event. Filterable by batch, user, action type.
  • Box QR Labels — launches the labeling workflow. See the Box Labeling section.
  • Dashboard — aggregates: devices by status, by condition, by city, by mode. Used for end-of-event reconciliation.
Batch History sheet showing Batch Handed Out, Handed Back, and Created events with timestamps
Batch History — tap History on any batch to see its full audit trail: created → handed out → handed back → handed out again, each with actor, timestamp, and from/to custody.
Inventory Dashboard showing Total Batches, In Store, With User, Partially Returned, Returned, Cancelled, and Batch Types breakdown
Inventory Dashboard — top row is Batch Inventory (Total, In Store, With User, Partially Returned, Returned, Cancelled). Bottom is the Batch Types breakdown (Student, Staff, Special, Generic) — ideal for end-of-event reconciliation.
Operations

Warehouse pipeline.

The prep flow every box travels before an event — from unstaged assets on the warehouse floor, through configuration and enrollment, out to the venue, and back for a final count. Ten stages, strict transitions, and an Admin recovery path for anomalies.

The ten stages

01Not Staged
02Staged
03Configuring
04Configured
05Enrolling
06Ready to Ship
07Shipped
08In Venue
09Back to WH
10Completed
StageColorWhat happensWho advances
01 Not Staged (.notStaged).secondaryDevices exist in the warehouse but have not been selected for this event.System (default state)
02 Staged (.staged).blueDevices selected and physically gathered for the event. The box has an identity, but its contents are not yet configured.Warehouse role
03 Configuring (.configuring).indigoDevice profiles, restrictions, and any custom settings are being applied. Supervised mode, MDM payload, wallpapers.Warehouse + Tech
04 Configured (.configured).purpleAll settings applied and verified. Box ready to be enrolled into the event scheme.System (auto on config complete)
05 Enrolling (.enrolling).tealWarehouse operator scans each device serial into the event scheme. The box gets its scheme tag and manifest.Warehouse role
06 Ready to Ship (.readyToShip).mintAll serials confirmed. Box sealed, labeled via the labeling wizard, and placed in the logistics queue.Warehouse role
07 Shipped (.shipped).brownLogistics has picked up the box. Handover approval state moves pending → approved. Box is in transit.Warehouse + TCA
08 In Venue (.inVenue).greenBox delivered on-site. TCA confirms receipt with a scan.TCA
09 Back to WH (.returnedToWH).orangePost-event return. Devices come back to the warehouse, get counted and condition-checked. Missing devices flagged.Warehouse role
10 Completed (.completed).primary (white in dark mode, black in light)Cycle complete. Boxes reset and available for the next scheme.System (auto)

Working with boxes

The Warehouse screen's Boxes tab groups every box by stage. Tap any stage to expand its list; each box row shows its number, device count, and a stage chip (color-matched to the pipeline). Tap a box to expand device-level detail with per-device condition pills (Clear / Damaged / Missing / Malfunction) and lifecycle state (Active / Backup / Retired).

Warehouse Boxes tab showing the six stages with collapsible counts
Stage overview — Not Staged (33 / 162), Staged (3 / 15), Configured (8 / 39), Enrolling (1 / 5), Ready to Ship (532 / 2660), In Venue (1 / 5). Each row is tappable to expand.
Configured stage expanded showing BOX #0092 with four device rows
Box expanded — expanded Configured list showing BOX #0092 with 4 device rows. Each device displays its serial (twice: label + identifier), its condition pill (Clear), and its lifecycle pill (Active).
Swipe actions on a box row revealing All Clear, All Missing, Reset Prep, Review, Box Label, Venue Ship Label
Box swipe actionsAll Clear, All Missing, Reset Prep, Review, Box Label, Venue Ship Label. Batch-apply condition across every device in the box, or launch the labeling wizard for this box only.
Swipe actions on a device row revealing All Active, All Backup, All Retired
Device swipe actionsAll Active, All Backup, All Retired. Lifecycle mass-updates scoped to the current box.

HandoverState values

The Ready to Ship → Shipped → In Venue transitions use a separate state machine for approvals:

none
Pre-handover. No approval pending yet.
pending
Warehouse has marked the box Ready to Ship; logistics / TCA has not yet acknowledged.
approved
TCA has scanned and confirmed receipt at the venue. Stage advances to In Venue.
rejected
TCA found missing or damaged boxes; warehouse must reconcile before advancing.

The start() method — four cases

Opening a box in the warehouse view triggers WarehousePrepFlowService.start() which branches into four cases based on the box's current state:

  1. New box — never enrolled. Creates a fresh prep flow document at stage 01.
  2. Label mismatch — scanned label doesn't match any box document. Surfaces a "Label not recognized" error with a suggestion to restage.
  3. Not-staged restage — box exists but is at stage 01. Resumes enrollment from the current scan cursor.
  4. Completed restage — box was fully cycled but is being reused for a new event. Prompts for confirmation, then resets to stage 01 with a fresh scheme tag.

Strict logistics transitions

The advanceLogisticsStage method enforces a transition table. You cannot skip stages or jump backward without the Admin recovery permission. Valid forward transitions:

Not Staged     → Staged
Staged         → Configuring
Configuring    → Configured
Configured     → Enrolling
Enrolling      → Ready to Ship
Ready to Ship  → Shipped         (requires handover pending)
Shipped        → In Venue        (requires handover approved)
In Venue       → Back to WH
Back to WH     → Completed       (requires reconciliation complete)

Enrollment recovery

When enrollment stalls mid-flight (e.g. app crash during serial scanning), a user with the warehouseEnrollRecovery permission can trigger the recovery flow. This is gated behind a confirmation alert to prevent accidents, and every recovery event writes an audit record.

Summary dashboard

The Warehouse → Summary tab aggregates KPIs for the current scheme: boxes by stage, operator productivity, avg stage duration, and an employee-activity drilldown that lets you click any operator and see every action they took across the event cycle.

Recovery is not reversal

Recovery resumes a stalled flow — it doesn't undo a completed stage. To reverse a stage advancement, an Admin must use Manage Settings → Warehouse to manually edit the stage field with a reason in the audit log.

Operations

Box labeling.

A three-step wizard that creates standardized PDF labels for boxes and venue shipments. Deterministic payloads ensure the same box always generates the same label, regardless of who prints it.

The three-step wizard

  1. Scan Box — scan the box's QR wallpaper. If the box is new, you're prompted to create a box document. If existing, you see a summary of what's currently inside.
  2. Scan Serials — scan each device serial going into the box. Max 5 serials per label. The wizard validates against the inventory to prevent double-boxing.
  3. Preview & Print — preview the generated label (A4 or Letter landscape). Confirm to print and advance the box to Ready to Ship.

Validation states

newBox
No prior document. Wizard creates one.
existingClean
Box exists with matching contents. Re-print only.
existingInconsistent
Box exists but contents don't match scan. Prompts to reconcile.
locked
Box has advanced past Ready to Ship. Labeling is blocked; contact Admin.

Venue shipping labels

For venue-wide shipping, a separate PDF template renders seat mappings: 20 seats per line, 15% margin around the edge, with city code banner at the top. Gender-mixed flag is currently a TODO — hold for the next scheme release.

Serial constraints

  • Serial length: 8–14 characters.
  • Alphanumeric only (A-Z, 0-9). Lowercase is auto-uppercased.
  • No separators, no hyphens.

Deterministic JSON payload

Every label encodes a JSON payload that the scanner can round-trip without ambiguity:

{
  "box": "BOX-RIY-2026-0047",
  "scheme": "2026",
  "city": "RIY",
  "serials": ["T9GN9Y37GH", "T9GN9Y38GH", "T9GN9Y39GH"],
  "mode": "studentSeatMapped",
  "version": 1
}
Operations

Settings & configuration.

Every rule the app follows lives here — business schedules, special periods, role activation, role permissions, cities, asset and container types. Settings is admin-only; non-admins won't see the entry in their menu.

Business Schedule

Each Business Schedule is a named window with its own working hours and reporting times. Multiple schedules coexist (one for each event), and the active schedule is whichever one currently brackets today's date. The schedule sets the daily threshold against which OT is calculated and the expected reporting times the iPad enforces during attendance scans.

Business Schedule list showing 2026 CDPNE NEST Preparation and 2025 APNE/VCGNE schedules with start/end dates, working hours, and per-day reporting times
Business Schedule list — one entry per event. Each shows title, start/end dates, working hours, and per-day reporting times (Weekdays / Friday / Saturday). At the bottom: Add New Business Schedule and Manage All Schedules for archive/edit access.

Long-press on any schedule reveals a context menu with two actions: Delete and Periods (open the Special Periods sub-editor for that schedule). Editing reporting times after attendance has been logged is allowed but will not retroactively re-classify existing records as on-time/late.

Long-press context menu on a Business Schedule showing red Delete button and orange Periods button
Schedule actions — the long-press menu. Delete removes the schedule (records remain in audit). Periods opens the Special Periods editor scoped to that schedule.

Special Periods

Overrides applied for specific date ranges within a schedule. Three built-in types:

  • Holiday — 100% OT. Name and date range required (e.g. "National Day" · Sep 23–24).
  • Ramadan — reduced daily threshold (default 6.0 h). Lunch block is swapped for an Iftar break.
  • Special Duty — custom schedule for high-intensity events. Free-form hour overrides.

Cities Management

Cities are the geographic scope of every venue, batch, and access profile. Each city has a 3-letter authoritative code, a display name, GPS coordinates (used by the QR's location stamp during attendance), a seat count, and an active/inactive flag. Inactive cities don't appear in role assignment dropdowns but their historical records remain queryable.

The system currently has 12 cities defined, 7 active and 5 inactive, with a combined active capacity of 2,350 seats:

CityCodeStatusLatitudeLongitudeSeatsVenues
RiyadhRIYActive24.71360046.6753005001
JeddahJEDActive21.38910039.8579005002
DammamDAMActive26.42070050.0888004000
AbhaABHActive18.22801642.5473872500
MubarrazMUBActive25.42910049.5789002500
MedinaMADActive24.44605739.6392982250
Ras TanuraRAHActive26.70920350.0548302251
TabukTBKInactive28.39895136.58347800
DhahranDHAInactive26.23610050.03930001
AbqiaqABQInactive25.95761949.72141600
SayhatSYHInactive26.48440050.04060000
YanbuYANInactive24.08559038.06358000
Cities Management list with Tabuk, Riyadh, Ras Tanura, Abha, Abqiaq cities; latitude/longitude/seats per city; INACTIVE chips on some, venue counts in blue, and Activate/Edit action pills on a long-pressed row
Cities Management — each row shows code, display name, lat/long, seat count, and venue count. Inactive cities show a red INACTIVE chip. Long-press reveals two actions: Activate (green play button) and Edit (blue pencil). New cities are added via Add New City at the bottom.

Code is authoritative

The 3-letter city code is used in QR payloads, box labels, seat maps, and Firestore paths. Changing the code for an existing city will break historic records and is blocked by the UI.

Activate Roles

The 10 system roles (Admin, Manager, TCA, ATCA, Moderator, Proctor, Tech, Helper, Student, Warehouse) are all defined in code, but each can be independently activated or deactivated for the current event. Deactivating a role hides it from "Add User" dropdowns and from broadcast routing, without deleting any users assigned to it. Switch a role back on at any time and existing users resume.

Activate Roles modal, All tab, showing 10 roles each with a toggle: ADMIN, MANAGER, TCA, ATCA, MODERATOR (off), PROCTOR, TECH, HELPER (off), STUDENT (off), WAREHOUSE
All tab — every role with its current state. The default for a fresh install is Admin / TCA / ATCA / Tech / Warehouse on; Moderator / Helper / Student off (Student is a seed role, not a login role).
Activate Roles, Active tab, listing only the roles currently turned on
Active tab — filter to only the roles currently in use. Useful for a quick sanity-check before going live with an event.
Activate Roles, Inactive tab, showing Moderator, Helper, and Student as the disabled roles
Inactive tab — only the disabled roles. Use this view when re-enabling a role mid-event after deciding to bring on Helpers or Moderators.

Roles & Permissions

Each role has a default permission bundle. Edit a role's bundle via long-press → Edit; clone an existing role's bundle to a new role via long-press → Replicate. The bundle assigned at the role level applies to every user with that role, but individual users can have permissions added or revoked from this default in Manage Users → Edit Access Profile.

Roles & Permissions screen showing MANAGER, TCA, ATCA permission strings (manualSelfAttendance, scanQR, broadcastCenter, generateQR, warehouseLabeling, manageUsers, viewAllAttendance, enableScanMode, etc.) with Replicate and Edit pill at top
Roles & Permissions list — each role's default permission bundle is laid out as a comma-separated string for quick visual diffing. Long-press any role to reveal Replicate (clone bundle to another role) and Edit (open the per-permission toggle editor).

15 named permissions are recognized by the app:

Access Profile sheet showing Role, Masquerading, Area, Assigned City, Accessible Venues, and Schedules
Access Profile sheet — opens from profile menu → Access Profile. Header shows role + masquerade state; the Area block lists assigned city plus every accessible venue; the Schedules block shows the current schedule window. Below this (next section), Menu Options list which routes are enabled for you.
PermissionWhat it allows
manualSelfAttendanceLog your own check-in/out without scanning a QR.
attendanceEditOthersEdit another user's attendance log. Bypasses the 3-day window.
broadcastSendPasswordSend Password-type broadcasts.
broadcastSendExecutiveSend Executive-type broadcasts.
broadcastSendDirectionSend Direction-type broadcasts.
broadcastSendGeneralSend General-type broadcasts.
broadcastRecallGlobalDelete broadcasts globally after the 120 s sender window.
inventoryReadView the Inventory dashboard.
inventoryWritesCreate, modify, or cancel batches.
warehouseReadsView the Warehouse dashboard and pipeline.
warehouseWritesAdvance stages, approve handovers.
warehouseLabelingAccess the box labeling wizard and print labels.
warehouseEnrollRecoveryRecover stalled enrollments (gated with confirmation).
manageUsersAdd, edit, remove users. Change access profiles.
manageSettingsEdit business schedule, special periods, cities, role permissions.

XBadge (internal badge-number encryption)

XBadge is an internal helper that obfuscates a user's real badge number before it's written to Firestore. Rather than persisting the literal badge number, the app applies a simple encryption transform and stores the resulting code, so the underlying value isn't readable directly from the database. It is an implementation detail of the user-record layer — there is no XBadge configuration UI to interact with, and it has no relationship to the Broadcast Center, the Password channel, or any operational workflow visible to users.

Warehouse asset & container types

Configure the catalog of items the warehouse tracks and the catalog of containers it puts them into. The whole warehouse pipeline — staging, configuring, enrolling, shipping, receiving — operates against these two catalogs, so any item not enabled here is invisible to every warehouse screen.

Warehouse settings list showing iPads, Routers, Data SIMs, eWriters, Mount Stands above a Manage Asset Types entry, then iPads Box, Charging Suitcase, Suitcase, Trolley, Carton, with capacity numbers, above a Manage Container Types entry
Warehouse settings landing — top half is asset types (iPads, Routers, Data SIMs, eWriters, Mount Stands, etc.) with Manage Asset Types at the bottom; lower half is container types (iPads Box, Charging Suitcase, Suitcase, Trolley, Carton) with their capacity counts on the right and Manage Container Types at the bottom.

Both Manage Asset Types and Manage Container Types open a modal where each row has two toggles:

  • Enabled — the type appears in pickers across the app (Inventory, Warehouse, Add Device).
  • Tracked — the warehouse pipeline records lifecycle events for individual instances of this type. A type can be Enabled but un-Tracked (it's pickable, but you don't care to track each one) — useful for accessories like covers or charging bricks where a count is enough.
Asset Types modal listing iPads, Routers, Data SIMs, eWriters, Mount Stands, Barcode Readers, Power Extensions, iPad Covers, Charging Bricks with Enabled and Tracked toggle columns. Most are Enabled+Tracked; iPad Covers is disabled
Asset Types modal — each row has two independent toggles: Enabled (visible in pickers) and Tracked (per-instance lifecycle in the warehouse). Disabled types stay in the catalog but never surface in the UI.
Container Types modal listing iPads Box, Charging Suitcase, Suitcase, Trolley, Carton, Other with capacity dashes and Enabled/Tracked toggles. Only iPads Box is Enabled+Tracked
Container Types modal — same two toggles plus a Cap. column for default device capacity. iPads Box is the only fully-on container by default; the rest sit dormant until your operation needs them (e.g. a regional team that uses Trolleys for distribution).

Help instructions

A three-state setting controlling in-app onboarding tooltips:

.none
No tooltips. For experienced users.
.brief
Short, one-line tooltips on hover/long-press.
.full
Full explanations with links into the help center.

First-run seed

On first install, three roles seed automatically: Admin, TCA, Proctor. Default Admin gets most permissions except inventoryWrites, warehouseWrites, and warehouseLabeling — these must be explicitly granted to avoid accidental warehouse actions.

Reference

Permission matrix.

Default permission bundles per role. An Admin can override any cell for a specific user via Manage Users → Edit Access Profile.

PermissionAdminManagerTCAATCATechWarehouseProctor
manualSelfAttendance
attendanceEditOthers
broadcastSendPassword
broadcastSendExecutive
broadcastSendDirection
broadcastSendGeneral
broadcastSendSupport
broadcastRecallGlobal
inventoryRead
inventoryWrites—*
warehouseReads
warehouseWrites—*
warehouseLabeling—*
warehouseEnrollRecovery
manageUsers
manageSettings

* Default Admin excludes inventory/warehouse writes to prevent accidental stock mutations. Granted explicitly per user as needed.

Seeing your own permissions

Any user — not just Admins — can see exactly what they have access to by opening profile menu → Access Profile and scrolling to Menu Options. Each menu route has a green dot (granted) or grey dot (denied). It's the quickest way to answer "why can't I see X?".

Access Profile Menu Options list showing View All Attendance, Scan QR, Manage Settings, Warehouse Labeling, all with green granted dots
Menu Options in Access Profile — each row is a menu route in the app; the green dot on the right means the route is enabled for this user. An Admin sees every dot green; a Proctor sees most of them grey.
Reference

Masquerade.

Admin-only superpower: see the app exactly as another user sees it. Invaluable for debugging "why can't they see X" and for on-the-fly permission tuning.

Starting a masquerade

  1. Go to Manage Users. Search or filter to find the target user.
  2. Long-press their row. A context sheet appears with Masquerade as... at the top.
  3. Confirm. The app rebuilds its navigation split view against the target's Access Profile.
  4. You're now them. A persistent red banner at the top reads "Masquerading as [Name]" with a Stop button.
Masquerading as a TCA named Hind, showing sidebar changed to TCA's Menu and an overflow menu with Edit Profile, Show Access Profile, Stop Viewing As
Masquerade in action — note the sidebar title is now TCA's Menu (not Admin's), with the orange "View as: Hind Al…" indicator up top. The overflow menu gives direct access to Show Access Profile (what she can see) and Stop Viewing As to exit.

What changes while masquerading

  • Home fully re-renders for the target's role and permissions.
  • Broadcasts you send are attributed to the target (use with care).
  • The scanner will behave as the target role (e.g. no warehouse scan if target lacks permission).
  • Your own notifications still arrive — they're not redirected.

Ending a masquerade

Two ways to exit:

  • Tap Stop in the red banner.
  • Open your profile menu and tap Stop Masquerade.

SuperAdmin lockout

The email my2komait@gmail.com is hardcoded as SuperAdmin. Nobody — not even another Admin — can masquerade as the SuperAdmin. This prevents the app from being used as an admin-escalation vector.

Implementation notes · UserAccessProfile rebuild

Masquerade triggers Notification.didSwitchMasquerade. The root ContentView observes this notification and rebuilds the UserAccessProfile via UserAccessProfile.build(), which re-derives the visible admin options, broadcast eligibility, and scanner capabilities.

See AppNotifyRouting.swift for the notification plumbing and UserAccessProfileStore.swift for the profile cache.

Reference

Errors & limits.

When something goes wrong, the app surfaces a human-readable message with an error code. This page is the canonical list — match the code you see to the row here for what it actually means and how to fix it.

Warehouse prep flow errors

CodeUser-facing messageMeaning & fix
WPF001"Box not recognized."Scanned wallpaper doesn't match any box document. Restage or contact Admin.
WPF002"Serial already enrolled."This serial belongs to another box in the current scheme. Can't double-enroll.
WPF003"Stage advance blocked."Strict transition violated. Check current stage against the transition table.
WPF004"Handover not approved."Cannot advance to In Venue until TCA approves receipt at the venue.
WPF005"Label mismatch."Printed label QR doesn't match scan. Regenerate the label.
WPF006"Max serials exceeded."5 serials per label maximum. Split into multiple labels.
WPF007"Scheme mismatch."Box was created under a different event scheme. Restage to re-tag.
WPF008"Recovery not permitted."User lacks warehouseEnrollRecovery permission.
WPF009"Box locked — contact admin."Box has advanced past Ready to Ship. Labeling blocked.
WPF010"Rejected handover."TCA rejected this handover. Reconcile missing/damaged contents.
WPF011"City mismatch."Scanned box belongs to a different city than your scope.
WPF012"Scheme expired."Event scheme is past its window. Archive or extend via Admin.
WPF013"Duplicate box."Two boxes share the same label — only possible if a label was reprinted incorrectly.
WPF014"Stage rewind blocked."Reverting a stage requires Admin intervention with a reason.
WPF015"Reconciliation incomplete."Cannot advance from Back to WH to Completed while devices remain unaccounted.
WPF016"Network unavailable."Warehouse writes require connectivity. Reconnect and retry.
WPF017"Scan buffer full."Too many simultaneous scans queued. Wait and retry.

Common Firestore errors

  • PERMISSION_DENIED — your Access Profile doesn't allow the requested action. Check your role permissions.
  • UNAVAILABLE — Firestore is reachable but the region is temporarily degraded. Retry in 30 s.
  • DEADLINE_EXCEEDED — the write took longer than allowed. Network is slow — retry on a stronger connection.
  • FAILED_PRECONDITION — a required index is missing. Surfaces during Admin analytics queries. Contact support.

Offline behavior

When the network drops, an offline overlay appears across every screen of the app. Writes are not cached — you must be online for attendance, broadcasts, and inventory writes. Reads of recent data remain visible via the last-cached state.

Hard limits

QR validity
15 seconds
Double-scan buffer
60 seconds
Self-edit window
3 days
Global-delete window
120 seconds from send
Broadcast retention
300 days
Student batch cap
20 devices / batch
Serials per label
5 maximum
Serial length
8–14 characters
Location accuracy
~10 m (nearest ten meters)
Location stamp precision
4 decimal places
Reference

Glossary.

Every term, every acronym, every shorthand that shows up in the app or in this help center. Alphabetical.

AES-GCM
Authenticated Encryption with Associated Data using Galois/Counter Mode. Used for broadcast message body encryption.
APNs
Apple Push Notification service. Delivers pushes to iOS devices.
ATCA
Assistant Test Centre Administrator. Rank 40 role. Supports the TCA.
Batch
A group of devices assigned together for an event. Four modes: student, staff, special, generic.
Broken log
Attendance anomaly flagged by the system. Five categories: missing check-in, missing check-out, out-of-order, duplicate, wrong city.
City code
Three-letter authoritative identifier for a city. Active set: RIY (Riyadh), JED (Jeddah), DAM (Dammam), ABH (Abha), MUB (Mubarraz), MAD (Medina), RAH (Ras Tanura). Inactive: TBK, DHA, ABQ, SYH, YAN.
Common-use iPad
Shared iPad running persistent biometric-locked scanner mode at the venue desk.
Custody state
Where a device currently lives: With Warehouse, In Transit, In Venue, With User, Returned.
DEK
Data Encryption Key. Per-message key wrapped via HKDF.
FCM
Firebase Cloud Messaging. The push-notification layer the app uses.
Handover
Stage 06 of the warehouse pipeline. Logistics pickup, TCA approval required to advance.
HKDF-SHA256
HMAC-based Key Derivation Function using SHA-256. Used to derive per-message wrap keys.
Manual attendance
Check-in/out logged via a form instead of QR scan. Requires permission. Flagged in audit.
Masquerade
Admin-only feature: see the app as another user for debugging.
Rank
Numeric weight from 1 (Student) to 100 (Admin). Drives broadcast eligibility and approval chains.
Scheme
Event-year identifier, e.g. "2026". Tags all boxes and batches for that cycle.
SoD
Start of Day. Marks the Business Schedule reference point.
Support lifecycle
Four states a support ticket moves through: none → open → inProgress → resolved.
TCA
Test Centre Administrator. Rank 50 role. Runs a single venue.
WOW Room
The operations command center during screening test events — the hub where real-time decisions happen. myProctor extends that same command experience to mobile devices: "The WOW Room in your Pocket" (iPhone, on the move) and "in your Hand" (iPad, at the desk). The WOW Room goes where you go.
XBadge
Internal helper that encrypts a user's badge number before it's written to Firestore. Implementation detail of the user-record layer; not user-facing and unrelated to broadcast messages.