Commit Graph
2 Commits
Author SHA1 Message Date
devsyncwrldandClaude Opus 5 cc2b8d3d33 feat(mobile): keep voice alive with the screen off
CI / Build & test (Node 20) (push) Canceled after 0s
CI / Build & test (Node 24) (push) Canceled after 0s
CI / Build & test (push) Canceled after 0s
CodeQL / Analyze (javascript-typescript) (push) Canceled after 0s
Security / Secret scan (gitleaks) (push) Canceled after 0s
Security / Dependency scan (OSV-Scanner) (push) Canceled after 0s
Security / IaC/config scan (Trivy) (push) Canceled after 0s
Security / License compliance scan (Trivy) (push) Canceled after 0s
OpenSSF Scorecard / Scorecard analysis (push) Canceled after 0s
A call dropped as soon as the screen locked. The cause is not configuration:
Android freezes background processes, and a frozen WebView stops running the
JavaScript that holds the LiveKit connection up. The only thing the system
accepts as "this app must keep running" is a foreground service, which is why
the persistent notification comes with it.

Two mechanisms, both needed: the service stops the process from being frozen,
and a partial wake lock stops the CPU from sleeping. Either one alone still
drops the call.

The notification carries a hangup action. The service cannot end the call
itself — only the web app knows how to leave a LiveKit room — so it notifies
the plugin's listeners and the web side disconnects through the same path the
in-app button uses.

The web side starts the service on mount when a call is already in progress,
not just on transition: the WebView can reload mid-call, and waiting for the
next connect would leave that call unprotected with no visible symptom until
someone locked their screen.

POST_NOTIFICATIONS is requested at launch on Android 13+. Denying it does not
drop calls — the service still runs — it only hides the notification.

Tests cover connect/disconnect, the already-in-a-call mount, the hangup action,
and that the component is inert in the browser and desktop builds. Verified by
mutation that dropping the start call fails the first one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:44:35 -03:00
devsyncwrldandClaude Opus 5 f808f2f535 feat(mobile): Android app shell with Capacitor
The web client already has a full mobile UI (MobileShell and 17 sibling
screens) and ships as a PWA, so what was missing to get an APK was a native
container, not an interface.

The WebView loads the instance over the network instead of bundling a copy of
the web build. Bundling would mean republishing an APK on every site deploy —
there is no electron-updater equivalent here — and it would move the page to
https://localhost, turning every same-origin /api and /ws call into a
cross-origin one. The cost is no offline mode, which for a realtime chat app
costs nothing: public/index.html covers the unreachable-server case.

packages/mobile sits outside the pnpm workspace on purpose. Capacitor's CLI and
Gradle both assume a flat node_modules, and keeping it out leaves pnpm-lock.yaml
and the existing web/desktop CI untouched by it.

Icons are generated from the existing brand art. The launch screen is a
layer-list over the brand background rather than Capacitor's per-density
splash.png set, which is light and flashed white before the WebView painted.

CI builds an *unsigned* APK. The signing key stays on the VPS and is applied
there — an APK signature is what proves an update came from us, so the key has
no reason to exist on a second machine.

Background voice (foreground service) is not in this commit: a plain WebView is
throttled when the screen goes off and the call drops. That is the next phase.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-03 13:09:15 -03:00