Files
edentext/CLAUDE.md
T
Steffen Becker c4239a6e72 test: the layout run holds a baseline, and the monkey edits frames and columns
tests/layout/baseline.json records where each page starts and what the load cost, per
engine and platform; a page starting elsewhere is a layout change the run prints page by
page, a load over three times its recorded time a regression, and LAYOUT_UPDATE=1 records
it again. The monkey draws frame sizes, wraps, alignment and column counts too, holds the
history against the edits rather than the caches the editor recomputes, and leaves it
unchecked where the columns flow rewrote the document outside it. The differences both
importers make by design are named in the comparison; unhoist moved to normalize.ts.
2026-09-06 19:07:17 +02:00

8.9 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 under invariants: schema, undo/redo, the saved file reads back (tests/monkey/)
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 four browser runs (tests/smoke/, tests/dom/, tests/layout/, tests/monkey/) 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 clear description, never an essay: a subject line plus at most ~8 lines, however large the change. Probed behaviour, measurements and rationale lists belong in docs/architecture/ or the nearest CLAUDE.md, not repeated in the history.

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.

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 Word/LibreOffice behaviour, sentinel order, constants that must agree across files. Keep this file under 120 lines: a section growing past ~5 lines belongs somewhere else.

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