Skip to content

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 delivery Component for NodeViews, so nothing from ADR-0002 is discarded.
  • 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.
  • 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.