Skip to content

feat(mobile): add app update management#14

Open
firstedition0123 wants to merge 7 commits into
BAWES-Universe:universefrom
firstedition0123:feat/mobile-update-management-codex-on-scaffold
Open

feat(mobile): add app update management#14
firstedition0123 wants to merge 7 commits into
BAWES-Universe:universefrom
firstedition0123:feat/mobile-update-management-codex-on-scaffold

Conversation

@firstedition0123

@firstedition0123 firstedition0123 commented May 15, 2026

Copy link
Copy Markdown

Summary

  • add back /api/version metadata endpoint for mobile update checks
  • add Capacitor App.getInfo-based native version prompts in the web frontend
  • add service worker update detection with a non-blocking reload banner
  • document the no-CodePush update architecture and Fastlane build-number guidance

Notes

Verification

  • npm install --package-lock-only --ignore-scripts (mobile)
  • npm run doctor (mobile)
  • npx vitest run tests/VersionController.test.ts --config vitest.config.ts (back)
  • npx prettier --check changed update-management files
  • npx tsc --noEmit --skipLibCheck --moduleResolution node --target ES2020 --module ESNext --lib ES2020,DOM play/src/front/Network/NativeUpdateManager.ts play/src/front/Stores/NativeUpdateStore.ts play/src/front/Stores/ServiceWorkerUpdateStore.ts
  • compiled new Svelte update prompt components with svelte/compiler
  • git diff --check

Known local verification limitation

  • Full back/play typecheck and env-doc generation currently require generated protobuf/i18n artifacts. In this checkout they fail before this change on missing libs/messages/src/ts-proto-generated/* and play/src/i18n/i18n-* artifacts. The targeted new back test and targeted frontend type checks pass.

Closes #12

Summary by CodeRabbit

  • New Features

    • Mobile CI validation workflow for Android/iOS
    • Native app version checks with blocking and non-blocking update prompts
    • Service worker update banner and immediate-activation support for faster reloads
    • New /api/version endpoint serving web/native version and update URLs
  • Tests

    • Added integration tests for the version endpoint
  • Documentation

    • Added mobile app setup and development guide

Review Change Stack

BAWES and others added 4 commits May 15, 2026 23:57
- Adds mobile/capacitor.config.ts with appId net.bawes.universe
- Configures server.url to universe.bawes.net (no local build needed)
- Adds mobile/package.json with cap sync/open scripts and Fastlane gem
- Adds mobile/Gemfile for Fastlane (shared by Android + iOS issues)
- Adds mobile/.gitignore excluding platforms and node_modules
- Adds mobile/README.md with full setup and workflow docs
- Adds .github/workflows/mobile-ci.yml for PR validation

Closes BAWES-Universe#2
- Upgrade Capacitor from v6 (EOL) to v8.3.4 (latest stable)
- Move @capacitor/push-notifications and @capacitor/splash-screen to
  dependencies (not devDependencies) so cap sync works in CI
- Add mobile/tsconfig.json so tsc --noEmit runs with strict options
- Remove || true from tsc step — failures now surface in CI output
- Fix cap doctor || true comment to be clear it's intentionally non-blocking
- Add .github/workflows/mobile-ci.yml to push.paths (was missing, caused
  workflow file changes on universe branch to not trigger the job)
- Fix misleading Gemfile comment: fastlane-plugin-versioning is for
  build/version bumping, not App Store Connect API auth
- Add mobile/package-lock.json (generated from package.json) so npm
  cache-dependency-path in CI resolves correctly
The tsc --noEmit step was failing because @capacitor/cli types are not
available until npm install runs against a real lockfile, and the stub
package-lock.json we committed cannot be resolved by npm ci.

Fix: convert capacitor.config.ts -> capacitor.config.js (plain CommonJS).
Capacitor 7+ supports and recommends .js configs for live-server setups.
This eliminates the @capacitor/cli type import entirely — the config is
just a JS object, which is what Capacitor reads at runtime anyway.

CI change: replace `npx tsc --noEmit` with `node -e "require('./capacitor.config.js')"`,
which validates the file parses and exports correctly without needing
TypeScript or the @capacitor/cli package installed.

Also: update tsconfig.json to remove the now-deleted .ts file from
include, keeping it for future use by Android/iOS platform scripts.
@coderabbitai

coderabbitai Bot commented May 15, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@firstedition0123 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 10 minutes and 15 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2624b517-1aaa-4f11-87a4-3f9dafd947ca

📥 Commits

Reviewing files that changed from the base of the PR and between d320195 and 3213ecd.

📒 Files selected for processing (12)
  • back/src/Services/MobileVersionService.ts
  • back/tests/VersionController.test.ts
  • play/src/front/Network/NativeUpdateManager.ts
  • play/src/i18n/ar-SA/refreshPrompt.ts
  • play/src/i18n/de-DE/refreshPrompt.ts
  • play/src/i18n/dsb-DE/refreshPrompt.ts
  • play/src/i18n/en-US/refreshPrompt.ts
  • play/src/i18n/fr-FR/refreshPrompt.ts
  • play/src/i18n/it-IT/refreshPrompt.ts
  • play/src/i18n/ko-KR/refreshPrompt.ts
  • play/src/i18n/nl-NL/refreshPrompt.ts
  • play/src/i18n/pt-BR/refreshPrompt.ts
📝 Walkthrough

Walkthrough

Adds a backend GET /api/version endpoint, a Capacitor mobile scaffold and CI workflow, and frontend service-worker and native-update flows with Svelte stores and UI (blocking modal vs dismissible banner) wired to version checks.

Changes

Backend Version API Endpoint

Layer / File(s) Summary
Version environment variables and schema
back/src/Enum/EnvironmentVariable.ts, back/src/Enum/EnvironmentVariableValidator.ts
Imports existing version fallback; exports MOBILE_* constants with defaults; extends zod EnvironmentVariables with MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION, MOBILE_ANDROID_UPDATE_URL, MOBILE_IOS_UPDATE_URL including semver and URL validation.
Version service and endpoint wiring
back/src/Services/MobileVersionService.ts, back/src/Controller/VersionController.ts, back/src/App.ts, back/tests/VersionController.test.ts
MobileVersionService assembles typed payload from env; VersionController registers GET /api/version returning that payload; App instantiates the controller; tests cover default and env-configured payload shapes.

Mobile Project Scaffold

Layer / File(s) Summary
Capacitor runtime and project configuration
mobile/capacitor.config.js, mobile/capacitor.config.ts, mobile/package.json, mobile/tsconfig.json, mobile/.gitignore
Adds CommonJS Capacitor config + TS wrapper, package manifest with sync/open/doctor scripts and Capacitor deps, tsconfig, and .gitignore for mobile artifacts.
Mobile documentation and CI workflow
mobile/README.md, .github/workflows/mobile-ci.yml
Documents mobile architecture, setup, Fastlane lanes, CI secrets, and adds mobile-ci.yml validating Capacitor config and Fastlane install on universe branch targeting mobile/** changes.

Frontend Service Worker and Native Update Management

Layer / File(s) Summary
Service worker store, registration, and UI
play/src/front/Stores/ServiceWorkerUpdateStore.ts, play/src/front/Network/ServiceWorker.ts, play/public/service-worker-prod.js, play/src/front/Components/ServiceWorkerUpdateBanner.svelte
Introduces serviceWorkerUpdateStore; refactors _ServiceWorker to modular registration/watch/controller-change handling; prod SW now claims clients and accepts SKIP_WAITING; banner component shows reload/dismiss actions.
Native update manager, store, UI integration
play/src/front/Stores/NativeUpdateStore.ts, play/src/front/Network/NativeUpdateManager.ts, play/src/front/Components/NativeUpdatePrompt.svelte, play/src/front/Components/App.svelte, play/src/front/Components/GameOverlay.svelte, play/package.json
Adds nativeUpdateStore, initNativeUpdateCheck() that uses Capacitor App info and /api/version to decide blocking vs non-blocking prompts via semver-based comparison; wires NativeUpdatePrompt UI and calls initNativeUpdateCheck() on app mount; includes semver deps.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client / WebView
  participant Manager as NativeUpdateManager
  participant Cap as Capacitor.App
  participant BE as Backend (/api/version)
  participant UI as UI Components
  Client->>Manager: initNativeUpdateCheck()
  par
    Manager->>Cap: App.getInfo()
    Manager->>BE: GET /api/version
  and
    Cap-->>Manager: {version}
    BE-->>Manager: {minNativeVersion, latestNativeVersion, updateUrl}
  end
  Manager->>Manager: compareVersions()
  alt version < minNativeVersion
    Manager->>UI: nativeUpdateStore (blocking=true)
    UI->>Client: Show blocking modal "Update required"
    Client->>UI: Tap "Update"
    UI->>Cap: Navigate to updateUrl
  else version < latestNativeVersion
    Manager->>UI: nativeUpdateStore (blocking=false)
    UI->>Client: Show banner "New version available"
    Client->>UI: Tap "Later" or "Update"
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • #12: App update management: seamless updates without app store bottleneck — Implements the backend endpoint, service-worker update flow, and native-version gating requested by the issue.

Possibly related PRs

Poem

🐰 I hopped through code and found the cue,

Workers claim and versions check anew,
A modal guards when native falls behind,
A gentle banner tells players "wait or find",
The rabbit cheers — web and app aligned.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(mobile): add app update management' is concise, clear, and accurately reflects the main feature added: mobile app update management infrastructure.
Linked Issues check ✅ Passed The PR implements all core objectives from issue #12: service worker update detection with non-blocking banner [#12], GET /api/version endpoint [#12], native version checking via Capacitor [#12], blocking modal for below-minimum versions [#12], mobile README documentation [#12], and targeting the universe branch [#12]. All acceptance criteria are addressed.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #12 objectives: mobile update infrastructure, version endpoint, service worker updates, native prompts, documentation, and mobile dependency setup. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e1b044759

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +95 to +97
const comparison = leftPart.localeCompare(rightPart);
if (comparison !== 0) {
return comparison > 0 ? 1 : -1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat pre-release suffixes as older than stable versions

compareVersions falls back to lexicographic comparison for non-numeric segments, which makes versions like 1.2.0-beta compare as newer than 1.2.0 (the extra beta segment is compared against default "0" and wins). In practice, Android versionName strings often include prerelease suffixes, so a prerelease build can incorrectly bypass the minNativeVersion block (or suppress the optional update banner) even though it should be treated as older than the final release.

Useful? React with 👍 / 👎.

Comment on lines +25 to +27
{#if $nativeUpdateStore.updateUrl}
<button type="button" class="light" on:click={openUpdate}>Update app</button>
{/if}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Provide an action for blocking updates without store URL

The blocking update modal renders no actionable control when updateUrl is missing, because the only button is conditional and blocking mode has no dismiss path. Since the backend leaves MOBILE_IOS_UPDATE_URL optional, an iOS user below minNativeVersion can be hard-blocked on a dead-end screen with no way to open the store from inside the app.

Useful? React with 👍 / 👎.

@firstedition0123

Copy link
Copy Markdown
Author

Follow-up after Codex review: addressed both actionable findings in e3b1c25.

  • NativeUpdateManager.compareVersions now treats prerelease/non-numeric suffixes as older than the matching stable version, while preserving numeric segment comparison.
  • Blocking native update prompts now provide a Try again action when no store/update URL is configured, avoiding a dead-end modal.

Verification run locally:

px vitest run tests/VersionController.test.ts --config vitest.config.ts from �ack/

  • targeted sc --noEmit for the changed front-end stores/network files
  • Svelte compile check for the two new update components

px prettier --check on the changed update-management files

  • git diff --check

No secrets or personal data were added; changed files were scanned for obvious credential material.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (4)
back/src/Enum/EnvironmentVariable.ts (1)

32-37: ⚡ Quick win

Consider adding explicit type annotations for the mobile constants.

While TypeScript will correctly infer the types, adding explicit annotations would improve clarity, especially for MOBILE_IOS_UPDATE_URL which can be undefined:

export const MOBILE_IOS_UPDATE_URL: string | undefined = env.MOBILE_IOS_UPDATE_URL;

This makes the optional nature immediately visible to consumers without needing to trace through the validation layer.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@back/src/Enum/EnvironmentVariable.ts` around lines 32 - 37, The exported
mobile constants lack explicit TypeScript type annotations; add explicit types
to make optionality clear by annotating MOBILE_WEB_VERSION,
MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION,
MOBILE_ANDROID_UPDATE_URL as string and annotate MOBILE_IOS_UPDATE_URL as string
| undefined so consumers can see it may be undefined; update the declarations
for MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION,
MOBILE_ANDROID_UPDATE_URL and MOBILE_IOS_UPDATE_URL accordingly (preserve
existing fallback logic).
back/tests/VersionController.test.ts (2)

5-30: ⚡ Quick win

Expand test coverage with additional scenarios.

The test only validates default environment values. Consider adding test cases for:

  1. Custom environment variables set – verify the endpoint correctly returns operator-configured versions and update URLs (especially MOBILE_IOS_UPDATE_URL)
  2. Version hierarchy – test when MOBILE_LATEST_NATIVE_VERSION > MOBILE_MIN_NATIVE_VERSION to ensure the payload reflects the distinction

This would increase confidence that the endpoint correctly adapts to different deployment configurations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@back/tests/VersionController.test.ts` around lines 5 - 30, Add two new tests
in VersionController.test.ts that instantiate VersionController and invoke the
"/api/version" handler: (1) a test that sets process.env values
(MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION,
MOBILE_ANDROID_UPDATE_URL, MOBILE_IOS_UPDATE_URL) to non-default values and
asserts the JSON response includes those exact values (referencing
VersionController and the "/api/version" route), and (2) a test for version
hierarchy where MOBILE_LATEST_NATIVE_VERSION is greater than
MOBILE_MIN_NATIVE_VERSION verifying both fields are returned distinctly; ensure
each test sets required process.env keys before creating VersionController and
restores/clears them after to avoid cross-test pollution, and reuse the existing
mock Express app pattern (routes Map, mocked res with status/json) used in the
current test.

21-29: 💤 Low value

Note: Test expectation vs. actual HTTP response.

The assertion expects ios: undefined in the payload object, which correctly matches the function argument to res.json(). However, the actual HTTP response body will omit the ios field entirely, since JSON.stringify() drops undefined values:

{
  "webVersion": "dev",
  "minNativeVersion": "1.0.0",
  "latestNativeVersion": "1.0.0",
  "updateUrl": {
    "android": "https://play.google.com/store/apps/details?id=net.bawes.universe"
  }
}

This is the correct API behavior for optional fields. Consider adding a comment clarifying this distinction if it might confuse future maintainers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@back/tests/VersionController.test.ts` around lines 21 - 29, The test
currently asserts expect(json).toHaveBeenCalledWith({... updateUrl: { android:
..., ios: undefined }}), but JSON responses drop undefined fields so the real
HTTP body omits the ios key; update the test in VersionController.test.ts to
reflect that by removing the ios: undefined expectation (e.g., assert updateUrl
only contains android or use expect(...).toHaveProperty('updateUrl.android') and
expect(...).not.toHaveProperty('updateUrl.ios')), and add a short comment near
the expect(json).toHaveBeenCalledWith line explaining that undefined properties
are omitted by JSON.stringify so the ios field will be absent in the actual
response.
play/src/front/Network/NativeUpdateManager.ts (1)

77-117: ⚡ Quick win

Use a well-known semver library instead of a custom comparison.

The custom compareVersions function doesn't handle semantic versioning prereleases correctly. For example, comparing "1.0-beta" to "1.0.0" would incorrectly conclude "1.0-beta" > "1.0.0" (line 110 lexicographically compares "beta" vs "0"), when semver specifies that prereleases are lower precedence than releases.

While typical mobile app versions like "1.2.0" will compare correctly, using a battle-tested semver library eliminates edge-case bugs and aligns with best practices. As per coding guidelines, prefer well-known libraries over rolling your own for common tasks like version comparison.

📦 Consider using the `semver` package

Install the package:

npm install semver
npm install --save-dev `@types/semver`

Then replace the compareVersions function:

+import * as semver from 'semver';
+
 export async function initNativeUpdateCheck(): Promise<void> {
     // ...
-        if (compareVersions(currentVersion, versionPayload.minNativeVersion) < 0) {
+        if (semver.lt(semver.coerce(currentVersion) ?? '0.0.0', semver.coerce(versionPayload.minNativeVersion) ?? '0.0.0')) {
             // ...
-        if (compareVersions(currentVersion, versionPayload.latestNativeVersion) < 0) {
+        if (semver.lt(semver.coerce(currentVersion) ?? '0.0.0', semver.coerce(versionPayload.latestNativeVersion) ?? '0.0.0')) {
             // ...
-
-function compareVersions(left: string, right: string): number {
-    // ... remove entire function
-}

Note: semver.coerce() normalizes versions; use semver.valid() + direct comparison if strict semver format is guaranteed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@play/src/front/Network/NativeUpdateManager.ts` around lines 77 - 117, The
custom compareVersions function in NativeUpdateManager.ts should be replaced
with a call to a battle-tested semver library: install semver (and
`@types/semver`), import it, and implement compareVersions to use semver.compare
when both inputs are valid semver strings; if not valid, attempt semver.coerce
on each and compare the coerced results, and finally fall back to a simple
lexical or numeric compare only if coercion fails—this preserves the existing
function signature and corrects prerelease vs release ordering per semver rules.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/mobile-ci.yml:
- Around line 39-41: The CI step named "Run cap doctor (info only)" currently
runs a floating Capacitor CLI via "npx --yes `@capacitor/cli`", making builds
non-reproducible; update that step to invoke a repo-pinned Capacitor CLI version
(for example use "npx `@capacitor/cli`@< pinned-version >" or install the exact
CLI version from package.json before running) so the job always runs a
deterministic CLI release—modify the command in that step accordingly and ensure
the pinned version matches the repository's lockfile/package.json.

In `@back/src/Enum/EnvironmentVariableValidator.ts`:
- Around line 18-27: The MOBILE_MIN_NATIVE_VERSION and
MOBILE_LATEST_NATIVE_VERSION zod schemas currently accept any string; add
semver-format validation (e.g., a semver-compatible regex or zod .refine) after
the .transform(emptyStringToUndefined) so only valid version strings like
"1.2.3" (optionally with pre-release/build) pass, and reject values like "abc";
apply the same change to both MOBILE_MIN_NATIVE_VERSION and
MOBILE_LATEST_NATIVE_VERSION, provide a clear validation error message (e.g.,
"must be a semantic version like X.Y.Z") so failures point to the bad env var,
and keep the emptyStringToUndefined behavior intact.

In `@mobile/capacitor.config.ts`:
- Around line 13-54: The Capacitor configuration is duplicated between
capacitor.config.ts and capacitor.config.js; keep a single source of truth by
consolidating the object exported as config (the CapacitorConfig constant and
its export default config) into one file and removing the other duplicate, or
make the .js import/export the canonical file and have capacitor.config.ts
re-export/import that file so only one object definition exists; update any
tooling or references that expect capacitor.config.js or capacitor.config.ts to
point to the canonical file and ensure the exported symbol remains export
default config and the shape (server, ios, android, plugins) is preserved.

In `@mobile/README.md`:
- Around line 9-10: The README currently points contributors to
capacitor.config.ts but the project and CI use capacitor.config.js as the
validated canonical config; update the README text to state that
capacitor.config.js is the canonical Capacitor config file (or vice versa if you
prefer TS), and add a short note instructing contributors to edit only that file
and keep the other in sync or autogenerated. Locate references to
capacitor.config.ts and capacitor.config.js in the README and adjust the wording
to explicitly name the single canonical file (capacitor.config.js), mention that
CI validates that file, and add a brief command or note about how to regenerate
or sync the other format if needed.

In `@mobile/tsconfig.json`:
- Around line 1-11: tsconfig.json currently has "include": [] and doesn't
inherit the shared baseline; change it to extend the mono-repo/base TS config
(add an "extends": "<path-to-base-tsconfig>") and replace the empty "include"
with the service-specific globs (e.g., ["src/**/*", "typings/**/*"]) so
type-checking runs for this mobile service; keep existing "compilerOptions"
overrides if needed and ensure the "extends" property is the first sibling in
the root object.

In `@play/src/front/Components/NativeUpdatePrompt.svelte`:
- Around line 21-48: The component uses hardcoded English strings; replace all
literals in NativeUpdatePrompt.svelte (the user-facing texts around
$nativeUpdateStore, and buttons that call openUpdate, reload, dismiss) with
typesafe-i18n lookups and add the corresponding keys to the nativeUpdate
translation module (e.g., modal.title, modal.message with placeholders
{currentVersion}/{requiredVersion}, modal.updateButton/modal.retryButton,
banner.message with {latestVersion}, banner.updateButton, banner.dismissButton);
import and use the typesafe-i18n translator (t or the project’s wrapper) in the
component to pass version placeholders when rendering the message and buttons so
all strings are translated in all languages.

In `@play/src/front/Components/ServiceWorkerUpdateBanner.svelte`:
- Around line 14-17: The banner's visible strings in
ServiceWorkerUpdateBanner.svelte (the <span> "Game updated - tap to reload" and
the two button labels "Reload" and "Later" used by the reload and dismiss
handlers) are hardcoded; replace them with typesafe-i18n translation keys (e.g.,
serviceWorker.update.title, serviceWorker.update.reload,
serviceWorker.update.later) and use the project's i18n helper to render those
keys in the component; then add corresponding entries under the same module file
in every language catalog in play/src/i18n/[language]/[module].ts so all
supported locales receive translations.

---

Nitpick comments:
In `@back/src/Enum/EnvironmentVariable.ts`:
- Around line 32-37: The exported mobile constants lack explicit TypeScript type
annotations; add explicit types to make optionality clear by annotating
MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION,
MOBILE_ANDROID_UPDATE_URL as string and annotate MOBILE_IOS_UPDATE_URL as string
| undefined so consumers can see it may be undefined; update the declarations
for MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION, MOBILE_LATEST_NATIVE_VERSION,
MOBILE_ANDROID_UPDATE_URL and MOBILE_IOS_UPDATE_URL accordingly (preserve
existing fallback logic).

In `@back/tests/VersionController.test.ts`:
- Around line 5-30: Add two new tests in VersionController.test.ts that
instantiate VersionController and invoke the "/api/version" handler: (1) a test
that sets process.env values (MOBILE_WEB_VERSION, MOBILE_MIN_NATIVE_VERSION,
MOBILE_LATEST_NATIVE_VERSION, MOBILE_ANDROID_UPDATE_URL, MOBILE_IOS_UPDATE_URL)
to non-default values and asserts the JSON response includes those exact values
(referencing VersionController and the "/api/version" route), and (2) a test for
version hierarchy where MOBILE_LATEST_NATIVE_VERSION is greater than
MOBILE_MIN_NATIVE_VERSION verifying both fields are returned distinctly; ensure
each test sets required process.env keys before creating VersionController and
restores/clears them after to avoid cross-test pollution, and reuse the existing
mock Express app pattern (routes Map, mocked res with status/json) used in the
current test.
- Around line 21-29: The test currently asserts
expect(json).toHaveBeenCalledWith({... updateUrl: { android: ..., ios: undefined
}}), but JSON responses drop undefined fields so the real HTTP body omits the
ios key; update the test in VersionController.test.ts to reflect that by
removing the ios: undefined expectation (e.g., assert updateUrl only contains
android or use expect(...).toHaveProperty('updateUrl.android') and
expect(...).not.toHaveProperty('updateUrl.ios')), and add a short comment near
the expect(json).toHaveBeenCalledWith line explaining that undefined properties
are omitted by JSON.stringify so the ios field will be absent in the actual
response.

In `@play/src/front/Network/NativeUpdateManager.ts`:
- Around line 77-117: The custom compareVersions function in
NativeUpdateManager.ts should be replaced with a call to a battle-tested semver
library: install semver (and `@types/semver`), import it, and implement
compareVersions to use semver.compare when both inputs are valid semver strings;
if not valid, attempt semver.coerce on each and compare the coerced results, and
finally fall back to a simple lexical or numeric compare only if coercion
fails—this preserves the existing function signature and corrects prerelease vs
release ordering per semver rules.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2972e11d-3ccd-4d01-9746-8df6416dd19e

📥 Commits

Reviewing files that changed from the base of the PR and between b44b517 and e3b1c25.

⛔ Files ignored due to path filters (1)
  • mobile/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (23)
  • .github/workflows/mobile-ci.yml
  • back/src/App.ts
  • back/src/Controller/VersionController.ts
  • back/src/Enum/EnvironmentVariable.ts
  • back/src/Enum/EnvironmentVariableValidator.ts
  • back/src/Services/MobileVersionService.ts
  • back/tests/VersionController.test.ts
  • mobile/.gitignore
  • mobile/Gemfile
  • mobile/README.md
  • mobile/capacitor.config.js
  • mobile/capacitor.config.ts
  • mobile/package.json
  • mobile/tsconfig.json
  • play/public/service-worker-prod.js
  • play/src/front/Components/App.svelte
  • play/src/front/Components/GameOverlay.svelte
  • play/src/front/Components/NativeUpdatePrompt.svelte
  • play/src/front/Components/ServiceWorkerUpdateBanner.svelte
  • play/src/front/Network/NativeUpdateManager.ts
  • play/src/front/Network/ServiceWorker.ts
  • play/src/front/Stores/NativeUpdateStore.ts
  • play/src/front/Stores/ServiceWorkerUpdateStore.ts

Comment thread .github/workflows/mobile-ci.yml
Comment thread back/src/Enum/EnvironmentVariableValidator.ts
Comment thread mobile/capacitor.config.ts Outdated
Comment thread mobile/README.md Outdated
Comment thread mobile/tsconfig.json
Comment on lines +1 to +11
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"moduleResolution": "node"
},
"include": []
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

tsconfig.json is not inheriting base config and currently type-checks nothing.

Line 10 ("include": []) makes the config effectively inert, and there is no extends to align with the shared baseline.

Proposed fix
{
+  "extends": "<path-to-repo-base-tsconfig>",
   "compilerOptions": {
     "target": "ES2020",
     "module": "commonjs",
     "esModuleInterop": true,
     "strict": true,
     "skipLibCheck": true,
     "moduleResolution": "node"
   },
-  "include": []
+  "include": ["./**/*.ts", "./**/*.tsx"]
}

As per coding guidelines, "**/tsconfig.json: TypeScript configuration files (tsconfig.json) in each service must inherit from the appropriate base configuration and be service-specific".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/tsconfig.json` around lines 1 - 11, tsconfig.json currently has
"include": [] and doesn't inherit the shared baseline; change it to extend the
mono-repo/base TS config (add an "extends": "<path-to-base-tsconfig>") and
replace the empty "include" with the service-specific globs (e.g., ["src/**/*",
"typings/**/*"]) so type-checking runs for this mobile service; keep existing
"compilerOptions" overrides if needed and ensure the "extends" property is the
first sibling in the root object.

Comment thread play/src/front/Components/NativeUpdatePrompt.svelte
Comment thread play/src/front/Components/ServiceWorkerUpdateBanner.svelte Outdated
@firstedition0123

Copy link
Copy Markdown
Author

Follow-up pushed in d320195 addressing the latest CodeRabbit review items:

  • pinned the mobile CI Capacitor CLI invocation
  • added semver validation for backend mobile version env vars
  • made capacitor.config.js the canonical config with a TS wrapper and README updates
  • replaced custom native version comparison with semver
  • moved update prompt strings to typesafe-i18n
  • expanded VersionController coverage and clarified optional iOS URL JSON behavior
  • regenerated env var docs after the backend validator change

Verification run locally:

  • npm exec --workspace=back -- vitest run tests/VersionController.test.ts --config vitest.config.ts
  • npm exec --workspace=back -- eslint --fix tests/VersionController.test.ts
  • npm exec -- tsc --noEmit --pretty false from mobile/
  • npm exec --workspace=play -- tsc --noEmit --pretty false src/front/Network/NativeUpdateManager.ts src/front/Stores/NativeUpdateStore.ts
  • Svelte preprocess/compile check for NativeUpdatePrompt and ServiceWorkerUpdateBanner
  • npm exec --workspace=play -- svelte-check (0 errors; existing unrelated bot a11y warnings remain)
  • git diff --check

No secrets, personal data, live services, payment/KYC data, or wallet actions were used.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (1)
mobile/tsconfig.json (1)

1-12: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add base-config inheritance in this service tsconfig.

Line 1 defines a standalone config with no extends, which violates the repo TS-config policy and risks drift from shared compiler defaults.

Proposed minimal fix
{
+  "extends": "<relative-path-to-repo-base-tsconfig>",
   "compilerOptions": {
     "target": "ES2020",
     "module": "commonjs",
     "esModuleInterop": true,
     "strict": true,
     "skipLibCheck": true,
     "ignoreDeprecations": "6.0",
     "moduleResolution": "node"
   },
   "include": ["*.ts", "src/**/*.ts", "typings/**/*.d.ts"]
}

As per coding guidelines, "**/tsconfig.json: TypeScript configuration files (tsconfig.json) in each service must inherit from the appropriate base configuration and be service-specific".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/tsconfig.json` around lines 1 - 12, This tsconfig.json is standalone
and must inherit the repo base; add an "extends" entry at the top of the JSON to
point to the repository's base TypeScript config (so this service inherits
shared defaults) while leaving the existing "compilerOptions" and "include" in
place to override service-specific settings; ensure the extends value references
the correct base config filename used in the repo (e.g., tsconfig.base.json or
equivalent) and keep "moduleResolution", "strict", and other local overrides
intact.
🧹 Nitpick comments (1)
back/tests/VersionController.test.ts (1)

58-76: 💤 Low value

Consider more explicit assertion for iOS property absence.

Line 75 uses JSON.stringify to verify the ios property is not present. While this works correctly (JSON.stringify omits undefined properties), a more direct assertion would improve readability:

-expect(JSON.stringify(payload?.updateUrl)).not.toContain("ios");
+expect(payload?.updateUrl).not.toHaveProperty("ios");
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@back/tests/VersionController.test.ts` around lines 58 - 76, The test
currently checks for absence of the iOS URL by asserting
JSON.stringify(payload?.updateUrl) does not contain "ios"; replace that with a
direct property absence assertion: update the assertion to use the payload and
updateUrl objects (e.g. expect(payload.updateUrl).not.toHaveProperty("ios") or
expect(payload.updateUrl.ios).toBeUndefined()) in the VersionController.test.ts
test where payload is derived and updateUrl is checked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@play/src/front/Network/NativeUpdateManager.ts`:
- Around line 64-72: The code only sets nativeUpdateStore when
compareVersions(...) < 0 and never clears it when the app is already up-to-date;
add an else branch after the compareVersions check to reset/clear
nativeUpdateStore so stale prompt state is removed on successful checks.
Specifically, beside the existing block that calls nativeUpdateStore.set({
blocking: false, currentVersion, requiredVersion:
versionPayload.minNativeVersion, latestVersion:
versionPayload.latestNativeVersion, updateUrl }), add an else that calls
nativeUpdateStore.set(...) to a neutral/cleared value (e.g., null or an
empty/default object such as { blocking: false, currentVersion, requiredVersion:
undefined, latestVersion: versionPayload.latestNativeVersion, updateUrl:
undefined }) so the store is explicitly reset when currentVersion >=
versionPayload.latestNativeVersion.
- Around line 78-87: compareVersions currently uses semver.coerce which strips
suffixes like "-hotfix" and makes "1.2.3-hotfix" compare equal to "1.2.3";
instead detect when semver.coerce produced a version but the original string
contains a suffix (e.g., contains '-' or '+' or non-numeric trailing chars) and
treat that coerced result as a prerelease by appending the original suffix (or a
stable prerelease token) to the coerced version before calling semver.compare;
update the logic around leftVersion/rightVersion in compareVersions to build
coercedLeftWithSuffix and coercedRightWithSuffix when needed so suffixed
versions sort older than the base stable version.

In `@play/src/i18n/en-US/refreshPrompt.ts`:
- Around line 5-25: Add the missing translation keys serviceWorkerUpdate and
nativeUpdate (including nested keys message/reload/later/dismissLabel and
modal.title/message/updateButton/retryButton and
banner.message/updateButton/laterButton/dismissLabel) to each supported locale
file for ar-SA, de-DE, dsb-DE, fr-FR, it-IT, ko-KR, nl-NL, and pt-BR; for each
locale, create the same object shape as in en-US/refreshPrompt.ts and provide
the appropriate translated strings (or placeholder translations) so the i18n
bundle includes these keys across all locales, then run the i18n validation/lint
to ensure no missing keys.

---

Duplicate comments:
In `@mobile/tsconfig.json`:
- Around line 1-12: This tsconfig.json is standalone and must inherit the repo
base; add an "extends" entry at the top of the JSON to point to the repository's
base TypeScript config (so this service inherits shared defaults) while leaving
the existing "compilerOptions" and "include" in place to override
service-specific settings; ensure the extends value references the correct base
config filename used in the repo (e.g., tsconfig.base.json or equivalent) and
keep "moduleResolution", "strict", and other local overrides intact.

---

Nitpick comments:
In `@back/tests/VersionController.test.ts`:
- Around line 58-76: The test currently checks for absence of the iOS URL by
asserting JSON.stringify(payload?.updateUrl) does not contain "ios"; replace
that with a direct property absence assertion: update the assertion to use the
payload and updateUrl objects (e.g.
expect(payload.updateUrl).not.toHaveProperty("ios") or
expect(payload.updateUrl.ios).toBeUndefined()) in the VersionController.test.ts
test where payload is derived and updateUrl is checked.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9791940e-0f28-4db0-b0c7-b842e95b8a47

📥 Commits

Reviewing files that changed from the base of the PR and between e3b1c25 and d320195.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • .github/workflows/mobile-ci.yml
  • back/src/Enum/EnvironmentVariable.ts
  • back/src/Enum/EnvironmentVariableValidator.ts
  • back/tests/VersionController.test.ts
  • docs/others/self-hosting/env-variables.md
  • mobile/README.md
  • mobile/capacitor.config.js
  • mobile/capacitor.config.ts
  • mobile/tsconfig.json
  • play/package.json
  • play/src/front/Components/NativeUpdatePrompt.svelte
  • play/src/front/Components/ServiceWorkerUpdateBanner.svelte
  • play/src/front/Network/NativeUpdateManager.ts
  • play/src/i18n/en-US/refreshPrompt.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • back/src/Enum/EnvironmentVariable.ts
  • play/src/front/Components/NativeUpdatePrompt.svelte
  • .github/workflows/mobile-ci.yml
  • play/src/front/Components/ServiceWorkerUpdateBanner.svelte

Comment thread play/src/front/Network/NativeUpdateManager.ts
Comment thread play/src/front/Network/NativeUpdateManager.ts
Comment on lines +5 to +25
serviceWorkerUpdate: {
message: "Game updated - tap to reload",
reload: "Reload",
later: "Later",
dismissLabel: "Dismiss update prompt",
},
nativeUpdate: {
modal: {
title: "Please update the app",
message:
"Version {currentVersion:string} is no longer supported. Update to {requiredVersion:string} or newer to continue.",
updateButton: "Update app",
retryButton: "Try again",
},
banner: {
message: "New app version {latestVersion:string} is available.",
updateButton: "Update",
laterButton: "Later",
dismissLabel: "Dismiss native update prompt",
},
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify refreshPrompt locale coverage for new keys
fd -p refreshPrompt.ts play/src/i18n | sort
rg -n --glob '**/refreshPrompt.ts' 'serviceWorkerUpdate|nativeUpdate' play/src/i18n

Repository: BAWES-Universe/workadventure-universe

Length of output: 541


🏁 Script executed:

cat -n play/src/i18n/fr-FR/refreshPrompt.ts

Repository: BAWES-Universe/workadventure-universe

Length of output: 367


🏁 Script executed:

cat -n play/src/i18n/de-DE/refreshPrompt.ts

Repository: BAWES-Universe/workadventure-universe

Length of output: 370


Add missing translations for serviceWorkerUpdate and nativeUpdate to all supported locales.

The new keys at lines 5-25 exist only in en-US/refreshPrompt.ts. They must be translated in all supported language files: ar-SA, de-DE, dsb-DE, fr-FR, it-IT, ko-KR, nl-NL, and pt-BR per the i18n requirement in the coding guidelines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@play/src/i18n/en-US/refreshPrompt.ts` around lines 5 - 25, Add the missing
translation keys serviceWorkerUpdate and nativeUpdate (including nested keys
message/reload/later/dismissLabel and
modal.title/message/updateButton/retryButton and
banner.message/updateButton/laterButton/dismissLabel) to each supported locale
file for ar-SA, de-DE, dsb-DE, fr-FR, it-IT, ko-KR, nl-NL, and pt-BR; for each
locale, create the same object shape as in en-US/refreshPrompt.ts and provide
the appropriate translated strings (or placeholder translations) so the i18n
bundle includes these keys across all locales, then run the i18n validation/lint
to ensure no missing keys.

@firstedition0123

Copy link
Copy Markdown
Author

Follow-up pushed in 3213ecd for the latest review:

  • reset nativeUpdateStore when the native app is already on the latest version
  • preserved suffix-bearing coerced versions as prerelease-style values before semver comparison
  • added service worker/native update prompt keys for all existing refreshPrompt locales
  • changed the optional iOS update URL path to omit the property when unset, then replaced the test with a direct property absence assertion
  • normalized refreshPrompt placeholders to the repo's generated i18n format ({currentVersion}, {requiredVersion}, {latestVersion})

I left mobile/tsconfig.json without an extends entry because this repo does not have a root/base tsconfig file for mobile to inherit; adding a placeholder extends would break the local validation.

Verification run locally:

  • npm exec --workspace=back -- vitest run tests/VersionController.test.ts --config vitest.config.ts
  • npm exec --workspace=play -- tsc --noEmit --pretty false
  • npx prettier --check on the changed backend, frontend, and locale files
  • git diff --check

No secrets, personal data, live services, payment/KYC data, or wallet actions were used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App update management: seamless updates without app store bottleneck

2 participants