@dimina-kit/electron-deck 0.1.0-dev.20260625063832 → 0.1.0-dev.20260629114208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dock-react/dock-view.d.ts +9 -0
- package/dist/dock-react/dock-view.d.ts.map +1 -1
- package/dist/dock-react/index.js +13 -3
- package/dist/dock-react/index.js.map +1 -1
- package/dist/layout/index.js +1 -1
- package/dist/layout/serialize.d.ts.map +1 -1
- package/dist/{layout-oJQ9hind.js → layout-B1t15b_q.js} +103 -73
- package/dist/layout-B1t15b_q.js.map +1 -0
- package/package.json +2 -2
- package/dist/layout-oJQ9hind.js.map +0 -1
|
@@ -27,6 +27,15 @@ export interface DockViewProps {
|
|
|
27
27
|
active: boolean;
|
|
28
28
|
}) => ReactNode;
|
|
29
29
|
bindNativeSlot: (panelId: string, el: HTMLElement | null) => void;
|
|
30
|
+
/**
|
|
31
|
+
* Opt-in: while a `dropPolicy:'reorder-only'` panel is dragged, paint NO drop
|
|
32
|
+
* indicator over a group body (its own edges or any other group) — those are
|
|
33
|
+
* no-op targets, so the highlight is misleading. Default `false` keeps the
|
|
34
|
+
* geometry-only indicator (it paints where the pointer is; the capability
|
|
35
|
+
* gate still rejects the drop). Hosts that want the cleaner "no misleading
|
|
36
|
+
* highlight" UX pass `true`.
|
|
37
|
+
*/
|
|
38
|
+
suppressReorderOnlyDropIndicator?: boolean;
|
|
30
39
|
}
|
|
31
40
|
/**
|
|
32
41
|
* Read the current dock layout epoch (the model revision). Re-renders the caller
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dock-view.d.ts","sourceRoot":"","sources":["../../src/dock-react/dock-view.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EASN,KAAK,SAAS,EACd,MAAM,OAAO,CAAA;AAWd,OAAO,KAAK,EACX,WAAW,EAGX,aAAa,EACb,cAAc,EAGd,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"dock-view.d.ts","sourceRoot":"","sources":["../../src/dock-react/dock-view.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EASN,KAAK,SAAS,EACd,MAAM,OAAO,CAAA;AAWd,OAAO,KAAK,EACX,WAAW,EAGX,aAAa,EACb,cAAc,EAGd,MAAM,oBAAoB,CAAA;AAwB3B,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,WAAW,CAAA;IAClB,QAAQ,EAAE,aAAa,CAAA;IACvB;;;;;;;OAOG;IACH,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,KAAK,SAAS,CAAA;IACzE,cAAc,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,EAAE,WAAW,GAAG,IAAI,KAAK,IAAI,CAAA;IACjE;;;;;;;OAOG;IACH,gCAAgC,CAAC,EAAE,OAAO,CAAA;CAC1C;AAqBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAaD;;;;;;GAMG;AACH,wBAAgB,0BAA0B,CACzC,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,GACzD,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAUrB;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAuJxD;AA4KD;;;;;;;qEAOqE;AACrE,wBAAgB,iBAAiB,CAChC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzB,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,OAAO,GAAE,MAAuB,GAC9B,OAAO,CAaT"}
|
package/dist/dock-react/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as extractPanel, l as setSizes, o as movePanel, s as setActive, t as closePanelForUser, u as splitPanel } from "../layout-
|
|
1
|
+
import { i as extractPanel, l as setSizes, o as movePanel, s as setActive, t as closePanelForUser, u as splitPanel } from "../layout-B1t15b_q.js";
|
|
2
2
|
import { Fragment, createContext, useCallback, useContext, useEffect, useRef, useState } from "react";
|
|
3
3
|
import { Group, Panel, Separator } from "react-resizable-panels";
|
|
4
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -128,6 +128,7 @@ function computeReorderIndex(tabRects, pointerX) {
|
|
|
128
128
|
* tab's `data-deck-tab` attribute (the jsdom seam relies on the latter).
|
|
129
129
|
*/
|
|
130
130
|
var DRAG_PANEL_MIME = "application/x-deck-panel";
|
|
131
|
+
var activeDragPanelId = null;
|
|
131
132
|
/**
|
|
132
133
|
* The current layout EPOCH — the model's revision, bumped once per committed
|
|
133
134
|
* mutation. `DockView` provides it; descendant panels read it via
|
|
@@ -181,7 +182,7 @@ function computeFlexiblePercentages(sizes, constraints) {
|
|
|
181
182
|
return out;
|
|
182
183
|
}
|
|
183
184
|
function DockView(props) {
|
|
184
|
-
const { model, registry, renderDomPanel, bindNativeSlot } = props;
|
|
185
|
+
const { model, registry, renderDomPanel, bindNativeSlot, suppressReorderOnlyDropIndicator } = props;
|
|
185
186
|
const [tree, setTree] = useState(() => model.get());
|
|
186
187
|
const [epoch, setEpoch] = useState(0);
|
|
187
188
|
useEffect(() => {
|
|
@@ -244,7 +245,8 @@ function DockView(props) {
|
|
|
244
245
|
onRedock: handleRedock,
|
|
245
246
|
onClose: handleClose,
|
|
246
247
|
canClose,
|
|
247
|
-
isPanelInTree
|
|
248
|
+
isPanelInTree,
|
|
249
|
+
suppressReorderOnlyDropIndicator: suppressReorderOnlyDropIndicator ?? false
|
|
248
250
|
})
|
|
249
251
|
});
|
|
250
252
|
}
|
|
@@ -580,6 +582,10 @@ function GroupView(props) {
|
|
|
580
582
|
};
|
|
581
583
|
const handleDragOver = (e) => {
|
|
582
584
|
e.preventDefault();
|
|
585
|
+
if (ctx.suppressReorderOnlyDropIndicator && activeDragPanelId !== null && ctx.registry.get(activeDragPanelId)?.dropPolicy === "reorder-only") {
|
|
586
|
+
setDropZone(null);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
583
589
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
584
590
|
setDropZone(computeDropZone({
|
|
585
591
|
width: rect.width,
|
|
@@ -675,6 +681,10 @@ function GroupView(props) {
|
|
|
675
681
|
e.dataTransfer.setData(DRAG_PANEL_MIME, panelId);
|
|
676
682
|
e.dataTransfer.setData("text/plain", panelId);
|
|
677
683
|
e.dataTransfer.effectAllowed = "move";
|
|
684
|
+
activeDragPanelId = panelId;
|
|
685
|
+
},
|
|
686
|
+
onDragEnd: () => {
|
|
687
|
+
activeDragPanelId = null;
|
|
678
688
|
},
|
|
679
689
|
onClick: () => {
|
|
680
690
|
if (!active) ctx.onActivate(node.id, panelId);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../src/dock-react/drag-redock.ts","../../src/dock-react/dock-view.tsx"],"sourcesContent":["/**\n * `drag-redock` — PURE geometry + descriptor layer for tab drag-to-redock.\n *\n * This module is intentionally REACT-FREE and ELECTRON-FREE so it can run under\n * the node `vitest.config.ts` suite (its spec is `drag-redock.test.ts`). It owns\n * two responsibilities, both pure functions of their inputs:\n *\n * 1. GEOMETRY — `computeDropZone(rect, point)` maps a pointer position over a\n * group's rectangle to one of five drop zones (the four edge bands plus the\n * interior `center`). This is the only place edge-band math lives; the React\n * layer feeds it real `getBoundingClientRect` numbers during dragover.\n *\n * 2. DESCRIPTOR — `dropZoneToMutation(zone, dragged, target)` translates a zone\n * into an engine-NEUTRAL `RedockMutation` intent (`move` | `split`). The\n * descriptor only NAMES the intent; the React layer realizes it against the\n * real tree (a split of an EXISTING panel needs extract-then-split, because\n * `splitPanel` throws if the new panel already exists — see the caller).\n *\n * Keeping this split (geometry/descriptor here, engine application in React)\n * means the geometry is exhaustively unit-testable without a DOM, and the React\n * layer stays a thin gesture binding.\n */\n\n/** The five drop zones: four edge bands + the tab-joining interior. */\nexport type DropZone = 'left' | 'right' | 'top' | 'bottom' | 'center'\n\n/**\n * Engine-neutral re-dock INTENT. `move` joins the dragged panel into a tab\n * group; `split` puts it adjacent to a target panel in a new split. This is a\n * pure descriptor — it does NOT itself touch a tree (the caller applies it,\n * extract-then-split'ing for an existing panel).\n */\nexport type RedockMutation =\n\t| { kind: 'move'; panelId: string; destGroupId: string }\n\t| {\n\t\tkind: 'split'\n\t\tatPanelId: string\n\t\tdir: 'row' | 'column'\n\t\tside: 'before' | 'after'\n\t\tnewPanelId: string\n\t}\n\n/**\n * Classify a pointer position (RELATIVE to the rect's top-left; `(0,0)` is the\n * corner) into a drop zone.\n *\n * Band thickness = `edgeFraction * min(width, height)` — a single symmetric band\n * width derived from the SHORTER side so a wide/short panel doesn't get an\n * absurdly fat horizontal band. A point in NO band is `center` (the tab-join\n * interior). A point in TWO bands (a corner) tie-breaks by NORMALIZED distance\n * to each active edge; on an exact tie HORIZONTAL wins. A point OUTSIDE the rect\n * clamps to the nearest edge zone (per-axis; diagonal corners pick the larger\n * overshoot, horizontal breaking ties).\n */\nexport function computeDropZone(\n\trect: { width: number; height: number },\n\tpoint: { x: number; y: number },\n\tedgeFraction = 0.25,\n): DropZone {\n\tconst { width, height } = rect\n\tconst { x, y } = point\n\n\t// ── DEGENERATE / NON-FINITE guard (N1) ─────────────────────────────────\n\t// A zero/negative/non-finite rect has no meaningful edge bands; a non-finite\n\t// point can't be classified. Treat either as the interior (tab-join). Note a\n\t// non-finite WIDTH/HEIGHT (e.g. Infinity) satisfies `> 0` and would otherwise\n\t// slip through — `band = ef * min(Infinity, h)` yields a finite band and the\n\t// point is misclassified as an EDGE zone — so reject finiteness EXPLICITLY.\n\tif (\n\t\t!(width > 0) || !(height > 0)\n\t\t|| !Number.isFinite(width) || !Number.isFinite(height)\n\t\t|| !Number.isFinite(x) || !Number.isFinite(y)\n\t) {\n\t\treturn 'center'\n\t}\n\t// Clamp the band fraction to [0, 0.5] (N1): a fraction > 0.5 makes the left\n\t// and right bands (and top/bottom) OVERLAP, so a point near the right edge of\n\t// a narrow rect would satisfy BOTH `inLeft` and `inRight` and be misread as\n\t// `left`. Capping at 0.5 keeps the two bands disjoint.\n\tconst ef = Number.isFinite(edgeFraction) ? Math.max(0, Math.min(0.5, edgeFraction)) : 0.25\n\n\t// ── OUT-OF-RECT clamp ──────────────────────────────────────────────────\n\t// A pointer dragged past the panel edge has no band membership; clamp it to\n\t// the nearest edge zone. Compute the signed per-axis overshoot: negative =\n\t// out the low side (left/top), positive = out the high side (right/bottom).\n\tconst outX = x < 0 ? x : x > width ? x - width : 0\n\tconst outY = y < 0 ? y : y > height ? y - height : 0\n\tif (outX !== 0 || outY !== 0) {\n\t\tconst magX = Math.abs(outX)\n\t\tconst magY = Math.abs(outY)\n\t\t// Both axes out (a diagonal corner): the axis with the LARGER overshoot\n\t\t// magnitude wins; an exact tie is broken by HORIZONTAL (the x axis).\n\t\tif (magX >= magY) {\n\t\t\tif (magX > 0) return outX < 0 ? 'left' : 'right'\n\t\t}\n\t\t// Only y out, or y overshoot strictly larger.\n\t\treturn outY < 0 ? 'top' : 'bottom'\n\t}\n\n\t// ── BAND membership (in-rect) ──────────────────────────────────────────\n\tconst band = ef * Math.min(width, height)\n\tconst inLeft = x < band\n\tconst inRight = x > width - band\n\tconst inTop = y < band\n\tconst inBottom = y > height - band\n\n\tconst activeHoriz = inLeft || inRight\n\tconst activeVert = inTop || inBottom\n\n\t// No band => interior => join the tab group.\n\tif (!activeHoriz && !activeVert) return 'center'\n\n\t// Single-axis band => that edge zone directly.\n\tif (activeHoriz && !activeVert) return inLeft ? 'left' : 'right'\n\tif (activeVert && !activeHoriz) return inTop ? 'top' : 'bottom'\n\n\t// ── CORNER tie-break (both axes active) ────────────────────────────────\n\t// Pick the edge with the SMALLER normalized distance. Only the ACTIVE\n\t// horizontal edge and ACTIVE vertical edge can compete (left vs right and\n\t// top vs bottom can't both be active for a sane rect). On an exact tie,\n\t// HORIZONTAL wins (`<=` on the horizontal distance).\n\tconst dHoriz = inLeft ? x / width : (width - x) / width\n\tconst dVert = inTop ? y / height : (height - y) / height\n\tif (dHoriz <= dVert) return inLeft ? 'left' : 'right'\n\treturn inTop ? 'top' : 'bottom'\n}\n\n/**\n * Translate a drop zone into an engine-neutral re-dock descriptor.\n *\n * center => move the dragged panel into the target's tab group.\n * left/right => split the target panel horizontally (row), dragged before/after.\n * top/bottom => split the target panel vertically (column), dragged before/after.\n */\nexport function dropZoneToMutation(\n\tzone: DropZone,\n\tdragged: string,\n\ttarget: { groupId: string; panelId: string },\n): RedockMutation {\n\tif (zone === 'center') {\n\t\treturn { kind: 'move', panelId: dragged, destGroupId: target.groupId }\n\t}\n\tconst dir: 'row' | 'column' = zone === 'left' || zone === 'right' ? 'row' : 'column'\n\tconst side: 'before' | 'after' = zone === 'left' || zone === 'top' ? 'before' : 'after'\n\treturn { kind: 'split', atPanelId: target.panelId, dir, side, newPanelId: dragged }\n}\n\n/**\n * A re-dock is a NO-OP when it would not change the tree:\n *\n * 1. The drop targets the dragged panel ITSELF (`dragged === target.panelId`),\n * in ANY zone — you can neither tab a panel onto its own tab nor split a\n * panel around itself. Crucially this also guards the SPLIT case: without\n * it, `extractPanel(dragged)` then `splitPanel(atPanelId = dragged)` removes\n * the very anchor it then splits at and THROWS (the self-collapse / single-\n * panel-self-split bug).\n * 2. A `center` drop into a group the dragged panel ALREADY lives in\n * (`draggedGroupId === target.groupId`) — `movePanel` would re-append it and\n * bump the revision for no visible change (churn).\n *\n * A SPLIT onto a DIFFERENT panel of the dragged panel's own group is still a real\n * re-dock (it splits the group around that sibling), so it is NOT a no-op.\n *\n * `draggedGroupId` is the id of the tab group currently holding `dragged`\n * (`undefined` if it is not in the tree).\n */\nexport function isNoopRedock(\n\tdragged: string,\n\tdraggedGroupId: string | undefined,\n\ttarget: { groupId: string; panelId: string },\n\tzone: DropZone,\n): boolean {\n\tif (dragged === target.panelId) return true\n\tif (zone === 'center' && draggedGroupId !== undefined && draggedGroupId === target.groupId) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n/**\n * Map a pointer x-position over a horizontal tab strip to an insertion index for\n * a within-strip REORDER (the `dropPolicy:'reorder-only'` gesture). The strip is\n * the dragged tab's own group; `tabRects` are the tab buttons' rects in visual\n * order (each `left` is the rect's left edge, `width` its width). The index is\n * the count of tabs whose MIDPOINT the pointer has passed: the LEFT half of a tab\n * inserts BEFORE it, the RIGHT half (and the exact midpoint) inserts AFTER it. A\n * pointer left of the first tab → 0; past the last tab → `tabRects.length`. Pure\n * (no DOM): the caller measures the rects and passes the pointer x. An empty\n * strip or a non-finite pointer → 0.\n */\nexport function computeReorderIndex(\n\ttabRects: readonly { left: number; width: number }[],\n\tpointerX: number,\n): number {\n\tif (!Number.isFinite(pointerX)) return 0\n\tlet index = 0\n\tfor (const rect of tabRects) {\n\t\tconst midpoint = rect.left + rect.width / 2\n\t\tif (pointerX >= midpoint) index += 1\n\t\telse break\n\t}\n\treturn index\n}\n","/**\n * `<DockView>` — React renderer for a layout-as-data tree.\n *\n * Pure function of the current model snapshot (+ registry + the two host\n * callbacks). It owns NO layout state: it subscribes to the `LayoutModel`,\n * keeps the latest tree in component state, and re-renders on every emission.\n * All structural targeting is via STABLE `data-*` attributes — see the\n * contract doc-block in `dock-view.test.tsx`.\n *\n * This file lives under `src/dock-react/` (NOT `src/layout/`), so importing\n * react here does not violate the pure-TS layout boundary.\n */\nimport {\n\tcreateContext,\n\tFragment,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n\ttype DragEvent,\n\ttype ReactNode,\n} from 'react'\nimport { Group, Panel, Separator } from 'react-resizable-panels'\nimport type { GroupImperativeHandle, Layout } from 'react-resizable-panels'\nimport {\n\tclosePanelForUser,\n\textractPanel,\n\tmovePanel,\n\tsetActive,\n\tsetSizes,\n\tsplitPanel,\n} from '../layout/index.js'\nimport type {\n\tLayoutModel,\n\tLayoutNode,\n\tLayoutTree,\n\tPanelRegistry,\n\tSizeConstraint,\n\tSplitNode,\n\tTabGroupNode,\n} from '../layout/index.js'\nimport {\n\tcomputeDropZone,\n\tcomputeReorderIndex,\n\tdropZoneToMutation,\n\tisNoopRedock,\n\ttype DropZone,\n} from './drag-redock.js'\n\n/**\n * The dataTransfer MIME under which a drag carries the dragged panel id. A\n * custom type (vs `text/plain`) keeps deck drags from being confused with\n * arbitrary text drops; the panel id also stays recoverable from the source\n * tab's `data-deck-tab` attribute (the jsdom seam relies on the latter).\n */\nconst DRAG_PANEL_MIME = 'application/x-deck-panel'\n\nexport interface DockViewProps {\n\tmodel: LayoutModel\n\tregistry: PanelRegistry\n\t/**\n\t * Render a DOM panel's body. `opts.active` is `true` iff `panelId` is its tab\n\t * group's currently-active panel. Under DOM-panel keepalive every DOM panel in\n\t * a group is rendered (the inactive ones hidden), so this callback is invoked\n\t * for ALL of them and `opts.active` is RE-EVALUATED on every activation change\n\t * WITHOUT remounting the kept-alive subtree — letting a host run on-activation\n\t * side effects (e.g. data refresh) off the false→true `active` edge.\n\t */\n\trenderDomPanel: (panelId: string, opts: { active: boolean }) => ReactNode\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n}\n\n/**\n * The current layout EPOCH — the model's revision, bumped once per committed\n * mutation. `DockView` provides it; descendant panels read it via\n * `useDockLayoutEpoch`.\n *\n * Its reason to exist: a native-view overlay anchored inside a dock panel\n * (a `WebContentsView` tracking its slot's geometry) re-publishes its bounds\n * only on GEOMETRY events (ResizeObserver / window-resize / splitter-drag). A\n * layout mutation that REORDERS a slot without resizing it — flipping a\n * fixed-width simulator column left↔right, moving a region — produces NO such\n * event (a same-size flex reorder fires nothing), so the overlay would freeze\n * at its old position. The epoch is the layout layer's explicit \"something\n * moved\" signal: a panel hosting a native overlay re-measures (e.g. pulses its\n * view-anchor) when the epoch changes, catching the translate the browser never\n * reports. Default 0 (outside a provider): the consuming effect runs once on\n * mount, which is harmless.\n */\nconst LayoutEpochContext = createContext<number>(0)\n\n/**\n * Read the current dock layout epoch (the model revision). Re-renders the caller\n * whenever a layout mutation commits. Keyed into a panel's effect deps, it lets\n * a native-overlay host re-measure on a reorder that fires no geometry event.\n * Returns 0 when used outside a `<DockView>`.\n */\nexport function useDockLayoutEpoch(): number {\n\treturn useContext(LayoutEpochContext)\n}\n\n/** Normalize raw split weights to percentages summing ~100 for `defaultSize`. */\nfunction toPercentages(sizes: readonly number[]): number[] {\n\tconst total = sizes.reduce((a, b) => a + (b > 0 ? b : 0), 0)\n\tif (total <= 0) {\n\t\t// Degenerate weights → distribute evenly.\n\t\tconst even = sizes.length > 0 ? 100 / sizes.length : 100\n\t\treturn sizes.map(() => even)\n\t}\n\treturn sizes.map((s) => ((s > 0 ? s : 0) / total) * 100)\n}\n\n/**\n * Compute the `defaultSize` percentage for each FLEXIBLE child, keyed by its\n * ORIGINAL child index. Px-sized children (a non-null `constraint` — `fixedPx`\n * locked OR `minPx` floored) are EXCLUDED from the pool: their size is px, not a\n * weight, so it must never pollute the flexible siblings' normalization (FIX E1).\n * `constraints[i]` non-null ⇒ child i is px-sized and absent from the returned map.\n */\nexport function computeFlexiblePercentages(\n\tsizes: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n): Map<number, number> {\n\tconst flexibleIndices = sizes\n\t\t.map((_, i) => i)\n\t\t.filter((i) => (constraints?.[i] ?? null) === null)\n\tconst pct = toPercentages(flexibleIndices.map((i) => sizes[i] ?? 0))\n\tconst out = new Map<number, number>()\n\tflexibleIndices.forEach((origIndex, j) => {\n\t\tout.set(origIndex, pct[j]!)\n\t})\n\treturn out\n}\n\nexport function DockView(props: DockViewProps): ReactNode {\n\tconst { model, registry, renderDomPanel, bindNativeSlot } = props\n\n\t// Snapshot the canonical tree; re-render on every external emission. The\n\t// `epoch` mirrors the model revision (0 before the first apply) and is\n\t// provided to descendant panels via `LayoutEpochContext` so a native-overlay\n\t// host can re-measure on a reorder that fires no geometry event.\n\tconst [tree, setTree] = useState<LayoutTree>(() => model.get())\n\tconst [epoch, setEpoch] = useState<number>(0)\n\n\tuseEffect(() => {\n\t\t// Re-sync immediately in case the model changed between the initial\n\t\t// `useState` read and effect commit, then track future emissions.\n\t\tsetTree(model.get())\n\t\tconst unsubscribe = model.subscribe((snap) => {\n\t\t\tsetTree(snap.tree)\n\t\t\tsetEpoch(snap.revision)\n\t\t})\n\t\treturn unsubscribe\n\t}, [model])\n\n\tconst handleActivate = useCallback(\n\t\t(groupId: string, panelId: string) => {\n\t\t\tmodel.apply((t) => setActive(t, groupId, panelId))\n\t\t},\n\t\t[model],\n\t)\n\n\t// Close write-back: every tab close funnels through the capability-aware\n\t// user action. The generic `closePanel` mutation remains available to\n\t// programmatic layout transforms.\n\tconst handleClose = useCallback(\n\t\t(panelId: string) => {\n\t\t\tmodel.apply((t) => closePanelForUser(t, panelId, registry))\n\t\t},\n\t\t[model, registry],\n\t)\n\n\t// Total panels across the WHOLE tree. The close affordance is suppressed only\n\t// when the entire layout has a single panel left (closing it would no-op);\n\t// a multi-panel single group still shows closes. GroupView sees only its own\n\t// node, so we compute the global count here and thread `canClose` down.\n\tconst canClose = countPanels(tree.root) > 1\n\n\t// Panel-id membership of the CURRENT tree (M2). A drop's payload must be a\n\t// panel that actually lives in the tree, not merely one that is registered —\n\t// see `handleDrop`. Derived from the snapshot the component already holds.\n\tconst isPanelInTree = useCallback(\n\t\t(panelId: string) => findPanelGroupId(tree.root, panelId) !== undefined,\n\t\t[tree],\n\t)\n\n\t// Resize write-back: a drag commit (or the `__deckApplyLayout` seam) funnels\n\t// new per-split weights here, which apply `setSizes` to the canonical model.\n\tconst handleApplyLayout = useCallback(\n\t\t(splitId: string, weights: number[]) => {\n\t\t\tmodel.apply((t) => setSizes(t, splitId, weights))\n\t\t},\n\t\t[model],\n\t)\n\n\t// Drag-to-redock commit. A tab dragged onto `groupId`'s `zone` (the GROUP seam\n\t// or a real dragover) lands here. `activePanelId` is the group's natural split\n\t// anchor (the visible body the user dropped onto). We translate the zone to an\n\t// engine-neutral descriptor, skip true no-ops (drop onto own tab center), and\n\t// apply the descriptor:\n\t// - move => single `movePanel` (joins the target tab group).\n\t// - split => extract-then-split COMPOSED in ONE `model.apply` so the whole\n\t// re-dock is a single atomic emission (one subscriber notification, one\n\t// re-render): `splitPanel` throws if the dragged panel already exists, so\n\t// an EXISTING panel must be `extractPanel`'d first, then split against the\n\t// target. Doing both inside one `apply` keeps the tree from ever being\n\t// observed in the transient extracted state.\n\tconst handleRedock = useCallback(\n\t\t(\n\t\t\tgroupId: string,\n\t\t\tactivePanelId: string,\n\t\t\tdraggedPanelId: string,\n\t\t\tzone: DropZone,\n\t\t\treorderIndex?: number,\n\t\t) => {\n\t\t\tconst target = { groupId, panelId: activePanelId }\n\t\t\t// The dragged panel's CURRENT group — needed to skip a center-drop back\n\t\t\t// into its own group (M2) and (via `dragged === target.panelId`) a\n\t\t\t// self-split (M1).\n\t\t\tconst draggedGroupId = findPanelGroupId(model.get().root, draggedPanelId)\n\n\t\t\t// ── PanelCapabilities gate (GOAL A source): a `draggable:false` panel is\n\t\t\t// a locked STRUCTURAL panel — it can never be torn into another region.\n\t\t\t// UI drag-start already refuses to lift it, but the imperative drop seam\n\t\t\t// bypasses that, so reject a locked dragged source here too (defense in\n\t\t\t// depth) — its position in the tree is fixed. ──\n\t\t\tif (registry.get(draggedPanelId)?.draggable === false) return\n\n\t\t\t// ── PanelCapabilities gate (GOAL A target): a group whose ACTIVE panel is\n\t\t\t// `draggable:false` is a locked drop ANCHOR — nothing may join or split\n\t\t\t// against it, in any zone. Checked before the no-op/reorder logic so a\n\t\t\t// locked simulator/editor can never absorb another panel. ──\n\t\t\tif (registry.get(activePanelId)?.draggable === false) return\n\n\t\t\t// ── PanelCapabilities gate (GOAL B): a `reorder-only` dragged panel may\n\t\t\t// ONLY reorder WITHIN its own group. It never leaves the group (a center\n\t\t\t// drop into another group) and never edge-splits (any edge zone, own or\n\t\t\t// other group). The one motion it permits — a center drop into its OWN\n\t\t\t// group — must OVERRIDE `isNoopRedock` (which would swallow it as churn).\n\t\t\tif (registry.get(draggedPanelId)?.dropPolicy === 'reorder-only') {\n\t\t\t\tif (draggedGroupId === undefined || draggedGroupId !== groupId) return\n\t\t\t\tif (zone !== 'center') return\n\t\t\t\t// Anchor the reorder on the pointer-derived index when the gesture\n\t\t\t\t// supplies one (real drag), else on the active anchor's CURRENT index\n\t\t\t\t// (the imperative seam carries no pointer).\n\t\t\t\tconst group = findGroupById(model.get().root, groupId)\n\t\t\t\tconst index = reorderIndex ?? (group ? group.panels.indexOf(activePanelId) : undefined)\n\t\t\t\tmodel.apply((t) => movePanel(t, draggedPanelId, { groupId, index }))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (isNoopRedock(draggedPanelId, draggedGroupId, target, zone)) return\n\t\t\tconst mutation = dropZoneToMutation(zone, draggedPanelId, target)\n\t\t\tif (mutation.kind === 'move') {\n\t\t\t\tmodel.apply((t) => movePanel(t, mutation.panelId, { groupId: mutation.destGroupId }))\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Atomic extract-then-split for an existing dragged panel. Defensive\n\t\t\t// guard (M1): if extracting the dragged panel removed the split anchor,\n\t\t\t// abort instead of letting `splitPanel` throw on a missing anchor.\n\t\t\tmodel.apply((t) => {\n\t\t\t\tconst { tree } = extractPanel(t, mutation.newPanelId)\n\t\t\t\tif (findPanelGroupId(tree.root, mutation.atPanelId) === undefined) return t\n\t\t\t\treturn splitPanel(tree, mutation.atPanelId, mutation.dir, mutation.newPanelId, mutation.side)\n\t\t\t})\n\t\t},\n\t\t[model, registry],\n\t)\n\n\treturn (\n\t\t<LayoutEpochContext.Provider value={epoch}>\n\t\t\t{renderNode(tree.root, {\n\t\t\t\tregistry,\n\t\t\t\trenderDomPanel,\n\t\t\t\tbindNativeSlot,\n\t\t\t\tonActivate: handleActivate,\n\t\t\t\tonApplyLayout: handleApplyLayout,\n\t\t\t\tonRedock: handleRedock,\n\t\t\t\tonClose: handleClose,\n\t\t\t\tcanClose,\n\t\t\t\tisPanelInTree,\n\t\t\t})}\n\t\t</LayoutEpochContext.Provider>\n\t)\n}\n\n/** A split wrapper element augmented with the resize write-back seam plus the\n * rrp Group imperative handle (M1 model→view sync). Hosts/tests reach the live\n * split layout through `__deckGroupApi` the same way they reach the write-back\n * through `__deckApplyLayout`. */\ntype DeckSplitElement = HTMLDivElement & {\n\t__deckApplyLayout?: (weights: number[]) => void\n\t__deckGroupApi?: GroupImperativeHandle\n}\n\n/** A group wrapper element augmented with the drop-handling seam. Mirrors the\n * `__deckApplyLayout` discipline on split elements: an imperative hook the\n * gesture (or a unit test) calls to commit a re-dock against the model. */\ntype DeckGroupElement = HTMLDivElement & {\n\t__deckHandleDrop?: (draggedPanelId: string, zone: DropZone) => void\n}\n\ninterface RenderContext {\n\tregistry: PanelRegistry\n\trenderDomPanel: (panelId: string, opts: { active: boolean }) => ReactNode\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n\tonActivate: (groupId: string, panelId: string) => void\n\tonApplyLayout: (splitId: string, weights: number[]) => void\n\tonRedock: (\n\t\tgroupId: string,\n\t\tactivePanelId: string,\n\t\tdraggedPanelId: string,\n\t\tzone: DropZone,\n\t\t/** Pointer-derived insertion index for a within-group reorder\n\t\t * (`dropPolicy:'reorder-only'`). Omitted by the imperative seam, which\n\t\t * falls back to the active anchor's current index. */\n\t\treorderIndex?: number,\n\t) => void\n\tonClose: (panelId: string) => void\n\t/** False when the whole tree has a single panel — suppresses every close\n\t * button so the layout can't be emptied (closePanel would no-op anyway). */\n\tcanClose: boolean\n\t/** True iff `panelId` is present in the CURRENT layout tree (M2). Registry\n\t * membership is NOT enough: a panel can be registered but absent from the tree\n\t * (closed out / never docked), and driving a re-dock on it makes the mutation\n\t * layer throw `panel not found`. A drop carrying such an id must be a no-op. */\n\tisPanelInTree: (panelId: string) => boolean\n}\n\n/** The id of the tab group currently holding `panelId`, or `undefined` if the\n * panel is not in the tree. Used by `handleRedock` to detect a drop back into\n * the dragged panel's own group (M2) and to guard a vanished split anchor (M1). */\nfunction findPanelGroupId(node: LayoutNode, panelId: string): string | undefined {\n\tif (node.kind === 'tabs') {\n\t\treturn node.panels.includes(panelId) ? node.id : undefined\n\t}\n\tfor (const child of node.children) {\n\t\tconst found = findPanelGroupId(child, panelId)\n\t\tif (found !== undefined) return found\n\t}\n\treturn undefined\n}\n\n/** The tab-group node with `groupId`, or `undefined`. Used by `handleRedock` to\n * read the live `panels` order of a reorder target (to anchor a same-group\n * reorder on the active panel's current index when the gesture carries no\n * pointer-derived index — e.g. the `__deckHandleDrop` test seam). */\nfunction findGroupById(node: LayoutNode, groupId: string): TabGroupNode | undefined {\n\tif (node.kind === 'tabs') return node.id === groupId ? node : undefined\n\tfor (const child of node.children) {\n\t\tconst found = findGroupById(child, groupId)\n\t\tif (found !== undefined) return found\n\t}\n\treturn undefined\n}\n\n/** Total panels anywhere in the tree. Drives the last-panel close suppression:\n * a GroupView only knows its own node, so DockView computes this global count\n * once and threads the resulting `canClose` boolean down. */\nfunction countPanels(node: LayoutNode): number {\n\tif (node.kind === 'tabs') return node.panels.length\n\treturn node.children.reduce((sum, child) => sum + countPanels(child), 0)\n}\n\nfunction renderNode(node: LayoutNode, ctx: RenderContext): ReactNode {\n\treturn node.kind === 'split'\n\t\t? renderSplit(node, ctx)\n\t\t: renderGroup(node, ctx)\n}\n\n/** A split is a stateful component (it holds the rrp Group imperative ref + runs\n * the M1 model→view sync effect), so render it via JSX with a STABLE key so\n * React preserves that ref / its kept-alive subtree across model emissions\n * rather than remounting on every snapshot. Mirrors `renderGroup`/`GroupView`. */\nfunction renderSplit(node: SplitNode, ctx: RenderContext): ReactNode {\n\treturn <SplitView key={node.id} node={node} ctx={ctx} />\n}\n\ninterface SplitViewProps {\n\tnode: SplitNode\n\tctx: RenderContext\n}\n\n/** Epsilon (in percentage points) within which two split layouts are treated as\n * equivalent. Guards BOTH sides of the model↔view loop: we skip pushing a\n * `setLayout` when the live layout already matches the target, and skip the\n * `onLayoutChanged` write-back when the incoming layout matches the model — so\n * `setLayout`→`onLayoutChanged`→write-back→re-sync cannot loop. */\nconst LAYOUT_EPSILON = 0.5\n\n/** TIGHT tolerance (percentage points) for the BASIS-NORMALIZED flexible-ratio\n * compare in `handleLayoutChanged`. We normalize the incoming layout's flexible\n * subset to ratios summing to 100 and compare them against the model's flexible\n * ratios (`computeFlexiblePercentages`, also summing to 100). If they match\n * within this tolerance the `onLayoutChanged` is either our own `setLayout` echo\n * OR a ratio-preserving spontaneous re-measure (mount / fixed-px re-pin /\n * container resize) — SKIP the write-back. If they differ it is a genuine user\n * resize (pointer OR keyboard) — WRITE BACK.\n *\n * Set to ~0.1pp: large enough to absorb rrp's ~3-decimal float noise on an echo,\n * yet FAR below a real drag's delta. R1's \"sub-0.5%\" drag moves a panel ~0.33pp\n * of the container; normalized over the two-flexible-child subset that is ~0.66pp\n * of ratio — comfortably above 0.1, so it is NOT mistaken for an echo and is\n * written back.\n *\n * CAVEAT: this is a flexible-RATIO tolerance (the flexible subset normalized to\n * sum 100), NOT a container-%. It is safe against rrp's 3-decimal echo noise.\n * In a pathologically WIDE split (~≥10 flexible children) a single arrow-key\n * nudge (±~5% of the container on one child) can, once normalized over many\n * flexible siblings, fall BELOW 0.1pp of ratio and be skipped — exotic and\n * self-healing (the next, larger resize writes back). If that ever matters,\n * scale the tolerance down by the flexible child count. */\nconst FLEX_RATIO_TOLERANCE = 0.1\n\n/**\n * The minimum weight a FLEXIBLE child may hold, given how many flexible children\n * share the split (Bug #1). A flexible `<Panel>` has no rrp `minSize` by default\n * (rrp floor is 0%), so a user can drag it to ~0 width; the resize write-back\n * would then persist a ~0 weight and the panel comes back invisible/stuck.\n *\n * The floor is `min(1, floor(90 / flexCount))` — i.e. ~1 weight unit (a flexible\n * split's weights are normalized to percentages downstream, so ~1 reads as ~1%\n * of the flexible pool). With any realistic flexible count this is exactly 1; the\n * `min(1, …)` only matters for a hypothetical 90+ flexible-child split. It is the\n * SAME value used for the rrp `minSize` (A) and the write-back clamp (B) so the\n * two defenses never disagree.\n */\nfunction flexibleFloor(flexCount: number): number {\n\t// `floor(90 / flexCount)` is ~1 for any realistic count, but goes to 0 once\n\t// flexCount > 90 — which would silently DEFEAT the floor (a 0 minSize / 0 clamp\n\t// is no floor at all). Clamp into [MIN_POSITIVE_FLOOR, 1] so the floor is always\n\t// a positive percentage even in a pathologically wide split.\n\tconst MIN_POSITIVE_FLOOR = 0.5\n\treturn Math.min(1, Math.max(MIN_POSITIVE_FLOOR, Math.floor(90 / Math.max(1, flexCount))))\n}\n\n/**\n * Clamp the FLEXIBLE entries of a full-length weights array up to `floor` (Bug #1\n * defense B). Px-sized children (a non-null constraint) are left untouched — their\n * `sizes[i]` is a preserved placeholder, not a live weight. Returns a new array;\n * a weight already ≥ floor is kept verbatim so a healthy ratio is undisturbed.\n */\nfunction clampFlexibleWeights(\n\tweights: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n): number[] {\n\tconst flexCount = weights.filter((_, i) => (constraints?.[i] ?? null) === null).length\n\tconst floor = flexibleFloor(flexCount)\n\treturn weights.map((w, i) => {\n\t\tif ((constraints?.[i] ?? null) !== null) return w // px child — leave as-is\n\t\treturn Number.isFinite(w) && w >= floor ? w : floor\n\t})\n}\n\n/** Are two panelId→percentage maps equivalent within `epsilon` percentage\n * points? Both maps must cover EXACTLY the `ids` key set — a missing key OR an\n * extra key (a key present in `a`/`b` but absent from `ids`) counts as NOT\n * equivalent so the sync is not falsely suppressed. A non-finite value\n * (NaN/±Infinity) is likewise NOT equivalent — `typeof NaN === 'number'` and\n * `Math.abs(NaN - x) > eps` is `false`, so without the `Number.isFinite` guard a\n * NaN would slip through as \"equivalent\" and wrongly suppress a legitimate\n * sync/write-back (N1). EXPORTED (pure) for direct unit coverage. */\nexport function layoutsEquivalent(\n\ta: Record<string, number>,\n\tb: Record<string, number>,\n\tids: readonly string[],\n\tepsilon: number = LAYOUT_EPSILON,\n): boolean {\n\t// Exact key-set match: any key in `a` or `b` that is not in `ids` (extra key)\n\t// makes the maps non-equivalent. `ids` is the authoritative compared set.\n\tconst idSet = new Set(ids)\n\tfor (const k of Object.keys(a)) if (!idSet.has(k)) return false\n\tfor (const k of Object.keys(b)) if (!idSet.has(k)) return false\n\tfor (const id of ids) {\n\t\tconst av = a[id]\n\t\tconst bv = b[id]\n\t\tif (!Number.isFinite(av) || !Number.isFinite(bv)) return false\n\t\tif (Math.abs((av as number) - (bv as number)) > epsilon) return false\n\t}\n\treturn true\n}\n\n/**\n * Build the panel-ID→PERCENTAGE map for an imperative `setLayout`, given the\n * model's full-length raw `sizes` (per child) and `constraints`:\n *\n * - FIXED (px-pinned) children keep their CURRENT measured percentage (read\n * from the live `getLayout()`); they are NOT derived from weights, so a\n * flexible-weights change never disturbs their pixel lock.\n * - The REMAINING percentage (100 − Σ fixed%) is distributed across the\n * FLEXIBLE children in proportion to their weights.\n *\n * Returns `null` when the map can't be built faithfully (e.g. `live` is empty —\n * jsdom's stub — or a fixed child's live % is missing), so the caller skips the\n * `setLayout` rather than pushing a corrupt total. The result always sums to\n * ~100 over all children.\n */\nfunction buildSetLayoutMap(\n\tchildIds: readonly string[],\n\tsizes: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n\tlive: Record<string, number>,\n): Record<string, number> | null {\n\tconst isFixedAt = (i: number): boolean => (constraints?.[i] ?? null) !== null\n\n\t// Sum the fixed children's CURRENT live percentages (preserve their px lock).\n\tlet fixedTotal = 0\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif (!isFixedAt(i)) continue\n\t\tconst livePct = live[childIds[i]!]\n\t\t// Without a measured live % for a fixed child we cannot preserve its px\n\t\t// lock faithfully — bail so we don't corrupt the fixed child.\n\t\tif (typeof livePct !== 'number') return null\n\t\tfixedTotal += livePct\n\t}\n\n\tconst remaining = Math.max(0, 100 - fixedTotal)\n\n\t// Flexible weight pool.\n\tlet flexWeightTotal = 0\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif (isFixedAt(i)) continue\n\t\tconst w = sizes[i] ?? 0\n\t\tflexWeightTotal += w > 0 ? w : 0\n\t}\n\tconst flexibleCount = childIds.length - childIds.filter((_, i) => isFixedAt(i)).length\n\n\tconst out: Record<string, number> = {}\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tconst id = childIds[i]!\n\t\tif (isFixedAt(i)) {\n\t\t\tout[id] = live[id]!\n\t\t\tcontinue\n\t\t}\n\t\tif (flexWeightTotal <= 0) {\n\t\t\t// Degenerate flexible weights → split the remaining space evenly.\n\t\t\tout[id] = flexibleCount > 0 ? remaining / flexibleCount : remaining\n\t\t\tcontinue\n\t\t}\n\t\tconst w = sizes[i] ?? 0\n\t\tout[id] = (remaining * (w > 0 ? w : 0)) / flexWeightTotal\n\t}\n\treturn out\n}\n\n/**\n * Extract an rrp `onLayoutChanged` map's FLEXIBLE subset (skipping fixed-px\n * children) in child order and NORMALIZE it by its own sum to ratios summing to\n * ~100 — the basis `computeFlexiblePercentages` already produces for the model,\n * so the two are directly comparable. Returns the original child `indices`\n * alongside the `ratios`. Null when the map is malformed (a flexible id is\n * missing / non-finite) or there is no flexible child — the caller then never\n * writes back from it.\n */\nfunction incomingFlexRatios(\n\tchildIds: readonly string[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n\tlayout: Record<string, number>,\n): { indices: number[]; ratios: number[] } | null {\n\tconst indices: number[] = []\n\tconst raw: number[] = []\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif ((constraints?.[i] ?? null) !== null) continue\n\t\tconst v = layout[childIds[i]!]\n\t\tif (typeof v !== 'number' || !Number.isFinite(v)) return null\n\t\tindices.push(i)\n\t\traw.push(v > 0 ? v : 0)\n\t}\n\tif (indices.length === 0) return null\n\treturn { indices, ratios: toPercentages(raw) }\n}\n\nfunction SplitView(props: SplitViewProps): ReactNode {\n\tconst { node, ctx } = props\n\tconst orientation = node.orientation === 'row' ? 'horizontal' : 'vertical'\n\n\t// A child is FIXED (px-pinned) iff it carries a non-null constraint. Fixed\n\t// children are excluded from the flexible-percentage pool: their px panel does\n\t// not participate in weight-based sizing, so polluting `toPercentages` with\n\t// their weight would skew the flexible siblings' defaultSize.\n\t// `computeFlexiblePercentages` therefore normalizes percentages over the\n\t// FLEXIBLE indices only and maps them back by index; fixed children keep their\n\t// px defaultSize/min/max. (Both write-back seams derive fixed-ness from\n\t// `nodeRef.current.constraints` directly — see `handleLayoutChanged` and\n\t// `__deckApplyLayout` — so no render-closure `isFixed` helper is needed here.)\n\tconst percentageByIndex = computeFlexiblePercentages(node.sizes, node.constraints)\n\n\t// Bug #1 defense A: the minimum % a flexible child may shrink to. rrp's default\n\t// flexible `minSize` is 0% (a panel can be dragged to nothing); this floors it\n\t// so the simulator/editor can never be pulled to 0 width. Unitless string =\n\t// percentage (same convention as `defaultSize` above; a px `minSize` next to a\n\t// %-`defaultSize` is misparsed by rrp). The floor is keyed on the COUNT of\n\t// flexible children in THIS split (the same value the write-back clamp uses).\n\tconst flexCount = node.children.filter((_, i) => (node.constraints?.[i] ?? null) === null).length\n\tconst flexMinSize = String(flexibleFloor(flexCount))\n\n\t// ── M1 model→view sync plumbing ────────────────────────────────────────\n\t// The rrp Group's imperative handle (getLayout/setLayout) — the seam through\n\t// which the model becomes the source of truth for the visible split ratio.\n\tconst groupRef = useRef<GroupImperativeHandle | null>(null)\n\t// Keep the latest node reachable from the imperative ref + drag callbacks\n\t// without re-binding the ref on every render. The sync effect reads the\n\t// freshest `node` directly (it re-runs when `node.sizes` change), so it does\n\t// not go stale; this ref is for the imperative seams that capture once.\n\tconst nodeRef = useRef(node)\n\tnodeRef.current = node\n\n\tconst items: ReactNode[] = []\n\tnode.children.forEach((child, i) => {\n\t\tif (i > 0) {\n\t\t\titems.push(\n\t\t\t\t<Separator\n\t\t\t\t\tkey={`handle-${i}`}\n\t\t\t\t\tdata-deck-resize-handle=\"\"\n\t\t\t\t/>,\n\t\t\t)\n\t\t}\n\t\t// PIXEL-sized children (`fixedPx`/`minPx`) are sized in px, NOT weights, so\n\t\t// they are excluded from the flexible % pool (`computeFlexiblePercentages`)\n\t\t// and never normalized against weight-sized siblings. rrp needs CONSISTENT\n\t\t// units per panel — a px `minSize` on a %-`defaultSize` panel is misparsed\n\t\t// (the panel grabs the whole region), so a px floor MUST pair with a px\n\t\t// `defaultSize`.\n\t\tconst constraint = node.constraints?.[i] ?? null\n\t\tif (constraint?.fixedPx != null) {\n\t\t\t// `fixedPx` LOCKS the child: min===max + `preserve-pixel-size` keeps it\n\t\t\t// fixed while flexible siblings absorb group resizes.\n\t\t\tconst px = `${constraint.fixedPx}px`\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={px}\n\t\t\t\t\tminSize={px}\n\t\t\t\t\tmaxSize={px}\n\t\t\t\t\tgroupResizeBehavior=\"preserve-pixel-size\"\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t\telse if (constraint?.minPx != null) {\n\t\t\t// `minPx` FLOORS the child at a pixel minimum but leaves it DRAGGABLE\n\t\t\t// above it: px `defaultSize`+`minSize` (starts AT the floor, can't shrink\n\t\t\t// past it), NO `maxSize` so the user can widen it. `preserve-pixel-size`\n\t\t\t// puts the panel in rrp's pixel mode — WITHOUT it rrp misparses a px\n\t\t\t// `defaultSize` next to %-sized siblings and the panel grabs ~80% of the\n\t\t\t// region. It only governs WINDOW-resize behavior (keep pixel width, don't\n\t\t\t// scale); the user can still drag the separator to resize it (≥ minSize).\n\t\t\tconst px = `${constraint.minPx}px`\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={px}\n\t\t\t\t\tminSize={px}\n\t\t\t\t\tgroupResizeBehavior=\"preserve-pixel-size\"\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t\telse {\n\t\t\t// `defaultSize` seeds the FLEXIBLE child at mount; post-mount the model\n\t\t\t// becomes the source of truth via the imperative `setLayout` driven from\n\t\t\t// the sync effect below (M1). The value is passed as a STRING percentage:\n\t\t\t// rrp treats a NUMBER `defaultSize` as PIXELS, so `defaultSize={70}` would\n\t\t\t// be 70px (not 70%) — which mis-proportions a flexible sibling next to a\n\t\t\t// px-sized (`fixedPx`/`minPx`) panel and the sync then preserves the wrong\n\t\t\t// size. A unitless string is parsed as a percentage.\n\t\t\tconst pct = percentageByIndex.get(i)\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={pct != null ? String(pct) : undefined}\n\t\t\t\t\tminSize={flexMinSize}\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t})\n\n\t// A real rrp resize (pointer OR keyboard) — or our own `setLayout` echo / a\n\t// spontaneous re-measure — commits new per-Panel percentages here. The SINGLE\n\t// discriminator is a BASIS-NORMALIZED flexible-ratio compare (no gate, no echo\n\t// token): write back IFF the incoming layout's FLEXIBLE-child ratios differ from\n\t// the model's. `incomingFlexRatios` normalizes rrp's CONTAINER-% over the\n\t// flexible subset; `computeFlexiblePercentages` already does the same for the\n\t// model — so both bases sum to 100 and are directly comparable.\n\tconst handleLayoutChanged = (layout: Layout): void => {\n\t\tconst cur = nodeRef.current\n\t\tconst curChildIds = cur.children.map((c) => c.id)\n\t\tconst isFixedAt = (i: number): boolean => (cur.constraints?.[i] ?? null) !== null\n\n\t\tconst incoming = incomingFlexRatios(curChildIds, cur.constraints, layout)\n\t\tif (!incoming) return // malformed / no flexible child → never write back\n\t\tconst modelPctByIndex = computeFlexiblePercentages(cur.sizes, cur.constraints)\n\t\tconst modelNorm = incoming.indices.map((i) => modelPctByIndex.get(i) ?? 0)\n\n\t\t// Equal within a TIGHT tolerance → our own `setLayout` echo OR a\n\t\t// ratio-preserving spontaneous re-measure (mount / fixed-px re-pin / container\n\t\t// resize). SKIP: no model churn, no loop, no R2 corruption. Otherwise it is a\n\t\t// genuine user resize → WRITE BACK.\n\t\tconst differs = incoming.ratios.some(\n\t\t\t(r, k) => Math.abs(r - modelNorm[k]!) > FLEX_RATIO_TOLERANCE,\n\t\t)\n\t\tif (!differs) return\n\n\t\t// FULL-LENGTH weights (setSizes requires length === children.length). FIXED\n\t\t// children keep the model's existing weight — rrp reports a container-derived %\n\t\t// for the pinned panel which, written back, would corrupt the stored weight and\n\t\t// lose it when the constraint is later cleared. FLEXIBLE children take rrp's.\n\t\tconst weights = curChildIds.map((id, i) =>\n\t\t\tisFixedAt(i) ? cur.sizes[i] ?? 1 : layout[id]!,\n\t\t)\n\t\t// Bug #1 defense B: a flexible child dragged to ~0 width reports a ~0 ratio;\n\t\t// floor it before persisting so a 0-width weight can never reach the model\n\t\t// (the rrp `minSize` from A is the first defense, this is the write-back\n\t\t// backstop). Px children are untouched (clampFlexibleWeights skips them).\n\t\tctx.onApplyLayout(cur.id, clampFlexibleWeights(weights, cur.constraints))\n\t}\n\n\t// Ref callback exposing the resize write-back seam + the rrp Group imperative\n\t// handle on the split element. A drag round-trip (e2e) and the unit seam both\n\t// land on the same engine path; the `__deckGroupApi` handle lets hosts/tests\n\t// read the LIVE split layout (M1 model→view sync seam).\n\tconst setSplitRef = (el: DeckSplitElement | null): void => {\n\t\tif (el) {\n\t\t\tel.__deckApplyLayout = (weights: number[]) => {\n\t\t\t\t// Mirror handleLayoutChanged: a fixed child's stored weight is never\n\t\t\t\t// overwritten by an incoming (container-derived) value — preserve\n\t\t\t\t// node.sizes[i] for fixed indices so clearing the constraint later\n\t\t\t\t// restores the original weight. Flexible indices take the supplied value.\n\t\t\t\t// Derive fixed-ness from `nodeRef.current.constraints` (the SAME fresh\n\t\t\t\t// node we read sizes from) so this seam has no fresh-sizes/stale-constraints\n\t\t\t\t// asymmetry. Mirrors `isFixedAt` in handleLayoutChanged.\n\t\t\t\tconst cur = nodeRef.current\n\t\t\t\tconst isFixedAt = (i: number): boolean => (cur.constraints?.[i] ?? null) !== null\n\t\t\t\tconst next = weights.map((w, i) => (isFixedAt(i) ? cur.sizes[i] ?? 1 : w))\n\t\t\t\t// Bug #1 defense B (same clamp as handleLayoutChanged): floor flexible\n\t\t\t\t// weights so a near-0 drag can never persist a 0-width panel.\n\t\t\t\tctx.onApplyLayout(cur.id, clampFlexibleWeights(next, cur.constraints))\n\t\t\t}\n\t\t\t// Expose the rrp Group imperative handle (getLayout/setLayout) so a host —\n\t\t\t// and the jsdom `[M1-seam-live]` test — can reach the live split layout.\n\t\t\t// May be null until the Group mounts its handle; the getter reads the\n\t\t\t// freshest ref each call.\n\t\t\tObject.defineProperty(el, '__deckGroupApi', {\n\t\t\t\tconfigurable: true,\n\t\t\t\tget: () => groupRef.current ?? undefined,\n\t\t\t})\n\t\t}\n\t}\n\n\t// ── M1 model→view sync ─────────────────────────────────────────────────\n\t// Push the model's FLEXIBLE weights into the live Group via `setLayout` so the\n\t// model is the source of truth for the visible split post-mount (without it rrp\n\t// freezes at the mount-time `defaultSize`). Reads the FRESHEST `node` from the\n\t// ref so an external `setSizes` syncs against the latest model. Returns true if\n\t// a `setLayout` was actually pushed.\n\tconst runSync = useCallback((): boolean => {\n\t\tconst api = groupRef.current\n\t\tif (!api) return false\n\t\tconst cur = nodeRef.current\n\t\tconst curChildIds = cur.children.map((c) => c.id)\n\n\t\tconst live = api.getLayout()\n\t\t// jsdom's stub returns `{}` (no measured geometry) — buildSetLayoutMap then\n\t\t// returns null for any fixed child, and for the all-flexible case the\n\t\t// equivalence check below sees missing live ids and proceeds; but `setLayout`\n\t\t// is a no-op under jsdom anyway. In a real renderer `live` is populated.\n\t\tconst targetMap = buildSetLayoutMap(curChildIds, cur.sizes, cur.constraints, live)\n\t\tif (!targetMap) return false\n\n\t\t// SET-side redundant-push skip: don't re-push a layout the live Group already\n\t\t// satisfies. This is the loop break on the SET side — pushing an identical\n\t\t// layout would re-emit `onLayoutChanged`, but its flexible ratios match the\n\t\t// model so the normalized compare in `handleLayoutChanged` skips the write-back\n\t\t// anyway; this skip just avoids the redundant work.\n\t\tif (layoutsEquivalent(live, targetMap, curChildIds)) return false\n\n\t\tapi.setLayout(targetMap)\n\t\treturn true\n\t}, [])\n\n\t// Re-run the sync whenever the model's raw weights change (every external\n\t// `setSizes`). The model is stable DURING a user drag — the write-back lands on\n\t// release — so this effect does not fire mid-drag and never fights the live\n\t// pointer snapshot. An external `setSizes` (incl. one after an away-and-back\n\t// drag) syncs immediately (R3): there is no drag flag to get stuck.\n\tconst sizesKey = node.sizes.join(',')\n\t// Child COUNT is part of the sync key (Bug #2 follow-up): the `key={children.\n\t// length}` on the Group remounts it on a cardinality change, and a remount\n\t// reseeds the rrp layout from each Panel's `defaultSize` — for a `minPx` column\n\t// that is the floor (the device-min), NOT the user's last-dragged width. Re-\n\t// running `runSync` after the remount re-pushes the model's stored weights into\n\t// the fresh Group so a surviving split's proportions are restored rather than\n\t// snapping back to the per-Panel defaults. A pure weight resize (same count)\n\t// already re-syncs via `sizesKey`; this only adds the count edge.\n\tconst childCountKey = node.children.length\n\tuseEffect(() => {\n\t\trunSync()\n\t\t// Keyed on `sizesKey` + `childCountKey`; `runSync` reads `node`/`childIds`/\n\t\t// `constraints` fresh from the ref, so they cannot go stale relative to either.\n\t}, [sizesKey, childCountKey, runSync])\n\n\t// The split-level `data-*` attributes ride on an outer wrapper rather than\n\t// the Group itself, so hosts/tests can target them regardless of how the\n\t// library forwards unknown props. `data-deck-sizes` mirrors the model's\n\t// CURRENT raw weights so the render is a function of model.sizes.\n\treturn (\n\t\t<div\n\t\t\tref={setSplitRef}\n\t\t\tdata-deck-split={node.id}\n\t\t\tdata-orientation={node.orientation}\n\t\t\tdata-deck-sizes={node.sizes.join(',')}\n\t\t\tstyle={{ width: '100%', height: '100%' }}\n\t\t>\n\t\t\t<Group\n\t\t\t\t// Bug #2 (white-screen crash guard): KEY the Group on its child COUNT.\n\t\t\t\t// rrp v4.10 caches the previous layout in a ref keyed by group id; on a\n\t\t\t\t// render where the child count CHANGES (a panel closed/split — the split\n\t\t\t\t// id stays 'root' so the Group instance is otherwise reused), rrp's\n\t\t\t\t// commit-phase layout effect synchronously validates the STALE\n\t\t\t\t// (old-length) cached layout against the NEW (different-length)\n\t\t\t\t// constraints and throws `Invalid N panel layout`. That throw escapes\n\t\t\t\t// SplitView during commit and unmounts the whole tree (white screen).\n\t\t\t\t// Keying on `node.children.length` remounts the Group with a fresh\n\t\t\t\t// internal layout sized to the new child count ONLY when the count\n\t\t\t\t// changes — an ordinary weight resize never changes the key, so this\n\t\t\t\t// adds NO remount on drags. The model→view sync (runSync) re-binds\n\t\t\t\t// `groupRef` on remount and re-pushes the model's weights via the\n\t\t\t\t// `sizesKey` effect; the simulator/console native overlay re-anchors via\n\t\t\t\t// its slot ref callback (same path as a tab switch). See\n\t\t\t\t// dock-view-robustness.test.tsx \"3 → 2\" for the regression.\n\t\t\t\tkey={node.children.length}\n\t\t\t\tgroupRef={groupRef}\n\t\t\t\torientation={orientation}\n\t\t\t\tonLayoutChanged={handleLayoutChanged}\n\t\t\t\tstyle={{ width: '100%', height: '100%' }}\n\t\t\t>\n\t\t\t\t{items}\n\t\t\t</Group>\n\t\t</div>\n\t)\n}\n\n/** Stable React key for a child node (split id or group id). */\nfunction panelKey(node: LayoutNode): string {\n\treturn node.id\n}\n\nfunction renderGroup(node: TabGroupNode, ctx: RenderContext): ReactNode {\n\t// A group is a stateful component (it tracks the live drop-hover zone during a\n\t// drag), so render it via JSX with a STABLE key so React preserves that hover\n\t// state across model emissions rather than remounting on every snapshot.\n\treturn <GroupView key={node.id} node={node} ctx={ctx} />\n}\n\ninterface GroupViewProps {\n\tnode: TabGroupNode\n\tctx: RenderContext\n}\n\n/**\n * One tab GROUP: tab strip (draggable tabs) + active body + the imperative\n * `__deckHandleDrop` seam + a geometry-driven drop indicator while a drag hovers.\n */\nfunction GroupView(props: GroupViewProps): ReactNode {\n\tconst { node, ctx } = props\n\n\t// The live drop zone under the pointer during a drag-over (null = no drag over\n\t// this group). Drives the `data-deck-drop-zone` indicator. jsdom can't produce\n\t// real geometry (getBoundingClientRect is 0), so this path is exercised by the\n\t// real-pointer e2e `it.todo`s; here we implement it for real-browser fidelity.\n\tconst [dropZone, setDropZone] = useState<DropZone | null>(null)\n\n\t// Keep the latest node + redock callback reachable from the imperative ref\n\t// seam without re-running the ref effect when they change identity. The seam\n\t// always anchors a split at the group's CURRENT active panel.\n\tconst nodeRef = useRef(node)\n\tnodeRef.current = node\n\tconst redockRef = useRef(ctx.onRedock)\n\tredockRef.current = ctx.onRedock\n\n\t// Ref-callback exposing the drop seam on the group element. Mirrors\n\t// `setSplitRef`/`__deckApplyLayout`: the gesture and the unit seam both land on\n\t// the same `onRedock` engine path. The seam owns choosing the target panel —\n\t// the group's active panel is the natural anchor for an edge split.\n\tconst setGroupRef = (el: DeckGroupElement | null): void => {\n\t\tif (el) {\n\t\t\tel.__deckHandleDrop = (draggedPanelId: string, zone: DropZone) => {\n\t\t\t\tconst current = nodeRef.current\n\t\t\t\tredockRef.current(current.id, current.active, draggedPanelId, zone)\n\t\t\t}\n\t\t}\n\t}\n\n\t// ── drag-over geometry ────────────────────────────────────────────────\n\t// Compute the hovered zone from the pointer position relative to the group\n\t// rect. `preventDefault` is required for the element to be a valid drop target.\n\tconst handleDragOver = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\tconst rect = e.currentTarget.getBoundingClientRect()\n\t\tconst zone = computeDropZone(\n\t\t\t{ width: rect.width, height: rect.height },\n\t\t\t{ x: e.clientX - rect.left, y: e.clientY - rect.top },\n\t\t)\n\t\tsetDropZone(zone)\n\t}\n\n\tconst handleDragLeave = (): void => {\n\t\tsetDropZone(null)\n\t}\n\n\t// On drop, recover the dragged panel id (custom MIME first, falling back to\n\t// the source tab marker carried as text), then commit via the same seam path.\n\tconst handleDrop = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\tconst rect = e.currentTarget.getBoundingClientRect()\n\t\tconst zone = computeDropZone(\n\t\t\t{ width: rect.width, height: rect.height },\n\t\t\t{ x: e.clientX - rect.left, y: e.clientY - rect.top },\n\t\t)\n\t\tsetDropZone(null)\n\t\tconst dragged\n\t\t\t= e.dataTransfer.getData(DRAG_PANEL_MIME) || e.dataTransfer.getData('text/plain')\n\t\t// Validate the payload before committing a re-dock:\n\t\t// - M4: a `text/plain` drop can carry arbitrary external text (a selection,\n\t\t// a file path); it must at least be a REGISTERED panel.\n\t\t// - M2: registry membership is NOT tree membership. A registered-but-absent\n\t\t// panel (closed out of the tree / never docked) passes the registry guard\n\t\t// yet drives `movePanel`/`extractPanel` on an id missing from the tree,\n\t\t// which THROWS `panel not found`. It must be a NO-OP — also require the id\n\t\t// to be present in the CURRENT tree.\n\t\tif (!dragged || ctx.registry.get(dragged) === undefined || !ctx.isPanelInTree(dragged)) return\n\t\t// Pointer-derived insertion index for a within-group REORDER\n\t\t// (`dropPolicy:'reorder-only'`): measure this group's tab rects and map the\n\t\t// pointer x onto an index. Ignored by `handleRedock` for every non-reorder\n\t\t// path, so it is safe to compute unconditionally.\n\t\tconst tabRects = Array.from(\n\t\t\te.currentTarget.querySelectorAll<HTMLElement>('[data-deck-tab]'),\n\t\t).map((el) => {\n\t\t\tconst r = el.getBoundingClientRect()\n\t\t\treturn { left: r.left, width: r.width }\n\t\t})\n\t\tconst reorderIndex = computeReorderIndex(tabRects, e.clientX)\n\t\tctx.onRedock(node.id, node.active, dragged, zone, reorderIndex)\n\t}\n\n\t// The tab STRIP is a first-class REORDER drop target. It sits at the TOP of\n\t// the group, so a tab dropped onto it would otherwise resolve to the group's\n\t// `top` EDGE zone via `computeDropZone` — which a `reorder-only` panel rejects\n\t// as a no-op, so reordering by dragging within the tab bar would never work.\n\t// Intercept strip drops here and commit a `center` re-dock carrying the\n\t// pointer-x insertion index (`handleRedock` reorders within the group).\n\t// `stopPropagation` keeps the group's edge/center drop handlers from also\n\t// firing for the same gesture.\n\tconst handleTabStripDragOver = (e: DragEvent<HTMLDivElement>): void => {\n\t\tif (!e.dataTransfer.types.includes(DRAG_PANEL_MIME)) return\n\t\te.preventDefault()\n\t\te.stopPropagation()\n\t\tsetDropZone(null)\n\t}\n\tconst handleTabStripDrop = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\te.stopPropagation()\n\t\tsetDropZone(null)\n\t\tconst dragged\n\t\t\t= e.dataTransfer.getData(DRAG_PANEL_MIME) || e.dataTransfer.getData('text/plain')\n\t\tif (!dragged || ctx.registry.get(dragged) === undefined || !ctx.isPanelInTree(dragged)) return\n\t\tconst tabRects = Array.from(\n\t\t\te.currentTarget.querySelectorAll<HTMLElement>('[data-deck-tab]'),\n\t\t).map((el) => {\n\t\t\tconst r = el.getBoundingClientRect()\n\t\t\treturn { left: r.left, width: r.width }\n\t\t})\n\t\tconst reorderIndex = computeReorderIndex(tabRects, e.clientX)\n\t\tctx.onRedock(node.id, node.active, dragged, 'center', reorderIndex)\n\t}\n\n\t// Panels that contribute a tab to the strip (`hideTab` panels carry their own\n\t// chrome — e.g. the simulator's device picker — so the engine tab is omitted).\n\t// When NONE remain the tab strip is not rendered at all and the body fills the\n\t// whole group region.\n\tconst visibleTabs = node.panels.filter((panelId) => !ctx.registry.get(panelId)?.hideTab)\n\n\t// FILL LAYOUT (FIX 2a): the group must be a flex COLUMN that fills its\n\t// allotted panel region so a leaf native slot can stretch to the full area.\n\t// Without this the group div is content-height (the tab strip only), the\n\t// active body / NativeSlot measures 0 height, the view-anchor publishes a\n\t// collapsed rect, and the simulator WebContentsView is invisible. The tab\n\t// strip is `shrink: 0`; the active body takes the remaining space (flex: 1).\n\treturn (\n\t\t<div\n\t\t\tref={setGroupRef}\n\t\t\tdata-deck-group={node.id}\n\t\t\tonDragOver={handleDragOver}\n\t\t\tonDragLeave={handleDragLeave}\n\t\t\tonDrop={handleDrop}\n\t\t\tstyle={{\n\t\t\t\tposition: 'relative',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexDirection: 'column',\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tminWidth: 0,\n\t\t\t\tminHeight: 0,\n\t\t\t}}\n\t\t>\n\t\t\t{visibleTabs.length > 0 ? (\n\t\t\t\t<div\n\t\t\t\trole=\"tablist\"\n\t\t\t\tstyle={{ flexShrink: 0 }}\n\t\t\t\tonDragOver={handleTabStripDragOver}\n\t\t\t\tonDrop={handleTabStripDrop}\n\t\t\t>\n\t\t\t\t{visibleTabs.map((panelId) => {\n\t\t\t\t\tconst active = panelId === node.active\n\t\t\t\t\tconst descriptor = ctx.registry.get(panelId)\n\t\t\t\t\tconst title = descriptor?.title ?? panelId\n\t\t\t\t\t// PanelCapabilities (GOAL A source): a `draggable:false` panel's tab\n\t\t\t\t\t// cannot be picked up — omit the marker entirely (an absent attribute,\n\t\t\t\t\t// not `draggable=\"false\"`, matches the \"no drag source\" contract).\n\t\t\t\t\tconst isDraggable = descriptor?.draggable !== false\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={panelId}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tdraggable={isDraggable ? 'true' : undefined}\n\t\t\t\t\t\t\tdata-deck-tab={panelId}\n\t\t\t\t\t\t\tdata-active={active ? 'true' : 'false'}\n\t\t\t\t\t\t\tonDragStart={(e) => {\n\t\t\t\t\t\t\t\t// A press that BEGINS on the close affordance must not drag the\n\t\t\t\t\t\t\t\t// whole tab: the HTML5 drag source is the draggable ANCESTOR\n\t\t\t\t\t\t\t\t// (this tab) regardless of which descendant the pointer landed\n\t\t\t\t\t\t\t\t// on, so a descendant's stopPropagation cannot cancel it —\n\t\t\t\t\t\t\t\t// detect the origin here and abort the drag. `closest` walks up\n\t\t\t\t\t\t\t\t// from the event target; a hit means the gesture started on ×.\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\te.target instanceof Element\n\t\t\t\t\t\t\t\t\t&& e.target.closest('[data-deck-tab-close]') !== null\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Record the dragged panel id so the drop target can recover it.\n\t\t\t\t\t\t\t\te.dataTransfer.setData(DRAG_PANEL_MIME, panelId)\n\t\t\t\t\t\t\t\te.dataTransfer.setData('text/plain', panelId)\n\t\t\t\t\t\t\t\te.dataTransfer.effectAllowed = 'move'\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tif (!active) ctx.onActivate(node.id, panelId)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t\t{/* Close affordance — a `role=\"button\"` SPAN (NOT a nested <button>:\n\t\t\t\t\t\t\t an interactive button may not be a descendant of another button —\n\t\t\t\t\t\t\t invalid HTML + illegal a11y nesting). `tabIndex={0}` + the\n\t\t\t\t\t\t\t Enter/Space keydown keep it keyboard/AT-operable. Suppressed when\n\t\t\t\t\t\t\t the WHOLE tree has one panel left (`canClose` false). Activate is\n\t\t\t\t\t\t\t kept off the tab (click stopPropagation) and a tab drag that\n\t\t\t\t\t\t\t begins here is cancelled by the tab's onDragStart guard above. */}\n\t\t\t\t\t\t\t{ctx.canClose && descriptor?.closable !== false\n\t\t\t\t\t\t\t\t? (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\t\t\t\t\tdata-deck-tab-close={panelId}\n\t\t\t\t\t\t\t\t\t\taria-label={`Close ${title}`}\n\t\t\t\t\t\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\t\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\t\t\tctx.onClose(panelId)\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\t\tif (e.key === 'Enter' || e.key === ' ') {\n\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\t\t\t\tctx.onClose(panelId)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: null}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)\n\t\t\t\t})}\n\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{renderActiveBody(node, ctx)}\n\t\t\t{dropZone ? <DropIndicator zone={dropZone} /> : null}\n\t\t</div>\n\t)\n}\n\n/**\n * Translate a drop zone into the indicator overlay's geometry. `center` covers\n * the whole group (a tab-join highlight); each edge zone is a HALF-band ribbon\n * pinned to that edge. Pure presentation — the host can re-skin via the\n * `data-deck-drop-zone` attribute; these inline styles are a sane default.\n */\nfunction dropIndicatorStyle(zone: DropZone): Record<string, string> {\n\tconst base: Record<string, string> = {\n\t\tposition: 'absolute',\n\t\t// `none` so the overlay never steals the drag-over/drop events from the\n\t\t// group beneath it (otherwise the indicator itself would become the target).\n\t\tpointerEvents: 'none',\n\t\tbackground: 'rgba(64, 128, 255, 0.25)',\n\t\toutline: '2px solid rgba(64, 128, 255, 0.6)',\n\t}\n\tswitch (zone) {\n\t\tcase 'left':\n\t\t\treturn { ...base, left: '0', top: '0', width: '50%', height: '100%' }\n\t\tcase 'right':\n\t\t\treturn { ...base, right: '0', top: '0', width: '50%', height: '100%' }\n\t\tcase 'top':\n\t\t\treturn { ...base, left: '0', top: '0', width: '100%', height: '50%' }\n\t\tcase 'bottom':\n\t\t\treturn { ...base, left: '0', bottom: '0', width: '100%', height: '50%' }\n\t\tcase 'center':\n\t\tdefault:\n\t\t\treturn { ...base, left: '0', top: '0', width: '100%', height: '100%' }\n\t}\n}\n\n/** The drop-zone highlight rendered over a group during a drag-over. */\nfunction DropIndicator(props: { zone: DropZone }): ReactNode {\n\treturn <div data-deck-drop-zone={props.zone} style={dropIndicatorStyle(props.zone)} />\n}\n\n/**\n * Render a tab group's body region under DOM-panel KEEPALIVE.\n *\n * - DOM panels: ALL of the group's DOM panels are mounted SIMULTANEOUSLY, each\n * under a STABLE React key (`dom-${panelId}`) so switching the active tab never\n * remounts a body — React state + scroll persist across A→B→A. The active body\n * fills the region (flex:1); inactive ones stay in the DOM but `display:none`.\n * Each body's host renderer receives `{ active }` and is re-invoked with the\n * new flag on every activation change (no remount), so a host can fire\n * on-activation side effects off the false→true edge.\n * - Native panels: EXEMPT from keepalive. The single ACTIVE native panel mounts a\n * `NativeSlot` (keyed on the active id, so deactivation unmounts it and fires\n * `bindNativeSlot(id, null)`); inactive native panels render nothing. Keeping a\n * bound native slot mounted-but-hidden would collapse its WebContentsView rect.\n */\nfunction renderActiveBody(node: TabGroupNode, ctx: RenderContext): ReactNode {\n\tconst activeId = node.active\n\tif (!activeId) return null\n\n\tconst activeDescriptor = ctx.registry.get(activeId)\n\n\t// Native active panel → active-only NativeSlot (no keepalive).\n\tconst nativeSlot\n\t\t= activeDescriptor?.kind === 'native'\n\t\t\t? (\n\t\t\t\t<NativeSlot\n\t\t\t\t\tkey={activeId}\n\t\t\t\t\tpanelId={activeId}\n\t\t\t\t\tbindNativeSlot={ctx.bindNativeSlot}\n\t\t\t\t/>\n\t\t\t\t)\n\t\t\t: null\n\n\t// Every DOM (non-native) panel in the group is kept alive: mounted up-front,\n\t// hidden unless active. A panel with no descriptor is treated as DOM (render\n\t// via the host's renderer) — matching the pre-keepalive fallback.\n\tconst domBodies = node.panels\n\t\t.filter((panelId) => ctx.registry.get(panelId)?.kind !== 'native')\n\t\t.map((panelId) => {\n\t\t\tconst active = panelId === activeId\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tkey={`dom-${panelId}`}\n\t\t\t\t\tdata-deck-panel-body={panelId}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t// Active body fills the region; inactive bodies stay mounted but\n\t\t\t\t\t\t// hidden (display:none preserves React state + scroll position).\n\t\t\t\t\t\t// A flex COLUMN container (not a bare block) so a host panel root\n\t\t\t\t\t\t// that fills via `flex:1`/`height:100%` actually stretches to the\n\t\t\t\t\t\t// full body height — without this such a root collapses to content\n\t\t\t\t\t\t// height and leaves dead space below it.\n\t\t\t\t\t\tdisplay: active ? 'flex' : 'none',\n\t\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\tminWidth: 0,\n\t\t\t\t\t\tminHeight: 0,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{ctx.renderDomPanel(panelId, { active })}\n\t\t\t\t</div>\n\t\t\t)\n\t\t})\n\n\treturn (\n\t\t<Fragment>\n\t\t\t{nativeSlot}\n\t\t\t{domBodies}\n\t\t</Fragment>\n\t)\n}\n\ninterface NativeSlotProps {\n\tpanelId: string\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n}\n\n/**\n * Empty anchor slot for an ACTIVE native panel. A ref-callback binds the live\n * element on mount and unbinds (`null`) on unmount. Keying the element on the\n * active panel id (in the parent) guarantees deactivation unmounts this slot —\n * firing the `null` cleanup — and re-activation mounts a fresh one, re-binding.\n */\nfunction NativeSlot(props: NativeSlotProps): ReactNode {\n\tconst { panelId, bindNativeSlot } = props\n\t// Keep the latest callback without re-running the ref effect on identity\n\t// churn of an inline `bindNativeSlot`.\n\tconst bindRef = useRef(bindNativeSlot)\n\tbindRef.current = bindNativeSlot\n\n\tconst setRef = useCallback(\n\t\t(el: HTMLDivElement | null) => {\n\t\t\tbindRef.current(panelId, el)\n\t\t},\n\t\t[panelId],\n\t)\n\n\t// FILL LAYOUT (FIX 2a): the empty anchor slot must fill the remaining group\n\t// region so the host's view-anchor measures the FULL panel rect, not a 0×0\n\t// box. `flex:1` claims the space below the tab strip; `min-*:0` lets it\n\t// shrink inside the flex column; `height:100%` is the belt-and-braces\n\t// fallback for any non-flex ancestor.\n\treturn (\n\t\t<div\n\t\t\tref={setRef}\n\t\t\tdata-deck-native-slot={panelId}\n\t\t\tstyle={{ flex: 1, minWidth: 0, minHeight: 0, height: '100%' }}\n\t\t/>\n\t)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/dock-react/drag-redock.ts","../../src/dock-react/dock-view.tsx"],"sourcesContent":["/**\n * `drag-redock` — PURE geometry + descriptor layer for tab drag-to-redock.\n *\n * This module is intentionally REACT-FREE and ELECTRON-FREE so it can run under\n * the node `vitest.config.ts` suite (its spec is `drag-redock.test.ts`). It owns\n * two responsibilities, both pure functions of their inputs:\n *\n * 1. GEOMETRY — `computeDropZone(rect, point)` maps a pointer position over a\n * group's rectangle to one of five drop zones (the four edge bands plus the\n * interior `center`). This is the only place edge-band math lives; the React\n * layer feeds it real `getBoundingClientRect` numbers during dragover.\n *\n * 2. DESCRIPTOR — `dropZoneToMutation(zone, dragged, target)` translates a zone\n * into an engine-NEUTRAL `RedockMutation` intent (`move` | `split`). The\n * descriptor only NAMES the intent; the React layer realizes it against the\n * real tree (a split of an EXISTING panel needs extract-then-split, because\n * `splitPanel` throws if the new panel already exists — see the caller).\n *\n * Keeping this split (geometry/descriptor here, engine application in React)\n * means the geometry is exhaustively unit-testable without a DOM, and the React\n * layer stays a thin gesture binding.\n */\n\n/** The five drop zones: four edge bands + the tab-joining interior. */\nexport type DropZone = 'left' | 'right' | 'top' | 'bottom' | 'center'\n\n/**\n * Engine-neutral re-dock INTENT. `move` joins the dragged panel into a tab\n * group; `split` puts it adjacent to a target panel in a new split. This is a\n * pure descriptor — it does NOT itself touch a tree (the caller applies it,\n * extract-then-split'ing for an existing panel).\n */\nexport type RedockMutation =\n\t| { kind: 'move'; panelId: string; destGroupId: string }\n\t| {\n\t\tkind: 'split'\n\t\tatPanelId: string\n\t\tdir: 'row' | 'column'\n\t\tside: 'before' | 'after'\n\t\tnewPanelId: string\n\t}\n\n/**\n * Classify a pointer position (RELATIVE to the rect's top-left; `(0,0)` is the\n * corner) into a drop zone.\n *\n * Band thickness = `edgeFraction * min(width, height)` — a single symmetric band\n * width derived from the SHORTER side so a wide/short panel doesn't get an\n * absurdly fat horizontal band. A point in NO band is `center` (the tab-join\n * interior). A point in TWO bands (a corner) tie-breaks by NORMALIZED distance\n * to each active edge; on an exact tie HORIZONTAL wins. A point OUTSIDE the rect\n * clamps to the nearest edge zone (per-axis; diagonal corners pick the larger\n * overshoot, horizontal breaking ties).\n */\nexport function computeDropZone(\n\trect: { width: number; height: number },\n\tpoint: { x: number; y: number },\n\tedgeFraction = 0.25,\n): DropZone {\n\tconst { width, height } = rect\n\tconst { x, y } = point\n\n\t// ── DEGENERATE / NON-FINITE guard (N1) ─────────────────────────────────\n\t// A zero/negative/non-finite rect has no meaningful edge bands; a non-finite\n\t// point can't be classified. Treat either as the interior (tab-join). Note a\n\t// non-finite WIDTH/HEIGHT (e.g. Infinity) satisfies `> 0` and would otherwise\n\t// slip through — `band = ef * min(Infinity, h)` yields a finite band and the\n\t// point is misclassified as an EDGE zone — so reject finiteness EXPLICITLY.\n\tif (\n\t\t!(width > 0) || !(height > 0)\n\t\t|| !Number.isFinite(width) || !Number.isFinite(height)\n\t\t|| !Number.isFinite(x) || !Number.isFinite(y)\n\t) {\n\t\treturn 'center'\n\t}\n\t// Clamp the band fraction to [0, 0.5] (N1): a fraction > 0.5 makes the left\n\t// and right bands (and top/bottom) OVERLAP, so a point near the right edge of\n\t// a narrow rect would satisfy BOTH `inLeft` and `inRight` and be misread as\n\t// `left`. Capping at 0.5 keeps the two bands disjoint.\n\tconst ef = Number.isFinite(edgeFraction) ? Math.max(0, Math.min(0.5, edgeFraction)) : 0.25\n\n\t// ── OUT-OF-RECT clamp ──────────────────────────────────────────────────\n\t// A pointer dragged past the panel edge has no band membership; clamp it to\n\t// the nearest edge zone. Compute the signed per-axis overshoot: negative =\n\t// out the low side (left/top), positive = out the high side (right/bottom).\n\tconst outX = x < 0 ? x : x > width ? x - width : 0\n\tconst outY = y < 0 ? y : y > height ? y - height : 0\n\tif (outX !== 0 || outY !== 0) {\n\t\tconst magX = Math.abs(outX)\n\t\tconst magY = Math.abs(outY)\n\t\t// Both axes out (a diagonal corner): the axis with the LARGER overshoot\n\t\t// magnitude wins; an exact tie is broken by HORIZONTAL (the x axis).\n\t\tif (magX >= magY) {\n\t\t\tif (magX > 0) return outX < 0 ? 'left' : 'right'\n\t\t}\n\t\t// Only y out, or y overshoot strictly larger.\n\t\treturn outY < 0 ? 'top' : 'bottom'\n\t}\n\n\t// ── BAND membership (in-rect) ──────────────────────────────────────────\n\tconst band = ef * Math.min(width, height)\n\tconst inLeft = x < band\n\tconst inRight = x > width - band\n\tconst inTop = y < band\n\tconst inBottom = y > height - band\n\n\tconst activeHoriz = inLeft || inRight\n\tconst activeVert = inTop || inBottom\n\n\t// No band => interior => join the tab group.\n\tif (!activeHoriz && !activeVert) return 'center'\n\n\t// Single-axis band => that edge zone directly.\n\tif (activeHoriz && !activeVert) return inLeft ? 'left' : 'right'\n\tif (activeVert && !activeHoriz) return inTop ? 'top' : 'bottom'\n\n\t// ── CORNER tie-break (both axes active) ────────────────────────────────\n\t// Pick the edge with the SMALLER normalized distance. Only the ACTIVE\n\t// horizontal edge and ACTIVE vertical edge can compete (left vs right and\n\t// top vs bottom can't both be active for a sane rect). On an exact tie,\n\t// HORIZONTAL wins (`<=` on the horizontal distance).\n\tconst dHoriz = inLeft ? x / width : (width - x) / width\n\tconst dVert = inTop ? y / height : (height - y) / height\n\tif (dHoriz <= dVert) return inLeft ? 'left' : 'right'\n\treturn inTop ? 'top' : 'bottom'\n}\n\n/**\n * Translate a drop zone into an engine-neutral re-dock descriptor.\n *\n * center => move the dragged panel into the target's tab group.\n * left/right => split the target panel horizontally (row), dragged before/after.\n * top/bottom => split the target panel vertically (column), dragged before/after.\n */\nexport function dropZoneToMutation(\n\tzone: DropZone,\n\tdragged: string,\n\ttarget: { groupId: string; panelId: string },\n): RedockMutation {\n\tif (zone === 'center') {\n\t\treturn { kind: 'move', panelId: dragged, destGroupId: target.groupId }\n\t}\n\tconst dir: 'row' | 'column' = zone === 'left' || zone === 'right' ? 'row' : 'column'\n\tconst side: 'before' | 'after' = zone === 'left' || zone === 'top' ? 'before' : 'after'\n\treturn { kind: 'split', atPanelId: target.panelId, dir, side, newPanelId: dragged }\n}\n\n/**\n * A re-dock is a NO-OP when it would not change the tree:\n *\n * 1. The drop targets the dragged panel ITSELF (`dragged === target.panelId`),\n * in ANY zone — you can neither tab a panel onto its own tab nor split a\n * panel around itself. Crucially this also guards the SPLIT case: without\n * it, `extractPanel(dragged)` then `splitPanel(atPanelId = dragged)` removes\n * the very anchor it then splits at and THROWS (the self-collapse / single-\n * panel-self-split bug).\n * 2. A `center` drop into a group the dragged panel ALREADY lives in\n * (`draggedGroupId === target.groupId`) — `movePanel` would re-append it and\n * bump the revision for no visible change (churn).\n *\n * A SPLIT onto a DIFFERENT panel of the dragged panel's own group is still a real\n * re-dock (it splits the group around that sibling), so it is NOT a no-op.\n *\n * `draggedGroupId` is the id of the tab group currently holding `dragged`\n * (`undefined` if it is not in the tree).\n */\nexport function isNoopRedock(\n\tdragged: string,\n\tdraggedGroupId: string | undefined,\n\ttarget: { groupId: string; panelId: string },\n\tzone: DropZone,\n): boolean {\n\tif (dragged === target.panelId) return true\n\tif (zone === 'center' && draggedGroupId !== undefined && draggedGroupId === target.groupId) {\n\t\treturn true\n\t}\n\treturn false\n}\n\n/**\n * Map a pointer x-position over a horizontal tab strip to an insertion index for\n * a within-strip REORDER (the `dropPolicy:'reorder-only'` gesture). The strip is\n * the dragged tab's own group; `tabRects` are the tab buttons' rects in visual\n * order (each `left` is the rect's left edge, `width` its width). The index is\n * the count of tabs whose MIDPOINT the pointer has passed: the LEFT half of a tab\n * inserts BEFORE it, the RIGHT half (and the exact midpoint) inserts AFTER it. A\n * pointer left of the first tab → 0; past the last tab → `tabRects.length`. Pure\n * (no DOM): the caller measures the rects and passes the pointer x. An empty\n * strip or a non-finite pointer → 0.\n */\nexport function computeReorderIndex(\n\ttabRects: readonly { left: number; width: number }[],\n\tpointerX: number,\n): number {\n\tif (!Number.isFinite(pointerX)) return 0\n\tlet index = 0\n\tfor (const rect of tabRects) {\n\t\tconst midpoint = rect.left + rect.width / 2\n\t\tif (pointerX >= midpoint) index += 1\n\t\telse break\n\t}\n\treturn index\n}\n","/**\n * `<DockView>` — React renderer for a layout-as-data tree.\n *\n * Pure function of the current model snapshot (+ registry + the two host\n * callbacks). It owns NO layout state: it subscribes to the `LayoutModel`,\n * keeps the latest tree in component state, and re-renders on every emission.\n * All structural targeting is via STABLE `data-*` attributes — see the\n * contract doc-block in `dock-view.test.tsx`.\n *\n * This file lives under `src/dock-react/` (NOT `src/layout/`), so importing\n * react here does not violate the pure-TS layout boundary.\n */\nimport {\n\tcreateContext,\n\tFragment,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n\ttype DragEvent,\n\ttype ReactNode,\n} from 'react'\nimport { Group, Panel, Separator } from 'react-resizable-panels'\nimport type { GroupImperativeHandle, Layout } from 'react-resizable-panels'\nimport {\n\tclosePanelForUser,\n\textractPanel,\n\tmovePanel,\n\tsetActive,\n\tsetSizes,\n\tsplitPanel,\n} from '../layout/index.js'\nimport type {\n\tLayoutModel,\n\tLayoutNode,\n\tLayoutTree,\n\tPanelRegistry,\n\tSizeConstraint,\n\tSplitNode,\n\tTabGroupNode,\n} from '../layout/index.js'\nimport {\n\tcomputeDropZone,\n\tcomputeReorderIndex,\n\tdropZoneToMutation,\n\tisNoopRedock,\n\ttype DropZone,\n} from './drag-redock.js'\n\n/**\n * The dataTransfer MIME under which a drag carries the dragged panel id. A\n * custom type (vs `text/plain`) keeps deck drags from being confused with\n * arbitrary text drops; the panel id also stays recoverable from the source\n * tab's `data-deck-tab` attribute (the jsdom seam relies on the latter).\n */\nconst DRAG_PANEL_MIME = 'application/x-deck-panel'\n\n// The panel id of the in-flight tab drag, set on `dragstart` and cleared on\n// `dragend`/`drop`. The DataTransfer VALUE is unreadable during `dragover` (only\n// `types` is exposed, by spec), so the drop-indicator path can't recover the\n// dragged id from the event — it reads it from here instead to decide whether to\n// paint a highlight for THIS drag (e.g. a `reorder-only` panel shows none).\nlet activeDragPanelId: string | null = null\n\nexport interface DockViewProps {\n\tmodel: LayoutModel\n\tregistry: PanelRegistry\n\t/**\n\t * Render a DOM panel's body. `opts.active` is `true` iff `panelId` is its tab\n\t * group's currently-active panel. Under DOM-panel keepalive every DOM panel in\n\t * a group is rendered (the inactive ones hidden), so this callback is invoked\n\t * for ALL of them and `opts.active` is RE-EVALUATED on every activation change\n\t * WITHOUT remounting the kept-alive subtree — letting a host run on-activation\n\t * side effects (e.g. data refresh) off the false→true `active` edge.\n\t */\n\trenderDomPanel: (panelId: string, opts: { active: boolean }) => ReactNode\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n\t/**\n\t * Opt-in: while a `dropPolicy:'reorder-only'` panel is dragged, paint NO drop\n\t * indicator over a group body (its own edges or any other group) — those are\n\t * no-op targets, so the highlight is misleading. Default `false` keeps the\n\t * geometry-only indicator (it paints where the pointer is; the capability\n\t * gate still rejects the drop). Hosts that want the cleaner \"no misleading\n\t * highlight\" UX pass `true`.\n\t */\n\tsuppressReorderOnlyDropIndicator?: boolean\n}\n\n/**\n * The current layout EPOCH — the model's revision, bumped once per committed\n * mutation. `DockView` provides it; descendant panels read it via\n * `useDockLayoutEpoch`.\n *\n * Its reason to exist: a native-view overlay anchored inside a dock panel\n * (a `WebContentsView` tracking its slot's geometry) re-publishes its bounds\n * only on GEOMETRY events (ResizeObserver / window-resize / splitter-drag). A\n * layout mutation that REORDERS a slot without resizing it — flipping a\n * fixed-width simulator column left↔right, moving a region — produces NO such\n * event (a same-size flex reorder fires nothing), so the overlay would freeze\n * at its old position. The epoch is the layout layer's explicit \"something\n * moved\" signal: a panel hosting a native overlay re-measures (e.g. pulses its\n * view-anchor) when the epoch changes, catching the translate the browser never\n * reports. Default 0 (outside a provider): the consuming effect runs once on\n * mount, which is harmless.\n */\nconst LayoutEpochContext = createContext<number>(0)\n\n/**\n * Read the current dock layout epoch (the model revision). Re-renders the caller\n * whenever a layout mutation commits. Keyed into a panel's effect deps, it lets\n * a native-overlay host re-measure on a reorder that fires no geometry event.\n * Returns 0 when used outside a `<DockView>`.\n */\nexport function useDockLayoutEpoch(): number {\n\treturn useContext(LayoutEpochContext)\n}\n\n/** Normalize raw split weights to percentages summing ~100 for `defaultSize`. */\nfunction toPercentages(sizes: readonly number[]): number[] {\n\tconst total = sizes.reduce((a, b) => a + (b > 0 ? b : 0), 0)\n\tif (total <= 0) {\n\t\t// Degenerate weights → distribute evenly.\n\t\tconst even = sizes.length > 0 ? 100 / sizes.length : 100\n\t\treturn sizes.map(() => even)\n\t}\n\treturn sizes.map((s) => ((s > 0 ? s : 0) / total) * 100)\n}\n\n/**\n * Compute the `defaultSize` percentage for each FLEXIBLE child, keyed by its\n * ORIGINAL child index. Px-sized children (a non-null `constraint` — `fixedPx`\n * locked OR `minPx` floored) are EXCLUDED from the pool: their size is px, not a\n * weight, so it must never pollute the flexible siblings' normalization (FIX E1).\n * `constraints[i]` non-null ⇒ child i is px-sized and absent from the returned map.\n */\nexport function computeFlexiblePercentages(\n\tsizes: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n): Map<number, number> {\n\tconst flexibleIndices = sizes\n\t\t.map((_, i) => i)\n\t\t.filter((i) => (constraints?.[i] ?? null) === null)\n\tconst pct = toPercentages(flexibleIndices.map((i) => sizes[i] ?? 0))\n\tconst out = new Map<number, number>()\n\tflexibleIndices.forEach((origIndex, j) => {\n\t\tout.set(origIndex, pct[j]!)\n\t})\n\treturn out\n}\n\nexport function DockView(props: DockViewProps): ReactNode {\n\tconst { model, registry, renderDomPanel, bindNativeSlot, suppressReorderOnlyDropIndicator } = props\n\n\t// Snapshot the canonical tree; re-render on every external emission. The\n\t// `epoch` mirrors the model revision (0 before the first apply) and is\n\t// provided to descendant panels via `LayoutEpochContext` so a native-overlay\n\t// host can re-measure on a reorder that fires no geometry event.\n\tconst [tree, setTree] = useState<LayoutTree>(() => model.get())\n\tconst [epoch, setEpoch] = useState<number>(0)\n\n\tuseEffect(() => {\n\t\t// Re-sync immediately in case the model changed between the initial\n\t\t// `useState` read and effect commit, then track future emissions.\n\t\tsetTree(model.get())\n\t\tconst unsubscribe = model.subscribe((snap) => {\n\t\t\tsetTree(snap.tree)\n\t\t\tsetEpoch(snap.revision)\n\t\t})\n\t\treturn unsubscribe\n\t}, [model])\n\n\tconst handleActivate = useCallback(\n\t\t(groupId: string, panelId: string) => {\n\t\t\tmodel.apply((t) => setActive(t, groupId, panelId))\n\t\t},\n\t\t[model],\n\t)\n\n\t// Close write-back: every tab close funnels through the capability-aware\n\t// user action. The generic `closePanel` mutation remains available to\n\t// programmatic layout transforms.\n\tconst handleClose = useCallback(\n\t\t(panelId: string) => {\n\t\t\tmodel.apply((t) => closePanelForUser(t, panelId, registry))\n\t\t},\n\t\t[model, registry],\n\t)\n\n\t// Total panels across the WHOLE tree. The close affordance is suppressed only\n\t// when the entire layout has a single panel left (closing it would no-op);\n\t// a multi-panel single group still shows closes. GroupView sees only its own\n\t// node, so we compute the global count here and thread `canClose` down.\n\tconst canClose = countPanels(tree.root) > 1\n\n\t// Panel-id membership of the CURRENT tree (M2). A drop's payload must be a\n\t// panel that actually lives in the tree, not merely one that is registered —\n\t// see `handleDrop`. Derived from the snapshot the component already holds.\n\tconst isPanelInTree = useCallback(\n\t\t(panelId: string) => findPanelGroupId(tree.root, panelId) !== undefined,\n\t\t[tree],\n\t)\n\n\t// Resize write-back: a drag commit (or the `__deckApplyLayout` seam) funnels\n\t// new per-split weights here, which apply `setSizes` to the canonical model.\n\tconst handleApplyLayout = useCallback(\n\t\t(splitId: string, weights: number[]) => {\n\t\t\tmodel.apply((t) => setSizes(t, splitId, weights))\n\t\t},\n\t\t[model],\n\t)\n\n\t// Drag-to-redock commit. A tab dragged onto `groupId`'s `zone` (the GROUP seam\n\t// or a real dragover) lands here. `activePanelId` is the group's natural split\n\t// anchor (the visible body the user dropped onto). We translate the zone to an\n\t// engine-neutral descriptor, skip true no-ops (drop onto own tab center), and\n\t// apply the descriptor:\n\t// - move => single `movePanel` (joins the target tab group).\n\t// - split => extract-then-split COMPOSED in ONE `model.apply` so the whole\n\t// re-dock is a single atomic emission (one subscriber notification, one\n\t// re-render): `splitPanel` throws if the dragged panel already exists, so\n\t// an EXISTING panel must be `extractPanel`'d first, then split against the\n\t// target. Doing both inside one `apply` keeps the tree from ever being\n\t// observed in the transient extracted state.\n\tconst handleRedock = useCallback(\n\t\t(\n\t\t\tgroupId: string,\n\t\t\tactivePanelId: string,\n\t\t\tdraggedPanelId: string,\n\t\t\tzone: DropZone,\n\t\t\treorderIndex?: number,\n\t\t) => {\n\t\t\tconst target = { groupId, panelId: activePanelId }\n\t\t\t// The dragged panel's CURRENT group — needed to skip a center-drop back\n\t\t\t// into its own group (M2) and (via `dragged === target.panelId`) a\n\t\t\t// self-split (M1).\n\t\t\tconst draggedGroupId = findPanelGroupId(model.get().root, draggedPanelId)\n\n\t\t\t// ── PanelCapabilities gate (GOAL A source): a `draggable:false` panel is\n\t\t\t// a locked STRUCTURAL panel — it can never be torn into another region.\n\t\t\t// UI drag-start already refuses to lift it, but the imperative drop seam\n\t\t\t// bypasses that, so reject a locked dragged source here too (defense in\n\t\t\t// depth) — its position in the tree is fixed. ──\n\t\t\tif (registry.get(draggedPanelId)?.draggable === false) return\n\n\t\t\t// ── PanelCapabilities gate (GOAL A target): a group whose ACTIVE panel is\n\t\t\t// `draggable:false` is a locked drop ANCHOR — nothing may join or split\n\t\t\t// against it, in any zone. Checked before the no-op/reorder logic so a\n\t\t\t// locked simulator/editor can never absorb another panel. ──\n\t\t\tif (registry.get(activePanelId)?.draggable === false) return\n\n\t\t\t// ── PanelCapabilities gate (GOAL B): a `reorder-only` dragged panel may\n\t\t\t// ONLY reorder WITHIN its own group. It never leaves the group (a center\n\t\t\t// drop into another group) and never edge-splits (any edge zone, own or\n\t\t\t// other group). The one motion it permits — a center drop into its OWN\n\t\t\t// group — must OVERRIDE `isNoopRedock` (which would swallow it as churn).\n\t\t\tif (registry.get(draggedPanelId)?.dropPolicy === 'reorder-only') {\n\t\t\t\tif (draggedGroupId === undefined || draggedGroupId !== groupId) return\n\t\t\t\tif (zone !== 'center') return\n\t\t\t\t// Anchor the reorder on the pointer-derived index when the gesture\n\t\t\t\t// supplies one (real drag), else on the active anchor's CURRENT index\n\t\t\t\t// (the imperative seam carries no pointer).\n\t\t\t\tconst group = findGroupById(model.get().root, groupId)\n\t\t\t\tconst index = reorderIndex ?? (group ? group.panels.indexOf(activePanelId) : undefined)\n\t\t\t\tmodel.apply((t) => movePanel(t, draggedPanelId, { groupId, index }))\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tif (isNoopRedock(draggedPanelId, draggedGroupId, target, zone)) return\n\t\t\tconst mutation = dropZoneToMutation(zone, draggedPanelId, target)\n\t\t\tif (mutation.kind === 'move') {\n\t\t\t\tmodel.apply((t) => movePanel(t, mutation.panelId, { groupId: mutation.destGroupId }))\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Atomic extract-then-split for an existing dragged panel. Defensive\n\t\t\t// guard (M1): if extracting the dragged panel removed the split anchor,\n\t\t\t// abort instead of letting `splitPanel` throw on a missing anchor.\n\t\t\tmodel.apply((t) => {\n\t\t\t\tconst { tree } = extractPanel(t, mutation.newPanelId)\n\t\t\t\tif (findPanelGroupId(tree.root, mutation.atPanelId) === undefined) return t\n\t\t\t\treturn splitPanel(tree, mutation.atPanelId, mutation.dir, mutation.newPanelId, mutation.side)\n\t\t\t})\n\t\t},\n\t\t[model, registry],\n\t)\n\n\treturn (\n\t\t<LayoutEpochContext.Provider value={epoch}>\n\t\t\t{renderNode(tree.root, {\n\t\t\t\tregistry,\n\t\t\t\trenderDomPanel,\n\t\t\t\tbindNativeSlot,\n\t\t\t\tonActivate: handleActivate,\n\t\t\t\tonApplyLayout: handleApplyLayout,\n\t\t\t\tonRedock: handleRedock,\n\t\t\t\tonClose: handleClose,\n\t\t\t\tcanClose,\n\t\t\t\tisPanelInTree,\n\t\t\t\tsuppressReorderOnlyDropIndicator: suppressReorderOnlyDropIndicator ?? false,\n\t\t\t})}\n\t\t</LayoutEpochContext.Provider>\n\t)\n}\n\n/** A split wrapper element augmented with the resize write-back seam plus the\n * rrp Group imperative handle (M1 model→view sync). Hosts/tests reach the live\n * split layout through `__deckGroupApi` the same way they reach the write-back\n * through `__deckApplyLayout`. */\ntype DeckSplitElement = HTMLDivElement & {\n\t__deckApplyLayout?: (weights: number[]) => void\n\t__deckGroupApi?: GroupImperativeHandle\n}\n\n/** A group wrapper element augmented with the drop-handling seam. Mirrors the\n * `__deckApplyLayout` discipline on split elements: an imperative hook the\n * gesture (or a unit test) calls to commit a re-dock against the model. */\ntype DeckGroupElement = HTMLDivElement & {\n\t__deckHandleDrop?: (draggedPanelId: string, zone: DropZone) => void\n}\n\ninterface RenderContext {\n\tregistry: PanelRegistry\n\trenderDomPanel: (panelId: string, opts: { active: boolean }) => ReactNode\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n\tonActivate: (groupId: string, panelId: string) => void\n\tonApplyLayout: (splitId: string, weights: number[]) => void\n\tonRedock: (\n\t\tgroupId: string,\n\t\tactivePanelId: string,\n\t\tdraggedPanelId: string,\n\t\tzone: DropZone,\n\t\t/** Pointer-derived insertion index for a within-group reorder\n\t\t * (`dropPolicy:'reorder-only'`). Omitted by the imperative seam, which\n\t\t * falls back to the active anchor's current index. */\n\t\treorderIndex?: number,\n\t) => void\n\tonClose: (panelId: string) => void\n\t/** False when the whole tree has a single panel — suppresses every close\n\t * button so the layout can't be emptied (closePanel would no-op anyway). */\n\tcanClose: boolean\n\t/** True iff `panelId` is present in the CURRENT layout tree (M2). Registry\n\t * membership is NOT enough: a panel can be registered but absent from the tree\n\t * (closed out / never docked), and driving a re-dock on it makes the mutation\n\t * layer throw `panel not found`. A drop carrying such an id must be a no-op. */\n\tisPanelInTree: (panelId: string) => boolean\n\t/** See {@link DockViewProps.suppressReorderOnlyDropIndicator}. */\n\tsuppressReorderOnlyDropIndicator: boolean\n}\n\n/** The id of the tab group currently holding `panelId`, or `undefined` if the\n * panel is not in the tree. Used by `handleRedock` to detect a drop back into\n * the dragged panel's own group (M2) and to guard a vanished split anchor (M1). */\nfunction findPanelGroupId(node: LayoutNode, panelId: string): string | undefined {\n\tif (node.kind === 'tabs') {\n\t\treturn node.panels.includes(panelId) ? node.id : undefined\n\t}\n\tfor (const child of node.children) {\n\t\tconst found = findPanelGroupId(child, panelId)\n\t\tif (found !== undefined) return found\n\t}\n\treturn undefined\n}\n\n/** The tab-group node with `groupId`, or `undefined`. Used by `handleRedock` to\n * read the live `panels` order of a reorder target (to anchor a same-group\n * reorder on the active panel's current index when the gesture carries no\n * pointer-derived index — e.g. the `__deckHandleDrop` test seam). */\nfunction findGroupById(node: LayoutNode, groupId: string): TabGroupNode | undefined {\n\tif (node.kind === 'tabs') return node.id === groupId ? node : undefined\n\tfor (const child of node.children) {\n\t\tconst found = findGroupById(child, groupId)\n\t\tif (found !== undefined) return found\n\t}\n\treturn undefined\n}\n\n/** Total panels anywhere in the tree. Drives the last-panel close suppression:\n * a GroupView only knows its own node, so DockView computes this global count\n * once and threads the resulting `canClose` boolean down. */\nfunction countPanels(node: LayoutNode): number {\n\tif (node.kind === 'tabs') return node.panels.length\n\treturn node.children.reduce((sum, child) => sum + countPanels(child), 0)\n}\n\nfunction renderNode(node: LayoutNode, ctx: RenderContext): ReactNode {\n\treturn node.kind === 'split'\n\t\t? renderSplit(node, ctx)\n\t\t: renderGroup(node, ctx)\n}\n\n/** A split is a stateful component (it holds the rrp Group imperative ref + runs\n * the M1 model→view sync effect), so render it via JSX with a STABLE key so\n * React preserves that ref / its kept-alive subtree across model emissions\n * rather than remounting on every snapshot. Mirrors `renderGroup`/`GroupView`. */\nfunction renderSplit(node: SplitNode, ctx: RenderContext): ReactNode {\n\treturn <SplitView key={node.id} node={node} ctx={ctx} />\n}\n\ninterface SplitViewProps {\n\tnode: SplitNode\n\tctx: RenderContext\n}\n\n/** Epsilon (in percentage points) within which two split layouts are treated as\n * equivalent. Guards BOTH sides of the model↔view loop: we skip pushing a\n * `setLayout` when the live layout already matches the target, and skip the\n * `onLayoutChanged` write-back when the incoming layout matches the model — so\n * `setLayout`→`onLayoutChanged`→write-back→re-sync cannot loop. */\nconst LAYOUT_EPSILON = 0.5\n\n/** TIGHT tolerance (percentage points) for the BASIS-NORMALIZED flexible-ratio\n * compare in `handleLayoutChanged`. We normalize the incoming layout's flexible\n * subset to ratios summing to 100 and compare them against the model's flexible\n * ratios (`computeFlexiblePercentages`, also summing to 100). If they match\n * within this tolerance the `onLayoutChanged` is either our own `setLayout` echo\n * OR a ratio-preserving spontaneous re-measure (mount / fixed-px re-pin /\n * container resize) — SKIP the write-back. If they differ it is a genuine user\n * resize (pointer OR keyboard) — WRITE BACK.\n *\n * Set to ~0.1pp: large enough to absorb rrp's ~3-decimal float noise on an echo,\n * yet FAR below a real drag's delta. R1's \"sub-0.5%\" drag moves a panel ~0.33pp\n * of the container; normalized over the two-flexible-child subset that is ~0.66pp\n * of ratio — comfortably above 0.1, so it is NOT mistaken for an echo and is\n * written back.\n *\n * CAVEAT: this is a flexible-RATIO tolerance (the flexible subset normalized to\n * sum 100), NOT a container-%. It is safe against rrp's 3-decimal echo noise.\n * In a pathologically WIDE split (~≥10 flexible children) a single arrow-key\n * nudge (±~5% of the container on one child) can, once normalized over many\n * flexible siblings, fall BELOW 0.1pp of ratio and be skipped — exotic and\n * self-healing (the next, larger resize writes back). If that ever matters,\n * scale the tolerance down by the flexible child count. */\nconst FLEX_RATIO_TOLERANCE = 0.1\n\n/**\n * The minimum weight a FLEXIBLE child may hold, given how many flexible children\n * share the split (Bug #1). A flexible `<Panel>` has no rrp `minSize` by default\n * (rrp floor is 0%), so a user can drag it to ~0 width; the resize write-back\n * would then persist a ~0 weight and the panel comes back invisible/stuck.\n *\n * The floor is `min(1, floor(90 / flexCount))` — i.e. ~1 weight unit (a flexible\n * split's weights are normalized to percentages downstream, so ~1 reads as ~1%\n * of the flexible pool). With any realistic flexible count this is exactly 1; the\n * `min(1, …)` only matters for a hypothetical 90+ flexible-child split. It is the\n * SAME value used for the rrp `minSize` (A) and the write-back clamp (B) so the\n * two defenses never disagree.\n */\nfunction flexibleFloor(flexCount: number): number {\n\t// `floor(90 / flexCount)` is ~1 for any realistic count, but goes to 0 once\n\t// flexCount > 90 — which would silently DEFEAT the floor (a 0 minSize / 0 clamp\n\t// is no floor at all). Clamp into [MIN_POSITIVE_FLOOR, 1] so the floor is always\n\t// a positive percentage even in a pathologically wide split.\n\tconst MIN_POSITIVE_FLOOR = 0.5\n\treturn Math.min(1, Math.max(MIN_POSITIVE_FLOOR, Math.floor(90 / Math.max(1, flexCount))))\n}\n\n/**\n * Clamp the FLEXIBLE entries of a full-length weights array up to `floor` (Bug #1\n * defense B). Px-sized children (a non-null constraint) are left untouched — their\n * `sizes[i]` is a preserved placeholder, not a live weight. Returns a new array;\n * a weight already ≥ floor is kept verbatim so a healthy ratio is undisturbed.\n */\nfunction clampFlexibleWeights(\n\tweights: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n): number[] {\n\tconst flexCount = weights.filter((_, i) => (constraints?.[i] ?? null) === null).length\n\tconst floor = flexibleFloor(flexCount)\n\treturn weights.map((w, i) => {\n\t\tif ((constraints?.[i] ?? null) !== null) return w // px child — leave as-is\n\t\treturn Number.isFinite(w) && w >= floor ? w : floor\n\t})\n}\n\n/** Are two panelId→percentage maps equivalent within `epsilon` percentage\n * points? Both maps must cover EXACTLY the `ids` key set — a missing key OR an\n * extra key (a key present in `a`/`b` but absent from `ids`) counts as NOT\n * equivalent so the sync is not falsely suppressed. A non-finite value\n * (NaN/±Infinity) is likewise NOT equivalent — `typeof NaN === 'number'` and\n * `Math.abs(NaN - x) > eps` is `false`, so without the `Number.isFinite` guard a\n * NaN would slip through as \"equivalent\" and wrongly suppress a legitimate\n * sync/write-back (N1). EXPORTED (pure) for direct unit coverage. */\nexport function layoutsEquivalent(\n\ta: Record<string, number>,\n\tb: Record<string, number>,\n\tids: readonly string[],\n\tepsilon: number = LAYOUT_EPSILON,\n): boolean {\n\t// Exact key-set match: any key in `a` or `b` that is not in `ids` (extra key)\n\t// makes the maps non-equivalent. `ids` is the authoritative compared set.\n\tconst idSet = new Set(ids)\n\tfor (const k of Object.keys(a)) if (!idSet.has(k)) return false\n\tfor (const k of Object.keys(b)) if (!idSet.has(k)) return false\n\tfor (const id of ids) {\n\t\tconst av = a[id]\n\t\tconst bv = b[id]\n\t\tif (!Number.isFinite(av) || !Number.isFinite(bv)) return false\n\t\tif (Math.abs((av as number) - (bv as number)) > epsilon) return false\n\t}\n\treturn true\n}\n\n/**\n * Build the panel-ID→PERCENTAGE map for an imperative `setLayout`, given the\n * model's full-length raw `sizes` (per child) and `constraints`:\n *\n * - FIXED (px-pinned) children keep their CURRENT measured percentage (read\n * from the live `getLayout()`); they are NOT derived from weights, so a\n * flexible-weights change never disturbs their pixel lock.\n * - The REMAINING percentage (100 − Σ fixed%) is distributed across the\n * FLEXIBLE children in proportion to their weights.\n *\n * Returns `null` when the map can't be built faithfully (e.g. `live` is empty —\n * jsdom's stub — or a fixed child's live % is missing), so the caller skips the\n * `setLayout` rather than pushing a corrupt total. The result always sums to\n * ~100 over all children.\n */\nfunction buildSetLayoutMap(\n\tchildIds: readonly string[],\n\tsizes: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n\tlive: Record<string, number>,\n): Record<string, number> | null {\n\tconst isFixedAt = (i: number): boolean => (constraints?.[i] ?? null) !== null\n\n\t// Sum the fixed children's CURRENT live percentages (preserve their px lock).\n\tlet fixedTotal = 0\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif (!isFixedAt(i)) continue\n\t\tconst livePct = live[childIds[i]!]\n\t\t// Without a measured live % for a fixed child we cannot preserve its px\n\t\t// lock faithfully — bail so we don't corrupt the fixed child.\n\t\tif (typeof livePct !== 'number') return null\n\t\tfixedTotal += livePct\n\t}\n\n\tconst remaining = Math.max(0, 100 - fixedTotal)\n\n\t// Flexible weight pool.\n\tlet flexWeightTotal = 0\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif (isFixedAt(i)) continue\n\t\tconst w = sizes[i] ?? 0\n\t\tflexWeightTotal += w > 0 ? w : 0\n\t}\n\tconst flexibleCount = childIds.length - childIds.filter((_, i) => isFixedAt(i)).length\n\n\tconst out: Record<string, number> = {}\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tconst id = childIds[i]!\n\t\tif (isFixedAt(i)) {\n\t\t\tout[id] = live[id]!\n\t\t\tcontinue\n\t\t}\n\t\tif (flexWeightTotal <= 0) {\n\t\t\t// Degenerate flexible weights → split the remaining space evenly.\n\t\t\tout[id] = flexibleCount > 0 ? remaining / flexibleCount : remaining\n\t\t\tcontinue\n\t\t}\n\t\tconst w = sizes[i] ?? 0\n\t\tout[id] = (remaining * (w > 0 ? w : 0)) / flexWeightTotal\n\t}\n\treturn out\n}\n\n/**\n * Extract an rrp `onLayoutChanged` map's FLEXIBLE subset (skipping fixed-px\n * children) in child order and NORMALIZE it by its own sum to ratios summing to\n * ~100 — the basis `computeFlexiblePercentages` already produces for the model,\n * so the two are directly comparable. Returns the original child `indices`\n * alongside the `ratios`. Null when the map is malformed (a flexible id is\n * missing / non-finite) or there is no flexible child — the caller then never\n * writes back from it.\n */\nfunction incomingFlexRatios(\n\tchildIds: readonly string[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n\tlayout: Record<string, number>,\n): { indices: number[]; ratios: number[] } | null {\n\tconst indices: number[] = []\n\tconst raw: number[] = []\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif ((constraints?.[i] ?? null) !== null) continue\n\t\tconst v = layout[childIds[i]!]\n\t\tif (typeof v !== 'number' || !Number.isFinite(v)) return null\n\t\tindices.push(i)\n\t\traw.push(v > 0 ? v : 0)\n\t}\n\tif (indices.length === 0) return null\n\treturn { indices, ratios: toPercentages(raw) }\n}\n\nfunction SplitView(props: SplitViewProps): ReactNode {\n\tconst { node, ctx } = props\n\tconst orientation = node.orientation === 'row' ? 'horizontal' : 'vertical'\n\n\t// A child is FIXED (px-pinned) iff it carries a non-null constraint. Fixed\n\t// children are excluded from the flexible-percentage pool: their px panel does\n\t// not participate in weight-based sizing, so polluting `toPercentages` with\n\t// their weight would skew the flexible siblings' defaultSize.\n\t// `computeFlexiblePercentages` therefore normalizes percentages over the\n\t// FLEXIBLE indices only and maps them back by index; fixed children keep their\n\t// px defaultSize/min/max. (Both write-back seams derive fixed-ness from\n\t// `nodeRef.current.constraints` directly — see `handleLayoutChanged` and\n\t// `__deckApplyLayout` — so no render-closure `isFixed` helper is needed here.)\n\tconst percentageByIndex = computeFlexiblePercentages(node.sizes, node.constraints)\n\n\t// Bug #1 defense A: the minimum % a flexible child may shrink to. rrp's default\n\t// flexible `minSize` is 0% (a panel can be dragged to nothing); this floors it\n\t// so the simulator/editor can never be pulled to 0 width. Unitless string =\n\t// percentage (same convention as `defaultSize` above; a px `minSize` next to a\n\t// %-`defaultSize` is misparsed by rrp). The floor is keyed on the COUNT of\n\t// flexible children in THIS split (the same value the write-back clamp uses).\n\tconst flexCount = node.children.filter((_, i) => (node.constraints?.[i] ?? null) === null).length\n\tconst flexMinSize = String(flexibleFloor(flexCount))\n\n\t// ── M1 model→view sync plumbing ────────────────────────────────────────\n\t// The rrp Group's imperative handle (getLayout/setLayout) — the seam through\n\t// which the model becomes the source of truth for the visible split ratio.\n\tconst groupRef = useRef<GroupImperativeHandle | null>(null)\n\t// Keep the latest node reachable from the imperative ref + drag callbacks\n\t// without re-binding the ref on every render. The sync effect reads the\n\t// freshest `node` directly (it re-runs when `node.sizes` change), so it does\n\t// not go stale; this ref is for the imperative seams that capture once.\n\tconst nodeRef = useRef(node)\n\tnodeRef.current = node\n\n\tconst items: ReactNode[] = []\n\tnode.children.forEach((child, i) => {\n\t\tif (i > 0) {\n\t\t\titems.push(\n\t\t\t\t<Separator\n\t\t\t\t\tkey={`handle-${i}`}\n\t\t\t\t\tdata-deck-resize-handle=\"\"\n\t\t\t\t/>,\n\t\t\t)\n\t\t}\n\t\t// PIXEL-sized children (`fixedPx`/`minPx`) are sized in px, NOT weights, so\n\t\t// they are excluded from the flexible % pool (`computeFlexiblePercentages`)\n\t\t// and never normalized against weight-sized siblings. rrp needs CONSISTENT\n\t\t// units per panel — a px `minSize` on a %-`defaultSize` panel is misparsed\n\t\t// (the panel grabs the whole region), so a px floor MUST pair with a px\n\t\t// `defaultSize`.\n\t\tconst constraint = node.constraints?.[i] ?? null\n\t\tif (constraint?.fixedPx != null) {\n\t\t\t// `fixedPx` LOCKS the child: min===max + `preserve-pixel-size` keeps it\n\t\t\t// fixed while flexible siblings absorb group resizes.\n\t\t\tconst px = `${constraint.fixedPx}px`\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={px}\n\t\t\t\t\tminSize={px}\n\t\t\t\t\tmaxSize={px}\n\t\t\t\t\tgroupResizeBehavior=\"preserve-pixel-size\"\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t\telse if (constraint?.minPx != null) {\n\t\t\t// `minPx` FLOORS the child at a pixel minimum but leaves it DRAGGABLE\n\t\t\t// above it: px `defaultSize`+`minSize` (starts AT the floor, can't shrink\n\t\t\t// past it), NO `maxSize` so the user can widen it. `preserve-pixel-size`\n\t\t\t// puts the panel in rrp's pixel mode — WITHOUT it rrp misparses a px\n\t\t\t// `defaultSize` next to %-sized siblings and the panel grabs ~80% of the\n\t\t\t// region. It only governs WINDOW-resize behavior (keep pixel width, don't\n\t\t\t// scale); the user can still drag the separator to resize it (≥ minSize).\n\t\t\tconst px = `${constraint.minPx}px`\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={px}\n\t\t\t\t\tminSize={px}\n\t\t\t\t\tgroupResizeBehavior=\"preserve-pixel-size\"\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t\telse {\n\t\t\t// `defaultSize` seeds the FLEXIBLE child at mount; post-mount the model\n\t\t\t// becomes the source of truth via the imperative `setLayout` driven from\n\t\t\t// the sync effect below (M1). The value is passed as a STRING percentage:\n\t\t\t// rrp treats a NUMBER `defaultSize` as PIXELS, so `defaultSize={70}` would\n\t\t\t// be 70px (not 70%) — which mis-proportions a flexible sibling next to a\n\t\t\t// px-sized (`fixedPx`/`minPx`) panel and the sync then preserves the wrong\n\t\t\t// size. A unitless string is parsed as a percentage.\n\t\t\tconst pct = percentageByIndex.get(i)\n\t\t\titems.push(\n\t\t\t\t<Panel\n\t\t\t\t\tkey={panelKey(child)}\n\t\t\t\t\tid={child.id}\n\t\t\t\t\tdefaultSize={pct != null ? String(pct) : undefined}\n\t\t\t\t\tminSize={flexMinSize}\n\t\t\t\t>\n\t\t\t\t\t{renderNode(child, ctx)}\n\t\t\t\t</Panel>,\n\t\t\t)\n\t\t}\n\t})\n\n\t// A real rrp resize (pointer OR keyboard) — or our own `setLayout` echo / a\n\t// spontaneous re-measure — commits new per-Panel percentages here. The SINGLE\n\t// discriminator is a BASIS-NORMALIZED flexible-ratio compare (no gate, no echo\n\t// token): write back IFF the incoming layout's FLEXIBLE-child ratios differ from\n\t// the model's. `incomingFlexRatios` normalizes rrp's CONTAINER-% over the\n\t// flexible subset; `computeFlexiblePercentages` already does the same for the\n\t// model — so both bases sum to 100 and are directly comparable.\n\tconst handleLayoutChanged = (layout: Layout): void => {\n\t\tconst cur = nodeRef.current\n\t\tconst curChildIds = cur.children.map((c) => c.id)\n\t\tconst isFixedAt = (i: number): boolean => (cur.constraints?.[i] ?? null) !== null\n\n\t\tconst incoming = incomingFlexRatios(curChildIds, cur.constraints, layout)\n\t\tif (!incoming) return // malformed / no flexible child → never write back\n\t\tconst modelPctByIndex = computeFlexiblePercentages(cur.sizes, cur.constraints)\n\t\tconst modelNorm = incoming.indices.map((i) => modelPctByIndex.get(i) ?? 0)\n\n\t\t// Equal within a TIGHT tolerance → our own `setLayout` echo OR a\n\t\t// ratio-preserving spontaneous re-measure (mount / fixed-px re-pin / container\n\t\t// resize). SKIP: no model churn, no loop, no R2 corruption. Otherwise it is a\n\t\t// genuine user resize → WRITE BACK.\n\t\tconst differs = incoming.ratios.some(\n\t\t\t(r, k) => Math.abs(r - modelNorm[k]!) > FLEX_RATIO_TOLERANCE,\n\t\t)\n\t\tif (!differs) return\n\n\t\t// FULL-LENGTH weights (setSizes requires length === children.length). FIXED\n\t\t// children keep the model's existing weight — rrp reports a container-derived %\n\t\t// for the pinned panel which, written back, would corrupt the stored weight and\n\t\t// lose it when the constraint is later cleared. FLEXIBLE children take rrp's.\n\t\tconst weights = curChildIds.map((id, i) =>\n\t\t\tisFixedAt(i) ? cur.sizes[i] ?? 1 : layout[id]!,\n\t\t)\n\t\t// Bug #1 defense B: a flexible child dragged to ~0 width reports a ~0 ratio;\n\t\t// floor it before persisting so a 0-width weight can never reach the model\n\t\t// (the rrp `minSize` from A is the first defense, this is the write-back\n\t\t// backstop). Px children are untouched (clampFlexibleWeights skips them).\n\t\tctx.onApplyLayout(cur.id, clampFlexibleWeights(weights, cur.constraints))\n\t}\n\n\t// Ref callback exposing the resize write-back seam + the rrp Group imperative\n\t// handle on the split element. A drag round-trip (e2e) and the unit seam both\n\t// land on the same engine path; the `__deckGroupApi` handle lets hosts/tests\n\t// read the LIVE split layout (M1 model→view sync seam).\n\tconst setSplitRef = (el: DeckSplitElement | null): void => {\n\t\tif (el) {\n\t\t\tel.__deckApplyLayout = (weights: number[]) => {\n\t\t\t\t// Mirror handleLayoutChanged: a fixed child's stored weight is never\n\t\t\t\t// overwritten by an incoming (container-derived) value — preserve\n\t\t\t\t// node.sizes[i] for fixed indices so clearing the constraint later\n\t\t\t\t// restores the original weight. Flexible indices take the supplied value.\n\t\t\t\t// Derive fixed-ness from `nodeRef.current.constraints` (the SAME fresh\n\t\t\t\t// node we read sizes from) so this seam has no fresh-sizes/stale-constraints\n\t\t\t\t// asymmetry. Mirrors `isFixedAt` in handleLayoutChanged.\n\t\t\t\tconst cur = nodeRef.current\n\t\t\t\tconst isFixedAt = (i: number): boolean => (cur.constraints?.[i] ?? null) !== null\n\t\t\t\tconst next = weights.map((w, i) => (isFixedAt(i) ? cur.sizes[i] ?? 1 : w))\n\t\t\t\t// Bug #1 defense B (same clamp as handleLayoutChanged): floor flexible\n\t\t\t\t// weights so a near-0 drag can never persist a 0-width panel.\n\t\t\t\tctx.onApplyLayout(cur.id, clampFlexibleWeights(next, cur.constraints))\n\t\t\t}\n\t\t\t// Expose the rrp Group imperative handle (getLayout/setLayout) so a host —\n\t\t\t// and the jsdom `[M1-seam-live]` test — can reach the live split layout.\n\t\t\t// May be null until the Group mounts its handle; the getter reads the\n\t\t\t// freshest ref each call.\n\t\t\tObject.defineProperty(el, '__deckGroupApi', {\n\t\t\t\tconfigurable: true,\n\t\t\t\tget: () => groupRef.current ?? undefined,\n\t\t\t})\n\t\t}\n\t}\n\n\t// ── M1 model→view sync ─────────────────────────────────────────────────\n\t// Push the model's FLEXIBLE weights into the live Group via `setLayout` so the\n\t// model is the source of truth for the visible split post-mount (without it rrp\n\t// freezes at the mount-time `defaultSize`). Reads the FRESHEST `node` from the\n\t// ref so an external `setSizes` syncs against the latest model. Returns true if\n\t// a `setLayout` was actually pushed.\n\tconst runSync = useCallback((): boolean => {\n\t\tconst api = groupRef.current\n\t\tif (!api) return false\n\t\tconst cur = nodeRef.current\n\t\tconst curChildIds = cur.children.map((c) => c.id)\n\n\t\tconst live = api.getLayout()\n\t\t// jsdom's stub returns `{}` (no measured geometry) — buildSetLayoutMap then\n\t\t// returns null for any fixed child, and for the all-flexible case the\n\t\t// equivalence check below sees missing live ids and proceeds; but `setLayout`\n\t\t// is a no-op under jsdom anyway. In a real renderer `live` is populated.\n\t\tconst targetMap = buildSetLayoutMap(curChildIds, cur.sizes, cur.constraints, live)\n\t\tif (!targetMap) return false\n\n\t\t// SET-side redundant-push skip: don't re-push a layout the live Group already\n\t\t// satisfies. This is the loop break on the SET side — pushing an identical\n\t\t// layout would re-emit `onLayoutChanged`, but its flexible ratios match the\n\t\t// model so the normalized compare in `handleLayoutChanged` skips the write-back\n\t\t// anyway; this skip just avoids the redundant work.\n\t\tif (layoutsEquivalent(live, targetMap, curChildIds)) return false\n\n\t\tapi.setLayout(targetMap)\n\t\treturn true\n\t}, [])\n\n\t// Re-run the sync whenever the model's raw weights change (every external\n\t// `setSizes`). The model is stable DURING a user drag — the write-back lands on\n\t// release — so this effect does not fire mid-drag and never fights the live\n\t// pointer snapshot. An external `setSizes` (incl. one after an away-and-back\n\t// drag) syncs immediately (R3): there is no drag flag to get stuck.\n\tconst sizesKey = node.sizes.join(',')\n\t// Child COUNT is part of the sync key (Bug #2 follow-up): the `key={children.\n\t// length}` on the Group remounts it on a cardinality change, and a remount\n\t// reseeds the rrp layout from each Panel's `defaultSize` — for a `minPx` column\n\t// that is the floor (the device-min), NOT the user's last-dragged width. Re-\n\t// running `runSync` after the remount re-pushes the model's stored weights into\n\t// the fresh Group so a surviving split's proportions are restored rather than\n\t// snapping back to the per-Panel defaults. A pure weight resize (same count)\n\t// already re-syncs via `sizesKey`; this only adds the count edge.\n\tconst childCountKey = node.children.length\n\tuseEffect(() => {\n\t\trunSync()\n\t\t// Keyed on `sizesKey` + `childCountKey`; `runSync` reads `node`/`childIds`/\n\t\t// `constraints` fresh from the ref, so they cannot go stale relative to either.\n\t}, [sizesKey, childCountKey, runSync])\n\n\t// The split-level `data-*` attributes ride on an outer wrapper rather than\n\t// the Group itself, so hosts/tests can target them regardless of how the\n\t// library forwards unknown props. `data-deck-sizes` mirrors the model's\n\t// CURRENT raw weights so the render is a function of model.sizes.\n\treturn (\n\t\t<div\n\t\t\tref={setSplitRef}\n\t\t\tdata-deck-split={node.id}\n\t\t\tdata-orientation={node.orientation}\n\t\t\tdata-deck-sizes={node.sizes.join(',')}\n\t\t\tstyle={{ width: '100%', height: '100%' }}\n\t\t>\n\t\t\t<Group\n\t\t\t\t// Bug #2 (white-screen crash guard): KEY the Group on its child COUNT.\n\t\t\t\t// rrp v4.10 caches the previous layout in a ref keyed by group id; on a\n\t\t\t\t// render where the child count CHANGES (a panel closed/split — the split\n\t\t\t\t// id stays 'root' so the Group instance is otherwise reused), rrp's\n\t\t\t\t// commit-phase layout effect synchronously validates the STALE\n\t\t\t\t// (old-length) cached layout against the NEW (different-length)\n\t\t\t\t// constraints and throws `Invalid N panel layout`. That throw escapes\n\t\t\t\t// SplitView during commit and unmounts the whole tree (white screen).\n\t\t\t\t// Keying on `node.children.length` remounts the Group with a fresh\n\t\t\t\t// internal layout sized to the new child count ONLY when the count\n\t\t\t\t// changes — an ordinary weight resize never changes the key, so this\n\t\t\t\t// adds NO remount on drags. The model→view sync (runSync) re-binds\n\t\t\t\t// `groupRef` on remount and re-pushes the model's weights via the\n\t\t\t\t// `sizesKey` effect; the simulator/console native overlay re-anchors via\n\t\t\t\t// its slot ref callback (same path as a tab switch). See\n\t\t\t\t// dock-view-robustness.test.tsx \"3 → 2\" for the regression.\n\t\t\t\tkey={node.children.length}\n\t\t\t\tgroupRef={groupRef}\n\t\t\t\torientation={orientation}\n\t\t\t\tonLayoutChanged={handleLayoutChanged}\n\t\t\t\tstyle={{ width: '100%', height: '100%' }}\n\t\t\t>\n\t\t\t\t{items}\n\t\t\t</Group>\n\t\t</div>\n\t)\n}\n\n/** Stable React key for a child node (split id or group id). */\nfunction panelKey(node: LayoutNode): string {\n\treturn node.id\n}\n\nfunction renderGroup(node: TabGroupNode, ctx: RenderContext): ReactNode {\n\t// A group is a stateful component (it tracks the live drop-hover zone during a\n\t// drag), so render it via JSX with a STABLE key so React preserves that hover\n\t// state across model emissions rather than remounting on every snapshot.\n\treturn <GroupView key={node.id} node={node} ctx={ctx} />\n}\n\ninterface GroupViewProps {\n\tnode: TabGroupNode\n\tctx: RenderContext\n}\n\n/**\n * One tab GROUP: tab strip (draggable tabs) + active body + the imperative\n * `__deckHandleDrop` seam + a geometry-driven drop indicator while a drag hovers.\n */\nfunction GroupView(props: GroupViewProps): ReactNode {\n\tconst { node, ctx } = props\n\n\t// The live drop zone under the pointer during a drag-over (null = no drag over\n\t// this group). Drives the `data-deck-drop-zone` indicator. jsdom can't produce\n\t// real geometry (getBoundingClientRect is 0), so this path is exercised by the\n\t// real-pointer e2e `it.todo`s; here we implement it for real-browser fidelity.\n\tconst [dropZone, setDropZone] = useState<DropZone | null>(null)\n\n\t// Keep the latest node + redock callback reachable from the imperative ref\n\t// seam without re-running the ref effect when they change identity. The seam\n\t// always anchors a split at the group's CURRENT active panel.\n\tconst nodeRef = useRef(node)\n\tnodeRef.current = node\n\tconst redockRef = useRef(ctx.onRedock)\n\tredockRef.current = ctx.onRedock\n\n\t// Ref-callback exposing the drop seam on the group element. Mirrors\n\t// `setSplitRef`/`__deckApplyLayout`: the gesture and the unit seam both land on\n\t// the same `onRedock` engine path. The seam owns choosing the target panel —\n\t// the group's active panel is the natural anchor for an edge split.\n\tconst setGroupRef = (el: DeckGroupElement | null): void => {\n\t\tif (el) {\n\t\t\tel.__deckHandleDrop = (draggedPanelId: string, zone: DropZone) => {\n\t\t\t\tconst current = nodeRef.current\n\t\t\t\tredockRef.current(current.id, current.active, draggedPanelId, zone)\n\t\t\t}\n\t\t}\n\t}\n\n\t// ── drag-over geometry ────────────────────────────────────────────────\n\t// Compute the hovered zone from the pointer position relative to the group\n\t// rect. `preventDefault` is required for the element to be a valid drop target.\n\tconst handleDragOver = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\t// Opt-in (suppressReorderOnlyDropIndicator): a `reorder-only` panel can ONLY\n\t\t// land via a within-group tab-strip reorder (handled by the strip handlers,\n\t\t// which paint no indicator). Anywhere the group body would resolve to — its\n\t\t// own edges or ANY other group — is a no-op for it, so the blue drop\n\t\t// highlight there is misleading. When opted in, suppress it entirely while\n\t\t// such a panel is in flight. Default keeps the geometry-only indicator (the\n\t\t// capability gate still rejects the drop at drop time).\n\t\tif (\n\t\t\tctx.suppressReorderOnlyDropIndicator\n\t\t\t&& activeDragPanelId !== null\n\t\t\t&& ctx.registry.get(activeDragPanelId)?.dropPolicy === 'reorder-only'\n\t\t) {\n\t\t\tsetDropZone(null)\n\t\t\treturn\n\t\t}\n\t\tconst rect = e.currentTarget.getBoundingClientRect()\n\t\tconst zone = computeDropZone(\n\t\t\t{ width: rect.width, height: rect.height },\n\t\t\t{ x: e.clientX - rect.left, y: e.clientY - rect.top },\n\t\t)\n\t\tsetDropZone(zone)\n\t}\n\n\tconst handleDragLeave = (): void => {\n\t\tsetDropZone(null)\n\t}\n\n\t// On drop, recover the dragged panel id (custom MIME first, falling back to\n\t// the source tab marker carried as text), then commit via the same seam path.\n\tconst handleDrop = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\tconst rect = e.currentTarget.getBoundingClientRect()\n\t\tconst zone = computeDropZone(\n\t\t\t{ width: rect.width, height: rect.height },\n\t\t\t{ x: e.clientX - rect.left, y: e.clientY - rect.top },\n\t\t)\n\t\tsetDropZone(null)\n\t\tconst dragged\n\t\t\t= e.dataTransfer.getData(DRAG_PANEL_MIME) || e.dataTransfer.getData('text/plain')\n\t\t// Validate the payload before committing a re-dock:\n\t\t// - M4: a `text/plain` drop can carry arbitrary external text (a selection,\n\t\t// a file path); it must at least be a REGISTERED panel.\n\t\t// - M2: registry membership is NOT tree membership. A registered-but-absent\n\t\t// panel (closed out of the tree / never docked) passes the registry guard\n\t\t// yet drives `movePanel`/`extractPanel` on an id missing from the tree,\n\t\t// which THROWS `panel not found`. It must be a NO-OP — also require the id\n\t\t// to be present in the CURRENT tree.\n\t\tif (!dragged || ctx.registry.get(dragged) === undefined || !ctx.isPanelInTree(dragged)) return\n\t\t// Pointer-derived insertion index for a within-group REORDER\n\t\t// (`dropPolicy:'reorder-only'`): measure this group's tab rects and map the\n\t\t// pointer x onto an index. Ignored by `handleRedock` for every non-reorder\n\t\t// path, so it is safe to compute unconditionally.\n\t\tconst tabRects = Array.from(\n\t\t\te.currentTarget.querySelectorAll<HTMLElement>('[data-deck-tab]'),\n\t\t).map((el) => {\n\t\t\tconst r = el.getBoundingClientRect()\n\t\t\treturn { left: r.left, width: r.width }\n\t\t})\n\t\tconst reorderIndex = computeReorderIndex(tabRects, e.clientX)\n\t\tctx.onRedock(node.id, node.active, dragged, zone, reorderIndex)\n\t}\n\n\t// The tab STRIP is a first-class REORDER drop target. It sits at the TOP of\n\t// the group, so a tab dropped onto it would otherwise resolve to the group's\n\t// `top` EDGE zone via `computeDropZone` — which a `reorder-only` panel rejects\n\t// as a no-op, so reordering by dragging within the tab bar would never work.\n\t// Intercept strip drops here and commit a `center` re-dock carrying the\n\t// pointer-x insertion index (`handleRedock` reorders within the group).\n\t// `stopPropagation` keeps the group's edge/center drop handlers from also\n\t// firing for the same gesture.\n\tconst handleTabStripDragOver = (e: DragEvent<HTMLDivElement>): void => {\n\t\tif (!e.dataTransfer.types.includes(DRAG_PANEL_MIME)) return\n\t\te.preventDefault()\n\t\te.stopPropagation()\n\t\tsetDropZone(null)\n\t}\n\tconst handleTabStripDrop = (e: DragEvent<HTMLDivElement>): void => {\n\t\te.preventDefault()\n\t\te.stopPropagation()\n\t\tsetDropZone(null)\n\t\tconst dragged\n\t\t\t= e.dataTransfer.getData(DRAG_PANEL_MIME) || e.dataTransfer.getData('text/plain')\n\t\tif (!dragged || ctx.registry.get(dragged) === undefined || !ctx.isPanelInTree(dragged)) return\n\t\tconst tabRects = Array.from(\n\t\t\te.currentTarget.querySelectorAll<HTMLElement>('[data-deck-tab]'),\n\t\t).map((el) => {\n\t\t\tconst r = el.getBoundingClientRect()\n\t\t\treturn { left: r.left, width: r.width }\n\t\t})\n\t\tconst reorderIndex = computeReorderIndex(tabRects, e.clientX)\n\t\tctx.onRedock(node.id, node.active, dragged, 'center', reorderIndex)\n\t}\n\n\t// Panels that contribute a tab to the strip (`hideTab` panels carry their own\n\t// chrome — e.g. the simulator's device picker — so the engine tab is omitted).\n\t// When NONE remain the tab strip is not rendered at all and the body fills the\n\t// whole group region.\n\tconst visibleTabs = node.panels.filter((panelId) => !ctx.registry.get(panelId)?.hideTab)\n\n\t// FILL LAYOUT (FIX 2a): the group must be a flex COLUMN that fills its\n\t// allotted panel region so a leaf native slot can stretch to the full area.\n\t// Without this the group div is content-height (the tab strip only), the\n\t// active body / NativeSlot measures 0 height, the view-anchor publishes a\n\t// collapsed rect, and the simulator WebContentsView is invisible. The tab\n\t// strip is `shrink: 0`; the active body takes the remaining space (flex: 1).\n\treturn (\n\t\t<div\n\t\t\tref={setGroupRef}\n\t\t\tdata-deck-group={node.id}\n\t\t\tonDragOver={handleDragOver}\n\t\t\tonDragLeave={handleDragLeave}\n\t\t\tonDrop={handleDrop}\n\t\t\tstyle={{\n\t\t\t\tposition: 'relative',\n\t\t\t\tdisplay: 'flex',\n\t\t\t\tflexDirection: 'column',\n\t\t\t\twidth: '100%',\n\t\t\t\theight: '100%',\n\t\t\t\tminWidth: 0,\n\t\t\t\tminHeight: 0,\n\t\t\t}}\n\t\t>\n\t\t\t{visibleTabs.length > 0 ? (\n\t\t\t\t<div\n\t\t\t\trole=\"tablist\"\n\t\t\t\tstyle={{ flexShrink: 0 }}\n\t\t\t\tonDragOver={handleTabStripDragOver}\n\t\t\t\tonDrop={handleTabStripDrop}\n\t\t\t>\n\t\t\t\t{visibleTabs.map((panelId) => {\n\t\t\t\t\tconst active = panelId === node.active\n\t\t\t\t\tconst descriptor = ctx.registry.get(panelId)\n\t\t\t\t\tconst title = descriptor?.title ?? panelId\n\t\t\t\t\t// PanelCapabilities (GOAL A source): a `draggable:false` panel's tab\n\t\t\t\t\t// cannot be picked up — omit the marker entirely (an absent attribute,\n\t\t\t\t\t// not `draggable=\"false\"`, matches the \"no drag source\" contract).\n\t\t\t\t\tconst isDraggable = descriptor?.draggable !== false\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tkey={panelId}\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\trole=\"tab\"\n\t\t\t\t\t\t\tdraggable={isDraggable ? 'true' : undefined}\n\t\t\t\t\t\t\tdata-deck-tab={panelId}\n\t\t\t\t\t\t\tdata-active={active ? 'true' : 'false'}\n\t\t\t\t\t\t\tonDragStart={(e) => {\n\t\t\t\t\t\t\t\t// A press that BEGINS on the close affordance must not drag the\n\t\t\t\t\t\t\t\t// whole tab: the HTML5 drag source is the draggable ANCESTOR\n\t\t\t\t\t\t\t\t// (this tab) regardless of which descendant the pointer landed\n\t\t\t\t\t\t\t\t// on, so a descendant's stopPropagation cannot cancel it —\n\t\t\t\t\t\t\t\t// detect the origin here and abort the drag. `closest` walks up\n\t\t\t\t\t\t\t\t// from the event target; a hit means the gesture started on ×.\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\te.target instanceof Element\n\t\t\t\t\t\t\t\t\t&& e.target.closest('[data-deck-tab-close]') !== null\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Record the dragged panel id so the drop target can recover it.\n\t\t\t\t\t\t\t\te.dataTransfer.setData(DRAG_PANEL_MIME, panelId)\n\t\t\t\t\t\t\t\te.dataTransfer.setData('text/plain', panelId)\n\t\t\t\t\t\t\t\te.dataTransfer.effectAllowed = 'move'\n\t\t\t\t\t\t\t\t// Also expose it to the dragover indicator path (DataTransfer\n\t\t\t\t\t\t\t\t// values are unreadable during dragover).\n\t\t\t\t\t\t\t\tactiveDragPanelId = panelId\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonDragEnd={() => {\n\t\t\t\t\t\t\t\tactiveDragPanelId = null\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\tif (!active) ctx.onActivate(node.id, panelId)\n\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{title}\n\t\t\t\t\t\t\t{/* Close affordance — a `role=\"button\"` SPAN (NOT a nested <button>:\n\t\t\t\t\t\t\t an interactive button may not be a descendant of another button —\n\t\t\t\t\t\t\t invalid HTML + illegal a11y nesting). `tabIndex={0}` + the\n\t\t\t\t\t\t\t Enter/Space keydown keep it keyboard/AT-operable. Suppressed when\n\t\t\t\t\t\t\t the WHOLE tree has one panel left (`canClose` false). Activate is\n\t\t\t\t\t\t\t kept off the tab (click stopPropagation) and a tab drag that\n\t\t\t\t\t\t\t begins here is cancelled by the tab's onDragStart guard above. */}\n\t\t\t\t\t\t\t{ctx.canClose && descriptor?.closable !== false\n\t\t\t\t\t\t\t\t? (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\trole=\"button\"\n\t\t\t\t\t\t\t\t\t\ttabIndex={0}\n\t\t\t\t\t\t\t\t\t\tdata-deck-tab-close={panelId}\n\t\t\t\t\t\t\t\t\t\taria-label={`Close ${title}`}\n\t\t\t\t\t\t\t\t\t\tonPointerDown={(e) => e.stopPropagation()}\n\t\t\t\t\t\t\t\t\t\tonClick={(e) => {\n\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\t\t\tctx.onClose(panelId)\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\tonKeyDown={(e) => {\n\t\t\t\t\t\t\t\t\t\t\tif (e.key === 'Enter' || e.key === ' ') {\n\t\t\t\t\t\t\t\t\t\t\t\te.stopPropagation()\n\t\t\t\t\t\t\t\t\t\t\t\te.preventDefault()\n\t\t\t\t\t\t\t\t\t\t\t\tctx.onClose(panelId)\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t\t×\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: null}\n\t\t\t\t\t\t</button>\n\t\t\t\t\t)\n\t\t\t\t})}\n\t\t\t</div>\n\t\t\t) : null}\n\t\t\t{renderActiveBody(node, ctx)}\n\t\t\t{dropZone ? <DropIndicator zone={dropZone} /> : null}\n\t\t</div>\n\t)\n}\n\n/**\n * Translate a drop zone into the indicator overlay's geometry. `center` covers\n * the whole group (a tab-join highlight); each edge zone is a HALF-band ribbon\n * pinned to that edge. Pure presentation — the host can re-skin via the\n * `data-deck-drop-zone` attribute; these inline styles are a sane default.\n */\nfunction dropIndicatorStyle(zone: DropZone): Record<string, string> {\n\tconst base: Record<string, string> = {\n\t\tposition: 'absolute',\n\t\t// `none` so the overlay never steals the drag-over/drop events from the\n\t\t// group beneath it (otherwise the indicator itself would become the target).\n\t\tpointerEvents: 'none',\n\t\tbackground: 'rgba(64, 128, 255, 0.25)',\n\t\toutline: '2px solid rgba(64, 128, 255, 0.6)',\n\t}\n\tswitch (zone) {\n\t\tcase 'left':\n\t\t\treturn { ...base, left: '0', top: '0', width: '50%', height: '100%' }\n\t\tcase 'right':\n\t\t\treturn { ...base, right: '0', top: '0', width: '50%', height: '100%' }\n\t\tcase 'top':\n\t\t\treturn { ...base, left: '0', top: '0', width: '100%', height: '50%' }\n\t\tcase 'bottom':\n\t\t\treturn { ...base, left: '0', bottom: '0', width: '100%', height: '50%' }\n\t\tcase 'center':\n\t\tdefault:\n\t\t\treturn { ...base, left: '0', top: '0', width: '100%', height: '100%' }\n\t}\n}\n\n/** The drop-zone highlight rendered over a group during a drag-over. */\nfunction DropIndicator(props: { zone: DropZone }): ReactNode {\n\treturn <div data-deck-drop-zone={props.zone} style={dropIndicatorStyle(props.zone)} />\n}\n\n/**\n * Render a tab group's body region under DOM-panel KEEPALIVE.\n *\n * - DOM panels: ALL of the group's DOM panels are mounted SIMULTANEOUSLY, each\n * under a STABLE React key (`dom-${panelId}`) so switching the active tab never\n * remounts a body — React state + scroll persist across A→B→A. The active body\n * fills the region (flex:1); inactive ones stay in the DOM but `display:none`.\n * Each body's host renderer receives `{ active }` and is re-invoked with the\n * new flag on every activation change (no remount), so a host can fire\n * on-activation side effects off the false→true edge.\n * - Native panels: EXEMPT from keepalive. The single ACTIVE native panel mounts a\n * `NativeSlot` (keyed on the active id, so deactivation unmounts it and fires\n * `bindNativeSlot(id, null)`); inactive native panels render nothing. Keeping a\n * bound native slot mounted-but-hidden would collapse its WebContentsView rect.\n */\nfunction renderActiveBody(node: TabGroupNode, ctx: RenderContext): ReactNode {\n\tconst activeId = node.active\n\tif (!activeId) return null\n\n\tconst activeDescriptor = ctx.registry.get(activeId)\n\n\t// Native active panel → active-only NativeSlot (no keepalive).\n\tconst nativeSlot\n\t\t= activeDescriptor?.kind === 'native'\n\t\t\t? (\n\t\t\t\t<NativeSlot\n\t\t\t\t\tkey={activeId}\n\t\t\t\t\tpanelId={activeId}\n\t\t\t\t\tbindNativeSlot={ctx.bindNativeSlot}\n\t\t\t\t/>\n\t\t\t\t)\n\t\t\t: null\n\n\t// Every DOM (non-native) panel in the group is kept alive: mounted up-front,\n\t// hidden unless active. A panel with no descriptor is treated as DOM (render\n\t// via the host's renderer) — matching the pre-keepalive fallback.\n\tconst domBodies = node.panels\n\t\t.filter((panelId) => ctx.registry.get(panelId)?.kind !== 'native')\n\t\t.map((panelId) => {\n\t\t\tconst active = panelId === activeId\n\t\t\treturn (\n\t\t\t\t<div\n\t\t\t\t\tkey={`dom-${panelId}`}\n\t\t\t\t\tdata-deck-panel-body={panelId}\n\t\t\t\t\tstyle={{\n\t\t\t\t\t\t// Active body fills the region; inactive bodies stay mounted but\n\t\t\t\t\t\t// hidden (display:none preserves React state + scroll position).\n\t\t\t\t\t\t// A flex COLUMN container (not a bare block) so a host panel root\n\t\t\t\t\t\t// that fills via `flex:1`/`height:100%` actually stretches to the\n\t\t\t\t\t\t// full body height — without this such a root collapses to content\n\t\t\t\t\t\t// height and leaves dead space below it.\n\t\t\t\t\t\tdisplay: active ? 'flex' : 'none',\n\t\t\t\t\t\tflexDirection: 'column',\n\t\t\t\t\t\tflex: 1,\n\t\t\t\t\t\tminWidth: 0,\n\t\t\t\t\t\tminHeight: 0,\n\t\t\t\t\t}}\n\t\t\t\t>\n\t\t\t\t\t{ctx.renderDomPanel(panelId, { active })}\n\t\t\t\t</div>\n\t\t\t)\n\t\t})\n\n\treturn (\n\t\t<Fragment>\n\t\t\t{nativeSlot}\n\t\t\t{domBodies}\n\t\t</Fragment>\n\t)\n}\n\ninterface NativeSlotProps {\n\tpanelId: string\n\tbindNativeSlot: (panelId: string, el: HTMLElement | null) => void\n}\n\n/**\n * Empty anchor slot for an ACTIVE native panel. A ref-callback binds the live\n * element on mount and unbinds (`null`) on unmount. Keying the element on the\n * active panel id (in the parent) guarantees deactivation unmounts this slot —\n * firing the `null` cleanup — and re-activation mounts a fresh one, re-binding.\n */\nfunction NativeSlot(props: NativeSlotProps): ReactNode {\n\tconst { panelId, bindNativeSlot } = props\n\t// Keep the latest callback without re-running the ref effect on identity\n\t// churn of an inline `bindNativeSlot`.\n\tconst bindRef = useRef(bindNativeSlot)\n\tbindRef.current = bindNativeSlot\n\n\tconst setRef = useCallback(\n\t\t(el: HTMLDivElement | null) => {\n\t\t\tbindRef.current(panelId, el)\n\t\t},\n\t\t[panelId],\n\t)\n\n\t// FILL LAYOUT (FIX 2a): the empty anchor slot must fill the remaining group\n\t// region so the host's view-anchor measures the FULL panel rect, not a 0×0\n\t// box. `flex:1` claims the space below the tab strip; `min-*:0` lets it\n\t// shrink inside the flex column; `height:100%` is the belt-and-braces\n\t// fallback for any non-flex ancestor.\n\treturn (\n\t\t<div\n\t\t\tref={setRef}\n\t\t\tdata-deck-native-slot={panelId}\n\t\t\tstyle={{ flex: 1, minWidth: 0, minHeight: 0, height: '100%' }}\n\t\t/>\n\t)\n}\n"],"mappings":"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"}
|
package/dist/layout/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as insertPanel, c as setConstraint, d as sanitizeFlexibleWeights, f as parseLayout, h as createPanelRegistry, i as extractPanel, l as setSizes, m as validateTree, n as createLayoutModel, o as movePanel, p as serializeLayout, r as closePanel, s as setActive, t as closePanelForUser, u as splitPanel } from "../layout-
|
|
1
|
+
import { a as insertPanel, c as setConstraint, d as sanitizeFlexibleWeights, f as parseLayout, h as createPanelRegistry, i as extractPanel, l as setSizes, m as validateTree, n as createLayoutModel, o as movePanel, p as serializeLayout, r as closePanel, s as setActive, t as closePanelForUser, u as splitPanel } from "../layout-B1t15b_q.js";
|
|
2
2
|
export { closePanel, closePanelForUser, createLayoutModel, createPanelRegistry, extractPanel, insertPanel, movePanel, parseLayout, sanitizeFlexibleWeights, serializeLayout, setActive, setConstraint, setSizes, splitPanel, validateTree };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/layout/serialize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,YAAY,CAAA;AAIxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAErD;
|
|
1
|
+
{"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/layout/serialize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,YAAY,CAAA;AAIxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAErD;AAyOD,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAMxF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAyBpD"}
|
|
@@ -22,6 +22,74 @@ var MAX_DEPTH = 64;
|
|
|
22
22
|
function serializeLayout(t) {
|
|
23
23
|
return JSON.stringify(t);
|
|
24
24
|
}
|
|
25
|
+
/** Report missing / duplicate node id, registering the id when first seen. */
|
|
26
|
+
function checkNodeId(id, kind, state, problems) {
|
|
27
|
+
if (id === void 0) problems.push(`node missing string id (kind=${String(kind)})`);
|
|
28
|
+
else if (state.seenIds.has(id)) problems.push(`duplicate node id: ${id}`);
|
|
29
|
+
else state.seenIds.add(id);
|
|
30
|
+
}
|
|
31
|
+
/** Validate a single panel id within a tabgroup: type, cross-group ownership, orphan. */
|
|
32
|
+
function checkPanel(p, id, state, problems) {
|
|
33
|
+
if (typeof p !== "string") {
|
|
34
|
+
problems.push(`tabs ${id ?? "?"}: non-string panel id`);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (state.panelOwners.get(p) !== void 0) problems.push(`duplicate panel id across groups: ${p}`);
|
|
38
|
+
else state.panelOwners.set(p, id ?? "?");
|
|
39
|
+
if (state.knownPanelIds && !state.knownPanelIds.has(p)) problems.push(`orphan panel not in known panel ids: ${p}`);
|
|
40
|
+
}
|
|
41
|
+
/** Validate a `tabs` node: panels array, per-panel rules, active membership. */
|
|
42
|
+
function checkTabs(node, id, state, problems) {
|
|
43
|
+
const tg = node;
|
|
44
|
+
const panels = Array.isArray(tg.panels) ? tg.panels : null;
|
|
45
|
+
if (!panels) {
|
|
46
|
+
problems.push(`tabs ${id ?? "?"}: panels is not an array`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (panels.length === 0) problems.push(`tabs ${id ?? "?"}: empty tabgroup`);
|
|
50
|
+
for (const p of panels) checkPanel(p, id, state, problems);
|
|
51
|
+
const active = tg.active;
|
|
52
|
+
if (typeof active !== "string" || !panels.includes(active)) problems.push(`tabs ${id ?? "?"}: active ${String(active)} not in panels`);
|
|
53
|
+
}
|
|
54
|
+
/** Validate one constraint entry. Returns true if it is PX-SIZED (non-null). */
|
|
55
|
+
function checkConstraintEntry(c, id, problems) {
|
|
56
|
+
if (c === null) return false;
|
|
57
|
+
if (typeof c !== "object") {
|
|
58
|
+
problems.push(`split ${id ?? "?"}: constraint is not null nor an object: ${String(c)}`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const keys = Object.keys(c);
|
|
62
|
+
const hasFixed = keys.includes("fixedPx");
|
|
63
|
+
const hasMin = keys.includes("minPx");
|
|
64
|
+
if (keys.length !== 1 || !(hasFixed || hasMin)) problems.push(`split ${id ?? "?"}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(", ")}]`);
|
|
65
|
+
const cKey = hasFixed ? "fixedPx" : "minPx";
|
|
66
|
+
const cVal = hasFixed ? c.fixedPx : c.minPx;
|
|
67
|
+
if (typeof cVal !== "number" || !Number.isFinite(cVal) || cVal <= 0) problems.push(`split ${id ?? "?"}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`);
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Validate the OPTIONAL `constraints` field's INTRINSIC format (array shape +
|
|
72
|
+
* per-entry rules + exactly-one-of fixedPx/minPx + all-px guard), INDEPENDENTLY
|
|
73
|
+
* of `children` validity. The LENGTH-vs-children comparison lives elsewhere.
|
|
74
|
+
*/
|
|
75
|
+
function checkConstraints(raw, id, problems) {
|
|
76
|
+
const constraints = Array.isArray(raw) ? raw : null;
|
|
77
|
+
if (!constraints) {
|
|
78
|
+
problems.push(`split ${id ?? "?"}: constraints is not an array`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
let everyConstrained = constraints.length > 0;
|
|
82
|
+
for (const c of constraints) if (!checkConstraintEntry(c, id, problems)) everyConstrained = false;
|
|
83
|
+
if (everyConstrained) problems.push(`split ${id ?? "?"}: all children are px-sized constraints; at least one must be weight-sized`);
|
|
84
|
+
}
|
|
85
|
+
/** Validate the `sizes` field against a valid `children` array. */
|
|
86
|
+
function checkSizes(sizes, children, id, problems) {
|
|
87
|
+
if (!sizes || sizes.length !== children.length) {
|
|
88
|
+
problems.push(`split ${id ?? "?"}: sizes ${sizes ? sizes.length : "missing"} != children ${children.length}`);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
for (const s of sizes) if (typeof s !== "number" || !Number.isFinite(s)) problems.push(`split ${id ?? "?"}: non-finite size ${String(s)}`);
|
|
92
|
+
}
|
|
25
93
|
/**
|
|
26
94
|
* Walk an arbitrary (possibly malformed / cyclic / shared-ref) node graph and
|
|
27
95
|
* collect structural problems. Never recurses infinitely: a node already on the
|
|
@@ -30,9 +98,12 @@ function serializeLayout(t) {
|
|
|
30
98
|
*/
|
|
31
99
|
function collectProblems(root, knownPanelIds) {
|
|
32
100
|
const problems = [];
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
101
|
+
const state = {
|
|
102
|
+
seenIds: /* @__PURE__ */ new Set(),
|
|
103
|
+
seenObjects: /* @__PURE__ */ new Set(),
|
|
104
|
+
panelOwners: /* @__PURE__ */ new Map(),
|
|
105
|
+
knownPanelIds
|
|
106
|
+
};
|
|
36
107
|
const visit = (node, depth, path) => {
|
|
37
108
|
if (depth > MAX_DEPTH) {
|
|
38
109
|
problems.push(`depth exceeds ${MAX_DEPTH}`);
|
|
@@ -47,85 +118,44 @@ function collectProblems(root, knownPanelIds) {
|
|
|
47
118
|
problems.push("cycle detected: node reachable from itself");
|
|
48
119
|
return;
|
|
49
120
|
}
|
|
50
|
-
if (seenObjects.has(obj)) {
|
|
121
|
+
if (state.seenObjects.has(obj)) {
|
|
51
122
|
problems.push("shared node reference: same object appears twice");
|
|
52
123
|
return;
|
|
53
124
|
}
|
|
54
|
-
seenObjects.add(obj);
|
|
125
|
+
state.seenObjects.add(obj);
|
|
55
126
|
const n = node;
|
|
56
127
|
const kind = n.kind;
|
|
57
128
|
const id = typeof n.id === "string" ? n.id : void 0;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
else
|
|
61
|
-
|
|
62
|
-
const tg = node;
|
|
63
|
-
const panels = Array.isArray(tg.panels) ? tg.panels : null;
|
|
64
|
-
if (!panels) problems.push(`tabs ${id ?? "?"}: panels is not an array`);
|
|
65
|
-
else {
|
|
66
|
-
if (panels.length === 0) problems.push(`tabs ${id ?? "?"}: empty tabgroup`);
|
|
67
|
-
for (const p of panels) {
|
|
68
|
-
if (typeof p !== "string") {
|
|
69
|
-
problems.push(`tabs ${id ?? "?"}: non-string panel id`);
|
|
70
|
-
continue;
|
|
71
|
-
}
|
|
72
|
-
if (panelOwners.get(p) !== void 0) problems.push(`duplicate panel id across groups: ${p}`);
|
|
73
|
-
else panelOwners.set(p, id ?? "?");
|
|
74
|
-
if (knownPanelIds && !knownPanelIds.has(p)) problems.push(`orphan panel not in known panel ids: ${p}`);
|
|
75
|
-
}
|
|
76
|
-
const active = tg.active;
|
|
77
|
-
if (typeof active !== "string" || !panels.includes(active)) problems.push(`tabs ${id ?? "?"}: active ${String(active)} not in panels`);
|
|
78
|
-
}
|
|
79
|
-
} else if (kind === "split") {
|
|
80
|
-
const sp = node;
|
|
81
|
-
const children = Array.isArray(sp.children) ? sp.children : null;
|
|
82
|
-
const sizes = Array.isArray(sp.sizes) ? sp.sizes : null;
|
|
83
|
-
const orientation = sp.orientation;
|
|
84
|
-
if (orientation !== "row" && orientation !== "column") problems.push(`split ${id ?? "?"}: invalid orientation ${String(orientation)}`);
|
|
85
|
-
if (sp.constraints !== void 0) {
|
|
86
|
-
const constraints = Array.isArray(sp.constraints) ? sp.constraints : null;
|
|
87
|
-
if (!constraints) problems.push(`split ${id ?? "?"}: constraints is not an array`);
|
|
88
|
-
else {
|
|
89
|
-
let everyConstrained = constraints.length > 0;
|
|
90
|
-
for (const c of constraints) {
|
|
91
|
-
if (c === null) {
|
|
92
|
-
everyConstrained = false;
|
|
93
|
-
continue;
|
|
94
|
-
}
|
|
95
|
-
if (typeof c !== "object") {
|
|
96
|
-
everyConstrained = false;
|
|
97
|
-
problems.push(`split ${id ?? "?"}: constraint is not null nor an object: ${String(c)}`);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
const keys = Object.keys(c);
|
|
101
|
-
const hasFixed = keys.includes("fixedPx");
|
|
102
|
-
const hasMin = keys.includes("minPx");
|
|
103
|
-
if (keys.length !== 1 || !(hasFixed || hasMin)) problems.push(`split ${id ?? "?"}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(", ")}]`);
|
|
104
|
-
const cKey = hasFixed ? "fixedPx" : "minPx";
|
|
105
|
-
const cVal = hasFixed ? c.fixedPx : c.minPx;
|
|
106
|
-
if (typeof cVal !== "number" || !Number.isFinite(cVal) || cVal <= 0) problems.push(`split ${id ?? "?"}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`);
|
|
107
|
-
}
|
|
108
|
-
if (everyConstrained) problems.push(`split ${id ?? "?"}: all children are px-sized constraints; at least one must be weight-sized`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
if (!children) problems.push(`split ${id ?? "?"}: children is not an array`);
|
|
112
|
-
else {
|
|
113
|
-
if (children.length < 2) problems.push(`split ${id ?? "?"}: must have >= 2 children, has ${children.length}`);
|
|
114
|
-
if (!sizes || sizes.length !== children.length) problems.push(`split ${id ?? "?"}: sizes ${sizes ? sizes.length : "missing"} != children ${children.length}`);
|
|
115
|
-
else for (const s of sizes) if (typeof s !== "number" || !Number.isFinite(s)) problems.push(`split ${id ?? "?"}: non-finite size ${String(s)}`);
|
|
116
|
-
if (sp.constraints !== void 0 && Array.isArray(sp.constraints)) {
|
|
117
|
-
const constraints = sp.constraints;
|
|
118
|
-
if (constraints.length !== children.length) problems.push(`split ${id ?? "?"}: constraints ${constraints.length} != children ${children.length}`);
|
|
119
|
-
}
|
|
120
|
-
const nextPath = new Set(path);
|
|
121
|
-
nextPath.add(obj);
|
|
122
|
-
for (const child of children) visit(child, depth + 1, nextPath);
|
|
123
|
-
}
|
|
124
|
-
} else problems.push(`unknown node kind: ${String(kind)}`);
|
|
129
|
+
checkNodeId(id, kind, state, problems);
|
|
130
|
+
if (kind === "tabs") checkTabs(node, id, state, problems);
|
|
131
|
+
else if (kind === "split") visitSplit(node, id, depth, path, obj, state, problems, visit);
|
|
132
|
+
else problems.push(`unknown node kind: ${String(kind)}`);
|
|
125
133
|
};
|
|
126
134
|
visit(root, 0, /* @__PURE__ */ new Set());
|
|
127
135
|
return problems;
|
|
128
136
|
}
|
|
137
|
+
/** Validate a `split` node and recurse into its children. */
|
|
138
|
+
function visitSplit(node, id, depth, path, obj, state, problems, visit) {
|
|
139
|
+
const sp = node;
|
|
140
|
+
const children = Array.isArray(sp.children) ? sp.children : null;
|
|
141
|
+
const sizes = Array.isArray(sp.sizes) ? sp.sizes : null;
|
|
142
|
+
const orientation = sp.orientation;
|
|
143
|
+
if (orientation !== "row" && orientation !== "column") problems.push(`split ${id ?? "?"}: invalid orientation ${String(orientation)}`);
|
|
144
|
+
if (sp.constraints !== void 0) checkConstraints(sp.constraints, id, problems);
|
|
145
|
+
if (!children) {
|
|
146
|
+
problems.push(`split ${id ?? "?"}: children is not an array`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (children.length < 2) problems.push(`split ${id ?? "?"}: must have >= 2 children, has ${children.length}`);
|
|
150
|
+
checkSizes(sizes, children, id, problems);
|
|
151
|
+
if (sp.constraints !== void 0 && Array.isArray(sp.constraints)) {
|
|
152
|
+
const constraints = sp.constraints;
|
|
153
|
+
if (constraints.length !== children.length) problems.push(`split ${id ?? "?"}: constraints ${constraints.length} != children ${children.length}`);
|
|
154
|
+
}
|
|
155
|
+
const nextPath = new Set(path);
|
|
156
|
+
nextPath.add(obj);
|
|
157
|
+
for (const child of children) visit(child, depth + 1, nextPath);
|
|
158
|
+
}
|
|
129
159
|
function validateTree(t, knownPanelIds) {
|
|
130
160
|
if (t === null || typeof t !== "object") return ["tree is not an object"];
|
|
131
161
|
if (t.version !== 1) return [`unsupported version: ${String(t.version)}`];
|
|
@@ -586,4 +616,4 @@ function closePanelForUser(tree, panelId, registry) {
|
|
|
586
616
|
//#endregion
|
|
587
617
|
export { insertPanel as a, setConstraint as c, sanitizeFlexibleWeights as d, parseLayout as f, createPanelRegistry as h, extractPanel as i, setSizes as l, validateTree as m, createLayoutModel as n, movePanel as o, serializeLayout as p, closePanel as r, setActive as s, closePanelForUser as t, splitPanel as u };
|
|
588
618
|
|
|
589
|
-
//# sourceMappingURL=layout-
|
|
619
|
+
//# sourceMappingURL=layout-B1t15b_q.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"layout-B1t15b_q.js","names":[],"sources":["../src/layout/registry.ts","../src/layout/serialize.ts","../src/layout/sanitize.ts","../src/layout/mutations.ts","../src/layout/model.ts","../src/layout/user-actions.ts"],"sourcesContent":["/**\n * Panel registry — pure in-memory map of panel descriptors. Pure TS.\n */\nimport type { Disposable, PanelDescriptor, PanelRegistry } from './types.js'\n\nexport function createPanelRegistry(): PanelRegistry {\n\tconst map = new Map<string, PanelDescriptor>()\n\treturn {\n\t\tregister(p: PanelDescriptor): Disposable {\n\t\t\tmap.set(p.id, p)\n\t\t\treturn {\n\t\t\t\tdispose(): void {\n\t\t\t\t\t// Only drop if this exact descriptor is still registered, so a\n\t\t\t\t\t// re-register under the same id isn't undone by a stale handle.\n\t\t\t\t\tif (map.get(p.id) === p) map.delete(p.id)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tget(id: string): PanelDescriptor | undefined {\n\t\t\treturn map.get(id)\n\t\t},\n\t\tlist(): readonly PanelDescriptor[] {\n\t\t\treturn [...map.values()]\n\t\t},\n\t}\n}\n","/**\n * Serialize / parse / validate — structural integrity + acyclicity. Pure TS.\n *\n * Default-DENY: parseLayout throws on any tree validateTree would flag.\n */\nimport type { LayoutNode, LayoutTree } from './types.js'\n\nconst MAX_DEPTH = 64\n\nexport function serializeLayout(t: LayoutTree): string {\n\treturn JSON.stringify(t)\n}\n\n/** Mutable cross-node state threaded through the whole walk. */\ninterface WalkState {\n\treadonly seenIds: Set<string>\n\treadonly seenObjects: Set<object>\n\treadonly panelOwners: Map<string, string>\n\treadonly knownPanelIds: ReadonlySet<string> | null\n}\n\n/** Report missing / duplicate node id, registering the id when first seen. */\nfunction checkNodeId(id: string | undefined, kind: unknown, state: WalkState, problems: string[]): void {\n\tif (id === undefined) {\n\t\tproblems.push(`node missing string id (kind=${String(kind)})`)\n\t}\n\telse if (state.seenIds.has(id)) {\n\t\tproblems.push(`duplicate node id: ${id}`)\n\t}\n\telse {\n\t\tstate.seenIds.add(id)\n\t}\n}\n\n/** Validate a single panel id within a tabgroup: type, cross-group ownership, orphan. */\nfunction checkPanel(p: unknown, id: string | undefined, state: WalkState, problems: string[]): void {\n\tif (typeof p !== 'string') {\n\t\tproblems.push(`tabs ${id ?? '?'}: non-string panel id`)\n\t\treturn\n\t}\n\tconst owner = state.panelOwners.get(p)\n\tif (owner !== undefined) {\n\t\tproblems.push(`duplicate panel id across groups: ${p}`)\n\t}\n\telse {\n\t\tstate.panelOwners.set(p, id ?? '?')\n\t}\n\tif (state.knownPanelIds && !state.knownPanelIds.has(p)) {\n\t\tproblems.push(`orphan panel not in known panel ids: ${p}`)\n\t}\n}\n\n/** Validate a `tabs` node: panels array, per-panel rules, active membership. */\nfunction checkTabs(node: unknown, id: string | undefined, state: WalkState, problems: string[]): void {\n\tconst tg = node as { panels?: unknown; active?: unknown }\n\tconst panels = Array.isArray(tg.panels) ? (tg.panels as unknown[]) : null\n\tif (!panels) {\n\t\tproblems.push(`tabs ${id ?? '?'}: panels is not an array`)\n\t\treturn\n\t}\n\tif (panels.length === 0) {\n\t\tproblems.push(`tabs ${id ?? '?'}: empty tabgroup`)\n\t}\n\tfor (const p of panels) {\n\t\tcheckPanel(p, id, state, problems)\n\t}\n\tconst active = tg.active\n\tif (typeof active !== 'string' || !panels.includes(active)) {\n\t\tproblems.push(`tabs ${id ?? '?'}: active ${String(active)} not in panels`)\n\t}\n}\n\n/** Validate one constraint entry. Returns true if it is PX-SIZED (non-null). */\nfunction checkConstraintEntry(c: unknown, id: string | undefined, problems: string[]): boolean {\n\tif (c === null) {\n\t\treturn false\n\t}\n\tif (typeof c !== 'object') {\n\t\tproblems.push(`split ${id ?? '?'}: constraint is not null nor an object: ${String(c)}`)\n\t\treturn false\n\t}\n\t// Exactly ONE of `fixedPx` / `minPx`, value finite > 0.\n\tconst keys = Object.keys(c as object)\n\tconst hasFixed = keys.includes('fixedPx')\n\tconst hasMin = keys.includes('minPx')\n\tif (keys.length !== 1 || !(hasFixed || hasMin)) {\n\t\tproblems.push(`split ${id ?? '?'}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(', ')}]`)\n\t}\n\tconst cKey = hasFixed ? 'fixedPx' : 'minPx'\n\tconst cVal = hasFixed ? (c as { fixedPx?: unknown }).fixedPx : (c as { minPx?: unknown }).minPx\n\tif (typeof cVal !== 'number' || !Number.isFinite(cVal) || cVal <= 0) {\n\t\tproblems.push(`split ${id ?? '?'}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`)\n\t}\n\treturn true\n}\n\n/**\n * Validate the OPTIONAL `constraints` field's INTRINSIC format (array shape +\n * per-entry rules + exactly-one-of fixedPx/minPx + all-px guard), INDEPENDENTLY\n * of `children` validity. The LENGTH-vs-children comparison lives elsewhere.\n */\nfunction checkConstraints(raw: unknown, id: string | undefined, problems: string[]): void {\n\tconst constraints = Array.isArray(raw) ? (raw as unknown[]) : null\n\tif (!constraints) {\n\t\tproblems.push(`split ${id ?? '?'}: constraints is not an array`)\n\t\treturn\n\t}\n\t// Tracks whether EVERY child is PX-SIZED (a non-null constraint — `fixedPx`\n\t// OR `minPx`). Both are sized in px and excluded from the weight pool, so an\n\t// all-constrained split trips the rrp footgun (needs >= 1 weight-sized\n\t// child); only a `null` child clears it.\n\tlet everyConstrained = constraints.length > 0\n\tfor (const c of constraints) {\n\t\tif (!checkConstraintEntry(c, id, problems)) {\n\t\t\teveryConstrained = false\n\t\t}\n\t}\n\t// Guard the rrp footgun: an all-px-sized split has no weight-sized child to\n\t// absorb leftover space (rrp v4.10 requires >= 1).\n\tif (everyConstrained) {\n\t\tproblems.push(`split ${id ?? '?'}: all children are px-sized constraints; at least one must be weight-sized`)\n\t}\n}\n\n/** Validate the `sizes` field against a valid `children` array. */\nfunction checkSizes(sizes: unknown[] | null, children: unknown[], id: string | undefined, problems: string[]): void {\n\tif (!sizes || sizes.length !== children.length) {\n\t\tproblems.push(\n\t\t\t`split ${id ?? '?'}: sizes ${sizes ? sizes.length : 'missing'} != children ${children.length}`,\n\t\t)\n\t\treturn\n\t}\n\tfor (const s of sizes) {\n\t\tif (typeof s !== 'number' || !Number.isFinite(s)) {\n\t\t\tproblems.push(`split ${id ?? '?'}: non-finite size ${String(s)}`)\n\t\t}\n\t}\n}\n\n/**\n * Walk an arbitrary (possibly malformed / cyclic / shared-ref) node graph and\n * collect structural problems. Never recurses infinitely: a node already on the\n * current path (cycle) or already visited elsewhere (shared ref) is reported and\n * not descended into.\n */\nfunction collectProblems(\n\troot: unknown,\n\tknownPanelIds: ReadonlySet<string> | null,\n): string[] {\n\tconst problems: string[] = []\n\tconst state: WalkState = {\n\t\tseenIds: new Set<string>(),\n\t\tseenObjects: new Set<object>(),\n\t\tpanelOwners: new Map<string, string>(),\n\t\tknownPanelIds,\n\t}\n\n\tconst visit = (node: unknown, depth: number, path: Set<object>): void => {\n\t\tif (depth > MAX_DEPTH) {\n\t\t\tproblems.push(`depth exceeds ${MAX_DEPTH}`)\n\t\t\treturn\n\t\t}\n\t\tif (node === null || typeof node !== 'object') {\n\t\t\tproblems.push(`node is not an object: ${String(node)}`)\n\t\t\treturn\n\t\t}\n\t\tconst obj = node as object\n\t\tif (path.has(obj)) {\n\t\t\tproblems.push('cycle detected: node reachable from itself')\n\t\t\treturn\n\t\t}\n\t\tif (state.seenObjects.has(obj)) {\n\t\t\tproblems.push('shared node reference: same object appears twice')\n\t\t\treturn\n\t\t}\n\t\tstate.seenObjects.add(obj)\n\n\t\tconst n = node as { kind?: unknown; id?: unknown }\n\t\tconst kind = n.kind\n\t\tconst id = typeof n.id === 'string' ? n.id : undefined\n\n\t\tcheckNodeId(id, kind, state, problems)\n\n\t\tif (kind === 'tabs') {\n\t\t\tcheckTabs(node, id, state, problems)\n\t\t}\n\t\telse if (kind === 'split') {\n\t\t\tvisitSplit(node, id, depth, path, obj, state, problems, visit)\n\t\t}\n\t\telse {\n\t\t\tproblems.push(`unknown node kind: ${String(kind)}`)\n\t\t}\n\t}\n\n\tvisit(root, 0, new Set())\n\treturn problems\n}\n\n/** Validate a `split` node and recurse into its children. */\nfunction visitSplit(\n\tnode: unknown,\n\tid: string | undefined,\n\tdepth: number,\n\tpath: Set<object>,\n\tobj: object,\n\tstate: WalkState,\n\tproblems: string[],\n\tvisit: (node: unknown, depth: number, path: Set<object>) => void,\n): void {\n\tconst sp = node as { children?: unknown; sizes?: unknown; orientation?: unknown; constraints?: unknown }\n\tconst children = Array.isArray(sp.children) ? (sp.children as unknown[]) : null\n\tconst sizes = Array.isArray(sp.sizes) ? (sp.sizes as unknown[]) : null\n\tconst orientation = sp.orientation\n\tif (orientation !== 'row' && orientation !== 'column') {\n\t\tproblems.push(`split ${id ?? '?'}: invalid orientation ${String(orientation)}`)\n\t}\n\tif (sp.constraints !== undefined) {\n\t\tcheckConstraints(sp.constraints, id, problems)\n\t}\n\n\tif (!children) {\n\t\tproblems.push(`split ${id ?? '?'}: children is not an array`)\n\t\treturn\n\t}\n\tif (children.length < 2) {\n\t\tproblems.push(`split ${id ?? '?'}: must have >= 2 children, has ${children.length}`)\n\t}\n\tcheckSizes(sizes, children, id, problems)\n\t// LENGTH-vs-children comparison (gated on a valid children array).\n\tif (sp.constraints !== undefined && Array.isArray(sp.constraints)) {\n\t\tconst constraints = sp.constraints as unknown[]\n\t\tif (constraints.length !== children.length) {\n\t\t\tproblems.push(\n\t\t\t\t`split ${id ?? '?'}: constraints ${constraints.length} != children ${children.length}`,\n\t\t\t)\n\t\t}\n\t}\n\tconst nextPath = new Set(path)\n\tnextPath.add(obj)\n\tfor (const child of children) {\n\t\tvisit(child, depth + 1, nextPath)\n\t}\n}\n\nexport function validateTree(t: LayoutTree, knownPanelIds: ReadonlySet<string>): string[] {\n\tif (t === null || typeof t !== 'object') return ['tree is not an object']\n\tif ((t as { version?: unknown }).version !== 1) {\n\t\treturn [`unsupported version: ${String((t as { version?: unknown }).version)}`]\n\t}\n\treturn collectProblems((t as { root?: unknown }).root, knownPanelIds)\n}\n\nexport function parseLayout(json: string): LayoutTree {\n\tlet raw: unknown\n\ttry {\n\t\traw = JSON.parse(json)\n\t}\n\tcatch {\n\t\tthrow new Error('parseLayout: input is not valid JSON')\n\t}\n\tif (raw === null || typeof raw !== 'object') {\n\t\tthrow new Error('parseLayout: top-level value is not an object')\n\t}\n\tconst obj = raw as { version?: unknown; root?: unknown }\n\tif (obj.version !== 1) {\n\t\tthrow new Error(`parseLayout: unsupported version ${String(obj.version)}`)\n\t}\n\tif (obj.root === undefined || obj.root === null) {\n\t\tthrow new Error('parseLayout: missing root')\n\t}\n\t// Validate structure with no knownPanelIds constraint (orphan check N/A on\n\t// pure deserialize — caller pairs with their own registry).\n\tconst problems = collectProblems(obj.root, null)\n\tif (problems.length > 0) {\n\t\tthrow new Error(`parseLayout: illegal layout — ${problems.join('; ')}`)\n\t}\n\treturn { version: 1, root: obj.root as LayoutNode }\n}\n","/**\n * Self-healing for persisted layout trees — pure TS (no react/electron).\n *\n * A FLEXIBLE child (`constraints[i]` null / no constraints) has no enforced\n * lower weight bound in the serialized tree: `validateTree` only rejects\n * NON-FINITE sizes, not non-positive ones. A user who dragged a flexible panel\n * to ~0 width writes a ~0 (or 0, or negative) weight into the tree; on the next\n * restore that panel comes back at 0 width, invisible and effectively stuck.\n *\n * `sanitizeFlexibleWeights` heals such a tree: every flexible child whose weight\n * is NON-POSITIVE (≤ 0 or non-finite) is lifted to a minimum positive weight, so\n * a restored panel is always visible. A small-but-POSITIVE weight is left as-is\n * — weights are a relative ratio (any positive scale is valid), so a tiny\n * positive weight is healthy, not collapsed. PX-SIZED children\n * (`fixedPx`/`minPx`) are sized in pixels, NOT weights — their `sizes[i]` entry\n * is a preserved-but-unused placeholder — so they are left untouched. The\n * function is pure (clone-on-write, never mutates the input).\n */\nimport type { LayoutNode, LayoutTree, SizeConstraint, SplitNode } from './types.js'\n\n/** The healed-up value for a collapsed flexible child. A small positive weight:\n * enough to make the panel visible without stealing meaningful space from its\n * healthy siblings (the user can re-drag afterwards). */\nconst HEALED_FLEX_WEIGHT = 1\n\n/** Is child `i` of `node` flexible (weight-sized) — i.e. has no px constraint? */\nfunction isFlexibleAt(node: SplitNode, i: number): boolean {\n\treturn (node.constraints?.[i] ?? null) === null\n}\n\nfunction sanitizeNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return node\n\n\t// Recurse first so nested splits are healed regardless of this level.\n\tconst children = node.children.map(sanitizeNode)\n\n\t// Heal this split's FLEXIBLE child weights. ONLY a NON-POSITIVE weight (≤ 0 or\n\t// non-finite — the \"dragged to 0 / negative\" collapse) is healed; every\n\t// POSITIVE weight is kept verbatim, however small. Weights are a RELATIVE ratio\n\t// (any positive scale is valid — `[0.001, 0.006]` is the same layout as\n\t// `[1, 6]`), so a small-but-positive weight is healthy and must not be rewritten\n\t// (that would crush the ratio). Px children are never touched.\n\tlet changed = children.some((c, i) => c !== node.children[i])\n\tconst sizes = node.sizes.map((w, i) => {\n\t\tif (!isFlexibleAt(node, i)) return w\n\t\tif (typeof w === 'number' && Number.isFinite(w) && w > 0) return w\n\t\tchanged = true\n\t\treturn HEALED_FLEX_WEIGHT\n\t})\n\n\tif (!changed) return node\n\tconst rebuilt: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren,\n\t\tsizes,\n\t}\n\t// Preserve the constraints array verbatim (only weights are touched).\n\treturn node.constraints !== undefined\n\t\t? { ...rebuilt, constraints: node.constraints as readonly (SizeConstraint | null)[] }\n\t\t: rebuilt\n}\n\n/**\n * Return a tree in which every FLEXIBLE child with a non-positive (≤ tiny\n * threshold) weight is healed to a minimum positive weight, recursively. Px\n * children are untouched. Pure: the input is never mutated; a fully-healthy tree\n * is returned structurally unchanged (same weights).\n */\nexport function sanitizeFlexibleWeights(tree: LayoutTree): LayoutTree {\n\tconst root = sanitizeNode(tree.root)\n\treturn root === tree.root ? tree : { version: 1, root }\n}\n","/**\n * Pure tree mutations. Every function returns a NEW tree and never mutates its\n * input (clone-on-write). All structural invariants (#1) are funnelled through\n * `normalizeRoot`, which collapses empty tabgroups + single-child splits,\n * cascading upward, while keeping the root a LayoutNode.\n */\nimport type { LayoutNode, LayoutTree, Orientation, SizeConstraint, SplitNode, TabGroupNode } from './types.js'\n\n// ───────────────────────── helpers ─────────────────────────\n\nfunction tg(id: string, panels: string[], active: string): TabGroupNode {\n\treturn { kind: 'tabs', id, panels, active }\n}\n\n/** Re-derive active after panels changed: keep current active if still present,\n * else select panels[min(removedIndex, len-1)] (clamp to last). */\nfunction deriveActive(panels: string[], prevActive: string, removedIndex: number): string {\n\tif (panels.length === 0) return ''\n\tif (panels.includes(prevActive)) return prevActive\n\tconst idx = Math.min(removedIndex, panels.length - 1)\n\treturn panels[Math.max(0, idx)]!\n}\n\n/**\n * Collapse a (already child-normalized) node:\n * - tabgroup with 0 panels => null (drop)\n * - split: drop null children; if 0 left => null; if 1 left => that child;\n * else rebuild with repaired sizes.\n */\nfunction normalize(node: LayoutNode): LayoutNode | null {\n\tif (node.kind === 'tabs') {\n\t\treturn node.panels.length === 0 ? null : node\n\t}\n\t// split: normalize children first.\n\tconst kept: LayoutNode[] = []\n\tconst keptSizes: number[] = []\n\tconst keptConstraints: (SizeConstraint | null)[] = []\n\tconst hadConstraints = node.constraints !== undefined\n\tnode.children.forEach((child, i) => {\n\t\tconst n = normalize(child)\n\t\tif (n !== null) {\n\t\t\tkept.push(n)\n\t\t\tkeptSizes.push(node.sizes[i] ?? 1)\n\t\t\tif (hadConstraints) keptConstraints.push(node.constraints![i] ?? null)\n\t\t}\n\t})\n\tif (kept.length === 0) return null\n\tif (kept.length === 1) return kept[0]!\n\tconst rebuilt: SplitNode = { kind: 'split', id: node.id, orientation: node.orientation, children: kept, sizes: keptSizes }\n\tif (!hadConstraints) return rebuilt\n\t// M3 repair: dropping children can leave a multi-child split whose survivors\n\t// are ALL fixed-px (the sole flexible child was the one removed). That tree is\n\t// rejected by validateTree (rrp needs >= 1 weight-sized child). Deterministically\n\t// clear the LAST survivor's constraint so >= 1 flexible child remains.\n\tconst allFixed = keptConstraints.length > 0 && !keptConstraints.some(c => c === null)\n\tif (allFixed) {\n\t\tkeptConstraints[keptConstraints.length - 1] = null\n\t}\n\treturn { ...rebuilt, constraints: keptConstraints }\n}\n\n/** Apply collapse to the root, guaranteeing the result is a LayoutNode. A root\n * that collapses to nothing is illegal upstream (we never empty the last group\n * without a replacement), so we only reach here with a survivor. */\nfunction normalizeRoot(root: LayoutNode): LayoutNode {\n\tconst n = normalize(root)\n\tif (n === null) {\n\t\tthrow new Error('mutation would empty the entire layout')\n\t}\n\treturn n\n}\n\ninterface Located {\n\tgroup: TabGroupNode\n}\n\nfunction findGroupContaining(root: LayoutNode, panelId: string): Located | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.panels.includes(panelId)) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found ? { group: found } : null\n}\n\nfunction findGroupById(root: LayoutNode, groupId: string): TabGroupNode | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.id === groupId) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/** Collect every panelId present anywhere in the tree. */\nfunction collectPanelIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (n.kind === 'tabs') {\n\t\t\tfor (const p of n.panels) out.add(p)\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn out\n}\n\n/** True if `panelId` exists in any tabgroup of the tree. */\nfunction hasPanel(root: LayoutNode, id: string): boolean {\n\treturn collectPanelIds(root).has(id)\n}\n\n/** Collect every node id present anywhere in the tree (splits + tabgroups). */\nfunction collectNodeIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tout.add(n.id)\n\t\tif (n.kind === 'split') n.children.forEach(walk)\n\t}\n\twalk(root)\n\treturn out\n}\n\n/**\n * Mint an id from `base` that is not already in `taken`. If `base` is free,\n * return it; otherwise append an incrementing `#N` suffix until free. Mutates\n * `taken` so successive calls in the same mutation can't collide with each other.\n */\nfunction freshId(taken: Set<string>, base: string): string {\n\tlet candidate = base\n\tlet n = 2\n\twhile (taken.has(candidate)) {\n\t\tcandidate = `${base}#${n}`\n\t\tn += 1\n\t}\n\ttaken.add(candidate)\n\treturn candidate\n}\n\nfunction findSplitById(root: LayoutNode, splitId: string): SplitNode | null {\n\tlet found: SplitNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'split') {\n\t\t\tif (n.id === splitId) found = n\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/**\n * Rebuild the tree, replacing the node with matching id by `replacement`\n * (which may be null to delete — handled by collapse). This produces a fresh\n * object graph (clone-on-write); untouched branches are still rebuilt so the\n * input is never shared.\n */\nfunction replaceNode(node: LayoutNode, targetId: string, replacement: LayoutNode): LayoutNode {\n\tif (node.id === targetId) return replacement\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(c => replaceNode(c, targetId, replacement)),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction cloneNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(cloneNode),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction wrap(root: LayoutNode): LayoutTree {\n\treturn { version: 1, root }\n}\n\n// ───────────────────────── mutations ─────────────────────────\n\nexport function setSizes(t: LayoutTree, splitId: string, sizes: readonly number[]): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setSizes: split not found: ${splitId}`)\n\tif (sizes.length !== target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setSizes: sizes length ${sizes.length} != children length ${target.children.length}`,\n\t\t)\n\t}\n\tif (!sizes.every(s => Number.isFinite(s))) {\n\t\tthrow new Error(`setSizes: every size must be a finite number, got [${sizes.join(', ')}]`)\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...sizes],\n\t}\n\t// setSizes must NOT touch constraints — carry them through unchanged.\n\tconst withConstraints: SplitNode = target.constraints !== undefined\n\t\t? { ...replacement, constraints: [...target.constraints] }\n\t\t: replacement\n\treturn wrap(replaceNode(t.root, splitId, withConstraints))\n}\n\n/**\n * Set (or clear) the fixed-px constraint on child `childIndex` of split\n * `splitId`. Pure (clone-on-write). Lazily materializes an all-null constraints\n * array aligned with `children` when absent, then writes the slot. Clearing\n * (`constraint === null`) keeps the array even if all entries become null.\n */\nexport function setConstraint(\n\tt: LayoutTree,\n\tsplitId: string,\n\tchildIndex: number,\n\tconstraint: SizeConstraint | null,\n): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setConstraint: split not found: ${splitId}`)\n\tif (!Number.isInteger(childIndex)) {\n\t\tthrow new Error(`setConstraint: childIndex must be an integer, got ${childIndex}`)\n\t}\n\tif (childIndex < 0 || childIndex >= target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setConstraint: childIndex ${childIndex} out of range [0, ${target.children.length})`,\n\t\t)\n\t}\n\tconst base: (SizeConstraint | null)[] = target.constraints !== undefined\n\t\t? [...target.constraints]\n\t\t: target.children.map(() => null)\n\tbase[childIndex] = constraint\n\t// M3 guard: never produce an all-FIXED-px split. If applying this constraint\n\t// would leave EVERY child `fixedPx`-locked (0 weight-sized children),\n\t// `validateTree`/`parseLayout` would later reject the serialized tree (rrp\n\t// requires >= 1 weight-sized child). You cannot fixedPx-pin the LAST flexible\n\t// child — treat it as a NO-OP. A `minPx` child is FLEXIBLE (weight-sized with a\n\t// floor), so it does NOT count toward \"all fixed\" — pinning a `minPx` floor is\n\t// always allowed.\n\tif (base.length > 0 && base.every(c => c !== null)) {\n\t\treturn t\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...target.sizes],\n\t\tconstraints: base,\n\t}\n\treturn wrap(replaceNode(t.root, splitId, replacement))\n}\n\nexport function setActive(t: LayoutTree, groupId: string, panelId: string): LayoutTree {\n\tconst group = findGroupById(t.root, groupId)\n\tif (!group) throw new Error(`setActive: group not found: ${groupId}`)\n\tif (!group.panels.includes(panelId)) {\n\t\tthrow new Error(`setActive: panel ${panelId} not in group ${groupId}`)\n\t}\n\tconst replacement = tg(group.id, [...group.panels], panelId)\n\treturn wrap(replaceNode(t.root, groupId, replacement))\n}\n\n/** Remove a panel from whichever group holds it; collapse. */\nfunction removePanel(root: LayoutNode, panelId: string): LayoutNode {\n\tconst located = findGroupContaining(root, panelId)\n\tif (!located) throw new Error(`panel not found: ${panelId}`)\n\tconst { group } = located\n\tconst removedIndex = group.panels.indexOf(panelId)\n\tconst panels = group.panels.filter(p => p !== panelId)\n\tconst replacement = tg(group.id, panels, deriveActive(panels, group.active, removedIndex))\n\tconst replaced = replaceNode(root, group.id, replacement)\n\treturn normalizeRoot(replaced)\n}\n\nexport function closePanel(t: LayoutTree, panelId: string): LayoutTree {\n\t// Last-panel no-op guard: closing the SOLE panel in the whole tree would\n\t// empty the layout (normalizeRoot throws). Instead return the tree unchanged\n\t// so hosts can wire a close button without special-casing the final panel.\n\t// An UNKNOWN id is NOT \"the last panel\" — only short-circuit when the panel\n\t// actually EXISTS and is the only one; otherwise fall through to removePanel,\n\t// which throws \"panel not found\" for an unknown id (behavior preserved).\n\tconst ids = collectPanelIds(t.root)\n\tif (ids.size === 1 && ids.has(panelId)) return t\n\treturn wrap(removePanel(t.root, panelId))\n}\n\nexport function extractPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n): { tree: LayoutTree; extracted: string } {\n\tconst located = findGroupContaining(t.root, panelId)\n\tif (!located) throw new Error(`extractPanel: panel not found: ${panelId}`)\n\treturn { tree: wrap(removePanel(t.root, panelId)), extracted: panelId }\n}\n\nexport function insertPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tif (hasPanel(t.root, panelId)) {\n\t\tthrow new Error(`insertPanel: panel already exists in the tree: ${panelId}`)\n\t}\n\tconst group = findGroupById(t.root, dest.groupId)\n\tif (!group) throw new Error(`insertPanel: dest group not found: ${dest.groupId}`)\n\tconst panels = [...group.panels]\n\tconst idx = clampInsertIndex(dest.index, panels.length)\n\tpanels.splice(idx, 0, panelId)\n\tconst replacement = tg(group.id, panels, group.active)\n\treturn wrap(normalizeRoot(replaceNode(t.root, group.id, replacement)))\n}\n\nfunction clampInsertIndex(index: number | undefined, len: number): number {\n\tif (index === undefined) return len\n\tif (index < 0) return 0\n\tif (index > len) return len\n\treturn index\n}\n\nexport function movePanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tconst src = findGroupContaining(t.root, panelId)\n\tif (!src) throw new Error(`movePanel: panel not found: ${panelId}`)\n\tconst destGroup = findGroupById(t.root, dest.groupId)\n\tif (!destGroup) throw new Error(`movePanel: dest group not found: ${dest.groupId}`)\n\n\tif (src.group.id === destGroup.id) {\n\t\t// Same-group reorder: remove then re-insert at the clamped index.\n\t\tconst without = src.group.panels.filter(p => p !== panelId)\n\t\tconst idx = clampInsertIndex(dest.index, without.length)\n\t\tconst panels = [...without]\n\t\tpanels.splice(idx, 0, panelId)\n\t\tconst replacement = tg(src.group.id, panels, src.group.active)\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, src.group.id, replacement)))\n\t}\n\n\t// Cross-group: remove from source (re-derive active + maybe collapse), then\n\t// insert into dest. Compute the destination panels against the ORIGINAL dest\n\t// group so the clamp matches its pre-move length.\n\tconst removedIndex = src.group.panels.indexOf(panelId)\n\tconst srcPanels = src.group.panels.filter(p => p !== panelId)\n\tconst srcReplacement = tg(src.group.id, srcPanels, deriveActive(srcPanels, src.group.active, removedIndex))\n\n\tconst destPanels = [...destGroup.panels]\n\tconst idx = clampInsertIndex(dest.index, destPanels.length)\n\tdestPanels.splice(idx, 0, panelId)\n\tconst destReplacement = tg(destGroup.id, destPanels, destGroup.active)\n\n\tlet root = replaceNode(t.root, src.group.id, srcReplacement)\n\troot = replaceNode(root, destGroup.id, destReplacement)\n\treturn wrap(normalizeRoot(root))\n}\n\nexport function splitPanel(\n\tt: LayoutTree,\n\tatPanelId: string,\n\tdir: Orientation,\n\tnewPanelId: string,\n\tside: 'before' | 'after',\n): LayoutTree {\n\tif (hasPanel(t.root, newPanelId)) {\n\t\tthrow new Error(`splitPanel: new panel already exists in the tree: ${newPanelId}`)\n\t}\n\tconst located = findGroupContaining(t.root, atPanelId)\n\tif (!located) throw new Error(`splitPanel: panel not found: ${atPanelId}`)\n\tconst { group } = located\n\n\t// Generated node ids must be unique within the current tree: a node literally\n\t// named `${origGroupId}__sp` could already exist and produce a duplicate-id\n\t// tree. Mint collision-free ids off the deterministic bases.\n\tconst taken = collectNodeIds(t.root)\n\n\t// Extract atPanelId into its own new group.\n\tconst origGroupId = group.id\n\tconst extractedGroup = tg(freshId(taken, `${origGroupId}__split`), [atPanelId], atPanelId)\n\tconst newGroup = tg(freshId(taken, `${origGroupId}__new`), [newPanelId], newPanelId)\n\n\tconst children: LayoutNode[] = side === 'after'\n\t\t? [extractedGroup, newGroup]\n\t\t: [newGroup, extractedGroup]\n\tconst newSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__sp`),\n\t\torientation: dir,\n\t\tchildren,\n\t\tsizes: [1, 1],\n\t}\n\n\tif (group.panels.length === 1) {\n\t\t// Replace the whole group with the new split.\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, newSplit)))\n\t}\n\n\t// Multi-panel group: pull atPanelId out, keep the rest in the orig group,\n\t// and place the new split where the orig group was... but the orig group must\n\t// survive too. So: replace orig group with a split [remainingGroup, newSplit]?\n\t// The contract only requires: atPanel + newPanel live in different groups, and\n\t// the orig group keeps the remaining panels. Replace orig group with a split\n\t// of [origRemaining, newSplit] using `dir` orientation.\n\tconst removedIndex = group.panels.indexOf(atPanelId)\n\tconst remaining = group.panels.filter(p => p !== atPanelId)\n\tconst origRemainingGroup = tg(origGroupId, remaining, deriveActive(remaining, group.active, removedIndex))\n\n\tconst outerChildren: LayoutNode[] = side === 'after'\n\t\t? [origRemainingGroup, newSplit]\n\t\t: [newSplit, origRemainingGroup]\n\tconst outerSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__outer`),\n\t\torientation: dir,\n\t\tchildren: outerChildren,\n\t\tsizes: [1, 1],\n\t}\n\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, outerSplit)))\n}\n","/**\n * Observable layout model — single-writer, synchronous, call-ordered.\n *\n * INV-3: mutations assume an already-validated (acyclic, integrity-checked) tree\n * — callers feeding hand-built trees should parseLayout/validateTree first.\n *\n * Conventions (pinned by model.test.ts):\n * - revision starts at 0 for the initial tree.\n * - subscribe() does NOT replay; first emission is on the first successful\n * apply, carrying revision 1.\n * - +1 per successful apply; a throwing mutation does NOT advance revision and\n * does NOT notify (the throw propagates to the caller).\n */\nimport type { LayoutModel, LayoutSnapshot, LayoutTree } from './types.js'\n\nexport function createLayoutModel(initial: LayoutTree): LayoutModel {\n\t// Own the initial tree: take a structural snapshot so a later external mutation\n\t// of the caller's `initial` object can't leak into model state. The tree is\n\t// readonly-typed / treated as immutable, so a deep clone is the cheapest\n\t// correct ownership stance. (The `next` from each apply is already a fresh tree\n\t// produced by the pure mutations, so it needs no further clone.)\n\tlet current: LayoutTree = structuredClone(initial)\n\tlet revision = 0\n\tconst subscribers = new Set<(snap: LayoutSnapshot) => void>()\n\n\t// Re-entrancy guard: a subscriber may call apply() during notification. We\n\t// must not re-enter and bump `revision` mid-delivery (subscribers would then\n\t// observe a non-monotonic sequence). Instead, enqueue and drain FIFO so each\n\t// apply fully commits + notifies before the next begins.\n\tlet applying = false\n\tconst queue: ((t: LayoutTree) => LayoutTree)[] = []\n\n\tconst commitAndNotify = (mut: (t: LayoutTree) => LayoutTree): void => {\n\t\t// Compute first; if it throws, nothing below runs — revision stays put\n\t\t// and no subscriber is notified.\n\t\tconst next = mut(current)\n\t\tcurrent = next\n\t\trevision += 1\n\t\tconst snap: LayoutSnapshot = { tree: current, revision }\n\t\t// Snapshot the subscriber set so unsubscribe during delivery is safe.\n\t\t// Isolate subscriber errors: a throw from one must not abort delivery to\n\t\t// the rest, nor make apply() throw after a committed state. The model's\n\t\t// job is delivery, not subscriber correctness — swallow and continue.\n\t\tfor (const fn of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tfn(snap)\n\t\t\t}\n\t\t\tcatch {\n\t\t\t\t// intentionally swallowed — see above.\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tget(): LayoutTree {\n\t\t\treturn current\n\t\t},\n\t\t/**\n\t\t * `mut` MUST be pure (clone-on-write); the built-in mutations satisfy this.\n\t\t */\n\t\tapply(mut: (t: LayoutTree) => LayoutTree): void {\n\t\t\tif (applying) {\n\t\t\t\t// Re-entrant call (a subscriber applied during notification): queue it\n\t\t\t\t// and let the in-flight drain pass process it after the current pass.\n\t\t\t\tqueue.push(mut)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tapplying = true\n\t\t\ttry {\n\t\t\t\t// Top-level (non-re-entrant) apply: a throw here propagates to the\n\t\t\t\t// caller and does NOT advance revision / notify. (`finally` below\n\t\t\t\t// still resets `applying`, so a throw can't wedge the model.)\n\t\t\t\tcommitAndNotify(mut)\n\t\t\t\t// Drain re-entrant mutations enqueued during notification. Each queued\n\t\t\t\t// apply was deferred and is therefore fire-and-forget: its original\n\t\t\t\t// caller already returned, so it cannot deliver a synchronous throw\n\t\t\t\t// back. Run each as an INDEPENDENT transaction — a throw skips that\n\t\t\t\t// item (no revision bump, no notify) and we continue draining the rest\n\t\t\t\t// in FIFO order. This keeps a thrown queued mut from bubbling into the\n\t\t\t\t// already-committed outer apply() and from disrupting the order /\n\t\t\t\t// processing of later queued items.\n\t\t\t\twhile (queue.length > 0) {\n\t\t\t\t\tconst queuedMut = queue.shift()!\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcommitAndNotify(queuedMut)\n\t\t\t\t\t}\n\t\t\t\t\tcatch {\n\t\t\t\t\t\t// Deferred apply: swallow + continue — see above.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\tapplying = false\n\t\t\t}\n\t\t},\n\t\tsubscribe(fn: (snap: LayoutSnapshot) => void): () => void {\n\t\t\tsubscribers.add(fn)\n\t\t\treturn (): void => {\n\t\t\t\tsubscribers.delete(fn)\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * Capability-aware layout operations for user-triggered interactions.\n *\n * Keep these separate from the pure mutations: callers may still use\n * `closePanel` for restore/migration/programmatic layout transforms, while UI\n * entry points consistently honor registry capabilities.\n */\nimport { closePanel } from './mutations.js'\nimport type { LayoutTree, PanelRegistry } from './types.js'\n\n/**\n * Close a panel only when its descriptor permits user closure.\n *\n * Missing descriptors and an omitted `closable` capability retain the historic\n * close behavior. Only an explicit `closable:false` blocks the operation.\n */\nexport function closePanelForUser(\n\ttree: LayoutTree,\n\tpanelId: string,\n\tregistry: PanelRegistry,\n): LayoutTree {\n\treturn registry.get(panelId)?.closable === false ? tree : closePanel(tree, panelId)\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dimina-kit/electron-deck",
|
|
3
|
-
"version": "0.1.0-dev.
|
|
3
|
+
"version": "0.1.0-dev.20260629114208",
|
|
4
4
|
"description": "Dimina devtools electron-deck framework — single `electronDeck(config)` entry for downstream hosts (dimina default).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dimina",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
],
|
|
67
67
|
"dependencies": {
|
|
68
68
|
"react-resizable-panels": "^4.10.0",
|
|
69
|
-
"@dimina-kit/view-anchor": "0.1.0-dev.
|
|
69
|
+
"@dimina-kit/view-anchor": "0.1.0-dev.20260629114208"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@testing-library/jest-dom": "^6.9.1",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"layout-oJQ9hind.js","names":[],"sources":["../src/layout/registry.ts","../src/layout/serialize.ts","../src/layout/sanitize.ts","../src/layout/mutations.ts","../src/layout/model.ts","../src/layout/user-actions.ts"],"sourcesContent":["/**\n * Panel registry — pure in-memory map of panel descriptors. Pure TS.\n */\nimport type { Disposable, PanelDescriptor, PanelRegistry } from './types.js'\n\nexport function createPanelRegistry(): PanelRegistry {\n\tconst map = new Map<string, PanelDescriptor>()\n\treturn {\n\t\tregister(p: PanelDescriptor): Disposable {\n\t\t\tmap.set(p.id, p)\n\t\t\treturn {\n\t\t\t\tdispose(): void {\n\t\t\t\t\t// Only drop if this exact descriptor is still registered, so a\n\t\t\t\t\t// re-register under the same id isn't undone by a stale handle.\n\t\t\t\t\tif (map.get(p.id) === p) map.delete(p.id)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tget(id: string): PanelDescriptor | undefined {\n\t\t\treturn map.get(id)\n\t\t},\n\t\tlist(): readonly PanelDescriptor[] {\n\t\t\treturn [...map.values()]\n\t\t},\n\t}\n}\n","/**\n * Serialize / parse / validate — structural integrity + acyclicity. Pure TS.\n *\n * Default-DENY: parseLayout throws on any tree validateTree would flag.\n */\nimport type { LayoutNode, LayoutTree } from './types.js'\n\nconst MAX_DEPTH = 64\n\nexport function serializeLayout(t: LayoutTree): string {\n\treturn JSON.stringify(t)\n}\n\n/**\n * Walk an arbitrary (possibly malformed / cyclic / shared-ref) node graph and\n * collect structural problems. Never recurses infinitely: a node already on the\n * current path (cycle) or already visited elsewhere (shared ref) is reported and\n * not descended into.\n */\nfunction collectProblems(\n\troot: unknown,\n\tknownPanelIds: ReadonlySet<string> | null,\n): string[] {\n\tconst problems: string[] = []\n\tconst seenIds = new Set<string>()\n\tconst seenObjects = new Set<object>()\n\tconst panelOwners = new Map<string, string>()\n\n\tconst visit = (node: unknown, depth: number, path: Set<object>): void => {\n\t\tif (depth > MAX_DEPTH) {\n\t\t\tproblems.push(`depth exceeds ${MAX_DEPTH}`)\n\t\t\treturn\n\t\t}\n\t\tif (node === null || typeof node !== 'object') {\n\t\t\tproblems.push(`node is not an object: ${String(node)}`)\n\t\t\treturn\n\t\t}\n\t\tconst obj = node as object\n\t\tif (path.has(obj)) {\n\t\t\tproblems.push('cycle detected: node reachable from itself')\n\t\t\treturn\n\t\t}\n\t\tif (seenObjects.has(obj)) {\n\t\t\tproblems.push('shared node reference: same object appears twice')\n\t\t\treturn\n\t\t}\n\t\tseenObjects.add(obj)\n\n\t\tconst n = node as { kind?: unknown; id?: unknown }\n\t\tconst kind = n.kind\n\t\tconst id = typeof n.id === 'string' ? n.id : undefined\n\n\t\tif (id === undefined) {\n\t\t\tproblems.push(`node missing string id (kind=${String(kind)})`)\n\t\t}\n\t\telse if (seenIds.has(id)) {\n\t\t\tproblems.push(`duplicate node id: ${id}`)\n\t\t}\n\t\telse {\n\t\t\tseenIds.add(id)\n\t\t}\n\n\t\tif (kind === 'tabs') {\n\t\t\tconst tg = node as { panels?: unknown; active?: unknown }\n\t\t\tconst panels = Array.isArray(tg.panels) ? (tg.panels as unknown[]) : null\n\t\t\tif (!panels) {\n\t\t\t\tproblems.push(`tabs ${id ?? '?'}: panels is not an array`)\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (panels.length === 0) {\n\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: empty tabgroup`)\n\t\t\t\t}\n\t\t\t\tfor (const p of panels) {\n\t\t\t\t\tif (typeof p !== 'string') {\n\t\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: non-string panel id`)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst owner = panelOwners.get(p)\n\t\t\t\t\tif (owner !== undefined) {\n\t\t\t\t\t\tproblems.push(`duplicate panel id across groups: ${p}`)\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tpanelOwners.set(p, id ?? '?')\n\t\t\t\t\t}\n\t\t\t\t\tif (knownPanelIds && !knownPanelIds.has(p)) {\n\t\t\t\t\t\tproblems.push(`orphan panel not in known panel ids: ${p}`)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst active = tg.active\n\t\t\t\tif (typeof active !== 'string' || !panels.includes(active)) {\n\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: active ${String(active)} not in panels`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (kind === 'split') {\n\t\t\tconst sp = node as { children?: unknown; sizes?: unknown; orientation?: unknown; constraints?: unknown }\n\t\t\tconst children = Array.isArray(sp.children) ? (sp.children as unknown[]) : null\n\t\t\tconst sizes = Array.isArray(sp.sizes) ? (sp.sizes as unknown[]) : null\n\t\t\tconst orientation = sp.orientation\n\t\t\tif (orientation !== 'row' && orientation !== 'column') {\n\t\t\t\tproblems.push(`split ${id ?? '?'}: invalid orientation ${String(orientation)}`)\n\t\t\t}\n\t\t\t// OPTIONAL constraints — validate the field's INTRINSIC format (array\n\t\t\t// shape + per-entry rules + exactly-one-of fixedPx/minPx + all-FIXED\n\t\t\t// guard) INDEPENDENTLY of `children` validity. Only the LENGTH-vs-children\n\t\t\t// comparison is gated on `children` being a valid array (done below).\n\t\t\tif (sp.constraints !== undefined) {\n\t\t\t\tconst constraints = Array.isArray(sp.constraints) ? (sp.constraints as unknown[]) : null\n\t\t\t\tif (!constraints) {\n\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraints is not an array`)\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Tracks whether EVERY child is PX-SIZED (a non-null constraint —\n\t\t\t\t\t// `fixedPx` OR `minPx`). Both are sized in px and excluded from the\n\t\t\t\t\t// weight pool, so an all-constrained split trips the rrp footgun\n\t\t\t\t\t// (needs >= 1 weight-sized child); only a `null` child clears it.\n\t\t\t\t\tlet everyConstrained = constraints.length > 0\n\t\t\t\t\tfor (const c of constraints) {\n\t\t\t\t\t\tif (c === null) {\n\t\t\t\t\t\t\teveryConstrained = false\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (typeof c !== 'object') {\n\t\t\t\t\t\t\teveryConstrained = false\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint is not null nor an object: ${String(c)}`)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Exactly ONE of `fixedPx` / `minPx`, value finite > 0.\n\t\t\t\t\t\tconst keys = Object.keys(c as object)\n\t\t\t\t\t\tconst hasFixed = keys.includes('fixedPx')\n\t\t\t\t\t\tconst hasMin = keys.includes('minPx')\n\t\t\t\t\t\tif (keys.length !== 1 || !(hasFixed || hasMin)) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(', ')}]`)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst cKey = hasFixed ? 'fixedPx' : 'minPx'\n\t\t\t\t\t\tconst cVal = hasFixed ? (c as { fixedPx?: unknown }).fixedPx : (c as { minPx?: unknown }).minPx\n\t\t\t\t\t\tif (typeof cVal !== 'number' || !Number.isFinite(cVal) || cVal <= 0) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Guard the rrp footgun: an all-px-sized split has no weight-sized\n\t\t\t\t\t// child to absorb leftover space (rrp v4.10 requires >= 1).\n\t\t\t\t\tif (everyConstrained) {\n\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: all children are px-sized constraints; at least one must be weight-sized`)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!children) {\n\t\t\t\tproblems.push(`split ${id ?? '?'}: children is not an array`)\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (children.length < 2) {\n\t\t\t\t\tproblems.push(`split ${id ?? '?'}: must have >= 2 children, has ${children.length}`)\n\t\t\t\t}\n\t\t\t\tif (!sizes || sizes.length !== children.length) {\n\t\t\t\t\tproblems.push(\n\t\t\t\t\t\t`split ${id ?? '?'}: sizes ${sizes ? sizes.length : 'missing'} != children ${children.length}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfor (const s of sizes) {\n\t\t\t\t\t\tif (typeof s !== 'number' || !Number.isFinite(s)) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: non-finite size ${String(s)}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// LENGTH-vs-children comparison (gated on a valid children array).\n\t\t\t\tif (sp.constraints !== undefined && Array.isArray(sp.constraints)) {\n\t\t\t\t\tconst constraints = sp.constraints as unknown[]\n\t\t\t\t\tif (constraints.length !== children.length) {\n\t\t\t\t\t\tproblems.push(\n\t\t\t\t\t\t\t`split ${id ?? '?'}: constraints ${constraints.length} != children ${children.length}`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst nextPath = new Set(path)\n\t\t\t\tnextPath.add(obj)\n\t\t\t\tfor (const child of children) {\n\t\t\t\t\tvisit(child, depth + 1, nextPath)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tproblems.push(`unknown node kind: ${String(kind)}`)\n\t\t}\n\t}\n\n\tvisit(root, 0, new Set())\n\treturn problems\n}\n\nexport function validateTree(t: LayoutTree, knownPanelIds: ReadonlySet<string>): string[] {\n\tif (t === null || typeof t !== 'object') return ['tree is not an object']\n\tif ((t as { version?: unknown }).version !== 1) {\n\t\treturn [`unsupported version: ${String((t as { version?: unknown }).version)}`]\n\t}\n\treturn collectProblems((t as { root?: unknown }).root, knownPanelIds)\n}\n\nexport function parseLayout(json: string): LayoutTree {\n\tlet raw: unknown\n\ttry {\n\t\traw = JSON.parse(json)\n\t}\n\tcatch {\n\t\tthrow new Error('parseLayout: input is not valid JSON')\n\t}\n\tif (raw === null || typeof raw !== 'object') {\n\t\tthrow new Error('parseLayout: top-level value is not an object')\n\t}\n\tconst obj = raw as { version?: unknown; root?: unknown }\n\tif (obj.version !== 1) {\n\t\tthrow new Error(`parseLayout: unsupported version ${String(obj.version)}`)\n\t}\n\tif (obj.root === undefined || obj.root === null) {\n\t\tthrow new Error('parseLayout: missing root')\n\t}\n\t// Validate structure with no knownPanelIds constraint (orphan check N/A on\n\t// pure deserialize — caller pairs with their own registry).\n\tconst problems = collectProblems(obj.root, null)\n\tif (problems.length > 0) {\n\t\tthrow new Error(`parseLayout: illegal layout — ${problems.join('; ')}`)\n\t}\n\treturn { version: 1, root: obj.root as LayoutNode }\n}\n","/**\n * Self-healing for persisted layout trees — pure TS (no react/electron).\n *\n * A FLEXIBLE child (`constraints[i]` null / no constraints) has no enforced\n * lower weight bound in the serialized tree: `validateTree` only rejects\n * NON-FINITE sizes, not non-positive ones. A user who dragged a flexible panel\n * to ~0 width writes a ~0 (or 0, or negative) weight into the tree; on the next\n * restore that panel comes back at 0 width, invisible and effectively stuck.\n *\n * `sanitizeFlexibleWeights` heals such a tree: every flexible child whose weight\n * is NON-POSITIVE (≤ 0 or non-finite) is lifted to a minimum positive weight, so\n * a restored panel is always visible. A small-but-POSITIVE weight is left as-is\n * — weights are a relative ratio (any positive scale is valid), so a tiny\n * positive weight is healthy, not collapsed. PX-SIZED children\n * (`fixedPx`/`minPx`) are sized in pixels, NOT weights — their `sizes[i]` entry\n * is a preserved-but-unused placeholder — so they are left untouched. The\n * function is pure (clone-on-write, never mutates the input).\n */\nimport type { LayoutNode, LayoutTree, SizeConstraint, SplitNode } from './types.js'\n\n/** The healed-up value for a collapsed flexible child. A small positive weight:\n * enough to make the panel visible without stealing meaningful space from its\n * healthy siblings (the user can re-drag afterwards). */\nconst HEALED_FLEX_WEIGHT = 1\n\n/** Is child `i` of `node` flexible (weight-sized) — i.e. has no px constraint? */\nfunction isFlexibleAt(node: SplitNode, i: number): boolean {\n\treturn (node.constraints?.[i] ?? null) === null\n}\n\nfunction sanitizeNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return node\n\n\t// Recurse first so nested splits are healed regardless of this level.\n\tconst children = node.children.map(sanitizeNode)\n\n\t// Heal this split's FLEXIBLE child weights. ONLY a NON-POSITIVE weight (≤ 0 or\n\t// non-finite — the \"dragged to 0 / negative\" collapse) is healed; every\n\t// POSITIVE weight is kept verbatim, however small. Weights are a RELATIVE ratio\n\t// (any positive scale is valid — `[0.001, 0.006]` is the same layout as\n\t// `[1, 6]`), so a small-but-positive weight is healthy and must not be rewritten\n\t// (that would crush the ratio). Px children are never touched.\n\tlet changed = children.some((c, i) => c !== node.children[i])\n\tconst sizes = node.sizes.map((w, i) => {\n\t\tif (!isFlexibleAt(node, i)) return w\n\t\tif (typeof w === 'number' && Number.isFinite(w) && w > 0) return w\n\t\tchanged = true\n\t\treturn HEALED_FLEX_WEIGHT\n\t})\n\n\tif (!changed) return node\n\tconst rebuilt: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren,\n\t\tsizes,\n\t}\n\t// Preserve the constraints array verbatim (only weights are touched).\n\treturn node.constraints !== undefined\n\t\t? { ...rebuilt, constraints: node.constraints as readonly (SizeConstraint | null)[] }\n\t\t: rebuilt\n}\n\n/**\n * Return a tree in which every FLEXIBLE child with a non-positive (≤ tiny\n * threshold) weight is healed to a minimum positive weight, recursively. Px\n * children are untouched. Pure: the input is never mutated; a fully-healthy tree\n * is returned structurally unchanged (same weights).\n */\nexport function sanitizeFlexibleWeights(tree: LayoutTree): LayoutTree {\n\tconst root = sanitizeNode(tree.root)\n\treturn root === tree.root ? tree : { version: 1, root }\n}\n","/**\n * Pure tree mutations. Every function returns a NEW tree and never mutates its\n * input (clone-on-write). All structural invariants (#1) are funnelled through\n * `normalizeRoot`, which collapses empty tabgroups + single-child splits,\n * cascading upward, while keeping the root a LayoutNode.\n */\nimport type { LayoutNode, LayoutTree, Orientation, SizeConstraint, SplitNode, TabGroupNode } from './types.js'\n\n// ───────────────────────── helpers ─────────────────────────\n\nfunction tg(id: string, panels: string[], active: string): TabGroupNode {\n\treturn { kind: 'tabs', id, panels, active }\n}\n\n/** Re-derive active after panels changed: keep current active if still present,\n * else select panels[min(removedIndex, len-1)] (clamp to last). */\nfunction deriveActive(panels: string[], prevActive: string, removedIndex: number): string {\n\tif (panels.length === 0) return ''\n\tif (panels.includes(prevActive)) return prevActive\n\tconst idx = Math.min(removedIndex, panels.length - 1)\n\treturn panels[Math.max(0, idx)]!\n}\n\n/**\n * Collapse a (already child-normalized) node:\n * - tabgroup with 0 panels => null (drop)\n * - split: drop null children; if 0 left => null; if 1 left => that child;\n * else rebuild with repaired sizes.\n */\nfunction normalize(node: LayoutNode): LayoutNode | null {\n\tif (node.kind === 'tabs') {\n\t\treturn node.panels.length === 0 ? null : node\n\t}\n\t// split: normalize children first.\n\tconst kept: LayoutNode[] = []\n\tconst keptSizes: number[] = []\n\tconst keptConstraints: (SizeConstraint | null)[] = []\n\tconst hadConstraints = node.constraints !== undefined\n\tnode.children.forEach((child, i) => {\n\t\tconst n = normalize(child)\n\t\tif (n !== null) {\n\t\t\tkept.push(n)\n\t\t\tkeptSizes.push(node.sizes[i] ?? 1)\n\t\t\tif (hadConstraints) keptConstraints.push(node.constraints![i] ?? null)\n\t\t}\n\t})\n\tif (kept.length === 0) return null\n\tif (kept.length === 1) return kept[0]!\n\tconst rebuilt: SplitNode = { kind: 'split', id: node.id, orientation: node.orientation, children: kept, sizes: keptSizes }\n\tif (!hadConstraints) return rebuilt\n\t// M3 repair: dropping children can leave a multi-child split whose survivors\n\t// are ALL fixed-px (the sole flexible child was the one removed). That tree is\n\t// rejected by validateTree (rrp needs >= 1 weight-sized child). Deterministically\n\t// clear the LAST survivor's constraint so >= 1 flexible child remains.\n\tconst allFixed = keptConstraints.length > 0 && !keptConstraints.some(c => c === null)\n\tif (allFixed) {\n\t\tkeptConstraints[keptConstraints.length - 1] = null\n\t}\n\treturn { ...rebuilt, constraints: keptConstraints }\n}\n\n/** Apply collapse to the root, guaranteeing the result is a LayoutNode. A root\n * that collapses to nothing is illegal upstream (we never empty the last group\n * without a replacement), so we only reach here with a survivor. */\nfunction normalizeRoot(root: LayoutNode): LayoutNode {\n\tconst n = normalize(root)\n\tif (n === null) {\n\t\tthrow new Error('mutation would empty the entire layout')\n\t}\n\treturn n\n}\n\ninterface Located {\n\tgroup: TabGroupNode\n}\n\nfunction findGroupContaining(root: LayoutNode, panelId: string): Located | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.panels.includes(panelId)) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found ? { group: found } : null\n}\n\nfunction findGroupById(root: LayoutNode, groupId: string): TabGroupNode | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.id === groupId) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/** Collect every panelId present anywhere in the tree. */\nfunction collectPanelIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (n.kind === 'tabs') {\n\t\t\tfor (const p of n.panels) out.add(p)\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn out\n}\n\n/** True if `panelId` exists in any tabgroup of the tree. */\nfunction hasPanel(root: LayoutNode, id: string): boolean {\n\treturn collectPanelIds(root).has(id)\n}\n\n/** Collect every node id present anywhere in the tree (splits + tabgroups). */\nfunction collectNodeIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tout.add(n.id)\n\t\tif (n.kind === 'split') n.children.forEach(walk)\n\t}\n\twalk(root)\n\treturn out\n}\n\n/**\n * Mint an id from `base` that is not already in `taken`. If `base` is free,\n * return it; otherwise append an incrementing `#N` suffix until free. Mutates\n * `taken` so successive calls in the same mutation can't collide with each other.\n */\nfunction freshId(taken: Set<string>, base: string): string {\n\tlet candidate = base\n\tlet n = 2\n\twhile (taken.has(candidate)) {\n\t\tcandidate = `${base}#${n}`\n\t\tn += 1\n\t}\n\ttaken.add(candidate)\n\treturn candidate\n}\n\nfunction findSplitById(root: LayoutNode, splitId: string): SplitNode | null {\n\tlet found: SplitNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'split') {\n\t\t\tif (n.id === splitId) found = n\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/**\n * Rebuild the tree, replacing the node with matching id by `replacement`\n * (which may be null to delete — handled by collapse). This produces a fresh\n * object graph (clone-on-write); untouched branches are still rebuilt so the\n * input is never shared.\n */\nfunction replaceNode(node: LayoutNode, targetId: string, replacement: LayoutNode): LayoutNode {\n\tif (node.id === targetId) return replacement\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(c => replaceNode(c, targetId, replacement)),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction cloneNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(cloneNode),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction wrap(root: LayoutNode): LayoutTree {\n\treturn { version: 1, root }\n}\n\n// ───────────────────────── mutations ─────────────────────────\n\nexport function setSizes(t: LayoutTree, splitId: string, sizes: readonly number[]): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setSizes: split not found: ${splitId}`)\n\tif (sizes.length !== target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setSizes: sizes length ${sizes.length} != children length ${target.children.length}`,\n\t\t)\n\t}\n\tif (!sizes.every(s => Number.isFinite(s))) {\n\t\tthrow new Error(`setSizes: every size must be a finite number, got [${sizes.join(', ')}]`)\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...sizes],\n\t}\n\t// setSizes must NOT touch constraints — carry them through unchanged.\n\tconst withConstraints: SplitNode = target.constraints !== undefined\n\t\t? { ...replacement, constraints: [...target.constraints] }\n\t\t: replacement\n\treturn wrap(replaceNode(t.root, splitId, withConstraints))\n}\n\n/**\n * Set (or clear) the fixed-px constraint on child `childIndex` of split\n * `splitId`. Pure (clone-on-write). Lazily materializes an all-null constraints\n * array aligned with `children` when absent, then writes the slot. Clearing\n * (`constraint === null`) keeps the array even if all entries become null.\n */\nexport function setConstraint(\n\tt: LayoutTree,\n\tsplitId: string,\n\tchildIndex: number,\n\tconstraint: SizeConstraint | null,\n): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setConstraint: split not found: ${splitId}`)\n\tif (!Number.isInteger(childIndex)) {\n\t\tthrow new Error(`setConstraint: childIndex must be an integer, got ${childIndex}`)\n\t}\n\tif (childIndex < 0 || childIndex >= target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setConstraint: childIndex ${childIndex} out of range [0, ${target.children.length})`,\n\t\t)\n\t}\n\tconst base: (SizeConstraint | null)[] = target.constraints !== undefined\n\t\t? [...target.constraints]\n\t\t: target.children.map(() => null)\n\tbase[childIndex] = constraint\n\t// M3 guard: never produce an all-FIXED-px split. If applying this constraint\n\t// would leave EVERY child `fixedPx`-locked (0 weight-sized children),\n\t// `validateTree`/`parseLayout` would later reject the serialized tree (rrp\n\t// requires >= 1 weight-sized child). You cannot fixedPx-pin the LAST flexible\n\t// child — treat it as a NO-OP. A `minPx` child is FLEXIBLE (weight-sized with a\n\t// floor), so it does NOT count toward \"all fixed\" — pinning a `minPx` floor is\n\t// always allowed.\n\tif (base.length > 0 && base.every(c => c !== null)) {\n\t\treturn t\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...target.sizes],\n\t\tconstraints: base,\n\t}\n\treturn wrap(replaceNode(t.root, splitId, replacement))\n}\n\nexport function setActive(t: LayoutTree, groupId: string, panelId: string): LayoutTree {\n\tconst group = findGroupById(t.root, groupId)\n\tif (!group) throw new Error(`setActive: group not found: ${groupId}`)\n\tif (!group.panels.includes(panelId)) {\n\t\tthrow new Error(`setActive: panel ${panelId} not in group ${groupId}`)\n\t}\n\tconst replacement = tg(group.id, [...group.panels], panelId)\n\treturn wrap(replaceNode(t.root, groupId, replacement))\n}\n\n/** Remove a panel from whichever group holds it; collapse. */\nfunction removePanel(root: LayoutNode, panelId: string): LayoutNode {\n\tconst located = findGroupContaining(root, panelId)\n\tif (!located) throw new Error(`panel not found: ${panelId}`)\n\tconst { group } = located\n\tconst removedIndex = group.panels.indexOf(panelId)\n\tconst panels = group.panels.filter(p => p !== panelId)\n\tconst replacement = tg(group.id, panels, deriveActive(panels, group.active, removedIndex))\n\tconst replaced = replaceNode(root, group.id, replacement)\n\treturn normalizeRoot(replaced)\n}\n\nexport function closePanel(t: LayoutTree, panelId: string): LayoutTree {\n\t// Last-panel no-op guard: closing the SOLE panel in the whole tree would\n\t// empty the layout (normalizeRoot throws). Instead return the tree unchanged\n\t// so hosts can wire a close button without special-casing the final panel.\n\t// An UNKNOWN id is NOT \"the last panel\" — only short-circuit when the panel\n\t// actually EXISTS and is the only one; otherwise fall through to removePanel,\n\t// which throws \"panel not found\" for an unknown id (behavior preserved).\n\tconst ids = collectPanelIds(t.root)\n\tif (ids.size === 1 && ids.has(panelId)) return t\n\treturn wrap(removePanel(t.root, panelId))\n}\n\nexport function extractPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n): { tree: LayoutTree; extracted: string } {\n\tconst located = findGroupContaining(t.root, panelId)\n\tif (!located) throw new Error(`extractPanel: panel not found: ${panelId}`)\n\treturn { tree: wrap(removePanel(t.root, panelId)), extracted: panelId }\n}\n\nexport function insertPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tif (hasPanel(t.root, panelId)) {\n\t\tthrow new Error(`insertPanel: panel already exists in the tree: ${panelId}`)\n\t}\n\tconst group = findGroupById(t.root, dest.groupId)\n\tif (!group) throw new Error(`insertPanel: dest group not found: ${dest.groupId}`)\n\tconst panels = [...group.panels]\n\tconst idx = clampInsertIndex(dest.index, panels.length)\n\tpanels.splice(idx, 0, panelId)\n\tconst replacement = tg(group.id, panels, group.active)\n\treturn wrap(normalizeRoot(replaceNode(t.root, group.id, replacement)))\n}\n\nfunction clampInsertIndex(index: number | undefined, len: number): number {\n\tif (index === undefined) return len\n\tif (index < 0) return 0\n\tif (index > len) return len\n\treturn index\n}\n\nexport function movePanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tconst src = findGroupContaining(t.root, panelId)\n\tif (!src) throw new Error(`movePanel: panel not found: ${panelId}`)\n\tconst destGroup = findGroupById(t.root, dest.groupId)\n\tif (!destGroup) throw new Error(`movePanel: dest group not found: ${dest.groupId}`)\n\n\tif (src.group.id === destGroup.id) {\n\t\t// Same-group reorder: remove then re-insert at the clamped index.\n\t\tconst without = src.group.panels.filter(p => p !== panelId)\n\t\tconst idx = clampInsertIndex(dest.index, without.length)\n\t\tconst panels = [...without]\n\t\tpanels.splice(idx, 0, panelId)\n\t\tconst replacement = tg(src.group.id, panels, src.group.active)\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, src.group.id, replacement)))\n\t}\n\n\t// Cross-group: remove from source (re-derive active + maybe collapse), then\n\t// insert into dest. Compute the destination panels against the ORIGINAL dest\n\t// group so the clamp matches its pre-move length.\n\tconst removedIndex = src.group.panels.indexOf(panelId)\n\tconst srcPanels = src.group.panels.filter(p => p !== panelId)\n\tconst srcReplacement = tg(src.group.id, srcPanels, deriveActive(srcPanels, src.group.active, removedIndex))\n\n\tconst destPanels = [...destGroup.panels]\n\tconst idx = clampInsertIndex(dest.index, destPanels.length)\n\tdestPanels.splice(idx, 0, panelId)\n\tconst destReplacement = tg(destGroup.id, destPanels, destGroup.active)\n\n\tlet root = replaceNode(t.root, src.group.id, srcReplacement)\n\troot = replaceNode(root, destGroup.id, destReplacement)\n\treturn wrap(normalizeRoot(root))\n}\n\nexport function splitPanel(\n\tt: LayoutTree,\n\tatPanelId: string,\n\tdir: Orientation,\n\tnewPanelId: string,\n\tside: 'before' | 'after',\n): LayoutTree {\n\tif (hasPanel(t.root, newPanelId)) {\n\t\tthrow new Error(`splitPanel: new panel already exists in the tree: ${newPanelId}`)\n\t}\n\tconst located = findGroupContaining(t.root, atPanelId)\n\tif (!located) throw new Error(`splitPanel: panel not found: ${atPanelId}`)\n\tconst { group } = located\n\n\t// Generated node ids must be unique within the current tree: a node literally\n\t// named `${origGroupId}__sp` could already exist and produce a duplicate-id\n\t// tree. Mint collision-free ids off the deterministic bases.\n\tconst taken = collectNodeIds(t.root)\n\n\t// Extract atPanelId into its own new group.\n\tconst origGroupId = group.id\n\tconst extractedGroup = tg(freshId(taken, `${origGroupId}__split`), [atPanelId], atPanelId)\n\tconst newGroup = tg(freshId(taken, `${origGroupId}__new`), [newPanelId], newPanelId)\n\n\tconst children: LayoutNode[] = side === 'after'\n\t\t? [extractedGroup, newGroup]\n\t\t: [newGroup, extractedGroup]\n\tconst newSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__sp`),\n\t\torientation: dir,\n\t\tchildren,\n\t\tsizes: [1, 1],\n\t}\n\n\tif (group.panels.length === 1) {\n\t\t// Replace the whole group with the new split.\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, newSplit)))\n\t}\n\n\t// Multi-panel group: pull atPanelId out, keep the rest in the orig group,\n\t// and place the new split where the orig group was... but the orig group must\n\t// survive too. So: replace orig group with a split [remainingGroup, newSplit]?\n\t// The contract only requires: atPanel + newPanel live in different groups, and\n\t// the orig group keeps the remaining panels. Replace orig group with a split\n\t// of [origRemaining, newSplit] using `dir` orientation.\n\tconst removedIndex = group.panels.indexOf(atPanelId)\n\tconst remaining = group.panels.filter(p => p !== atPanelId)\n\tconst origRemainingGroup = tg(origGroupId, remaining, deriveActive(remaining, group.active, removedIndex))\n\n\tconst outerChildren: LayoutNode[] = side === 'after'\n\t\t? [origRemainingGroup, newSplit]\n\t\t: [newSplit, origRemainingGroup]\n\tconst outerSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__outer`),\n\t\torientation: dir,\n\t\tchildren: outerChildren,\n\t\tsizes: [1, 1],\n\t}\n\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, outerSplit)))\n}\n","/**\n * Observable layout model — single-writer, synchronous, call-ordered.\n *\n * INV-3: mutations assume an already-validated (acyclic, integrity-checked) tree\n * — callers feeding hand-built trees should parseLayout/validateTree first.\n *\n * Conventions (pinned by model.test.ts):\n * - revision starts at 0 for the initial tree.\n * - subscribe() does NOT replay; first emission is on the first successful\n * apply, carrying revision 1.\n * - +1 per successful apply; a throwing mutation does NOT advance revision and\n * does NOT notify (the throw propagates to the caller).\n */\nimport type { LayoutModel, LayoutSnapshot, LayoutTree } from './types.js'\n\nexport function createLayoutModel(initial: LayoutTree): LayoutModel {\n\t// Own the initial tree: take a structural snapshot so a later external mutation\n\t// of the caller's `initial` object can't leak into model state. The tree is\n\t// readonly-typed / treated as immutable, so a deep clone is the cheapest\n\t// correct ownership stance. (The `next` from each apply is already a fresh tree\n\t// produced by the pure mutations, so it needs no further clone.)\n\tlet current: LayoutTree = structuredClone(initial)\n\tlet revision = 0\n\tconst subscribers = new Set<(snap: LayoutSnapshot) => void>()\n\n\t// Re-entrancy guard: a subscriber may call apply() during notification. We\n\t// must not re-enter and bump `revision` mid-delivery (subscribers would then\n\t// observe a non-monotonic sequence). Instead, enqueue and drain FIFO so each\n\t// apply fully commits + notifies before the next begins.\n\tlet applying = false\n\tconst queue: ((t: LayoutTree) => LayoutTree)[] = []\n\n\tconst commitAndNotify = (mut: (t: LayoutTree) => LayoutTree): void => {\n\t\t// Compute first; if it throws, nothing below runs — revision stays put\n\t\t// and no subscriber is notified.\n\t\tconst next = mut(current)\n\t\tcurrent = next\n\t\trevision += 1\n\t\tconst snap: LayoutSnapshot = { tree: current, revision }\n\t\t// Snapshot the subscriber set so unsubscribe during delivery is safe.\n\t\t// Isolate subscriber errors: a throw from one must not abort delivery to\n\t\t// the rest, nor make apply() throw after a committed state. The model's\n\t\t// job is delivery, not subscriber correctness — swallow and continue.\n\t\tfor (const fn of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tfn(snap)\n\t\t\t}\n\t\t\tcatch {\n\t\t\t\t// intentionally swallowed — see above.\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tget(): LayoutTree {\n\t\t\treturn current\n\t\t},\n\t\t/**\n\t\t * `mut` MUST be pure (clone-on-write); the built-in mutations satisfy this.\n\t\t */\n\t\tapply(mut: (t: LayoutTree) => LayoutTree): void {\n\t\t\tif (applying) {\n\t\t\t\t// Re-entrant call (a subscriber applied during notification): queue it\n\t\t\t\t// and let the in-flight drain pass process it after the current pass.\n\t\t\t\tqueue.push(mut)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tapplying = true\n\t\t\ttry {\n\t\t\t\t// Top-level (non-re-entrant) apply: a throw here propagates to the\n\t\t\t\t// caller and does NOT advance revision / notify. (`finally` below\n\t\t\t\t// still resets `applying`, so a throw can't wedge the model.)\n\t\t\t\tcommitAndNotify(mut)\n\t\t\t\t// Drain re-entrant mutations enqueued during notification. Each queued\n\t\t\t\t// apply was deferred and is therefore fire-and-forget: its original\n\t\t\t\t// caller already returned, so it cannot deliver a synchronous throw\n\t\t\t\t// back. Run each as an INDEPENDENT transaction — a throw skips that\n\t\t\t\t// item (no revision bump, no notify) and we continue draining the rest\n\t\t\t\t// in FIFO order. This keeps a thrown queued mut from bubbling into the\n\t\t\t\t// already-committed outer apply() and from disrupting the order /\n\t\t\t\t// processing of later queued items.\n\t\t\t\twhile (queue.length > 0) {\n\t\t\t\t\tconst queuedMut = queue.shift()!\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcommitAndNotify(queuedMut)\n\t\t\t\t\t}\n\t\t\t\t\tcatch {\n\t\t\t\t\t\t// Deferred apply: swallow + continue — see above.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\tapplying = false\n\t\t\t}\n\t\t},\n\t\tsubscribe(fn: (snap: LayoutSnapshot) => void): () => void {\n\t\t\tsubscribers.add(fn)\n\t\t\treturn (): void => {\n\t\t\t\tsubscribers.delete(fn)\n\t\t\t}\n\t\t},\n\t}\n}\n","/**\n * Capability-aware layout operations for user-triggered interactions.\n *\n * Keep these separate from the pure mutations: callers may still use\n * `closePanel` for restore/migration/programmatic layout transforms, while UI\n * entry points consistently honor registry capabilities.\n */\nimport { closePanel } from './mutations.js'\nimport type { LayoutTree, PanelRegistry } from './types.js'\n\n/**\n * Close a panel only when its descriptor permits user closure.\n *\n * Missing descriptors and an omitted `closable` capability retain the historic\n * close behavior. Only an explicit `closable:false` blocks the operation.\n */\nexport function closePanelForUser(\n\ttree: LayoutTree,\n\tpanelId: string,\n\tregistry: PanelRegistry,\n): LayoutTree {\n\treturn registry.get(panelId)?.closable === false ? tree : closePanel(tree, panelId)\n}\n"],"mappings":"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"}
|