# Working on the web pages This is part of the root `AGENTS.md`, loaded when work reaches `web/`. - The web pages are the SvelteKit app in `web/`; knotless-serve answers what they show as XRPC queries, each with its lexicon under `lexicons/` and the views it shares in `dev.knotless.defs`, as atproto's appviews do. The pages' TypeScript types are made from those lexicons by @atproto/lex (`npm run lex` in `web/`, which `dev`, `build` and `check` run first), into `web/src/lib/lexicons`, not kept in git. `just lex` checks the answers the screenshots recorded against their lexicons, and the `test` check every answer the serve tests receive. - Before changing a page, load the `ui` skill (`.claude/skills/ui`): its house rules (sentence case, empty states, space), and `just look`, which draws any page in any state from the recording as pictures to open: empty, signed in, a menu open, a long title. Look at every state you touch before pushing. - For the web pages: the pull's `shots` check shows what moved, with pictures. No screenshot is kept anywhere: the pages are shot as main draws them and as the change does, in one run. To look before pushing, `just shots`, which builds `web/` and main's, shoots only the pages whose HTML differs, drawn from `harness/shots/fixtures.json` with no PDS, and leaves each that moved under `target/shots/results/` (its expected, actual and diff). After a change to what a page's answer holds, `just shots-up` in the background once, then `just shots-record`. Each checkout's shots have three ports of their own, from its path; `SHOTS_PORT` sets another base for all of these. - `shots-live` shoots this checkout's pages from the recording and again through knotless-serve over a world: one that moved means the recording is out of date. Keep `just shots-up` running in the background while working on pages: `shots-live` then reuses its world rather than seeding one, which is most of its time. - The social cards are drawn by knotless-serve: `just shots-cards` takes them from a world into `target/shots/cards` to look at. - The components in `web/src/lib` are layered, atomic design's way: `atoms` (one element: an icon, an avatar), `molecules` (a few atoms that mean something together: a person, a commit row), `organisms` (a part of a page: a check list, a feed), `templates` (a page's layout, without its data). A component uses its own layer and those below it, never one above; `just lint` checks it. Pages under `web/src/routes` fill a template with what the queries answer. - The pages' styles are `web/src/app.css`, and every colour, size, space, radius and weight in it is a token from `web/src/tokens.css`: take one by name rather than writing a value, and add one there when none fits. A colour for what it means is its role, `--danger`, `--success`, `--attention`, `--done` or `--neutral`, never the hue under it (`--red`), so a theme can draw success in blue. Every colour that is text meets WCAG AA (4.5:1) on what it is drawn on, in every theme: `harness/contrast.mjs` checks it, in `web` and `just lint`, and a new colour that is text goes in `web/src/lib/contrast.json`, which the theme editor, and knotless's own check of a theme made anywhere (`crates/knotless/src/theme.rs`), hold people's own themes to too. A menu that opens is a `
`. - In Svelte markup, keep a run of inline text one expression or one line: a block or component beside it splits the text into nodes, which shapes differently from the same text whole and moves pixels.