Agents.md

This commit is contained in:
2026-09-30 11:42:21 +02:00
parent 6edd03aace
commit e1df4d3ae1

View File

@@ -2,6 +2,7 @@
A collaborative wiki of math concepts. Articles are written in Markdown with LaTeX A collaborative wiki of math concepts. Articles are written in Markdown with LaTeX
(KaTeX) and stored in MongoDB behind an Express API; a Nuxt 4 app renders and edits them. (KaTeX) and stored in MongoDB behind an Express API; a Nuxt 4 app renders and edits them.
The interface itself speaks five languages (en, es, ca, fr, de), chosen per account.
``` ```
mathew/ mathew/
@@ -46,7 +47,9 @@ Plain JavaScript, ESM (`"type": "module"`, `.js` files with relative `.js` impor
`User` (username + `passwordHash`, `select: false`; `setPassword`/`checkPassword` `User` (username + `passwordHash`, `select: false`; `setPassword`/`checkPassword`
via bcrypt, `toPublic()` for responses, and `credentialProblem()` as the single via bcrypt, `toPublic()` for responses, and `credentialProblem()` as the single
place account rules are checked). The profile picture lives on `User.avatar` as a place account rules are checked). The profile picture lives on `User.avatar` as a
small data URL — no file storage. `User.role` is `'admin'` or `'member'` small data URL — no file storage. `User.locale` is the interface language the
person picked (`SUPPORTED_LOCALES`: `en es ca fr de`, default `'en'`).
`User.role` is `'admin'` or `'member'`
(`ROLES`); `User.hasNone()` and `User.ensureAnAdmin()` cover who gets the (`ROLES`); `User.hasNone()` and `User.ensureAnAdmin()` cover who gets the
former — see the roles gotcha below. former — see the roles gotcha below.
- `src/routes/` — one Express `Router` per resource, mounted in `index.js`. - `src/routes/` — one Express `Router` per resource, mounted in `index.js`.
@@ -55,7 +58,8 @@ Plain JavaScript, ESM (`"type": "module"`, `.js` files with relative `.js` impor
`POST /api/auth/login`, `GET /api/auth/me` (who a token belongs to). `POST /api/auth/login`, `GET /api/auth/me` (who a token belongs to).
Login answers the same way to a wrong name and a wrong password. Account updates, Login answers the same way to a wrong name and a wrong password. Account updates,
all behind `requireAuth`: `PUT /api/auth/me` (rename), `PUT /api/auth/password` all behind `requireAuth`: `PUT /api/auth/me` (rename), `PUT /api/auth/password`
(needs the current one), `PUT /api/auth/avatar` (data URL, `""` clears). (needs the current one), `PUT /api/auth/avatar` (data URL, `""` clears),
`PUT /api/auth/locale` (interface language, one of `SUPPORTED_LOCALES`).
- `src/routes/admin.js` — mounted at `/api/admin`, everything behind - `src/routes/admin.js` — mounted at `/api/admin`, everything behind
`requireAuth, requireAdmin`: `GET /api/admin/users` (every account, newest `requireAuth, requireAdmin`: `GET /api/admin/users` (every account, newest
first, with `createdAt`), `PUT /api/admin/users/:id/role` (`role` is `'admin'` first, with `createdAt`), `PUT /api/admin/users/:id/role` (`role` is `'admin'`
@@ -91,6 +95,16 @@ the accent themes generate from a map in `_tokens.scss`. Colours stay CSS custom
properties so themes swap at runtime via `data-theme`/`data-accent` attributes properties so themes swap at runtime via `data-theme`/`data-accent` attributes
(set pre-paint by an inline script in `nuxt.config.ts`). (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 309 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
from the first draw. Every UI string goes through `t()` in the catalogs; messages that
carry `<code>`/`<span>` markup are drawn with `v-html` through `th()`
(`useAppLocale`), which escapes interpolated values. Adding a language = add a catalog
copy + an `APP_LANGUAGES` entry + the code in the backend's `SUPPORTED_LOCALES`.
- `app/pages/` — `/` (search), `/wiki/[slug]` (article + TOC), `/wiki/[slug]/edit`, `/new`, - `app/pages/` — `/` (search), `/wiki/[slug]` (article + TOC), `/wiki/[slug]/edit`, `/new`,
`/admin` (the dashboard: every account and its role, plus the buttons that hand the `/admin` (the dashboard: every account and its role, plus the buttons that hand the
role on or take it back — an admin alone sees anything but a refusal here). role on or take it back — an admin alone sees anything but a refusal here).
@@ -117,9 +131,13 @@ properties so themes swap at runtime via `data-theme`/`data-accent` attributes
profile popup), `useArticleActionsMenu` (same, for an article's ⋮ menu), and profile popup), `useArticleActionsMenu` (same, for an article's ⋮ menu), and
`useAccountActionsMenu` (same, for a dashboard row's ⋮ — it holds the **id** of `useAccountActionsMenu` (same, for a dashboard row's ⋮ — it holds the **id** of
the row that is open, so one row's menu is open at a time rather than a the row that is open, so one row's menu is open at a time rather than a
boolean per row). boolean per row), and `useAppLocale` (the interface language: `APP_LANGUAGES` —
the five supported UI languages with their own names —, `choose(code)` to turn
the UI (`setLocale` also keeps the `mathew-locale` cookie in step), `applyAccount(code)`
to let an account overrule the device, and `th()` for messages drawn as HTML).
- `app/plugins/auth.ts` — runs `restoreSession()` once at startup on server and client, - `app/plugins/auth.ts` — runs `restoreSession()` once at startup on server and client,
so the sidebar's account row and the editor's gate are right in the first render. so the sidebar's account row and the editor's gate are right in the first render,
and applies the account's `locale` when it restores one.
- `app/utils/markdown.ts` — the single render pipeline: `markdown-it` (**html: false**, - `app/utils/markdown.ts` — the single render pipeline: `markdown-it` (**html: false**,
raw HTML is escaped) + `markdown-it-texmath` + KaTeX (`$...$` inline, `$$...$$` display, raw HTML is escaped) + `markdown-it-texmath` + KaTeX (`$...$` inline, `$$...$$` display,
macros `\R \N \Z \Q \C`) + highlight.js, sanitized with DOMPurify. It also builds the macros `\R \N \Z \Q \C`) + highlight.js, sanitized with DOMPurify. It also builds the
@@ -128,6 +146,18 @@ properties so themes swap at runtime via `data-theme`/`data-accent` attributes
## Gotchas ## Gotchas
- The **interface language** is not the article language: `/wiki/fr/…` addresses the
French *article*, never the UI. UI precedence: the signed-in account's `locale` (applied
at startup and on signing in) > the `mathew-locale` cookie (written by `setLocale`, read
on the server pre-render) > browser > `en`. The settings popup always turns this device;
with a session it also saves to the account — an account that refuses (dead session)
doesn't undo the switch. Error messages that come back from the API stay English; only
the frontend's own fallbacks are translated.
- Never call `t()` in `withDefaults` static prop defaults — they don't react to locale
changes. Use `props.x ?? t(...)` in the template/computed instead (see `AuthGate`,
`ConfirmModal`).
- Message catalogs are compiled: literal `{`/`@` break the compiler, so the editor's LaTeX
seed lives as a JS constant in `ArticleForm.vue` and only its words are catalog keys.
- Reading is open; **writing takes an account**. `POST /api/articles` and `PUT`/`DELETE` - Reading is open; **writing takes an account**. `POST /api/articles` and `PUT`/`DELETE`
by slug need `Authorization: Bearer <token>` from `/api/auth/login` or by slug need `Authorization: Bearer <token>` from `/api/auth/login` or
`/api/auth/register`. The frontend keeps that token in the `mathew-session` cookie `/api/auth/register`. The frontend keeps that token in the `mathew-session` cookie