Manual/Reference

Build and platform maintenance

This page explains how to keep Meratyn healthy over time: dependency updates, the compatibility mirrors that silent bumps can break, the verification gates every change must pass, and the platform-specific policies the desktop shell applies at runtime.

Dependency maintenance

One root bun.lock wires every JavaScript workspace (apps/*, packages/*, services/*), so a single Bun entry covers the whole JS tree. Python and Rust ecosystems have their own manifests and lockfiles.

Dependabot opens grouped weekly PRs for each ecosystem (.github/dependabot.yml):

EcosystemDirectoryNotes
bun/One lockfile for all JS workspaces
pip/services/serverThe FastAPI sidecar
pip/pythonThe meratyn Jupyter anywidget
cargo/apps/mappview/src-tauriThe Tauri desktop crate
github-actions/Workflow actions

Security fixes always land as their own immediately actionable PRs; routine minor/patch bumps are batched into one PR per ecosystem. The CI audit job runs bun audit --audit-level=high (blocking) plus a non-blocking pip-audit of the resolved backend environment.

When reviewing a dependency PR:

  1. Run the full local gate (bun run ci) before merging — several libraries are mirrored or pinned by behavior, and the suite catches drift (see the next section).
  2. Refresh the sidecar lockfile whenever services/server/pyproject.toml changes: uv lock --project services/server. The lock is committed because the desktop installers launch the sidecar with uv run --frozen from a read-only resource directory; a stale lock fails CI’s “Check the bundled sidecar lockfile is in sync” step, and a missing one fails at the user’s desktop with an opaque exit code 2.
  3. Re-check the Tauri plugin option contracts described under Windows native sidecar transport when @tauri-apps/plugin-http is upgraded.

Guarded compatibility mirrors

Some external libraries expose constants or class names Meratyn depends on but does not export. Copies live in our source and must be re-checked whenever the external package moves (including Dependabot PRs). Each has a test that fails in CI when the mirror drifts, so running the frontend suite after a bump is usually enough:

MirrorMirrorsOwner in this repoRe-check when
MAX_VECTOR_PMTILES_ZOOMThe deepest zoom vector_to_pmtiles accepts (18), compiled into the WASM binarypackages/processing/src/conversion/wasm.ts@mappview/wasm is rebuilt
Whitebox menu catalogThe WASM tool registryapps/mappview/src/lib/whitebox-menu-catalog.ts (generated, do not hand-edit)@mappview/wasm is rebuilt: run bun scripts/assets/whitebox-catalog.ts and commit the result
MAX_VECTOR_BYTESmaplibre-gl-vector’s internal MAX_REMOTE_FILE_BYTES (2 GiB)packages/plugins/src/plugins/shared/remote/file-formats.tsmaplibre-gl-vector is bumped
MAP_PANEL_SELECTORRendered class names from maplibre-gl-componentsapps/mappview/src/features/recording/video/operations.tsmaplibre-gl-components is bumped
propertySpecFor@maplibre/maplibre-gl-style-spec’s unexported StylePropertySpecificationpackages/core/src/style/expressions.ts@maplibre/maplibre-gl-style-spec is bumped: run tests/styles/expressions.test.ts

Verification gates

Every change should pass the full local gate:

bun run ci

This runs lint, the web build, workspace type checks, the frontend suite with coverage, the worker type check, backend coverage, and a Rust check of the Tauri crate.

  • Coverage floors are a ratchet: test:frontend:coverage fails below 78% lines / 78% branches / 63% functions, and test:backend:coverage fails below 55%. When coverage rises comfortably above a floor, raise the floor to lock in the gain.
  • The backend coverage run needs pytest-cov; installing the test extra (pip install -e "services/server[test]") enables the optional engine tests (geopandas/rasterio/sedona/httpx) that otherwise skip.
  • .pre-commit-config.yaml includes a local bun-build hook that compiles the whole app; scope it to the files you changed with pre-commit run --files <paths>.
  • Playwright smoke tests live under e2e/ and run against the built web app via bun run test:e2e.

Platform compatibility

The rest of this page documents platform-specific policies the desktop shell applies at runtime. They are part of the maintenance surface: when a dependency in the chain (WebKitGTK, WebView2, Tauri plugins) changes, re-read the relevant section before shipping.

Redistributable artifact boundary

Meratyn’s web build only exposes the VITE_ names listed in apps/mappview/vite.config.ts. Unknown names are removed before Vite resolves the explicit client environment (their process values are blanked first). Embed builds (MAPPVIEW_EMBED=1), Tauri builds, and any build with MAPPVIEW_STRIP_CREDENTIALS=1 blank provider credentials so a key from the build machine cannot enter a wheel or desktop installer. Users provide their own provider keys at runtime in Settings → Environment variables.

The boundary is enforced twice:

  • scripts/scan-credentials.ts scans emitted JavaScript, CSS, HTML, JSON, maps, and text files for known provider-token shapes, credential environment assignments, and URLs containing userinfo. It redacts matches in diagnostics.
  • python/hatch_build.py runs the same scan for every wheel/sdist build, including when python/src/mappview/static/app already contains a stale build.

The scanner shares its definitions through scripts/credential-patterns.json. The only allowlisted token is CesiumJS’s vendor-owned public Ion token; refresh its hash only after confirming a dependency upgrade still ships that token and identifies CesiumJS. Do not allowlist Meratyn, provider, or operator keys.

Linux WebKitGTK/NVIDIA pacing

At startup the desktop shell selects a compatibility policy from the WebKitGTK version and the active NVIDIA renderer:

WebKitGTK / rendererDefault policy
older than 2.48WEBKIT_DISABLE_DMABUF_RENDERER=1
NVIDIA, 2.48–2.51WEBKIT_DISABLE_DMABUF_RENDERER=1
NVIDIA, 2.52 or newerkeep DMABUF enabled and set WEBKIT_DMABUF_RENDERER_FORCE_SHM=1
non-NVIDIA, 2.48 or newerno override

An explicit WEBKIT_DISABLE_DMABUF_RENDERER or WEBKIT_DMABUF_RENDERER_FORCE_SHM value always wins. CI covers the pure policy matrix; actual driver/WebKit combinations remain distribution-specific, so a blank window or frame drops should be reproduced with the distro’s WebKitGTK and NVIDIA packages before changing the defaults.

Linux CPUs without AVX

On x86 Linux, JavaScriptCore’s WebAssembly tier-up trampoline can execute AVX instructions without checking the CPU. A machine without AVX can therefore lose its WebKit web process with SIGILL as soon as DuckDB-WASM or Whitebox gets hot, leaving a blank desktop window. At startup Meratyn checks the runtime feature bit and, only when AVX is absent, sets both options below before WebKit starts:

JSC_useWasmOSR=false
JSC_useBBQTierUpChecks=false

The options are an all-or-nothing pair: either one left on still reaches the unsafe trampoline. An explicit value parsed as on (true, yes, 1, or an unknown value) on either option is an opt-out and leaves both untouched; an explicit off value (false, no, 0, case-insensitive) is preserved while the missing peer is filled in. WebAssembly remains baseline-compiled, so this preserves functionality with a bounded performance cost. arm64 and CPUs with AVX are unchanged. The pure decision and JavaScriptCore value matrix live in the Tauri Rust tests; do not replace this runtime feature check with a CPU model or release-date list.

Windows native sidecar transport

Windows WebView2 can reject the local processing sidecar because of browser CORS and Local Network Access policy. The desktop bootstrap therefore installs Tauri’s native HTTP fetch on Windows before mounting the app. Only the exact loopback origin http://127.0.0.1:8765/* is sent through that client; custom VITE_SIDECAR_URL values and same-origin /sidecar deployments keep browser fetch. The matching Tauri capability is in apps/mappview/src-tauri/capabilities/default.json.

Two plugin options are security and connectivity invariants, not merely type choices:

  • maxRedirections: 0 prevents the per-launch X-MappView-Token from being replayed to another host by a redirect.
  • proxy: { all: { url: "http://127.0.0.1:8765", noProxy: "*" } } disables reqwest’s system-proxy lookup and excludes every host from the supplied proxy, keeping loopback traffic direct even on a corporate-proxied machine.

The option names are derived from @tauri-apps/plugin-http’s fetch type and the adapter tests pin the exact values. When that plugin is upgraded, re-check the reqwest/hyper-util behavior as well as the compiler-checked names: neither redirect nor proxy semantics are guaranteed by the TypeScript declarations.