Adds MapLibre's own GeolocateControl to the geo map, in the bottom-right corner above the camera group, in tracking mode: the first press asks the browser for the device's position, frames it to the accuracy of the fix and follows it; dragging the map keeps the dot and frees the camera; a second press turns the watch off and removes the dot. The control is reused rather than rewritten. Its behaviour is what a Trilium implementation would have had to copy: fitBounds on the accuracy radius with a zoom cap, the active/background state machine driven by movestart, the stale-dot treatment on error, and a permission probe that notices an insecure origin and disables the button. Only its white button clashes with the map's own controls, and that is a later step: the plan is to hide the stock button and drive trigger() from an OverlayControlButton on MapToolbar, mirroring the control's events. The dot and accuracy circle are DOM markers, so unlike the layer-drawn pins they need no carrying across a style switch. The stock button stands one inset above MapToolbar's group, placed through the existing --geo-map-foot variable, so the two do not overlap and the button follows the group's safe-area insets in fullscreen. Electron denied geolocation on purpose in the session permission allowlist; it is now granted to the trilium-app://app shell only, gated by the same origin check as clipboard-write and notifications, so a remote embed in the default session does not learn where the device is. MapLibre probes the permission through the synchronous check handler before it enables its button, which is why the allowlist entry is needed for the button to be usable at all on desktop. Android declares coarse and fine location so Capacitor's WebChromeClient can prompt for them, and marks the location hardware features as optional: ACCESS_FINE_LOCATION otherwise implies GPS as a required feature, which Google Play uses to hide the app from GPS-less devices. iOS declares the when-in-use usage description, without which WebKit refuses the API. Verified: the MapToolbar spec asserts the control is added in tracking mode, removed on unmount and not removed from a map that already tore down; the desktop spec asserts the app shell is granted geolocation and embeds and guests are not. Both fail with the respective change reverted. Not verified: the running app, the mobile builds, and the Linux desktop path, where Chromium's network location provider may need a Google API key. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| android | ||
| ios | ||
| .gitignore | ||
| capacitor.config.json | ||
| package.json | ||
| README.md | ||
@triliumnext/mobile
Capacitor shell that wraps the @triliumnext/standalone PWA build as a native mobile app. This package does not ship its own web assets — webDir in capacitor.config.json points directly at ../standalone/dist.
Prerequisites
- Android SDK + an emulator or attached device (set up
ANDROID_HOME/ANDROID_SDK_ROOT). - JDK 17+.
- The monorepo installed:
corepack enable && pnpm installat the repo root.
First-time setup
# 1. Build the standalone web app into apps/standalone/dist
pnpm --filter @triliumnext/mobile build
# 2. Generate the native Android project (one-off — commits as apps/mobile/android/)
pnpm --filter @triliumnext/mobile exec cap add android
Everyday loop
pnpm --filter @triliumnext/mobile build # rebuild standalone dist
pnpm --filter @triliumnext/mobile sync # copy dist into android/
pnpm --filter @triliumnext/mobile run:android # launch on emulator/device
# or
pnpm --filter @triliumnext/mobile open:android # open Android Studio
How web requests reach the local server (Android vs iOS)
There is no network backend — the whole server runs in-process as WASM in a web worker. How the client's API/sync calls (/api, /sync, /bootstrap, /search) reach that worker differs by platform, because the two WebViews resolve *Scheme: "https" differently:
- Android —
androidScheme: "https"works: the app loads athttps://localhost(a real secure origin), so the service worker (apps/standalone/src/sw.ts) intercepts those requests and forwards them to the worker. - iOS — the app loads at
capacitor://localhostand uses fetch / XHR / image interceptors (apps/standalone/src/main.ts, gated onlocation.protocol === "capacitor:") instead, because a service worker cannot register oncapacitor://(navigator.serviceWorker.register()throws — the scheme is not HTTP/HTTPS).
Why iOS is on capacitor:// and not https: Capacitor ignores iosScheme: "https". WKWebView reserves the http/https schemes, so CAPInstanceDescriptor.normalize() (WKWebView.handlesURLScheme("https") == true) rejects it and resets the scheme to the default capacitor. That's why iosScheme is intentionally not set here — it would be a no-op that falsely implies iOS runs on an https origin.
⚠️ Do not remove the iOS interceptor path as "dead code." It is the only working request path on iOS. And do not re-add
iosScheme: "https"— it does nothing on iOS and is misleading.