@adea-ai/ui 0.104.1 → 0.104.3

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.
Files changed (26) hide show
  1. package/README.md +15 -0
  2. package/dist/components/composites/catalog-browser/catalog-detail.d.ts.map +1 -1
  3. package/dist/components/composites/catalog-browser/catalog-detail.js +4 -4
  4. package/dist/components/composites/catalog-browser/catalog-detail.js.map +1 -1
  5. package/dist/components/layout/split-layout/geometry.js +1 -1
  6. package/dist/components/layout/split-layout/index.js +2 -1
  7. package/dist/components/layout/split-layout/model.d.ts +2 -20
  8. package/dist/components/layout/split-layout/model.d.ts.map +1 -1
  9. package/dist/components/layout/split-layout/model.js +2 -14
  10. package/dist/components/layout/split-layout/model.js.map +1 -1
  11. package/dist/components/layout/split-layout/split-layout.js +1 -1
  12. package/dist/components/layout/split-layout/tree.d.ts +21 -0
  13. package/dist/components/layout/split-layout/tree.d.ts.map +1 -0
  14. package/dist/components/layout/split-layout/tree.js +18 -0
  15. package/dist/components/layout/split-layout/tree.js.map +1 -0
  16. package/dist/index.js +2 -1
  17. package/dist/r/registry.json +5 -0
  18. package/dist/r/split-layout.json +5 -0
  19. package/dist/r/src/components/composites/catalog-browser/catalog-detail.tsx +11 -5
  20. package/dist/r/src/components/layout/split-layout/model.ts +11 -30
  21. package/dist/r/src/components/layout/split-layout/tree.ts +51 -0
  22. package/package.json +8 -2
  23. package/registry.json +5 -0
  24. package/src/components/composites/catalog-browser/catalog-detail.tsx +11 -5
  25. package/src/components/layout/split-layout/model.ts +11 -30
  26. package/src/components/layout/split-layout/tree.ts +51 -0
package/README.md CHANGED
@@ -825,3 +825,18 @@ own their preference provider and storage. Pass `mode` and `onModeChange`; the
825
825
  control owns roving keyboard focus, pressed state and explanatory tooltips. It
826
826
  does not require or mount the shared `ThemeProvider`. `ThemeToggle` retains its
827
827
  existing provider-bound API.
828
+
829
+ Catalog detail summaries place actions and feedback beneath identity on narrow
830
+ screens, preserving readable title/description flow. Grouped field sections keep
831
+ their content at the top while sharing the outline and dividers.
832
+
833
+ ### Small split-layout tree imports
834
+
835
+ Scoped persistence decoders can import `MAX_LAYOUT_LEAVES`, `MAX_LAYOUT_DEPTH`,
836
+ `MIN_SPLIT_RATIO`, `MAX_SPLIT_RATIO`, `listLeaves`, `countLeaves`, `layoutDepth`,
837
+ and the opaque tree types from
838
+ `@adea-ai/ui/components/layout/split-layout/tree`. This read-only entry has no
839
+ UI runtime, renderer, or pane-editing dependencies. The existing `/model` entry
840
+ re-exports the same functions and types for compatibility. Use `/model` for
841
+ split, close, move, resize, and undo operations; use the component entry when
842
+ rendering the layout.
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-detail.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/catalog-browser/catalog-detail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAIpF,KAAK,cAAc,GAAG,SAAS,GAAG,SAAS,CAAA;AAqB3C,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/C,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,eAqCA;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CAAE,eAcpF"}
1
+ {"version":3,"file":"catalog-detail.d.ts","sourceRoot":"","sources":["../../../../src/components/composites/catalog-browser/catalog-detail.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAmD,KAAK,GAAG,EAAE,MAAM,UAAU,CAAA;AAIpF,KAAK,cAAc,GAAG,SAAS,GAAG,SAAS,CAAA;AAqB3C,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,KAAK,EAAE;IACnC,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IAC/C,OAAO,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACrB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;IACpB,+EAA+E;IAC/E,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CACvB,eA2CA;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,OAAO,CAAA;CAAE,eAcpF"}
@@ -4,16 +4,16 @@ import { Show, createContext, createUniqueId, useContext } from "solid-js";
4
4
  import { cva } from "class-variance-authority";
5
5
  //#region src/components/composites/catalog-browser/catalog-detail.tsx
6
6
  var _tmpl$ = /*#__PURE__*/ template(`<span class=shrink-0>`);
7
- var _tmpl$2 = /*#__PURE__*/ template(`<div class="flex shrink-0 flex-col items-end gap-2">`);
7
+ var _tmpl$2 = /*#__PURE__*/ template(`<div data-catalog-detail-actions class="flex min-w-0 basis-full flex-col items-start gap-2 wrap-anywhere sm:max-w-xs sm:basis-auto sm:items-end">`);
8
8
  var _tmpl$3 = /*#__PURE__*/ template(`<div data-catalog-detail-sections>`);
9
- var _tmpl$4 = /*#__PURE__*/ template(`<article class="grid min-w-0 gap-2"data-catalog-detail><div class="flex min-w-0 items-start gap-3 rounded-xl border border-border p-3"><div class="grid min-w-0 flex-1 gap-1"><div class="flex min-w-0 flex-wrap items-center gap-1.5"></div><h2 class="text-base leading-tight font-semibold tracking-tight"></h2><p class="text-muted-foreground text-sm"></p><p class="text-muted-foreground text-xs">`);
10
- var _tmpl$5 = /*#__PURE__*/ template(`<section><h3 class="text-2xs font-semibold tracking-wider text-muted-foreground uppercase"></h3><div class="grid min-w-0 gap-1.5">`);
9
+ var _tmpl$4 = /*#__PURE__*/ template(`<article class="grid min-w-0 gap-2"data-catalog-detail><div data-catalog-detail-header class="flex min-w-0 flex-wrap items-start gap-3 rounded-xl border border-border p-3 sm:flex-nowrap"><div data-catalog-detail-identity class="grid min-w-0 flex-1 gap-1 wrap-anywhere"><div class="flex min-w-0 flex-wrap items-center gap-1.5"></div><h2 class="text-base leading-tight font-semibold tracking-tight"></h2><p class="text-muted-foreground text-sm"></p><p class="text-muted-foreground text-xs">`);
10
+ var _tmpl$5 = /*#__PURE__*/ template(`<section><h3 class="text-2xs font-semibold tracking-wider text-muted-foreground uppercase"></h3><div class="grid min-w-0 content-start gap-1.5">`);
11
11
  var SectionsLayoutContext = createContext(() => "stacked");
12
12
  var sectionsVariants = cva("grid min-w-0", { variants: { layout: {
13
13
  stacked: "gap-2",
14
14
  columns: "gap-4 rounded-xl border border-border p-3 md:auto-cols-fr md:grid-flow-col md:gap-0"
15
15
  } } });
16
- var sectionVariants = cva("grid min-w-0 gap-1.5 wrap-anywhere", { variants: { layout: {
16
+ var sectionVariants = cva("grid min-w-0 content-start gap-1.5 wrap-anywhere", { variants: { layout: {
17
17
  stacked: "rounded-xl border border-border p-3",
18
18
  columns: "md:border-s md:border-border md:px-3 md:first:border-s-0 md:first:ps-0 md:last:pe-0"
19
19
  } } });
@@ -1 +1 @@
1
- {"version":3,"file":"catalog-detail.js","names":["createContext","createUniqueId","Show","useContext","JSX","Badge","cva","SectionsLayout","SectionsLayoutContext","sectionsVariants","variants","layout","stacked","columns","sectionVariants","CatalogDetail","props","title","description","category","publisher","publishedByLabel","leading","Element","eyebrow","badges","action","status","sectionsLayout","children","_el$","_tmpl$4","_el$2","firstChild","_el$4","_el$5","_el$6","nextSibling","_el$7","_el$8","_$insert","_$createComponent","when","_el$3","_tmpl$","variant","_el$9","_tmpl$2","Provider","value","_el$0","_tmpl$3","_$effect","_p$","_v$","_v$2","e","_$setAttribute","t","_$className","undefined","CatalogDetailSection","headingId","_el$1","_tmpl$5","_el$10","_el$11","_$createComponent","_$setAttribute","_$className"],"sources":["../../../../src/components/composites/catalog-browser/catalog-detail.tsx"],"sourcesContent":["import { createContext, createUniqueId, Show, useContext, type JSX } from 'solid-js'\nimport { Badge } from '../../ui/badge'\nimport { cva } from 'class-variance-authority'\n\ntype SectionsLayout = 'stacked' | 'columns'\nconst SectionsLayoutContext = createContext<() => SectionsLayout>((): SectionsLayout => 'stacked')\nconst sectionsVariants = cva('grid min-w-0', {\n variants: {\n layout: {\n stacked: 'gap-2',\n columns:\n 'gap-4 rounded-xl border border-border p-3 md:auto-cols-fr md:grid-flow-col md:gap-0',\n },\n },\n})\nconst sectionVariants = cva('grid min-w-0 gap-1.5 wrap-anywhere', {\n variants: {\n layout: {\n stacked: 'rounded-xl border border-border p-3',\n columns:\n 'md:border-s md:border-border md:px-3 md:first:border-s-0 md:first:ps-0 md:last:pe-0',\n },\n },\n})\n\n/** Shared item summary and action frame for catalog-owned detail content. */\nexport function CatalogDetail(props: {\n title: string\n description: string\n category: string\n publisher: string\n publishedByLabel: (publisher: string) => string\n leading?: JSX.Element\n eyebrow?: string\n badges?: JSX.Element\n action?: JSX.Element\n status?: JSX.Element\n /** Groups related fields in one padded card; stacks them on narrow screens. */\n sectionsLayout?: SectionsLayout\n children?: JSX.Element\n}) {\n return (\n <article class=\"grid min-w-0 gap-2\" data-catalog-detail>\n <div class=\"flex min-w-0 items-start gap-3 rounded-xl border border-border p-3\">\n <Show when={props.leading}>\n <span class=\"shrink-0\">{props.leading}</span>\n </Show>\n <div class=\"grid min-w-0 flex-1 gap-1\">\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <Show when={props.eyebrow}>\n <Badge variant=\"outline\">{props.eyebrow}</Badge>\n </Show>\n <Badge variant=\"secondary\">{props.category}</Badge>\n {props.badges}\n </div>\n <h2 class=\"text-base leading-tight font-semibold tracking-tight\">{props.title}</h2>\n <p class=\"text-muted-foreground text-sm\">{props.description}</p>\n <p class=\"text-muted-foreground text-xs\">{props.publishedByLabel(props.publisher)}</p>\n </div>\n <Show when={props.action || props.status}>\n <div class=\"flex shrink-0 flex-col items-end gap-2\">\n {props.action}\n {props.status}\n </div>\n </Show>\n </div>\n <SectionsLayoutContext.Provider value={() => props.sectionsLayout ?? 'stacked'}>\n <div\n data-catalog-detail-sections\n data-layout={props.sectionsLayout ?? 'stacked'}\n class={sectionsVariants({ layout: props.sectionsLayout ?? 'stacked' })}\n >\n {props.children}\n </div>\n </SectionsLayoutContext.Provider>\n </article>\n )\n}\n\n/** A shared compact row for host-specific fields, permissions, and copy. */\nexport function CatalogDetailSection(props: { title: string; children?: JSX.Element }) {\n const layout = useContext(SectionsLayoutContext)\n const headingId = `catalog-detail-section-${createUniqueId()}`\n return (\n <section class={sectionVariants({ layout: layout() })} aria-labelledby={headingId}>\n <h3\n id={headingId}\n class=\"text-2xs font-semibold tracking-wider text-muted-foreground uppercase\"\n >\n {props.title}\n </h3>\n <div class=\"grid min-w-0 gap-1.5\">{props.children}</div>\n </section>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"catalog-detail.js","names":["createContext","createUniqueId","Show","useContext","JSX","Badge","cva","SectionsLayout","SectionsLayoutContext","sectionsVariants","variants","layout","stacked","columns","sectionVariants","CatalogDetail","props","title","description","category","publisher","publishedByLabel","leading","Element","eyebrow","badges","action","status","sectionsLayout","children","_el$","_tmpl$4","_el$2","firstChild","_el$4","_el$5","_el$6","nextSibling","_el$7","_el$8","_$insert","_$createComponent","when","_el$3","_tmpl$","variant","_el$9","_tmpl$2","Provider","value","_el$0","_tmpl$3","_$effect","_p$","_v$","_v$2","e","_$setAttribute","t","_$className","undefined","CatalogDetailSection","headingId","_el$1","_tmpl$5","_el$10","_el$11","_$createComponent","_$setAttribute","_$className"],"sources":["../../../../src/components/composites/catalog-browser/catalog-detail.tsx"],"sourcesContent":["import { createContext, createUniqueId, Show, useContext, type JSX } from 'solid-js'\nimport { Badge } from '../../ui/badge'\nimport { cva } from 'class-variance-authority'\n\ntype SectionsLayout = 'stacked' | 'columns'\nconst SectionsLayoutContext = createContext<() => SectionsLayout>((): SectionsLayout => 'stacked')\nconst sectionsVariants = cva('grid min-w-0', {\n variants: {\n layout: {\n stacked: 'gap-2',\n columns:\n 'gap-4 rounded-xl border border-border p-3 md:auto-cols-fr md:grid-flow-col md:gap-0',\n },\n },\n})\nconst sectionVariants = cva('grid min-w-0 content-start gap-1.5 wrap-anywhere', {\n variants: {\n layout: {\n stacked: 'rounded-xl border border-border p-3',\n columns:\n 'md:border-s md:border-border md:px-3 md:first:border-s-0 md:first:ps-0 md:last:pe-0',\n },\n },\n})\n\n/** Shared item summary and action frame for catalog-owned detail content. */\nexport function CatalogDetail(props: {\n title: string\n description: string\n category: string\n publisher: string\n publishedByLabel: (publisher: string) => string\n leading?: JSX.Element\n eyebrow?: string\n badges?: JSX.Element\n action?: JSX.Element\n status?: JSX.Element\n /** Groups related fields in one padded card; stacks them on narrow screens. */\n sectionsLayout?: SectionsLayout\n children?: JSX.Element\n}) {\n return (\n <article class=\"grid min-w-0 gap-2\" data-catalog-detail>\n <div\n data-catalog-detail-header\n class=\"flex min-w-0 flex-wrap items-start gap-3 rounded-xl border border-border p-3 sm:flex-nowrap\"\n >\n <Show when={props.leading}>\n <span class=\"shrink-0\">{props.leading}</span>\n </Show>\n <div data-catalog-detail-identity class=\"grid min-w-0 flex-1 gap-1 wrap-anywhere\">\n <div class=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n <Show when={props.eyebrow}>\n <Badge variant=\"outline\">{props.eyebrow}</Badge>\n </Show>\n <Badge variant=\"secondary\">{props.category}</Badge>\n {props.badges}\n </div>\n <h2 class=\"text-base leading-tight font-semibold tracking-tight\">{props.title}</h2>\n <p class=\"text-muted-foreground text-sm\">{props.description}</p>\n <p class=\"text-muted-foreground text-xs\">{props.publishedByLabel(props.publisher)}</p>\n </div>\n <Show when={props.action || props.status}>\n <div\n data-catalog-detail-actions\n class=\"flex min-w-0 basis-full flex-col items-start gap-2 wrap-anywhere sm:max-w-xs sm:basis-auto sm:items-end\"\n >\n {props.action}\n {props.status}\n </div>\n </Show>\n </div>\n <SectionsLayoutContext.Provider value={() => props.sectionsLayout ?? 'stacked'}>\n <div\n data-catalog-detail-sections\n data-layout={props.sectionsLayout ?? 'stacked'}\n class={sectionsVariants({ layout: props.sectionsLayout ?? 'stacked' })}\n >\n {props.children}\n </div>\n </SectionsLayoutContext.Provider>\n </article>\n )\n}\n\n/** A shared compact row for host-specific fields, permissions, and copy. */\nexport function CatalogDetailSection(props: { title: string; children?: JSX.Element }) {\n const layout = useContext(SectionsLayoutContext)\n const headingId = `catalog-detail-section-${createUniqueId()}`\n return (\n <section class={sectionVariants({ layout: layout() })} aria-labelledby={headingId}>\n <h3\n id={headingId}\n class=\"text-2xs font-semibold tracking-wider text-muted-foreground uppercase\"\n >\n {props.title}\n </h3>\n <div class=\"grid min-w-0 content-start gap-1.5\">{props.children}</div>\n </section>\n )\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { MAX_SPLIT_RATIO, MIN_SPLIT_RATIO } from "./model.js";
1
+ import { MAX_SPLIT_RATIO, MIN_SPLIT_RATIO } from "./tree.js";
2
2
  //#region src/components/layout/split-layout/geometry.ts
3
3
  /** Stable leaf DOM can use these fractions without being reparented into a changing nested tree. */
4
4
  function computeLayoutFrames(node) {
@@ -1,4 +1,5 @@
1
- import { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, closePane, countLeaves, createLayoutState, focusPane, layoutDepth, listLeaves, movePane, neighborLeaf, normalizeLayout, resizeSplit, splitPane, swapPanes, undoClosePane } from "./model.js";
1
+ import { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, countLeaves, layoutDepth, listLeaves } from "./tree.js";
2
+ import { closePane, createLayoutState, focusPane, movePane, neighborLeaf, normalizeLayout, resizeSplit, splitPane, swapPanes, undoClosePane } from "./model.js";
2
3
  import { computeLayoutFrames } from "./geometry.js";
3
4
  import { SplitLayout } from "./split-layout.js";
4
5
  export { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, SplitLayout, closePane, computeLayoutFrames, countLeaves, createLayoutState, focusPane, layoutDepth, listLeaves, movePane, neighborLeaf, normalizeLayout, resizeSplit, splitPane, swapPanes, undoClosePane };
@@ -1,20 +1,5 @@
1
- /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
2
- export type SplitLayoutLeaf = Readonly<{
3
- kind: 'leaf';
4
- id: string;
5
- }>;
6
- export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
7
- kind: 'split';
8
- id: string;
9
- direction: 'row' | 'column';
10
- ratio: number;
11
- children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>];
12
- }>;
13
- export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>;
14
- export declare const MAX_LAYOUT_LEAVES = 8;
15
- export declare const MAX_LAYOUT_DEPTH = 8;
16
- export declare const MIN_SPLIT_RATIO = 0.1;
17
- export declare const MAX_SPLIT_RATIO = 0.9;
1
+ import type { SplitLayoutLeaf, SplitLayoutBranch, SplitLayoutNode } from './tree';
2
+ export * from './tree';
18
3
  export type SplitLayoutState<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
19
4
  center: SplitLayoutNode<L>;
20
5
  focusedLeafId: string;
@@ -29,9 +14,6 @@ export type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readon
29
14
  leaf: L;
30
15
  splitId: string;
31
16
  }>;
32
- export declare function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[];
33
- export declare function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number;
34
- export declare function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number;
35
17
  export declare function createLayoutState<L extends SplitLayoutLeaf>(center: SplitLayoutNode<L>): SplitLayoutState<L>;
36
18
  export declare function splitPane<L extends SplitLayoutLeaf>(state: SplitLayoutState<L>, targetLeafId: string, input: SplitPaneInput<L>): SplitLayoutState<L>;
37
19
  export declare function closePane<L extends SplitLayoutLeaf>(state: SplitLayoutState<L>, leafId: string, createPlaceholderLeaf: () => L): SplitLayoutState<L>;
@@ -1 +1 @@
1
- {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/split-layout/model.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/split-layout/model.ts"],"names":[],"mappings":"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"}
@@ -1,17 +1,5 @@
1
+ import { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, countLeaves, layoutDepth, listLeaves } from "./tree.js";
1
2
  //#region src/components/layout/split-layout/model.ts
2
- var MAX_LAYOUT_LEAVES = 8;
3
- var MAX_LAYOUT_DEPTH = 8;
4
- var MIN_SPLIT_RATIO = .1;
5
- var MAX_SPLIT_RATIO = .9;
6
- function listLeaves(node) {
7
- return node.kind === "leaf" ? [node] : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])];
8
- }
9
- function countLeaves(node) {
10
- return node.kind === "leaf" ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1]);
11
- }
12
- function layoutDepth(node) {
13
- return node.kind === "leaf" ? 1 : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]));
14
- }
15
3
  function replaceLeaf(node, leafId, replacement) {
16
4
  if (node.kind === "leaf") return node.id === leafId ? replacement : node;
17
5
  const first = replaceLeaf(node.children[0], leafId, replacement);
@@ -225,7 +213,7 @@ function normalizeLayout(state) {
225
213
  };
226
214
  const first = visit(node.children[0]);
227
215
  const second = visit(node.children[1]);
228
- const ratio = Number.isFinite(node.ratio) ? Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, node.ratio)) : 1 / 2;
216
+ const ratio = Number.isFinite(node.ratio) ? Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, node.ratio)) : (MIN_SPLIT_RATIO + MAX_SPLIT_RATIO) / 2;
229
217
  const changed = first.changed || second.changed || ratio !== node.ratio;
230
218
  return {
231
219
  node: changed ? {
@@ -1 +1 @@
1
- {"version":3,"file":"model.js","names":[],"sources":["../../../../src/components/layout/split-layout/model.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Michael Yong\n * Copyright (c) 2026 Muxy\n * SPDX-License-Identifier: MIT\n *\n * Portions of the binary split behavior are substantially translated from:\n * - get-bb/bb apps/app/src/lib/split-layout/ops.ts (MIT), revision\n * 52a9256373d4d36f9b60e9e2a7f333464091a2ac.\n * - muxy-app/muxy Muxy/Models/Workspace/SplitNode.swift (MIT), revision\n * 5c5be8697c57a2fe70cda97fdbaf7c912e2e31b6.\n * Adapted first in Adea packages/dev-view/src/layout/operations.ts at\n * 0322ab09ff5e9775bfddc0bf2d81e1df436dbef7. Extracted here with opaque\n * host-owned leaves and an injected final-pane placeholder; immutable strict\n * binary operations, accepted limits and undoable close are preserved.\n * See NOTICE and docs/research/dev-view-donor-audit.md.\n */\n\n/** A visual identity only. Extend this with host-owned payload; UI never interprets it. */\nexport type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>\nexport type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n kind: 'split'\n id: string\n direction: 'row' | 'column'\n ratio: number\n children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]\n}>\nexport type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>\n\nexport const MAX_LAYOUT_LEAVES = 8\nexport const MAX_LAYOUT_DEPTH = 8\nexport const MIN_SPLIT_RATIO = 0.1\nexport const MAX_SPLIT_RATIO = 0.9\n\nexport type SplitLayoutState<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n center: SplitLayoutNode<L>\n focusedLeafId: string\n closed: readonly Readonly<{ center: SplitLayoutNode<L>; leafId: string }>[]\n}>\n\nexport type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n direction: SplitLayoutBranch<L>['direction']\n placement: 'before' | 'after'\n leaf: L\n splitId: string\n}>\n\nexport function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {\n return node.kind === 'leaf'\n ? [node]\n : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]\n}\n\nexport function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {\n return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])\n}\n\nexport function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {\n return node.kind === 'leaf'\n ? 1\n : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))\n}\n\nfunction replaceLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string,\n replacement: SplitLayoutNode<L>\n): SplitLayoutNode<L> {\n if (node.kind === 'leaf') return node.id === leafId ? replacement : node\n const first = replaceLeaf(node.children[0], leafId, replacement)\n const second = replaceLeaf(node.children[1], leafId, replacement)\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n}\n\nfunction removeLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string\n): SplitLayoutNode<L> | null {\n if (node.kind === 'leaf') return node.id === leafId ? null : node\n const first = removeLeaf(node.children[0], leafId)\n const second = removeLeaf(node.children[1], leafId)\n if (!first) return second\n if (!second) return first\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n}\n\nfunction containsLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string\n): boolean {\n return node.kind === 'leaf'\n ? node.id === leafId\n : containsLeaf(node.children[0], leafId) || containsLeaf(node.children[1], leafId)\n}\n\nfunction assertUniqueInput<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leaf: L,\n splitId: string\n) {\n assertLayoutTree(leaf)\n if (typeof splitId !== 'string' || splitId.length === 0)\n throw new Error('invalid_state: split identity must be nonempty')\n const ids = new Set<string>()\n const visit = (node: SplitLayoutNode<L>) => {\n if (ids.has(node.id)) throw new Error('invalid_state: duplicate pane id')\n ids.add(node.id)\n if (node.kind === 'split') node.children.forEach(visit)\n }\n visit(state.center)\n if (ids.has(leaf.id) || ids.has(splitId) || leaf.id === splitId)\n throw new Error('invalid_state: duplicate pane id')\n}\n\n/** Visual-tree validation only; applications still decode and scope persisted preferences. */\nfunction assertLayoutTree<L extends SplitLayoutLeaf>(center: SplitLayoutNode<L>): void {\n const ids = new Set<string>()\n const seen = new WeakSet<object>()\n let leaves = 0\n const visit = (node: SplitLayoutNode<L>, depth: number) => {\n if (depth > MAX_LAYOUT_DEPTH) throw new Error('limit_exceeded: layout depth exceeds eight')\n if (!node || typeof node !== 'object') throw new Error('invalid_state: missing layout node')\n if (seen.has(node)) throw new Error('invalid_state: cyclic or reused layout node')\n seen.add(node)\n if (typeof node.id !== 'string' || node.id.length === 0)\n throw new Error('invalid_state: layout identity must be nonempty')\n if (ids.has(node.id)) throw new Error('invalid_state: duplicate pane id')\n ids.add(node.id)\n if (node.kind === 'leaf') {\n leaves += 1\n if (leaves > MAX_LAYOUT_LEAVES)\n throw new Error('limit_exceeded: layout has more than eight leaves')\n return\n }\n if (\n node.kind !== 'split' ||\n !Array.isArray(node.children) ||\n node.children.length !== 2 ||\n (node.direction !== 'row' && node.direction !== 'column')\n )\n throw new Error('invalid_state: layout must be a strict binary row or column tree')\n visit(node.children[0], depth + 1)\n visit(node.children[1], depth + 1)\n }\n visit(center, 1)\n}\n\nexport function createLayoutState<L extends SplitLayoutLeaf>(\n center: SplitLayoutNode<L>\n): SplitLayoutState<L> {\n assertLayoutTree(center)\n const first = listLeaves(center)[0]\n if (!first) throw new Error('invalid_state: layout requires a leaf')\n return { center, focusedLeafId: first.id, closed: [] }\n}\n\nexport function splitPane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n targetLeafId: string,\n input: SplitPaneInput<L>\n): SplitLayoutState<L> {\n if (!containsLeaf(state.center, targetLeafId)) return state\n if (countLeaves(state.center) >= MAX_LAYOUT_LEAVES)\n throw new Error('limit_exceeded: center layout has eight leaves')\n assertUniqueInput(state, input.leaf, input.splitId)\n const target = listLeaves(state.center).find((leaf) => leaf.id === targetLeafId)\n if (!target) return state\n const children =\n input.placement === 'before' ? ([input.leaf, target] as const) : ([target, input.leaf] as const)\n const center = replaceLeaf(state.center, targetLeafId, {\n kind: 'split',\n id: input.splitId,\n direction: input.direction,\n ratio: 0.5,\n children,\n })\n if (layoutDepth(center) > MAX_LAYOUT_DEPTH)\n throw new Error('limit_exceeded: center layout depth exceeds eight')\n return { ...state, center, focusedLeafId: input.leaf.id, closed: [] }\n}\n\nexport function closePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n createPlaceholderLeaf: () => L\n): SplitLayoutState<L> {\n if (!containsLeaf(state.center, leafId)) return state\n const before = state.center\n const readingOrder = listLeaves(before)\n const closedIndex = readingOrder.findIndex((leaf) => leaf.id === leafId)\n const removed = removeLeaf(before, leafId)\n const center: SplitLayoutNode<L> = removed ?? createPlaceholderLeaf()\n if (!removed) {\n if (center.kind !== 'leaf') throw new Error('invalid_state: placeholder must be a leaf')\n assertLayoutTree(center)\n }\n const leaves = listLeaves(center)\n const fallback = leaves[Math.min(closedIndex, leaves.length - 1)] ?? leaves[0]\n if (!fallback) throw new Error('invalid_state: layout requires a leaf')\n return {\n center,\n focusedLeafId: state.focusedLeafId === leafId ? fallback.id : state.focusedLeafId,\n closed: [...state.closed, { center: before, leafId }],\n }\n}\n\nexport function undoClosePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>\n): SplitLayoutState<L> {\n const previous = state.closed.at(-1)\n if (!previous) return state\n return {\n center: previous.center,\n focusedLeafId: previous.leafId,\n closed: state.closed.slice(0, -1),\n }\n}\n\nexport function focusPane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string\n): SplitLayoutState<L> {\n return containsLeaf(state.center, leafId) && state.focusedLeafId !== leafId\n ? { ...state, focusedLeafId: leafId }\n : state\n}\n\nexport function swapPanes<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n firstLeafId: string,\n secondLeafId: string\n): SplitLayoutState<L> {\n if (firstLeafId === secondLeafId) return state\n const leaves = listLeaves(state.center)\n const first = leaves.find((leaf) => leaf.id === firstLeafId)\n const second = leaves.find((leaf) => leaf.id === secondLeafId)\n if (!first || !second) return state\n const swap = (node: SplitLayoutNode<L>): SplitLayoutNode<L> => {\n if (node.kind === 'leaf') {\n if (node.id === firstLeafId) return second\n if (node.id === secondLeafId) return first\n return node\n }\n const left = swap(node.children[0])\n const right = swap(node.children[1])\n return left === node.children[0] && right === node.children[1]\n ? node\n : { ...node, children: [left, right] }\n }\n return { ...state, center: swap(state.center), closed: [] }\n}\n\nexport function movePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n targetLeafId: string,\n placement: 'before' | 'after',\n direction: SplitLayoutBranch<L>['direction'],\n splitId: string\n): SplitLayoutState<L> {\n if (leafId === targetLeafId) return state\n const moving = listLeaves(state.center).find((leaf) => leaf.id === leafId)\n if (!moving || !containsLeaf(state.center, targetLeafId)) return state\n const detached = removeLeaf(state.center, leafId)\n if (!detached) return state\n const moved = splitPane(\n { center: detached, focusedLeafId: state.focusedLeafId, closed: state.closed },\n targetLeafId,\n { direction, placement, leaf: moving, splitId }\n )\n return { ...moved, focusedLeafId: leafId }\n}\n\nexport function resizeSplit<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n splitId: string,\n ratio: number\n): SplitLayoutState<L> {\n if (!Number.isFinite(ratio)) return state\n const nextRatio = Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, ratio))\n let found = false\n const visit = (node: SplitLayoutNode<L>): SplitLayoutNode<L> => {\n if (node.kind === 'leaf') return node\n if (node.id === splitId) {\n found = true\n return node.ratio === nextRatio ? node : { ...node, ratio: nextRatio }\n }\n const first = visit(node.children[0])\n const second = visit(node.children[1])\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n }\n const center = visit(state.center)\n return found && center !== state.center ? { ...state, center, closed: [] } : state\n}\n\n/**\n * Pure repair walk ported from bb's size normalization: every split ratio is\n * clamped into the normative range and a non-finite ratio falls back to an\n * even split. Structure, IDs, and focus are untouched.\n */\nexport function normalizeLayout<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>\n): SplitLayoutState<L> {\n const visit = (node: SplitLayoutNode<L>): { node: SplitLayoutNode<L>; changed: boolean } => {\n if (node.kind === 'leaf') return { node, changed: false }\n const first = visit(node.children[0])\n const second = visit(node.children[1])\n const ratio = Number.isFinite(node.ratio)\n ? Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, node.ratio))\n : (MIN_SPLIT_RATIO + MAX_SPLIT_RATIO) / 2\n const changed = first.changed || second.changed || ratio !== node.ratio\n return {\n node: changed ? { ...node, ratio, children: [first.node, second.node] } : node,\n changed,\n }\n }\n const result = visit(state.center)\n return result.changed ? { ...state, center: result.node } : state\n}\n\n/** The leaf immediately after (`1`) or before (`-1`) the given leaf in reading order. */\nexport function neighborLeaf<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n step: 1 | -1\n): L | undefined {\n const leaves = listLeaves(state.center)\n const index = leaves.findIndex((leaf) => leaf.id === leafId)\n if (index < 0) return undefined\n return leaves[index + step]\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"model.js","names":[],"sources":["../../../../src/components/layout/split-layout/model.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Michael Yong\n * Copyright (c) 2026 Muxy\n * SPDX-License-Identifier: MIT\n *\n * Portions of the binary split behavior are substantially translated from:\n * - get-bb/bb apps/app/src/lib/split-layout/ops.ts (MIT), revision\n * 52a9256373d4d36f9b60e9e2a7f333464091a2ac.\n * - muxy-app/muxy Muxy/Models/Workspace/SplitNode.swift (MIT), revision\n * 5c5be8697c57a2fe70cda97fdbaf7c912e2e31b6.\n * Adapted first in Adea packages/dev-view/src/layout/operations.ts at\n * 0322ab09ff5e9775bfddc0bf2d81e1df436dbef7. Extracted here with opaque\n * host-owned leaves and an injected final-pane placeholder; immutable strict\n * binary operations, accepted limits and undoable close are preserved.\n * See NOTICE and docs/research/dev-view-donor-audit.md.\n */\n\nimport {\n MAX_LAYOUT_LEAVES,\n MAX_LAYOUT_DEPTH,\n MIN_SPLIT_RATIO,\n MAX_SPLIT_RATIO,\n listLeaves,\n countLeaves,\n layoutDepth,\n} from './tree'\nimport type { SplitLayoutLeaf, SplitLayoutBranch, SplitLayoutNode } from './tree'\n\nexport * from './tree'\n\nexport type SplitLayoutState<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n center: SplitLayoutNode<L>\n focusedLeafId: string\n closed: readonly Readonly<{ center: SplitLayoutNode<L>; leafId: string }>[]\n}>\n\nexport type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n direction: SplitLayoutBranch<L>['direction']\n placement: 'before' | 'after'\n leaf: L\n splitId: string\n}>\n\nfunction replaceLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string,\n replacement: SplitLayoutNode<L>\n): SplitLayoutNode<L> {\n if (node.kind === 'leaf') return node.id === leafId ? replacement : node\n const first = replaceLeaf(node.children[0], leafId, replacement)\n const second = replaceLeaf(node.children[1], leafId, replacement)\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n}\n\nfunction removeLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string\n): SplitLayoutNode<L> | null {\n if (node.kind === 'leaf') return node.id === leafId ? null : node\n const first = removeLeaf(node.children[0], leafId)\n const second = removeLeaf(node.children[1], leafId)\n if (!first) return second\n if (!second) return first\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n}\n\nfunction containsLeaf<L extends SplitLayoutLeaf>(\n node: SplitLayoutNode<L>,\n leafId: string\n): boolean {\n return node.kind === 'leaf'\n ? node.id === leafId\n : containsLeaf(node.children[0], leafId) || containsLeaf(node.children[1], leafId)\n}\n\nfunction assertUniqueInput<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leaf: L,\n splitId: string\n) {\n assertLayoutTree(leaf)\n if (typeof splitId !== 'string' || splitId.length === 0)\n throw new Error('invalid_state: split identity must be nonempty')\n const ids = new Set<string>()\n const visit = (node: SplitLayoutNode<L>) => {\n if (ids.has(node.id)) throw new Error('invalid_state: duplicate pane id')\n ids.add(node.id)\n if (node.kind === 'split') node.children.forEach(visit)\n }\n visit(state.center)\n if (ids.has(leaf.id) || ids.has(splitId) || leaf.id === splitId)\n throw new Error('invalid_state: duplicate pane id')\n}\n\n/** Visual-tree validation only; applications still decode and scope persisted preferences. */\nfunction assertLayoutTree<L extends SplitLayoutLeaf>(center: SplitLayoutNode<L>): void {\n const ids = new Set<string>()\n const seen = new WeakSet<object>()\n let leaves = 0\n const visit = (node: SplitLayoutNode<L>, depth: number) => {\n if (depth > MAX_LAYOUT_DEPTH) throw new Error('limit_exceeded: layout depth exceeds eight')\n if (!node || typeof node !== 'object') throw new Error('invalid_state: missing layout node')\n if (seen.has(node)) throw new Error('invalid_state: cyclic or reused layout node')\n seen.add(node)\n if (typeof node.id !== 'string' || node.id.length === 0)\n throw new Error('invalid_state: layout identity must be nonempty')\n if (ids.has(node.id)) throw new Error('invalid_state: duplicate pane id')\n ids.add(node.id)\n if (node.kind === 'leaf') {\n leaves += 1\n if (leaves > MAX_LAYOUT_LEAVES)\n throw new Error('limit_exceeded: layout has more than eight leaves')\n return\n }\n if (\n node.kind !== 'split' ||\n !Array.isArray(node.children) ||\n node.children.length !== 2 ||\n (node.direction !== 'row' && node.direction !== 'column')\n )\n throw new Error('invalid_state: layout must be a strict binary row or column tree')\n visit(node.children[0], depth + 1)\n visit(node.children[1], depth + 1)\n }\n visit(center, 1)\n}\n\nexport function createLayoutState<L extends SplitLayoutLeaf>(\n center: SplitLayoutNode<L>\n): SplitLayoutState<L> {\n assertLayoutTree(center)\n const first = listLeaves(center)[0]\n if (!first) throw new Error('invalid_state: layout requires a leaf')\n return { center, focusedLeafId: first.id, closed: [] }\n}\n\nexport function splitPane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n targetLeafId: string,\n input: SplitPaneInput<L>\n): SplitLayoutState<L> {\n if (!containsLeaf(state.center, targetLeafId)) return state\n if (countLeaves(state.center) >= MAX_LAYOUT_LEAVES)\n throw new Error('limit_exceeded: center layout has eight leaves')\n assertUniqueInput(state, input.leaf, input.splitId)\n const target = listLeaves(state.center).find((leaf) => leaf.id === targetLeafId)\n if (!target) return state\n const children =\n input.placement === 'before' ? ([input.leaf, target] as const) : ([target, input.leaf] as const)\n const center = replaceLeaf(state.center, targetLeafId, {\n kind: 'split',\n id: input.splitId,\n direction: input.direction,\n ratio: 0.5,\n children,\n })\n if (layoutDepth(center) > MAX_LAYOUT_DEPTH)\n throw new Error('limit_exceeded: center layout depth exceeds eight')\n return { ...state, center, focusedLeafId: input.leaf.id, closed: [] }\n}\n\nexport function closePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n createPlaceholderLeaf: () => L\n): SplitLayoutState<L> {\n if (!containsLeaf(state.center, leafId)) return state\n const before = state.center\n const readingOrder = listLeaves(before)\n const closedIndex = readingOrder.findIndex((leaf) => leaf.id === leafId)\n const removed = removeLeaf(before, leafId)\n const center: SplitLayoutNode<L> = removed ?? createPlaceholderLeaf()\n if (!removed) {\n if (center.kind !== 'leaf') throw new Error('invalid_state: placeholder must be a leaf')\n assertLayoutTree(center)\n }\n const leaves = listLeaves(center)\n const fallback = leaves[Math.min(closedIndex, leaves.length - 1)] ?? leaves[0]\n if (!fallback) throw new Error('invalid_state: layout requires a leaf')\n return {\n center,\n focusedLeafId: state.focusedLeafId === leafId ? fallback.id : state.focusedLeafId,\n closed: [...state.closed, { center: before, leafId }],\n }\n}\n\nexport function undoClosePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>\n): SplitLayoutState<L> {\n const previous = state.closed.at(-1)\n if (!previous) return state\n return {\n center: previous.center,\n focusedLeafId: previous.leafId,\n closed: state.closed.slice(0, -1),\n }\n}\n\nexport function focusPane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string\n): SplitLayoutState<L> {\n return containsLeaf(state.center, leafId) && state.focusedLeafId !== leafId\n ? { ...state, focusedLeafId: leafId }\n : state\n}\n\nexport function swapPanes<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n firstLeafId: string,\n secondLeafId: string\n): SplitLayoutState<L> {\n if (firstLeafId === secondLeafId) return state\n const leaves = listLeaves(state.center)\n const first = leaves.find((leaf) => leaf.id === firstLeafId)\n const second = leaves.find((leaf) => leaf.id === secondLeafId)\n if (!first || !second) return state\n const swap = (node: SplitLayoutNode<L>): SplitLayoutNode<L> => {\n if (node.kind === 'leaf') {\n if (node.id === firstLeafId) return second\n if (node.id === secondLeafId) return first\n return node\n }\n const left = swap(node.children[0])\n const right = swap(node.children[1])\n return left === node.children[0] && right === node.children[1]\n ? node\n : { ...node, children: [left, right] }\n }\n return { ...state, center: swap(state.center), closed: [] }\n}\n\nexport function movePane<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n targetLeafId: string,\n placement: 'before' | 'after',\n direction: SplitLayoutBranch<L>['direction'],\n splitId: string\n): SplitLayoutState<L> {\n if (leafId === targetLeafId) return state\n const moving = listLeaves(state.center).find((leaf) => leaf.id === leafId)\n if (!moving || !containsLeaf(state.center, targetLeafId)) return state\n const detached = removeLeaf(state.center, leafId)\n if (!detached) return state\n const moved = splitPane(\n { center: detached, focusedLeafId: state.focusedLeafId, closed: state.closed },\n targetLeafId,\n { direction, placement, leaf: moving, splitId }\n )\n return { ...moved, focusedLeafId: leafId }\n}\n\nexport function resizeSplit<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n splitId: string,\n ratio: number\n): SplitLayoutState<L> {\n if (!Number.isFinite(ratio)) return state\n const nextRatio = Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, ratio))\n let found = false\n const visit = (node: SplitLayoutNode<L>): SplitLayoutNode<L> => {\n if (node.kind === 'leaf') return node\n if (node.id === splitId) {\n found = true\n return node.ratio === nextRatio ? node : { ...node, ratio: nextRatio }\n }\n const first = visit(node.children[0])\n const second = visit(node.children[1])\n return first === node.children[0] && second === node.children[1]\n ? node\n : { ...node, children: [first, second] }\n }\n const center = visit(state.center)\n return found && center !== state.center ? { ...state, center, closed: [] } : state\n}\n\n/**\n * Pure repair walk ported from bb's size normalization: every split ratio is\n * clamped into the normative range and a non-finite ratio falls back to an\n * even split. Structure, IDs, and focus are untouched.\n */\nexport function normalizeLayout<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>\n): SplitLayoutState<L> {\n const visit = (node: SplitLayoutNode<L>): { node: SplitLayoutNode<L>; changed: boolean } => {\n if (node.kind === 'leaf') return { node, changed: false }\n const first = visit(node.children[0])\n const second = visit(node.children[1])\n const ratio = Number.isFinite(node.ratio)\n ? Math.min(MAX_SPLIT_RATIO, Math.max(MIN_SPLIT_RATIO, node.ratio))\n : (MIN_SPLIT_RATIO + MAX_SPLIT_RATIO) / 2\n const changed = first.changed || second.changed || ratio !== node.ratio\n return {\n node: changed ? { ...node, ratio, children: [first.node, second.node] } : node,\n changed,\n }\n }\n const result = visit(state.center)\n return result.changed ? { ...state, center: result.node } : state\n}\n\n/** The leaf immediately after (`1`) or before (`-1`) the given leaf in reading order. */\nexport function neighborLeaf<L extends SplitLayoutLeaf>(\n state: SplitLayoutState<L>,\n leafId: string,\n step: 1 | -1\n): L | undefined {\n const leaves = listLeaves(state.center)\n const index = leaves.findIndex((leaf) => leaf.id === leafId)\n if (index < 0) return undefined\n return leaves[index + step]\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { Button } from "../../ui/button/button.js";
3
- import { MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, listLeaves } from "./model.js";
3
+ import { MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, listLeaves } from "./tree.js";
4
4
  import { computeLayoutFrames } from "./geometry.js";
5
5
  import { paneDropIntent } from "./drop.js";
6
6
  import { className, createComponent, delegateEvents, effect, insert, memo, setAttribute, style, template, use } from "solid-js/web";
@@ -0,0 +1,21 @@
1
+ /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
2
+ export type SplitLayoutLeaf = Readonly<{
3
+ kind: 'leaf';
4
+ id: string;
5
+ }>;
6
+ export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
7
+ kind: 'split';
8
+ id: string;
9
+ direction: 'row' | 'column';
10
+ ratio: number;
11
+ children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>];
12
+ }>;
13
+ export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>;
14
+ export declare const MAX_LAYOUT_LEAVES = 8;
15
+ export declare const MAX_LAYOUT_DEPTH = 8;
16
+ export declare const MIN_SPLIT_RATIO = 0.1;
17
+ export declare const MAX_SPLIT_RATIO = 0.9;
18
+ export declare function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[];
19
+ export declare function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number;
20
+ export declare function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number;
21
+ //# sourceMappingURL=tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../../../src/components/layout/split-layout/tree.ts"],"names":[],"mappings":"AAoBA,2FAA2F;AAC3F,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC,CAAA;AACpE,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,IAAI,QAAQ,CAAC;IACpF,IAAI,EAAE,OAAO,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,KAAK,GAAG,QAAQ,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,SAAS,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAA;CAC5D,CAAC,CAAA;AACF,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,eAAe,GAAG,eAAe,IAAI,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAA;AAEnG,eAAO,MAAM,iBAAiB,IAAI,CAAA;AAClC,eAAO,MAAM,gBAAgB,IAAI,CAAA;AACjC,eAAO,MAAM,eAAe,MAAM,CAAA;AAClC,eAAO,MAAM,eAAe,MAAM,CAAA;AAElC,wBAAgB,UAAU,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,EAAE,CAI5F;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAEvF;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,MAAM,CAIvF"}
@@ -0,0 +1,18 @@
1
+ //#region src/components/layout/split-layout/tree.ts
2
+ var MAX_LAYOUT_LEAVES = 8;
3
+ var MAX_LAYOUT_DEPTH = 8;
4
+ var MIN_SPLIT_RATIO = .1;
5
+ var MAX_SPLIT_RATIO = .9;
6
+ function listLeaves(node) {
7
+ return node.kind === "leaf" ? [node] : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])];
8
+ }
9
+ function countLeaves(node) {
10
+ return node.kind === "leaf" ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1]);
11
+ }
12
+ function layoutDepth(node) {
13
+ return node.kind === "leaf" ? 1 : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]));
14
+ }
15
+ //#endregion
16
+ export { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, countLeaves, layoutDepth, listLeaves };
17
+
18
+ //# sourceMappingURL=tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree.js","names":[],"sources":["../../../../src/components/layout/split-layout/tree.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Michael Yong\n * Copyright (c) 2026 Muxy\n * SPDX-License-Identifier: MIT\n *\n * Portions of the binary split behavior are substantially translated from:\n * - get-bb/bb apps/app/src/lib/split-layout/ops.ts (MIT), revision\n * 52a9256373d4d36f9b60e9e2a7f333464091a2ac.\n * - muxy-app/muxy Muxy/Models/Workspace/SplitNode.swift (MIT), revision\n * 5c5be8697c57a2fe70cda97fdbaf7c912e2e31b6.\n * Adapted first in Adea packages/dev-view/src/layout/operations.ts at\n * 0322ab09ff5e9775bfddc0bf2d81e1df436dbef7. Extracted here with opaque\n * host-owned leaves and an injected final-pane placeholder; immutable strict\n * binary operations, accepted limits and undoable close are preserved.\n * See NOTICE and docs/research/dev-view-donor-audit.md.\n */\n\n// Read-only tree shape, limits, and traversal form a small import boundary.\n// Persistence decoders can use these without loading pane-editing operations.\n\n/** A visual identity only. Extend this with host-owned payload; UI never interprets it. */\nexport type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>\nexport type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{\n kind: 'split'\n id: string\n direction: 'row' | 'column'\n ratio: number\n children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]\n}>\nexport type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>\n\nexport const MAX_LAYOUT_LEAVES = 8\nexport const MAX_LAYOUT_DEPTH = 8\nexport const MIN_SPLIT_RATIO = 0.1\nexport const MAX_SPLIT_RATIO = 0.9\n\nexport function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {\n return node.kind === 'leaf'\n ? [node]\n : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]\n}\n\nexport function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {\n return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])\n}\n\nexport function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {\n return node.kind === 'leaf'\n ? 1\n : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))\n}\n"],"mappings":";AA+BA,IAAa,oBAAoB;AACjC,IAAa,mBAAmB;AAChC,IAAa,kBAAkB;AAC/B,IAAa,kBAAkB;AAE/B,SAAgB,WAAsC,MAAwC;CAC5F,OAAO,KAAK,SAAS,SACjB,CAAC,IAAI,IACL,CAAC,GAAG,WAAW,KAAK,SAAS,EAAE,GAAG,GAAG,WAAW,KAAK,SAAS,EAAE,CAAC;AACvE;AAEA,SAAgB,YAAuC,MAAkC;CACvF,OAAO,KAAK,SAAS,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE,IAAI,YAAY,KAAK,SAAS,EAAE;AAChG;AAEA,SAAgB,YAAuC,MAAkC;CACvF,OAAO,KAAK,SAAS,SACjB,IACA,IAAI,KAAK,IAAI,YAAY,KAAK,SAAS,EAAE,GAAG,YAAY,KAAK,SAAS,EAAE,CAAC;AAC/E"}
package/dist/index.js CHANGED
@@ -68,7 +68,8 @@ import { Toggle, toggleVariants } from "./components/ui/toggle/toggle.js";
68
68
  import { ToggleGroup, ToggleGroupItem } from "./components/ui/toggle-group/toggle-group.js";
69
69
  import { Presence } from "./components/motion/presence.js";
70
70
  import { Motion } from "./components/motion/index.js";
71
- import { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, closePane, countLeaves, createLayoutState, focusPane, layoutDepth, listLeaves, movePane, neighborLeaf, normalizeLayout, resizeSplit, splitPane, swapPanes, undoClosePane } from "./components/layout/split-layout/model.js";
71
+ import { MAX_LAYOUT_DEPTH, MAX_LAYOUT_LEAVES, MAX_SPLIT_RATIO, MIN_SPLIT_RATIO, countLeaves, layoutDepth, listLeaves } from "./components/layout/split-layout/tree.js";
72
+ import { closePane, createLayoutState, focusPane, movePane, neighborLeaf, normalizeLayout, resizeSplit, splitPane, swapPanes, undoClosePane } from "./components/layout/split-layout/model.js";
72
73
  import { computeLayoutFrames } from "./components/layout/split-layout/geometry.js";
73
74
  import { SplitLayout } from "./components/layout/split-layout/split-layout.js";
74
75
  import { FLOATING_PREVIEW_CORNER_RADIUS, FLOATING_PREVIEW_EDGE_GAP, NO_FLOATING_PREVIEW_OBSTACLES, NO_PREVIEW_MINI_PLAYER_OBSTACLES, PREVIEW_MINI_PLAYER_CORNER_RADIUS, PREVIEW_MINI_PLAYER_EDGE_GAP, clampFloatingPreviewPosition, clampPreviewMiniPlayerPosition, resizeFloatingPreview, resizePreviewMiniPlayer, resolveFloatingPreviewFrame, resolvePreviewMiniPlayerFrame } from "./components/layout/floating-preview/model.js";
@@ -1750,6 +1750,11 @@
1750
1750
  "path": "src/components/layout/split-layout/split-layout.tsx",
1751
1751
  "type": "registry:ui",
1752
1752
  "target": "components/layout/split-layout/split-layout.tsx"
1753
+ },
1754
+ {
1755
+ "path": "src/components/layout/split-layout/tree.ts",
1756
+ "type": "registry:ui",
1757
+ "target": "components/layout/split-layout/tree.ts"
1753
1758
  }
1754
1759
  ]
1755
1760
  },
@@ -36,6 +36,11 @@
36
36
  "path": "src/components/layout/split-layout/split-layout.tsx",
37
37
  "type": "registry:ui",
38
38
  "target": "components/layout/split-layout/split-layout.tsx"
39
+ },
40
+ {
41
+ "path": "src/components/layout/split-layout/tree.ts",
42
+ "type": "registry:ui",
43
+ "target": "components/layout/split-layout/tree.ts"
39
44
  }
40
45
  ]
41
46
  }
@@ -13,7 +13,7 @@ const sectionsVariants = cva('grid min-w-0', {
13
13
  },
14
14
  },
15
15
  })
16
- const sectionVariants = cva('grid min-w-0 gap-1.5 wrap-anywhere', {
16
+ const sectionVariants = cva('grid min-w-0 content-start gap-1.5 wrap-anywhere', {
17
17
  variants: {
18
18
  layout: {
19
19
  stacked: 'rounded-xl border border-border p-3',
@@ -41,11 +41,14 @@ export function CatalogDetail(props: {
41
41
  }) {
42
42
  return (
43
43
  <article class="grid min-w-0 gap-2" data-catalog-detail>
44
- <div class="flex min-w-0 items-start gap-3 rounded-xl border border-border p-3">
44
+ <div
45
+ data-catalog-detail-header
46
+ class="flex min-w-0 flex-wrap items-start gap-3 rounded-xl border border-border p-3 sm:flex-nowrap"
47
+ >
45
48
  <Show when={props.leading}>
46
49
  <span class="shrink-0">{props.leading}</span>
47
50
  </Show>
48
- <div class="grid min-w-0 flex-1 gap-1">
51
+ <div data-catalog-detail-identity class="grid min-w-0 flex-1 gap-1 wrap-anywhere">
49
52
  <div class="flex min-w-0 flex-wrap items-center gap-1.5">
50
53
  <Show when={props.eyebrow}>
51
54
  <Badge variant="outline">{props.eyebrow}</Badge>
@@ -58,7 +61,10 @@ export function CatalogDetail(props: {
58
61
  <p class="text-muted-foreground text-xs">{props.publishedByLabel(props.publisher)}</p>
59
62
  </div>
60
63
  <Show when={props.action || props.status}>
61
- <div class="flex shrink-0 flex-col items-end gap-2">
64
+ <div
65
+ data-catalog-detail-actions
66
+ class="flex min-w-0 basis-full flex-col items-start gap-2 wrap-anywhere sm:max-w-xs sm:basis-auto sm:items-end"
67
+ >
62
68
  {props.action}
63
69
  {props.status}
64
70
  </div>
@@ -89,7 +95,7 @@ export function CatalogDetailSection(props: { title: string; children?: JSX.Elem
89
95
  >
90
96
  {props.title}
91
97
  </h3>
92
- <div class="grid min-w-0 gap-1.5">{props.children}</div>
98
+ <div class="grid min-w-0 content-start gap-1.5">{props.children}</div>
93
99
  </section>
94
100
  )
95
101
  }
@@ -15,21 +15,18 @@
15
15
  * See NOTICE and docs/research/dev-view-donor-audit.md.
16
16
  */
17
17
 
18
- /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
19
- export type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>
20
- export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
21
- kind: 'split'
22
- id: string
23
- direction: 'row' | 'column'
24
- ratio: number
25
- children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]
26
- }>
27
- export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>
18
+ import {
19
+ MAX_LAYOUT_LEAVES,
20
+ MAX_LAYOUT_DEPTH,
21
+ MIN_SPLIT_RATIO,
22
+ MAX_SPLIT_RATIO,
23
+ listLeaves,
24
+ countLeaves,
25
+ layoutDepth,
26
+ } from './tree'
27
+ import type { SplitLayoutLeaf, SplitLayoutBranch, SplitLayoutNode } from './tree'
28
28
 
29
- export const MAX_LAYOUT_LEAVES = 8
30
- export const MAX_LAYOUT_DEPTH = 8
31
- export const MIN_SPLIT_RATIO = 0.1
32
- export const MAX_SPLIT_RATIO = 0.9
29
+ export * from './tree'
33
30
 
34
31
  export type SplitLayoutState<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
35
32
  center: SplitLayoutNode<L>
@@ -44,22 +41,6 @@ export type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readon
44
41
  splitId: string
45
42
  }>
46
43
 
47
- export function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {
48
- return node.kind === 'leaf'
49
- ? [node]
50
- : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]
51
- }
52
-
53
- export function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
54
- return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])
55
- }
56
-
57
- export function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
58
- return node.kind === 'leaf'
59
- ? 1
60
- : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))
61
- }
62
-
63
44
  function replaceLeaf<L extends SplitLayoutLeaf>(
64
45
  node: SplitLayoutNode<L>,
65
46
  leafId: string,
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright (c) 2026 Michael Yong
3
+ * Copyright (c) 2026 Muxy
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Portions of the binary split behavior are substantially translated from:
7
+ * - get-bb/bb apps/app/src/lib/split-layout/ops.ts (MIT), revision
8
+ * 52a9256373d4d36f9b60e9e2a7f333464091a2ac.
9
+ * - muxy-app/muxy Muxy/Models/Workspace/SplitNode.swift (MIT), revision
10
+ * 5c5be8697c57a2fe70cda97fdbaf7c912e2e31b6.
11
+ * Adapted first in Adea packages/dev-view/src/layout/operations.ts at
12
+ * 0322ab09ff5e9775bfddc0bf2d81e1df436dbef7. Extracted here with opaque
13
+ * host-owned leaves and an injected final-pane placeholder; immutable strict
14
+ * binary operations, accepted limits and undoable close are preserved.
15
+ * See NOTICE and docs/research/dev-view-donor-audit.md.
16
+ */
17
+
18
+ // Read-only tree shape, limits, and traversal form a small import boundary.
19
+ // Persistence decoders can use these without loading pane-editing operations.
20
+
21
+ /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
22
+ export type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>
23
+ export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
24
+ kind: 'split'
25
+ id: string
26
+ direction: 'row' | 'column'
27
+ ratio: number
28
+ children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]
29
+ }>
30
+ export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>
31
+
32
+ export const MAX_LAYOUT_LEAVES = 8
33
+ export const MAX_LAYOUT_DEPTH = 8
34
+ export const MIN_SPLIT_RATIO = 0.1
35
+ export const MAX_SPLIT_RATIO = 0.9
36
+
37
+ export function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {
38
+ return node.kind === 'leaf'
39
+ ? [node]
40
+ : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]
41
+ }
42
+
43
+ export function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
44
+ return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])
45
+ }
46
+
47
+ export function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
48
+ return node.kind === 'leaf'
49
+ ? 1
50
+ : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))
51
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.104.1",
3
+ "version": "0.104.3",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -87,6 +87,12 @@
87
87
  "solid": "./src/components/conversation/transcript-composition.tsx",
88
88
  "development": "./src/components/conversation/transcript-composition.tsx",
89
89
  "import": "./dist/components/conversation/transcript-composition.js"
90
+ },
91
+ "./components/layout/split-layout/tree": {
92
+ "types": "./dist/components/layout/split-layout/tree.d.ts",
93
+ "solid": "./src/components/layout/split-layout/tree.ts",
94
+ "development": "./src/components/layout/split-layout/tree.ts",
95
+ "import": "./dist/components/layout/split-layout/tree.js"
90
96
  }
91
97
  },
92
98
  "scripts": {
@@ -104,7 +110,7 @@
104
110
  "theme:build": "bun scripts/build-default-theme.ts",
105
111
  "theme:check": "bun scripts/build-default-theme.ts --check",
106
112
  "design-system:build": "bun scripts/design-system/build.ts",
107
- "check:packed-layout": "node --test scripts/check-packed-layout-process.test.mjs && node scripts/check-packed-layout.mjs",
113
+ "check:packed-layout": "node --test scripts/check-packed-layout-process.test.mjs && node scripts/check-packed-layout.mjs && node scripts/check-packed-layout-tree.mjs",
108
114
  "check:packed-consumer": "bun scripts/check-packed-consumer.ts",
109
115
  "check:packed-list-row": "bun scripts/check-packed-list-row.ts",
110
116
  "check:packed-scene-controls": "bun scripts/check-packed-scene-controls.ts",
package/registry.json CHANGED
@@ -1750,6 +1750,11 @@
1750
1750
  "path": "src/components/layout/split-layout/split-layout.tsx",
1751
1751
  "type": "registry:ui",
1752
1752
  "target": "components/layout/split-layout/split-layout.tsx"
1753
+ },
1754
+ {
1755
+ "path": "src/components/layout/split-layout/tree.ts",
1756
+ "type": "registry:ui",
1757
+ "target": "components/layout/split-layout/tree.ts"
1753
1758
  }
1754
1759
  ]
1755
1760
  },
@@ -13,7 +13,7 @@ const sectionsVariants = cva('grid min-w-0', {
13
13
  },
14
14
  },
15
15
  })
16
- const sectionVariants = cva('grid min-w-0 gap-1.5 wrap-anywhere', {
16
+ const sectionVariants = cva('grid min-w-0 content-start gap-1.5 wrap-anywhere', {
17
17
  variants: {
18
18
  layout: {
19
19
  stacked: 'rounded-xl border border-border p-3',
@@ -41,11 +41,14 @@ export function CatalogDetail(props: {
41
41
  }) {
42
42
  return (
43
43
  <article class="grid min-w-0 gap-2" data-catalog-detail>
44
- <div class="flex min-w-0 items-start gap-3 rounded-xl border border-border p-3">
44
+ <div
45
+ data-catalog-detail-header
46
+ class="flex min-w-0 flex-wrap items-start gap-3 rounded-xl border border-border p-3 sm:flex-nowrap"
47
+ >
45
48
  <Show when={props.leading}>
46
49
  <span class="shrink-0">{props.leading}</span>
47
50
  </Show>
48
- <div class="grid min-w-0 flex-1 gap-1">
51
+ <div data-catalog-detail-identity class="grid min-w-0 flex-1 gap-1 wrap-anywhere">
49
52
  <div class="flex min-w-0 flex-wrap items-center gap-1.5">
50
53
  <Show when={props.eyebrow}>
51
54
  <Badge variant="outline">{props.eyebrow}</Badge>
@@ -58,7 +61,10 @@ export function CatalogDetail(props: {
58
61
  <p class="text-muted-foreground text-xs">{props.publishedByLabel(props.publisher)}</p>
59
62
  </div>
60
63
  <Show when={props.action || props.status}>
61
- <div class="flex shrink-0 flex-col items-end gap-2">
64
+ <div
65
+ data-catalog-detail-actions
66
+ class="flex min-w-0 basis-full flex-col items-start gap-2 wrap-anywhere sm:max-w-xs sm:basis-auto sm:items-end"
67
+ >
62
68
  {props.action}
63
69
  {props.status}
64
70
  </div>
@@ -89,7 +95,7 @@ export function CatalogDetailSection(props: { title: string; children?: JSX.Elem
89
95
  >
90
96
  {props.title}
91
97
  </h3>
92
- <div class="grid min-w-0 gap-1.5">{props.children}</div>
98
+ <div class="grid min-w-0 content-start gap-1.5">{props.children}</div>
93
99
  </section>
94
100
  )
95
101
  }
@@ -15,21 +15,18 @@
15
15
  * See NOTICE and docs/research/dev-view-donor-audit.md.
16
16
  */
17
17
 
18
- /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
19
- export type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>
20
- export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
21
- kind: 'split'
22
- id: string
23
- direction: 'row' | 'column'
24
- ratio: number
25
- children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]
26
- }>
27
- export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>
18
+ import {
19
+ MAX_LAYOUT_LEAVES,
20
+ MAX_LAYOUT_DEPTH,
21
+ MIN_SPLIT_RATIO,
22
+ MAX_SPLIT_RATIO,
23
+ listLeaves,
24
+ countLeaves,
25
+ layoutDepth,
26
+ } from './tree'
27
+ import type { SplitLayoutLeaf, SplitLayoutBranch, SplitLayoutNode } from './tree'
28
28
 
29
- export const MAX_LAYOUT_LEAVES = 8
30
- export const MAX_LAYOUT_DEPTH = 8
31
- export const MIN_SPLIT_RATIO = 0.1
32
- export const MAX_SPLIT_RATIO = 0.9
29
+ export * from './tree'
33
30
 
34
31
  export type SplitLayoutState<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
35
32
  center: SplitLayoutNode<L>
@@ -44,22 +41,6 @@ export type SplitPaneInput<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readon
44
41
  splitId: string
45
42
  }>
46
43
 
47
- export function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {
48
- return node.kind === 'leaf'
49
- ? [node]
50
- : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]
51
- }
52
-
53
- export function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
54
- return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])
55
- }
56
-
57
- export function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
58
- return node.kind === 'leaf'
59
- ? 1
60
- : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))
61
- }
62
-
63
44
  function replaceLeaf<L extends SplitLayoutLeaf>(
64
45
  node: SplitLayoutNode<L>,
65
46
  leafId: string,
@@ -0,0 +1,51 @@
1
+ /*
2
+ * Copyright (c) 2026 Michael Yong
3
+ * Copyright (c) 2026 Muxy
4
+ * SPDX-License-Identifier: MIT
5
+ *
6
+ * Portions of the binary split behavior are substantially translated from:
7
+ * - get-bb/bb apps/app/src/lib/split-layout/ops.ts (MIT), revision
8
+ * 52a9256373d4d36f9b60e9e2a7f333464091a2ac.
9
+ * - muxy-app/muxy Muxy/Models/Workspace/SplitNode.swift (MIT), revision
10
+ * 5c5be8697c57a2fe70cda97fdbaf7c912e2e31b6.
11
+ * Adapted first in Adea packages/dev-view/src/layout/operations.ts at
12
+ * 0322ab09ff5e9775bfddc0bf2d81e1df436dbef7. Extracted here with opaque
13
+ * host-owned leaves and an injected final-pane placeholder; immutable strict
14
+ * binary operations, accepted limits and undoable close are preserved.
15
+ * See NOTICE and docs/research/dev-view-donor-audit.md.
16
+ */
17
+
18
+ // Read-only tree shape, limits, and traversal form a small import boundary.
19
+ // Persistence decoders can use these without loading pane-editing operations.
20
+
21
+ /** A visual identity only. Extend this with host-owned payload; UI never interprets it. */
22
+ export type SplitLayoutLeaf = Readonly<{ kind: 'leaf'; id: string }>
23
+ export type SplitLayoutBranch<L extends SplitLayoutLeaf = SplitLayoutLeaf> = Readonly<{
24
+ kind: 'split'
25
+ id: string
26
+ direction: 'row' | 'column'
27
+ ratio: number
28
+ children: readonly [SplitLayoutNode<L>, SplitLayoutNode<L>]
29
+ }>
30
+ export type SplitLayoutNode<L extends SplitLayoutLeaf = SplitLayoutLeaf> = L | SplitLayoutBranch<L>
31
+
32
+ export const MAX_LAYOUT_LEAVES = 8
33
+ export const MAX_LAYOUT_DEPTH = 8
34
+ export const MIN_SPLIT_RATIO = 0.1
35
+ export const MAX_SPLIT_RATIO = 0.9
36
+
37
+ export function listLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): readonly L[] {
38
+ return node.kind === 'leaf'
39
+ ? [node]
40
+ : [...listLeaves(node.children[0]), ...listLeaves(node.children[1])]
41
+ }
42
+
43
+ export function countLeaves<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
44
+ return node.kind === 'leaf' ? 1 : countLeaves(node.children[0]) + countLeaves(node.children[1])
45
+ }
46
+
47
+ export function layoutDepth<L extends SplitLayoutLeaf>(node: SplitLayoutNode<L>): number {
48
+ return node.kind === 'leaf'
49
+ ? 1
50
+ : 1 + Math.max(layoutDepth(node.children[0]), layoutDepth(node.children[1]))
51
+ }