@dimina-kit/electron-deck 0.1.0-dev.20260711141929 → 0.1.0-dev.20260716163758

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/dock-react/drag-redock.ts","../../src/dock-react/split-sizing.ts","../../src/dock-react/split-view.tsx","../../src/dock-react/panel-body.tsx","../../src/dock-react/group-view.tsx","../../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 * A zero/negative/non-finite rect has no meaningful edge bands; a non-finite\n * point can't be classified either. Note a non-finite WIDTH/HEIGHT (e.g.\n * Infinity) satisfies `> 0` and would otherwise slip through — `band = ef *\n * min(Infinity, h)` yields a finite band and the point is misclassified as an\n * EDGE zone — so reject finiteness EXPLICITLY (N1).\n */\nfunction isDegenerateDropInput(width: number, height: number, x: number, y: number): boolean {\n\treturn (\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}\n\n/**\n * Clamp the band fraction to [0, 0.5] (N1): a fraction > 0.5 makes the left\n * and right bands (and top/bottom) OVERLAP, so a point near the right edge of\n * a narrow rect would satisfy BOTH `inLeft` and `inRight` and be misread as\n * `left`. Capping at 0.5 keeps the two bands disjoint.\n */\nfunction clampEdgeFraction(edgeFraction: number): number {\n\treturn Number.isFinite(edgeFraction) ? Math.max(0, Math.min(0.5, edgeFraction)) : 0.25\n}\n\n/**\n * A pointer dragged past the panel edge has no band membership; clamp it to\n * the nearest edge zone. `undefined` when the point is IN the rect (the\n * caller falls through to band classification). Both axes out (a diagonal\n * corner): the axis with the LARGER overshoot magnitude wins; an exact tie is\n * broken by HORIZONTAL (the x axis).\n */\nfunction classifyOutOfRectDropZone(width: number, height: number, x: number, y: number): DropZone | undefined {\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) return undefined\n\n\tconst magX = Math.abs(outX)\n\tconst magY = Math.abs(outY)\n\tif (magX >= magY && magX > 0) return outX < 0 ? 'left' : 'right'\n\t// Only y out, or y overshoot strictly larger.\n\treturn outY < 0 ? 'top' : 'bottom'\n}\n\n/** Edge-band membership for an in-rect point, plus the per-axis \"is the point\n * in SOME band on this axis\" summary the zone decision branches on. */\ninterface BandMembership {\n\tinLeft: boolean\n\tinTop: boolean\n\tactiveHoriz: boolean\n\tactiveVert: boolean\n}\n\nfunction computeBandMembership(width: number, height: number, x: number, y: number, ef: number): BandMembership {\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\treturn { inLeft, inTop, activeHoriz: inLeft || inRight, activeVert: inTop || inBottom }\n}\n\n/**\n * Corner tie-break (both axes active): pick the edge with the SMALLER\n * normalized distance. Only the ACTIVE horizontal edge and ACTIVE vertical\n * edge can compete (left vs right and top vs bottom can't both be active for\n * a sane rect). On an exact tie, HORIZONTAL wins (`<=` on the horizontal\n * distance).\n */\nfunction cornerTieBreak(width: number, height: number, x: number, y: number, inLeft: boolean, inTop: boolean): DropZone {\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 * Classify an IN-RECT point by edge-band membership. No band => interior\n * (`center`, the tab-join zone). One active band => that edge directly. Both\n * axes active (a corner) => {@link cornerTieBreak}.\n */\nfunction classifyInRectDropZone(width: number, height: number, x: number, y: number, ef: number): DropZone {\n\tconst { inLeft, inTop, activeHoriz, activeVert } = computeBandMembership(width, height, x, y, ef)\n\n\tif (!activeHoriz && !activeVert) return 'center'\n\tif (activeHoriz && !activeVert) return inLeft ? 'left' : 'right'\n\tif (activeVert && !activeHoriz) return inTop ? 'top' : 'bottom'\n\n\treturn cornerTieBreak(width, height, x, y, inLeft, inTop)\n}\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\tif (isDegenerateDropInput(width, height, x, y)) return 'center'\n\n\tconst ef = clampEdgeFraction(edgeFraction)\n\n\tconst outOfRectZone = classifyOutOfRectDropZone(width, height, x, y)\n\tif (outOfRectZone !== undefined) return outOfRectZone\n\n\treturn classifyInRectDropZone(width, height, x, y, ef)\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/**\n * Translate a tab strip's VISIBLE-tab drop index into the insertion index\n * `movePanel`'s same-group reorder expects.\n *\n * `computeReorderIndex` reports how many VISIBLE-tab midpoints the pointer has\n * passed (0..`visibleTabIds.length`), COUNTING the dragged tab's own midpoint and\n * measured over rects that OMIT `hideTab` panels. `movePanel` inserts into\n * `panels.filter(p => p !== dragged)` — a different coordinate space. Two shifts\n * reconcile them:\n *\n * 1. Once the pointer passes the dragged tab's OWN midpoint the strip index has\n * counted the dragged slot, so drop it back out (−1) to get the insertion\n * slot among the OTHER visible tabs.\n * 2. Map that visible slot onto the full `panels` order (which may carry hidden\n * tabs the strip never measured): insert before whichever visible tab now\n * occupies the slot, or append when the slot is past the last visible tab.\n */\nexport function resolveReorderInsertIndex(\n\tpanels: readonly string[],\n\tvisibleTabIds: readonly string[],\n\tdraggedPanelId: string,\n\tstripInsertIndex: number,\n): number {\n\tconst draggedVisibleIndex = visibleTabIds.indexOf(draggedPanelId)\n\tconst passedOwnMidpoint = draggedVisibleIndex >= 0 && stripInsertIndex > draggedVisibleIndex\n\tconst filteredVisible = visibleTabIds.filter((id) => id !== draggedPanelId)\n\tconst filteredPanels = panels.filter((id) => id !== draggedPanelId)\n\n\tlet visibleInsert = passedOwnMidpoint ? stripInsertIndex - 1 : stripInsertIndex\n\tif (visibleInsert < 0) visibleInsert = 0\n\tif (visibleInsert > filteredVisible.length) visibleInsert = filteredVisible.length\n\n\tif (visibleInsert >= filteredVisible.length) return filteredPanels.length\n\tconst anchor = filteredVisible[visibleInsert]!\n\tconst anchorIndex = filteredPanels.indexOf(anchor)\n\treturn anchorIndex >= 0 ? anchorIndex : filteredPanels.length\n}\n","/**\n * Pure split-sizing math for the dock renderer — no react import.\n *\n * These functions translate between the model's raw per-child WEIGHTS (+ px\n * constraints) and the percentage/ratio bases the react-resizable-panels Group\n * consumes and reports. They are the single arithmetic authority behind\n * `SplitView`'s model→view sync and resize write-back; several are exported so\n * the sizing invariants can be unit-tested directly.\n */\nimport type { SizeConstraint } from '../layout/index.js'\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. */\nexport const 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. */\nexport const FLEX_RATIO_TOLERANCE = 0.1\n\n/** Normalize raw split weights to percentages summing ~100 for `defaultSize`. */\nexport function 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\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 */\nexport function 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 */\nexport function 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 */\nexport function 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\tconst fixedTotal = sumFixedLivePercent(childIds, isFixedAt, live)\n\t// Without a measured live % for a fixed child we cannot preserve its px\n\t// lock faithfully — bail so we don't corrupt the fixed child.\n\tif (fixedTotal === null) return null\n\n\tconst remaining = Math.max(0, 100 - fixedTotal)\n\n\t// Flexible weight pool.\n\tlet flexWeightTotal = 0\n\tlet flexibleCount = 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\tflexibleCount += 1\n\t}\n\n\tconst out: Record<string, number> = {}\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tconst id = childIds[i]!\n\t\tout[id] = isFixedAt(i)\n\t\t\t? live[id]!\n\t\t\t: flexibleShare(sizes[i] ?? 0, remaining, flexWeightTotal, flexibleCount)\n\t}\n\treturn out\n}\n\n/**\n * Sum the fixed children's CURRENT live percentages (preserving their px\n * lock). Null when a fixed child has no measured live % — the caller cannot\n * build a faithful map then.\n */\nfunction sumFixedLivePercent(\n\tchildIds: readonly string[],\n\tisFixedAt: (i: number) => boolean,\n\tlive: Record<string, number>,\n): number | null {\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\tif (typeof livePct !== 'number') return null\n\t\tfixedTotal += livePct\n\t}\n\treturn fixedTotal\n}\n\n/**\n * One flexible child's percentage share of the `remaining` space. A degenerate\n * weight pool (total ≤ 0) splits the remaining space evenly instead.\n */\nfunction flexibleShare(\n\tweight: number,\n\tremaining: number,\n\tflexWeightTotal: number,\n\tflexibleCount: number,\n): number {\n\tif (flexWeightTotal <= 0) {\n\t\treturn flexibleCount > 0 ? remaining / flexibleCount : remaining\n\t}\n\treturn (remaining * (weight > 0 ? weight : 0)) / flexWeightTotal\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 */\nexport function 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","/**\n * `SplitView` — one split node rendered as a react-resizable-panels Group, plus\n * the model↔view sync machine (M1): it pushes the model's flexible weights into\n * the live Group via `setLayout` and writes a genuine user resize back through\n * `ctx.onApplyLayout`. The split-sizing arithmetic lives in `./split-sizing`.\n */\nimport { useCallback, useEffect, useRef, type ReactNode } from 'react'\nimport { Group, Panel, Separator } from 'react-resizable-panels'\nimport type { GroupImperativeHandle, Layout } from 'react-resizable-panels'\nimport type { LayoutNode, SplitNode } from '../layout/index.js'\nimport {\n\tbuildSetLayoutMap,\n\tclampFlexibleWeights,\n\tcomputeFlexiblePercentages,\n\tFLEX_RATIO_TOLERANCE,\n\tflexibleFloor,\n\tincomingFlexRatios,\n\tlayoutsEquivalent,\n} from './split-sizing.js'\nimport { renderNode, type RenderContext } from './dock-view.js'\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\nexport interface SplitViewProps {\n\tnode: SplitNode\n\tctx: RenderContext\n}\n\nexport function 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","/**\n * Tab-group body rendering under DOM-panel keepalive: `renderActiveBody` (the\n * body region) + `NativeSlot` (the anchor for an active native panel).\n */\nimport { Fragment, useCallback, useRef, type ReactNode } from 'react'\nimport type { TabGroupNode } from '../layout/index.js'\nimport type { RenderContext } from './dock-view.js'\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 */\nexport function 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\t// Bodies are absolutely stacked and inactive ones are `display:none`, so their\n\t// DOM sibling order carries no visual meaning. Render them in a STABLE order\n\t// (by panelId) rather than tab order: a tab reorder must not move a body's DOM\n\t// node, because a web host renders bodies as iframes and moving an iframe node\n\t// reloads it. The `key` stays panel-stable so activation never remounts a body.\n\tconst domBodies = node.panels\n\t\t.filter((panelId) => ctx.registry.get(panelId)?.kind !== 'native')\n\t\t.slice()\n\t\t.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\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","/**\n * `GroupView` — one tab GROUP: the draggable tab strip, the active body, the\n * imperative `__deckHandleDrop` seam, and a geometry-driven drop indicator while\n * a drag hovers. Owns all tab drag/drop handlers.\n */\nimport { useRef, useState, type DragEvent, type ReactNode } from 'react'\nimport type { TabGroupNode } from '../layout/index.js'\nimport { computeDropZone, computeReorderIndex, type DropZone } from './drag-redock.js'\nimport { renderActiveBody } from './panel-body.js'\nimport type { RenderContext } from './dock-view.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/** 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\nexport interface 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 */\nexport function 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\t// Only a genuine deck drag (carrying the deck panel MIME) may drive the drop\n\t\t// geometry — same contract the tab strip enforces. A foreign OS drag (files,\n\t\t// external text) never previews a drop indicator nor becomes a valid target.\n\t\tif (!e.dataTransfer?.types.includes(DRAG_PANEL_MIME)) return\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\tconst draggedId = ctx.activeDragPanelId.current\n\t\tif (\n\t\t\tctx.suppressReorderOnlyDropIndicator\n\t\t\t&& draggedId !== null\n\t\t\t&& ctx.registry.get(draggedId)?.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\t// Only the deck's own MIME identifies a panel drag. A `text/plain` value from\n\t\t// a foreign drag may coincide with a registered panel id, so it is NOT trusted.\n\t\tconst dragged = e.dataTransfer?.getData(DRAG_PANEL_MIME)\n\t\t// Validate the payload before committing a re-dock:\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\t// Only the deck's own MIME identifies a panel drag (a foreign `text/plain`\n\t\t// value may coincide with a registered id and must not drive a reorder).\n\t\tconst dragged = e.dataTransfer?.getData(DRAG_PANEL_MIME)\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\tctx.activeDragPanelId.current = 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\tctx.activeDragPanelId.current = 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\telse ctx.onActiveTabClick?.(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 * `<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. The stateful child\n * views (`SplitView`, `GroupView`) and the body renderer live in sibling\n * modules; this file owns the orchestration (`renderNode`) + the shared\n * `RenderContext`.\n */\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n\ttype ReactNode,\n} from 'react'\nimport {\n\tclosePanelForUser,\n\tcountPanels,\n\textractPanel,\n\tfindGroupById,\n\tfindPanelGroupId,\n\tmovePanel,\n\tsetActive,\n\tsetSizes,\n\tsplitPanel,\n} from '../layout/index.js'\nimport type {\n\tLayoutModel,\n\tLayoutNode,\n\tLayoutTree,\n\tPanelRegistry,\n\tSplitNode,\n\tTabGroupNode,\n} from '../layout/index.js'\nimport {\n\tdropZoneToMutation,\n\tisNoopRedock,\n\tresolveReorderInsertIndex,\n\ttype DropZone,\n} from './drag-redock.js'\nimport { SplitView } from './split-view.js'\nimport { GroupView } from './group-view.js'\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\t/**\n\t * Fires when the user clicks a tab that is ALREADY active. The host can use\n\t * this to toggle the panel's visibility (e.g. collapse the debug group).\n\t * When absent, clicking an active tab is a no-op (the existing behaviour).\n\t */\n\tonActiveTabClick?: (panelId: string) => 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/**\n * Apply a `reorder-only` panel's redock. Such a panel may ONLY reorder WITHIN\n * its own group: it never leaves the group (a center drop into another group)\n * and never edge-splits (any edge zone, own or other group) — those drops are\n * rejected here. The reorder anchors on the pointer-derived index when the\n * gesture supplies one (real drag), else on the active anchor's CURRENT index\n * (the imperative seam carries no pointer). The pointer index is a VISIBLE-tab\n * strip index (hideTab panels omitted, dragged tab's own slot counted); the\n * strip↔model coordinate reconciliation lives only here.\n */\nfunction applyReorderOnlyRedock(\n\tmodel: LayoutModel,\n\tregistry: PanelRegistry,\n\tdrop: {\n\t\tgroupId: string\n\t\tactivePanelId: string\n\t\tdraggedPanelId: string\n\t\tdraggedGroupId: string | undefined\n\t\tzone: DropZone\n\t\treorderIndex: number | undefined\n\t},\n): void {\n\tconst { groupId, activePanelId, draggedPanelId, draggedGroupId, zone, reorderIndex } = drop\n\tif (draggedGroupId === undefined || draggedGroupId !== groupId) return\n\tif (zone !== 'center') return\n\tconst group = findGroupById(model.get().root, groupId)\n\tlet index: number | undefined\n\tif (reorderIndex !== undefined && group) {\n\t\tconst visibleTabIds = group.panels.filter(\n\t\t\t(panelId) => registry.get(panelId)?.hideTab !== true,\n\t\t)\n\t\tindex = resolveReorderInsertIndex(group.panels, visibleTabIds, draggedPanelId, reorderIndex)\n\t}\n\telse {\n\t\tindex = group ? group.panels.indexOf(activePanelId) : undefined\n\t}\n\tmodel.apply((t) => movePanel(t, draggedPanelId, { groupId, index }))\n}\n\nexport function DockView(props: DockViewProps): ReactNode {\n\tconst { model, registry, renderDomPanel, bindNativeSlot, suppressReorderOnlyDropIndicator, onActiveTabClick } = 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\t// The panel id of the in-flight tab drag, INSTANCE-scoped (per DockView). Set\n\t// on `dragstart`, cleared on `dragend`/`drop`. The DataTransfer VALUE is\n\t// unreadable during `dragover` (only `types` is exposed, by spec), so the\n\t// drop-indicator path can't recover the dragged id from the event — it reads\n\t// it from here instead to decide whether to paint a highlight for THIS drag\n\t// (e.g. a `reorder-only` panel shows none). Held per instance (not module\n\t// scope) so two DockViews on one page never share a drag and unmount leaves no\n\t// residue. Threaded to every GroupView via the RenderContext.\n\tconst activeDragPanelId = useRef<string | null>(null)\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 is\n\t\t\t// routed to its dedicated handler BEFORE the no-op gate — its one legal\n\t\t\t// motion (a center drop into its OWN group) must OVERRIDE `isNoopRedock`\n\t\t\t// (which would swallow it as churn). ──\n\t\t\tif (registry.get(draggedPanelId)?.dropPolicy === 'reorder-only') {\n\t\t\t\tapplyReorderOnlyRedock(model, registry, {\n\t\t\t\t\tgroupId,\n\t\t\t\t\tactivePanelId,\n\t\t\t\t\tdraggedPanelId,\n\t\t\t\t\tdraggedGroupId,\n\t\t\t\t\tzone,\n\t\t\t\t\treorderIndex,\n\t\t\t\t})\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\tonActiveTabClick,\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\tactiveDragPanelId,\n\t\t\t})}\n\t\t</LayoutEpochContext.Provider>\n\t)\n}\n\n/** The shared per-render context threaded from `DockView` down through\n * `renderNode` into every `SplitView`/`GroupView`. Exported so the sibling view\n * modules type their `ctx` against the single source. */\nexport interface 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\tonActiveTabClick: ((panelId: string) => void) | undefined\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\t/** The in-flight tab-drag panel id, INSTANCE-scoped to the owning DockView\n\t * (set on `dragstart`, cleared on `dragend`/`drop`). The drop-indicator path\n\t * reads it during `dragover`, where the DataTransfer value is unreadable. */\n\tactiveDragPanelId: { current: string | null }\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\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\nexport { renderNode }\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/dock-react/drag-redock.ts","../../src/dock-react/split-sizing.ts","../../src/dock-react/split-view.tsx","../../src/dock-react/panel-body.tsx","../../src/dock-react/group-view.tsx","../../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 * A zero/negative/non-finite rect has no meaningful edge bands; a non-finite\n * point can't be classified either. Note a non-finite WIDTH/HEIGHT (e.g.\n * Infinity) satisfies `> 0` and would otherwise slip through — `band = ef *\n * min(Infinity, h)` yields a finite band and the point is misclassified as an\n * EDGE zone — so reject finiteness EXPLICITLY (N1).\n */\nfunction isDegenerateDropInput(width: number, height: number, x: number, y: number): boolean {\n\treturn (\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}\n\n/**\n * Clamp the band fraction to [0, 0.5] (N1): a fraction > 0.5 makes the left\n * and right bands (and top/bottom) OVERLAP, so a point near the right edge of\n * a narrow rect would satisfy BOTH `inLeft` and `inRight` and be misread as\n * `left`. Capping at 0.5 keeps the two bands disjoint.\n */\nfunction clampEdgeFraction(edgeFraction: number): number {\n\treturn Number.isFinite(edgeFraction) ? Math.max(0, Math.min(0.5, edgeFraction)) : 0.25\n}\n\n/**\n * A pointer dragged past the panel edge has no band membership; clamp it to\n * the nearest edge zone. `undefined` when the point is IN the rect (the\n * caller falls through to band classification). Both axes out (a diagonal\n * corner): the axis with the LARGER overshoot magnitude wins; an exact tie is\n * broken by HORIZONTAL (the x axis).\n */\nfunction classifyOutOfRectDropZone(width: number, height: number, x: number, y: number): DropZone | undefined {\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) return undefined\n\n\tconst magX = Math.abs(outX)\n\tconst magY = Math.abs(outY)\n\tif (magX >= magY && magX > 0) return outX < 0 ? 'left' : 'right'\n\t// Only y out, or y overshoot strictly larger.\n\treturn outY < 0 ? 'top' : 'bottom'\n}\n\n/** Edge-band membership for an in-rect point, plus the per-axis \"is the point\n * in SOME band on this axis\" summary the zone decision branches on. */\ninterface BandMembership {\n\tinLeft: boolean\n\tinTop: boolean\n\tactiveHoriz: boolean\n\tactiveVert: boolean\n}\n\nfunction computeBandMembership(width: number, height: number, x: number, y: number, ef: number): BandMembership {\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\treturn { inLeft, inTop, activeHoriz: inLeft || inRight, activeVert: inTop || inBottom }\n}\n\n/**\n * Corner tie-break (both axes active): pick the edge with the SMALLER\n * normalized distance. Only the ACTIVE horizontal edge and ACTIVE vertical\n * edge can compete (left vs right and top vs bottom can't both be active for\n * a sane rect). On an exact tie, HORIZONTAL wins (`<=` on the horizontal\n * distance).\n */\nfunction cornerTieBreak(width: number, height: number, x: number, y: number, inLeft: boolean, inTop: boolean): DropZone {\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 * Classify an IN-RECT point by edge-band membership. No band => interior\n * (`center`, the tab-join zone). One active band => that edge directly. Both\n * axes active (a corner) => {@link cornerTieBreak}.\n */\nfunction classifyInRectDropZone(width: number, height: number, x: number, y: number, ef: number): DropZone {\n\tconst { inLeft, inTop, activeHoriz, activeVert } = computeBandMembership(width, height, x, y, ef)\n\n\tif (!activeHoriz && !activeVert) return 'center'\n\tif (activeHoriz && !activeVert) return inLeft ? 'left' : 'right'\n\tif (activeVert && !activeHoriz) return inTop ? 'top' : 'bottom'\n\n\treturn cornerTieBreak(width, height, x, y, inLeft, inTop)\n}\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\tif (isDegenerateDropInput(width, height, x, y)) return 'center'\n\n\tconst ef = clampEdgeFraction(edgeFraction)\n\n\tconst outOfRectZone = classifyOutOfRectDropZone(width, height, x, y)\n\tif (outOfRectZone !== undefined) return outOfRectZone\n\n\treturn classifyInRectDropZone(width, height, x, y, ef)\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/**\n * Translate a tab strip's VISIBLE-tab drop index into the insertion index\n * `movePanel`'s same-group reorder expects.\n *\n * `computeReorderIndex` reports how many VISIBLE-tab midpoints the pointer has\n * passed (0..`visibleTabIds.length`), COUNTING the dragged tab's own midpoint and\n * measured over rects that OMIT `hideTab` panels. `movePanel` inserts into\n * `panels.filter(p => p !== dragged)` — a different coordinate space. Two shifts\n * reconcile them:\n *\n * 1. Once the pointer passes the dragged tab's OWN midpoint the strip index has\n * counted the dragged slot, so drop it back out (−1) to get the insertion\n * slot among the OTHER visible tabs.\n * 2. Map that visible slot onto the full `panels` order (which may carry hidden\n * tabs the strip never measured): insert before whichever visible tab now\n * occupies the slot, or append when the slot is past the last visible tab.\n */\nexport function resolveReorderInsertIndex(\n\tpanels: readonly string[],\n\tvisibleTabIds: readonly string[],\n\tdraggedPanelId: string,\n\tstripInsertIndex: number,\n): number {\n\tconst draggedVisibleIndex = visibleTabIds.indexOf(draggedPanelId)\n\tconst passedOwnMidpoint = draggedVisibleIndex >= 0 && stripInsertIndex > draggedVisibleIndex\n\tconst filteredVisible = visibleTabIds.filter((id) => id !== draggedPanelId)\n\tconst filteredPanels = panels.filter((id) => id !== draggedPanelId)\n\n\tlet visibleInsert = passedOwnMidpoint ? stripInsertIndex - 1 : stripInsertIndex\n\tif (visibleInsert < 0) visibleInsert = 0\n\tif (visibleInsert > filteredVisible.length) visibleInsert = filteredVisible.length\n\n\tif (visibleInsert >= filteredVisible.length) return filteredPanels.length\n\tconst anchor = filteredVisible[visibleInsert]!\n\tconst anchorIndex = filteredPanels.indexOf(anchor)\n\treturn anchorIndex >= 0 ? anchorIndex : filteredPanels.length\n}\n","/**\n * Pure split-sizing math for the dock renderer — no react import.\n *\n * These functions translate between the model's raw per-child WEIGHTS (+ px\n * constraints) and the percentage/ratio bases the react-resizable-panels Group\n * consumes and reports. They are the single arithmetic authority behind\n * `SplitView`'s model→view sync and resize write-back; several are exported so\n * the sizing invariants can be unit-tested directly.\n */\nimport type { SizeConstraint } from '../layout/index.js'\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. */\nexport const 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. */\nexport const FLEX_RATIO_TOLERANCE = 0.1\n\n/** Normalize raw split weights to percentages summing ~100 for `defaultSize`. */\nexport function 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\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 */\nexport function 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 */\nexport function 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 * A real measurement of the split's container, along its layout axis (width\n * for a `row` split, height for `column`) — the ONLY reliable source for a\n * px-constrained child's target percentage right after a fresh Group mount,\n * when rrp has not yet established a trustworthy live layout for it (see\n * `buildSetLayoutMap`).\n */\nexport interface MeasuredContainer {\n\t/** Real measured pixel size of the split's container. */\n\tcontainerPx: number\n\t/**\n\t * Whether a `minPx` child's CURRENT live percentage should be trusted over\n\t * recomputing it from `constraint.minPx`. `minPx` is a FLOOR, not a lock —\n\t * the user may have legitimately dragged it wider, and an ONGOING sync\n\t * (this flag `true`) must not snap that back down to the floor. Only the\n\t * FIRST sync after a fresh Group remount (this flag `false`) has no such\n\t * history to preserve, so the floor is authoritative there. `fixedPx`\n\t * children are unaffected by this flag — a locked child is always computed\n\t * from the measurement, in both cases.\n\t */\n\ttrustLiveForMinPx: boolean\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 normally keep their CURRENT measured\n * percentage (read from the live `getLayout()`) — they are NOT derived\n * from weights, so a flexible-weights change never disturbs their pixel\n * lock. When `measured` is supplied, a `fixedPx` child is instead always\n * computed from the real container size (it never legitimately differs\n * from its exact px value), and a `minPx` child is too, but ONLY when\n * `measured.trustLiveForMinPx` is `false` — see `MeasuredContainer`. This\n * matters because right after `<Group>` cold-mounts a `minPx`/`fixedPx`\n * child whose content is itself a NESTED split, rrp's own mount-time\n * px→percentage conversion for that child can land on a degenerate ratio\n * (observed: a pinned child grabbing ~99% while its lone flexible sibling\n * collapses to rrp's floor) — the live layout it reports is simply wrong,\n * and blindly trusting it would perpetuate the collapse forever (there is\n * no subsequent event that would ever correct it).\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 and no `measured`\n * fallback applies), so the caller skips the `setLayout` rather than pushing a\n * corrupt total. The result always sums to ~100 over all children.\n */\nexport function buildSetLayoutMap(\n\tchildIds: readonly string[],\n\tsizes: readonly number[],\n\tconstraints: readonly (SizeConstraint | null)[] | undefined,\n\tlive: Record<string, number>,\n\tmeasured?: MeasuredContainer,\n): Record<string, number> | null {\n\tconst isFixedAt = (i: number): boolean => (constraints?.[i] ?? null) !== null\n\n\t// The target percentage for a FIXED (px-constrained) child at index `i`.\n\t// Prefers a direct px→percentage computation from a real measurement over\n\t// trusting rrp's live-reported value — see the doc comment above for why.\n\tconst fixedPercentAt = (i: number): number | null => {\n\t\tconst constraint = constraints?.[i] ?? null\n\t\tif (measured && measured.containerPx > 0 && constraint) {\n\t\t\tif (constraint.fixedPx != null) {\n\t\t\t\treturn (constraint.fixedPx / measured.containerPx) * 100\n\t\t\t}\n\t\t\tif (constraint.minPx != null && !measured.trustLiveForMinPx) {\n\t\t\t\treturn (constraint.minPx / measured.containerPx) * 100\n\t\t\t}\n\t\t}\n\t\tconst livePct = live[childIds[i]!]\n\t\treturn typeof livePct === 'number' ? livePct : null\n\t}\n\n\tconst fixedTotal = sumFixedPercent(childIds, isFixedAt, fixedPercentAt)\n\t// Without a usable percentage for a fixed child we cannot build a faithful\n\t// map — bail so we don't corrupt the fixed child.\n\tif (fixedTotal === null) return null\n\n\tconst remaining = Math.max(0, 100 - fixedTotal)\n\n\t// Flexible weight pool.\n\tlet flexWeightTotal = 0\n\tlet flexibleCount = 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\tflexibleCount += 1\n\t}\n\n\tconst out: Record<string, number> = {}\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tconst id = childIds[i]!\n\t\tout[id] = isFixedAt(i)\n\t\t\t? fixedPercentAt(i)!\n\t\t\t: flexibleShare(sizes[i] ?? 0, remaining, flexWeightTotal, flexibleCount)\n\t}\n\treturn out\n}\n\n/**\n * Sum the fixed children's target percentages (via `fixedPercentAt`). Null\n * when any fixed child has no usable percentage — the caller cannot build a\n * faithful map then.\n */\nfunction sumFixedPercent(\n\tchildIds: readonly string[],\n\tisFixedAt: (i: number) => boolean,\n\tfixedPercentAt: (i: number) => number | null,\n): number | null {\n\tlet fixedTotal = 0\n\tfor (let i = 0; i < childIds.length; i++) {\n\t\tif (!isFixedAt(i)) continue\n\t\tconst pct = fixedPercentAt(i)\n\t\tif (pct === null) return null\n\t\tfixedTotal += pct\n\t}\n\treturn fixedTotal\n}\n\n/**\n * One flexible child's percentage share of the `remaining` space. A degenerate\n * weight pool (total ≤ 0) splits the remaining space evenly instead.\n */\nfunction flexibleShare(\n\tweight: number,\n\tremaining: number,\n\tflexWeightTotal: number,\n\tflexibleCount: number,\n): number {\n\tif (flexWeightTotal <= 0) {\n\t\treturn flexibleCount > 0 ? remaining / flexibleCount : remaining\n\t}\n\treturn (remaining * (weight > 0 ? weight : 0)) / flexWeightTotal\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 */\nexport function 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","/**\n * `SplitView` — one split node rendered as a react-resizable-panels Group, plus\n * the model↔view sync machine (M1): it pushes the model's flexible weights into\n * the live Group via `setLayout` and writes a genuine user resize back through\n * `ctx.onApplyLayout`. The split-sizing arithmetic lives in `./split-sizing`.\n */\nimport { useCallback, useEffect, useRef, type ReactNode } from 'react'\nimport { Group, Panel, Separator } from 'react-resizable-panels'\nimport type { GroupImperativeHandle, Layout } from 'react-resizable-panels'\nimport type { LayoutNode, SplitNode } from '../layout/index.js'\nimport {\n\tbuildSetLayoutMap,\n\tclampFlexibleWeights,\n\tcomputeFlexiblePercentages,\n\tFLEX_RATIO_TOLERANCE,\n\tflexibleFloor,\n\tincomingFlexRatios,\n\tlayoutsEquivalent,\n} from './split-sizing.js'\nimport { renderNode, type RenderContext } from './dock-view.js'\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\nexport interface SplitViewProps {\n\tnode: SplitNode\n\tctx: RenderContext\n}\n\nexport function 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// The split's own wrapper element — read for its REAL measured pixel size\n\t// (Bug #3 defense, see `runSync`). Set by `setSplitRef` alongside the other\n\t// imperative seams on the same element.\n\tconst containerElRef = useRef<HTMLDivElement | 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\tcontainerElRef.current = el\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.\n\t//\n\t// `isFreshRemount` is `true` exactly once per Group instance — the first sync\n\t// right after it mounted (see the `childCountKey` effect below). Bug #3 defense:\n\t// when a `minPx`/`fixedPx` child's content is itself a NESTED split, rrp's\n\t// mount-time px→percentage conversion for that child can land on a degenerate\n\t// ratio (observed: the pinned child grabbing ~99% while its lone flexible\n\t// sibling collapses to rrp's floor) — and because nothing ever re-measures\n\t// afterward, that wrong `live` value would otherwise be trusted and\n\t// perpetuated forever. On this first sync we instead derive the fixed child's\n\t// target percentage from a REAL measurement of the split's own container\n\t// (`containerElRef`), bypassing rrp's untrustworthy live value. Every\n\t// subsequent sync (`isFreshRemount: false`) keeps trusting `live` for a\n\t// `minPx` child, since the user may have legitimately dragged it wider than\n\t// its floor by then — see `MeasuredContainer`.\n\t//\n\t// Returns `stuck: true` when nothing needed pushing OR the push is confirmed\n\t// (by re-reading `getLayout()`) to have taken hold — `false` when rrp is not\n\t// ready yet (`groupRef` unpopulated) or SILENTLY CLAMPED our push back to a\n\t// stale constraint (empirically: right after a fresh mount, rrp's own\n\t// `derivedPanelConstraints` for the nested-split pinned child can still\n\t// reflect the same bad initial measurement, and `validatePanelGroupLayout`\n\t// rejects a legitimate corrective value against it — the constraint only\n\t// self-corrects once rrp's ResizeObserver re-measures on a LATER frame). The\n\t// `childCountKey` effect below retries (bounded) on `stuck: false`.\n\tconst runSync = useCallback((isFreshRemount: boolean): { pushed: boolean; stuck: boolean } => {\n\t\tconst api = groupRef.current\n\t\tif (!api) return { pushed: false, stuck: 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\tconst containerEl = containerElRef.current\n\t\tconst containerRect = containerEl?.getBoundingClientRect()\n\t\tconst containerPx = containerRect\n\t\t\t? (cur.orientation === 'row' ? containerRect.width : containerRect.height)\n\t\t\t: 0\n\t\tconst measured = containerPx > 0 ? { containerPx, trustLiveForMinPx: !isFreshRemount } : undefined\n\t\t// jsdom's stub returns `{}` (no measured geometry) and a 0 `getBoundingClientRect`\n\t\t// — `measured` is then `undefined` and `buildSetLayoutMap` falls back to its\n\t\t// live-trusting behavior, which in turn returns null for any fixed child; but\n\t\t// `setLayout` is a no-op under jsdom anyway. In a real renderer both are populated.\n\t\tconst targetMap = buildSetLayoutMap(curChildIds, cur.sizes, cur.constraints, live, measured)\n\t\tif (!targetMap) return { pushed: false, stuck: 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 { pushed: false, stuck: true }\n\n\t\tapi.setLayout(targetMap)\n\t\t// Confirm the push actually stuck — rrp's `setLayout` validates the\n\t\t// incoming layout against its OWN panel constraints and silently clamps\n\t\t// anything that violates them (see the doc comment above).\n\t\tconst stuck = layoutsEquivalent(api.getLayout(), targetMap, curChildIds)\n\t\treturn { pushed: true, stuck }\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\t// Tracks the child count as of the LAST sync, so this render can tell whether\n\t// the Group instance behind `groupRef` is the one that was already synced\n\t// (an ordinary weight change) or a brand new one (`key={node.children.length}`\n\t// just remounted it, or this is the very first mount — seeded `null` so the\n\t// initial sync is ALSO treated as fresh, covering a session that restores\n\t// straight into a shape prone to Bug #3, e.g. `belowSimulator`).\n\tconst prevChildCountKeyRef = useRef<number | null>(null)\n\t// Bug #3 defense (continued from `runSync`'s doc comment): an ordinary\n\t// (non-remount) sync sticks synchronously today — pinned so this fix does\n\t// not add latency to that already-working path. A FRESH remount's push can\n\t// get silently clamped by a stale rrp constraint that only self-corrects a\n\t// couple of animation frames later; retry (bounded, empirically converges\n\t// within 2 frames) until `runSync` confirms the push stuck, rather than\n\t// hard-coding an exact frame count that could be too short on a slower host.\n\tconst MAX_FRESH_REMOUNT_SYNC_ATTEMPTS = 8\n\tuseEffect(() => {\n\t\tconst isFreshRemount = prevChildCountKeyRef.current !== childCountKey\n\t\tprevChildCountKeyRef.current = childCountKey\n\n\t\tif (!isFreshRemount) {\n\t\t\trunSync(false)\n\t\t\treturn\n\t\t}\n\n\t\tlet cancelled = false\n\t\tlet rafId = 0\n\t\tlet attempts = 0\n\t\tconst tick = (): void => {\n\t\t\tif (cancelled) return\n\t\t\tattempts += 1\n\t\t\tconst { stuck } = runSync(true)\n\t\t\tif (!stuck && attempts < MAX_FRESH_REMOUNT_SYNC_ATTEMPTS) {\n\t\t\t\trafId = requestAnimationFrame(tick)\n\t\t\t}\n\t\t}\n\t\trafId = requestAnimationFrame(tick)\n\t\treturn () => {\n\t\t\tcancelled = true\n\t\t\tcancelAnimationFrame(rafId)\n\t\t}\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","/**\n * Tab-group body rendering under DOM-panel keepalive: `renderActiveBody` (the\n * body region) + `NativeSlot` (the anchor for an active native panel).\n */\nimport { Fragment, useCallback, useRef, type ReactNode } from 'react'\nimport type { TabGroupNode } from '../layout/index.js'\nimport type { RenderContext } from './dock-view.js'\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 */\nexport function 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\t// Bodies are absolutely stacked and inactive ones are `display:none`, so their\n\t// DOM sibling order carries no visual meaning. Render them in a STABLE order\n\t// (by panelId) rather than tab order: a tab reorder must not move a body's DOM\n\t// node, because a web host renders bodies as iframes and moving an iframe node\n\t// reloads it. The `key` stays panel-stable so activation never remounts a body.\n\tconst domBodies = node.panels\n\t\t.filter((panelId) => ctx.registry.get(panelId)?.kind !== 'native')\n\t\t.slice()\n\t\t.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))\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","/**\n * `GroupView` — one tab GROUP: the draggable tab strip, the active body, the\n * imperative `__deckHandleDrop` seam, and a geometry-driven drop indicator while\n * a drag hovers. Owns all tab drag/drop handlers.\n */\nimport { useRef, useState, type DragEvent, type ReactNode } from 'react'\nimport type { TabGroupNode } from '../layout/index.js'\nimport { computeDropZone, computeReorderIndex, type DropZone } from './drag-redock.js'\nimport { renderActiveBody } from './panel-body.js'\nimport type { RenderContext } from './dock-view.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/** 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\nexport interface 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 */\nexport function 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\t// Only a genuine deck drag (carrying the deck panel MIME) may drive the drop\n\t\t// geometry — same contract the tab strip enforces. A foreign OS drag (files,\n\t\t// external text) never previews a drop indicator nor becomes a valid target.\n\t\tif (!e.dataTransfer?.types.includes(DRAG_PANEL_MIME)) return\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\tconst draggedId = ctx.activeDragPanelId.current\n\t\tif (\n\t\t\tctx.suppressReorderOnlyDropIndicator\n\t\t\t&& draggedId !== null\n\t\t\t&& ctx.registry.get(draggedId)?.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\t// Only the deck's own MIME identifies a panel drag. A `text/plain` value from\n\t\t// a foreign drag may coincide with a registered panel id, so it is NOT trusted.\n\t\tconst dragged = e.dataTransfer?.getData(DRAG_PANEL_MIME)\n\t\t// Validate the payload before committing a re-dock:\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\t// Only the deck's own MIME identifies a panel drag (a foreign `text/plain`\n\t\t// value may coincide with a registered id and must not drive a reorder).\n\t\tconst dragged = e.dataTransfer?.getData(DRAG_PANEL_MIME)\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\tctx.activeDragPanelId.current = 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\tctx.activeDragPanelId.current = 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\telse ctx.onActiveTabClick?.(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 * `<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. The stateful child\n * views (`SplitView`, `GroupView`) and the body renderer live in sibling\n * modules; this file owns the orchestration (`renderNode`) + the shared\n * `RenderContext`.\n */\nimport {\n\tcreateContext,\n\tuseCallback,\n\tuseContext,\n\tuseEffect,\n\tuseRef,\n\tuseState,\n\ttype ReactNode,\n} from 'react'\nimport {\n\tclosePanelForUser,\n\tcountPanels,\n\textractPanel,\n\tfindGroupById,\n\tfindPanelGroupId,\n\tmovePanel,\n\tsetActive,\n\tsetSizes,\n\tsplitPanel,\n} from '../layout/index.js'\nimport type {\n\tLayoutModel,\n\tLayoutNode,\n\tLayoutTree,\n\tPanelRegistry,\n\tSplitNode,\n\tTabGroupNode,\n} from '../layout/index.js'\nimport {\n\tdropZoneToMutation,\n\tisNoopRedock,\n\tresolveReorderInsertIndex,\n\ttype DropZone,\n} from './drag-redock.js'\nimport { SplitView } from './split-view.js'\nimport { GroupView } from './group-view.js'\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\t/**\n\t * Fires when the user clicks a tab that is ALREADY active. The host can use\n\t * this to toggle the panel's visibility (e.g. collapse the debug group).\n\t * When absent, clicking an active tab is a no-op (the existing behaviour).\n\t */\n\tonActiveTabClick?: (panelId: string) => 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/**\n * Apply a `reorder-only` panel's redock. Such a panel may ONLY reorder WITHIN\n * its own group: it never leaves the group (a center drop into another group)\n * and never edge-splits (any edge zone, own or other group) — those drops are\n * rejected here. The reorder anchors on the pointer-derived index when the\n * gesture supplies one (real drag), else on the active anchor's CURRENT index\n * (the imperative seam carries no pointer). The pointer index is a VISIBLE-tab\n * strip index (hideTab panels omitted, dragged tab's own slot counted); the\n * strip↔model coordinate reconciliation lives only here.\n */\nfunction applyReorderOnlyRedock(\n\tmodel: LayoutModel,\n\tregistry: PanelRegistry,\n\tdrop: {\n\t\tgroupId: string\n\t\tactivePanelId: string\n\t\tdraggedPanelId: string\n\t\tdraggedGroupId: string | undefined\n\t\tzone: DropZone\n\t\treorderIndex: number | undefined\n\t},\n): void {\n\tconst { groupId, activePanelId, draggedPanelId, draggedGroupId, zone, reorderIndex } = drop\n\tif (draggedGroupId === undefined || draggedGroupId !== groupId) return\n\tif (zone !== 'center') return\n\tconst group = findGroupById(model.get().root, groupId)\n\tlet index: number | undefined\n\tif (reorderIndex !== undefined && group) {\n\t\tconst visibleTabIds = group.panels.filter(\n\t\t\t(panelId) => registry.get(panelId)?.hideTab !== true,\n\t\t)\n\t\tindex = resolveReorderInsertIndex(group.panels, visibleTabIds, draggedPanelId, reorderIndex)\n\t}\n\telse {\n\t\tindex = group ? group.panels.indexOf(activePanelId) : undefined\n\t}\n\tmodel.apply((t) => movePanel(t, draggedPanelId, { groupId, index }))\n}\n\nexport function DockView(props: DockViewProps): ReactNode {\n\tconst { model, registry, renderDomPanel, bindNativeSlot, suppressReorderOnlyDropIndicator, onActiveTabClick } = 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\t// The panel id of the in-flight tab drag, INSTANCE-scoped (per DockView). Set\n\t// on `dragstart`, cleared on `dragend`/`drop`. The DataTransfer VALUE is\n\t// unreadable during `dragover` (only `types` is exposed, by spec), so the\n\t// drop-indicator path can't recover the dragged id from the event — it reads\n\t// it from here instead to decide whether to paint a highlight for THIS drag\n\t// (e.g. a `reorder-only` panel shows none). Held per instance (not module\n\t// scope) so two DockViews on one page never share a drag and unmount leaves no\n\t// residue. Threaded to every GroupView via the RenderContext.\n\tconst activeDragPanelId = useRef<string | null>(null)\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 is\n\t\t\t// routed to its dedicated handler BEFORE the no-op gate — its one legal\n\t\t\t// motion (a center drop into its OWN group) must OVERRIDE `isNoopRedock`\n\t\t\t// (which would swallow it as churn). ──\n\t\t\tif (registry.get(draggedPanelId)?.dropPolicy === 'reorder-only') {\n\t\t\t\tapplyReorderOnlyRedock(model, registry, {\n\t\t\t\t\tgroupId,\n\t\t\t\t\tactivePanelId,\n\t\t\t\t\tdraggedPanelId,\n\t\t\t\t\tdraggedGroupId,\n\t\t\t\t\tzone,\n\t\t\t\t\treorderIndex,\n\t\t\t\t})\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\tonActiveTabClick,\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\tactiveDragPanelId,\n\t\t\t})}\n\t\t</LayoutEpochContext.Provider>\n\t)\n}\n\n/** The shared per-render context threaded from `DockView` down through\n * `renderNode` into every `SplitView`/`GroupView`. Exported so the sibling view\n * modules type their `ctx` against the single source. */\nexport interface 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\tonActiveTabClick: ((panelId: string) => void) | undefined\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\t/** The in-flight tab-drag panel id, INSTANCE-scoped to the owning DockView\n\t * (set on `dragstart`, cleared on `dragend`/`drop`). The drop-indicator path\n\t * reads it during `dragover`, where the DataTransfer value is unreadable. */\n\tactiveDragPanelId: { current: string | null }\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\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\nexport { renderNode }\n"],"mappings":"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"}
@@ -77,22 +77,55 @@ export declare function clampFlexibleWeights(weights: readonly number[], constra
77
77
  * NaN would slip through as "equivalent" and wrongly suppress a legitimate
78
78
  * sync/write-back (N1). EXPORTED (pure) for direct unit coverage. */
79
79
  export declare function layoutsEquivalent(a: Record<string, number>, b: Record<string, number>, ids: readonly string[], epsilon?: number): boolean;
80
+ /**
81
+ * A real measurement of the split's container, along its layout axis (width
82
+ * for a `row` split, height for `column`) — the ONLY reliable source for a
83
+ * px-constrained child's target percentage right after a fresh Group mount,
84
+ * when rrp has not yet established a trustworthy live layout for it (see
85
+ * `buildSetLayoutMap`).
86
+ */
87
+ export interface MeasuredContainer {
88
+ /** Real measured pixel size of the split's container. */
89
+ containerPx: number;
90
+ /**
91
+ * Whether a `minPx` child's CURRENT live percentage should be trusted over
92
+ * recomputing it from `constraint.minPx`. `minPx` is a FLOOR, not a lock —
93
+ * the user may have legitimately dragged it wider, and an ONGOING sync
94
+ * (this flag `true`) must not snap that back down to the floor. Only the
95
+ * FIRST sync after a fresh Group remount (this flag `false`) has no such
96
+ * history to preserve, so the floor is authoritative there. `fixedPx`
97
+ * children are unaffected by this flag — a locked child is always computed
98
+ * from the measurement, in both cases.
99
+ */
100
+ trustLiveForMinPx: boolean;
101
+ }
80
102
  /**
81
103
  * Build the panel-ID→PERCENTAGE map for an imperative `setLayout`, given the
82
104
  * model's full-length raw `sizes` (per child) and `constraints`:
83
105
  *
84
- * - FIXED (px-pinned) children keep their CURRENT measured percentage (read
85
- * from the live `getLayout()`); they are NOT derived from weights, so a
86
- * flexible-weights change never disturbs their pixel lock.
106
+ * - FIXED (px-pinned) children normally keep their CURRENT measured
107
+ * percentage (read from the live `getLayout()`) they are NOT derived
108
+ * from weights, so a flexible-weights change never disturbs their pixel
109
+ * lock. When `measured` is supplied, a `fixedPx` child is instead always
110
+ * computed from the real container size (it never legitimately differs
111
+ * from its exact px value), and a `minPx` child is too, but ONLY when
112
+ * `measured.trustLiveForMinPx` is `false` — see `MeasuredContainer`. This
113
+ * matters because right after `<Group>` cold-mounts a `minPx`/`fixedPx`
114
+ * child whose content is itself a NESTED split, rrp's own mount-time
115
+ * px→percentage conversion for that child can land on a degenerate ratio
116
+ * (observed: a pinned child grabbing ~99% while its lone flexible sibling
117
+ * collapses to rrp's floor) — the live layout it reports is simply wrong,
118
+ * and blindly trusting it would perpetuate the collapse forever (there is
119
+ * no subsequent event that would ever correct it).
87
120
  * - The REMAINING percentage (100 − Σ fixed%) is distributed across the
88
121
  * FLEXIBLE children in proportion to their weights.
89
122
  *
90
123
  * Returns `null` when the map can't be built faithfully (e.g. `live` is empty —
91
- * jsdom's stub — or a fixed child's live % is missing), so the caller skips the
92
- * `setLayout` rather than pushing a corrupt total. The result always sums to
93
- * ~100 over all children.
124
+ * jsdom's stub — or a fixed child's live % is missing and no `measured`
125
+ * fallback applies), so the caller skips the `setLayout` rather than pushing a
126
+ * corrupt total. The result always sums to ~100 over all children.
94
127
  */
95
- export declare function buildSetLayoutMap(childIds: readonly string[], sizes: readonly number[], constraints: readonly (SizeConstraint | null)[] | undefined, live: Record<string, number>): Record<string, number> | null;
128
+ export declare function buildSetLayoutMap(childIds: readonly string[], sizes: readonly number[], constraints: readonly (SizeConstraint | null)[] | undefined, live: Record<string, number>, measured?: MeasuredContainer): Record<string, number> | null;
96
129
  /**
97
130
  * Extract an rrp `onLayoutChanged` map's FLEXIBLE subset (skipping fixed-px
98
131
  * children) in child order and NORMALIZE it by its own sum to ratios summing to
@@ -1 +1 @@
1
- {"version":3,"file":"split-sizing.d.ts","sourceRoot":"","sources":["../../src/dock-react/split-sizing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD;;;;mEAImE;AACnE,eAAO,MAAM,cAAc,MAAM,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;;;2DAqB2D;AAC3D,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhE;AAED;;;;;;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;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,GACzD,MAAM,EAAE,CAOV;AAED;;;;;;;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;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,EAC3D,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA4B/B;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,EAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAYhD"}
1
+ {"version":3,"file":"split-sizing.d.ts","sourceRoot":"","sources":["../../src/dock-react/split-sizing.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAExD;;;;mEAImE;AACnE,eAAO,MAAM,cAAc,MAAM,CAAA;AAEjC;;;;;;;;;;;;;;;;;;;;;2DAqB2D;AAC3D,eAAO,MAAM,oBAAoB,MAAM,CAAA;AAEvC,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAQhE;AAED;;;;;;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;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAOvD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,GACzD,MAAM,EAAE,CAOV;AAED;;;;;;;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;AAED;;;;;;GAMG;AACH,MAAM,WAAW,iBAAiB;IACjC,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAA;IACnB;;;;;;;;;OASG;IACH,iBAAiB,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAChC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,KAAK,EAAE,SAAS,MAAM,EAAE,EACxB,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,EAC3D,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,QAAQ,CAAC,EAAE,iBAAiB,GAC1B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CA6C/B;AAsCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,WAAW,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,EAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC5B;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG,IAAI,CAYhD"}
@@ -1 +1 @@
1
- {"version":3,"file":"split-view.d.ts","sourceRoot":"","sources":["../../src/dock-react/split-view.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtE,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAU/D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAW/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,aAAa,CAAA;CAClB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAmR1D"}
1
+ {"version":3,"file":"split-view.d.ts","sourceRoot":"","sources":["../../src/dock-react/split-view.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAkC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAGtE,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAU/D,OAAO,EAAc,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAW/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,SAAS,CAAA;IACf,GAAG,EAAE,aAAa,CAAA;CAClB;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CA+V1D"}
@@ -1,7 +1,7 @@
1
1
  import { n as DeckRemoteError, r as EventNotBoundError } from "./errors-NE2ig7L5.js";
2
2
  import { i as DeckChannel, t as BRIDGE_PROTOCOL_VERSION } from "./protocol-B6TOT9AP.js";
3
3
  import { i as createScope, r as createCompositor, t as createViewHandle } from "./view-handle-zav25k3-.js";
4
- import { i as reconcile, n as dispatchOps, r as createInitialState, t as cleanSnapshot } from "./layout-D-LYJIpM.js";
4
+ import { i as reconcile, n as dispatchOps, r as createInitialState, t as cleanSnapshot } from "./layout-CxEGC10g.js";
5
5
  import { randomUUID } from "node:crypto";
6
6
  //#region src/events.ts
7
7
  var internals = /* @__PURE__ */ new WeakMap();
@@ -2409,4 +2409,4 @@ function assertToolbarSourceShape(source) {
2409
2409
  //#endregion
2410
2410
  export { WireTransport as a, InMemoryTypedIpcRegistry as c, createControlBus as i, EventBus as l, startElectronDeck as n, createCapabilityRegistry as o, validateConfig as r, createTrustSet as s, electronDeck as t, defineEvent as u };
2411
2411
 
2412
- //# sourceMappingURL=electron-deck-Bi-u-Aze.js.map
2412
+ //# sourceMappingURL=electron-deck-Bnm8LqQh.js.map