Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Frontend Architecture

The moltis web UI is a TypeScript single-page application built with Preact and Vite.

Directory Layout

crates/web/
├── ui/                          # TypeScript source & tooling
│   ├── src/                     # Application source
│   │   ├── app.tsx              # Main entry point
│   │   ├── login-app.tsx        # Login page entry
│   │   ├── onboarding-app.tsx   # Onboarding wizard entry
│   │   ├── types/               # Shared type definitions
│   │   ├── stores/              # Preact Signal stores
│   │   ├── components/          # Reusable Preact components
│   │   │   └── forms/           # Form field & layout components
│   │   ├── pages/               # Page components
│   │   │   ├── sections/        # Settings page sections
│   │   │   ├── channels/        # Channel modal sub-components
│   │   │   └── chat/            # Chat page sub-modules
│   │   ├── providers/           # Provider setup sub-modules
│   │   ├── sessions/            # Session management sub-modules
│   │   ├── onboarding/          # Onboarding step components
│   │   ├── ws/                  # WebSocket handler sub-modules
│   │   ├── hooks/               # Custom Preact hooks
│   │   └── locales/             # i18n translations (en, fr, zh)
│   ├── e2e/                     # Playwright E2E tests
│   ├── vite.config.ts           # Vite build configuration
│   ├── tsconfig.json            # TypeScript strict config
│   └── package.json             # Dependencies & scripts
├── src/
│   ├── assets/                  # Served static assets
│   │   ├── dist/                # Vite build output (committed)
│   │   ├── css/                 # Stylesheets (Tailwind + custom)
│   │   ├── js/                  # E2E test shims + share page
│   │   ├── icons/               # Favicons & PWA icons
│   │   └── sw.js                # Service worker
│   └── templates/               # Askama HTML templates

Build Pipeline

TypeScript → JavaScript (Vite)

Source files in ui/src/ are compiled and bundled by Vite into src/assets/dist/. Three entry points produce three bundles:

  • dist/main.js — main app (chat, settings, all pages)
  • dist/login.js — login page
  • dist/onboarding.js — onboarding wizard
cd crates/web/ui
npm run build          # Production build → ../src/assets/dist/
npm run dev            # Watch mode (rebuilds on file changes)

The dist/ output is committed to git (unminified, no source maps) so that cargo build works without Node.js installed. This mirrors the approach used for the committed Tailwind CSS output.

CSS (Tailwind)

Tailwind CSS is built separately from the TypeScript pipeline:

cd crates/web/ui
npm run build:css      # input.css → ../src/assets/css/style.css
npm run watch:css      # Watch mode

The output style.css is committed unminified (one rule per line) so diffs merge cleanly.

Service Worker

The service worker is built from TypeScript via esbuild:

cd crates/web/ui
npm run build:sw       # src/sw.ts → ../src/assets/sw.js

Full Build

cd crates/web/ui
npm run build:all      # Vite + Tailwind + service worker

Technology Stack

LayerTechnology
UI frameworkPreact (lightweight React alternative)
TemplatingJSX with typed Props interfaces
State managementPreact Signals
Build toolVite with @preact/preset-vite
Type checkingTypeScript strict mode (tsc --noEmit)
Linting/formattingBiome
CSSTailwind CSS v4
i18ni18next (en, fr, zh)
ChartsuPlot
Terminalxterm.js
Syntax highlightingShiki (bundled, lazy-loaded)
E2E testingPlaywright

Type Safety

The codebase enforces strict TypeScript with zero tolerance for any:

  • tsc --noEmit runs in CI and local-validate (must pass with 0 errors)
  • 107 typed RPC methods via RpcMethodMap — calling sendRpc("models.list", {}) infers the response type as ModelInfo[]
  • 28 WebSocket events via WsEventName enum with typed payload discriminated unions
  • ChannelType enum for channel type comparisons (no raw strings)
  • targetValue(e) / targetChecked(e) helpers eliminate (e.target as HTMLInputElement).value casts

Shared Component Library

Reusable components in components/forms/:

  • Form fields: TextField, TextAreaField, SelectField, CheckboxField
  • Layout: SectionHeading, SubHeading, SettingsCard, DangerZone
  • Lists: ListItem, Badge, EmptyState, Loading, CopyButton
  • Navigation: TabBar
  • State: useSaveState() hook, SaveButton, StatusMessage

Asset Serving

The Rust moltis-web crate serves assets with three-tier resolution:

  1. Dev filesystemMOLTIS_ASSETS_DIR env var or auto-detected from the crate source tree (cargo run dev mode)
  2. External share dirshare_dir()/web/ for packaged deployments
  3. Embedded fallbackinclude_dir! compiled into the binary

HTML templates are rendered by Askama with server-injected data (window.__MOLTIS__, the “gon” pattern).

E2E Test Compatibility

E2E tests dynamically import individual JS modules (e.g., await import("js/state.js")) to inspect and mock internal app state. With Vite bundling, individual modules don’t exist as standalone files.

Shim layer: small proxy files in src/assets/js/ re-export from window.__moltis_modules (populated by app.tsx at startup). This lets tests import modules at their original paths without changes.

The shims are only loaded by E2E tests, never by the production app.

Development Workflow

After changing TypeScript source files:

cd crates/web/ui

# 1. Type check
npx tsc --noEmit

# 2. Lint and format
biome check --write src/

# 3. Build (commits dist/ output)
npm run build

# 4. Run E2E tests
npx playwright test --project default

For CSS changes, also run npm run build:css and commit style.css.