| .. | ||
| icons | ||
| src | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| README.md | ||
| tauri.conf.json | ||
deployment/src-tauri/ — the desktop shell (deploy method 5)
A Tauri window around the same public/ tree the CDN, Capacitor and local staging already
serve. This crate deliberately contains no application logic: the product is the web tree in
plugin-designs/ → public/, and this is a window around it — the desktop counterpart of
deployment/capacitor/android.
Why this file exists instead of comments in the config
capacitor.config.json documents itself with "//key": [...] comment entries, because Capacitor
ignores unknown keys. Tauri does not. Its config parser is strict and rejects unknown fields:
unknown field `//THE_TOKEN_CAVEAT`, expected one of `$schema`, `product-name`, `productName`,
`main-binary-name`, `mainBinaryName`, `version`, `identifier`, `app`, `build`, `bundle`, `plugins`
That is a hard build failure, found after cargo check compiled 228 crates. So the rationale
lives here and tauri.conf.json stays schema-pure. Do not add // keys to it.
The two placement facts that cost a build each
-
tauri.conf.jsonmust live indeployment/src-tauri/, not the repo root.tauri-buildreads it from the crate directory; at the root it is invisible and the build dies with "unable to read Tauri config file … because entity not found".check:taurinow asserts the canonical location and fails on a stray root copy. -
frontendDistis relative to this directory, hence../public. It is a POINTER, NOT A COPY — onebun run buildfeeds four consumers:# Consumer Reads 1 Railway / CDN public/served statically2 Capacitor webDir: "public"→ copied into the Android assets3 local staging bun run serve(public/ on :4173)4 Tauri build.frontendDist: "../public"beforeBuildCommandis deliberately empty.bun run buildis the authoritative single-writer ofpublic/(CI runs it asbuild:unlocked); wiring it here would make a desktop build a second writer of the shared tree — the concurrent-clean race the build lock exists to prevent. Build first, then package.
The token / CORS caveat — read before debugging blank basemaps
A Tauri webview serves the app from a custom origin (tauri://localhost, or
http://tauri.localhost on Windows), not from your token host. Every map bundle fetches its
Mapbox token from the configured tokenUrl, so that endpoint must send CORS headers permitting
the Tauri origin — exactly the surprise capacitor.config.json documents for https://localhost.
The fix is a response header on the token host, not a change in tauri.conf.json.
Measured 2026-09-21: https://doxa.life/api/maps/token → 200;
the test host's /api/maps/token → 404. A desktop build pointed at that host would launch
with blank basemaps even though the window opens and the element mounts correctly. Separately, the
Mapbox token is URL-restricted: with the test host as Referer the style document returns 200 but
every tile 403s, while the same token with a doxa.life Referer returns 200. Verify the token
path before blaming the shell.
Why the window opens on the cap-5 SPA
app.windows[0].url points at doxa-maps/doxa-maps-spa/doxa-maps-spa/index.html — the flagship
capability-5 app, not the gallery index. 1440×900 is the viewport the SPA is verified at (its rail
renders 11 rows there: 6 curated + 5 auto-discovered).
security.csp is null so the page inherits whatever it declares when served, matching CDN
behaviour. Do not tighten it without re-running the mount check: the leaf pages load their map
engine from a CDN — api.mapbox.com/mapbox-gl-js/… for Mapbox and cdn.jsdelivr.net/npm/ maplibre-gl@5/… for MapLibre (see the ENGINES registry in
library/composables/useMapInstance.js). A naive script-src 'self' would break every map.
Corrected 2026-09-21. This previously claimed the bundles "self-host
mapbox-glfrompublic/assets/vendor/(the no-CDN rule)". That is false — measured:vendor/contains onlytabulator, and the leaves carrysrc="https://api.mapbox.com/mapbox-gl-js/v3.24.0/mapbox-gl.js". A CSP written from the old sentence would have been wrong in exactly the way that produces a blank map, so the error mattered.
Cargo.lock is committed — on purpose
Cargo.lock was originally gitignored here, grouped with /target and /gen. Those two are
genuinely regenerated; a lockfile is not. It is the resolution record — the only artifact that
makes cargo build reproducible. Cargo's rule splits on crate type: commit it for a binary,
ignore it for a library. cargo metadata reports this crate as kinds = ['bin'].
The stakes are concrete: Cargo.toml pins nothing tighter than tauri = { version = "2" }, while
the lockfile pins 430 packages. Without it, an inheritor building next month resolves a different
dependency tree than the one that produced the shipped 64 MB binary. Verified 2026-09-22 that the
committed lockfile is consistent with the manifest (cargo metadata --locked --offline succeeds) and
contains no local paths or credentials.
Commands
bun run check:tauri # config contract — NO Rust, NO Tauri CLI needed (CI-safe)
cargo check # typechecks the crate (needs Rust + webkit2gtk)
bun run tauri:build # the real binary + .deb (CLI is a devDependency: @tauri-apps/cli)
bundle.targets is ["deb"], not "all" — on purpose
"all" attempts deb + rpm + appimage. Measured on this box: dpkg-deb present, but
rpmbuild and linuxdeploy (AppImage) absent, so "all" would fail on two of three
formats after a full release compile. Narrowed to what the machine can actually produce.
Add targets back when the packaging tools exist — the failure comes at the very END of a
~6-minute release build, which is the expensive place to discover it.
Status: BUILT — binary + .deb emitted; LAUNCH still unverified
Verified 2026-09-21 with the real toolchain (tauri info: webkit2gtk 2.50.6, rustc/cargo
1.96.1, frontendDist ../public, framework Vue.js, bundler Vite — all ✔):
| Artifact | Result |
|---|---|
target/release/doxa-maps-desktop |
65 MB ELF 64-bit executable |
target/release/bundle/deb/*.deb |
58 MB, Package: doxa-maps, Depends: libwebkit2gtk-4.1-0, libgtk-3-0 |
The web tree is compiled INTO the binary — not shipped as loose files, which is why the
.deb has only 6 entries. Confirmed by 19 distinct embedded doxa-maps/... asset paths in the
ELF (all bundle dirs + assets/), and by size: a logic-free shell is 65 MB only because the
132 MB public/ tree is compressed inside it.
What is still NOT proven: that the window opens and renders. This VPS is headless — running
the binary panics in tao with "Failed to initialize GTK", which is the absence of a display,
not a defect. No Xvfb is installed. A human on a desktop machine (or a CI job with a virtual
display) must run it once to move method 5 to shot.