The minifier strips every legal comment, so the bundle reached users without the notice MIT and Apache-2.0 require to travel with a copy. collect-licenses quotes all 97 bundled packages into public/licenses.txt, which deploys like the font and dictionary licenses do. The About dialog's version now carries the short commit, so a user of the hosted app can identify the source it was built from - what AGPL 13 asks for and a fixed 0.1.0 could not answer.
7.8 KiB
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:watch # Vitest in watch mode
npm run test:lo # LibreOffice round-trip leg only (needs `soffice` on PATH)
npm run test:smoke # boots the dist/ build in headless Chromium (tests/smoke/run.mjs)
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 # re-vendor public/thesaurus/ from LibreOffice's MyThes data
node scripts/collect-licenses.mjs # regenerate public/licenses.txt after a dependency change
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 self-skips without soffice, so
npm test/CI stay green; corpus.test.ts round-trips the committed tests/corpus/
documents (authored by render-parity/make-fixtures.mjs, never by our own exporter);
fuzz-roundtrip.test.ts round-trips seeded random documents (fuzzDoc.ts generator);
tests/unit/ holds fast helper tests. 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/-bottomonp,ul/olor 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-facemaps it to metric-identical Liberation Sans), sizes/margins inHEADING_STYLE_OVERRIDES(export/odt.ts— seesrc/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.tsfiltersCANDIDATE_FONTSto 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 — this container is linux arm64. Never puppeteer /
@puppeteer/browsers: they fetch an x86-64 Chrome that cannot run here. Use playwright-core +
its Chromium; full recipe in docs/headless-testing.md. Driving the live app is the only way to
verify rendering, layout or NodeView behaviour — there is no DOM test suite for it.
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/ – ODF+DOCX I/O, spell-check, persistence, named styles
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 |
| Headless browser + PDF-export debugging | docs/headless-testing.md |