src/features/app-update.
Endpoint
GET /v1/trainee/app-release returns:
X-App-Platform on every request, so the server can answer per platform. The latest version is server configuration. Releasing a new binary does not require an app change to announce it.
Is an update due?
installedVersionisConstants.expoConfig.version.isVersionAtLeastfromfeatures/health/lib/runtime.tscompares dot-separated numeric parts. Missing parts count as 0, so1.0equals1.0.0. Non-numeric parts count as 0.- If the server sends no version or no store URL, nothing is due. That is how the prompt is switched off.
When it shows
promptForAppUpdate() is the last of the three startup prompts, after push permission and health access. runStartupPrompts in src/lib/startupPrompts.ts runs them in sequence, so the dialog never appears on top of a system permission sheet.
It runs once each time the trainee becomes signed in, which in practice means each cold start.
The function returns without showing anything when:
A failed request rejects the promise.
runStartupPrompts catches errors from each prompt and moves on, so an offline launch shows no dialog and no error.
The dialog and the queue
When an update is due, the function sets it in the store and returns a promise that stays pending until the trainee acts:useStartupPrompts only reports settled once the dialog is gone. Because it is the last prompt, nothing else is waiting behind it.
AppUpdatePrompt subscribes to update !== null and renders AppUpdateDialog, a transparent fade Modal with a title, a body and one filled button.
Tapping update does not record a dismissal. If the trainee opens the store and comes back without updating, the dialog shows again on the next cold start.
Dismissal
dismissAppUpdate() writes { day: todayIsoDate() } to app-update-dismissed.json through jsonFileStore. todayIsoDate() is the device’s local calendar date.
The result is at most one prompt per calendar day after a dismissal. The first cold start on a later day shows it again for as long as the binary is behind.
The file is not tied to a version. It only stores the day.
Strings
appUpdateTitle, appUpdateBody and appUpdateCta in every locale file, plus the shared close label for the icon button.
Relation to OTA updates
The two mechanisms are independent.
The prompt component itself is JS, so it also reaches old binaries over the air. That is the point: an old binary can be told about a new one. See OTA updates.