Files
edentext/CLAUDE.md
T
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

9.2 KiB
Raw Blame History

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

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