Repository navigation
feat(apps): ship native shells and release downloads - #180
Conversation
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Credential leakage, missing Electron CSP protection, identity-schema mismatch, and routing defects must be resolved before release.
Review effort: Balanced
Findings: 2
Open (6)
What changed in this PR
Adds generic Capacitor and Electron clients for self-hosted TeslaSync servers, app-token authentication, native deep links, platform packaging, and release artifacts.
Changes:
- Adds Android/iOS and Windows shells with server selection and authenticated API/SSE routing.
- Adds app-bound API keys, Digital Asset Links, CORS configuration, and schema support.
- Extends release packaging, documentation, privacy disclosures, and branded assets.
| File | Description |
|---|---|
.env.example |
Documents Android asset links. |
.github/workflows/release.yml |
Builds native release artifacts. |
apps/desktop/.gitignore |
Ignores desktop outputs. |
apps/desktop/README.md |
Documents Electron packaging. |
apps/desktop/assets.js |
Resolves bundled web assets. |
apps/desktop/assets.test.js |
Tests asset resolution. |
apps/desktop/assets/icon.ico |
Adds Windows icon. |
apps/desktop/electron-builder.yml |
Configures Windows packages. |
apps/desktop/main.js |
Implements Electron main process. |
apps/desktop/package-lock.json |
Locks desktop dependencies. |
apps/desktop/package-msix.ps1 |
Repackages and validates MSIX. |
apps/desktop/package.json |
Defines desktop scripts. |
apps/desktop/preload.js |
Bridges shell integration. |
apps/desktop/titlebar.js |
Validates title-bar messages. |
apps/desktop/titlebar.test.js |
Tests title-bar validation. |
apps/mobile/.gitignore |
Ignores mobile dependencies. |
apps/mobile/README.md |
Documents mobile builds. |
apps/mobile/android/.gitignore |
Ignores Android outputs. |
apps/mobile/android/app/.gitignore |
Ignores app build outputs. |
apps/mobile/android/app/build.gradle |
Configures Android app build. |
apps/mobile/android/app/capacitor.build.gradle |
Configures Capacitor plugins. |
apps/mobile/android/app/proguard-rules.pro |
Adds ProGuard defaults. |
apps/mobile/android/app/src/androidTest/java/com/getcapacitor/myapp/ExampleInstrumentedTest.java |
Adds Android instrumented test. |
apps/mobile/android/app/src/main/AndroidManifest.xml |
Declares Android shell and links. |
apps/mobile/android/app/src/main/java/com/teslasync/app/MainActivity.java |
Adds Capacitor activity. |
apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-ldpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-hdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-ldpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-mdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-xhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-xxhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-night-xxxhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-night/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-ldpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-hdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-ldpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-mdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-xhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-xxhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-night-xxxhdpi/splash.png |
Adds dark splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png |
Adds splash asset. |
apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml |
Defines adaptive icon foreground. |
apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml |
Defines launcher background. |
apps/mobile/android/app/src/main/res/drawable/splash.png |
Adds default splash. |
apps/mobile/android/app/src/main/res/layout/activity_main.xml |
Defines WebView activity layout. |
apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml |
Defines adaptive launcher icon. |
apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml |
Defines round launcher icon. |
apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-ldpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-ldpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-ldpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-ldpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png |
Adds launcher asset. |
apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png |
Adds launcher background. |
apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png |
Adds launcher foreground. |
apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png |
Adds round launcher asset. |
apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml |
Defines launcher color. |
apps/mobile/android/app/src/main/res/values/strings.xml |
Defines Android app strings. |
apps/mobile/android/app/src/main/res/values/styles.xml |
Defines Android themes. |
apps/mobile/android/app/src/main/res/xml/data_extraction_rules.xml |
Restricts Android backups. |
apps/mobile/android/app/src/main/res/xml/file_paths.xml |
Configures FileProvider paths. |
apps/mobile/android/app/src/test/java/com/getcapacitor/myapp/ExampleUnitTest.java |
Adds local Android test. |
apps/mobile/android/build.gradle |
Configures Android toolchain. |
apps/mobile/android/capacitor.settings.gradle |
Registers Capacitor modules. |
apps/mobile/android/gradle.properties |
Defines Gradle settings. |
apps/mobile/android/gradle/wrapper/gradle-wrapper.jar |
Adds Gradle wrapper. |
apps/mobile/android/gradle/wrapper/gradle-wrapper.properties |
Pins Gradle distribution. |
apps/mobile/android/gradlew |
Adds Unix Gradle launcher. |
apps/mobile/android/gradlew.bat |
Adds Windows Gradle launcher. |
apps/mobile/android/settings.gradle |
Defines Android modules. |
apps/mobile/android/variables.gradle |
Pins Android dependencies. |
apps/mobile/assets/icon.png |
Adds mobile icon source. |
apps/mobile/assets/splash.png |
Adds mobile splash source. |
apps/mobile/capacitor.config.json |
Configures Capacitor shell. |
apps/mobile/icons/icon-128.webp |
Adds mobile icon. |
apps/mobile/icons/icon-192.webp |
Adds mobile icon. |
apps/mobile/icons/icon-256.webp |
Adds mobile icon. |
apps/mobile/icons/icon-48.webp |
Adds mobile icon. |
apps/mobile/icons/icon-512.webp |
Adds mobile icon. |
apps/mobile/icons/icon-72.webp |
Adds mobile icon. |
apps/mobile/icons/icon-96.webp |
Adds mobile icon. |
apps/mobile/ios/.gitignore |
Ignores iOS outputs. |
apps/mobile/ios/App/App.xcodeproj/project.pbxproj |
Configures Xcode project. |
apps/mobile/ios/App/App.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist |
Adds workspace metadata. |
apps/mobile/ios/App/App/AppDelegate.swift |
Integrates Capacitor lifecycle. |
apps/mobile/ios/App/App/Assets.xcassets/AppIcon.appiconset/AppIcon-512@2x.png |
Adds iOS app icon. |
apps/mobile/ios/App/App/Assets.xcassets/AppIcon.appiconset/Contents.json |
Defines iOS icon set. |
apps/mobile/ios/App/App/Assets.xcassets/Contents.json |
Defines iOS assets. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Contents.json |
Defines splash variants. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany-dark.png |
Adds dark splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@1x~universal~anyany.png |
Adds splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany-dark.png |
Adds dark splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@2x~universal~anyany.png |
Adds splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany-dark.png |
Adds dark splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/Default@3x~universal~anyany.png |
Adds splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-1.png |
Adds splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732-2.png |
Adds splash asset. |
apps/mobile/ios/App/App/Assets.xcassets/Splash.imageset/splash-2732x2732.png |
Adds splash asset. |
apps/mobile/ios/App/App/Base.lproj/LaunchScreen.storyboard |
Defines iOS launch screen. |
apps/mobile/ios/App/App/Base.lproj/Main.storyboard |
Defines Capacitor controller. |
apps/mobile/ios/App/App/Info.plist |
Configures iOS app and links. |
apps/mobile/ios/App/Podfile |
Declares Capacitor pods. |
apps/mobile/package-lock.json |
Locks mobile dependencies. |
apps/mobile/package.json |
Defines mobile dependencies. |
apps/msix/.gitignore |
Ignores MSIX output. |
apps/msix/README.md |
Documents PWABuilder flow. |
apps/msix/package.ps1 |
Adds PowerShell preflight. |
apps/msix/package.sh |
Adds shell preflight. |
apps/twa/.gitignore |
Ignores TWA secrets. |
apps/twa/README.md |
Documents TWA builds. |
apps/twa/build.sh |
Builds TWA artifacts. |
apps/twa/init.sh |
Initializes per-domain TWA. |
docker-compose.yml |
Exposes native-app configuration. |
docs/.vitepress/config.ts |
Links app and privacy docs. |
docs/apps.md |
Documents native apps. |
docs/privacy.md |
Adds privacy policy. |
helm/teslasync/templates/configmap.yaml |
Renders CORS and asset links. |
helm/teslasync/values.yaml |
Adds native-app values. |
internal/api/apikey/handler.go |
Creates app-bound API keys. |
internal/api/apikey/handler_test.go |
Tests app-key behavior. |
internal/api/apikey_middleware.go |
Authenticates bearer app tokens. |
internal/api/apikey_middleware_test.go |
Tests token middleware. |
internal/api/assetlinks_test.go |
Tests asset-links endpoint. |
internal/api/middleware/csrf.go |
Allows validated app tokens. |
internal/api/middleware/csrf_test.go |
Tests app-token CSRF handling. |
internal/api/router.go |
Wires auth and asset links. |
internal/auth/apptoken.go |
Carries app-token identity. |
internal/auth/apptoken_test.go |
Tests identity context. |
internal/auth/session_tracker.go |
Skips app-token sessions. |
internal/config/config.go |
Loads asset-links configuration. |
migrations/000254_api_keys_subject.down.sql |
Removes app-token binding. |
migrations/000254_api_keys_subject.up.sql |
Adds app-token binding. |
ops/migrations/manifest.yaml |
Records migration risk. |
web/nginx.conf |
Proxies well-known resources. |
web/public/favicon.svg |
Updates favicon branding. |
web/public/icons/README.md |
Documents icon generation. |
web/public/icons/apple-touch-icon.png |
Updates Apple icon. |
web/public/icons/icon-192.png |
Updates PWA icon. |
web/public/icons/icon-192.svg |
Updates icon source. |
web/public/icons/icon-512.png |
Updates PWA icon. |
web/public/icons/icon-512.svg |
Updates icon source. |
web/public/icons/icon-maskable-192.png |
Updates maskable icon. |
web/public/icons/icon-maskable-192.svg |
Updates maskable source. |
web/public/icons/icon-maskable-512.png |
Updates maskable icon. |
web/public/icons/icon-maskable-512.svg |
Updates maskable source. |
web/scripts/generate-app-icons.mjs |
Generates cross-platform icons. |
web/src/App.tsx |
Mounts server and link routing. |
web/src/api/client.ts |
Adds remote bearer requests. |
web/src/api/devtools.ts |
Authenticates import uploads. |
web/src/api/hooks/useAdmin.ts |
Supports app-key creation. |
web/src/api/hooks/useVehiclePhoto.ts |
Authenticates photo uploads. |
web/src/api/sseClient.ts |
Supports remote SSE. |
web/src/components/feedback/ReauthDialog.tsx |
Authenticates reauthorization calls. |
web/src/components/ui/FontProvider.tsx |
Loads remote font settings. |
web/src/components/ui/MaskedValue.tsx |
Authenticates reveal audits. |
web/src/components/ui/ThemeProvider.tsx |
Loads remote theme settings. |
web/src/features/admin/components/api-keys/ApiKeyCard.tsx |
Displays app-bound keys. |
web/src/features/admin/components/api-keys/CreateApiKeyModal.test.tsx |
Tests app-key form. |
web/src/features/admin/components/api-keys/CreateApiKeyModal.tsx |
Adds app sign-in option. |
web/src/features/admin/pages/ApiPlaygroundPage.tsx |
Authenticates playground requests. |
web/src/features/server/ConnectPage.test.tsx |
Tests server connection flow. |
web/src/features/server/ConnectPage.tsx |
Implements server picker. |
web/src/features/server/NativeDeepLinks.test.ts |
Tests native link allowlist. |
web/src/features/server/NativeDeepLinks.tsx |
Routes native deep links. |
web/src/features/server/ServerGate.tsx |
Enforces first-launch setup. |
web/src/features/settings/components/ServerSection.tsx |
Adds server settings. |
web/src/features/settings/pages/SettingsPage.tsx |
Registers server section. |
web/src/features/system/hooks/useStatusLiveSSE.ts |
Routes status SSE remotely. |
web/src/hooks/useAiStream.ts |
Authenticates AI streaming. |
web/src/hooks/useSSE.ts |
Routes signal SSE remotely. |
web/src/i18n/en.json |
Adds canonical English strings. |
web/src/i18n/en/locale-admin.json |
Generates admin translations. |
web/src/i18n/en/locale-settings.json |
Generates connection translations. |
web/src/i18n/en/runtime-manifest.json |
Registers runtime namespace. |
web/src/i18n/en/shell.json |
Adds route label. |
web/src/i18n/en/usage-manifest.json |
Registers translation usage. |
web/src/index.css |
Styles desktop title bar. |
web/src/lib/__tests__/appIcon.test.ts |
Updates icon expectations. |
web/src/lib/appIcon.ts |
Updates icon background. |
web/src/lib/automationSSE.ts |
Routes automation SSE remotely. |
web/src/lib/eventStream.test.ts |
Tests authenticated SSE transport. |
web/src/lib/eventStream.ts |
Implements fetch-backed SSE. |
web/src/lib/resilience.ts |
Routes resilient remote requests. |
web/src/lib/routePrefetch.ts |
Prefetches connection page. |
web/src/lib/routeRegistry.ts |
Registers connection route. |
web/src/lib/serverConnection.test.ts |
Tests connection state. |
web/src/lib/serverConnection.ts |
Manages server and token state. |
web/src/lib/sseManager.ts |
Uses authenticated SSE. |
web/src/types/admin.ts |
Adds app-key subject type. |
web/vite.config.ts |
Registers protocol handler. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const data = await fs.promises.readFile(file); | ||
| return new Response(data, { | ||
| headers: { 'Content-Type': MIME[path.extname(file)] || 'application/octet-stream' }, | ||
| }); |
| headers: { 'Content-Type': 'application/json' }, | ||
| ...restOpts, | ||
| ...(demoCredentialsMode() ? { credentials: demoCredentialsMode() } : {}), | ||
| headers: authHeaders(optHeaders ?? { 'Content-Type': 'application/json' }), |
| // Context of the app under test. | ||
| Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); | ||
|
|
||
| assertEquals("com.getcapacitor.app", appContext.getPackageName()); |
| return | ||
| } | ||
|
|
||
| _, _ = q.Exec(r.Context(), `UPDATE api_keys SET last_used_at = NOW() WHERE id = $1`, id) |
| -- as an app token and additionally authenticates as that subject on the | ||
| -- whole API via `Authorization: Bearer`, so a phone/desktop app can sign | ||
| -- in to any self-hosted server. | ||
| ALTER TABLE api_keys ADD COLUMN IF NOT EXISTS subject VARCHAR(255); |
| <Route path="s/:token" element={<SafeRoute name="SharedDrive"><SharedDrive /></SafeRoute>} /> | ||
| <Route path="watch" element={<SafeRoute name="WatchFace"><WatchFace /></SafeRoute>} /> | ||
| <Route path="onboarding" element={<SafeRoute name="Onboarding"><Onboarding /></SafeRoute>} /> | ||
| <Route path="connect" element={<SafeRoute name="Connect"><ConnectPage /></SafeRoute>} /> |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Native authentication misses several direct downloads and streams, leaks tokens on one demo fallback path, and has unresolved credential-security and Android-test defects.
Review effort: Balanced
Findings: 3
Open (10)
App-token creation bypasses required sudo authentication · New Strip bearer tokens from cross-origin demo retries Add CSP to Electron protocol responses Deployment handler lacks tracing and error observability · New Browser-owned requests omit authentication in remote mode · New Allow /connect through onboarding gate Preserve full forward-auth subject length Debounce last-used timestamp database updates Fix Android applicationId assertion HTTP LAN hint conflicts with disabled mixed content · New
| } | ||
|
|
||
| var subjectArg *string | ||
| if body.AppToken { |
|
|
||
| // ServeAssetLinks serves the deployment-specific Android Digital Asset Links | ||
| // statement list; an unset value leaves the association endpoint disabled. | ||
| func ServeAssetLinks(configuredJSON string) http.HandlerFunc { |
| * pipeline. | ||
| */ | ||
| import { resilientFetch, ApiError, getApiBase, isApiError, camelCaseKeys } from '../lib/resilience' | ||
| import { authHeaders, isRemoteMode } from '../lib/serverConnection' |
| "subtitle": "Enter your TeslaSync address to continue.", | ||
| "serverLabel": "Server address", | ||
| "serverPlaceholder": "https://teslasync.example.com", | ||
| "serverHint": "Your self-hosted TeslaSync URL — local addresses like http://192.168.1.10:4000 work too.", |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Remote authentication currently has credential-disclosure risks and leaves at least one protected stream unusable from native shells.
Review effort: Balanced
Findings: 4
Open (11)
Reject bearer tokens over non-HTTPS connections · New App-token creation bypasses required sudo authentication Strip bearer tokens from cross-origin demo retries Add CSP to Electron protocol responses Browser-owned requests omit authentication in remote mode Deployment handler lacks tracing and error observability Allow /connect through onboarding gate Preserve full forward-auth subject length Debounce last-used timestamp database updates Fix Android applicationId assertion HTTP LAN hint conflicts with disabled mixed content
Previously missed (1)
In code that hasn't changed since last review

Ignore mobile signing key files

apps/mobile/android/.gitignore:58
The mobile README tells distributors to create and back up a signing key, but this new ignore file leaves both common key formats trackable. Uncomment these patterns so a release keystore cannot be committed accidentally.
| setBusy(true) | ||
| try { | ||
| const probe = await probeServer(normalized.url, token.trim() === '' ? null : token) |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Protected resources, token-expiry recovery, Windows navigation, and Android validation contain blocking native-app failures.
Review effort: Balanced
Findings: 4
Open (12)
Reject bearer tokens over non-HTTPS connections App-token creation bypasses required sudo authentication Strip bearer tokens from cross-origin demo retries Add CSP to Electron protocol responses Browser-owned requests omit authentication in remote mode Deployment handler lacks tracing and error observability Allow /connect through onboarding gate Preserve full forward-auth subject length Debounce last-used timestamp database updates Fix Android applicationId assertion Android job ignores selected runner input · New HTTP LAN hint conflicts with disabled mixed content
Previously missed (5)
In code that hasn't changed since last review

External navigation and download flows are blocked

apps/desktop/main.js:110
The shell blocks every navigation away from the private app origin and denies every new window without handing approved URLs to the OS. As a result, existing documentation/support links and window.open(...) download flows become inert in the Windows app. Add a strict http:/https: allowlist and open accepted external links with shell.openExternal; keep authenticated API downloads on a dedicated fetch/save path.

Error logs expose forward-auth user identity

internal/api/apikey/handler.go:190
The forward-auth subject is commonly an email address or username, so adding it to this error log exposes user identity in backend logs. Keep the operational context but remove the raw subject field.

Revoked tokens trigger endless event-stream retries

web/src/features/system/hooks/useStatusLiveSSE.ts:130
FetchEventStream marks 401/403 responses as CLOSED specifically to stop retrying a rejected token, but this hook treats every CLOSED error as reconnectable at lines 157–162. A revoked app token therefore causes /system/status/live to be retried forever. Detect this terminal remote-token state and remain offline or invoke the shared authentication recovery instead.

Protected resource URLs omit authentication

web/src/lib/resilience.ts:58
Using the selected server as the API base does not authenticate URL-only consumers. Existing apiUrl() callers such as vehicle-photo <img src> values and export/download anchors now point at the remote server without the session-stored Authorization header, so those resources return 401 on the forward-auth setup this feature is designed to support. Convert protected resources to authenticated fetches (then blob/object URLs or a native save/open bridge) instead of exposing them as plain navigation URLs.

Expired native tokens lack recovery path

web/src/lib/serverConnection.ts:159
A revoked or expired stored app token leaves native users without a recovery path. API 401s still invoke the browser-session reauthentication modal, but the connection gate remains bypassed while this token is in sessionStorage and Electron blocks navigation to the external IdP. Remote-mode authentication failures need to clear the token and return to /connect (or make the modal perform that native-specific recovery).
| name: Build Android test APK | ||
| needs: version | ||
| # The Android SDK is preinstalled on GitHub-hosted Ubuntu runners. | ||
| runs-on: ubuntu-latest |


What changed
Why
Let self-hosted users install and configure the app for their own servers; make testing binaries downloadable from GitHub Releases without claiming Store-ready signing.
How to test
Android APK and Windows packages were built and their formats verified locally. Emulator launch was blocked on this machine by Android Emulator reporting too many instances despite adb showing no device.