# PRD — myPass-App (MyPASS)

Evidence date: 2026-10-09. Source: `git@bitbucket.org:overdrive/mypass-app.git`, `master` at `d8d04bf` (2022-06-22).

This document records behaviour found in that revision. It does not decide what the product should become. Provenance labels: **Documented**, **Implemented**, **Inferred**, **Planned**, **Unknown**.

There is no README. Statements below are **Implemented** unless another label is given.

## Product purpose

MyPASS lets a person who already has an accreditation reference sign in on a phone, see event and health status, show a pass with a QR code, update their profile until an event is approved, submit a Covid-19 symptom check, and upload a vaccine or test certificate.

The shell is a portrait standalone PWA named "MyPASS" (`_manifest.json`). Theme colour is `#E9573F`. Start URL is `index.html`.

## Users

| User | Evidence | Confidence |
|---|---|---|
| Accredited person | Sign-in asks for a reference number and password. Home, pass, health, and profile all read `MyPass-LoggedInUserData` from `localStorage`. | High. **Inferred** that this is the only real user of the shell. |
| Person opening a symptom or result link | `index.html?page=symptom_step_1&userID=...` and `page=screen_result_review&userID=...&srid=...` skip the normal sign-in gate and hide the footer and menu. | High |
| Person applying for accreditation | `registration_form.html` posts `postaccreditation` for event id `304`. No menu item loads it. | Medium. Entry from outside this shell is **Unknown**. |
| Organisation admin | Side menu section "MyORGANISATION" (Home, Events, Scan, Members) uses class `disableMenu`. No script enables those links. | The items exist. They do not run. |
| Anonymous visitor | With no session and no query string, `indexLoad` sends the browser to `sign_in_sign_up.html`. | High |

No role table, permission matrix, or admin screen is implemented.

## FR-001 — Open the app

Requirement: The browser loads `index.html`. A service worker registers `/_service-worker.js` and caches the shell (`Cache-v3.06` in `_service-worker.js`, registration in `scripts/app.js` and `scripts/custom.js`). An install icon is shown when the browser fires `beforeinstallprompt`. Choosing it runs the deferred install prompt.

Trigger: Visit the start URL, or launch the installed PWA.

Output: Preloader, footer (Home, Health, MyPASS, My Profile), title bar, empty `#page-content`.

Error: If a content fetch fails, a toast says "Unable to find page!".

Implementation: Implemented.

## FR-002 — Session gate

Requirement: On `index.html`, if `localStorage` key `MyPass-LoggedInUserData` is set, the shell loads `home_screen`, or the page named by `?page=` and `?params=` (including a stored `MyPass-AfterLoginRedirect`). If that key is missing and the URL has no search string, the browser goes to `sign_in_sign_up.html`.

The onboarding redirect to `Onboard.html` is commented out in `indexLoad`. `Onboard.html` still exists and, if opened directly, walks three screens and then sets `MyPass-showGetStarted` and returns to `index.html`.

Deep links that are allowed without an existing session:

- `page=symptom_step_1` or `page=screen_result`, with optional `userID` and `eventID`. Missing ids become user `0` and event `346`.
- A dedicated branch for `page=symptom_step_1` when logged out, which forces event id `346`.
- `page=screen_result_review` with `userID` and `srid`, which forces event id `346`.

When `userID` is present, `userAutoLogin` calls `getlogin` with that reference and a fixed password literal stored in `scripts/core.js`. If `login` is not `1`, a toast says "Could not auto login" and the browser goes to `sign_in_sign_up.html` after 3 seconds. If the returned reference does not equal `userID`, `localStorage` is cleared.

If `userID` is `0`, `blankPerson` writes a synthetic profile (random `AMID` between the generated range in that function, `FirstLogin` `"N"`) and does not call the API.

External symptom and result screens hide the footer, menu, and user icons, and show the add-user icon, which calls `logout()`.

Implementation: Implemented. Whether the fixed auto-login password is still accepted by the API is **Unknown / Requires Confirmation**.

## FR-003 — Sign in

Requirement: On `sign_in_sign_up.html`, Sign In opens the sign-in sheet. Reference number and password are required. The reference is upper-cased. `POST getlogin` is sent with `{refNumber, Password}`.

When `json.login == 1`, `json.data` is stored as `MyPass-LoggedInUserData` and the browser goes to `index.html`. Otherwise the sheet shows "Invalid username or password!".

Enter on the page calls `SubmitSignIn`.

A call to `getfirstlogin` after a successful login is commented out. The comment would have opened the first-login sheet when `FirstLogin == 1`.

Empty fields list "- Reference Number" and/or "- Password".

Failed HTTP from `requestAPI` is written to the console. The spinner is not hidden in that `catch`.

Implementation: Implemented. Passwords are sent as JSON text. Branch `encryption` (`1fc083c`, not on `master`) is titled "md5 hashing on password". Hashing is not in this revision.

## FR-004 — First login and forgot password

Requirement: The landing buttons include "First Login", and the sign-in sheet links "Forgot Password?" to the same verify sheet (`menu-firstloginVerify`).

Verify requires a reference number, a mobile number of at least 10 characters, and the terms checkbox. A leading `0` is stripped from the mobile number. `POST getpwdreset` is sent with the upper-cased reference, `mobileNumberPre + mobile`, and `mobileNumberPre`.

When `json.login == 1`, `AMID` is stored as `MyPass-AMID` and the set-password sheet opens. Otherwise a toast says "Unable to verify account".

Set password requires at least 6 characters and a matching confirmation. `POST postpwd` with `{AMID, Password}`. When `json.updated == 1`, `MyPass-AMID` is removed and the sign-in sheet opens. Otherwise a toast says "Unable to update password".

Implementation: Implemented. The link text "Forgot Password?" and the button "First Login" open the same verify flow. Whether the API treats that as a reset or as a first login is **Unknown** (the client only checks `login` and `updated`).

## FR-005 — Sign up

Requirement: The sign-in sheet links to a sign-up sheet. Required fields: reference number, mobile number, password, confirm password, and the terms checkbox. Email is collected and is not required by this validation.

On success the client reads `./userData.json` and compares the typed username to `DATA[0].Username` only. If they match, it shows "Sorry, this username is already taken." Otherwise it alerts "continue register" and `POST`s to `userDataTest.json`.

`userData.json` in the repo is a two-row fixture (`TestUser1`, `TestUser2`). `userDataTest.json` is not in the repository.

Implementation: Implemented as a local stub. It does not call `footprintapp.net`. **Inferred** that sign-up is unfinished.

## FR-006 — Home

Requirement: `home_screen` reads the signed-in reference and requests `geteventsperuser/MemberReference/{ref}`.

Each event card shows banner `AccreBanner`, name `AccreditationName` (or "n/a"), `DateFrom`–`DateTo` (date portion), role `RoleDescription` (or "n/a"), and status `StatusDescription` coloured by numeric `Status` (see FR-016).

Empty list copy: "No events found".

The first event's `AC` is stored as `MyPass-HomeNextEventID`. Each card also stores `AccreditationStatus` as `MyPass-AccreditationStatus`. Nothing in `core.js` or `core2.js` reads that key back. Profile locking uses `StatusDescription` instead (FR-010).

Then, in parallel:

- `getupcomingtest` with `AMID` and `HomeNextEventID`. A row shows order number, requisition date, test type, and location under the title "SYMPTOM TEST". Empty copy: "No upcoming tests found".
- `getresults` with `AMID`. Cards by `HealthTitle`. "coronavirus" is labelled "Test History". "symptom check" is labelled "Symptom Check History". A symptom-check card opens `screen_result` with `eventID` and `screeningID`. A title of "SCREENING" shows the temperature. Empty copy: "No health results found".
- `gethealthcertificatevaccine` with `AMID`. One vaccine card, or "No vaccinations found". The card opens `add_health_certificate` with `existingCert: true`.

Implementation: Implemented. `JSON.parse` is applied to `HomeNextEventID` when calling `getupcomingtest`. If the id is not valid JSON, that call throws. **Inferred** failure mode, not a documented requirement.

## FR-007 — MyPASS card

Requirement: `my_pass` shows the stored photo (`User_pic_base64`, falling back to `images/avatars/profilePlaceholder.png` on image error) and the stored first and last name.

It loads the first event from `geteventsperuser`, then `getmypassdetails` with `AMID` and that event id. Status codes colour five icons: access, vaccination, screening, symptom, Covid test (FR-016). `QRCode` is drawn into `#screeningQR` at 200×200. An empty or null `QRCode` is replaced with the text `NoCode`, and that text is what the QR encodes.

Implementation: Implemented. The meaning of each status code, beyond the colour map, is **Unknown**. The API does not return labels that this screen prints.

## FR-008 — Health history

Requirement: Footer "Health" and menu "Health" load `screen_history`.

The page lists `getresults` for the signed-in `AMID`, then the next event and `getupcomingtest`, using the same card patterns as home. A floating add button (`#healthAddButton`) is shown on `screen_history` and on `health_certificates`. Its click handler is `doCertificationAdd()`, which opens `add_health_certificate`.

`doSymptomCheck()` is still in `core2.js`. It resolves a next event id and opens `symptom_step_1`. The add button does not call it. Symptom check is reached by choosing certificate type "Symptom Check" (FR-012) or by a direct page load.

Implementation: Implemented.

## FR-009 — Symptom check

Requirement: The active path is:

1. `symptom_step_1` — name, surname, ID/passport, dial code, mobile, email, gender (`M`, `F`, or `N`).
2. `symptom_step_3` — "Have you interacted with a person who has tested positive for Covid-19 in the last 7 Days?" Yes/No. Stored as `covidContact` "Yes" or "No". Labelled "STEP 2/3".
3. `symptom_step_5` — "Do you exhibit any of the following conditions or symptoms in the past 24 to 48 hours?" The list shows fever above 38°C, body aches, and loss of smell/taste. Yes goes to step 5b. No stores `symptoms` as `[]` and goes to confirmation. Labelled "STEP 3/3".
4. `symptom_step_5b` — checkboxes Fever, Cough, Sore Throat, Body aches, Loss of smell. Labelled "STEP 3B/3".
5. `symptom_step_confirmation` — declaration that the information is true, and permission for the event name (filled from `AccreditationName`) to use it for on-site Covid-19 risk. Submit stays disabled until "I Accept" is checked.

Submit calls `postsymptomcheck`. Response `status == "FAILED"` opens a warning modal titled FAILED. Any other status opens SUCCESS. The modal body is `response.date` and `response.SRID`. Closing a success modal opens `screen_result`. An HTTP error opens a warning titled ERROR and then `screen_history`. Local symptom keys are removed either way.

On step 1, if the stored profile `FirstLogin == "N"`, the client also `POST`s `postaccreditation` with event id `345`, terms and covid flags set to `1`, and `familyName` set from the first name field (the surname field is not copied into `familyName`). `blankPerson` sets `FirstLogin` to `"N"`, so an anonymous deep link can trigger that post.

Validation on step 1, skipped when `MyPass-external` is the string `"true"` (the external deep-link flag):

- ID/passport at least 6 characters
- name not empty
- mobile at least 8 characters
- email required and matched against the regular expression in `symptom_screening_submit_handler`

Event details for step 1 come from `getevent/EventID/{id}`: code, name, date, banner, terms. Missing event id toasts "Could not find the event!" and disables `.btn`.

`symptom_step_2` (high-risk travel) and `symptom_step_4` / `symptom_step_4b` (untreated conditions) are real HTML files and are cached by the service worker. `symptom_screening_submit_handler` has no branch for those page names, and step 1 navigates to step 3, which navigates to step 5. **Inferred:** those screens are not on the active path in this revision.

If step 1 is opened with a `screeningID`, `getsymptomcheck` loads prior answers, then overwrites name, surname, ID, mobile, email, and gender from the signed-in profile.

Implementation: Implemented. Pass/fail rules that produce `status: FAILED` are not in this repository. They are **Unknown** and belong to the API.

## FR-010 — Profile

Requirement: Profile view shows photo, full name, first name, family name, ID number, date of birth, mobile, city, and gender (Male, Female, or "n/a") from `MyPass-LoggedInUserData`. Empty strings display "n/a".

It also calls `geteventsperuser`. If any row has `StatusDescription == "Approved"`, the edit control loses its click handler and its button class changes from `bg-google` to `bg-grey`.

Profile edit loads the same stored fields into inputs. Birth year options run from 1930 through the current year. Saving sends `postuserprofile`. If the password field is non-empty, `postpwd` is also sent. A new image file is base64-encoded onto `profileImage`. Success toast "Details Updated", local storage is updated, and the view loads after 3 seconds. Failure toast "Details not updated".

Gender on edit is `F`, `M`, or `""`.

Implementation: Implemented. The profile view reads `loggedInUserData[0].length` when formatting the mobile number (`myprofile_view`). That is the array length, not the mobile string length. **Inferred** display bug. The edit screen uses `Mobile.length`.

`MyPass-AccreditationStatus` is written on home and never read. The comment in `home_screen` says it is for the edit icon. The icon gate that actually runs is `StatusDescription == "Approved"`.

## FR-011 — Health certificates

Requirement: Menu "Health Certificates" loads `health_certificates`.

`gethealthcertificatevaccine` renders one vaccination card (type, date, vaccine name, batch code, fully vaccinated Yes/No) or "No vaccinations found". Vaccine name ids: `0` N/A, `1` Johnson & Johnson, `2` Astra-Zeneca, `3` Novovac, `4` Pfizer, `5` Moderna, `6` Sputnik V. `certStatus == 1` is "Yes" and colour code 3. Any other status is "No" and colour code 2.

`gethealthcertificatetest` renders one test card (title, type, date, status) or "No test results found". Colour uses `testStatusCode`.

The vaccine card opens the add form in existing-certificate mode, which refills type, date, name, batch, and status from `gethealthcertificatevaccine`.

Implementation: Implemented. Both calls appear to expect a single object, not a list. Whether the API can return more than one certificate is **Unknown**.

## FR-012 — Add a health certificate

Requirement: The form title is "Beta Health Certificate". Types:

- COVID-19 Vaccine (requires a vaccine name; status labels become Fully Vaccinated Yes/No)
- COVID-19 PCR Test, LFD Test, Antigen Test (status labels become Test Result Positive/Negative; vaccine name hidden)
- Symptom Check (hides the form and sets the button label to NEXT; save calls `doSymptomCheck`)
- Scan (opens the camera scanner)

For vaccine and test types, day, month, year, and status are required. Year options run from 1930 through the current year, defaulting to the current year. Batch/code is optional (the required check is commented out). A file is required. Files over 3 MB are rejected. Accepted content markers for an existing data URI are `data:image/pdf`, `data:image/jpeg`, `data:image/gif`, `data:image/png`. Images preview in an `img`. PDFs preview with PDF.js.

Save posts `posthealthcertificate` with `AMID`, file data, extension, type, `YYYY-MM-DD`, name id, batch, and status, then loads `health_certificates`. Failure toast: "Unable to save!".

Scan: if the scanned value is "No QR code found", a toast says a QR code must be scanned. Otherwise a toast says "Still awaiting API CODE!" and the function returns. No API call is made. The scanner stops when `loadPage` runs.

Implementation: Implemented. The scan save path is an explicit placeholder in `doSaveCertificate`.

## FR-013 — Accreditation registration form

Requirement: Loading page `registration_form` calls `getevent` with `staticEventID` `304`. The response fills the title, category options (`Cats`), role options (`Roles`), and terms HTML. Birth years run from 1930 to the current year, with 1990 pre-selected.

Organisation search calls `getorganisation` with `ORefNumber` and writes `TradingName` into the organisation name field. A sample reference `07D40F4D5C91` appears only in a comment.

Submit builds `registerUser` (identity, address, pass name, organisation, role, file name, event id `304`) and posts `postaccreditation`. The response panel shows `EVENTNAME`, `REFNUMBER`, `EMAIL`, and first plus surname.

Gender defaults to `M` unless `#female` is checked. `tandcCheck` and `covidCheck` compare the checkbox `.value` to `true`. Both inputs have `value="1"`, which stays `"1"` whether or not the box is checked. In JavaScript `"1" == true` is true, so both flags are sent as `1` whenever those elements exist. The Covid checkbox's change handler only reveals the submit button. It does not re-check the box on submit.

Nationality selection tries to select a matching country and dial code by option text.

`scripts/registration_form.js` is commented out in `registration_form.html`. The live handler is `registration_form` in `scripts/core.js`.

Implementation: Implemented. No shell menu item calls `loadPage('registration_form')`. How a person reaches the form in production is **Unknown / Requires Confirmation**.

The public registration product documented under `silicon_ark/myPass-registration/` is a different repository and a later history. Do not read its field rules as rules of this form.

## FR-014 — Terms

Requirement: Menu "Terms and Conditions" calls `showTermsAndConditions()`, which opens modal type `termsAndConditions`. Event-specific terms from a symptom-check event can be shown via `showEventTermsAndConditions`. Accepting toggles a disabled style on the footer, title, and selected cards. The modal body for the generic terms type is whatever `popupModal` renders for that type; this PRD does not reproduce theme HTML as a legal text.

Registration terms HTML is `json.DATA.TermsAndConditions` from `getevent`.

Implementation: Implemented. The legal source of the generic modal text is the theme markup in `popupModal`, not a versioned policy file. The PDF under `docs/` was not transcribed.

## FR-015 — Sign out

Requirement: Menu "Log out", and the add-user icon on external screens, call `logout()`, which runs `localStorage.clear()` and goes to `index.html`. `indexLoad` then sends a person with no session to `sign_in_sign_up.html`.

Implementation: Implemented. This also clears theme, install-prompt, and PWA keys stored under the `MyPass-` prefix by `scripts/custom.js`.

## FR-016 — Status colours

Requirement: `getStatusColour` maps a numeric code to a theme class:

| Code | Class |
|---|---|
| 1 | `color-blue-dark` |
| 2 | `color-orange-dark` |
| 3 | `color-green-dark` |
| 4 | `color-red-dark` |
| other | `nocolor` |

Event cards replace `color` with `bg`. Several health icons replace `dark` with `light`.

What each code means in the accreditation or health domain is **Unknown**. The client does not define those names except for the vaccine Yes/No mapping in FR-011.

## FR-017 — Navigation chrome

Requirement: Footer and side menu load Home, Health (`screen_history`), MyPASS, and Profile. Menu also loads Health Certificates, Terms, Dark Mode, and Log out.

Disabled in the menu (`disableMenu`, or an `onclick` that is commented out): Events, MyORGANISATION Home, Events, Scan, Members, Settings, Admin.

The footer Events item is an HTML comment.

Dark mode is the theme toggle in `scripts/custom.js` (`MyPass-Theme` in `localStorage`). It is presentation, not a product rule.

Implementation: Implemented.

## Integrations

All product data calls use `requestAPI` unless noted. Default method is POST, `mode: cors`, body `JSON.stringify(parameters)`. Non-OK HTTP throws. The JSON body is returned to the caller. There is no auth header. The signed-in record in `localStorage` is the only client session.

| Action | Caller | Body or path | Client expects |
|---|---|---|---|
| `getlogin` | Sign-in, auto-login | `{refNumber, Password}` | `login == 1` and `data` array |
| `getpwdreset` | Verify account | `{refNumber, Mobile, mobileNumberPre}` | `login == 1` and `data[0].AMID` |
| `postpwd` | Set password, profile save | `{AMID, Password}` | `updated == 1` |
| `geteventsperuser/MemberReference/{ref}` | Home, pass, profile, symptom entry | path, no parameters object | array of events |
| `getupcomingtest` | Home, health | `{AMID, eventID}` | `data` array or `0` |
| `getresults` | Home, health | `{AMID}` | `data` array |
| `gethealthcertificatevaccine` | Home, certificates, edit | `{AMID}` | object with `certDate`, `certType`, `certNameID`, `certBatch`, `certStatus` |
| `gethealthcertificatetest` | Certificates | `{AMID}` | object with test fields |
| `posthealthcertificate` | Save certificate | file and certificate fields | any resolved JSON, then navigation |
| `getmypassdetails` | Pass card | `{AMID, eventID}` | status codes and `QRCode` |
| `getevent` | Registration | `{EventID: 304}` | `AccreditationTitle`, `Cats`, `Roles`, `DATA` |
| `getevent/EventID/{id}` | Symptom flow, result screens | path | `DATA` event fields |
| `getorganisation` | Registration org search | `{ORefNumber}` | `[0].TradingName` |
| `postaccreditation` | Registration submit; symptom step when `FirstLogin == "N"` | registration or symptom payload | registration UI reads `EVENTNAME`, `REFNUMBER`, `EMAIL`, `FIRSTNAME`, `SURNAME` |
| `postuserprofile` | Profile save | profile fields plus image | any resolved JSON |
| `postsymptomcheck` | Symptom submit | screening object | `status`, `date`, `SRID` |
| `getsymptomcheck` | Reload screening, result screens | `{SRID}` | `data` array, or `0` when missing |

Host: `https://footprintapp.net/api/mypass/` while `debug` is false.

Failure behaviour is local: toasts, console logs, or empty-state copy. There is no retry queue.

## Important constraints

- Script tags in `index.html` use root-absolute paths (`/scripts/core.js`). The site is expected to be hosted at the domain root.
- `staticEventID` is `304` for the registration form.
- External symptom and review links force event id `346`.
- Auto-login symptom payload and the `FirstLogin == "N"` accreditation post use event id `345`.
- `domains_old.js` maps host labels to event ids (`bil2021` → `304`, `local` and `9hour` → `346`, others). No HTML or script in the shell loads that file.
- Sign-up does not use the Footprint API.
- Certificate type Scan does not use the Footprint API.
- No automated tests are in the repository.
- Content pages are not a server-side router. `loadPage` fetches `{page}.html` from the same origin and pushes `?page=&params=`.

## Workflows

### Signed-in person

1. Open the PWA.
2. Sign in with reference and password, or resume `MyPass-LoggedInUserData`.
3. Home shows events and health.
4. MyPASS shows the pass QR for the first event returned.
5. Health or the certificate form records a symptom check or a certificate.
6. Profile can be edited until some event status text is "Approved".
7. Log out clears local storage.

### External symptom link

1. Open `index.html` with `page=symptom_step_1` and a `userID`.
2. Client attempts auto-login.
3. Footer and menu stay hidden.
4. Person completes steps 1, 3, 5, optional 5b, and the declaration.
5. Result screen shows the screening returned by `getsymptomcheck`.

### Registration (if the page is opened)

1. Open the `registration_form` content page.
2. Event `304` supplies categories, roles, and terms.
3. Optional organisation reference lookup.
4. Submit posts the accreditation application and shows the returned reference.

## Conflicts inside this revision

| Topic | What the code does |
|---|---|
| Profile lock comment vs code | Comment names `AccreditationStatus`. The lock checks `StatusDescription == "Approved"`. |
| Event id | `304` on the registration form, `345` on auto-login screening and the extra accreditation post, `346` on external links. `domains_old.js` is unused. |
| Symptom step numbers | Files exist for steps 2 and 4. The handler skips them. |
| Sign-up | UI is present. Persistence is a missing local JSON file. |
| QR scan save | UI scans. Save tells the user the API code is still awaited. |
| First login after sign-in | UI and `getpwdreset` exist. The automatic redirect from `getfirstlogin` is commented out. |
| Onboarding | `Onboard.html` works if opened. `indexLoad` does not send anyone there. |

## Requires confirmation

- Live hostname and whether `master` is still production.
- Meaning of status codes 1–4.
- Which event ids are current.
- Whether registration in this repo is still a supported journey.
- Whether the API still accepts the auto-login password and plaintext `postpwd` / `getlogin` passwords.
- Pass/fail rules for `postsymptomcheck`.
- Intended relationship to `myPass-registration` and to `track.mypass.zone`.
