Frontend map

The SeeKi UI is a small Svelte 5 application. It lives under frontend/src/, builds with Vite, and ends up as a handful of static files that the Rust binary serves itself. Everything starts at main.ts, which mounts App.svelte and steps out of the way.

Component tree

Source files under frontend/src/components/. App.svelte at the root owns every piece of state; children receive props and fire events.

Boot sequence

From cold load to first grid render:

  1. main.ts mounts App.svelte into #app.
  2. App.svelte’s onMount calls fetchStatus(). If the server is in setup mode, the wizard renders and the data path is skipped.
  3. In normal mode, App.svelte calls fetchTables() and fetchDisplayConfig() in parallel.
  4. When the user picks a table, App.svelte calls fetchColumns(table) then fetchRows(params) and passes the result down to DataGrid.

State lives at the top

There is no store, no context, no external state library. App.svelte is the single source of truth; every child receives what it needs as props. This is deliberate — the data flow is small enough that adding a store would cost more than it saves.

State is declared with Svelte 5 runes. A cut-down view of the orchestrator:

let tables: TableInfo[] = $state([]);
let selectedTable: string = $state('');
let columns: ColumnInfo[] = $state([]);
let queryResult: QueryResult | null = $state(null);
let sortState: SortState = $state({ column: null, direction: null });
let filters: FilterState = $state({});
let searchTerm: string = $state('');

let activeFilterCount = $derived(
  Object.values(filters).filter((v) => v.trim().length > 0).length
);
let visibleColumns = $derived.by(
  () => columns.filter((c) => columnVisibility[c.name] !== false)
);

Children receive these as props. When the user acts — types into search, clicks a sort header, changes a page — the child fires an event, App.svelte updates its state, a $derived or an $effect rebuilds the query params, and fetchRows runs again.

RevoGrid, the hero

DataGrid.svelte is a thin wrapper over RevoGrid. Its job is narrow: take visibleColumns and queryResult.rows, translate them into RevoGrid’s column and data arrays, and render. Three details matter:

  • Columns are keyed by name; widths persist per-table in localStorage under a prefixed key.
  • Yes/No and NULL are cell templates, not CSS on raw text — the shape and label carry the meaning, not colour alone.
  • Sort headers are intercepted at the wrapper level and turned into events, so sorting goes through the server every time (no client-side reshuffle that would lie about the row count).

The API client

Every call to the backend goes through frontend/src/lib/api.ts. One helper, one shape, one set of timeouts.

// frontend/src/lib/api.ts
const DEFAULT_TIMEOUT_MS = 30_000;
const SETUP_TIMEOUT_MS   = 60_000;
const USE_MOCK = import.meta.env.VITE_MOCK === 'true';

async function apiFetch<T>(path: string): Promise<T> {
  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS);
  // ... fetch, handle non-2xx, parse JSON, assertShape ...
}

Three details worth knowing:

  • Timeout by default. Every request aborts after 30 seconds (60 for setup). A slow database never freezes the UI.
  • Shape assertions. Responses are checked with assertShape before they reach the renderers. If the server ships an unexpected shape, the user sees a readable error instead of a silent broken grid.
  • Mock mode. When VITE_MOCK=true, the client routes to in-memory fixtures in frontend/src/lib/mock.ts. That is how just dev-mock runs the UI with no backend at all — useful for component work and CI snapshots.

The lib/ folder

FilePurpose
lib/api.tsThe HTTP client. All network calls live here.
lib/types.tsShared TypeScript shapes: TableInfo, ColumnInfo, QueryResult, SortState, FilterState.
lib/constants.tsMagic strings: localStorage keys, default page size.
lib/data-grid.tsPure helpers used by DataGrid.svelte — formatting, column mapping, value rendering.
lib/mock.tsFixture responses for VITE_MOCK=true. Mirrors the real API shape exactly.

The .test.ts siblings are Vitest unit tests. Keep them next to the code they exercise.

Theming and tokens

frontend/src/theme/tokens.css is where the design tokens live — colours, spacing, typography, the --sk-* variables the whole UI reads. app.css wires those tokens to base element styles. Component styles go in the component file, scoped by Svelte, and reach for tokens through var(--sk-*). No raw hex, no per-component palette.

Where to go from here

  • Architecture — the backend side of every fetch call.
  • Build & release — how frontend/dist/ ends up inside the Rust binary.
  • Data grid — the product surface these components serve.