diff --git a/README.md b/README.md index 5cebf1d..557cfb6 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ pnpm install && pnpm dev # playground at http://localhost:5173 | [block](https://mermaid.js.org/syntax/block.html) | ✅ | ✅ | ❌ | | [zenuml](https://mermaid.js.org/syntax/zenuml.html) | ✅ | ❌ | ❌ | -**Item editing** is select, edit in place, add, and delete; **structural editing** adds connect, reorder, and restyle. Every type round-trips losslessly and syncs selection between code and canvas, including view-only zenuml (an external plugin). +**Item editing** is select, edit in place, add, and delete; **structural editing** adds connect, reorder, and restyle. Every type round-trips losslessly and syncs selection between code and canvas, including view-only zenuml (an external plugin). All editable types also work when rendering through Pele (`peleAdapter` in `@visimer/dom`) — most edit fully there; a few gitgraph/architecture/c4 items remain view-only pending follow-up, and zenuml is Mermaid-only since it is an external plugin. ## Packages @@ -122,6 +122,7 @@ Some other editor? Implement the adapter, it's about forty lines. ## Architecture - Leverages the native Mermaid.js renderer for visualizing the Mermaid diagram true to how it’s intended. We just overlay/add point-edit functionality on top. - We map the rendered SVG elements back to a Concrete Syntax Tree (CST) representation, which we can then use to edit only the parts of the Mermaid source that need updating (without affecting the rest of the file). +- The canvas renders through a swappable renderer seam: bring your own `mermaid` instance, or render via Pele by passing a `peleAdapter` (from `@visimer/dom`). Every editable diagram type works under both renderers — under Pele a few gitgraph/architecture/c4 items remain view-only pending follow-up, and zenuml is a Mermaid-only external plugin. ## Design diff --git a/TESTING.md b/TESTING.md index 037fd68..bb2450a 100644 --- a/TESTING.md +++ b/TESTING.md @@ -14,7 +14,8 @@ test exercises the seam. | CodeMirror binding | `@visimer/codemirror` against a real CodeMirror 6 `EditorView` | `packages/codemirror/test/binding.test.ts` (jsdom): engine ops → view, view edits → engine, decorations in the DOM, caret → entity selection, engine-authoritative undo, teardown | A | | Monaco binding | `@visimer/monaco` against the structural editor interface it binds | `packages/monaco/test/binding.test.ts`: fake implementing exactly the bound surface (both sync directions, decorations, caret reasons, undo keys, dispose), plus a compile-time conformance check that real `monaco-editor` types satisfy the interface | B | | Canvas render loop (what makes `@visimer/dom` re-render) | The triggers and the guards on them: source changes, config changes, and the webfont-settled re-measure that keeps labels from being clipped on a cold load | `packages/dom/test/fonts.test.ts` drives a real `MermaidCanvasView` over a fake mermaid and a stubbed `FontFaceSet`: re-renders once when fonts land late, never when they were already there, never after `destroy()`, never where the document exposes no font set | B | -| SVG correlation (dom package ↔ Mermaid's rendered DOM) | Third-party dependency seam: correlators key off Mermaid's internal SVG structure, which can shift between Mermaid releases | None automated. Verified manually in the playground across all 23 diagram types | uncovered | +| SVG correlation — mermaid side (dom package ↔ Mermaid's rendered DOM) | Third-party dependency seam: correlators key off Mermaid's internal SVG structure, which can shift between Mermaid releases | None automated. Verified manually in the playground across all 23 diagram types | uncovered | +| SVG correlation — pele side (dom package ↔ pele's rendered SVG) | Third-party dependency seam: pele correlators key off pele's internal SVG structure | `packages/dom/test/correlate-pele*.test.ts` (~100 fixture tests, one suite per correlation family group; jsdom; pele's metrics fallback) render real pele SVG — flowchart, sequence, state, class, ER, pie, gantt, and the 15 line-item types — and assert entities were tagged, ambiguous matches degraded (never mis-paired), scenery left untagged, and edits round-tripped visibly through the view | A | | Canvas interaction layer (popovers, drag, in-place editing) | `@visimer/dom` gestures compiled to engine ops | None automated. Verified manually in the playground | uncovered | | React bindings | `@visimer/react` hooks/components over core events | None automated. Thin subscription layer; exercised manually via the playground | uncovered | @@ -23,8 +24,11 @@ test exercises the seam. What the current suite cannot catch: - **A Mermaid upgrade that shifts the rendered SVG structure.** This is the - highest-risk seam. Correlators fail soft (elements degrade to view-only), - so the failure is silent feature loss, not a crash. Closing it needs a + highest-risk seam on the Mermaid side. Correlators fail soft (elements + degrade to view-only), so the failure is silent feature loss, not a crash. + The pele side of this seam is now covered: every diagram family has a + real-render fixture suite (`correlate-pele*.test.ts`) asserting entities + were tagged and degraded sanely. Closing the Mermaid side still needs a real-browser rung (vitest browser mode or Playwright) that renders real Mermaid per diagram type and asserts entities were tagged; jsdom cannot provide it because Mermaid layout requires real text measurement. @@ -38,8 +42,8 @@ What the current suite cannot catch: - **React render-loop regressions** (stale subscriptions, effect ordering). Would need @testing-library/react coverage. -The playground doubles as the manual harness for all three: every editable -type has a sample, and the toolbar/popovers exercise the gesture surface. +The playground doubles as the manual harness for the uncovered seams: every +editable type has a sample, and the toolbar/popovers exercise the gesture surface. ## Running diff --git a/apps/playground/package.json b/apps/playground/package.json index 9f0304b..839f831 100644 --- a/apps/playground/package.json +++ b/apps/playground/package.json @@ -22,6 +22,7 @@ "@visimer/react": "workspace:*", "mermaid": "^11.16.1", "monaco-editor": "^0.55.1", + "pele": "^1.13.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, diff --git a/apps/playground/src/App.tsx b/apps/playground/src/App.tsx index bb63f8b..ae3e543 100644 --- a/apps/playground/src/App.tsx +++ b/apps/playground/src/App.tsx @@ -7,12 +7,22 @@ import { type ParticipantType, type ShapeId, } from '@visimer/core' -import type { MermaidCanvasView, Tool } from '@visimer/dom' +import * as pele from 'pele' +import { peleAdapter, type MermaidCanvasView, type Tool } from '@visimer/dom' import { MermaidCanvas, useMermaidEditor } from '@visimer/react' import { MermaidCodeMirror } from '@visimer/codemirror' import { MonacoPane } from './MonacoPane' import { SAMPLES } from '../../js-playground/src/samples' +/** + * Pele is stateless, so one adapter serves every canvas — built once at module + * scope instead of per-render, keeping the `renderer` prop identity stable (the + * canvas re-creates its view when props it was constructed with change). + */ +const peleRenderer = peleAdapter(pele, { + fontFamily: '"Inter Variable", "Inter", ui-sans-serif, system-ui, sans-serif', +}) + let zenumlReady: Promise | null = null function ensureZenuml(): Promise { zenumlReady ??= import('@mermaid-js/mermaid-zenuml') @@ -142,6 +152,13 @@ export default function App() { const [theme, setTheme] = useState('ink') const [readOnly, setReadOnly] = useState(false) const [zenumlOk, setZenumlOk] = useState(false) + const [renderer, setRenderer] = useState<'mermaid' | 'pele'>(() => + localStorage.getItem('visimer.renderer') === 'pele' ? 'pele' : 'mermaid', + ) + + useEffect(() => { + localStorage.setItem('visimer.renderer', renderer) + }, [renderer]) const sample = SAMPLES[sampleIndex] useEffect(() => { @@ -160,6 +177,10 @@ export default function App() { playground
+
+ + +