Domotion

Contributing

Architecture, test suites, and how to add a new CSS feature.

This page is for people landing on the Domotion repo who want to fix a bug, add a CSS feature, or otherwise touch the source. Users of the library should start at Quick start instead.

Architecture at 30,000 ft

Domotion has three logical stages, all in src/:

StageFilesWhat happens
Capture dom-to-svg.ts (CAPTURE_SCRIPT), capture.ts Playwright loads the page; an in-page script walks the DOM, recording position, computed styles, text, and per-glyph offsets into a serialisable CapturedElement tree.
Render dom-to-svg.ts (server side), text-renderer.ts, text-to-path.ts, form-controls.ts, gradients.ts, chrome.ts Pure functions take a tree and emit SVG markup — <rect> for backgrounds, <path> for glyph outlines via fontkit, <linearGradient> for CSS gradients, etc.
Animate animator.ts, frame-merge.ts Multiple captured frames get composed into one SVG with CSS keyframe transitions. The merge pipeline de-duplicates shared elements across frames and emits per-element visibility timelines for the all-crossfade fast path.

The CLI (src/cli/) is a thin shell over the public API (src/index.ts). The user manual under site/ is a separate static-site generator that imports types and helpers from the library — it's a good source of "how is this actually used" examples.

The CAPTURE_SCRIPT discipline

The DOM-walking code lives as a string literal at the top of src/dom-to-svg.ts and is page.evaluate'd into the captured page. Two consequences:

  • No import statements, no closures over module-scope vars. The script only sees the args passed to evaluate and globals available in the page.
  • Use var / function declarations or arrow functions that don't reference outer scope.

If you find yourself wanting to call a helper from CAPTURE_SCRIPT, either inline it (small) or capture the data needed and call the helper after the script returns (large). Anything reusable across capture and render can live as plain TypeScript helpers in src/ and be re-imported by the renderer side — just don't try to call them from inside CAPTURE_SCRIPT.

This is also why CAPTURE_SCRIPT is tested via the test runner (capturing real fixtures) rather than unit tests: the script only behaves correctly when the surrounding evaluate() injects its arguments.

Running the test suites

CommandWhat it runs
npm testVitest unit tests under src/**/*.test.ts — fast, no browser.
npm run demos:testThe feature visual-regression suite (tests/features.ts). One fixture per CSS feature; each is rendered both via Chromium and via Domotion's pipeline and the PNG diff thresholded at 3%.
npm run demos:test:htmlThe broad-coverage sweep against ~/Documents/html-test/*.html. Some failures are pre-existing and tracked separately; new code shouldn't introduce regressions here.
npm run demos:test:allfeatures + showcase + html-test-suite.
npm run demos:reviewLocal web UI to compare expected / actual / diff PNGs from each suite by hand. Most useful when adding or updating fixtures.

Adding a new CSS feature

  1. Capture the new property in CAPTURE_SCRIPT — add it to the styles object that gets serialised onto each CapturedElement.
  2. Plumb it through the CapturedElement type (src/dom-to-svg.ts, near the top).
  3. Render it in the appropriate place — usually renderElement in src/dom-to-svg.ts for box properties, or src/text-renderer.ts for text-related ones.
  4. Add a fixture to tests/features.ts: a minimal HTML snippet that exercises the feature and a single matching expected behavior. Run npm run demos:test to record the expected PNG.
  5. Update the support matrix in site/pages/css/<area>.tsx (the per-area page) and the top-level FEATURES.md checklist.
  6. Document any new fallback behavior in docs/ — these are the contract with consumers about what round-trips and what doesn't.

If the feature has no faithful SVG representation (e.g. complex CSS filters), it goes through the raster-fallback path: capture page.screenshot() for the affected region and emit it as an embedded <image>. Add a capture warning so users can find it.

Where the docs live

LocationAudience
docs/Implementation requirements (rendering fidelity, supported CSS, known caveats). The internal contract.
site/pages/Public user manual. CLI- and use-case-focused.
FEATURES.mdPer-feature support checklist with links to fixtures. Keep in sync when fixtures land.
CLAUDE.mdGuidance for AI assistants working in the repo.

Quality gates

  • npm run build must pass (TypeScript strict). No any outside the fontkit / bidi-js boundary types.
  • npm test must pass.
  • npm run demos:test is the primary regression signal — every feature has a fixture; new features need fixtures.

Reading list before your first PR

  1. The capture pipeline — visual overview of the same architecture this page lists.
  2. The element tree — what flows from capture to render.
  3. Text rendering — the most subtle subsystem; understanding subpixel positioning and run-based shaping saves hours later.
  4. The top of CLAUDE.md — house-style invariants that aren't enforceable by the type-checker.