@@ -64,21 +64,58 @@ cargo build --release # Release build
6464cargo run / cargo run --release
6565```
6666
67- # # Web UI Assets
67+ # # Web UI (TypeScript + Preact + Vite)
6868
69- Assets in `crates/web/src/assets/` (JS, CSS, HTML). Dev mode serves from disk (edit and reload);
70- release mode embeds via `include_dir!` with versioned URLs.
69+ TypeScript/TSX source in `crates/web/ui/src/`, built with Vite to `crates/web/src/assets/dist/`.
70+ CSS and static assets in `crates/web/src/assets/`. Release mode embeds via `include_dir!`.
71+ Both `dist/` and `style.css` are committed (unminified) so `cargo build` works without Node.js
72+ and diffs merge cleanly. See `docs/src/frontend.md` for the full architecture guide.
73+
74+ # ## Build Commands
75+
76+ ```bash
77+ cd crates/web/ui
78+ npm run build # Vite: TS/TSX → dist/ (MUST commit dist/ after)
79+ npm run build:css # Tailwind: input.css → ../src/assets/css/style.css
80+ npm run build:sw # esbuild: src/sw.ts → ../src/assets/sw.js
81+ npm run build:all # All three above
82+ npm run dev # Vite watch mode (rebuilds on save)
83+ npx tsc --noEmit # Type check (strict, must be 0 errors)
84+ ```
85+
86+ **After changing TS/TSX files**, always:
87+ 1. `biome check --write crates/web/ui/src/`
88+ 2. `cd crates/web/ui && npm run build`
89+ 3. `cd crates/web/ui && npx tsc --noEmit`
90+ 4. Commit both the source changes AND the `dist/` output
91+
92+ # ## TypeScript Rules
93+
94+ - **File size limit: 1,500 lines** (same rule as Rust). Split large files into modules by domain.
95+ - Pages: extract sections/modals into `pages/sections/`, `pages/channels/`, `pages/chat/`, etc.
96+ - Utilities: extract sub-modules into sibling directories (`providers/`, `sessions/`, `ws/`).
97+ - Keep shared signals, types, and re-exports in the main file; move logic into sub-modules.
98+ - All UI code is **TypeScript** with **JSX** (Preact). No HTM tagged templates.
99+ - Add typed Props interfaces for all Preact components.
100+ - Use `@preact/signals` with generic type parameters: `signal<string[]>([])`.
101+ - Prefer typed interfaces over `Record<string, unknown>` — define concrete shapes where property access is known.
102+ - Use `targetValue(e)` / `targetChecked(e)` from `typed-events.ts` for form event handlers.
103+ - No `any` types — use `unknown` with type guards or specific interfaces.
104+ - Use shared components from `components/forms/` (TextField, SaveButton, ListItem, Badge, TabBar, etc.).
105+
106+ # ## CSS Rules
71107
72- - **Always** run `biome check --write` when JS files change.
73- - Avoid creating HTML from JS — add hidden elements in `index.html`, toggle visibility. Preact/HTM exceptions allowed.
74108- **Always use Tailwind classes** instead of inline `style="..."`.
75109- Reuse CSS classes from `components.css`: `provider-btn`, `provider-btn-secondary`, `provider-btn-danger`.
76110- Match button heights/text sizes when elements sit together.
77- - **Rebuild Tailwind** after adding new classes and **commit the output**:
78- ```bash
79- cd crates/web/ui && npx tailwindcss -i input.css -o ../src/assets/style.css
80- ```
81- `style.css` is checked in (unminified, one rule per line) so `cargo build` works without Node.js and diffs merge cleanly.
111+ - **Rebuild Tailwind** after adding new classes: `cd crates/web/ui && npm run build:css`.
112+
113+ # ## E2E Test Shims
114+
115+ E2E tests dynamically import individual JS modules (`js/state.js`, `js/helpers.js`, etc.).
116+ With Vite bundling, these don't exist as standalone files. Shim files in `src/assets/js/`
117+ proxy to `window.__moltis_modules` (populated by `app.tsx`). When adding new modules that
118+ tests import, add a shim file and expose the module in `app.tsx`.
82119
83120# ## Selection Cards
84121
@@ -93,12 +130,13 @@ When adding fields, update: `ProviderConfig` struct, `available()` response, `sa
93130# ## Server-Injected Data (gon pattern)
94131
95132For server data needed at page load: add to `GonData` in `server.rs` / `build_gon_data()`.
96- JS side: `import * as gon from "./gon.js"` — use `gon.get()`, `gon.onChange()`, `gon.refresh()`.
133+ TS side: `import * as gon from "./gon"` — use `gon.get()`, `gon.onChange()`, `gon.refresh()`.
134+ Types in `crates/web/ui/src/types/gon.ts` mirror the Rust `GonData` struct.
97135Never inject inline `<script>` tags or build HTML in Rust.
98136
99137# ## Event Bus
100138
101- Server events via WebSocket: `import { onEvent } from "./events.js "`. Returns unsubscribe function.
139+ Server events via WebSocket: `import { onEvent } from "./events"`. Returns unsubscribe function.
102140Do **not** use `window.addEventListener`/`CustomEvent` for server events.
103141
104142# # API Namespace Convention
@@ -166,7 +204,7 @@ just format-check # CI format check
166204just release-preflight # fmt + clippy gates
167205cargo check # Fast compile check
168206taplo fmt # Format TOML files
169- biome check --write # Lint/format JS
207+ biome check --write # Lint/format TS/TSX
170208```
171209
172210# # Sandbox Architecture
@@ -275,9 +313,9 @@ Conventional commits: `feat|fix|docs|style|refactor|test|chore(scope): descripti
275313**Always** run `./scripts/local-validate.sh <PR_NUMBER>` when a PR exists.
276314
277315For incremental local edits before full validation:
278- - JS changed: run `biome check --write`.
316+ - TS/TSX changed: run `biome check --write` and `cd crates/web/ui && npm run build `.
279317- Rust changed: run `cargo +nightly-2025-11-30 fmt --all -- --check`.
280- - JS + Rust changed: run both .
318+ - Both changed: run all three .
281319
282320Exact commands (must match `local-validate.sh`):
283321- Fmt: `cargo +nightly-2025-11-30 fmt --all -- --check`
@@ -298,7 +336,7 @@ with exact commands), `## Manual QA`. Include concrete test steps.
298336**Run before every commit:**
299337- [ ] No secrets or private tokens (CRITICAL)
300338- [ ] `taplo fmt` (TOML changes)
301- - [ ] `biome check --write` (JS changes)
339+ - [ ] `biome check --write` (TS/TSX changes)
302340- [ ] Rust fmt passes (exact command above)
303341- [ ] `just lint` passes (OS-aware clippy)
304342- [ ] `just release-preflight` passes
0 commit comments