Design consolidation from the 2026-07-30 planning session, grounded in #143 and Travis-Gilbert/Theorem#385. Everything here sits above what those PRs land: they give us the model canvas, the program canvas, the playground contracts (node events + drain markers, pins, tweaks, published tools, durable parks, QuickJS/Extism authoring), the OKF model profile, and versioned registry projections. This issue is the shared-substrate layer that makes the two canvases one system, plus answers to three open design questions.
ComfyUI law stands: GPL, read-and-reimplement only. Everything below is pattern adaptation, zero code movement.
A. One substrate, many node kinds
Extract the canvas shell from @commonplace/model-canvas into a substrate layer (package split or internal module boundary, implementer's call) with a node-kind registry: one generic node shell (header, badges, ports, selection, collapse) hosting kind-specific bodies.
Initial kinds: model-card (the ERD card from the fork), program-node (catalog-driven per PG1, contracts from @commonplace/program-contracts), note-markdown (ComfyUI's Markdown Note pattern), group-frame (titled region, see C), and the JSON Canvas kinds (text/file/link/group) so the inspector rail's Z-layer can converge on the same substrate later. Kinds are registry entries, not bespoke components; adding a kind must not touch the shell.
B. The edge language (settling the "between Railway and ComfyUI" ask)
One geometry + dash system, two palettes:
- Geometry: cubic bezier (curvilinear), never orthogonal elbows.
- Stroke: 1.5px,
stroke-linecap: round, stroke-dasharray: 0.1 6 → true round dots, the Railway feel. Hover/selected: width 2px and dash tightens to 4 3 (reads as solidifying without a hard state jump).
- Program-canvas palette: edge takes the source port's shape-class OKLCH family (PG law, max 5 families), ComfyUI's legibility without its solid heaviness. Port dots wear the same family color.
- Model-canvas palette: relation edges stay neutral ink, low chroma; information rides the relation chip (
order_id = order_id) and cardinality glyphs (N:N badge kept from the fork, restyled to console tokens). Relations aren't typed flows; coloring them would spend the hue budget on nothing.
- Motion: animated
stroke-dashoffset only on running edges during program execution (flow direction made visible), driven by the #385 node-event stream; prefers-reduced-motion disables; cap concurrently animated edges (~40) and fall back to a static "active" width bump beyond it.
- Connection preview: the drag line renders in the source family and the satisfaction function (PG's drag-release filter) dims non-satisfying targets live.
C. ComfyUI adaptations, mapped to what exists
| Pattern |
Substrate mapping |
Status |
| Group frames with titles (Step1/Step2/Step3) |
group-frame kind; membership = geometry, persisted in the layout doc, never in content identity |
needs kind |
Node id badges (#66) |
shell badge slot; id = catalog node id short-hash |
needs shell slot |
| Widgets-on-node (seed/steps/cfg steppers) |
parameter rows rendered from the generated FieldType/port types; same renderer map as the records surface cells |
needs UI; contracts exist |
| Show/hide advanced inputs |
per-kind port sections with a collapse, port visibility in layout doc |
needs UI |
| Bypass / mute |
already in #385 contracts (constructors default not-bypassed/not-muted); needs shell affordance + dimmed render states |
needs UI |
| Inline output preview on node |
playground per-node inspection over the event stream; large outputs via spill handles |
contracts exist |
| Drag-from-port quick add |
PG drag-release palette filter, already specced; wire on substrate |
specced |
| Double-click canvas search |
quick-add palette, same satisfaction filter, no port context |
needs UI |
| Reroute dots |
edge waypoints stored in layout doc; render as bare port-dots on the edge |
needs UI |
| Queue affordance |
run panel per the Langflow borrowings register; #385 events/drain markers are the transport |
contracts exist |
| Subgraph composition |
program-as-node (published programs in catalog, #385) is the semantic form; visual collapse-selection-to-program is a later authoring convenience |
semantic form exists |
D. The registry question (does RustyRed schema need anything for data modeling?)
Mostly no, and specifically yes once. Declared object types + FieldType + relations already model the ERD; the OKF model profile round-trips it. The one addition worth making, Theorem-side: a provider facet on declared object types, native-view | declared-record | derived-program | connector, which is the general form of the fork's per-card SQL badge. The canvas renders it as the card badge, the pg-server native-views seam consumes it to know which types it can serve, derived-program links a type to the published program that materializes it (closing the loop between the two canvases), and OKF export carries it as an x-theorem-* extension. Node kinds stay in the catalog; the facet is the only registry change. Multiple node types the ComfyUI way is section A's registry, not a schema feature.
E. The MF7 seam (converting #385's honest block into a plan)
#385 correctly refused to fake it: theorem-canvas-compile compiles semantic CanvasDoc and Graph Lisp state and has no registry-ERD projection contract. Proposal: now, the ERD canvas subscribes to the tenant-scoped registry GraphQL door's change signal from #385 (versioned projections make this clean); next, a small spec adds a registry-ERD projection producer to canvas-compile as a proper surface kind fed through its existing invalidation, so the canvas joins the reactive system instead of polling beside it. MF7's acceptance then applies unchanged. Until that spec exists, nothing pretends to be compile-integrated.
Checklist
Layout, membership, waypoints, and port visibility all persist in the canvas layout document through the object seam; none of it ever enters content identity, per the standing law.
Design consolidation from the 2026-07-30 planning session, grounded in #143 and Travis-Gilbert/Theorem#385. Everything here sits above what those PRs land: they give us the model canvas, the program canvas, the playground contracts (node events + drain markers, pins, tweaks, published tools, durable parks, QuickJS/Extism authoring), the OKF model profile, and versioned registry projections. This issue is the shared-substrate layer that makes the two canvases one system, plus answers to three open design questions.
ComfyUI law stands: GPL, read-and-reimplement only. Everything below is pattern adaptation, zero code movement.
A. One substrate, many node kinds
Extract the canvas shell from
@commonplace/model-canvasinto a substrate layer (package split or internal module boundary, implementer's call) with a node-kind registry: one generic node shell (header, badges, ports, selection, collapse) hosting kind-specific bodies.Initial kinds:
model-card(the ERD card from the fork),program-node(catalog-driven per PG1, contracts from@commonplace/program-contracts),note-markdown(ComfyUI's Markdown Note pattern),group-frame(titled region, see C), and the JSON Canvas kinds (text/file/link/group) so the inspector rail's Z-layer can converge on the same substrate later. Kinds are registry entries, not bespoke components; adding a kind must not touch the shell.B. The edge language (settling the "between Railway and ComfyUI" ask)
One geometry + dash system, two palettes:
stroke-linecap: round,stroke-dasharray: 0.1 6→ true round dots, the Railway feel. Hover/selected: width 2px and dash tightens to4 3(reads as solidifying without a hard state jump).order_id = order_id) and cardinality glyphs (N:Nbadge kept from the fork, restyled to console tokens). Relations aren't typed flows; coloring them would spend the hue budget on nothing.stroke-dashoffsetonly on running edges during program execution (flow direction made visible), driven by the #385 node-event stream;prefers-reduced-motiondisables; cap concurrently animated edges (~40) and fall back to a static "active" width bump beyond it.C. ComfyUI adaptations, mapped to what exists
group-framekind; membership = geometry, persisted in the layout doc, never in content identity#66)FieldType/port types; same renderer map as the records surface cellsD. The registry question (does RustyRed schema need anything for data modeling?)
Mostly no, and specifically yes once. Declared object types +
FieldType+ relations already model the ERD; the OKF model profile round-trips it. The one addition worth making, Theorem-side: a provider facet on declared object types,native-view | declared-record | derived-program | connector, which is the general form of the fork's per-cardSQLbadge. The canvas renders it as the card badge, the pg-server native-views seam consumes it to know which types it can serve,derived-programlinks a type to the published program that materializes it (closing the loop between the two canvases), and OKF export carries it as anx-theorem-*extension. Node kinds stay in the catalog; the facet is the only registry change. Multiple node types the ComfyUI way is section A's registry, not a schema feature.E. The MF7 seam (converting #385's honest block into a plan)
#385 correctly refused to fake it:
theorem-canvas-compilecompiles semantic CanvasDoc and Graph Lisp state and has no registry-ERD projection contract. Proposal: now, the ERD canvas subscribes to the tenant-scoped registry GraphQL door's change signal from #385 (versioned projections make this clean); next, a small spec adds a registry-ERD projection producer to canvas-compile as a proper surface kind fed through its existing invalidation, so the canvas joins the reactive system instead of polling beside it. MF7's acceptance then applies unchanged. Until that spec exists, nothing pretends to be compile-integrated.Checklist
group-frameandnote-markdownkindsLayout, membership, waypoints, and port visibility all persist in the canvas layout document through the object seam; none of it ever enters content identity, per the standing law.