# 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) | "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 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 } 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" mcp_url: string; // site MCP, for Keycloak sign-in install_dir: string; } 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"; 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: