WYSIWYG item authoring on TipTap/ProseMirror
Status: accepted
Item authoring is a WYSIWYG body editor built on TipTap (ProseMirror) via
@mantine/tiptap, living in the emergent skin (never the MIT core). The editor
renders the item body; interactions are atomic editor nodes whose NodeView shows the
delivery skin Component, and whose configuration is edited through that kind’s
AuthoringForm (the skin descriptor’s form) in a side panel.
- The platform is language-first, so even simple prompts need ruby (furigana), emphasis, inline math, and audio refs — plain-text or shorthand authoring can’t express that comfortably, and we chose to build the real surface rather than a stopgap.
- TipTap’s explicit ProseMirror schema lets us constrain the editor to exactly the supported content model, NodeViews embed React interaction components, and Mantine ships an integration — the best fit of the React editors evaluated.
- It composes with the locked core↔skin contract: the editor hosts the per-kind
AuthoringForms and reuses the deliveryComponentfor NodeViews, so nothing from ADR-0002 is discarded.
Considered and rejected
Section titled “Considered and rejected”- Plain-text templated forms. Cheapest, but cannot author the furigana/math the product centres on.
- Shorthand markup → parsed tree. Dependency-light and covers language content, but a poorer authoring experience and a parser to maintain; rejected in favour of building the real editor now.
- Slate / Lexical. Slate’s doc model could mirror the canonical tree (thinner mapping) but is less mature with no Mantine integration; Lexical is fast but weak at schema-constrained docs with no Mantine integration.
Consequences / guardrails
Section titled “Consequences / guardrails”- Lock-in: ProseMirror is a hard-to-reverse dependency; its document model is not the canonical QTI body tree, so a lossless PM-JSON ↔ canonical-tree mapping (emergent-side, round-trip tested) is required.
- Single content-model source of truth: one definition of the allowed body vocabulary lives in conform-ed (as data); the renderer allowlist walk, the editor schema, and the later-tightened zod contracts all derive from it.
- Anti-scope-trap: the editor schema covers only the v0 content model and grows incrementally with the renderer — not all of HTML5 at once.