# UI ↔ backend contract The Svelte UI (`src/`) talks to the Rust backend (`src-tauri/`) only through the Tauri commands and events below. `src/lib/backend.ts` is the single place that calls them. When the page runs outside Tauri (a plain browser, for design work), it falls back to a mock with the same shapes. All names are snake_case on the wire (serde defaults), and timestamps are Unix seconds. ## Types ```ts type Phase = | "idle" // nothing running | "checking" // probing the site | "signing_in" // browser sign-in in progress | "installing" // downloading + verifying a version | "restoring" // loading the database snapshot into local Postgres | "syncing" // exchanging operator data with the DHP (ODIAN is not running) | "pulling_tiles" // fetching tiles the DHP baked while this PC was away | "removing" // deleting marketplace items the operator unticked | "building" // dev mode only: compiling the checkout before it launches | "launching" // starting Postgres → API → tile server → Odian.exe | "running" // Odian.exe is open | "error"; // the last long operation failed; see `error` interface Status { online: boolean; // the site's update service answered /v1/health (2 s, then 10 s if that timed out: a hotspot is slow, not absent) site: { url: string; name: string | null }; operator: string | null; // last signed-in ODIAN username (cached; valid offline) signed_in: boolean; // this PC remembers the operator's site sign-in until Sign out: the // launcher renews the short Engine token by itself (launcher session) installed: string | null; // installed version, e.g. "0.51.0" available: string | null; // the site's CURRENT version (online only) update_required: boolean; // online && installed !== null && available !== null && installed !== available // (never true when nothing is installed: that is a first install) can_launch: boolean; // installed && !update_required && phase is idle or error phase: Phase; error: string | null; // set with phase "error"; cleared by the next command or `refresh` install_dir: string; // e.g. "D:\\ODIAN" portable: boolean; // this launcher ships on an ODIAN drive: it runs that drive's // install (any letter), the folder is fixed, no self-update sync: SyncStatus | null; // counts from the last sync this session; null before one live: boolean | null; // while ODIAN runs: true = its data comes from the DHP, // false = from this PC. null when it is not running, or // the DHP offers no Engine API to forward to map_from_site: boolean | null; // while ODIAN runs: false = the map is this PC's own tiles // because the site's tile service did not answer map_problem: string | null; // why, in one line, when map_from_site is false tiles_waiting: Waiting | null; // tiles the DHP baked for the sets this PC holds since its // last sync: what a sync would fetch. null offline, with // nothing installed, or when the DHP is too old to ask unfinished: Unfinished | null; // chosen marketplace items not fully on this PC. ODIAN // still runs, with less map than was asked for dev: Licence | null; // this PC's dev licence. null on every operator PC, and // the window then shows no trace of dev mode at all } interface Waiting { tiles: number; // 0 means this PC is level with the site sets: number; // how many of its tile sets have something waiting capped: boolean; // the DHP stopped counting: "50000+" } interface Unfinished { items: number; bytes: number; // what finishing them downloads IN FULL — the whole item, // not the gap, which is what the installer skips through titles: string[]; // the first few, to name in one line } interface Licence { machine: string | null; // the PC it was issued for; null = not tied to one install_id: string | null; // the install it was issued for; null = not tied to one expires_at: string; // "YYYY-MM-DD", the last day it works note: string | null; } interface SyncStatus { pending: number; // local edits the DHP has not taken yet conflicts: number; // records both sides changed: the operator decides rejected: number; // edits the DHP refused (another operator's record, ...) waiting: number; // DHP changes this PC could not apply yet } interface SyncConflict { tbl: string; // database table, e.g. "db_ground_poi" key: Record; // the record's key columns kind: "conflict" | "rejected"; fields: string[]; // clashing fields; "*exists": one side deleted it; "*value": a whole-value clash base: unknown; // the DHP value before this PC's edit mine: unknown; // this PC's value (null = deleted here) theirs: unknown; // the DHP's value (null = deleted there) reason: string | null; // why the DHP refused it (rejected only) } interface Settings { site_url: string; // e.g. "http://192.168.1.41:30097"; "" = no DHP, run offline mcp_url: string; // site MCP, for Keycloak sign-in; "" = no sign-in, run offline ai_url: string; // MCP serving /api/ai/... over the internet; "" = none install_dir: string; // Dev mode. Only honoured on a PC whose `dev` licence is valid: `set_settings` stores // dev_mode as false without one, whatever the window sends. dev_root: string; // folder holding the checkouts (ODIAN_Engine); "" = none dev_mode: boolean; // run the stack from the checkout, each part in its own console dev_user: string; // run as this operator instead of the signed-in one; "" = them. // A DIFFERENT name switches sync OFF for that run dev_build: boolean; // build the checkout before launching (phase "building") dev_target: "client" | "web" | "both"; // which client(s) Launch starts. "both" runs them // against the ONE stack: same API, same database } interface TileSyncProgress { tiles: number; // tiles written so far bytes: number; page_tiles: number; // tiles in the page being written page_done: number; // of them, written } interface RemoveProgress { title: string; // the item being deleted done: number; // bytes deleted of it total: number; index: number; // which of them, 1-based: one Apply can drop several count: number; } interface InstallProgress { stage: "manifest" | "app" | "db" | "src" | "tiles" | "restore" | "finalize"; stage_index: number; // 1-based stage_count: number; done_bytes: number; // within the current stage total_bytes: number; // of the current stage; 0 when unknown done_items: number; // within the current stage total_items: number; bytes_per_sec: number; message: string | null; // e.g. "tiles dark/protomaps_20260423: 412/1402 blocks" } interface StackEvent { component: "postgres" | "api" | "tiles" | "odian" | "web"; // "web" only on a dev run state: "starting" | "ready" | "stopped" | "failed"; message: string | null; } interface LogEvent { level: "info" | "warn" | "error"; message: string; at: number } interface MarketItem { key: string; // stable across versions: "tiles: