@vibecook/ice 0.1.0 → 0.3.0

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 (119) hide show
  1. package/README.md +37 -2
  2. package/dist/chunk-2GUYATUM.js +643 -0
  3. package/dist/chunk-2GUYATUM.js.map +1 -0
  4. package/dist/{chunk-5S7GSXJG.js → chunk-5QY7HK6G.js} +3145 -1306
  5. package/dist/chunk-5QY7HK6G.js.map +1 -0
  6. package/dist/{chunk-BCJ47SX7.js → chunk-OR5FNVUE.js} +460 -1
  7. package/dist/chunk-OR5FNVUE.js.map +1 -0
  8. package/dist/{chunk-FIAXPNNY.js → chunk-PKK6GLSA.js} +143 -551
  9. package/dist/chunk-PKK6GLSA.js.map +1 -0
  10. package/dist/devtools.js +508 -372
  11. package/dist/devtools.js.map +1 -1
  12. package/dist/dom.js +3 -11
  13. package/dist/ground.js +715 -0
  14. package/dist/ground.js.map +1 -0
  15. package/dist/index.js +80 -2
  16. package/dist/kernel.js +31 -1
  17. package/dist/r3f.js +605 -62
  18. package/dist/r3f.js.map +1 -1
  19. package/dist/react.js +36 -418
  20. package/dist/react.js.map +1 -1
  21. package/dist/types/packages/core/src/catalog/camera-derived.d.ts +65 -3
  22. package/dist/types/packages/core/src/catalog/gesture.d.ts +40 -4
  23. package/dist/types/packages/core/src/catalog/graph.d.ts +7 -6
  24. package/dist/types/packages/core/src/catalog/index.d.ts +1 -0
  25. package/dist/types/packages/core/src/catalog/insert.d.ts +31 -0
  26. package/dist/types/packages/core/src/catalog/pointer.d.ts +11 -0
  27. package/dist/types/packages/core/src/catalog/scene.d.ts +37 -4
  28. package/dist/types/packages/core/src/catalog/selection-presence.d.ts +7 -0
  29. package/dist/types/packages/core/src/catalog/settings-resources.d.ts +21 -0
  30. package/dist/types/packages/core/src/doc/autosave.d.ts +49 -2
  31. package/dist/types/packages/core/src/doc/bootstrap.d.ts +29 -0
  32. package/dist/types/packages/core/src/doc/channels.d.ts +9 -0
  33. package/dist/types/packages/core/src/doc/doc-commit-sink.d.ts +2 -1
  34. package/dist/types/packages/core/src/doc/doc-kit.d.ts +16 -1
  35. package/dist/types/packages/core/src/doc/envelope.d.ts +2 -1
  36. package/dist/types/packages/core/src/doc/migrate.d.ts +8 -6
  37. package/dist/types/packages/core/src/doc/schema-migrate.d.ts +50 -0
  38. package/dist/types/packages/core/src/doc/version-gate.d.ts +3 -0
  39. package/dist/types/packages/core/src/engine/commit-sink.d.ts +24 -2
  40. package/dist/types/packages/core/src/engine/engine.d.ts +11 -0
  41. package/dist/types/packages/core/src/engine/frame-control.d.ts +71 -0
  42. package/dist/types/packages/core/src/engine/frame-info.d.ts +16 -1
  43. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +79 -6
  44. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +4 -2
  45. package/dist/types/packages/core/src/helpers/churn-guard.d.ts +40 -0
  46. package/dist/types/packages/core/src/index.d.ts +15 -7
  47. package/dist/types/packages/core/src/input/queue.d.ts +11 -0
  48. package/dist/types/packages/core/src/interaction/gesture-status.d.ts +13 -0
  49. package/dist/types/packages/core/src/nav/nested-canvas.d.ts +47 -11
  50. package/dist/types/packages/core/src/ops/arrange.d.ts +15 -0
  51. package/dist/types/packages/core/src/ops/insert.d.ts +62 -0
  52. package/dist/types/packages/core/src/ops/point-pick.d.ts +11 -7
  53. package/dist/types/packages/core/src/ops/sibling-order.d.ts +70 -0
  54. package/dist/types/packages/core/src/schema/meta.d.ts +1 -0
  55. package/dist/types/packages/core/src/settings/defaults.d.ts +45 -0
  56. package/dist/types/packages/core/src/settings/ground-config.d.ts +55 -0
  57. package/dist/types/packages/core/src/systems/chrome.d.ts +6 -2
  58. package/dist/types/packages/core/src/systems/insert-ghost.d.ts +25 -0
  59. package/dist/types/packages/core/src/systems/l1-pick.d.ts +25 -11
  60. package/dist/types/packages/core/src/systems/l2-recognize.d.ts +10 -4
  61. package/dist/types/packages/core/src/systems/l3-behave.d.ts +6 -4
  62. package/dist/types/packages/core/src/systems/l3-claim.d.ts +7 -3
  63. package/dist/types/packages/core/src/systems/l3-drop.d.ts +3 -1
  64. package/dist/types/packages/core/src/systems/l3-resize.d.ts +3 -1
  65. package/dist/types/packages/core/src/systems/l3-snap.d.ts +25 -7
  66. package/dist/types/packages/core/src/systems/nav-flight.d.ts +95 -0
  67. package/dist/types/packages/core/src/widget/define-widget.d.ts +39 -0
  68. package/dist/types/packages/core/src/widget/mount-store.d.ts +11 -3
  69. package/dist/types/packages/core/src/widget/set-props.d.ts +16 -0
  70. package/dist/types/packages/core/src/widget/spawn.d.ts +21 -1
  71. package/dist/types/packages/devtools/src/attach.d.ts +77 -0
  72. package/dist/types/packages/devtools/src/dock.d.ts +38 -0
  73. package/dist/types/packages/devtools/src/gl-panel.d.ts +69 -0
  74. package/dist/types/packages/devtools/src/index.d.ts +8 -6
  75. package/dist/types/packages/dom/src/index.d.ts +1 -3
  76. package/dist/types/packages/dom/src/loop.d.ts +11 -0
  77. package/dist/types/packages/dom/src/measure-wiring.d.ts +2 -2
  78. package/dist/types/packages/dom/src/pointer-adapter.d.ts +21 -1
  79. package/dist/types/packages/dom/src/reflectors/dom-widgets.d.ts +5 -0
  80. package/dist/types/packages/ground/src/index.d.ts +52 -0
  81. package/dist/types/packages/ground/src/layer.d.ts +40 -0
  82. package/dist/types/packages/ground/src/pass.d.ts +41 -0
  83. package/dist/types/packages/ground/src/passes/grid.d.ts +21 -0
  84. package/dist/types/packages/ground/src/passes/guides-collect.d.ts +14 -0
  85. package/dist/types/packages/ground/src/passes/guides.d.ts +3 -0
  86. package/dist/types/packages/ground/src/passes/soup-collect.d.ts +35 -0
  87. package/dist/types/packages/ground/src/passes/soup-mesh.d.ts +8 -0
  88. package/dist/types/packages/ground/src/passes/wires-collect.d.ts +31 -0
  89. package/dist/types/packages/ground/src/passes/wires.d.ts +10 -0
  90. package/dist/types/packages/ground/src/renderer.d.ts +15 -0
  91. package/dist/types/packages/ice/src/ground.d.ts +1 -0
  92. package/dist/types/packages/kernel/src/bezier.d.ts +15 -0
  93. package/dist/types/packages/kernel/src/coords.d.ts +15 -0
  94. package/dist/types/packages/kernel/src/easing.d.ts +11 -0
  95. package/dist/types/packages/kernel/src/index.d.ts +3 -0
  96. package/dist/types/packages/kernel/src/layout.d.ts +118 -0
  97. package/dist/types/packages/kernel/src/nav-flight.d.ts +93 -0
  98. package/dist/types/packages/r3f/src/bridge.d.ts +73 -12
  99. package/dist/types/packages/r3f/src/composite-material.d.ts +9 -0
  100. package/dist/types/packages/r3f/src/compositor-pass.d.ts +22 -0
  101. package/dist/types/packages/r3f/src/gl-root.d.ts +76 -1
  102. package/dist/types/packages/r3f/src/gl-router.d.ts +10 -0
  103. package/dist/types/packages/r3f/src/index.d.ts +3 -2
  104. package/dist/types/packages/r3f/src/island-state.d.ts +44 -0
  105. package/dist/types/packages/r3f/src/preview-capture.d.ts +19 -0
  106. package/dist/types/packages/r3f/src/self-sustain.d.ts +39 -0
  107. package/dist/types/packages/r3f/src/use-island-frame.d.ts +16 -1
  108. package/dist/types/packages/react/src/engine-context.d.ts +21 -0
  109. package/dist/types/packages/react/src/index.d.ts +4 -2
  110. package/dist/types/packages/react/src/infinite-canvas.d.ts +40 -6
  111. package/dist/types/packages/react/src/preview-snapshots.d.ts +17 -0
  112. package/dist/types/packages/react/src/widget-preview.d.ts +14 -0
  113. package/package.json +32 -61
  114. package/dist/chunk-5S7GSXJG.js.map +0 -1
  115. package/dist/chunk-BCJ47SX7.js.map +0 -1
  116. package/dist/chunk-FIAXPNNY.js.map +0 -1
  117. package/dist/types/packages/devtools/src/panel.d.ts +0 -48
  118. package/dist/types/packages/dom/src/reflectors/grid.d.ts +0 -63
  119. package/dist/types/packages/dom/src/reflectors/wires.d.ts +0 -88
@@ -21,6 +21,7 @@
21
21
  */
22
22
  import type { World } from "@vibecook/strata-ecs";
23
23
  import type { AnySystem } from "./pipeline";
24
+ import { type FrameControl } from "./frame-control";
24
25
  import { type PhaseGroup } from "./pipeline";
25
26
  import { type ReflectorDef } from "./reflectors";
26
27
  export type PublishHook = (world: World) => void;
@@ -54,6 +55,16 @@ export interface Engine {
54
55
  onPublish(hook: PublishHook): () => void;
55
56
  /** Run one full frame. `now` in ms (rAF timestamp or a test counter). */
56
57
  step(now: number): void;
58
+ /**
59
+ * The frame gate (2026-08-04): freeze/thaw plus the settle protocol a host
60
+ * loop consults before each frame. It lives HERE, next to the step it
61
+ * governs, so every host gets it without a new argument — and so a
62
+ * subsystem holding only the `Engine` (the GL compositor) can report itself
63
+ * busy. `step()` itself stays unconditional: the gate governs the loop, not
64
+ * the primitive, which keeps headless hosts and traces driving frames by
65
+ * hand exactly as before.
66
+ */
67
+ readonly frame: FrameControl;
57
68
  /** Arm per-system run/skip + µs collection (idempotent; costs timer calls per system). */
58
69
  enableTelemetry(): void;
59
70
  /** The most recent completed frame's readout (undefined until telemetry armed + one step). */
@@ -0,0 +1,71 @@
1
+ /**
2
+ * The frame gate (design-002 §1 amendment, 2026-08-04) — the frozen-world mode
3
+ * design-005 §7 named and deliberately left unbuilt.
4
+ *
5
+ * Stage holds are PRESENTATION policy: `StageMode.backgroundHolds` freezes the
6
+ * compositor on its retained textures while the world keeps stepping, which is
7
+ * exactly what makes a hold safe under a PARTIAL overlay — a menu panel floats
8
+ * over a still-live canvas, undo reflects, collab applies, tweens land. The
9
+ * rev-2 decision recorded that a stricter mode "would be a SEPARATE concept".
10
+ * This is that concept: while a freeze is held the host loop stops calling
11
+ * `step` at all — no systems, no publish, no notify, no reflectors, no rAF
12
+ * callback. It is for chrome that COVERS the canvas outright, where every
13
+ * frame drawn behind it is work nobody can see.
14
+ *
15
+ * THE SETTLE — why a freeze does not park on the spot. Parking the instant a
16
+ * freeze is taken would strand the canvas mid-churn: a cancelled gesture that
17
+ * has not reached its commit tick, islands still owed a first paint, a
18
+ * reflector holding a write that landed this frame. So a freeze walks the loop
19
+ * to QUIET — every registered settle reporter idle — then takes ONE further
20
+ * step so the quiet state itself reflects, and only then closes the gate. The
21
+ * walk is bounded by {@link SETTLE_CAP}: a reporter that never goes quiet makes
22
+ * a freeze park LATE, never never.
23
+ *
24
+ * Core stays scheduler-free (design-002 §1). This module owns no timer and
25
+ * starts nothing — it answers {@link FrameControl.claimStep} and announces
26
+ * transitions. The DOM package's rAF loop is what actually parks and restarts;
27
+ * `engine.step(now)` itself stays callable by hand (headless hosts, traces),
28
+ * because the gate governs the LOOP, not the primitive.
29
+ */
30
+ import type { World } from "@vibecook/strata-ecs";
31
+ /**
32
+ * Frames a freeze may spend walking to quiet before it parks regardless.
33
+ * Two seconds at 60 Hz: comfortably longer than a cancelled gesture's path to
34
+ * its commit tick or a cold board's first paints, short enough that a wedged
35
+ * reporter reads as a hiccup rather than a hang.
36
+ */
37
+ export declare const SETTLE_CAP = 120;
38
+ export interface FrameControl {
39
+ /**
40
+ * Take a named freeze. Returns an IDEMPOTENT thaw — the disposer pattern
41
+ * that makes overlay lifecycles leak-proof (release on unmount), the name
42
+ * that makes "why is the engine parked?" answerable. Refcounted: the loop
43
+ * resumes when the LAST freeze thaws.
44
+ */
45
+ freeze(name: string): () => void;
46
+ /** A freeze is held — the loop is settling or already parked. */
47
+ isFrozen(): boolean;
48
+ /** The settle finished and the gate closed: no further steps are being run. */
49
+ isParked(): boolean;
50
+ /** Live freeze names, in take order (debug/devtools). */
51
+ holds(): readonly string[];
52
+ /**
53
+ * Register a settle reporter: while `busy()` is true, a taken freeze keeps
54
+ * stepping instead of parking. This is how a subsystem that owes visible
55
+ * work — the GL compositor's pending first paints, a gesture walking to its
56
+ * commit — keeps a freeze from landing on a half-finished image. Returns an
57
+ * idempotent unregister.
58
+ */
59
+ settleWhile(name: string, busy: () => boolean): () => void;
60
+ /** Reporters answering busy right now ("why hasn't it parked yet?"). */
61
+ settling(): readonly string[];
62
+ /**
63
+ * The loop gate. CONSUMING — each call advances the settle walk, so it is
64
+ * for frame hosts only, never a predicate to poll. Use {@link isFrozen} /
65
+ * {@link isParked} to ask questions.
66
+ */
67
+ claimStep(): boolean;
68
+ /** Wake on any freeze/thaw transition (the host loop restarts here). */
69
+ onChange(fn: () => void): () => void;
70
+ }
71
+ export declare function createFrameControl(world: World): FrameControl;
@@ -1,13 +1,28 @@
1
1
  import type { World } from "@vibecook/strata-ecs";
2
- /** { tick, dt ≤ dtClampMs (ms), now (ms timeline of the caller's clock) }. */
2
+ /**
3
+ * { tick, dt ≤ dtClampMs (ms), now (ms timeline of the caller's clock),
4
+ * clock (accumulated CLAMPED dt — the gesture clock) }.
5
+ *
6
+ * `clock` exists because `now` cannot be trusted for HUMAN timing windows:
7
+ * rAF timestamps are the compositor's frame clock, and under throttling
8
+ * (occluded window, headless BeginFrame control, software rendering) they lag
9
+ * wall time by 100s of ms to SECONDS (measured 0.3–5.8s, 2026-07-17) — and
10
+ * event.timeStamp does NOT lag, so any `now − eventTs` mixes two clocks and
11
+ * tap/long-press windows evaluate garbage. `clock` is monotonic, advances by
12
+ * the clamped dt each frame (a 640ms stall counts 64ms — the 2026-07-12
13
+ * "stall must not eat the window" intent), and is never compared against
14
+ * event timestamps.
15
+ */
3
16
  export declare const FrameInfo: import("@vibecook/strata-ecs").Resource<import("@vibecook/strata-ecs").ValueOf<{
4
17
  readonly tick: import("@vibecook/strata-ecs").FieldSpec<"u32", true>;
5
18
  readonly dt: import("@vibecook/strata-ecs").FieldSpec<"f32", true>;
6
19
  readonly now: import("@vibecook/strata-ecs").FieldSpec<"f64", true>;
20
+ readonly clock: import("@vibecook/strata-ecs").FieldSpec<"f64", true>;
7
21
  }>, {
8
22
  readonly tick: import("@vibecook/strata-ecs").FieldSpec<"u32", true>;
9
23
  readonly dt: import("@vibecook/strata-ecs").FieldSpec<"f32", true>;
10
24
  readonly now: import("@vibecook/strata-ecs").FieldSpec<"f64", true>;
25
+ readonly clock: import("@vibecook/strata-ecs").FieldSpec<"f64", true>;
11
26
  }>;
12
27
  /**
13
28
  * Advance the frame clock. First frame gets dt 0 (no previous `now` to diff
@@ -20,8 +20,11 @@
20
20
  */
21
21
  import type { Entity, World } from "@vibecook/strata-ecs";
22
22
  import { type Engine } from "../engine/engine";
23
+ import type { FrameControl } from "../engine/frame-control";
23
24
  import { type InteractionStack } from "../interaction/install";
24
- import { type NestedCanvas } from "../nav/nested-canvas";
25
+ import { type NavOpts, type NestedCanvas } from "../nav/nested-canvas";
26
+ import { type ArrangeOpts } from "../ops/arrange";
27
+ import { type InsertByDragOpts } from "../ops/insert";
25
28
  import { type WidgetRuntime } from "../widget/mount-store";
26
29
  import { type SpawnWidgetOpts } from "../widget/spawn";
27
30
  import { type Tool } from "../tools/define-tool";
@@ -29,6 +32,7 @@ import { type DocSession, type DocSessionOpts, type OpenDocResult } from "../doc
29
32
  import { type JoinDocOpts, type JoinResult } from "../doc/bootstrap";
30
33
  import type { ByteChannel } from "../doc/channels";
31
34
  import { type Autosave, type AutosaveOpts, type AutosaveStorageWrite } from "../doc/autosave";
35
+ import { type PresenceSession } from "../presence/presence-kit";
32
36
  import type { MeasureQueue } from "../input/measure-queue";
33
37
  import { GESTURE_DEFAULTS, POINTER_DEFAULTS } from "../settings/defaults";
34
38
  export interface CanvasEngineOpts {
@@ -53,6 +57,10 @@ export interface CanvasEngineOpts {
53
57
  readonly enabled?: boolean;
54
58
  readonly thresholdPx?: number;
55
59
  };
60
+ /** Selection-chrome knobs: liftScale = the app's visual drag-lift scale (union box wraps it). */
61
+ readonly chrome?: {
62
+ readonly liftScale?: number;
63
+ };
56
64
  };
57
65
  readonly policy?: {
58
66
  /** The gate verdict a "migrate"-classified doc downgrades to when migration is off/fails. */
@@ -65,40 +73,96 @@ export interface CanvasOps {
65
73
  /** cancel active gestures → switch (design-003 §8; the strata-example lesson). */
66
74
  setTool(id: string): void;
67
75
  spawnWidget(type: string, opts: SpawnWidgetOpts): Entity;
76
+ /**
77
+ * Tray adoption (2026-07-19): DRAFT-spawn a runtime ghost of `type` centered
78
+ * under the pressing pointer and enqueue one synthetic down — the ordinary
79
+ * drag stack takes over (lift, snap, drop targets, folder consume). Release
80
+ * promotes it through ONE `create` tx (selected, one undo step); cancel or a
81
+ * rejected drop flies it back to the tray press point and despawns it with
82
+ * zero undo footprint. Call from the tray tile's `pointerdown` AFTER
83
+ * `stopPropagation()` (the down must not double-land via the adapter).
84
+ */
85
+ insertByDrag(type: string, opts: InsertByDragOpts): Entity;
86
+ /** Validated prop update: Standard-Schema-checked, json serialized, ONE tx (2026-07-13 review). */
87
+ setWidgetProps(entity: Entity, props: Readonly<Record<string, unknown>>): void;
68
88
  deleteSelection(): void;
69
89
  /** Twin spawns offset +16/+16; the clones become the selection. */
70
90
  duplicateSelection(): Entity[];
71
91
  setSelection(ids: readonly Entity[], mode?: "replace" | "add" | "toggle"): void;
72
92
  clearSelection(): void;
73
93
  selectAll(): void;
74
- /** StackZ sweep, one tx: selection (or ids) to top/bottom. */
94
+ /** Sibling-sequence sweep, one tx: the ids to the frame top/bottom (petition 8). */
75
95
  reorder(ids: readonly Entity[], mode: "top" | "bottom"): void;
96
+ /**
97
+ * Desktop-style Clean Up (kernel packLayout): tidy reading-order rows, one
98
+ * undo step, 240ms glide (durationMs: 0 snaps). Scope: ids > selection ≥2 >
99
+ * current nav frame. Returns the movers (empty = already tidy).
100
+ */
101
+ arrange(opts?: ArrangeOpts): Entity[];
76
102
  zoomToFit(ids?: readonly Entity[]): void;
103
+ /**
104
+ * The natural DEFAULT framing (2026-07-18): zoom-to-fit the current
105
+ * frame's widgets inside the FIT_DEFAULTS band (never past 100%, never
106
+ * below 50%, ∩ settings.zoom) — same band as the folder arrival camera.
107
+ * Returns false (camera untouched) until a real viewport AND content
108
+ * exist, so boot code can retry. zoomToFit stays the uncapped explicit
109
+ * "show me everything".
110
+ */
111
+ frameContent(): boolean;
77
112
  zoomTo(zoom: number, anchor?: {
78
113
  x: number;
79
114
  y: number;
80
115
  }): void;
81
116
  panTo(x: number, y: number): void;
82
- enterContainer(container: Entity): void;
83
- exitContainer(): void;
117
+ enterContainer(container: Entity, opts?: NavOpts): void;
118
+ exitContainer(opts?: NavOpts): void;
119
+ /** Pop several levels in ONE transition (breadcrumb jumps; design-006 §5). */
120
+ exitTo(targetDepth: number, opts?: NavOpts): void;
84
121
  cancelActiveGestures(): void;
85
122
  }
86
123
  export interface CanvasDocs {
87
124
  create(opts?: DocSessionOpts): DocSession;
88
125
  open(bytes: Uint8Array, opts?: DocSessionOpts): OpenDocResult;
89
- /** §6.5 bootstrap + optional presence sugar over the same channel. */
90
- join(channel: ByteChannel, opts?: Omit<JoinDocOpts, "presence"> & {
126
+ /**
127
+ * §6.5 bootstrap + optional presence sugar over the same channel. `signal`/
128
+ * `onSession` are facade-owned (supersede + re-bootstrap wiring) — track the
129
+ * live session through `docs.current()`. Rejects if another document is
130
+ * opened while the join is in flight.
131
+ */
132
+ join(channel: ByteChannel, opts?: Omit<JoinDocOpts, "presence" | "signal" | "onSession"> & {
91
133
  readonly presence?: {
92
134
  name: string;
93
135
  color: string;
94
136
  };
95
137
  }): Promise<JoinResult>;
96
138
  current(): DocSession | undefined;
139
+ /**
140
+ * The live presence session (the `join({presence})` sugar's attachment), or
141
+ * undefined when doc-less / presence-less. An INSPECTION seam — the devtools
142
+ * dock's ephemeral tab reads `presence().eph` (DevtoolsOpts.presence) —
143
+ * never a sync path; same contract as `DocSession.attachment`. Swaps with
144
+ * the doc lifecycle exactly like `current()`: set by join, cleared by close.
145
+ */
146
+ presence(): PresenceSession | undefined;
97
147
  close(): void;
98
148
  undo(): boolean;
99
149
  redo(): boolean;
100
150
  autosave(storage: AutosaveStorageWrite, opts?: Omit<AutosaveOpts, "storage" | "world">): Autosave;
101
151
  }
152
+ /**
153
+ * Stage presentation control (StageMode resource, 2026-07-19): app chrome
154
+ * that covers/recedes the canvas takes a refcounted, NAMED background hold —
155
+ * the disposer pattern makes overlay lifecycles leak-proof (release on
156
+ * unmount), the name makes "why isn't the canvas animating?" answerable.
157
+ * Rendering policy only — input/interactability stays the overlay's concern.
158
+ */
159
+ export interface StageControl {
160
+ /** Take a named background hold. Returns an IDEMPOTENT release. */
161
+ background(name: string): () => void;
162
+ isBackgrounded(): boolean;
163
+ /** Live hold names, in take order (debug/devtools). */
164
+ holds(): readonly string[];
165
+ }
102
166
  export interface CanvasEngine {
103
167
  readonly world: World;
104
168
  readonly engine: Engine;
@@ -109,6 +173,15 @@ export interface CanvasEngine {
109
173
  readonly nav: NestedCanvas;
110
174
  readonly ops: CanvasOps;
111
175
  readonly docs: CanvasDocs;
176
+ readonly stage: StageControl;
177
+ /**
178
+ * The frame gate (2026-08-04) — `stage`'s stricter sibling. A background
179
+ * hold is presentation policy over a LIVE world; a freeze stops the host
180
+ * loop outright. Reach for `stage` when chrome RECEDES the canvas, `frame`
181
+ * when chrome COVERS it. Passthrough of `engine.frame`, so there is one gate
182
+ * whether you hold the facade or the raw engine.
183
+ */
184
+ readonly frame: FrameControl;
112
185
  readonly budgets: {
113
186
  readonly keepMounted: number;
114
187
  readonly fboBytes: number;
@@ -11,7 +11,7 @@
11
11
  * Tags are not eligibility-checked in M2 (essential tags spawn with the
12
12
  * prefab; optional-tag modeling is future work — documented).
13
13
  */
14
- import type { Component, Entity, Relation, Resource, Tag, World } from "@vibecook/strata-ecs";
14
+ import type { Component, Entity, OrderPlace, Relation, Resource, Tag, World } from "@vibecook/strata-ecs";
15
15
  import type { Mutator } from "@vibecook/strata-ecs/durable";
16
16
  import { type ComponentInit, type Prefab } from "../schema/prefab";
17
17
  export interface GuardedTx {
@@ -26,9 +26,11 @@ export interface GuardedTx {
26
26
  };
27
27
  addTag(e: Entity, t: Tag): void;
28
28
  removeTag(e: Entity, t: Tag): void;
29
- setRelation(e: Entity, r: Relation, target: Entity): void;
29
+ setRelation(e: Entity, r: Relation, target: Entity, place?: OrderPlace): void;
30
30
  addRelation(e: Entity, r: Relation, target: Entity): void;
31
31
  removeRelation(e: Entity, r: Relation, target?: Entity): void;
32
+ /** Reorder within the current parent's sibling sequence — ordered relations only (petition 8). */
33
+ moveRelation(e: Entity, r: Relation, place: OrderPlace): void;
32
34
  setResource<S>(res: Resource<S>, v: S): void;
33
35
  }
34
36
  interface GuardedEditor {
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Churn guard — the `runIf` half of the petition-7 gating pattern
3
+ * (design-002 §4 "guards live in runIf, never the body" × strata 0.7.0
4
+ * `world.changes`).
5
+ *
6
+ * `activeMembership` gates by draining a collector INSIDE its tick body
7
+ * (legal there: it declares no writes, so an early-out stamps nothing).
8
+ * Systems that DECLARE `access.write` cannot use that shape — strata
9
+ * blanket-stamps declared writes for any system that RUNS, even one that
10
+ * early-outs (see helpers/version-stamps.ts) — so their gate must be a real
11
+ * `runIf`. But a naive collector-in-runIf would LOSE the drained delta when
12
+ * the body runs: this guard drains in `runIf`, stashes the delta, and hands
13
+ * it to the body via `take()` in the same frame.
14
+ *
15
+ * Contract:
16
+ * - `runIf` drains the collector every frame (collector creation is lazy —
17
+ * a guard whose system is never installed never journals) and evaluates
18
+ * the optional `extra` trigger (a value-compare closure, e.g. "camera
19
+ * zoom changed"; it is called every frame so it can keep its own cache
20
+ * current). Returns true iff there is work.
21
+ * - `take()` (body, same frame) returns the work: `full` means rebuild
22
+ * everything (first run, `world.reset`, coarse writes, or `extra` fired);
23
+ * otherwise `changed`/`removed` are the journaled entities. The arrays
24
+ * alias the collector's internal buffers — valid until the next drain,
25
+ * i.e. for the body that runs this frame; do not retain them.
26
+ */
27
+ import type { CollectOptions, Entity, World } from "@vibecook/strata-ecs";
28
+ export interface ChurnWork {
29
+ /** Rebuild everything: first run, reset, coarse writes, or `extra` fired. */
30
+ readonly full: boolean;
31
+ readonly changed: readonly Entity[];
32
+ readonly removed: readonly Entity[];
33
+ }
34
+ export interface ChurnGuard {
35
+ /** Drop into a system's `runIf`. */
36
+ readonly runIf: () => boolean;
37
+ /** The work stashed by this frame's `runIf` (undefined if it returned false). */
38
+ take(): ChurnWork | undefined;
39
+ }
40
+ export declare function makeChurnGuard(world: World, collect: CollectOptions, extra?: () => boolean): ChurnGuard;
@@ -3,10 +3,10 @@
3
3
  * scene systems · doc kit · publish step · engine facade · reflector registry.
4
4
  * Import wall: strata-ecs + @ice/kernel ONLY — never react/dom/three (enforced).
5
5
  */
6
- export declare const CORE_VERSION = "0.0.0";
6
+ export declare const CORE_VERSION = "0.1.0";
7
7
  export { defineSchemaOnce, hmrInvalidateOnSchemaChange } from "./boot/hmr";
8
8
  export { All, Any, Not, Related, createWorld, defineQuery, defineSystem, defineTickSystem, enumOf, field, Local, } from "@vibecook/strata-ecs";
9
- export type { Batch, Component, Condition, Entity, Pipeline, Query, Relation, Resource, System, SystemCtx, Tag, TickSystem, World, } from "@vibecook/strata-ecs";
9
+ export type { Batch, Component, Condition, Entity, OrderPlace, Pipeline, Query, Relation, Resource, System, SystemCtx, Tag, TickSystem, World, } from "@vibecook/strata-ecs";
10
10
  export { defineComponent, defineRelation, defineResource, defineTag, schemaMeta, type ComponentMeta, type RawSchema, type RelationMeta, type ResourceMeta, } from "./schema/meta";
11
11
  export { definePrefab, init, PrefabId, prefabs, type ComponentInit, type FieldWrite, type Prefab, type PrefabClass, type PrefabDef, } from "./schema/prefab";
12
12
  export { instantiate, type EphSpawner, type SpawnTarget } from "./engine/instantiate";
@@ -14,6 +14,7 @@ export { FrameInfo, setFrameInfo } from "./engine/frame-info";
14
14
  export { createPipelineRegistry, PHASE_GROUPS, type AnySystem, type PhaseGroup, type PipelineRegistry } from "./engine/pipeline";
15
15
  export { createReflectorRegistry, type ReflectorDef, type ReflectorObserve, type ReflectorQuerySpec, type ReflectorRegistry, } from "./engine/reflectors";
16
16
  export { createEngine, type Engine, type EngineOpts, type FrameTelemetry, type PublishHook, type SystemRunRecord, } from "./engine/engine";
17
+ export { createFrameControl, SETTLE_CAP, type FrameControl } from "./engine/frame-control";
17
18
  export { devGuardsEnabled, setDevGuards } from "./guards/dev";
18
19
  export { createLiveWriter, type LiveWriter, type LiveWriterOpts } from "./guards/live-writer";
19
20
  export { guardedTransaction, type GuardedTx, type GuardedTxOpts } from "./guards/guarded-tx";
@@ -21,14 +22,18 @@ export { writeRuntimeResource } from "./guards/resource-writer";
21
22
  export * from "./catalog";
22
23
  export * from "./helpers/phase-set";
23
24
  export * from "./helpers/version-stamps";
25
+ export * from "./ops/arrange";
24
26
  export * from "./ops/selection";
25
27
  export * from "./ops/cascade";
26
28
  export * from "./ops/claims";
27
29
  export * from "./ops/gestures";
28
30
  export * from "./ops/point-pick";
31
+ export * from "./ops/sibling-order";
32
+ export { portSlots, portSlotOf } from "./ops/port-geometry";
29
33
  export { createInputQueue, NO_MODS, type InputEvent, type InputEventKind, type InputMods, type InputQueue } from "./input/queue";
30
- export { createRecordingCommitSink, type CommitIntent, type CommitCreate, type CommitReparent, type CommitSink, type CommitWrite, } from "./engine/commit-sink";
34
+ export { createRecordingCommitSink, type CommitIntent, type CommitCreate, type CommitOrder, type CommitReparent, type CommitSink, type CommitWrite, } from "./engine/commit-sink";
31
35
  export { ensureCanvasSurface, installInteractionCore, installInteractionStack, type InteractionCore, type InteractionCoreOpts, type InteractionStack, } from "./interaction/install";
36
+ export { anyGestureNonTerminal, isMidGesture } from "./interaction/gesture-status";
32
37
  export type { MarqueeBuffer } from "./systems/l3-marquee";
33
38
  export { DEFAULT_SPAWN_PROFILES, type SingleKindName, type SpawnProfiles } from "./systems/l2-recognize";
34
39
  export { createCursorSync } from "./systems/l4-cursor";
@@ -45,19 +50,22 @@ export { runMigrations, type MigrationCtx, type MigrationOutcome } from "./doc/m
45
50
  export { p, type JsonShape, type PropSpec, type PropsDecl, type StandardSchemaV1 } from "./widget/props";
46
51
  export { WidgetEquipped, defineWidget, widgets, type SizeMode, type WidgetDef, type WidgetGroup, type WidgetInteraction, type WidgetPortDecl, type WidgetSurface, type WidgetType, } from "./widget/define-widget";
47
52
  export { createWidgetEquipSystem } from "./widget/equip";
48
- export { spawnWidget, widgetSpawnInits, type SpawnWidgetOpts } from "./widget/spawn";
53
+ export { attachSpawnParent, spawnWidget, widgetSpawnInits, type SpawnWidgetOpts } from "./widget/spawn";
54
+ export { setWidgetProps } from "./widget/set-props";
49
55
  export { createWidgetRuntime, installWidgetRuntime, type MountEntry, type WidgetMountStore, type WidgetRuntime, } from "./widget/mount-store";
50
56
  export * from "./settings/defaults";
57
+ export * from "./settings/ground-config";
51
58
  export { CameraInertia, createCameraSystems, type CameraSystems } from "./systems/camera-sim";
52
59
  export { restoreAutosave, startAutosave, type Autosave, type AutosaveOpts, type AutosaveState, type AutosaveStatus, type AutosaveStorageRead, type AutosaveStorageWrite, type CancelScheduled, type RestoreResult, } from "./doc/autosave";
53
60
  export { attachBroadcastRelay, type BroadcastRelay, type BroadcastRelayOpts, type RelayChannel, } from "./doc/broadcast-relay";
54
61
  export { createMeasureQueue, type MeasureEvent, type MeasureQueue } from "./input/measure-queue";
55
62
  export { createMeasureIngest } from "./systems/measure-ingest";
56
63
  export { createBreakpointSystem, createSelectionChromeSystem } from "./systems/chrome";
57
- export { createNestedCanvas, currentNavEntry, currentNavFrame, type NestedCanvas, type NestedCanvasOpts, } from "./nav/nested-canvas";
64
+ export { createActiveMembership, createNestedCanvas, currentNavEntry, currentNavFrame, type NavOpts, type NestedCanvas, type NestedCanvasOpts, } from "./nav/nested-canvas";
65
+ export { abortNavFlight, createNavFlight, navFlightActive, NavTransition, startNavFlight, } from "./systems/nav-flight";
58
66
  export { attachPresence, createPresencePublish, createRemoteCursorsSystem, DEFAULT_PRESENCE_TTL_MS, installPresence, type InstallPresenceOpts, type PresenceOpts, type PresencePublishOpts, type PresenceSession, } from "./presence";
59
- export { joinDoc, type BootstrapClock, type JoinDocOpts, type JoinResult, } from "./doc/bootstrap";
67
+ export { joinDoc, offerBase, type BootstrapClock, type JoinDocOpts, type JoinResult, } from "./doc/bootstrap";
60
68
  export { broadcastChannelByteChannel, webSocketByteChannel, type ByteChannel, type OwnedByteChannel, type WebSocketLike, } from "./doc/channels";
61
69
  export { createDrawTool, defineTool, tools, type CanvasDragRoute, type Tool, type ToolDef, type ToolGates, type ToolRoute, type WidgetDragRoute, } from "./tools/define-tool";
62
- export { createCanvasEngine, type CanvasDocs, type CanvasEngine, type CanvasEngineOpts, type CanvasOps, } from "./facade/create-canvas-engine";
70
+ export { createCanvasEngine, type CanvasDocs, type CanvasEngine, type CanvasEngineOpts, type StageControl, type CanvasOps, } from "./facade/create-canvas-engine";
63
71
  export { createDrawBehavior } from "./systems/l3-draw";
@@ -40,6 +40,17 @@ export interface InputEvent {
40
40
  };
41
41
  /** GL-island opt-out (pinned widget-event contract, design-002 §8): fact still lands, recognizers skip. */
42
42
  readonly surfaceHandled?: boolean;
43
+ /**
44
+ * Hover-time widget-boundary fact (design-002 §8 amendment, 2026-07-18): the
45
+ * pointer is currently over content that would opt a down out — a native
46
+ * interactive / `[data-canvas-interactive]` (DOM) or claim-capable island
47
+ * content (GL). Stamped on down/move by adapters that track it; ABSENT means
48
+ * "no hover information this event" (wheel, key, blur-cancel) — ingest keeps
49
+ * the pointer's `OverInteractive` tag unchanged, it never treats absent as
50
+ * false. Presentation-grade truth (cursor affordances), never a gesture gate:
51
+ * recognizers keep reading `surfaceHandled`/`HandledByWidget` only.
52
+ */
53
+ readonly overInteractive?: boolean;
43
54
  /**
44
55
  * Event timestamp (DOMHighResTimeStamp — the same clock rAF hands
45
56
  * `engine.step`). Gesture timing anchors on THIS, not the processing frame:
@@ -0,0 +1,13 @@
1
+ import type { World } from "@vibecook/strata-ecs";
2
+ /**
3
+ * Any recognizer NOT in a terminal phase ⇒ a gesture is in flight (mirrors the
4
+ * cleanup.ts terminal definition: Failed/Cancelled/Ended, plus Recognized-Tap).
5
+ */
6
+ export declare function anyGestureNonTerminal(world: World): boolean;
7
+ /**
8
+ * The full mid-interaction read: a live camera gesture (pan/zoom, which writes
9
+ * `Camera.gesturing` without a recognizer entity of its own) OR any recognizer
10
+ * still walking its phases. Either means runtime edits are outstanding and the
11
+ * durable transaction that will carry them has not been sealed yet.
12
+ */
13
+ export declare function isMidGesture(world: World): boolean;
@@ -7,10 +7,25 @@
7
7
  * (at root: no container ancestor at all). Children of a deeper container
8
8
  * are that container's content — not Active until you enter it.
9
9
  * - `activeMembership` (derive tick, before cull): recomputes membership and
10
- * flips Active/Visible/Culled CHANGE-ONLY. A nav switch is O(frame
11
- * content) tag flips — bounded, rare, migration-free. Boards without
12
- * containers converge to "everything Active" on the first pass and never
13
- * write again (M3–M7 demos unaffected).
10
+ * flips Active/Visible/Culled CHANGE-ONLY, gated per design-004 §7
11
+ * (`runIf: nav-change ∨ ChildOf churn`) as a drain-and-early-return tick
12
+ * body — the spatialSync shape (petition 7 ChangeCollector; a `runIf`
13
+ * cannot drain, and this system declares no writes so an early-out
14
+ * blanket-stamps nothing). Idle frames cost one drain + one nav compare.
15
+ * Full resweeps run on seed / reset / coarse / NAV CHANGE / container
16
+ * despawn; everything else is delta-driven: a journaled entity whose
17
+ * membership INPUTS (ChildOf edge, Container-ness) are unchanged is
18
+ * skipped (drag frames journal Position every tick — O(1) each), a real
19
+ * reparent/container-flip reclassifies its re-anchored subtree.
20
+ * MEMBERSHIP-CHURN SIGNAL (why Position is the reparent proxy): relations
21
+ * are not collector-journalable (strata CollectOptions = components +
22
+ * tags), but coordinates are FRAME-LOCAL (design-001 §5.1) — a reparent
23
+ * that does not rewrite Position into the new frame's space is already a
24
+ * bug, and every real path co-writes: the doc sink commits reparent +
25
+ * Position in ONE tx (doc-commit-sink.ts), and undo/redo/remote imports
26
+ * project both rows of that tx. A bare runtime `world.setRelation(e,
27
+ * ChildOf, …)` with no Position write on a pre-existing entity is OUTSIDE
28
+ * the contract and goes stale until the next resweep.
14
29
  * - Nav stack = SESSION entities: `NavEntry` rows carry NavDepth + NavCamera
15
30
  * (the camera to restore on exit) + NavFrame(entry→container). Root =
16
31
  * empty stack. Current frame = deepest live entry's NavFrame target.
@@ -23,11 +38,20 @@
23
38
  * container died (undo/remote — NavFrame edge auto-cleared), pop to the
24
39
  * nearest LIVE ancestor entry (skipping dead ones) or root, restore that
25
40
  * camera, rebuild. The recognizer-integrity pattern applied to nav.
26
- * - Camera memory: enter saves the live camera on the pushed entry
27
- * (NavCamera) and applies the container's `ContainerCamera` rider (else
28
- * zoom-to-fit over the frame's content, else identity); exit writes the
29
- * rider back (per-user view memory; lost on undo-despawn — accepted) and
30
- * restores the entry's NavCamera.
41
+ * - Camera policy (design-006, amending design-004 §7; James 2026-07-15):
42
+ * enter saves the live camera on the pushed entry (NavCamera) and ALWAYS
43
+ * lands on the DEFAULT framing — zoom-to-fit over the frame's content, else
44
+ * identity. The `ContainerCamera` rider is neither read nor written by the
45
+ * engine anymore (per-canvas view memory made re-entry unpredictable); the
46
+ * component stays in the catalog for userland. Exit restores the entry's
47
+ * NavCamera — "back where you came from" is the nav stack, not view memory.
48
+ * - Transitions (design-006 T1): with a live Viewport and `transition` not
49
+ * "none", enter/exit CUT exactly as before and then FLY — the camera snaps
50
+ * to the continuity-solved start and `navFlight` springs it to the arrival
51
+ * (systems/nav-flight.ts). `exitTo(depth)` pops multi-level in ONE flight
52
+ * through the composed portals (rebuilt from CURRENT container rects — a
53
+ * collaborator may have moved a card). Headless (no Viewport) or opted-out
54
+ * calls snap, preserving the pre-T1 behavior exactly.
31
55
  */
32
56
  import type { Entity, TickSystem, World } from "@vibecook/strata-ecs";
33
57
  import type { SpatialIndex } from "@ice/kernel";
@@ -35,11 +59,17 @@ import type { SpatialIndex } from "@ice/kernel";
35
59
  export declare function currentNavEntry(world: World): Entity | undefined;
36
60
  /** The container the user is inside, or undefined at root. */
37
61
  export declare function currentNavFrame(world: World): Entity | undefined;
62
+ /** Per-op transition choice (design-006 §3.1); default is the portal zoom. */
63
+ export interface NavOpts {
64
+ readonly transition?: "zoom" | "none";
65
+ }
38
66
  export interface NestedCanvas {
39
67
  readonly activeMembership: TickSystem;
40
68
  readonly navIntegrity: TickSystem;
41
- enterContainer(container: Entity): void;
42
- exitContainer(): void;
69
+ enterContainer(container: Entity, opts?: NavOpts): void;
70
+ exitContainer(opts?: NavOpts): void;
71
+ /** Pop to `targetDepth` in ONE transition (breadcrumb jumps; design-006 §5). */
72
+ exitTo(targetDepth: number, opts?: NavOpts): void;
43
73
  /** Depth of the nav stack (0 = root). */
44
74
  depth(): number;
45
75
  }
@@ -57,6 +87,12 @@ export interface NestedCanvasOpts {
57
87
  * The membership stamper alone — installed by `installWidgetRuntime` (before
58
88
  * cull) so EVERY app partitions correctly, containers or not: a board with no
59
89
  * containers converges to all-Active on the first pass and never writes again.
90
+ *
91
+ * Gated per design-004 §7 (see the file header): the tick body drains a
92
+ * petition-7 collector and early-returns on idle frames; membership work runs
93
+ * only on nav change or membership-input churn. Baseline before this gate
94
+ * (bench/membership-scale.test.ts, 2026-07-15): the full scan cost 19.0 ms
95
+ * PER IDLE FRAME at 100k widgets nested 8 deep.
60
96
  */
61
97
  export declare function createActiveMembership(world: World): TickSystem;
62
98
  export declare function createNestedCanvas(world: World, opts: NestedCanvasOpts): NestedCanvas;
@@ -0,0 +1,15 @@
1
+ import type { Entity, World } from "@vibecook/strata-ecs";
2
+ import type { DurableStore } from "@vibecook/strata-ecs/durable";
3
+ import type { LiveWriter } from "../guards/live-writer";
4
+ export interface ArrangeOpts {
5
+ /** Explicit scope; wins over selection/frame resolution. */
6
+ readonly ids?: readonly Entity[];
7
+ /** Min separation between packed widgets (default 24). */
8
+ readonly gutter?: number;
9
+ /** Row wrap width; default = packLayout's near-square derivation. */
10
+ readonly maxWidth?: number;
11
+ /** Glide duration; 0 snaps instantly (tests/headless). Default 240ms. */
12
+ readonly durationMs?: number;
13
+ }
14
+ /** Returns the entities that actually moved (empty = already tidy). */
15
+ export declare function arrangeWidgets(store: DurableStore, liveWriter: LiveWriter, world: World, opts?: ArrangeOpts): Entity[];
@@ -0,0 +1,62 @@
1
+ /**
2
+ * insertByDrag — the tray-adoption op (2026-07-19, James: the widget tray).
3
+ *
4
+ * Called from the app's tray-tile `pointerdown` (which `stopPropagation`s the
5
+ * native down — the sanctioned widget-content boundary, design-002 §8). Two
6
+ * event-time moves, then the ENGINE owns everything:
7
+ *
8
+ * 1. DRAFT-spawn the ghost (design-001 §3; instantiate `{ draft: true }`) —
9
+ * a runtime instance of the widget prefab centered under the pointer,
10
+ * equipped + Active + top-of-frame IMMEDIATELY (the equip system runs in
11
+ * `derive`, AFTER this tick's pick — the down below must find the ghost
12
+ * indexed and topmost in the SAME tick, so the op stamps what equip
13
+ * would). Two capability tags are deliberately withheld:
14
+ * - `LongPressDrag`: a tray insert is always instant-drag — nobody holds
15
+ * 500ms over a palette;
16
+ * - `SweepsContained`: a comment ghost at the tray's world point must not
17
+ * sweep the widgets resting under the tray into the insert.
18
+ * The projected twin gets the FULL tag set from the equip system.
19
+ *
20
+ * 2. Enqueue ONE synthetic un-flagged `down` (Law 2 — this op is an adapter
21
+ * here, it only enqueues; the GL router precedent). Next tick: spatialSync
22
+ * indexes the ghost, targeting resolves it topmost, the Drag recognizer
23
+ * captures it — and the ordinary L0–L4 stack runs the whole drag: lift,
24
+ * snap, drop targets, folder consume. moveBehavior's ghost terminals
25
+ * promote (one `create` tx) or fly the ghost home and reap it.
26
+ */
27
+ import type { Entity, World } from "@vibecook/strata-ecs";
28
+ import { type InputQueue } from "../input/queue";
29
+ export interface InsertByDragOpts {
30
+ /** The press point in canvas-container CSS px (the kernel screen space). */
31
+ readonly screenX: number;
32
+ readonly screenY: number;
33
+ /** Prop overrides for the eventual create (validated by widgetSpawnInits). */
34
+ readonly props?: Readonly<Record<string, unknown>>;
35
+ /** Adapter pointer id of the pressing pointer (default "mouse"). */
36
+ readonly pointerId?: string;
37
+ readonly device?: "mouse" | "touch" | "pen";
38
+ /** PointerEvent.buttons at the press (default 1 = primary). */
39
+ readonly buttons?: number;
40
+ /**
41
+ * Fly-back HOME in screen px (default: the press point). Apps that adopt
42
+ * the drag AWAY from the tray (the deferred-spawn proxy pattern — the ghost
43
+ * spawns at the tray-EXIT point) aim the cancel/reject return at the
44
+ * persistent chrome (the toolbar) instead of a spot in open canvas.
45
+ */
46
+ readonly home?: {
47
+ readonly x: number;
48
+ readonly y: number;
49
+ };
50
+ /**
51
+ * Proportional GRAB POINT (0–1 within the widget rect; default center).
52
+ * The grab point is the drag's INVARIANT (2026-07-19 polish): the spot
53
+ * under the finger in the tray tile stays the same spot of the spawned
54
+ * widget — the proxy→widget morph then composes as a pure scale about the
55
+ * cursor, with no re-anchoring snap.
56
+ */
57
+ readonly anchor?: {
58
+ readonly u: number;
59
+ readonly v: number;
60
+ };
61
+ }
62
+ export declare function insertByDrag(world: World, queue: InputQueue, type: string, opts: InsertByDragOpts): Entity;