@dimina-kit/electron-deck 0.1.0-dev.20260616085026 → 0.1.0-dev.20260618090552

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 (36) hide show
  1. package/README.md +26 -24
  2. package/dist/client/index.js +1 -1
  3. package/dist/client/index.js.map +1 -1
  4. package/dist/client/layout-client.d.ts +1 -1
  5. package/dist/dock-react/dock-view.d.ts +14 -9
  6. package/dist/dock-react/dock-view.d.ts.map +1 -1
  7. package/dist/dock-react/drag-redock.d.ts +15 -0
  8. package/dist/dock-react/drag-redock.d.ts.map +1 -1
  9. package/dist/dock-react/index.d.ts +2 -2
  10. package/dist/dock-react/index.d.ts.map +1 -1
  11. package/dist/dock-react/index.js +197 -39
  12. package/dist/dock-react/index.js.map +1 -1
  13. package/dist/{electron-deck-DfFKeFEC.js → electron-deck-CW6gkGS-.js} +4 -4
  14. package/dist/electron-deck-CW6gkGS-.js.map +1 -0
  15. package/dist/host/index.js +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/internal/deck-app.d.ts +3 -3
  18. package/dist/layout/index.d.ts +2 -1
  19. package/dist/layout/index.d.ts.map +1 -1
  20. package/dist/layout/index.js +2 -2
  21. package/dist/layout/mutations.d.ts.map +1 -1
  22. package/dist/layout/sanitize.d.ts +27 -0
  23. package/dist/layout/sanitize.d.ts.map +1 -0
  24. package/dist/layout/serialize.d.ts.map +1 -1
  25. package/dist/layout/types.d.ts +49 -7
  26. package/dist/layout/types.d.ts.map +1 -1
  27. package/dist/{layout-CZtF0auJ.js → layout-WI8y1hxP.js} +55 -6
  28. package/dist/layout-WI8y1hxP.js.map +1 -0
  29. package/dist/main/compositor.d.ts +1 -1
  30. package/dist/main/index.d.ts +2 -2
  31. package/dist/main/view-handle.d.ts +8 -8
  32. package/dist/types.d.ts +2 -2
  33. package/dist/view-handle-CR-yWNfr.js.map +1 -1
  34. package/package.json +5 -5
  35. package/dist/electron-deck-DfFKeFEC.js.map +0 -1
  36. package/dist/layout-CZtF0auJ.js.map +0 -1
@@ -5,9 +5,10 @@
5
5
  * Implementation lives in the colocated modules; this file is the single public
6
6
  * entry that re-exports every symbol.
7
7
  */
8
- export type { Orientation, SizeConstraint, SplitNode, TabGroupNode, LayoutNode, LayoutTree, DomPanelDescriptor, NativeHandleRef, NativePanelDescriptor, PanelDescriptor, Disposable, PanelRegistry, LayoutSnapshot, LayoutModel, } from './types.js';
8
+ export type { Orientation, SizeConstraint, SplitNode, TabGroupNode, LayoutNode, LayoutTree, DomPanelDescriptor, NativeHandleRef, NativePanelDescriptor, PanelCapabilities, PanelDescriptor, Disposable, PanelRegistry, LayoutSnapshot, LayoutModel, } from './types.js';
9
9
  export { createPanelRegistry } from './registry.js';
10
10
  export { serializeLayout, parseLayout, validateTree } from './serialize.js';
11
+ export { sanitizeFlexibleWeights } from './sanitize.js';
11
12
  export { setSizes, setConstraint, setActive, movePanel, splitPanel, closePanel, extractPanel, insertPanel, } from './mutations.js';
12
13
  export { createLayoutModel } from './model.js';
13
14
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/layout/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACX,WAAW,EACX,cAAc,EACd,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,eAAe,EACf,UAAU,EACV,aAAa,EACb,cAAc,EACd,WAAW,GACX,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC3E,OAAO,EACN,QAAQ,EACR,aAAa,EACb,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,WAAW,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/layout/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,YAAY,EACX,WAAW,EACX,cAAc,EACd,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,qBAAqB,EACrB,iBAAiB,EACjB,eAAe,EACf,UAAU,EACV,aAAa,EACb,cAAc,EACd,WAAW,GACX,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC3E,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AACvD,OAAO,EACN,QAAQ,EACR,aAAa,EACb,SAAS,EACT,SAAS,EACT,UAAU,EACV,UAAU,EACV,YAAY,EACZ,WAAW,GACX,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA"}
@@ -1,2 +1,2 @@
1
- import { a as movePanel, c as setSizes, d as serializeLayout, f as validateTree, i as insertPanel, l as splitPanel, n as closePanel, o as setActive, p as createPanelRegistry, r as extractPanel, s as setConstraint, t as createLayoutModel, u as parseLayout } from "../layout-CZtF0auJ.js";
2
- export { closePanel, createLayoutModel, createPanelRegistry, extractPanel, insertPanel, movePanel, parseLayout, serializeLayout, setActive, setConstraint, setSizes, splitPanel, validateTree };
1
+ import { a as movePanel, c as setSizes, d as parseLayout, f as serializeLayout, i as insertPanel, l as splitPanel, m as createPanelRegistry, n as closePanel, o as setActive, p as validateTree, r as extractPanel, s as setConstraint, t as createLayoutModel, u as sanitizeFlexibleWeights } from "../layout-WI8y1hxP.js";
2
+ export { closePanel, createLayoutModel, createPanelRegistry, extractPanel, insertPanel, movePanel, parseLayout, sanitizeFlexibleWeights, serializeLayout, setActive, setConstraint, setSizes, splitPanel, validateTree };
@@ -1 +1 @@
1
- {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/layout/mutations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,WAAW,EAAE,cAAc,EAA2B,MAAM,YAAY,CAAA;AAqM9G,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAuB7F;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,cAAc,GAAG,IAAI,GAC/B,UAAU,CAgCZ;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAQrF;AAcD,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAUrE;AAED,wBAAgB,YAAY,CAC3B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAIzC;AAED,wBAAgB,WAAW,CAC1B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,UAAU,CAWZ;AASD,wBAAgB,SAAS,CACxB,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,UAAU,CA+BZ;AAED,wBAAgB,UAAU,CACzB,CAAC,EAAE,UAAU,EACb,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GAAG,OAAO,GACtB,UAAU,CAuDZ"}
1
+ {"version":3,"file":"mutations.d.ts","sourceRoot":"","sources":["../../src/layout/mutations.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,WAAW,EAAE,cAAc,EAA2B,MAAM,YAAY,CAAA;AAqM9G,wBAAgB,QAAQ,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,CAuB7F;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,cAAc,GAAG,IAAI,GAC/B,UAAU,CAkCZ;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAQrF;AAcD,wBAAgB,UAAU,CAAC,CAAC,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAUrE;AAED,wBAAgB,YAAY,CAC3B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,GACb;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAIzC;AAED,wBAAgB,WAAW,CAC1B,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,UAAU,CAWZ;AASD,wBAAgB,SAAS,CACxB,CAAC,EAAE,UAAU,EACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,UAAU,CA+BZ;AAED,wBAAgB,UAAU,CACzB,CAAC,EAAE,UAAU,EACb,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,WAAW,EAChB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,QAAQ,GAAG,OAAO,GACtB,UAAU,CAuDZ"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Self-healing for persisted layout trees — pure TS (no react/electron).
3
+ *
4
+ * A FLEXIBLE child (`constraints[i]` null / no constraints) has no enforced
5
+ * lower weight bound in the serialized tree: `validateTree` only rejects
6
+ * NON-FINITE sizes, not non-positive ones. A user who dragged a flexible panel
7
+ * to ~0 width writes a ~0 (or 0, or negative) weight into the tree; on the next
8
+ * restore that panel comes back at 0 width, invisible and effectively stuck.
9
+ *
10
+ * `sanitizeFlexibleWeights` heals such a tree: every flexible child whose weight
11
+ * is NON-POSITIVE (≤ 0 or non-finite) is lifted to a minimum positive weight, so
12
+ * a restored panel is always visible. A small-but-POSITIVE weight is left as-is
13
+ * — weights are a relative ratio (any positive scale is valid), so a tiny
14
+ * positive weight is healthy, not collapsed. PX-SIZED children
15
+ * (`fixedPx`/`minPx`) are sized in pixels, NOT weights — their `sizes[i]` entry
16
+ * is a preserved-but-unused placeholder — so they are left untouched. The
17
+ * function is pure (clone-on-write, never mutates the input).
18
+ */
19
+ import type { LayoutTree } from './types.js';
20
+ /**
21
+ * Return a tree in which every FLEXIBLE child with a non-positive (≤ tiny
22
+ * threshold) weight is healed to a minimum positive weight, recursively. Px
23
+ * children are untouched. Pure: the input is never mutated; a fully-healthy tree
24
+ * is returned structurally unchanged (same weights).
25
+ */
26
+ export declare function sanitizeFlexibleWeights(tree: LayoutTree): LayoutTree;
27
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/layout/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,EAAc,UAAU,EAA6B,MAAM,YAAY,CAAA;AA8CnF;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAGpE"}
@@ -1 +1 @@
1
- {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/layout/serialize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,YAAY,CAAA;AAIxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAErD;AA8KD,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAMxF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAyBpD"}
1
+ {"version":3,"file":"serialize.d.ts","sourceRoot":"","sources":["../../src/layout/serialize.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAc,UAAU,EAAE,MAAM,YAAY,CAAA;AAIxD,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,GAAG,MAAM,CAErD;AAqLD,wBAAgB,YAAY,CAAC,CAAC,EAAE,UAAU,EAAE,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAMxF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAyBpD"}
@@ -8,13 +8,23 @@
8
8
  */
9
9
  export type Orientation = 'row' | 'column';
10
10
  /**
11
- * Per-child fixed-pixel size lock. `fixedPx` must be a finite number > 0
12
- * (validation lives in `serialize.ts`, not the type). A child carrying a
13
- * `SizeConstraint` is rendered at that exact pixel width/height instead of
14
- * being weight-sized.
11
+ * Per-child pixel size constraint. EXACTLY ONE of the two keys is set; the value
12
+ * is a finite number > 0 (validation lives in `serialize.ts`, not the type):
13
+ *
14
+ * - `fixedPx` — LOCKED to exactly N px (min === max). A fixed child is excluded
15
+ * from the flexible weight pool: it never resizes, weight changes don't touch
16
+ * it.
17
+ * - `minPx` — a FLEXIBLE FLOOR: the child is weight-sized like an unconstrained
18
+ * child (participates in the flex pool, resizable), but never shrinks below N
19
+ * px. Use for a panel that must keep a minimum size while still being
20
+ * draggable (e.g. a simulator column floored at the device width).
21
+ *
22
+ * KEY SEMANTIC: "is this child fixed?" === "does the constraint carry `fixedPx`?"
23
+ * A `minPx` child is NOT fixed — it is flexible-with-a-floor.
15
24
  */
16
25
  export interface SizeConstraint {
17
- readonly fixedPx: number;
26
+ readonly fixedPx?: number;
27
+ readonly minPx?: number;
18
28
  }
19
29
  export interface SplitNode {
20
30
  readonly kind: 'split';
@@ -44,7 +54,39 @@ export interface LayoutTree {
44
54
  readonly version: 1;
45
55
  readonly root: LayoutNode;
46
56
  }
47
- export interface DomPanelDescriptor {
57
+ /**
58
+ * Per-panel drag/drop capability policy. All fields are OPTIONAL and
59
+ * DEFAULT-PERMISSIVE, so existing registrations keep today's behavior (a fully
60
+ * draggable panel that may move/split freely). The two fields are orthogonal:
61
+ * `draggable` governs whether the panel is a drag SOURCE / drop ANCHOR at all;
62
+ * `dropPolicy` governs where a draggable panel may LAND.
63
+ */
64
+ export interface PanelCapabilities {
65
+ /**
66
+ * When `false`: the panel's tab cannot be picked up (its tab is not
67
+ * `draggable`) AND it is not a valid drop ANCHOR — no other panel may
68
+ * join/split against the group while this panel is that group's active tab.
69
+ * `undefined` is treated as `true`.
70
+ */
71
+ readonly draggable?: boolean;
72
+ /**
73
+ * Drop/move policy for THIS panel when it is the one being dragged:
74
+ * - `'free'` (default): may move to any group or edge-split anywhere.
75
+ * - `'reorder-only'`: may ONLY reorder within its CURRENT tab group — it
76
+ * never leaves the group and never edge-splits.
77
+ * `undefined` is treated as `'free'`.
78
+ */
79
+ readonly dropPolicy?: 'free' | 'reorder-only';
80
+ /**
81
+ * When `true`, this panel contributes NO tab to the group's tab strip. A
82
+ * group whose every panel hides its tab renders no tab strip at all (its body
83
+ * fills the whole region). Use for structural/chrome-owning panels that carry
84
+ * their own header (e.g. a simulator panel that draws its own device picker),
85
+ * where the engine tab would be redundant. `undefined` is treated as `false`.
86
+ */
87
+ readonly hideTab?: boolean;
88
+ }
89
+ export interface DomPanelDescriptor extends PanelCapabilities {
48
90
  readonly kind: 'dom';
49
91
  readonly id: string;
50
92
  readonly title?: string;
@@ -56,7 +98,7 @@ export interface DomPanelDescriptor {
56
98
  export interface NativeHandleRef {
57
99
  readonly id: string;
58
100
  }
59
- export interface NativePanelDescriptor {
101
+ export interface NativePanelDescriptor extends PanelCapabilities {
60
102
  readonly kind: 'native';
61
103
  readonly id: string;
62
104
  readonly title?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/layout/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,QAAQ,CAAA;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAA;IACxC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,EAAE,CAAA;CACzD;AAED,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;IAClC,wBAAwB;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,YAAY,CAAA;AAEjD,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CACzB;AAID,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAA;IACpB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAA;CACnC;AAED,MAAM,MAAM,eAAe,GAAG,kBAAkB,GAAG,qBAAqB,CAAA;AAExE,MAAM,WAAW,UAAU;IAC1B,OAAO,IAAI,IAAI,CAAA;CACf;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,CAAC,EAAE,eAAe,GAAG,UAAU,CAAA;IACxC,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAA;IAC5C,IAAI,IAAI,SAAS,eAAe,EAAE,CAAA;CAClC;AAID,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC3B,GAAG,IAAI,UAAU,CAAA;IACjB,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,UAAU,GAAG,IAAI,CAAA;IAC/C,SAAS,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,GAAG,MAAM,IAAI,CAAA;CACzD"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/layout/types.ts"],"names":[],"mappings":"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"}
@@ -98,11 +98,14 @@ function collectProblems(root, knownPanelIds) {
98
98
  continue;
99
99
  }
100
100
  const keys = Object.keys(c);
101
- if (keys.length !== 1 || keys[0] !== "fixedPx") problems.push(`split ${id ?? "?"}: constraint must have exactly the key 'fixedPx', got [${keys.join(", ")}]`);
102
- const fixedPx = c.fixedPx;
103
- if (typeof fixedPx !== "number" || !Number.isFinite(fixedPx) || fixedPx <= 0) problems.push(`split ${id ?? "?"}: constraint fixedPx must be a finite number > 0, got ${String(fixedPx)}`);
101
+ const hasFixed = keys.includes("fixedPx");
102
+ const hasMin = keys.includes("minPx");
103
+ if (keys.length !== 1 || !(hasFixed || hasMin)) problems.push(`split ${id ?? "?"}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(", ")}]`);
104
+ const cKey = hasFixed ? "fixedPx" : "minPx";
105
+ const cVal = hasFixed ? c.fixedPx : c.minPx;
106
+ if (typeof cVal !== "number" || !Number.isFinite(cVal) || cVal <= 0) problems.push(`split ${id ?? "?"}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`);
104
107
  }
105
- if (everyConstrained) problems.push(`split ${id ?? "?"}: all children are fixed-px constraints; at least one must be weight-sized`);
108
+ if (everyConstrained) problems.push(`split ${id ?? "?"}: all children are px-sized constraints; at least one must be weight-sized`);
106
109
  }
107
110
  }
108
111
  if (!children) problems.push(`split ${id ?? "?"}: children is not an array`);
@@ -147,6 +150,52 @@ function parseLayout(json) {
147
150
  };
148
151
  }
149
152
  //#endregion
153
+ //#region src/layout/sanitize.ts
154
+ /** The healed-up value for a collapsed flexible child. A small positive weight:
155
+ * enough to make the panel visible without stealing meaningful space from its
156
+ * healthy siblings (the user can re-drag afterwards). */
157
+ var HEALED_FLEX_WEIGHT = 1;
158
+ /** Is child `i` of `node` flexible (weight-sized) — i.e. has no px constraint? */
159
+ function isFlexibleAt(node, i) {
160
+ return (node.constraints?.[i] ?? null) === null;
161
+ }
162
+ function sanitizeNode(node) {
163
+ if (node.kind === "tabs") return node;
164
+ const children = node.children.map(sanitizeNode);
165
+ let changed = children.some((c, i) => c !== node.children[i]);
166
+ const sizes = node.sizes.map((w, i) => {
167
+ if (!isFlexibleAt(node, i)) return w;
168
+ if (typeof w === "number" && Number.isFinite(w) && w > 0) return w;
169
+ changed = true;
170
+ return HEALED_FLEX_WEIGHT;
171
+ });
172
+ if (!changed) return node;
173
+ const rebuilt = {
174
+ kind: "split",
175
+ id: node.id,
176
+ orientation: node.orientation,
177
+ children,
178
+ sizes
179
+ };
180
+ return node.constraints !== void 0 ? {
181
+ ...rebuilt,
182
+ constraints: node.constraints
183
+ } : rebuilt;
184
+ }
185
+ /**
186
+ * Return a tree in which every FLEXIBLE child with a non-positive (≤ tiny
187
+ * threshold) weight is healed to a minimum positive weight, recursively. Px
188
+ * children are untouched. Pure: the input is never mutated; a fully-healthy tree
189
+ * is returned structurally unchanged (same weights).
190
+ */
191
+ function sanitizeFlexibleWeights(tree) {
192
+ const root = sanitizeNode(tree.root);
193
+ return root === tree.root ? tree : {
194
+ version: 1,
195
+ root
196
+ };
197
+ }
198
+ //#endregion
150
199
  //#region src/layout/mutations.ts
151
200
  function tg(id, panels, active) {
152
201
  return {
@@ -517,6 +566,6 @@ function createLayoutModel(initial) {
517
566
  };
518
567
  }
519
568
  //#endregion
520
- export { movePanel as a, setSizes as c, serializeLayout as d, validateTree as f, insertPanel as i, splitPanel as l, closePanel as n, setActive as o, createPanelRegistry as p, extractPanel as r, setConstraint as s, createLayoutModel as t, parseLayout as u };
569
+ export { movePanel as a, setSizes as c, parseLayout as d, serializeLayout as f, insertPanel as i, splitPanel as l, createPanelRegistry as m, closePanel as n, setActive as o, validateTree as p, extractPanel as r, setConstraint as s, createLayoutModel as t, sanitizeFlexibleWeights as u };
521
570
 
522
- //# sourceMappingURL=layout-CZtF0auJ.js.map
571
+ //# sourceMappingURL=layout-WI8y1hxP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-WI8y1hxP.js","names":[],"sources":["../src/layout/registry.ts","../src/layout/serialize.ts","../src/layout/sanitize.ts","../src/layout/mutations.ts","../src/layout/model.ts"],"sourcesContent":["/**\n * Panel registry — pure in-memory map of panel descriptors. Pure TS.\n */\nimport type { Disposable, PanelDescriptor, PanelRegistry } from './types.js'\n\nexport function createPanelRegistry(): PanelRegistry {\n\tconst map = new Map<string, PanelDescriptor>()\n\treturn {\n\t\tregister(p: PanelDescriptor): Disposable {\n\t\t\tmap.set(p.id, p)\n\t\t\treturn {\n\t\t\t\tdispose(): void {\n\t\t\t\t\t// Only drop if this exact descriptor is still registered, so a\n\t\t\t\t\t// re-register under the same id isn't undone by a stale handle.\n\t\t\t\t\tif (map.get(p.id) === p) map.delete(p.id)\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t\tget(id: string): PanelDescriptor | undefined {\n\t\t\treturn map.get(id)\n\t\t},\n\t\tlist(): readonly PanelDescriptor[] {\n\t\t\treturn [...map.values()]\n\t\t},\n\t}\n}\n","/**\n * Serialize / parse / validate — structural integrity + acyclicity. Pure TS.\n *\n * Default-DENY: parseLayout throws on any tree validateTree would flag.\n */\nimport type { LayoutNode, LayoutTree } from './types.js'\n\nconst MAX_DEPTH = 64\n\nexport function serializeLayout(t: LayoutTree): string {\n\treturn JSON.stringify(t)\n}\n\n/**\n * Walk an arbitrary (possibly malformed / cyclic / shared-ref) node graph and\n * collect structural problems. Never recurses infinitely: a node already on the\n * current path (cycle) or already visited elsewhere (shared ref) is reported and\n * not descended into.\n */\nfunction collectProblems(\n\troot: unknown,\n\tknownPanelIds: ReadonlySet<string> | null,\n): string[] {\n\tconst problems: string[] = []\n\tconst seenIds = new Set<string>()\n\tconst seenObjects = new Set<object>()\n\tconst panelOwners = new Map<string, string>()\n\n\tconst visit = (node: unknown, depth: number, path: Set<object>): void => {\n\t\tif (depth > MAX_DEPTH) {\n\t\t\tproblems.push(`depth exceeds ${MAX_DEPTH}`)\n\t\t\treturn\n\t\t}\n\t\tif (node === null || typeof node !== 'object') {\n\t\t\tproblems.push(`node is not an object: ${String(node)}`)\n\t\t\treturn\n\t\t}\n\t\tconst obj = node as object\n\t\tif (path.has(obj)) {\n\t\t\tproblems.push('cycle detected: node reachable from itself')\n\t\t\treturn\n\t\t}\n\t\tif (seenObjects.has(obj)) {\n\t\t\tproblems.push('shared node reference: same object appears twice')\n\t\t\treturn\n\t\t}\n\t\tseenObjects.add(obj)\n\n\t\tconst n = node as { kind?: unknown; id?: unknown }\n\t\tconst kind = n.kind\n\t\tconst id = typeof n.id === 'string' ? n.id : undefined\n\n\t\tif (id === undefined) {\n\t\t\tproblems.push(`node missing string id (kind=${String(kind)})`)\n\t\t}\n\t\telse if (seenIds.has(id)) {\n\t\t\tproblems.push(`duplicate node id: ${id}`)\n\t\t}\n\t\telse {\n\t\t\tseenIds.add(id)\n\t\t}\n\n\t\tif (kind === 'tabs') {\n\t\t\tconst tg = node as { panels?: unknown; active?: unknown }\n\t\t\tconst panels = Array.isArray(tg.panels) ? (tg.panels as unknown[]) : null\n\t\t\tif (!panels) {\n\t\t\t\tproblems.push(`tabs ${id ?? '?'}: panels is not an array`)\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (panels.length === 0) {\n\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: empty tabgroup`)\n\t\t\t\t}\n\t\t\t\tfor (const p of panels) {\n\t\t\t\t\tif (typeof p !== 'string') {\n\t\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: non-string panel id`)\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tconst owner = panelOwners.get(p)\n\t\t\t\t\tif (owner !== undefined) {\n\t\t\t\t\t\tproblems.push(`duplicate panel id across groups: ${p}`)\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tpanelOwners.set(p, id ?? '?')\n\t\t\t\t\t}\n\t\t\t\t\tif (knownPanelIds && !knownPanelIds.has(p)) {\n\t\t\t\t\t\tproblems.push(`orphan panel not in known panel ids: ${p}`)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst active = tg.active\n\t\t\t\tif (typeof active !== 'string' || !panels.includes(active)) {\n\t\t\t\t\tproblems.push(`tabs ${id ?? '?'}: active ${String(active)} not in panels`)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse if (kind === 'split') {\n\t\t\tconst sp = node as { children?: unknown; sizes?: unknown; orientation?: unknown; constraints?: unknown }\n\t\t\tconst children = Array.isArray(sp.children) ? (sp.children as unknown[]) : null\n\t\t\tconst sizes = Array.isArray(sp.sizes) ? (sp.sizes as unknown[]) : null\n\t\t\tconst orientation = sp.orientation\n\t\t\tif (orientation !== 'row' && orientation !== 'column') {\n\t\t\t\tproblems.push(`split ${id ?? '?'}: invalid orientation ${String(orientation)}`)\n\t\t\t}\n\t\t\t// OPTIONAL constraints — validate the field's INTRINSIC format (array\n\t\t\t// shape + per-entry rules + exactly-one-of fixedPx/minPx + all-FIXED\n\t\t\t// guard) INDEPENDENTLY of `children` validity. Only the LENGTH-vs-children\n\t\t\t// comparison is gated on `children` being a valid array (done below).\n\t\t\tif (sp.constraints !== undefined) {\n\t\t\t\tconst constraints = Array.isArray(sp.constraints) ? (sp.constraints as unknown[]) : null\n\t\t\t\tif (!constraints) {\n\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraints is not an array`)\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// Tracks whether EVERY child is PX-SIZED (a non-null constraint —\n\t\t\t\t\t// `fixedPx` OR `minPx`). Both are sized in px and excluded from the\n\t\t\t\t\t// weight pool, so an all-constrained split trips the rrp footgun\n\t\t\t\t\t// (needs >= 1 weight-sized child); only a `null` child clears it.\n\t\t\t\t\tlet everyConstrained = constraints.length > 0\n\t\t\t\t\tfor (const c of constraints) {\n\t\t\t\t\t\tif (c === null) {\n\t\t\t\t\t\t\teveryConstrained = false\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (typeof c !== 'object') {\n\t\t\t\t\t\t\teveryConstrained = false\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint is not null nor an object: ${String(c)}`)\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Exactly ONE of `fixedPx` / `minPx`, value finite > 0.\n\t\t\t\t\t\tconst keys = Object.keys(c as object)\n\t\t\t\t\t\tconst hasFixed = keys.includes('fixedPx')\n\t\t\t\t\t\tconst hasMin = keys.includes('minPx')\n\t\t\t\t\t\tif (keys.length !== 1 || !(hasFixed || hasMin)) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint must have exactly one of 'fixedPx' or 'minPx', got [${keys.join(', ')}]`)\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst cKey = hasFixed ? 'fixedPx' : 'minPx'\n\t\t\t\t\t\tconst cVal = hasFixed ? (c as { fixedPx?: unknown }).fixedPx : (c as { minPx?: unknown }).minPx\n\t\t\t\t\t\tif (typeof cVal !== 'number' || !Number.isFinite(cVal) || cVal <= 0) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: constraint ${cKey} must be a finite number > 0, got ${String(cVal)}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t// Guard the rrp footgun: an all-px-sized split has no weight-sized\n\t\t\t\t\t// child to absorb leftover space (rrp v4.10 requires >= 1).\n\t\t\t\t\tif (everyConstrained) {\n\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: all children are px-sized constraints; at least one must be weight-sized`)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!children) {\n\t\t\t\tproblems.push(`split ${id ?? '?'}: children is not an array`)\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (children.length < 2) {\n\t\t\t\t\tproblems.push(`split ${id ?? '?'}: must have >= 2 children, has ${children.length}`)\n\t\t\t\t}\n\t\t\t\tif (!sizes || sizes.length !== children.length) {\n\t\t\t\t\tproblems.push(\n\t\t\t\t\t\t`split ${id ?? '?'}: sizes ${sizes ? sizes.length : 'missing'} != children ${children.length}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tfor (const s of sizes) {\n\t\t\t\t\t\tif (typeof s !== 'number' || !Number.isFinite(s)) {\n\t\t\t\t\t\t\tproblems.push(`split ${id ?? '?'}: non-finite size ${String(s)}`)\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// LENGTH-vs-children comparison (gated on a valid children array).\n\t\t\t\tif (sp.constraints !== undefined && Array.isArray(sp.constraints)) {\n\t\t\t\t\tconst constraints = sp.constraints as unknown[]\n\t\t\t\t\tif (constraints.length !== children.length) {\n\t\t\t\t\t\tproblems.push(\n\t\t\t\t\t\t\t`split ${id ?? '?'}: constraints ${constraints.length} != children ${children.length}`,\n\t\t\t\t\t\t)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst nextPath = new Set(path)\n\t\t\t\tnextPath.add(obj)\n\t\t\t\tfor (const child of children) {\n\t\t\t\t\tvisit(child, depth + 1, nextPath)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tproblems.push(`unknown node kind: ${String(kind)}`)\n\t\t}\n\t}\n\n\tvisit(root, 0, new Set())\n\treturn problems\n}\n\nexport function validateTree(t: LayoutTree, knownPanelIds: ReadonlySet<string>): string[] {\n\tif (t === null || typeof t !== 'object') return ['tree is not an object']\n\tif ((t as { version?: unknown }).version !== 1) {\n\t\treturn [`unsupported version: ${String((t as { version?: unknown }).version)}`]\n\t}\n\treturn collectProblems((t as { root?: unknown }).root, knownPanelIds)\n}\n\nexport function parseLayout(json: string): LayoutTree {\n\tlet raw: unknown\n\ttry {\n\t\traw = JSON.parse(json)\n\t}\n\tcatch {\n\t\tthrow new Error('parseLayout: input is not valid JSON')\n\t}\n\tif (raw === null || typeof raw !== 'object') {\n\t\tthrow new Error('parseLayout: top-level value is not an object')\n\t}\n\tconst obj = raw as { version?: unknown; root?: unknown }\n\tif (obj.version !== 1) {\n\t\tthrow new Error(`parseLayout: unsupported version ${String(obj.version)}`)\n\t}\n\tif (obj.root === undefined || obj.root === null) {\n\t\tthrow new Error('parseLayout: missing root')\n\t}\n\t// Validate structure with no knownPanelIds constraint (orphan check N/A on\n\t// pure deserialize — caller pairs with their own registry).\n\tconst problems = collectProblems(obj.root, null)\n\tif (problems.length > 0) {\n\t\tthrow new Error(`parseLayout: illegal layout — ${problems.join('; ')}`)\n\t}\n\treturn { version: 1, root: obj.root as LayoutNode }\n}\n","/**\n * Self-healing for persisted layout trees — pure TS (no react/electron).\n *\n * A FLEXIBLE child (`constraints[i]` null / no constraints) has no enforced\n * lower weight bound in the serialized tree: `validateTree` only rejects\n * NON-FINITE sizes, not non-positive ones. A user who dragged a flexible panel\n * to ~0 width writes a ~0 (or 0, or negative) weight into the tree; on the next\n * restore that panel comes back at 0 width, invisible and effectively stuck.\n *\n * `sanitizeFlexibleWeights` heals such a tree: every flexible child whose weight\n * is NON-POSITIVE (≤ 0 or non-finite) is lifted to a minimum positive weight, so\n * a restored panel is always visible. A small-but-POSITIVE weight is left as-is\n * — weights are a relative ratio (any positive scale is valid), so a tiny\n * positive weight is healthy, not collapsed. PX-SIZED children\n * (`fixedPx`/`minPx`) are sized in pixels, NOT weights — their `sizes[i]` entry\n * is a preserved-but-unused placeholder — so they are left untouched. The\n * function is pure (clone-on-write, never mutates the input).\n */\nimport type { LayoutNode, LayoutTree, SizeConstraint, SplitNode } from './types.js'\n\n/** The healed-up value for a collapsed flexible child. A small positive weight:\n * enough to make the panel visible without stealing meaningful space from its\n * healthy siblings (the user can re-drag afterwards). */\nconst HEALED_FLEX_WEIGHT = 1\n\n/** Is child `i` of `node` flexible (weight-sized) — i.e. has no px constraint? */\nfunction isFlexibleAt(node: SplitNode, i: number): boolean {\n\treturn (node.constraints?.[i] ?? null) === null\n}\n\nfunction sanitizeNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return node\n\n\t// Recurse first so nested splits are healed regardless of this level.\n\tconst children = node.children.map(sanitizeNode)\n\n\t// Heal this split's FLEXIBLE child weights. ONLY a NON-POSITIVE weight (≤ 0 or\n\t// non-finite — the \"dragged to 0 / negative\" collapse) is healed; every\n\t// POSITIVE weight is kept verbatim, however small. Weights are a RELATIVE ratio\n\t// (any positive scale is valid — `[0.001, 0.006]` is the same layout as\n\t// `[1, 6]`), so a small-but-positive weight is healthy and must not be rewritten\n\t// (that would crush the ratio). Px children are never touched.\n\tlet changed = children.some((c, i) => c !== node.children[i])\n\tconst sizes = node.sizes.map((w, i) => {\n\t\tif (!isFlexibleAt(node, i)) return w\n\t\tif (typeof w === 'number' && Number.isFinite(w) && w > 0) return w\n\t\tchanged = true\n\t\treturn HEALED_FLEX_WEIGHT\n\t})\n\n\tif (!changed) return node\n\tconst rebuilt: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren,\n\t\tsizes,\n\t}\n\t// Preserve the constraints array verbatim (only weights are touched).\n\treturn node.constraints !== undefined\n\t\t? { ...rebuilt, constraints: node.constraints as readonly (SizeConstraint | null)[] }\n\t\t: rebuilt\n}\n\n/**\n * Return a tree in which every FLEXIBLE child with a non-positive (≤ tiny\n * threshold) weight is healed to a minimum positive weight, recursively. Px\n * children are untouched. Pure: the input is never mutated; a fully-healthy tree\n * is returned structurally unchanged (same weights).\n */\nexport function sanitizeFlexibleWeights(tree: LayoutTree): LayoutTree {\n\tconst root = sanitizeNode(tree.root)\n\treturn root === tree.root ? tree : { version: 1, root }\n}\n","/**\n * Pure tree mutations. Every function returns a NEW tree and never mutates its\n * input (clone-on-write). All structural invariants (#1) are funnelled through\n * `normalizeRoot`, which collapses empty tabgroups + single-child splits,\n * cascading upward, while keeping the root a LayoutNode.\n */\nimport type { LayoutNode, LayoutTree, Orientation, SizeConstraint, SplitNode, TabGroupNode } from './types.js'\n\n// ───────────────────────── helpers ─────────────────────────\n\nfunction tg(id: string, panels: string[], active: string): TabGroupNode {\n\treturn { kind: 'tabs', id, panels, active }\n}\n\n/** Re-derive active after panels changed: keep current active if still present,\n * else select panels[min(removedIndex, len-1)] (clamp to last). */\nfunction deriveActive(panels: string[], prevActive: string, removedIndex: number): string {\n\tif (panels.length === 0) return ''\n\tif (panels.includes(prevActive)) return prevActive\n\tconst idx = Math.min(removedIndex, panels.length - 1)\n\treturn panels[Math.max(0, idx)]!\n}\n\n/**\n * Collapse a (already child-normalized) node:\n * - tabgroup with 0 panels => null (drop)\n * - split: drop null children; if 0 left => null; if 1 left => that child;\n * else rebuild with repaired sizes.\n */\nfunction normalize(node: LayoutNode): LayoutNode | null {\n\tif (node.kind === 'tabs') {\n\t\treturn node.panels.length === 0 ? null : node\n\t}\n\t// split: normalize children first.\n\tconst kept: LayoutNode[] = []\n\tconst keptSizes: number[] = []\n\tconst keptConstraints: (SizeConstraint | null)[] = []\n\tconst hadConstraints = node.constraints !== undefined\n\tnode.children.forEach((child, i) => {\n\t\tconst n = normalize(child)\n\t\tif (n !== null) {\n\t\t\tkept.push(n)\n\t\t\tkeptSizes.push(node.sizes[i] ?? 1)\n\t\t\tif (hadConstraints) keptConstraints.push(node.constraints![i] ?? null)\n\t\t}\n\t})\n\tif (kept.length === 0) return null\n\tif (kept.length === 1) return kept[0]!\n\tconst rebuilt: SplitNode = { kind: 'split', id: node.id, orientation: node.orientation, children: kept, sizes: keptSizes }\n\tif (!hadConstraints) return rebuilt\n\t// M3 repair: dropping children can leave a multi-child split whose survivors\n\t// are ALL fixed-px (the sole flexible child was the one removed). That tree is\n\t// rejected by validateTree (rrp needs >= 1 weight-sized child). Deterministically\n\t// clear the LAST survivor's constraint so >= 1 flexible child remains.\n\tconst allFixed = keptConstraints.length > 0 && !keptConstraints.some(c => c === null)\n\tif (allFixed) {\n\t\tkeptConstraints[keptConstraints.length - 1] = null\n\t}\n\treturn { ...rebuilt, constraints: keptConstraints }\n}\n\n/** Apply collapse to the root, guaranteeing the result is a LayoutNode. A root\n * that collapses to nothing is illegal upstream (we never empty the last group\n * without a replacement), so we only reach here with a survivor. */\nfunction normalizeRoot(root: LayoutNode): LayoutNode {\n\tconst n = normalize(root)\n\tif (n === null) {\n\t\tthrow new Error('mutation would empty the entire layout')\n\t}\n\treturn n\n}\n\ninterface Located {\n\tgroup: TabGroupNode\n}\n\nfunction findGroupContaining(root: LayoutNode, panelId: string): Located | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.panels.includes(panelId)) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found ? { group: found } : null\n}\n\nfunction findGroupById(root: LayoutNode, groupId: string): TabGroupNode | null {\n\tlet found: TabGroupNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'tabs') {\n\t\t\tif (n.id === groupId) found = n\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/** Collect every panelId present anywhere in the tree. */\nfunction collectPanelIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (n.kind === 'tabs') {\n\t\t\tfor (const p of n.panels) out.add(p)\n\t\t}\n\t\telse {\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn out\n}\n\n/** True if `panelId` exists in any tabgroup of the tree. */\nfunction hasPanel(root: LayoutNode, id: string): boolean {\n\treturn collectPanelIds(root).has(id)\n}\n\n/** Collect every node id present anywhere in the tree (splits + tabgroups). */\nfunction collectNodeIds(root: LayoutNode): Set<string> {\n\tconst out = new Set<string>()\n\tconst walk = (n: LayoutNode): void => {\n\t\tout.add(n.id)\n\t\tif (n.kind === 'split') n.children.forEach(walk)\n\t}\n\twalk(root)\n\treturn out\n}\n\n/**\n * Mint an id from `base` that is not already in `taken`. If `base` is free,\n * return it; otherwise append an incrementing `#N` suffix until free. Mutates\n * `taken` so successive calls in the same mutation can't collide with each other.\n */\nfunction freshId(taken: Set<string>, base: string): string {\n\tlet candidate = base\n\tlet n = 2\n\twhile (taken.has(candidate)) {\n\t\tcandidate = `${base}#${n}`\n\t\tn += 1\n\t}\n\ttaken.add(candidate)\n\treturn candidate\n}\n\nfunction findSplitById(root: LayoutNode, splitId: string): SplitNode | null {\n\tlet found: SplitNode | null = null\n\tconst walk = (n: LayoutNode): void => {\n\t\tif (found) return\n\t\tif (n.kind === 'split') {\n\t\t\tif (n.id === splitId) found = n\n\t\t\tn.children.forEach(walk)\n\t\t}\n\t}\n\twalk(root)\n\treturn found\n}\n\n/**\n * Rebuild the tree, replacing the node with matching id by `replacement`\n * (which may be null to delete — handled by collapse). This produces a fresh\n * object graph (clone-on-write); untouched branches are still rebuilt so the\n * input is never shared.\n */\nfunction replaceNode(node: LayoutNode, targetId: string, replacement: LayoutNode): LayoutNode {\n\tif (node.id === targetId) return replacement\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(c => replaceNode(c, targetId, replacement)),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction cloneNode(node: LayoutNode): LayoutNode {\n\tif (node.kind === 'tabs') return tg(node.id, [...node.panels], node.active)\n\tconst out: SplitNode = {\n\t\tkind: 'split',\n\t\tid: node.id,\n\t\torientation: node.orientation,\n\t\tchildren: node.children.map(cloneNode),\n\t\tsizes: [...node.sizes],\n\t}\n\treturn node.constraints !== undefined ? { ...out, constraints: [...node.constraints] } : out\n}\n\nfunction wrap(root: LayoutNode): LayoutTree {\n\treturn { version: 1, root }\n}\n\n// ───────────────────────── mutations ─────────────────────────\n\nexport function setSizes(t: LayoutTree, splitId: string, sizes: readonly number[]): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setSizes: split not found: ${splitId}`)\n\tif (sizes.length !== target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setSizes: sizes length ${sizes.length} != children length ${target.children.length}`,\n\t\t)\n\t}\n\tif (!sizes.every(s => Number.isFinite(s))) {\n\t\tthrow new Error(`setSizes: every size must be a finite number, got [${sizes.join(', ')}]`)\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...sizes],\n\t}\n\t// setSizes must NOT touch constraints — carry them through unchanged.\n\tconst withConstraints: SplitNode = target.constraints !== undefined\n\t\t? { ...replacement, constraints: [...target.constraints] }\n\t\t: replacement\n\treturn wrap(replaceNode(t.root, splitId, withConstraints))\n}\n\n/**\n * Set (or clear) the fixed-px constraint on child `childIndex` of split\n * `splitId`. Pure (clone-on-write). Lazily materializes an all-null constraints\n * array aligned with `children` when absent, then writes the slot. Clearing\n * (`constraint === null`) keeps the array even if all entries become null.\n */\nexport function setConstraint(\n\tt: LayoutTree,\n\tsplitId: string,\n\tchildIndex: number,\n\tconstraint: SizeConstraint | null,\n): LayoutTree {\n\tconst target = findSplitById(t.root, splitId)\n\tif (!target) throw new Error(`setConstraint: split not found: ${splitId}`)\n\tif (!Number.isInteger(childIndex)) {\n\t\tthrow new Error(`setConstraint: childIndex must be an integer, got ${childIndex}`)\n\t}\n\tif (childIndex < 0 || childIndex >= target.children.length) {\n\t\tthrow new Error(\n\t\t\t`setConstraint: childIndex ${childIndex} out of range [0, ${target.children.length})`,\n\t\t)\n\t}\n\tconst base: (SizeConstraint | null)[] = target.constraints !== undefined\n\t\t? [...target.constraints]\n\t\t: target.children.map(() => null)\n\tbase[childIndex] = constraint\n\t// M3 guard: never produce an all-FIXED-px split. If applying this constraint\n\t// would leave EVERY child `fixedPx`-locked (0 weight-sized children),\n\t// `validateTree`/`parseLayout` would later reject the serialized tree (rrp\n\t// requires >= 1 weight-sized child). You cannot fixedPx-pin the LAST flexible\n\t// child — treat it as a NO-OP. A `minPx` child is FLEXIBLE (weight-sized with a\n\t// floor), so it does NOT count toward \"all fixed\" — pinning a `minPx` floor is\n\t// always allowed.\n\tif (base.length > 0 && base.every(c => c !== null)) {\n\t\treturn t\n\t}\n\tconst replacement: SplitNode = {\n\t\tkind: 'split',\n\t\tid: target.id,\n\t\torientation: target.orientation,\n\t\tchildren: target.children.map(cloneNode),\n\t\tsizes: [...target.sizes],\n\t\tconstraints: base,\n\t}\n\treturn wrap(replaceNode(t.root, splitId, replacement))\n}\n\nexport function setActive(t: LayoutTree, groupId: string, panelId: string): LayoutTree {\n\tconst group = findGroupById(t.root, groupId)\n\tif (!group) throw new Error(`setActive: group not found: ${groupId}`)\n\tif (!group.panels.includes(panelId)) {\n\t\tthrow new Error(`setActive: panel ${panelId} not in group ${groupId}`)\n\t}\n\tconst replacement = tg(group.id, [...group.panels], panelId)\n\treturn wrap(replaceNode(t.root, groupId, replacement))\n}\n\n/** Remove a panel from whichever group holds it; collapse. */\nfunction removePanel(root: LayoutNode, panelId: string): LayoutNode {\n\tconst located = findGroupContaining(root, panelId)\n\tif (!located) throw new Error(`panel not found: ${panelId}`)\n\tconst { group } = located\n\tconst removedIndex = group.panels.indexOf(panelId)\n\tconst panels = group.panels.filter(p => p !== panelId)\n\tconst replacement = tg(group.id, panels, deriveActive(panels, group.active, removedIndex))\n\tconst replaced = replaceNode(root, group.id, replacement)\n\treturn normalizeRoot(replaced)\n}\n\nexport function closePanel(t: LayoutTree, panelId: string): LayoutTree {\n\t// Last-panel no-op guard: closing the SOLE panel in the whole tree would\n\t// empty the layout (normalizeRoot throws). Instead return the tree unchanged\n\t// so hosts can wire a close button without special-casing the final panel.\n\t// An UNKNOWN id is NOT \"the last panel\" — only short-circuit when the panel\n\t// actually EXISTS and is the only one; otherwise fall through to removePanel,\n\t// which throws \"panel not found\" for an unknown id (behavior preserved).\n\tconst ids = collectPanelIds(t.root)\n\tif (ids.size === 1 && ids.has(panelId)) return t\n\treturn wrap(removePanel(t.root, panelId))\n}\n\nexport function extractPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n): { tree: LayoutTree; extracted: string } {\n\tconst located = findGroupContaining(t.root, panelId)\n\tif (!located) throw new Error(`extractPanel: panel not found: ${panelId}`)\n\treturn { tree: wrap(removePanel(t.root, panelId)), extracted: panelId }\n}\n\nexport function insertPanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tif (hasPanel(t.root, panelId)) {\n\t\tthrow new Error(`insertPanel: panel already exists in the tree: ${panelId}`)\n\t}\n\tconst group = findGroupById(t.root, dest.groupId)\n\tif (!group) throw new Error(`insertPanel: dest group not found: ${dest.groupId}`)\n\tconst panels = [...group.panels]\n\tconst idx = clampInsertIndex(dest.index, panels.length)\n\tpanels.splice(idx, 0, panelId)\n\tconst replacement = tg(group.id, panels, group.active)\n\treturn wrap(normalizeRoot(replaceNode(t.root, group.id, replacement)))\n}\n\nfunction clampInsertIndex(index: number | undefined, len: number): number {\n\tif (index === undefined) return len\n\tif (index < 0) return 0\n\tif (index > len) return len\n\treturn index\n}\n\nexport function movePanel(\n\tt: LayoutTree,\n\tpanelId: string,\n\tdest: { groupId: string; index?: number },\n): LayoutTree {\n\tconst src = findGroupContaining(t.root, panelId)\n\tif (!src) throw new Error(`movePanel: panel not found: ${panelId}`)\n\tconst destGroup = findGroupById(t.root, dest.groupId)\n\tif (!destGroup) throw new Error(`movePanel: dest group not found: ${dest.groupId}`)\n\n\tif (src.group.id === destGroup.id) {\n\t\t// Same-group reorder: remove then re-insert at the clamped index.\n\t\tconst without = src.group.panels.filter(p => p !== panelId)\n\t\tconst idx = clampInsertIndex(dest.index, without.length)\n\t\tconst panels = [...without]\n\t\tpanels.splice(idx, 0, panelId)\n\t\tconst replacement = tg(src.group.id, panels, src.group.active)\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, src.group.id, replacement)))\n\t}\n\n\t// Cross-group: remove from source (re-derive active + maybe collapse), then\n\t// insert into dest. Compute the destination panels against the ORIGINAL dest\n\t// group so the clamp matches its pre-move length.\n\tconst removedIndex = src.group.panels.indexOf(panelId)\n\tconst srcPanels = src.group.panels.filter(p => p !== panelId)\n\tconst srcReplacement = tg(src.group.id, srcPanels, deriveActive(srcPanels, src.group.active, removedIndex))\n\n\tconst destPanels = [...destGroup.panels]\n\tconst idx = clampInsertIndex(dest.index, destPanels.length)\n\tdestPanels.splice(idx, 0, panelId)\n\tconst destReplacement = tg(destGroup.id, destPanels, destGroup.active)\n\n\tlet root = replaceNode(t.root, src.group.id, srcReplacement)\n\troot = replaceNode(root, destGroup.id, destReplacement)\n\treturn wrap(normalizeRoot(root))\n}\n\nexport function splitPanel(\n\tt: LayoutTree,\n\tatPanelId: string,\n\tdir: Orientation,\n\tnewPanelId: string,\n\tside: 'before' | 'after',\n): LayoutTree {\n\tif (hasPanel(t.root, newPanelId)) {\n\t\tthrow new Error(`splitPanel: new panel already exists in the tree: ${newPanelId}`)\n\t}\n\tconst located = findGroupContaining(t.root, atPanelId)\n\tif (!located) throw new Error(`splitPanel: panel not found: ${atPanelId}`)\n\tconst { group } = located\n\n\t// Generated node ids must be unique within the current tree: a node literally\n\t// named `${origGroupId}__sp` could already exist and produce a duplicate-id\n\t// tree. Mint collision-free ids off the deterministic bases.\n\tconst taken = collectNodeIds(t.root)\n\n\t// Extract atPanelId into its own new group.\n\tconst origGroupId = group.id\n\tconst extractedGroup = tg(freshId(taken, `${origGroupId}__split`), [atPanelId], atPanelId)\n\tconst newGroup = tg(freshId(taken, `${origGroupId}__new`), [newPanelId], newPanelId)\n\n\tconst children: LayoutNode[] = side === 'after'\n\t\t? [extractedGroup, newGroup]\n\t\t: [newGroup, extractedGroup]\n\tconst newSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__sp`),\n\t\torientation: dir,\n\t\tchildren,\n\t\tsizes: [1, 1],\n\t}\n\n\tif (group.panels.length === 1) {\n\t\t// Replace the whole group with the new split.\n\t\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, newSplit)))\n\t}\n\n\t// Multi-panel group: pull atPanelId out, keep the rest in the orig group,\n\t// and place the new split where the orig group was... but the orig group must\n\t// survive too. So: replace orig group with a split [remainingGroup, newSplit]?\n\t// The contract only requires: atPanel + newPanel live in different groups, and\n\t// the orig group keeps the remaining panels. Replace orig group with a split\n\t// of [origRemaining, newSplit] using `dir` orientation.\n\tconst removedIndex = group.panels.indexOf(atPanelId)\n\tconst remaining = group.panels.filter(p => p !== atPanelId)\n\tconst origRemainingGroup = tg(origGroupId, remaining, deriveActive(remaining, group.active, removedIndex))\n\n\tconst outerChildren: LayoutNode[] = side === 'after'\n\t\t? [origRemainingGroup, newSplit]\n\t\t: [newSplit, origRemainingGroup]\n\tconst outerSplit: SplitNode = {\n\t\tkind: 'split',\n\t\tid: freshId(taken, `${origGroupId}__outer`),\n\t\torientation: dir,\n\t\tchildren: outerChildren,\n\t\tsizes: [1, 1],\n\t}\n\treturn wrap(normalizeRoot(replaceNode(t.root, origGroupId, outerSplit)))\n}\n","/**\n * Observable layout model — single-writer, synchronous, call-ordered.\n *\n * INV-3: mutations assume an already-validated (acyclic, integrity-checked) tree\n * — callers feeding hand-built trees should parseLayout/validateTree first.\n *\n * Conventions (pinned by model.test.ts):\n * - revision starts at 0 for the initial tree.\n * - subscribe() does NOT replay; first emission is on the first successful\n * apply, carrying revision 1.\n * - +1 per successful apply; a throwing mutation does NOT advance revision and\n * does NOT notify (the throw propagates to the caller).\n */\nimport type { LayoutModel, LayoutSnapshot, LayoutTree } from './types.js'\n\nexport function createLayoutModel(initial: LayoutTree): LayoutModel {\n\t// Own the initial tree: take a structural snapshot so a later external mutation\n\t// of the caller's `initial` object can't leak into model state. The tree is\n\t// readonly-typed / treated as immutable, so a deep clone is the cheapest\n\t// correct ownership stance. (The `next` from each apply is already a fresh tree\n\t// produced by the pure mutations, so it needs no further clone.)\n\tlet current: LayoutTree = structuredClone(initial)\n\tlet revision = 0\n\tconst subscribers = new Set<(snap: LayoutSnapshot) => void>()\n\n\t// Re-entrancy guard: a subscriber may call apply() during notification. We\n\t// must not re-enter and bump `revision` mid-delivery (subscribers would then\n\t// observe a non-monotonic sequence). Instead, enqueue and drain FIFO so each\n\t// apply fully commits + notifies before the next begins.\n\tlet applying = false\n\tconst queue: ((t: LayoutTree) => LayoutTree)[] = []\n\n\tconst commitAndNotify = (mut: (t: LayoutTree) => LayoutTree): void => {\n\t\t// Compute first; if it throws, nothing below runs — revision stays put\n\t\t// and no subscriber is notified.\n\t\tconst next = mut(current)\n\t\tcurrent = next\n\t\trevision += 1\n\t\tconst snap: LayoutSnapshot = { tree: current, revision }\n\t\t// Snapshot the subscriber set so unsubscribe during delivery is safe.\n\t\t// Isolate subscriber errors: a throw from one must not abort delivery to\n\t\t// the rest, nor make apply() throw after a committed state. The model's\n\t\t// job is delivery, not subscriber correctness — swallow and continue.\n\t\tfor (const fn of [...subscribers]) {\n\t\t\ttry {\n\t\t\t\tfn(snap)\n\t\t\t}\n\t\t\tcatch {\n\t\t\t\t// intentionally swallowed — see above.\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\tget(): LayoutTree {\n\t\t\treturn current\n\t\t},\n\t\t/**\n\t\t * `mut` MUST be pure (clone-on-write); the built-in mutations satisfy this.\n\t\t */\n\t\tapply(mut: (t: LayoutTree) => LayoutTree): void {\n\t\t\tif (applying) {\n\t\t\t\t// Re-entrant call (a subscriber applied during notification): queue it\n\t\t\t\t// and let the in-flight drain pass process it after the current pass.\n\t\t\t\tqueue.push(mut)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tapplying = true\n\t\t\ttry {\n\t\t\t\t// Top-level (non-re-entrant) apply: a throw here propagates to the\n\t\t\t\t// caller and does NOT advance revision / notify. (`finally` below\n\t\t\t\t// still resets `applying`, so a throw can't wedge the model.)\n\t\t\t\tcommitAndNotify(mut)\n\t\t\t\t// Drain re-entrant mutations enqueued during notification. Each queued\n\t\t\t\t// apply was deferred and is therefore fire-and-forget: its original\n\t\t\t\t// caller already returned, so it cannot deliver a synchronous throw\n\t\t\t\t// back. Run each as an INDEPENDENT transaction — a throw skips that\n\t\t\t\t// item (no revision bump, no notify) and we continue draining the rest\n\t\t\t\t// in FIFO order. This keeps a thrown queued mut from bubbling into the\n\t\t\t\t// already-committed outer apply() and from disrupting the order /\n\t\t\t\t// processing of later queued items.\n\t\t\t\twhile (queue.length > 0) {\n\t\t\t\t\tconst queuedMut = queue.shift()!\n\t\t\t\t\ttry {\n\t\t\t\t\t\tcommitAndNotify(queuedMut)\n\t\t\t\t\t}\n\t\t\t\t\tcatch {\n\t\t\t\t\t\t// Deferred apply: swallow + continue — see above.\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfinally {\n\t\t\t\tapplying = false\n\t\t\t}\n\t\t},\n\t\tsubscribe(fn: (snap: LayoutSnapshot) => void): () => void {\n\t\t\tsubscribers.add(fn)\n\t\t\treturn (): void => {\n\t\t\t\tsubscribers.delete(fn)\n\t\t\t}\n\t\t},\n\t}\n}\n"],"mappings":"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"}
@@ -111,7 +111,7 @@ export interface Compositor {
111
111
  /** Fold the intent state to EMPTY and commit, removing every native view
112
112
  * this window's compositor mounted from the host. The resulting commit is
113
113
  * REMOVALS-ONLY, so it reuses {@link commit}'s teardown-friendly
114
- * "destroyed host + only removals → silent" path (A1.2.1): on an
114
+ * "destroyed host + only removals → silent" path (commit failure semantics): on an
115
115
  * already-destroyed host it makes zero host calls and throws nothing.
116
116
  *
117
117
  * Optional on the interface so a partial test double (or a caller that only
@@ -3,8 +3,8 @@
3
3
  *
4
4
  * The connection layer (foundation.md §4): one `Connection` per trusted
5
5
  * webContents, owning a single `DisposableRegistry` lifetime segment that tears
6
- * down deterministically on hard-destroy or soft-reuse. devtools (and qdmp)
7
- * consume this as the substrate for connection-scoped resource ownership.
6
+ * down deterministically on hard-destroy or soft-reuse. devtools (and other
7
+ * downstream hosts) consume this as the substrate for connection-scoped resource ownership.
8
8
  */
9
9
  export { createConnectionRegistry, type Connection, type ConnectionRegistry, } from './connection.js';
10
10
  export { DisposableRegistry, toDisposable, type Disposable, type DisposeFn, } from './disposable.js';
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `ViewHandle` — the per-view orchestrator (build-plan §2(a)/(b)/(d)). One handle
2
+ * `ViewHandle` — the per-view orchestrator (view-handle.md「placeIn 与挂载」/「dispose(viewScope LIFO 序)」/「moveTo 跨窗迁移」). One handle
3
3
  * owns ONE native view and threads it through a window's z-planner. It composes
4
4
  * three INJECTED primitives and nothing else (no deck-app, no Electron):
5
5
  * - a {@link NativeView} (its `ref` + a `setBounds` sink) — the native surface;
@@ -12,16 +12,16 @@
12
12
  * (scope.ts cross-layer LIFO): the native view is detached and the placement
13
13
  * sink goes inert.
14
14
  *
15
- * gap#1. The handle drives `setBounds` DIRECTLY on its native view; the
15
+ * handle 直接驱动 bounds. The handle drives `setBounds` DIRECTLY on its native view; the
16
16
  * Compositor stays a pure z-order planner (mount/unmount/commit only) and never
17
17
  * sees geometry.
18
18
  *
19
- * A4 teardown order (build-plan §2(b)). The viewScope owns the native detach
19
+ * per-window teardown 顺序 (view-handle.md「dispose(viewScope LIFO 序)」). The viewScope owns the native detach
20
20
  * FIRST and the sink-disable LAST, so LIFO teardown runs the sink-disable
21
21
  * (STEP0) BEFORE the detach (STEP1): a late `place` frame can never drive a
22
22
  * native effect on a half-torn-down view.
23
23
  *
24
- * Cross-window move (build-plan §2(d) / A1.3.2). {@link ViewHandle.moveTo}
24
+ * Cross-window move (view-handle.md「moveTo 跨窗迁移」/ compositor-and-teardown.md「moveTo 事务状态机」). {@link ViewHandle.moveTo}
25
25
  * migrates the view to another `{ compositor, windowScope }` as TWO independent
26
26
  * Compositor commits, guarded by a per-view async mutex (THE migrationLock —
27
27
  * each handle is one view). The current placement is a MUTABLE token so the
@@ -51,9 +51,9 @@ export type Placement = {
51
51
  visible: false;
52
52
  };
53
53
  /** The native surface a handle drives: its z-order identity (`ref`) plus the
54
- * `setBounds` sink the handle calls directly (gap#1). `destroy` (optional)
54
+ * `setBounds` sink the handle calls directly (handle 直接驱动 bounds). `destroy` (optional)
55
55
  * destroys the backing native view (its WebContents) — owned by the viewScope so
56
- * it runs on teardown AFTER the detach (keepAlive B3.1 lifetime/leak fix).
56
+ * it runs on teardown AFTER the detach (keepAlive「保活寿命归 Scope、淘汰策略归 host」lifetime/leak fix).
57
57
  * Optional so fakes that don't model a native WebContents stay valid.
58
58
  *
59
59
  * `webContents` / `capturePage` (optional) expose the backing native view's
@@ -84,7 +84,7 @@ export interface ViewHandle {
84
84
  * detaches (unmount + commit) but keeps the native view alive. */
85
85
  applyPlacement(p: Placement): void;
86
86
  /**
87
- * Cross-window move (build-plan §2(d) / A1.3.2). Migrate the view from its
87
+ * Cross-window move (view-handle.md「moveTo 跨窗迁移」/ compositor-and-teardown.md「moveTo 事务状态机」). Migrate the view from its
88
88
  * current placement (`src`) to `dest` as TWO independent Compositor commits,
89
89
  * serialized by a per-view async mutex (migrationLock):
90
90
  *
@@ -95,7 +95,7 @@ export interface ViewHandle {
95
95
  *
96
96
  * On success the compositor token moves to `dest` (later `applyPlacement`
97
97
  * drives the dest host). With `rehome:true`, the viewScope is re-parented under
98
- * dest's `windowScope` (lifetime follows display). gap#2: moveTo moves DISPLAY
98
+ * dest's `windowScope` (lifetime follows display). moveTo 迁移显示而非寿命: moveTo moves DISPLAY
99
99
  * (and, with rehome, LIFETIME) — it does NOT carry capability grants; the dest
100
100
  * window's own control layer issues its own grant. Terminal (Promise<void>, not
101
101
  * chainable).
package/dist/types.d.ts CHANGED
@@ -16,7 +16,7 @@ export interface Disposable {
16
16
  * `runtime.view` / `DeckViewHandle`, `runtime.scopes` / `DeckSession`,
17
17
  * `runtime.grants`, `runtime.layout` — is fully built and wired, but has NO
18
18
  * production consumer yet: the only callers are `examples/layout-demo` and
19
- * `spike/popout`. The devtools/qdmp host integrates through the `RuntimeBackend`
19
+ * `spike/popout`. The devtools (and other downstream) host integrates through the `RuntimeBackend`
20
20
  * lifecycle path with `ownsWindows:true` and never touches this surface.
21
21
  *
22
22
  * Until a SECOND, real consumer adopts it, these signatures are NOT API-stable —
@@ -300,7 +300,7 @@ export interface ViewCreateOptions {
300
300
  * `runtime.scopes.create()` is accepted (provenance check). */
301
301
  readonly scope?: DeckSession;
302
302
  /**
303
- * Opt-in keep-alive eviction policy (B3.2). When set, the framework disposes
303
+ * Opt-in keep-alive eviction policy (opt-in LRU helper). When set, the framework disposes
304
304
  * the least-recently-VISIBLE HIDDEN view in this view's group once the group's
305
305
  * HIDDEN count exceeds `max` — destroying that view's native WebContents.
306
306
  * Currently-visible views are NEVER evicted. Views sharing the same `max` form