- Date: 2026-06-09
- Status: Accepted
- Phase: Sorgente Studio
- Scope: Sorgente library surfaces only (Tokens / Components / etc.). Other ARNO surfaces follow later.
- Builds on: ADR 0052 (shared-DB), 0053 (Foundation rules), 0054 (Library surface), 0055 (Inspector — Tweak/Tap/Ruler).
Context
Sorgente library surfaces are currently hand-coded React. To rearrange anything — move "Status palette" before "Brand", split a row into a row + column — the maintainer has to edit code. ADR 0055 added per-element edits (Tap hide, Tweak token tweaks) but not page layout.
Maintainer asked for a Studio editing mode: every page is a tree of rows / columns / components; rows and columns can nest arbitrarily; everything is draggable; spacing edits flow through Tweak. Always on, no toggle.
Decision
§1 — Data model
A Studio page is a recursive layout tree with three node kinds:
type LayoutNode =
| { id: string; type: "row"; gap?: string; padding?: string; align?: string; justify?: string; children: LayoutNode[] }
| { id: string; type: "column"; gap?: string; padding?: string; align?: string; justify?: string; children: LayoutNode[] }
| { id: string; type: "component"; ref: string; props?: Record<string, unknown> }row— horizontal flex containercolumn— vertical flex containercomponent— terminal leaf, instance of an entry in the component registry (built-in TokenChip / Card / Button + maintainer-saved sorgente.component_entry rows)
gap / padding are CSS strings (typically var(--space-N)) so spacing edits feed the same grid that the rest of Sorgente uses. align/justify map to flex align-items / justify-content.
§2 — Storage
sorgente.composition_instance already keys (user_id, route_path) → tree jsonb. Reuse:
| column | role |
|---|---|
| user_id | per-maintainer (sorgente.* convention) |
| route_path | which page this tree paints (e.g. /sorgente/tokens) |
| tree | the LayoutNode root |
Default seed trees ship in code (apps/web/src/dev-tools/studio/seeds/) — the maintainer's first save creates the row; absence → render the seed. Reset = delete the row.
§3 — Render
<StudioRenderer node={tree} /> walks the tree, renders each container as <div data-arno-box="row|column"> with flex props from the node, and each component leaf via the registry (@/dev-tools/studio/registry). The registry maps ref → React component. Adding a new component to the catalog = adding one entry.
§4 — Drag & drop
@dnd-kit/core + @dnd-kit/sortable. Always on across all Studio surfaces — no toggle:
- Hover a box → outline (thin mint border, 1px); cursor
grab. - MouseDown on container's drag-handle → drag starts; container becomes a
<DragOverlay>. - Drop indicator — thin mint line across the parent's cross-axis at the insertion point (between two siblings, or at the start/end of a container).
- Drop targets:
- Reorder — drop between two siblings in the same parent.
- Move-into — drop on another container; node becomes child.
- Rows can move into rows, columns into columns, rows into columns, etc. — orientation of parent determines layout, not the child's type.
- Terminal
componentleaves drag the same way; they can nest anywhere a container would.
§5 — Tweak integration (next PR)
Clicking a container while Tweak is open populates Tweak's panel with container-specific controls: gap, padding, direction (row/column toggle), align, justify. Edits write back to the tree node. Apply persists. Same Apply/Reset/Delete/Go-to-component/Add-to-library toolbar applies for component leaves.
§6 — Component palette + Cmd+C / Cmd+V
Sidebar palette lists every available component:
- Built-in registry (TokenChip, Card, Button, ...)
- Maintainer-saved entries from
sorgente.component_entry
Drag palette → tree drop target → adds a component leaf with default props.
Keyboard:
Cmd+Con a selected box (container or component) — copies the subtree to an in-memory clipboardCmd+Vwhile a target is selected — pastes the clipboard into the target (containers: append; components: insert as next sibling)
§7 — Scope today
- Only Sorgente library surfaces (/app/library?project=prj-arno-sorgente-seed). Per-user (sorgente.* convention).
- Tweak container-controls + palette + Cmd+C/V land in the next PR. This PR delivers tree data model + render + drag&drop reorder.
§8 — Phasing
- ADR 0056 (this doc)
- MVP — data types, default seed tree for Tokens, StudioRenderer with dnd-kit reorder, storage upsert. (this PR)
- Tweak container controls (gap / padding / direction / align / justify) — write back to tree.
- Component palette UI + Cmd+C / Cmd+V.
- Roll out to Components / Brand / History sub-tabs.
- Roll out to other ARNO surfaces.
Anti-patterns explicit
- Do not introduce a Studio toggle. Maintainer asked for always-on; toggle = an extra place to forget.
- Do not persist resolved CSS into the tree.
gap/paddingstay as token-ref strings (var(--space-16)); SorgenteOverlay keeps the cascade. - Do not mix Foundation Catalog rendering with Studio. Foundation surfaces stay code-rendered; Studio is Sorgente-only.
- Do not persist drag state in the DB. Only the final tree shape — drag is a UX layer.
- Do not silently truncate trees. If a default seed grows, ship the new seed; existing maintainer trees stay theirs.