Pin the bounded SVG subset from a real d2 sample + our templates (markers for arrowheads in; mask deferred to v1-ignore; foreignObject/filters/ gradients out, so mermaid isn't a launch target). Class styling resolves via an inline-normalize preprocess (flatten <style> classes to inline attrs) so the painter stays a pure attribute renderer. Tool-PATH for the d2 binary now resolves via D-104. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
6.4 KiB
Drawing-card JSON schema (T-317 / D-91 / D-103) — draft
Status: draft for build, SVG-substrate model (D-103), refined from the T-317 wireframe set, 2026-06-28.
Model — what this is, and what it is NOT
- SVG is the substrate. The card's primitive / scene-graph layer is SVG;
the clide-owned
CustomPaintSVG renderer (T-320) is the engine the rest builds on (D-103). - It is not the HTML Canvas 2D API and not a third-party package. SVG is
a document format we render ourselves — "own the rendering stack" holds.
("HTML
<canvas>" in D-91 was only a mental model, chosen to reject Obsidian's.canvasschema; never an API to port.) - The card is two layers:
- SVG content — painted by the SVG renderer.
- A thin Flutter overlay — the clide chrome that is not content
(per-object label/description captions, lightbox affordance), anchored to
SVG elements via
data-*attributes.
- Display-only (D-78); re-rendered from the document. The graph template is the live-widget exception (below).
Document envelope
{
"card": { "label": "Build pipeline", "description": "…" }, // optional caption (overlay)
"template": "icon", // optional → template sugar
"…template fields…": "…",
"svg": "<svg viewBox='0 0 480 360'>…</svg>" // primitive mode = raw SVG
// or "svgPath": "diagram.svg"
}
- Template mode —
templatenames a component; clide lowers it to SVG (+ overlay anchors). - Primitive mode —
svg(inline) orsvgPath— arbitrary SVG, the escape hatch. One less invented format; external SVG / graphviz / mermaid render free. - Card size comes from the SVG
viewBox(orwidth/height); the painter scales to the pane width.
Primitive layer = a bounded SVG subset
Grounded in a real d2 sample + our own templates — not a full SVG engine:
- structure:
<svg>(viewBox/width/height, incl. nested<svg>),<g>(transform, opacity, class),<defs>,<marker>(+ marker-start/mid/end,orient="auto", refX/refY, viewBox) — edge arrowheads - shapes:
rect(rx/ry),circle,ellipse,line,polyline,polygon,path(full data —M L H V C S Q T A Z+ relatives) - text:
text+tspan(x/y/dx/dy, font-family incl. Phosphor, font-size/weight, text-anchor, dominant-baseline) - raster:
image(href/xlink:href, x/y/w/h, preserveAspectRatio) - styling: presentation attrs (fill, fill-opacity, stroke, stroke-width,
stroke-linecap/linejoin, stroke-dasharray, opacity, color),
transform(translate/scale/rotate/matrix);class=resolved by the normalizer below - deferred v1:
<mask>(d2 masks connections for clean edge/node joins) — ignore and lean on node-over-edge paint order; add only if output looks wrong - out:
foreignObject, filters,<animate>/SMIL, scripting,<use>/<symbol>, gradients, patterns,clipPath→ mermaid is not a launch target (it leans onforeignObject)
color everywhere is an arbitrary value (hex / named) — content, not a clide
SurfaceTokens token (D-7 governs clide chrome, not rendered content).
Class styling → inline-normalize (not a render-time CSS engine)
d2 / graphviz emit a <style> block of flat single-class selectors
(.fill-B1, .shape, .connection, .text-bold → presentation props), not
inline attributes. A preprocessing normalizer parses <style> into
class→props and merges each element's class props into inline presentation
attributes (inline wins), then drops <style>. The painter therefore only ever
sees inline attrs — a pure, testable presentation-attribute renderer. The
normalizer is a bounded, fixture-testable transform (real d2 + graphviz output).
Overlay (clide chrome, layered over the SVG)
Flutter widgets anchored to SVG elements that carry:
data-label→ themed caption beneath the element's bounding boxdata-description→ secondary caption linedata-lightbox(on<image>) → click-to-zoom affordance
Templates emit these attributes; raw-SVG authors may add them. The icon
template's data-label is also the bridge to the interaction-zone choice list.
Templates (lower to SVG + overlay)
| template | lowers to | ticket |
|---|---|---|
image |
<image href> + data-lightbox + caption attrs |
T-316 |
icon |
<text font=Phosphor> glyphs at 10,11,12,13,14,15,18,20,24,32,48 + hero 52; per-item data-label/data-description/color |
T-313 |
compare |
two+ <image> side by side in a <g>, per-image data-lightbox + captions |
T-319 |
svg |
identity — the source is the SVG | T-320 |
d2 |
compile d2 → SVG → render | T-494 |
graph |
exception — hosts the live native graph subsystem widget (D-46 / T-323), not static SVG | T-321 |
CLI / transport (D-6 parity)
clide draw --file doc.json (or inline JSON). Flutter-free handler validates,
publishes on a draw MessageBus channel; the Claude extension injects the card —
mirroring image.show. The shipped image show (T-249/T-252) stays as
convenience and migrates onto this card later (D-91). --stdin deferred (T-315);
--file is the path.
Error contract
Unknown template, unparseable / unsupported SVG, bad href / glyph / color
→ honest IpcError (userError / notFound), surfaced like image.show —
never a blank card.
Build sequence (D-103)
- T-320 — the SVG renderer (engine): parse + paint the bounded SVG subset.
- T-318 — envelope + template dispatch + the Flutter overlay (
data-*→ captions / lightbox) on top of the renderer. - Templates: image / icon (T-316 / T-313) → compare (T-319) → d2 (T-494) → graph (T-321, after the graph subsystem T-323).
Decisions
- SVG subset boundary (T-320): RESOLVED — see the subset above, grounded in a real d2 sample; expand deliberately.
- Class styling (T-320): RESOLVED — inline-normalize, not a render-time CSS engine (above).
- Tool-PATH resolution: RESOLVED — explicit user-scope override + first-run auto-detect (D-104 / T-495); the d2 binary resolves through it.
- D2 compiler delivery (T-494): open — shell out to a
d2binary as a pql-style supporter tool (resolution now handled by D-104), vs. vendor. - Graph (T-321): gated on the native graph subsystem (D-46 / T-323).