Skip to main content
Accepted by the owner on 2026-10-28 (design-system session c6, STA-1371), as drafted. It gates gap-inventory batch 2.

Context

0270 makes @stackra/builder the one editor kernel, with a “document” surface for posts, articles, help articles, product descriptions and email templates, and full Yjs co-editing through @stackra/collaboration. It does not name the rich-text engine. The HeroUI Pro rich-text editor left core with ADR 0461, so core has none. Products need Shopify-level product descriptions, Yoast-style SEO beside the text, AI writing actions, and Markdown/MDX in and out, on web and on mobile. Weighed: TipTap v3 (ProseMirror), Lexical, BlockNote (built on TipTap; parts GPL or commercial), Plate (Slate) and Milkdown.

Decision

  1. Engine. The document surface runs on TipTap v3 core (MIT, ProseMirror) and only its MIT packages. TipTap’s paid cloud and Pro extensions are not used. On native the same engine runs through a TipTap-based React Native editor (10tap class), so one schema and one set of extensions serve web and mobile.
  2. One schema, owned by @stackra/builder. Nodes and marks are declared once. The stored form is the L2 document JSON (0110): ProseMirror JSON that references registered blocks by name, never markup, CSS or code (0154).
  3. Editor contributions. A package extends the editor by registering contributions in its module, never by importing editor internals:
    • nodes and marks;
    • slash-menu commands and toolbar or bubble-menu buttons;
    • side panels (for example the SEO analysis panel);
    • input rules and paste handlers;
    • AI actions.
    A contribution names the document kinds it applies to. Contributions are UI slots in the sense of 0350, and gen lists them in the manifest like other registered code.
  4. AI is a contributor, not part of the kernel. @stackra/ai contributes actions: rewrite, shorten, expand, change tone, translate EN↔AR, summarise, generate SEO title and description, alt text, FAQ from content, and suggest category. Each runs as an ordinary action (permission, risk, human approval for writes, 0021). Its result is applied as one undoable transaction that the author accepts or rejects.
  5. Markdown and MDX are serialisers.
    • Markdown import and export use TipTap’s Markdown support, both CommonMark and GFM.
    • MDX import maps each JSX element to a registered block by name and its props through the block’s Zod schema. Unknown elements are refused, and expressions are never evaluated.
    • MDX export writes registered blocks back as JSX.
    • HTML is accepted on paste only, sanitised to the schema.
  6. Collaboration binds through @stackra/collaboration (Yjs), per 0270 decision 5. Comments use @Commentable (0225).
  7. Design system. The editor’s chrome (toolbar, bubble menu, slash menu, block handle, link and image dialogs) is @stackra/ui components (gap-inventory row 67). @stackra/builder composes them; the UI kit has no editor logic.
  8. Bidi. Every block carries dir="auto" unless set, and Arabic is the first-class test language for the schema, paste and serialisers.

Consequences

  • One engine and schema on web and mobile, with Yjs co-editing ready, and every package can add to the editor without forking it.
  • AI and SEO arrive as contributions, so a product without them ships a smaller editor.
  • We depend on ProseMirror’s model and TipTap’s release cadence. MDX is limited to registered blocks, by design.
  • Chore: @stackra/builder owns the schema version and its migrations. A contribution that adds a node must ship its Markdown and MDX serialiser.

Enforcement

  • Planned: gen refuses a contribution whose node has no serialiser.
  • Planned: FORBIDDEN_DEPENDENCIES adds @tiptap-pro/*.
  • Planned: a schema round-trip test in @stackra/builder covers JSON ↔ Markdown ↔ MDX on an Arabic fixture.

Amendment 2026-10-31: the native editor is tentap (STA-1371)

Context. Decision 1 named “a TipTap-based React Native editor (10tap class)” for native. The owner approved @10play/tentap-editor and react-native-webview on 2026-10-31 (MIT, MIT-licensed packages only, no TipTap Pro). Decision.
  1. @stackra/ui/native’s RichTextEditor runs on @10play/tentap-editor 1.0.1 with react-native-webview at Expo SDK 57’s bundled version (catalog:mobile). Both are optional peers of @stackra/ui.
  2. tentap ships its own prebuilt TipTap v3 editor inside the WebView page (its TenTapStartKit schema), isolated from the app’s JavaScript and from the web editor’s catalog:frontend TipTap. Its schema has no code block, horizontal rule, dir attribute or CharacterCount, so the wrapper strips dir on the way in and writes dir: "auto" back on every block on the way out (the web JSON shape, so documents round-trip), counts characters itself and puts back a change past the limit, and keeps a value with nodes the native schema lacks read-only, shown as its text.
  3. Bidi on native is CSS inside the page (unicode-bidi: plaintext per block, the page’s base direction from the app), not dir attributes. Editor contributions stay web-only until @stackra/builder builds its own tentap web bundle with the shared schema (tentap’s customSource).