Files
Steffen Becker 280726f730 docs: run only the test legs a change can break
A new rule in CLAUDE.md: weigh which legs the change touches, run each once
when its input is final, no up-front baseline round. Reclaimed the four lines
it costs by tightening the commit-message and documenting-a-change rules.

Claude-Session: https://claude.ai/code/session_01Wzs2qG6Y5qm7mKmJHfu44G
2026-09-11 20:26:32 +02:00

121 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CLAUDE.md
Serverless, fully client-side rich-text editor that saves `.odt` (and `.docx`). No backend; all
state lives in the browser (localStorage).
**Stack:** Svelte 5 (runes) + TypeScript + Vite. Editor engine TipTap 3 (ProseMirror). ODF export
via `odf-kit` + `fflate` for re-zipping during post-processing.
## Commands
```bash
npm run dev # dev server (Vite, hot-reload, --host)
npm run build # production build → dist/
npm run preview # serve the dist/ build locally
npm run check # svelte-check type-check (svelte + ts)
npm test # Vitest suite once (tests/**/*.test.ts)
npm run test:lo # LibreOffice legs: round trip, fuzz re-read, ODT/DOCX render consistency (needs `soffice`)
npm run test:smoke # boots the dist/ build headless (tests/smoke/run.mjs); BROWSER=firefox|webkit
npm run test:dom # pagination + editing in the real browser (tests/dom/run.mjs); same BROWSER
npm run test:layout # page counts vs LibreOffice, layout invariants, page starts vs baseline.json (tests/layout/)
npm run test:monkey # random editing, body + header/footer, under invariants: schema, undo/redo, the saved file reads back
npm run test:tabs # two tabs, two documents: each keeps its own across reloads (tests/tabs/)
npm run test:coverage # vitest + v8 coverage over src/ → coverage/index.html
npm run test:parity # render parity vs LibreOffice (tests/render-parity/README.md)
node scripts/make-thesaurus.mjs; node scripts/collect-licenses.mjs # re-vendor public/thesaurus/ (MyThes); regenerate public/licenses.txt
node scripts/showcase/run.mjs # rebuild docs/showcase/ (sample .odt/.docx + README screenshots); [regex] limits it
```
Tests live in `tests/` (outside `src/`, so `svelte-check` ignores them), jsdom via Vitest.
`roundtrip.test.ts` covers the ODF export↔import round trip + a foreign-doc/style-resolver leg; `lo-roundtrip.test.ts` re-saves through LibreOffice and `lo-fuzz.test.ts` reads the fuzz seeds back through it (`LO_SEEDS`, `LO_DUMP=<file>` for triage) — both **self-skip** without `soffice`, so `npm test`/CI stay green;
`corpus.test.ts` round-trips the committed `tests/corpus/` documents (by `make-fixtures.mjs` + Word re-saves in `corpus/word/`, never our exporter); `fuzz-roundtrip.test.ts` round-trips seeded random documents under random export options (`fuzzDoc.ts`, `fuzzOptions.ts`) through both formats and validates every export against the schemas (`schemaValidate.ts`; `FUZZ_SEEDS=500` widens it);
`schema-validation.test.ts` validates the `kitchenSink.ts` exports against the vendored schemas in `tests/schemas/` (self-skips without `xmllint`); `package-lint.test.ts` checks the semantic invariants schemas can't express (unique ids, dangling references); `tests/unit/` holds fast helper tests; the five browser runs (`tests/smoke/`, `tests/dom/`, `tests/layout/`, `tests/monkey/`, `tests/tabs/`) share `tests/browser.mjs`. All test tooling stays a `devDependency`. No linter/formatter. CI (`.github/workflows/ci.yml`) runs `check` + `test`.
## Rules
**Comments** — keep them precise and short:
- **Never longer than three lines.** No exceptions — file-header comments, tests and config included. A comment that needs a fourth line is explaining what the code already says; cut it back to what the code can't say.
- **Never describe how the current code differs from an older version** (no "previously…", "this used to…", "changed from…"). Comment only what the current code does and why — git history covers the rest.
- **Don't use Word as a placeholder for "a word processor".** Where LibreOffice does the same thing, describe the behaviour itself ("the caret moves", "the zone auto-grows") instead of "Word-style" / "like Word" / "as in Word". Name a product only where the statement really is about that product: its file format (`w:tblLook`, DOCX), or a quirk only it has — then name both if both apply.
**Commit messages** — a subject line plus **at most ~8 lines**, however large the change. Probed
behaviour, measurements and rationale belong in `docs/architecture/` or the nearest `CLAUDE.md`.
**IMPORTANT: no real-world document's name anywhere in the repo, and a commit message names no
document at all** — not even "the thesis": describe the fix and its measurement, never the file it
helped. Detail in `tests/render-parity/README.md`.
**IMPORTANT: never introduce a default only this editor has.** Both importers suppress values equal
to the defaults, so an editor-only default is indistinguishable from a failed style resolution and
silently lands in every imported document as direct formatting. The defaults follow **LibreOffice**
(we save `.odt`, so LO wins the ties against Word's Calibri 11pt / 8pt after / 2.54cm):
- Paragraph spacing **0** — no `margin-top`/`-bottom` on `p`, `ul`/`ol` or the table wrapper. Blank lines come from the document's own empty paragraphs.
- Body **Liberation Serif 12pt**, single line spacing; page margins **2cm**; tab/indent step **1.25cm**.
- Headings sans (`HEADING_FONT` = Arial, bundled `@font-face` maps it to metric-identical Liberation Sans), sizes/margins in `HEADING_STYLE_OVERRIDES` (`styles/headings.ts` — see `src/lib/export/CLAUDE.md`).
- Liberation Serif TTFs are bundled (`src/assets/fonts/`) and metric-identical to Times New Roman, so editor, LibreOffice and Word share the same metrics. `utils/fontDetect.ts` filters `CANDIDATE_FONTS` to what's installed for the font picker.
**IMPORTANT: keep the layout constants in sync** between `editor/extensions/pageBreaks.ts`,
`components/Editor.svelte` and `styles/editor.css` (`PAGE_HEIGHT` 1123px, `PAGE_GAP` 20px, the
`--user-page-*`/`--user-margin-*` custom properties) — details in `docs/architecture/pagination.md`.
**Headless browser testing** — `playwright-core` with its own engines, never `puppeteer` (it
fetches an x86-64 Chrome that cannot run on arm64). Recipe and the three-engine CI matrix in
`docs/headless-testing.md`; driving the live app is the only way to verify rendering or NodeViews.
**Running tests** — weigh which legs the change can actually break and run only those: `npm test`
(or the one file) for logic, the LO legs for export/import, the browser runs for rendering,
`test:parity` for layout. Each leg once, at the point its input is final — no up-front baseline
round, no re-run of a leg that is still green.
**Naming** — components `PascalCase.svelte`, every `.ts` module `camelCase`; extension files are
named by feature (`image.ts`, `indent.ts`), not `XyzExtension.ts`.
**Documenting a change** — a feature that changes how this codebase behaves gets documented where
it loads on demand, not here. **This file** only grows for a new command, a new hard rule, or a new
top-level directory; everything else is one line in the nearest directory `CLAUDE.md`, or a section
in its `docs/architecture/` file. Write only what the code can't say — probed behaviour, sentinel
order, cross-file constants. Keep this file under 120 lines: a section past ~5 lines moves out.
## Source layout
```
src/
App.svelte – app shell + app-level state
lib/
components/ – all Svelte UI (toolbars, pickers, dialogs, Editor, HeaderFooterLayer)
ribbon/ – the Word-style ribbon chrome (shell, controls, one file per tab)
editor/
extensions.ts – the TipTap extension registry
extensions/ – the custom TipTap/ProseMirror extensions, one file per feature
utils/ – framework-free helpers (fontDetect, specialChars, wordCount, orderedListTypes, historyLog, colorDebug)
math/ – formula AST: LaTeX parser/serializer, MathML, OMML
export/ import/ spell/ storage/ styles/ crypto/ – ODF+DOCX I/O, spell-check, persistence, named styles, password protection
templates/ – built-in document templates: registry + one localized module each
styles/ – global.css + editor.css
```
## Where the detail lives
Directory-level `CLAUDE.md` files load automatically when you touch that directory; the
`docs/` files are read on demand.
| Topic | File |
|---|---|
| Components, data flow, zoom, header/footer layer, debug dump | `src/lib/components/CLAUDE.md` |
| Extension registry index, shortcuts, context menu | `src/lib/editor/CLAUDE.md`, `src/lib/editor/extensions/CLAUDE.md` |
| ODF/DOCX export, post-processing passes, sentinels | `src/lib/export/CLAUDE.md` |
| ODF/DOCX import, style resolver, image formats | `src/lib/import/CLAUDE.md` |
| Paragraph + character styles, style manager | `src/lib/styles/CLAUDE.md` |
| localStorage keys, page margins/orientation, themes | `src/lib/storage/CLAUDE.md` |
| The ribbon chrome, its palette and the mode switch | `docs/architecture/ribbon.md` |
| Pagination, widow/orphan, columns flow | `docs/architecture/pagination.md` |
| Images, text boxes, wrap modes | `docs/architecture/frames.md` |
| Table extensions + table styles | `docs/architecture/tables.md` |
| Text effects, indents, tab stops, list markers, date fields | `docs/architecture/formatting.md` |
| Formulas (LaTeX model, MathML, OMML, the math font) | `docs/architecture/formulas.md` |
| Footnotes and endnotes (model, page-foot reservation) | `docs/architecture/notes.md` |
| Password protection of the saved file | `docs/architecture/encryption.md` |
| Headless browser + PDF-export debugging | `docs/headless-testing.md` |