- Date: 2026-07-14
- Status: Accepted
- Builds on: ADR 0066 (artboard persistence). Phase 4 of Studio-in-Workflow.
Context
Artboards now persist (ADR 0066), but a built design-component is still a dead
end — you can't reuse it. Arno already has the shape of a component library in
Sorgente: sorgente.component_entry + Tweak's "Add to library" dialog + the
StudioPalette drag-source + the palette-drop path in StudioDndShell. But that
library is maintainer-scoped and stores an HTML snapshot (display-only) —
wrong for project-scoped, editable design components.
Decision
A project-scoped Component Library that stores the picked box's ponte
LayoutNode subtree (not an HTML snapshot), with Copy semantics: dragging
a library component onto an artboard inserts an independent copy (fresh ids). No
live master↔instance link — that's a later phase.
Storage (workflow_component, ADR 0066 sibling)
workflow_component
project_id text FK → project.id ON DELETE CASCADE
component_id text (client key "wc-<ts>")
name text
kind text (atomic: atom|molecule|organism|template|page)
tree jsonb (LayoutNode subtree; typed `unknown`)
created_at / updated_at timestamptz
PRIMARY KEY (project_id, component_id)API (apps/api/src/workflow-component.ts, mirrors workflow-artboard)
GET /api/v1/projects/:projectId/workflow-components→{ items }PUT /api/v1/workflow-components→ upsertDELETE /api/v1/workflow-components?project=&id=
requireOwnedProject gate, 500k tree cap.
Reuse — the drag-back loop already exists
The Copy insert is the existing paste machinery: freshenIds (id refresh) +
splice into the drop target (as in pasteIntoActiveBox). What's new:
insertSubtree(root, targetId, subtree, position)in@arno/ponte/tree-ops— clone + regenerate ids + insert (the container/leaf placement rules matchinsertFromPalette). Pure + unit-tested.StudioDndShellpalette-drop learns a second payload: a palette item may carrydata.tree(a subtree) instead ofdata.ref(a single component). When present, drop callsinsertSubtree; otherwise the existinginsertFromPalette.WorkflowPalette— a workflow-scoped drag-source (mirrorsStudioPalette) listingworkflow_componentrows, grouped by atomic kind, each draggable withdata: { source:"palette", tree }.- Save action — a small workflow affordance reads the studio-bus picked box,
grabs its subtree (
findNode), and POSTs it as aworkflow_component.
Frontend data
useWorkflowComponents(projectId) — server-backed list + save(name,kind,tree) +
remove(id). Mounted in the workflow shell alongside the artboards.
Consequences
- Build once, reuse anywhere on the project's artboards. Foundation for publish.
- Copy-only: editing a library component does NOT update already-placed copies. Simple + predictable; master/instance propagation is deferred (needs an instance-ref node type + resolve-at-render + detach — its own ADR).
- Kind is set at save time (atomic-design bucket); no re-classification UI yet.
Deferred
Master↔instance (live components); rename / re-kind UI; thumbnails/previews in the palette; sharing a component across projects; publish → code.