Ai slop
Some checks failed
Build and Deploy Nuxt / build (push) Failing after 41s

This commit is contained in:
2026-10-03 14:13:08 +02:00
parent 68b74eb60b
commit 6df7950161
45 changed files with 4594 additions and 125 deletions

View File

@@ -6,9 +6,10 @@ The interface itself speaks five languages (en, es, ca, fr, de), chosen per acco
```
mathew/
├── dev.sh # starts/stops both dev servers together
├── dev.sh # starts/stops all three services together (lean via docker compose)
├── backend/ Express 5 + Mongoose API (port 5000)
└── frontend/ Nuxt 4 / Vue 3 wiki UI (port 3000)
├── frontend/ Nuxt 4 / Vue 3 wiki UI (port 3000)
└── lean/ lean4web editor server — its own Docker image (port 8080)
```
## Running the project
@@ -16,13 +17,16 @@ mathew/
Requires MongoDB on `localhost:27017` (database `mathew`).
```bash
./dev.sh # both dev servers in foreground (Ctrl+C stops both)
./dev.sh # all three services in foreground (Ctrl+C stops all)
./dev.sh start|stop|restart|status
./dev.sh logs [all|backend|frontend]
./dev.sh logs [all|backend|frontend|lean]
```
Or per-service: `npm run dev` inside `backend/` or `frontend/`
(backend uses `node --watch`; backend has no build step).
(backend uses `node --watch`; backend has no build step). The Lean editor
(`/lean`) is the third service: `dev.sh` brings it up through docker compose
alongside the npm pair — the first build fetches Mathlib and takes a while,
and when docker is unavailable dev.sh says so and still starts the other two.
Seed sample articles: `npm run seed` in `backend/`.
@@ -36,7 +40,10 @@ Production (`docker-compose.yml`) points the backend at the LAN MongoDB server
with the dev key). The stack's nginx resolves `backend`/`frontend` through Docker's DNS per
request, so container recreates cannot leave it on a stale IP.
Frontend API URL comes from `NUXT_PUBLIC_API_BASE`
(default `http://localhost:5000`, read via `runtimeConfig.public.apiBase`). The frontend image
(default `http://localhost:5000`, read via `runtimeConfig.public.apiBase`). The Lean
editor's address comes from `NUXT_PUBLIC_LEAN_URL` (default `http://localhost:8080`,
read via `runtimeConfig.public.leanUrl`) — on an HTTPS site it must be an https://
address too, or the browser blocks it (and the `wss://` derived from it) as mixed content (see `lean/README.md`). The frontend image
builds it empty: the browser then calls `/api` on its own origin, which nginx routes to
`backend:5000`, while the Nuxt server's own (SSR) `/api` calls go through a `routeRules` proxy
aimed at `NUXT_API_PROXY_TARGET` (`https://mathew.aranroig.com` baked in at image build — the
@@ -45,6 +52,40 @@ by service name).
There is **no test suite, linter, or typecheck script** configured — don't invent commands.
## Lean editor (`lean/`)
`/lean` is now a **native editor**, not an iframe. The frontend speaks to the lean4web
server (`lean/`) directly over its websocket (`ws(s)://<leanUrl>/websocket/<project>`),
carrying the Lean 4 **language server protocol** (LSP): the client sends `initialize`,
`didOpen`/`didChange`, `$/lean/plainGoal`; the server streams back `publishDiagnostics`
and goal answers. That replaces the old frame entirely — the wiki owns the editor (Monaco),
its theme (live, same page), and the Goals/Messages pane.
`lean/Dockerfile` still builds lean4web (upstream's real Lean + Mathlib per session;
`--build-arg LEAN4WEB_REF=<tag|branch>` pins it) and runs its websocket server on port 8080;
the compose `lean` service runs it. Upstream asks for a dedicated machine, and the image is
heavy (Mathlib). It runs `NO_BWRAP=true` — bubblewrap cannot nest in a container, so Docker
itself is the sandbox; whoever reaches the port can run Lean. Build/run, TLS, security and
projects: `lean/README.md`.
- `frontend/app/utils/lean/lsp.ts` — a minimal LSP JSON-RPC client over a websocket
(request/response pairing, notification dispatch, and auto-ack of the server→client
requests a single-document client has no use for). It negotiates `utf-16` positions,
matching Monaco's column model.
- `frontend/app/utils/lean/lean-language.ts` — registers the `lean4` language with a
Monarch tokenizer and Monaco themes rebuilt from the wiki's live tokens: light/dark surfaces
plus the chosen `--accent`, so the caret, selection, active line number and keywords follow
the accent (`applyLeanTheme()` re-reads the tokens and switches on a live change; themes are
named by their palette, so a redefinition never races one Monaco has already cached).
- `frontend/app/composables/useLeanSession.ts` — the session: connect to a project, open
`Main.lean`, send debounced whole-document `didChange`s, track the cursor for `$/lean/plainGoal`,
split diagnostics into editor marks vs. Messages (severity 3 = Lean's `#check`-style info),
read elaboration progress. Lives at module scope so the running elaboration outlives the
book moving the page between hosts.
- `frontend/app/components/LeanWorkbench.vue` — Monaco + the Goals/Messages sidebar; loads
Monaco (and the worker) client-side only. Provides completions, hover, `\`→unicode input,
and the "open the classic lean4web client" escape link.
## Backend (`backend/`)
Plain JavaScript, ESM (`"type": "module"`, `.js` files with relative `.js` import specifiers).
@@ -156,7 +197,7 @@ properties so themes swap at runtime via `data-theme`/`data-accent` attributes
(set pre-paint by an inline script in `nuxt.config.ts`).
UI wording is localized with `@nuxtjs/i18n` (`frontend/i18n/`): `i18n.config.ts` plus
one JSON catalog per language in `i18n/locales/` (all 350 keys, kept in parity with
one JSON catalog per language in `i18n/locales/` (all 405 keys, kept in parity with
`en.json`). `strategy: 'no_prefix'` — the interface language never appears in a URL
(`/wiki/<lang>/<slug>` is the *article's* language) — with cookie detection
(`mathew-locale`, `fallbackLocale: 'en'`) so the server renders in the right language
@@ -200,6 +241,26 @@ copy + an `APP_LANGUAGES` entry + the code in the backend's `SUPPORTED_LOCALES`.
*calc*, *plot*, *map*) and opening one lays the live surface on a page of the book
beside the articles — `ToolPane.vue` is that page (spine, ×, `inert` when not in
view), holding the surface extracted as `GraphStage.vue` / `CalculatorStage.vue`.
The 3D plotter is a third page of its own, `/calculator3d` — sidebar link
and all — drawn by `Calculator3DStage.vue` over `Scene3D.vue`: surfaces
z = f(x, y), a sampled mesh projected and shaded by hand onto one canvas,
quads laid farthest-first, no 3D library (`compileSurfaceExpression` in
`utils/plot-expression.ts` is its algebra). The two calculators share the
dock's shapes but never rows — each keeps its own, in localStorage.
A name in a field that is none of x, y, a constant or a called function
is a slider: a lone `k` or a `k = 2` claims one (plots nothing), and every
expression speaking the name answers to its value — the compile pass hands
each result its `vars`, and `useVarSliders` (values, each slider anchored
under the row that claims it, persistence) and `VarSliders.vue` are shared
by both calculators.
The Lean editor is a fourth page, `/lean`: `LeanWorkbench.vue` *is* the editor — Monaco
(loaded client-side; the `lean4` language and two wiki-matched themes live in
`utils/lean/lean-language.ts`) beside a native infoview of Goals, Messages and a status
line. `useLeanSession` speaks the Lean language protocol to the lean4web server (`lean/`,
address `runtimeConfig.public.leanUrl`) over one websocket, and lives at module scope so a
running elaboration survives the book remounting the page. A dead server answers with the
unreachable banner and a retry; a status-bar link opens the classic lean4web client in its
own tab.
`useArticleTabs` files such a page under the identity `page:<name>` and finds it
at its own address (`tabAt`/`tabHref` know `toolFromPath`); while its page is
open, the plain route hosts defer to the book and draw nothing, and asked plain
@@ -349,5 +410,20 @@ copy + an `APP_LANGUAGES` entry + the code in the backend's `SUPPORTED_LOCALES`.
the folder tallies come from `/articles/facets` rather than from the cards on
screen — counting the loaded batch would let "40 topics" and the tallies
grow as the reader scrolls.
- **`/lean` speaks to a server outside the stack.** The wiki does not use an iframe: the
frontend speaks Lean itself (see the **Lean editor** section) — the browser opens
`<leanUrl>/websocket/<project>` (`NUXT_PUBLIC_LEAN_URL`, default `http://localhost:8080`
— an address for the browser, not a server-side proxy target to fix up). The Lean server
is strict about the handshake: `rootUri` must be a *string* (the server's URI rewriter
throws on a null and the message vanishes), and capabilities like `showDocument.support`
and `insertTextFormat.valueSet` must be shaped objects — the Lean watchdog quits the
session otherwise. The Messages pane reads severity-3 diagnostics (that is where Lean
puts `#check` output; there is no `$/lean/plainMessages` anymore). Dev runs the server
with `./dev.sh` (via docker compose; or lean4web natively: `NO_BWRAP=true PORT=8080 npm run prod`).
On an HTTPS site the address must be https (its `wss://` is derived from it — an `http://`
Lean server refuses an https wiki outright).
A tool page like this is wired in step: `ToolPage`/`TOOL_PAGE_PATHS`
(`utils/routes.ts`), the page file, `TOOL_LABEL_KEYS`, the `ToolPane` branch and spine
glyph, the sidebar link, and the `PAGE_TOOLS` entry.
- Frontend dev server may fall back to port 3001 if 3000 is taken.
- `frontend/types/markdown-it-texmath.d.ts` provides types for the untyped texmath package.