ADRs
ADR 0056 — Studio layout tree (drag-rearrangeable Sorgente surfaces)
  • 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 container
  • column — vertical flex container
  • component — 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:

columnrole
user_idper-maintainer (sorgente.* convention)
route_pathwhich page this tree paints (e.g. /sorgente/tokens)
treethe 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 component leaves 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+C on a selected box (container or component) — copies the subtree to an in-memory clipboard
  • Cmd+V while 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

  1. ADR 0056 (this doc)
  2. MVP — data types, default seed tree for Tokens, StudioRenderer with dnd-kit reorder, storage upsert. (this PR)
  3. Tweak container controls (gap / padding / direction / align / justify) — write back to tree.
  4. Component palette UI + Cmd+C / Cmd+V.
  5. Roll out to Components / Brand / History sub-tabs.
  6. 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/padding stay 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.