Code Content Editor Experiment - SekiroKenjii/Quizzivy GitHub Wiki
This is a synthetic, local-only harness. It is not an import route and does not save to the server. Switching examples retains edits in memory and starts a separate undo history; reloading discards edits. Existing question storage, Markdown readers, published versions and student routes are unchanged.
From web/, run pnpm e2e:content to build and test the harness with one Chromium
worker. For a walkthrough, run
pnpm exec vite preview --config tests/support/content-editor/vite.config.ts
and open http://localhost:4174/tests/support/content-editor/index.html.
Stop a previous preview before rebuilding. reader.html beside it is a separate
read-only entry point. Neither entry is included in the normal application build.
src/components/shared/content/ owns a candidate, versioned semantic AST,
strict validation, plain-text projection and an editor-independent renderer.
The Tiptap adapter owns conversion to and from editor JSON; that JSON is not a
storage contract. W02/W05 must finalize OpenAPI, server validation and asset
bindings before these components enter production authoring or delivery.
The candidate supports paragraphs, three heading levels, six text marks, numbered/bullet lists, HTTPS text links, tables with header cells and row/column spans, stable atomic gaps, and asset references. Unknown fields/marks, unsafe links, duplicate gaps, nonrectangular/nested tables and excessive content are rejected. Lists begin each item with a paragraph. Budgets are 2,048 semantic nodes, 100,000 text characters, 16 JSON nesting levels, 50 table rows and 12 columns. The preflight bounds traversal before recursive validation and accepts shared values generated by editor commands without accepting cycles.
The editor toolbar exercises marks, lists, headings, gaps, table changes and undo/redo. Supported formatted paste now opens a local conversion preview before insertion; files, unsafe/unsupported HTML and external drops are refused in full. Explicit plain-text paste remains available. The parser loads only on formatted paste and never mounts source HTML. Assets are inert placeholders with no URL loading. Question gap bindings are integrated in W-06b; link editing, asset binding and durable/revision-aware saves remain pending. Legacy Markdown still goes through the existing renderer unchanged.
Tiptap/ProseMirror 3.31.3 and Lexical 0.51.0 (both MIT) were exercised against the
same fixtures.ts through their real document models. Both retained Vietnamese
text, combined marks, superscript/subscript, merged cells, ordered-list starts
and custom stable gap IDs across JSON serialization and reload. This comparison
does not establish native IME behavior or equivalent UI accessibility.
Tiptap is the working candidate for the interactive spike: its existing table commands and React selection subscription can be exercised with a small adapter. Lexical remains a viable alternative; its comparison packages stay outside the application. Final selection still requires native Vietnamese IME, Safari/mobile selection, teacher walkthrough and representative long-document interaction. Chromium Unicode insertion is not evidence that an operating-system IME works.
To repeat the Lexical model comparison with Node 24, create a temporary package outside the repository and install these exact dependencies there:
pnpm add --save-exact --ignore-scripts [email protected] @lexical/[email protected] @lexical/[email protected] @lexical/[email protected] @lexical/[email protected]Then, from web/:
node --experimental-strip-types tests/support/content-editor/compare-lexical.mjs /absolute/path/to/temporary-package
pnpm exec vitest run tests/units/content tests/integration/content --maxWorkers=1The Tiptap packages are pinned: core/pm supply the schema and transactions, React supplies the isolated editor/selection bindings, StarterKit supplies common text commands and undo, and table/subscript/superscript supply required exam semantics. No premium extensions or new backend dependencies are used.
The real-build test rejects Tiptap/ProseMirror in the reader's dependency closure. It caps initial additional editor JavaScript at 160 KiB gzip, the on-demand clipboard converter at 64 KiB gzip, and the complete reader harness (including React, existing Markdown support and both locales) at 220 KiB gzip. Before clipboard integration, a local production build measured approximately 142 kB and 183 kB gzip for the editor and reader, respectively. The new parser is a separate lazy chunk and must not enter either initial dependency closure. These are harness transfer sizes, not incremental production route sizes or interaction-latency guarantees.
References: Tiptap React, tables, selection subscriptions, Lexical, Lexical tables.
Generated from web/tests/support/content-editor/README.md. Edit that file in the repository; changes made on this page are overwritten.