PR: #514: feat(matter): substrate light+full presets + toolbar + round-trip [Story-2035] by @ankitmaheshwari-com
View PR →

Files changed

8 file(s), +487 / -44 lines

Path Role + -
messages/en.jsonConfig+13-1
messages/hi.jsonConfig+13-1
src/components/shared/editable-surface-preset.test.tsComponent+108-4
src/components/shared/editable-surface-preset.tsComponent+104-28
src/components/shared/editable-surface-toolbar.tsxComponent+148-0
src/components/shared/editable-surface.stories.tsxComponent+63-0
src/components/shared/editable-surface.test.tsxComponent+12-0
src/components/shared/editable-surface.tsxComponent+26-10

Story-2035: light + full presets + toolbar + per-preset round-trip

Story-2024 laid the keystone: markdown installed under EditableSurface and a preset seam, shipping only plain. This story fills the seam — it makes light and full reachable, adds the formatting bubble-menu toolbar, and proves markdown round-trip identity per preset. Adding a tier stayed one entry in resolvePreset, never a new editor. The Brief is untouched — it stays on plain.

What landed

How a markdown preset loads without an editor in hand

plain builds its ProseMirror doc directly in toContent. A markdown preset can't — parsing needs the editor's markdown manager, which doesn't exist at useEditor init. The seam that avoids re-plumbing the primitive: the strategy exposes a contentType, and toContent for a markdown preset just returns the raw string. Every setContent call site (init, Escape-revert, external-value sync) passes { contentType: strategy.contentType }, so TipTap parses markdown at set-time. toValue is getMarkdown() with a trailing newline stripped, so an untouched value doesn't spuriously trip dirty/save (the story's "modulo trailing-newline normalization").

Round-trip identity, tested as a fixpoint

Rather than pin brittle exact serializer output, each preset's round-trip test loads a value, saves it (first), loads that and saves again (second), and asserts second === first — a stable fixpoint — plus that the preset's promised formatting survived. So light preserves **bold**, *italic*, and [text](url) but drops a stray # to inline text; full preserves headings, lists, and blockquotes.

Files

No Brief regression

preset still defaults to "plain"; the Brief data fields pass no preset and behave exactly as before. light/full are reachable but not yet wired into any Brief field — that flip is a later per-field prop change (RFD §6 step 4), out of this story's scope.

Deliberately deferred (not this story)

Verification

pnpm lint (clean, one pre-existing unrelated warning), pnpm build (green, TypeScript passes), and the full pnpm test suite (1009 tests, incl. the per-preset round-trip and toolbar tests) all pass. The LightPreset / FullPreset Storybook stories demonstrate the bubble-menu toolbar on selection, the dirty flag tripping on a formatting-only edit, and the markdown value round-tripping.