DreamLake

Layout controller

A request describes where a view should go and what happens when it gets there. Callers do not traverse the layout tree. User interactions and agents use the same controller; the resulting panels retain native drag, dock, resize, tabs and close.

Three layers

LayerResponsibility
Low-level UIKit PanelLayoutStable view instances, layout geometry, splits, tabs, drag, docking, sizing and connected content surfaces
High-level UIKit controllerInspect, resolve, apply; named and spatial destinations, content filters, pin-aware replacement and fallbacks
Application integrationResource identity, safe descriptors, authorized view loaders, source/project metadata and interaction defaults

The controller is transport-neutral. Browser automation, a CLI bridge and application buttons can call the same API. It creates no network listener and bypasses no application authorization or view-disposal guard.

Starting layout → command → result

Each set shares one starting layout. Its tabs fork alternative commands from that same state. Open Exact request to see executable JSON. The illustrations are rendered from actual controller snapshots; these are topology diagrams, not product screenshots. Blue + tabs are new, red − tabs are removed from the starting layout, and amber ↔ tabs moved between panels or changed order. Unchanged tabs stay neutral. Dashed tabs are replaceable previews; a dot marks a pinned tab. Hover, click, or use arrow keys to switch branches. Each example has its own section heading and stays within the text column, without an enclosing card.

One panel

Same source, three placements. No existing X.

A · Shared starting layout

One panel: starting layoutAAAregion-1

A remains mounted behind X.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "A"
  },
  "placement": "tab"
}

B · Result

One panel: Tab resultAAAregion-1

Resolving

No tabs added, removed or moved.

List and editor

The editor is the source. Only its region is divided.

A · Shared starting layout

List and editor: starting layoutListListListregion-1AAAregion-2

The list stays untouched.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "A"
  },
  "placement": "tab"
}

B · Result

List and editor: Tab in editor resultListListListregion-1AAAregion-2

Resolving

No tabs added, removed or moved.

A source with two tabs

B is active. Placement beside B means beside its entire panel.

A · Shared starting layout

A source with two tabs: starting layoutAABBBregion-1

A and B remain in the same panel.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "B"
  },
  "placement": "tab"
}

B · Result

A source with two tabs: Third tab resultAABBBregion-1

Resolving

No tabs added, removed or moved.

One panel versus the whole area

A is above B. Choose the placement scope explicitly.

A · Shared starting layout

One panel versus the whole area: starting layoutAAAregion-1BBBregion-2

Only A is split.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "A"
  },
  "placement": "right"
}

B · Result

One panel versus the whole area: Right of A resultAAAregion-1BBBregion-2

Resolving

No tabs added, removed or moved.

A full-width bottom panel

A and B begin side by side.

A · Shared starting layout

A full-width bottom panel: starting layoutAAAregion-1BBBregion-2

B keeps its full height.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "A"
  },
  "placement": "below"
}

B · Result

A full-width bottom panel: Below A resultAAAregion-1BBBregion-2

Resolving

No tabs added, removed or moved.

Detach a named preview tab

Preview originally contains Y and Z. First detach Z below Y; then choose where X opens.

A · Shared starting layout

Detach a named preview tab: starting layoutAAAregion-1YYZZZpreview

The original named region remains anchored to Y.

Exact request sequence
[
  {
    "action": "move",
    "tabId": "Z",
    "destination": {
      "id": "Y"
    },
    "placement": "below"
  },
  {
    "action": "open",
    "view": {
      "kind": "artifact",
      "resource": "X",
      "title": "X"
    },
    "destination": {
      "name": "preview",
      "select": "original"
    },
    "placement": "tab"
  }
]

B · Result

Detach a named preview tab: Original resultAAAregion-1YYZZZpreview

Resolving

No tabs added, removed or moved.

X already exists

X is inactive behind Y in the right panel.

A · Shared starting layout

X already exists: starting layoutAAAregion-1XXYYYregion-2

No tab order or geometry changes.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "id": "A"
  },
  "placement": "right"
}

B · Result

X already exists: Activate existing resultAAAregion-1XXYYYregion-2

Resolving

No tabs added, removed or moved.

Preview replacement and pins

Y is an unpinned preview on the right.

A · Shared starting layout

Preview replacement and pins: starting layoutAAAregion-1YYYpreview

X replaces the eligible preview Y.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "role": "preview"
  },
  "placement": "tab",
  "mode": "preview",
  "role": "preview"
}

B · Result

Preview replacement and pins: Replace preview resultAAAregion-1YYYpreview

Resolving

No tabs added, removed or moved.

Contains versus displays

The middle panel contains an inactive artifact Y. The right panel displays artifact Z.

A · Shared starting layout

Contains versus displays: starting layoutAAAregion-1YYBBBregion-2ZZZregion-3

Two regions qualify; no implicit choice.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "content": {
      "kind": "artifact"
    }
  },
  "placement": "tab"
}

B · Result

Contains versus displays: Contains artifact resultAAAregion-1YYBBBregion-2ZZZregion-3

Resolving

No tabs added, removed or moved.

Available space

Two empty panels sit to the right of A; their sizes differ.

A · Shared starting layout

Available space: starting layoutAAAregion-1EmptyEmptyEmptyregion-2EmptyEmptyEmptyregion-3

Choose the nearer empty panel.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "empty": true,
    "from": "A",
    "direction": "right",
    "select": "closest"
  },
  "placement": "tab"
}

B · Result

Available space: Closest empty resultAAAregion-1EmptyEmptyEmptyregion-2EmptyEmptyEmptyregion-3

Resolving

No tabs added, removed or moved.

No destination, ambiguity, and guards

Only A exists. Failures keep the starting layout intact.

A · Shared starting layout

No destination, ambiguity, and guards: starting layoutAAAregion-1

No implicit creation when a named destination is absent.

Exact request
{
  "action": "open",
  "view": {
    "kind": "artifact",
    "resource": "X",
    "title": "X"
  },
  "destination": {
    "name": "preview"
  },
  "placement": "tab"
}

B · Result

No destination, ambiguity, and guards: Missing destination resultAAAregion-1

Resolving

No tabs added, removed or moved.

Destination queries

A destination combines filters with one selection rule. Multiple matches are ambiguous by default, not an invitation to pick an arbitrary panel.

FieldMeaning
idAddress a current region ID, native node ID or tab's containing panel
nameNamed panel family; split/detached members can remain associated
rolePanels hosting tabs with an application role, such as preview
scope, descendantsSearch within a region; set descendants false for direct children
levelPanel by default; area selects a spatial split containing panels
spanningSmallest existing area containing all addressed panels; may include other panels
content, visibilityExact metadata equality; contains includes inactive tabs, displays considers active tabs only
empty, replaceableFilter empty regions or regions with an unpinned preview tab
minWidth, minHeightMinimum dimensions in the inspection's geometry units
from, directionPanels wholly right, left, above or below the source region
selectunique, original, first, last, closest, largest or smallest

First and last use region creation order, not screen order. Original remains anchored to the initial named region; if it closes, original does not silently become another region. Closest uses edge-to-edge rectangle distance. Ties use center-to-center distance, then creation order and stable ID. Sizes use region area. Hidden tabs share their visible panel's bounds; they are not independent spatial candidates.

An area cannot receive a tab directly. Select a child panel or place a new panel beside the whole area. Names are local to one controller session; they are globally addressable within that session. Naming a split area keeps its identity separate from a named family of panels. Layout/session IDs and family creation history are not a cross-reload persistence contract.

Open and replacement policy

ts
const request = {
  action: "open",
  view: { kind: "artifact", resource: "demo", content: { project: "alpha" } },
  destination: { role: "preview", select: "last" },
  mode: "preview",
  role: "preview",
  reuse: "existing",
  fallback: { destination: { id: sourceTabId }, placement: "right" },
} as const;

Resolution order: validate → check expected revision → reuse an existing resource → select destination → use explicit fallback only on no match → select eligible replacement. Ambiguity never triggers a creation fallback.

  • placement: tab (default), right, left, above or below.
  • reuse: existing (default) or new-instance. Existing matches kind and resource; prefer an active matching tab, then the first in current layout order.
  • mode: tab (default) always adds a tab; preview can replace an unpinned preview in the destination with the requested role. Prefer its active eligible tab, otherwise the last eligible tab in that group.
  • name and role: assign destination-family metadata and tab role to new views.
  • fallback: destination plus placement, applied atomically with creation.

Pinning protects replacement, not explicit movement or close. Reusing an already open resource preserves its pin and preview state. A disposal guard applies before replacement and close. If the layout changes while the guard awaits, the request returns stale without committing its mutation.

Agent control loop

ts
import { createLayoutController } from "@dreamlake/uikit";

const controller = createLayoutController({
  getRoot: () => layout.current!.getRoot(),
  replaceRoot: (root) => layout.current!.replaceRoot(root),
  describe: (leaf) => leaf.layout?.view,
  canDispose: (leaf) => canCloseView(leaf.id),
  // Supply live root bounds for pixel-based spatial/size queries.
  bounds: () => ({
    x: 0,
    y: 0,
    width: container.clientWidth,
    height: container.clientHeight,
  }),
});
const snapshot = controller.inspect();
const request = {
  action: "open",
  view: { kind: "preview", resource: "example" },
  destination: { select: "last" },
  ifRevision: snapshot.revision,
} as const;
const plan = controller.resolve(request); // no layout mutation
const result = await controller.apply(request);

Inspect returns a flat region list, active and inactive tabs, safe descriptors, bounds, names, roles, pins and a revision. Without bounds, geometry is normalized to a 100-by-100 area and reported as such. Resolve reports chosen IDs, candidates, effect and reason. Apply re-resolves and returns applied, blocked, stale, not-found, ambiguous or invalid. Agents should inspect after uncertain delivery before retrying a mutating request.

Other requests: activate and close take tabId; pin takes tabId and pinned; name takes destination and name; move takes tabId, destination and placement. All accept an optional ifRevision. Stable tab IDs survive native moves. Replacing content creates a new instance, so the old view is disposed rather than silently retargeted under an unrelated document's state.

The host's createView/updateView callbacks materialize validated resource descriptors. validateView lets resolve reject unsupported resources before any layout mutation. describe must return undefined only for empty placeholders. Keep bodies, credentials, tokens and editor state out of descriptor metadata.

Browser registry and CLI integration

createLayoutRegistry() exposes version 1, register, list and dispatch. Dispatch accepts inspect, resolve and apply. Register each mounted controller with an explicit ID; call its cleanup function on unmount. A host may expose this registry as window.dreamlakeLayouts for trusted browser automation. It installs no remote transport by itself.

DreamLake CLI layout control attaches to an explicitly selected local browser page and layout. DreamLake supplies note, artifact, resource-list and opt-in web-preview descriptors; ordinary web links retain browser navigation.

Source and verification

The examples use layout-examples.ts; the controller test suite executes every branch. Additional tests cover whole-group placement, named lineage after native docking, pinned replacement, save guards, concurrent changes, and invalid requests. The generated UIKit skill includes this reference. This page defines the generic API; DreamLake documents its application policies separately.

Browser evidence

Local browser verification of the documentation's live branches (September 26, 2026):

Preview replacement: removed Y is red and new X is blue.

Detached Z is amber in both snapshots; new X is blue.