Skip to content

Commit 43badd3

Browse files
authored
Begin TypeScript web UI migration (#775)
1 parent 5d7052f commit 43badd3

665 files changed

Lines changed: 127831 additions & 39863 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.config/nextest.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ failure-output = "immediate-final"
44
# No retries locally by default.
55
retries = 0
66

7+
# QMD tests spawn shell scripts that time out under heavy parallelism.
8+
# Run them serially to avoid flaky failures.
9+
[test-groups.qmd-serial]
10+
max-threads = 1
11+
12+
[[profile.default.overrides]]
13+
filter = "package(moltis-qmd)"
14+
test-group = "qmd-serial"
15+
716
[profile.ci]
817
# In CI, retry flaky tests once before failing the run.
918
failure-output = "immediate-final"

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ jobs:
104104
- uses: biomejs/setup-biome@29711cbb52afee00eb13aeb30636592f9edc0088 # v2.7.0
105105
with:
106106
version: "2.4.6"
107-
- run: biome ci crates/web/src/assets/js/
107+
- run: biome ci crates/web/ui/src/ crates/web/ui/e2e/
108108
- run: ./scripts/i18n-check.sh
109109

110110
fmt:

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
with:
8484
version: "2.4.6"
8585
- if: ${{ env.RELEASE_DRY_RUN != 'true' }}
86-
run: biome ci crates/web/src/assets/js/
86+
run: biome ci crates/web/ui/src/ crates/web/ui/e2e/
8787
- if: ${{ env.RELEASE_DRY_RUN != 'true' }}
8888
run: ./scripts/i18n-check.sh
8989

.npmrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# .npmrc
2+
min-release-age=7
3+
save-exact=true

CLAUDE.md

Lines changed: 54 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,58 @@ cargo build --release # Release build
6464
cargo 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

95132
For 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.
97135
Never 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.
102140
Do **not** use `window.addEventListener`/`CustomEvent` for server events.
103141

104142
## API Namespace Convention
@@ -166,7 +204,7 @@ just format-check # CI format check
166204
just release-preflight # fmt + clippy gates
167205
cargo check # Fast compile check
168206
taplo 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

277315
For 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

282320
Exact 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

biome.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,19 @@
66
"useIgnoreFile": true
77
},
88
"files": {
9-
"includes": ["crates/web/src/assets/js/**/*.js", "crates/web/ui/**/*.js"]
9+
"includes": ["crates/web/ui/src/**/*.ts", "crates/web/ui/src/**/*.tsx", "crates/web/ui/e2e/**/*.js"]
1010
},
1111
"linter": {
1212
"enabled": true,
1313
"rules": {
1414
"recommended": true,
1515
"a11y": {
16-
"recommended": true
16+
"recommended": true,
17+
"noAutofocus": "warn",
18+
"noLabelWithoutControl": "warn",
19+
"noStaticElementInteractions": "warn",
20+
"useButtonType": "warn",
21+
"useKeyWithClickEvents": "warn"
1722
},
1823
"complexity": {
1924
"recommended": true,

crates/tools/src/task_list.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ impl AgentTool for TaskListTool {
544544
}
545545

546546
#[cfg(test)]
547+
#[allow(clippy::unwrap_used, clippy::expect_used)]
547548
mod tests {
548549
use super::*;
549550

0 commit comments

Comments
 (0)