@vibecook/ice 0.3.0 → 0.4.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.
- package/dist/{chunk-PKK6GLSA.js → chunk-3PHPWRB2.js} +133 -11
- package/dist/chunk-3PHPWRB2.js.map +1 -0
- package/dist/{chunk-5QY7HK6G.js → chunk-7LZAXQAO.js} +333 -51
- package/dist/chunk-7LZAXQAO.js.map +1 -0
- package/dist/{chunk-2GUYATUM.js → chunk-ENMRSITH.js} +31 -11
- package/dist/chunk-ENMRSITH.js.map +1 -0
- package/dist/devtools.js +1 -1
- package/dist/dom.js +16 -2
- package/dist/ground.js +1 -1
- package/dist/index.js +7 -1
- package/dist/r3f.js +3 -3
- package/dist/react.js +3 -3
- package/dist/types/packages/core/src/catalog/pointer.d.ts +11 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +10 -0
- package/dist/types/packages/core/src/doc/rename-sweep.d.ts +3 -0
- package/dist/types/packages/core/src/doc/rename.d.ts +7 -0
- package/dist/types/packages/core/src/doc/version-gate.d.ts +14 -1
- package/dist/types/packages/core/src/index.d.ts +1 -1
- package/dist/types/packages/core/src/input/queue.d.ts +9 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +12 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +61 -0
- package/dist/types/packages/dom/src/index.d.ts +2 -0
- package/dist/types/packages/dom/src/input-ownership.d.ts +52 -0
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +16 -3
- package/dist/types/packages/dom/src/widget-focus.d.ts +58 -0
- package/dist/types/packages/react/src/infinite-canvas.d.ts +20 -2
- package/dist/types/packages/react/src/keymap.d.ts +23 -4
- package/package.json +61 -27
- package/dist/chunk-2GUYATUM.js.map +0 -1
- package/dist/chunk-5QY7HK6G.js.map +0 -1
- package/dist/chunk-PKK6GLSA.js.map +0 -1
|
@@ -11,6 +11,32 @@ export interface WidgetPortDecl {
|
|
|
11
11
|
/** Compatibility keys (empty = connects to anything). */
|
|
12
12
|
readonly accepts?: readonly string[];
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* A prior durable id of this widget type (design-008 §3, petition I5). Docs
|
|
16
|
+
* written under the old id fold to this type at open (doc/rename.ts) and via
|
|
17
|
+
* the live zombie sweep (doc/rename-sweep.ts) — replicas converge through
|
|
18
|
+
* ordinary CRDT delivery, retiring offline byte surgery. Declares group-NAME
|
|
19
|
+
* continuity at the rename boundary (the pack runner's existing fence).
|
|
20
|
+
*/
|
|
21
|
+
export interface WidgetRename {
|
|
22
|
+
/** The old durable type id (`PrefabId.id` in pre-rename docs). */
|
|
23
|
+
readonly type: string;
|
|
24
|
+
/**
|
|
25
|
+
* The pack version old-build writers of `type` were at (default 1). Only
|
|
26
|
+
* the SWEEP consumes it — a late old-shape delivery carries no marker, so
|
|
27
|
+
* its values chain-fold from here; the open-path runner reads the version
|
|
28
|
+
* off the doc's own markers instead (design-008 §6.3).
|
|
29
|
+
*/
|
|
30
|
+
readonly atVersion?: number;
|
|
31
|
+
}
|
|
32
|
+
/** A rename registry entry (doc/rename runner + sweep input). */
|
|
33
|
+
export interface WidgetRenameEntry {
|
|
34
|
+
readonly widget: WidgetType;
|
|
35
|
+
readonly oldType: string;
|
|
36
|
+
readonly atVersion: number;
|
|
37
|
+
/** Legacy `<oldType>:<group>` components, index-aligned with `widget.groups`. */
|
|
38
|
+
readonly legacyGroups: readonly Component[];
|
|
39
|
+
}
|
|
14
40
|
export interface WidgetInteraction {
|
|
15
41
|
readonly selectable?: boolean;
|
|
16
42
|
readonly movable?: boolean;
|
|
@@ -26,6 +52,25 @@ export interface WidgetInteraction {
|
|
|
26
52
|
* membership — never reparenting). Default false.
|
|
27
53
|
*/
|
|
28
54
|
readonly sweepContained?: boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Keyboard claim (design-007 §3.1, petitions I1/I4). `"exclusive"`: while a
|
|
57
|
+
* node inside this widget holds browser focus, the engine keymap and the
|
|
58
|
+
* adapter's Space pan modifier stand down — keys flow to the widget's own
|
|
59
|
+
* handlers (the engine stops competing; it never delivers behavior). The
|
|
60
|
+
* dom-widgets reflector marks the host `data-canvas-keyboard` + `tabindex=-1`
|
|
61
|
+
* so a click anywhere in the widget acquires focus, and plain wheel over the
|
|
62
|
+
* widget's scrollable content cedes to native scroll (ctrl/pinch stays
|
|
63
|
+
* canvas zoom always). Default `"shared"` — today's behavior, byte-identical.
|
|
64
|
+
*/
|
|
65
|
+
readonly keyboard?: "shared" | "exclusive";
|
|
66
|
+
/**
|
|
67
|
+
* Escape ownership under an exclusive claim (design-007 §3.3 / §7-Q1).
|
|
68
|
+
* `"release"` (default): Escape is the engine-reserved release gesture — it
|
|
69
|
+
* blurs the widget; the NEXT Escape, with focus gone, cancels gestures.
|
|
70
|
+
* `"widget"`: even Escape flows to the widget (vim-grade terminals); release
|
|
71
|
+
* is click-away or `blurFocus()`. Ignored unless `keyboard: "exclusive"`.
|
|
72
|
+
*/
|
|
73
|
+
readonly keyboardEscape?: "release" | "widget";
|
|
29
74
|
}
|
|
30
75
|
export interface WidgetDef {
|
|
31
76
|
readonly type: string;
|
|
@@ -101,6 +146,13 @@ export interface WidgetDef {
|
|
|
101
146
|
* (its `provides` wins).
|
|
102
147
|
*/
|
|
103
148
|
readonly provides?: readonly string[];
|
|
149
|
+
/**
|
|
150
|
+
* Prior durable ids folded to this type (design-008; petition I5). Additive:
|
|
151
|
+
* registers read-only LEGACY group components under the old names so
|
|
152
|
+
* pre-rename cells project, gates pre-rename docs "migrate" instead of
|
|
153
|
+
* read-only, and arms the live zombie sweep on every writable session.
|
|
154
|
+
*/
|
|
155
|
+
readonly renamedFrom?: readonly WidgetRename[];
|
|
104
156
|
/**
|
|
105
157
|
* fromVersion → idempotent absolute transform (M9's `runMigrations` runs the
|
|
106
158
|
* chain at open; `prev`/return are flat prop records spanning every group).
|
|
@@ -146,6 +198,10 @@ export interface WidgetType {
|
|
|
146
198
|
readonly ports: readonly WidgetPortDecl[];
|
|
147
199
|
/** Runtime capability tags the equip system stamps at projection. */
|
|
148
200
|
readonly capabilityTags: readonly Tag[];
|
|
201
|
+
/** Keyboard claim (design-007): the dom layer reads THIS (registry truth), not the tag. */
|
|
202
|
+
readonly keyboard: "shared" | "exclusive";
|
|
203
|
+
/** Escape ownership under an exclusive claim ("release" = engine-reserved). */
|
|
204
|
+
readonly keyboardEscape: "release" | "widget";
|
|
149
205
|
readonly migrate: Readonly<Record<number, (prev: Record<string, unknown>) => Record<string, unknown>>>;
|
|
150
206
|
/** Normalized author preview component (opaque; react narrows). null = none declared. */
|
|
151
207
|
readonly previewComponent: unknown;
|
|
@@ -158,6 +214,11 @@ export declare const widgets: {
|
|
|
158
214
|
get(type: string): WidgetType | undefined;
|
|
159
215
|
all(): WidgetType[];
|
|
160
216
|
};
|
|
217
|
+
/** The rename registry (design-008 §3): old durable id → fold target. */
|
|
218
|
+
export declare const renames: {
|
|
219
|
+
get(oldType: string): WidgetRenameEntry | undefined;
|
|
220
|
+
all(): WidgetRenameEntry[];
|
|
221
|
+
};
|
|
161
222
|
export declare function defineWidget(def: WidgetDef): WidgetType;
|
|
162
223
|
/** TEST-ONLY wipe (mirrors __resetPrefabsForTests; not on the barrel). */
|
|
163
224
|
export declare function __resetWidgetsForTests(): void;
|
|
@@ -10,6 +10,8 @@ export { createGrayboxReflector } from "./reflectors/graybox";
|
|
|
10
10
|
export { createDomWidgetsReflector, type DomWidgetsHost, type DomWidgetsReflector, } from "./reflectors/dom-widgets";
|
|
11
11
|
export { startRafLoop } from "./loop";
|
|
12
12
|
export { attachPointerAdapter, type GLRoute, type GLRouteVerdict, type PointerAdapterOpts } from "./pointer-adapter";
|
|
13
|
+
export { CLAIM_OWNS_ESCAPE, KEYBOARD_CLAIM_ATTR, isEditableTarget, keyboardClaimOf, wheelCede, type KeyboardClaim, } from "./input-ownership";
|
|
14
|
+
export { FOCUS_PROXY_ATTR, attachWidgetFocus, type FocusHostLookup, type WidgetFocusHandle, } from "./widget-focus";
|
|
13
15
|
export { createCursorReflector } from "./reflectors/cursor";
|
|
14
16
|
export { createRemoteCursorsReflector, type RemoteCursorsReflector } from "./reflectors/remote-cursors";
|
|
15
17
|
export { createChromeReflector } from "./reflectors/chrome";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input-ownership predicates (design-007 §4 — one predicate FAMILY, not one
|
|
3
|
+
* predicate; petitions I1/I4). "Does the widget own input here" is answered
|
|
4
|
+
* per-surface at the right scope, sharing the DOM-walk mechanics:
|
|
5
|
+
*
|
|
6
|
+
* - `isEditableTarget` — the 4-class editable gate (keymap default + the
|
|
7
|
+
* adapter's Space-types fix). Kept narrow so UNDECLARED widgets behave
|
|
8
|
+
* exactly as before (design-007 F5).
|
|
9
|
+
* - `keyboardClaimOf` — the `data-canvas-keyboard` claim marker written by the
|
|
10
|
+
* dom-widgets reflector for `interaction.keyboard: "exclusive"` widgets.
|
|
11
|
+
* Read UNBOUNDED (page-global) on purpose: `document.activeElement` is one
|
|
12
|
+
* per page, so engine B's keymap must stand down while you type into engine
|
|
13
|
+
* A's claiming widget (design-007 F2 — only the DOM is page-global).
|
|
14
|
+
* - `wheelCede` — the NARROW, DYNAMIC scroll opt-out (design-007 §3.4/§4):
|
|
15
|
+
* plain wheel cedes to native scroll only when a scroller WITH ROOM in the
|
|
16
|
+
* wheel's dominant axis sits inside a claim-marked / `data-canvas-
|
|
17
|
+
* interactive` / editable subtree. At the scroll bounds the predicate turns
|
|
18
|
+
* false and the wheel falls through to canvas pan/zoom — the familiar
|
|
19
|
+
* nested-scroll feel — which also keeps scroll-chaining from ever reaching
|
|
20
|
+
* the page. Deliberately NOT the full `crossesInteractive` set: hovering a
|
|
21
|
+
* bare `<button>` must not kill canvas zoom (design-007 F4).
|
|
22
|
+
*
|
|
23
|
+
* The keymap (`@ice/react`) imports these — the two historically disagreeing
|
|
24
|
+
* guards now share one implementation (the I1 unification).
|
|
25
|
+
*/
|
|
26
|
+
/** The claim marker the dom-widgets reflector writes on an exclusive widget's host. */
|
|
27
|
+
export declare const KEYBOARD_CLAIM_ATTR = "data-canvas-keyboard";
|
|
28
|
+
/** Marker value for `keyboardEscape: "widget"` (the widget receives even Escape). */
|
|
29
|
+
export declare const CLAIM_OWNS_ESCAPE = "escape";
|
|
30
|
+
export interface KeyboardClaim {
|
|
31
|
+
/** The event-target chain crosses a claim marker (⇒ engine keyboard stands down). */
|
|
32
|
+
readonly claimed: boolean;
|
|
33
|
+
/** The claiming widget owns Escape too (`keyboardEscape: "widget"`). */
|
|
34
|
+
readonly ownsEscape: boolean;
|
|
35
|
+
}
|
|
36
|
+
/** The keymap's editable gate — exactly INPUT/TEXTAREA/SELECT/contentEditable. */
|
|
37
|
+
export declare function isEditableTarget(target: EventTarget | null): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Resolve the keyboard claim for an event target (keydown targets ARE
|
|
40
|
+
* `document.activeElement`, so this reads browser focus — the single source of
|
|
41
|
+
* truth, design-007 §2.1). Unbounded `closest` by design (see header).
|
|
42
|
+
*/
|
|
43
|
+
export declare function keyboardClaimOf(target: EventTarget | null): KeyboardClaim;
|
|
44
|
+
/**
|
|
45
|
+
* Should this plain (non-pinch) wheel be ceded to native widget scroll?
|
|
46
|
+
* Walks target → container: remembers any scroller-with-room, cedes on the
|
|
47
|
+
* first opt-out root at or above it. Walking up meets scrollers BEFORE their
|
|
48
|
+
* widget's marker (the marker sits on the host), so "a scroller inside an
|
|
49
|
+
* opt-out subtree" is exactly "saw a scroller by the time the marker matches".
|
|
50
|
+
* ctrl/pinch wheels never reach this — they are ALWAYS the canvas's zoom.
|
|
51
|
+
*/
|
|
52
|
+
export declare function wheelCede(target: EventTarget | null, container: HTMLElement, dx: number, dy: number): boolean;
|
|
@@ -13,9 +13,22 @@
|
|
|
13
13
|
* - `setPointerCapture` on down so a drag keeps delivering outside the element;
|
|
14
14
|
* - Space is the pan modifier (design-003 §4.4): tracked here and threaded onto
|
|
15
15
|
* every event's `mods.space`; keydown/keyup emit a `key` fact only when the
|
|
16
|
-
* modifier tuple actually changes
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* modifier tuple actually changes. Space's preventDefault/latch is OWNERSHIP-
|
|
17
|
+
* AWARE (design-007 §3.4, petition I1): into an editable it neither latches
|
|
18
|
+
* nor preventDefaults (Space must TYPE — the §1.6 live-bug fix); into a
|
|
19
|
+
* `data-canvas-keyboard` claim it cedes the pan SEMANTICS but keeps killing
|
|
20
|
+
* the page-scroll default (the key still reaches the widget's own listener —
|
|
21
|
+
* preventDefault is not stopPropagation); elsewhere it pans, as ever;
|
|
22
|
+
* - wheel: ctrl-wheel / trackpad pinch is ALWAYS `preventDefault` + the canvas
|
|
23
|
+
* zoom signal (`wheel.pinch`) — a browser page-zoom over a widget is
|
|
24
|
+
* catastrophic, claimed or not. A PLAIN wheel over a scroller-with-room
|
|
25
|
+
* inside a claim/`data-canvas-interactive`/editable subtree (`wheelCede`,
|
|
26
|
+
* design-007 §3.4/§3.5) is ceded: NO preventDefault (native scroll
|
|
27
|
+
* proceeds), the fact still lands flagged `wheelHandled` so ingest stamps
|
|
28
|
+
* the one-tick `WheelHandled` and the wheel recognizers skip the tick. At
|
|
29
|
+
* the scroll bounds the predicate flips and the wheel falls through to
|
|
30
|
+
* canvas pan/zoom. Everything else: `preventDefault`, dx/dy (deltaMode
|
|
31
|
+
* lines → px ×16) — unchanged;
|
|
19
32
|
* - window blur enqueues a synthetic `cancel` for every pointer still down
|
|
20
33
|
* (design-003 §8) — the browser will not send the pointerups.
|
|
21
34
|
*
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The focus driver — acquisition + programmatic focus for keyboard-claiming
|
|
3
|
+
* widgets (design-007 §2.3–§2.5, petition I1).
|
|
4
|
+
*
|
|
5
|
+
* The single source of truth is BROWSER focus: `document.activeElement` inside
|
|
6
|
+
* a `data-canvas-keyboard` subtree is what makes every engine keyboard surface
|
|
7
|
+
* stand down (input-ownership.ts). This driver's whole job is to make that
|
|
8
|
+
* state reachable — it drives `.focus()`, it never mirrors focus into the
|
|
9
|
+
* world (there is no Focus resource; design-007 §2.2 demoted it, and despawn
|
|
10
|
+
* integrity is free: removing the node resets `activeElement`).
|
|
11
|
+
*
|
|
12
|
+
* Acquisition is DOM-at-event-time, not a world system: for DOM widgets the
|
|
13
|
+
* browser's own event targeting IS the pick (the down target sits inside the
|
|
14
|
+
* claiming host), and for GL widgets the claim lives on the DOM-chrome host
|
|
15
|
+
* under the pointer (the GL plane is `pointer-events:none`, so the down lands
|
|
16
|
+
* on the chrome — design-007 §2.5's proxy path). A capture-phase listener
|
|
17
|
+
* keeps ordering deterministic across browsers (Safari's click-focus quirks):
|
|
18
|
+
*
|
|
19
|
+
* - down on a natively-focusable node inside the claim (textarea, canvas
|
|
20
|
+
* with tabindex, the declared proxy itself) → the browser's own mousedown
|
|
21
|
+
* focus is correct; the driver does nothing.
|
|
22
|
+
* - down anywhere else inside the claim → the driver focuses the widget's
|
|
23
|
+
* declared `[data-canvas-focus]` proxy when one exists, else the host
|
|
24
|
+
* (the reflector gave it `tabindex="-1"`), with `preventScroll` — and
|
|
25
|
+
* `preventDefault()`s the pointerdown so the native focus fixup cannot
|
|
26
|
+
* fight the choice. Canceling a pointerdown suppresses only the compat
|
|
27
|
+
* mouse events (focus fixup + text selection — the container is already
|
|
28
|
+
* `user-select:none`); `click` still derives and the adapter's container
|
|
29
|
+
* listener has already enqueued the down fact (one input path intact).
|
|
30
|
+
* - down outside any claim → untouched; the browser blurs to body/other
|
|
31
|
+
* content on its own (click-away release, design-007 §2.4).
|
|
32
|
+
*
|
|
33
|
+
* Release stays where the design put it: Escape in the keymap (blur), lifecycle
|
|
34
|
+
* via node removal, window blur deliberately does NOT force-blur (the browser
|
|
35
|
+
* restores focus on refocus — adapter's cancelAllInput keeps its hands off).
|
|
36
|
+
*/
|
|
37
|
+
import type { Entity } from "@ice/core";
|
|
38
|
+
import type { CanvasHost } from "./host";
|
|
39
|
+
/** The widget-declared focus target (a proxy descendant): `<div data-canvas-focus tabIndex={-1}>`. */
|
|
40
|
+
export declare const FOCUS_PROXY_ATTR = "data-canvas-focus";
|
|
41
|
+
/** Resolves an entity to its host's content element (the dom-widgets reflector's `hostFor`). */
|
|
42
|
+
export interface FocusHostLookup {
|
|
43
|
+
hostFor(entity: Entity): HTMLElement | undefined;
|
|
44
|
+
}
|
|
45
|
+
export interface WidgetFocusHandle {
|
|
46
|
+
/**
|
|
47
|
+
* Programmatically focus a claiming widget (its proxy, else its host).
|
|
48
|
+
* Returns false when the entity has no mounted host or no keyboard claim —
|
|
49
|
+
* only `keyboard: "exclusive"` widgets are focusable (declaration drives
|
|
50
|
+
* focusability; design-007 §3.1).
|
|
51
|
+
*/
|
|
52
|
+
focusWidget(entity: Entity): boolean;
|
|
53
|
+
/** Blur whatever claim currently holds focus. False when none does. */
|
|
54
|
+
blurFocus(): boolean;
|
|
55
|
+
/** Remove the acquisition listener. */
|
|
56
|
+
detach(): void;
|
|
57
|
+
}
|
|
58
|
+
export declare function attachWidgetFocus(host: CanvasHost, lookup: FocusHostLookup): WidgetFocusHandle;
|
|
@@ -36,13 +36,21 @@
|
|
|
36
36
|
* `measureQueue` prop (ResizeObserver side). Absent ⇒ measurement is skipped.
|
|
37
37
|
*/
|
|
38
38
|
import { type CanvasEngine, type GridConfig, type MeasureQueue, type ReflectorDef, type WirePreviewBuffer, type World } from "@ice/core";
|
|
39
|
-
import { type CanvasHost, type GLRoute, type Planes } from "@ice/dom";
|
|
39
|
+
import { type CanvasHost, type GLRoute, type Planes, type WidgetFocusHandle } from "@ice/dom";
|
|
40
40
|
import { type CSSProperties, type ReactElement, type ReactNode } from "react";
|
|
41
|
+
import { type KeymapEntry } from "./keymap";
|
|
41
42
|
/** Handed to {@link InfiniteCanvasProps.onReady} for app-side GL/devtools wiring. */
|
|
42
43
|
export interface InfiniteCanvasHandle {
|
|
43
44
|
readonly engine: CanvasEngine;
|
|
44
45
|
readonly host: CanvasHost;
|
|
45
46
|
readonly planes: Planes;
|
|
47
|
+
/**
|
|
48
|
+
* Programmatic widget focus (design-007 §2.3): `focusWidget(entity)` /
|
|
49
|
+
* `blurFocus()` for `keyboard: "exclusive"` widgets. Focus is VIEW state
|
|
50
|
+
* (design-007 §2.6) — it rides this handle, not `engine.ops` (a headless
|
|
51
|
+
* engine correctly has no focus concept).
|
|
52
|
+
*/
|
|
53
|
+
readonly focus: WidgetFocusHandle;
|
|
46
54
|
}
|
|
47
55
|
/**
|
|
48
56
|
* STRUCTURAL mirror of `@ice/ground`'s GroundLayer/GroundFactory (the
|
|
@@ -97,9 +105,19 @@ export interface InfiniteCanvasProps {
|
|
|
97
105
|
* re-attaching the adapter.
|
|
98
106
|
*/
|
|
99
107
|
readonly glRoute?: GLRoute;
|
|
108
|
+
/**
|
|
109
|
+
* Keymap overrides plumbed to {@link attachKeymap} (design-007 §5 M-d — the
|
|
110
|
+
* declared alternative to capture-phase `stopPropagation` folklore; retires
|
|
111
|
+
* the widgetlab C-key hack). An entry replaces a default by its
|
|
112
|
+
* `key|mod|shift` signature; conditional behavior belongs INSIDE `run`
|
|
113
|
+
* (read engine state there — e.g. selection → comment, else the default's
|
|
114
|
+
* action). BOUND ONCE at mount, like the adapter: entries should read live
|
|
115
|
+
* state from the engine at run time, never close over render-time values.
|
|
116
|
+
*/
|
|
117
|
+
readonly keymapOverrides?: readonly KeymapEntry[];
|
|
100
118
|
readonly className?: string;
|
|
101
119
|
readonly style?: CSSProperties;
|
|
102
120
|
/** Overlays inside the viewport (toolbars, HUD) — rendered under the EngineProvider. */
|
|
103
121
|
readonly children?: ReactNode;
|
|
104
122
|
}
|
|
105
|
-
export declare function InfiniteCanvas({ engine, measureQueue, onReady, ground, grid: gridConfig, glRoute, className, style, children, }: InfiniteCanvasProps): ReactElement;
|
|
123
|
+
export declare function InfiniteCanvas({ engine, measureQueue, onReady, ground, grid: gridConfig, glRoute, keymapOverrides, className, style, children, }: InfiniteCanvasProps): ReactElement;
|
|
@@ -13,13 +13,32 @@
|
|
|
13
13
|
* tool shortcuts → ops.setTool (v/h/c + any registered tool's shortcut)
|
|
14
14
|
*
|
|
15
15
|
* Space-hold pan is ALREADY owned by the pointer adapter (design-003 §2) — it is
|
|
16
|
-
* NOT rebound here.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* NOT rebound here. `mod` = ⌘ on macOS, Ctrl elsewhere (metaKey || ctrlKey).
|
|
17
|
+
*
|
|
18
|
+
* The widget input contract (design-007, petition I1) — three gates, in order:
|
|
19
|
+
* 1. `event.defaultPrevented` → the widget's own handler consumed the key
|
|
20
|
+
* ("preventDefault = handled by content"); the engine never competes.
|
|
21
|
+
* 2. The `data-canvas-keyboard` claim marker on the event-target chain (the
|
|
22
|
+
* keydown target IS `document.activeElement` — browser focus is the one
|
|
23
|
+
* page-global truth, so two engines on one page stand down together) →
|
|
24
|
+
* EVERY entry cedes except Escape, the engine-reserved release gesture:
|
|
25
|
+
* Escape blurs the claim (the NEXT press, with focus gone, falls through
|
|
26
|
+
* to cancelActiveGestures). A widget that declared `keyboardEscape:
|
|
27
|
+
* "widget"` receives even Escape — release is click-away or blurFocus().
|
|
28
|
+
* This gate runs BEFORE the editable gate on purpose (2026-08-09 review):
|
|
29
|
+
* a claim whose focus node is an editable proxy (the Ghosttea-style
|
|
30
|
+
* hidden textarea, design-007 §6) must still get the Escape release —
|
|
31
|
+
* editable-first would shadow it and silently turn every such widget
|
|
32
|
+
* into `keyboardEscape: "widget"`.
|
|
33
|
+
* 3. Editable target (input/textarea/select/contentEditable — the shared
|
|
34
|
+
* 4-class predicate from @ice/dom) → ignored, so typing never triggers a
|
|
35
|
+
* shortcut. Kept narrow so UNDECLARED widgets behave exactly as before.
|
|
19
36
|
*
|
|
20
37
|
* Overrides replace a default by its key-signature `key|mod|shift`; a signature
|
|
21
38
|
* with no default is added. A matched entry `preventDefault()`s (the target was
|
|
22
|
-
* already filtered to non-editable, so no typing is swallowed).
|
|
39
|
+
* already filtered to non-editable, so no typing is swallowed). An override on
|
|
40
|
+
* Space warns at attach: the pointer adapter owns Space (the pan modifier,
|
|
41
|
+
* design-003 §4.4) and preventDefaults it before gate 1 can let it through.
|
|
23
42
|
*/
|
|
24
43
|
import { type CanvasEngine } from "@ice/core";
|
|
25
44
|
export interface KeymapEntry {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibecook/ice",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "ICE — infinite canvas engine. Figma-grade infinite-canvas UX as a framework: defineWidget turns React/R3F components into canvas citizens — selectable, movable, resizable, wired into node graphs, CRDT-synced, undoable per gesture.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "James Yong <me@jamesyong42.com>",
|
|
@@ -26,25 +26,47 @@
|
|
|
26
26
|
"three",
|
|
27
27
|
"gestures"
|
|
28
28
|
],
|
|
29
|
-
"files": [
|
|
29
|
+
"files": [
|
|
30
|
+
"dist"
|
|
31
|
+
],
|
|
30
32
|
"main": "./dist/index.js",
|
|
31
33
|
"types": "./dist/types/packages/ice/src/index.d.ts",
|
|
32
34
|
"exports": {
|
|
33
|
-
".": {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
"./
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/types/packages/ice/src/index.d.ts",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./kernel": {
|
|
40
|
+
"types": "./dist/types/packages/ice/src/kernel.d.ts",
|
|
41
|
+
"default": "./dist/kernel.js"
|
|
42
|
+
},
|
|
43
|
+
"./dom": {
|
|
44
|
+
"types": "./dist/types/packages/ice/src/dom.d.ts",
|
|
45
|
+
"default": "./dist/dom.js"
|
|
46
|
+
},
|
|
47
|
+
"./react": {
|
|
48
|
+
"types": "./dist/types/packages/ice/src/react.d.ts",
|
|
49
|
+
"default": "./dist/react.js"
|
|
50
|
+
},
|
|
51
|
+
"./r3f": {
|
|
52
|
+
"types": "./dist/types/packages/ice/src/r3f.d.ts",
|
|
53
|
+
"default": "./dist/r3f.js"
|
|
54
|
+
},
|
|
55
|
+
"./ground": {
|
|
56
|
+
"types": "./dist/types/packages/ice/src/ground.d.ts",
|
|
57
|
+
"default": "./dist/ground.js"
|
|
58
|
+
},
|
|
59
|
+
"./devtools": {
|
|
60
|
+
"types": "./dist/types/packages/ice/src/devtools.d.ts",
|
|
61
|
+
"default": "./dist/devtools.js"
|
|
62
|
+
},
|
|
40
63
|
"./package.json": "./package.json"
|
|
41
64
|
},
|
|
42
|
-
"engines": {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
"
|
|
47
|
-
"prepack": "pnpm run build"
|
|
65
|
+
"engines": {
|
|
66
|
+
"node": ">=20"
|
|
67
|
+
},
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
48
70
|
},
|
|
49
71
|
"dependencies": {
|
|
50
72
|
"@vibecook/strata-ecs": "0.11.0",
|
|
@@ -59,18 +81,20 @@
|
|
|
59
81
|
"three": ">=0.160.0"
|
|
60
82
|
},
|
|
61
83
|
"peerDependenciesMeta": {
|
|
62
|
-
"@react-three/fiber": {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
"
|
|
84
|
+
"@react-three/fiber": {
|
|
85
|
+
"optional": true
|
|
86
|
+
},
|
|
87
|
+
"react": {
|
|
88
|
+
"optional": true
|
|
89
|
+
},
|
|
90
|
+
"react-dom": {
|
|
91
|
+
"optional": true
|
|
92
|
+
},
|
|
93
|
+
"three": {
|
|
94
|
+
"optional": true
|
|
95
|
+
}
|
|
66
96
|
},
|
|
67
97
|
"devDependencies": {
|
|
68
|
-
"@ice/core": "workspace:*",
|
|
69
|
-
"@ice/devtools": "workspace:*",
|
|
70
|
-
"@ice/dom": "workspace:*",
|
|
71
|
-
"@ice/kernel": "workspace:*",
|
|
72
|
-
"@ice/r3f": "workspace:*",
|
|
73
|
-
"@ice/react": "workspace:*",
|
|
74
98
|
"@react-three/fiber": "^9",
|
|
75
99
|
"@types/rbush": "^3.0.3",
|
|
76
100
|
"@types/react": "^19",
|
|
@@ -78,6 +102,16 @@
|
|
|
78
102
|
"@types/three": "^0.185.0",
|
|
79
103
|
"react": "^19",
|
|
80
104
|
"react-dom": "^19",
|
|
81
|
-
"three": "^0.185.0"
|
|
105
|
+
"three": "^0.185.0",
|
|
106
|
+
"@ice/devtools": "0.0.0",
|
|
107
|
+
"@ice/dom": "0.0.0",
|
|
108
|
+
"@ice/core": "0.0.0",
|
|
109
|
+
"@ice/react": "0.0.0",
|
|
110
|
+
"@ice/kernel": "0.0.0",
|
|
111
|
+
"@ice/r3f": "0.0.0"
|
|
112
|
+
},
|
|
113
|
+
"scripts": {
|
|
114
|
+
"typecheck": "tsc --noEmit",
|
|
115
|
+
"build": "tsup && tsc -p tsconfig.dts.json"
|
|
82
116
|
}
|
|
83
|
-
}
|
|
117
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../react/src/hooks.ts","../../react/src/widget-root.tsx","../../react/src/engine-context.tsx","../../react/src/use-commit.ts","../../react/src/preview-snapshots.ts","../../react/src/widget-preview.tsx","../../react/src/use-tool.ts","../../react/src/use-presence.ts","../../react/src/keymap.ts","../../react/src/infinite-canvas.tsx","../../react/src/index.ts"],"sourcesContent":["/**\n * React hooks over the engine's Tier-3 reactivity (design-005 §2 hooks;\n * design-004 §2 frozen-hidden rule).\n *\n * Implemented directly on `world.reactive.observeValue` + `useSyncExternalStore`\n * rather than strata's own react module: core is react-free by law, so\n * strata/react cannot be re-exported through it — and the adapter is ~20\n * lines. Value semantics are strata Tier-3: equality-suppressed, fired at\n * notify(), never during the tick.\n *\n * FROZEN-HIDDEN: a widget inside a hidden (culled-but-kept-mounted) host must\n * not re-render on doc edits — `useWidgetProps` reads {@link WidgetHiddenContext}\n * (provided per-portal by WidgetRoot) and suspends its subscription while\n * hidden, serving the last snapshot; on show it resubscribes and refreshes.\n */\nimport {\n Selected,\n WidgetBreakpoint,\n defineQuery,\n widgets,\n type Component,\n type Entity,\n type World,\n} from \"@ice/core\";\nimport { createContext, useCallback, useContext, useMemo, useRef, useSyncExternalStore } from \"react\";\n\n/** Per-portal hidden flag (WidgetRoot provides it; defaults to live). */\nexport const WidgetHiddenContext = createContext(false);\n\nconst selectedQ = defineQuery([Selected]);\n\n/** Shallow field equality — strata cells are flat field records. */\nfunction shallowEq(a: unknown, b: unknown): boolean {\n if (Object.is(a, b)) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n const ka = Object.keys(a);\n const kb = Object.keys(b);\n if (ka.length !== kb.length) return false;\n for (const k of ka) {\n if (!Object.is((a as Record<string, unknown>)[k], (b as Record<string, unknown>)[k])) return false;\n }\n return true;\n}\n\n/**\n * Tier-3 component value subscription (equality-suppressed). Snapshot\n * STABILITY is load-bearing: strata's `get()` materializes a fresh object per\n * read (SoA columns), and `useSyncExternalStore` treats a new reference as a\n * new snapshot — so reads go through a shallow-equality cache and only a real\n * field change yields a new reference.\n */\nexport function useWorldComponent<S>(world: World, entity: Entity, component: Component<S>): S | undefined {\n const hidden = useContext(WidgetHiddenContext);\n const last = useRef<S | undefined>(undefined);\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (hidden) return () => {}; // frozen while hidden — no wakeups, no renders\n return world.reactive.observeValue(entity, component, onChange);\n },\n [world, entity, component, hidden],\n );\n const getSnapshot = useCallback(() => {\n if (hidden) return last.current; // stable snapshot while frozen\n const v = world.isAlive(entity) ? world.get(entity, component) : undefined;\n if (!shallowEq(v, last.current)) last.current = v;\n return last.current;\n }, [world, entity, component, hidden]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/**\n * Typed props for one conflict group of a widget (design-005 §2). Group\n * component values come back whole; `p.json` fields are parsed. Identity is\n * stable per underlying group value (memo on the raw reference).\n */\nexport function useWidgetProps<T extends Record<string, unknown>>(\n world: World,\n entity: Entity,\n type: string,\n group = \"props\",\n): T | undefined {\n const widget = widgets.get(type);\n const g = widget?.groups.find((x) => x.name === group);\n if (widget === undefined || g === undefined) {\n throw new Error(`ice: useWidgetProps — unknown widget type/group \"${type}\"/\"${group}\".`);\n }\n const raw = useWorldComponent(world, entity, g.component) as Record<string, unknown> | undefined;\n return useMemo(() => {\n if (raw === undefined) return undefined;\n const out: Record<string, unknown> = {};\n for (const [name, spec] of Object.entries(g.fields)) {\n const v = raw[name];\n out[name] = spec.kind === \"json\" && typeof v === \"string\" ? parseJsonCell(v, spec.default) : v;\n }\n return out as T;\n }, [raw, g]);\n}\n\n/**\n * A json cell is one CRDT string a remote peer may have written malformed —\n * decoding must never throw mid-render (2026-07-13 review). Fall back to the\n * spec's serialized default (engine-derived, always valid JSON), then null.\n */\nfunction parseJsonCell(cell: string, serializedDefault: string | undefined): unknown {\n try {\n return JSON.parse(cell);\n } catch {\n try {\n return JSON.parse(serializedDefault ?? \"null\");\n } catch {\n return null;\n }\n }\n}\n\n/**\n * Selection membership (Tier-1 wake on Selected churn; O(1) snapshot).\n * Frozen-hidden like every widget hook: a selection change must not re-render\n * 256 hidden trees (review finding — design-004 §2 applies to ALL hooks).\n */\nexport function useSelected(world: World, entity: Entity): boolean {\n const hidden = useContext(WidgetHiddenContext);\n const last = useRef(false);\n const subscribe = useCallback(\n (onChange: () => void) => {\n if (hidden) return () => {};\n return world.reactive.observeQuery(selectedQ, onChange);\n },\n [world, hidden],\n );\n const getSnapshot = useCallback(() => {\n if (hidden) return last.current;\n last.current = world.isAlive(entity) && world.hasTag(entity, Selected);\n return last.current;\n }, [world, entity, hidden]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n\n/** LOD tier (design-004 §8); \"normal\" until the breakpoint system stamps one. */\nexport function useBreakpoint(world: World, entity: Entity): string {\n const bp = useWorldComponent(world, entity, WidgetBreakpoint);\n return (bp as { tier?: string } | undefined)?.tier ?? \"normal\";\n}\n","/**\n * WidgetRoot — portals from ONE app root (design-004 §2).\n *\n * Portal-list membership (mount/unmount/evict) is owned HERE, by React,\n * subscribed to the engine's mount store via `useSyncExternalStore`; the\n * imperative dom-widgets reflector owns only the host DIVs. Eviction is\n * decided engine-side (LRU derive) and merely consumed. Each mounted entry\n * portals its widget type's component (from the defineWidget registry) into\n * the host's content element; hidden entries stay mounted (React state\n * preserved — cull ≠ unmount) inside a `WidgetHiddenContext` so their prop\n * subscriptions freeze (design-004 §2).\n *\n * A host div may not exist yet on the very first snapshot after mount (the\n * host reflector runs in the same post-notify pass, ordered by reflector\n * registration) — entries without a host render nothing this pass and pick\n * up on the next notification.\n */\nimport { PrefabId, widgets, type Entity, type World, type WidgetMountStore } from \"@ice/core\";\nimport { createElement, type ComponentType, type ReactElement } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useSyncExternalStore } from \"react\";\nimport { WidgetHiddenContext } from \"./hooks\";\n\nexport interface WidgetHosts {\n /** The dom-widgets reflector's lookup (content element per entity). */\n hostFor(entity: Entity): HTMLElement | undefined;\n}\n\nexport interface WidgetRootProps {\n readonly world: World;\n readonly store: WidgetMountStore;\n readonly hosts: WidgetHosts;\n}\n\nexport interface WidgetComponentProps {\n readonly entity: Entity;\n readonly world: World;\n}\n\nexport function WidgetRoot({ world, store, hosts }: WidgetRootProps): ReactElement {\n const entries = useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);\n\n const portals: ReactElement[] = [];\n for (const entry of entries) {\n if (!world.isAlive(entry.entity)) continue;\n const type = world.get(entry.entity, PrefabId)?.id;\n const widget = typeof type === \"string\" ? widgets.get(type) : undefined;\n if (widget === undefined) continue;\n // dom widgets portal their component; gl widgets portal their optional\n // DOM CHROME — the host div lives in the content plane (P1), which stacks\n // UNDER the GL canvas (P2), so the card body renders beneath the island's\n // 3D content (v1's CardChrome sandwich — shared CSS with DOM cards).\n const view = widget.surface === \"dom\" ? widget.component : widget.chrome;\n if (view == null) continue;\n const target = hosts.hostFor(entry.entity);\n if (target === undefined) continue; // host lands next pass\n const View = view as ComponentType<WidgetComponentProps>;\n portals.push(\n createPortal(\n createElement(\n WidgetHiddenContext.Provider,\n { value: entry.hidden },\n createElement(View, { entity: entry.entity, world }),\n ),\n target,\n String(entry.entity),\n ),\n );\n }\n return createElement(\"div\", { \"data-ice-widget-root\": \"\" }, portals);\n}\n","/**\n * `<EngineProvider>` + engine access hooks (design-005 §5; the M6 field\n * amendment 2026-07-10 gap — grep \"useCommit\" in design-005).\n *\n * The whole React surface reaches the running canvas through ONE context\n * carrying the {@link CanvasEngine} facade (world + engine + stack + runtime +\n * ops + docs). Widgets never hand-roll store plumbing again: {@link useOps}\n * gives the engine-owned write paths, {@link useCommit} (its own module) the\n * sanctioned durable commit seam, and {@link useWorld} the read/observe escape\n * hatch. `<InfiniteCanvas>` provides this automatically; apps that mount\n * WidgetRoot themselves wrap it in `<EngineProvider>`.\n */\nimport { devGuardsEnabled, type CanvasEngine, type CanvasOps, type World } from \"@ice/core\";\nimport { createContext, useContext, useEffect, type ReactElement, type ReactNode } from \"react\";\n\nconst EngineContext = createContext<CanvasEngine | null>(null);\n\nexport interface EngineProviderProps {\n readonly engine: CanvasEngine;\n readonly children?: ReactNode;\n}\n\n/** Publish the canvas engine to the React subtree (WidgetRoot + userland hooks). */\nexport function EngineProvider({ engine, children }: EngineProviderProps): ReactElement {\n return <EngineContext.Provider value={engine}>{children}</EngineContext.Provider>;\n}\n\n/** The canvas engine for this subtree; throws with guidance outside a provider. */\nexport function useCanvasEngine(): CanvasEngine {\n const engine = useContext(EngineContext);\n if (engine === null) {\n throw new Error(\n \"ice: useCanvasEngine — no <EngineProvider>. Render widgets inside <InfiniteCanvas> (which provides it) or wrap your tree in <EngineProvider engine={...}>.\",\n );\n }\n return engine;\n}\n\n/** The engine-owned write paths (design-005 §4): setTool, deleteSelection, etc. */\nexport function useOps(): CanvasOps {\n return useCanvasEngine().ops;\n}\n\n/**\n * Take a stage background hold while `active` (ce.stage, 2026-07-19): the\n * overlay-lifecycle sugar — the disposer releases on deactivate AND unmount,\n * so a crashed/unmounted overlay can never wedge the canvas backgrounded.\n * `engine` is an explicit param (not context): overlays typically render as\n * SIBLINGS of <InfiniteCanvas>, outside the provider.\n */\nexport function useStageHold(engine: CanvasEngine, active: boolean, name: string): void {\n useEffect(() => {\n if (!active) return undefined;\n return engine.stage.background(name);\n }, [engine, active, name]);\n}\n\n/**\n * Freeze the engine while `active` (ce.frame, 2026-08-04) — {@link useStageHold}'s\n * stricter sibling, same lifecycle sugar: the effect cleanup IS the thaw, so an\n * overlay that unmounts (or crashes) can never wedge the engine parked.\n *\n * WHICH ONE. A stage hold quiets discretionary rendering over a world that\n * keeps stepping — right for chrome that RECEDES the canvas, where undo,\n * collab and tweens must still land visibly behind a partial overlay. A freeze\n * stops the loop outright — right for chrome that COVERS the canvas, where\n * every frame behind it is work nobody can see. Reaching for a freeze under a\n * partial overlay will look broken: the canvas is a photograph until thaw.\n */\nexport function useFrameFreeze(engine: CanvasEngine, active: boolean, name: string): void {\n useEffect(() => {\n if (!active) return undefined;\n return engine.frame.freeze(name);\n }, [engine, active, name]);\n}\n\nlet warnedUseWorld = false;\n\n/**\n * Escape hatch — the raw {@link World} for READS and Tier-3 OBSERVATION only.\n * Mutation goes through {@link useOps}/{@link useCommit} (a gesture-claimed,\n * one-tx-per-gesture durable write; direct world writes bypass sovereignty and\n * the guards). DEV-warns once on first use.\n */\nexport function useWorld(): World {\n const engine = useCanvasEngine();\n if (devGuardsEnabled() && !warnedUseWorld) {\n warnedUseWorld = true;\n console.warn(\n \"ice: useWorld() is a READ/OBSERVE escape hatch — never write to the world directly. Use useOps()/useCommit() for mutation (design-005 §5).\",\n );\n }\n return engine.world;\n}\n","/**\n * `useCommit` + `useUndoStatus` — the sanctioned widget durable-write path and\n * the undo/redo status subscription (design-005 §2 M6 field amendment,\n * 2026-07-10: \"widgets also need a SANCTIONED commit path\").\n *\n * A widget commits a prop edit through {@link useCommit}: it resolves the\n * CURRENT doc session (`engine.docs.current()`) and runs a\n * {@link guardedTransaction} against that session's store — one transaction per\n * call, eligibility-guarded, absolute writes only. The session is resolved at\n * CALL time (not hook time) so a widget rendered doc-less that gains a doc later\n * commits correctly without re-mounting.\n */\nimport { DurableUndoStatus, guardedTransaction, type GuardedTx, type GuardedTxOpts } from \"@ice/core\";\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { useCanvasEngine } from \"./engine-context\";\n\n/** A durable commit: run `fn` against the current doc session's store, one tx. */\nexport type Commit = (fn: (tx: GuardedTx) => void, opts?: GuardedTxOpts) => void;\n\n/**\n * The widget commit seam. The returned function throws with guidance when no\n * document is attached (commits need a durable store — call\n * `engine.docs.create()/open()/join()` first).\n */\nexport function useCommit(): Commit {\n const engine = useCanvasEngine();\n return useCallback(\n (fn, opts) => {\n const session = engine.docs.current();\n if (session === undefined) {\n throw new Error(\n \"ice: useCommit — no active document. Attach one with engine.docs.create()/open()/join() before committing widget edits.\",\n );\n }\n if (session.readOnly) {\n // The version gate said read-only: a raw store write here would edit —\n // and broadcast — a document the gate refused (2026-07-13 review).\n throw new Error(\"ice: useCommit — the document is read-only (version gate); widget edits are disabled.\");\n }\n guardedTransaction(session.store, engine.world, fn, opts);\n },\n [engine],\n );\n}\n\nexport interface UndoStatus {\n readonly canUndo: boolean;\n readonly canRedo: boolean;\n}\n\nconst NO_UNDO: UndoStatus = { canUndo: false, canRedo: false };\n\n/**\n * Undo/redo availability for the current document (drives toolbar\n * disablement) — the design-005 §5 path: the `DurableUndoStatus` RESOURCE\n * (strata plan-undo U2, re-exported through @ice/core) via\n * `observeResource`. A resource subscription is WORLD-level, so it survives\n * document swaps — no remount needed, the earlier onHistoryChange-per-store\n * caveat is gone. Fires at notify(), equality-suppressed; snapshot identity\n * is stable while the flags are unchanged.\n */\nexport function useUndoStatus(): UndoStatus {\n const engine = useCanvasEngine();\n const last = useRef<UndoStatus>(NO_UNDO);\n const subscribe = useCallback(\n (onChange: () => void) => engine.world.reactive.observeResource(DurableUndoStatus, onChange),\n [engine],\n );\n const getSnapshot = useCallback(() => {\n const status = engine.world.getResource(DurableUndoStatus);\n const canUndo = status?.canUndo ?? false;\n const canRedo = status?.canRedo ?? false;\n if (last.current.canUndo !== canUndo || last.current.canRedo !== canRedo) {\n last.current = { canUndo, canRedo };\n }\n return last.current;\n }, [engine]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n","/**\n * The GL preview snapshot store (design-005 §2 preview contract, P2 —\n * 2026-07-19). react stays three-free by LAW, so the capture pipeline lives\n * in @ice/r3f and meets `<WidgetPreview>` HERE, at a dumb data seam: a\n * module-level map of widget type → captured image, with subscription so an\n * open palette re-renders the moment a capture lands.\n *\n * `PreviewImage` accepts a canvas alongside ImageBitmap so tests (and any\n * app-side prebuilt-cache policy — the P3 seam) can inject without a GPU.\n */\nexport type PreviewImage = ImageBitmap | HTMLCanvasElement;\n\nconst images = new Map<string, PreviewImage>();\nconst listeners = new Set<() => void>();\n\nexport function setPreviewSnapshot(type: string, image: PreviewImage): void {\n images.set(type, image);\n for (const l of listeners) l();\n}\n\nexport function getPreviewSnapshot(type: string): PreviewImage | undefined {\n return images.get(type);\n}\n\nexport function hasPreviewSnapshot(type: string): boolean {\n return images.has(type);\n}\n\nexport function subscribePreviewSnapshots(listener: () => void): () => void {\n listeners.add(listener);\n return () => listeners.delete(listener);\n}\n\n/** TEST-ONLY wipe (not on the barrel). */\nexport function __resetPreviewSnapshotsForTests(): void {\n images.clear();\n}\n","/**\n * `<WidgetPreview>` — the framework answer to \"give me the preview for type\n * X\" (design-005 §2 amendment, 2026-07-19). Palettes/trays stay app chrome\n * (design-004); THIS is the seam they consume, owning the whole fallback\n * chain so every app and every `defineWidget` author gets the same behavior:\n *\n * 1. the def's DECLARED preview (engine-free by contract — no world, no\n * entity, no ops; authored at `defaultSize`, scaled here);\n * 2. dom surfaces with no declaration: the REAL component with default (or\n * `preview.props`-curated) props, mounted against the framework's\n * internal preview sandbox — truthful by construction;\n * 3. the `fallback` prop (the app's silhouette), else a neutral placeholder.\n *\n * Every branch renders INERT (`inert` + pointer-events none) inside an error\n * boundary — a broken preview degrades to the fallback, it never kills the\n * palette. GL surfaces without a declared preview go straight to fallback in\n * P1 (the r3f snapshot pipeline is P2; a DOM mockup declaration is the\n * sanctioned escape hatch meanwhile).\n *\n * The sandbox: one lazy module-level engine (own in-memory doc, no adapters,\n * never stepped past two settle ticks) spawning ONE entity per previewed\n * type ON DEMAND. Preview entities never receive Selected/Grab/overlap tags,\n * so chrome renders permanently at rest — \"static\" is a property of the\n * sandbox, not a discipline widget authors must follow.\n */\nimport {\n createCanvasEngine,\n widgets,\n type CanvasEngine,\n type Entity,\n type WidgetType,\n type World,\n} from \"@ice/core\";\nimport {\n Component,\n useEffect,\n useRef,\n useSyncExternalStore,\n type ComponentType,\n type CSSProperties,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { EngineProvider } from \"./engine-context\";\nimport { getPreviewSnapshot, subscribePreviewSnapshots, type PreviewImage } from \"./preview-snapshots\";\n\n// --- the preview sandbox -----------------------------------------------------\n\ninterface PreviewSandbox {\n readonly ce: CanvasEngine;\n readonly entities: Map<string, Entity>;\n}\n\nlet sandbox: PreviewSandbox | null = null;\n\nfunction ensurePreviewEntity(def: WidgetType): Entity | undefined {\n if (sandbox === null) {\n const ce = createCanvasEngine();\n ce.docs.create();\n ce.world.sync();\n ce.step(16);\n sandbox = { ce, entities: new Map() };\n }\n const existing = sandbox.entities.get(def.type);\n if (existing !== undefined) return existing;\n try {\n const e = sandbox.ce.ops.spawnWidget(def.type, {\n x: 0,\n y: 0,\n undoable: false,\n ...(def.previewProps !== undefined ? { props: def.previewProps } : {}),\n });\n sandbox.ce.world.sync();\n sandbox.ce.step(32); // settle tick: equip stamps land before first render\n sandbox.entities.set(def.type, e);\n return e;\n } catch (err) {\n console.warn(`ice: WidgetPreview(\"${def.type}\") sandbox spawn failed — falling back.`, err);\n return undefined;\n }\n}\n\n/** TEST-ONLY sandbox wipe (mirrors __resetWidgetsForTests; not on the barrel). */\nexport function __resetPreviewSandboxForTests(): void {\n sandbox?.ce.dispose();\n sandbox = null;\n}\n\n// --- error containment ---------------------------------------------------------\n\nclass PreviewBoundary extends Component<{ fallback: ReactNode; children: ReactNode }, { failed: boolean }> {\n override state = { failed: false };\n static getDerivedStateFromError(): { failed: boolean } {\n return { failed: true };\n }\n override componentDidCatch(error: unknown): void {\n console.warn(\"ice: a widget preview threw while rendering — showing the fallback.\", error);\n }\n override render(): ReactNode {\n return this.state.failed ? this.props.fallback : this.props.children;\n }\n}\n\n// --- the host ------------------------------------------------------------------\n\nexport interface WidgetPreviewProps {\n readonly type: string;\n /** The available box; the preview renders aspect-FITTED inside it. */\n readonly width: number;\n readonly height: number;\n /** App silhouette shown when no preview path applies (or one throws). */\n readonly fallback?: ReactNode;\n readonly className?: string;\n readonly style?: CSSProperties;\n}\n\n/** Draws a captured snapshot (P2 pipeline / injected cache) at natural size. */\nfunction SnapshotLayer({ image }: { image: PreviewImage }): ReactElement {\n const ref = useRef<HTMLCanvasElement>(null);\n useEffect(() => {\n const canvas = ref.current;\n if (canvas === null) return;\n canvas.width = image.width;\n canvas.height = image.height;\n try {\n canvas.getContext(\"2d\")?.drawImage(image, 0, 0);\n } catch {\n // a torn-down ImageBitmap or a contextless test DOM — the layer stays blank\n }\n }, [image]);\n return (\n <canvas\n ref={ref}\n data-preview-snapshot=\"\"\n style={{ position: \"absolute\", inset: 0, width: \"100%\", height: \"100%\" }}\n />\n );\n}\n\nfunction defaultPlaceholder(label: string): ReactElement {\n return (\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n borderRadius: 12,\n background: \"rgba(127, 127, 127, 0.12)\",\n boxShadow: \"inset 0 0 0 1px rgba(127, 127, 127, 0.25)\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: 11,\n color: \"rgba(127, 127, 127, 0.8)\",\n overflow: \"hidden\",\n }}\n >\n {label}\n </div>\n );\n}\n\nexport function WidgetPreview({ type, width, height, fallback, className, style }: WidgetPreviewProps): ReactElement {\n // Subscribed BEFORE any early return (hook order): an open palette\n // re-renders the moment the P2 capture pipeline lands this type's snapshot.\n const snapshot = useSyncExternalStore(subscribePreviewSnapshots, () => getPreviewSnapshot(type));\n const def = widgets.get(type);\n const safeFallback = fallback ?? defaultPlaceholder(type);\n if (def === undefined || def.defaultSize.w <= 0 || def.defaultSize.h <= 0) {\n return (\n <div className={className} style={{ width, height, ...style }}>\n {safeFallback}\n </div>\n );\n }\n\n const s = Math.min(width / def.defaultSize.w, height / def.defaultSize.h);\n const fitW = def.defaultSize.w * s;\n const fitH = def.defaultSize.h * s;\n\n // The inert, natural-size stage every preview branch renders on: authors\n // (and the real component) write at defaultSize; the host scales.\n const stage = (content: ReactNode): ReactElement => (\n <div className={className} style={{ width: fitW, height: fitH, overflow: \"hidden\", ...style }}>\n <div\n inert\n style={{\n width: def.defaultSize.w,\n height: def.defaultSize.h,\n transform: `scale(${s})`,\n transformOrigin: \"0 0\",\n pointerEvents: \"none\",\n userSelect: \"none\",\n }}\n >\n <PreviewBoundary fallback={safeFallback}>{content}</PreviewBoundary>\n </div>\n </div>\n );\n\n // 1. Author-declared preview: engine-free by contract — rendered bare.\n if (def.previewComponent !== null && def.previewComponent !== undefined) {\n const Declared = def.previewComponent as ComponentType;\n return stage(<Declared />);\n }\n\n // 2. gl surface with a captured snapshot (the P2 pipeline / an injected\n // cache): the widget's DOM chrome (if any) renders UNDERNEATH via the\n // sandbox — the live card's chrome-beneath-canvas sandwich, reproduced —\n // and the transparent 3D capture layers on top. ONE capture serves both\n // themes: the 3D content is theme-independent; theme lives in the chrome's\n // CSS.\n if (def.surface === \"gl\" && snapshot !== undefined) {\n let chromeLayer: ReactNode = null;\n if (def.chrome !== null && def.chrome !== undefined) {\n const entity = ensurePreviewEntity(def);\n if (entity !== undefined && sandbox !== null) {\n const Chrome = def.chrome as ComponentType<{ entity: Entity; world: World }>;\n chromeLayer = (\n <EngineProvider engine={sandbox.ce}>\n <div style={{ position: \"absolute\", inset: 0 }}>\n <Chrome entity={entity} world={sandbox.ce.world} />\n </div>\n </EngineProvider>\n );\n }\n }\n return stage(\n <div style={{ position: \"relative\", width: \"100%\", height: \"100%\" }}>\n {chromeLayer}\n <SnapshotLayer image={snapshot} />\n </div>,\n );\n }\n\n // 3. dom surface: the real component + default/curated props in the sandbox.\n if (def.surface === \"dom\" && def.component !== null && def.component !== undefined) {\n const entity = ensurePreviewEntity(def);\n if (entity !== undefined && sandbox !== null) {\n const View = def.component as ComponentType<{ entity: Entity; world: World }>;\n return stage(\n <EngineProvider engine={sandbox.ce}>\n <View entity={entity} world={sandbox.ce.world} />\n </EngineProvider>,\n );\n }\n }\n\n // 4. No path (gl before its capture lands, broken spawn): the fallback.\n return (\n <div className={className} style={{ width: fitW, height: fitH, ...style }}>\n {safeFallback}\n </div>\n );\n}\n","/**\n * `useTool` + `useToolState` — the active-tool subscription (design-005 §5).\n *\n * The active tool is the `ActiveTool` runtime resource (design-005 §4). Reads go\n * through strata's Tier-3 `observeResource` (fires only when the id actually\n * changes, at notify()); the setter is `ops.setTool`, which cancels active\n * gestures before the switch (the strata-example lesson, design-003 §8) — never\n * a raw resource write. The snapshot is a primitive string, so identity is\n * inherently stable.\n */\nimport { ActiveTool } from \"@ice/core\";\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { useCanvasEngine } from \"./engine-context\";\n\n/** `[activeToolId, setTool]`. `setTool` routes through `ops.setTool`. */\nexport function useTool(): [string, (id: string) => void] {\n const engine = useCanvasEngine();\n const subscribe = useCallback(\n (onChange: () => void) => engine.world.reactive.observeResource(ActiveTool, onChange),\n [engine],\n );\n const getSnapshot = useCallback(() => engine.world.getResource(ActiveTool)?.id ?? \"select\", [engine]);\n const active = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n const setTool = useCallback((id: string) => engine.ops.setTool(id), [engine]);\n return [active, setTool];\n}\n\n/** Sugar: is tool `id` the active one? (toolbar button pressed-state). */\nexport function useToolState(id: string): boolean {\n const [active] = useTool();\n return active === id;\n}\n","/**\n * `usePresencePeers` — the remote-collaborator roster (design-005 §5:\n * \"Not(Local) presence query\").\n *\n * Queries `PresencePeer` entities that are NOT `Local` (the local peer carries\n * `Local`, auto-applied by the ephemeral store and never transmitted) and pairs\n * each with its `PresenceInfo` (name/color). Wakes on Tier-1 query membership\n * churn (`observeQuery`) — peers joining/leaving. The snapshot is\n * MEMBERSHIP-KEYED: identity stays stable across unrelated re-renders, and info\n * is sampled when membership changes (steady-state presence info is set once at\n * join; a mid-session rename reflects on the next membership change).\n */\nimport { Local, Not, PresenceInfo, PresencePeer, defineQuery, type Entity } from \"@ice/core\";\nimport { useCallback, useRef, useSyncExternalStore } from \"react\";\nimport { useCanvasEngine } from \"./engine-context\";\n\nconst remotePeersQ = defineQuery([PresencePeer, Not(Local)]);\n\nexport interface PresencePeerView {\n readonly entity: Entity;\n /** The peer's `PresenceInfo` (strata \"string\" fields are nullable until set). */\n readonly info: { readonly name: string | null; readonly color: string | null } | undefined;\n}\n\n/** Remote presence peers (excludes the local peer); stable identity by membership. */\nexport function usePresencePeers(): readonly PresencePeerView[] {\n const engine = useCanvasEngine();\n const cache = useRef<{ key: string; value: readonly PresencePeerView[] }>({ key: \"\u0000\", value: [] });\n const subscribe = useCallback(\n (onChange: () => void) => engine.world.reactive.observeQuery(remotePeersQ, onChange),\n [engine],\n );\n const getSnapshot = useCallback(() => {\n const world = engine.world;\n const ids: Entity[] = [];\n world.query(remotePeersQ).each((b) => {\n for (const r of b) ids.push(b.entity(r));\n });\n const key = ids.join(\",\");\n if (key !== cache.current.key) {\n cache.current = {\n key,\n value: ids.map((entity) => ({ entity, info: world.get(entity, PresenceInfo) })),\n };\n }\n return cache.current.value;\n }, [engine]);\n return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);\n}\n","/**\n * The default keymap (design-005 §4 \"Keymap\"; design-003 §2 — NO system-side\n * keyboard logic, every entry resolves to an `ops.*`/`docs.*` call).\n *\n * Locked defaults:\n * ⌫/Delete → ops.deleteSelection\n * ⌘Z / Ctrl-Z → docs.undo\n * ⇧⌘Z / ⇧Ctrl-Z → docs.redo\n * ⌘D → ops.duplicateSelection\n * ⌘A → ops.selectAll\n * Esc → ops.cancelActiveGestures\n * Arrows → nudge the selection ±1px (⇧ = ±10px) — ONE tx per press\n * tool shortcuts → ops.setTool (v/h/c + any registered tool's shortcut)\n *\n * Space-hold pan is ALREADY owned by the pointer adapter (design-003 §2) — it is\n * NOT rebound here. A keystroke is IGNORED when the event target is editable\n * (input/textarea/select/contenteditable) so typing into widget fields never\n * triggers a shortcut. `mod` = ⌘ on macOS, Ctrl elsewhere (metaKey || ctrlKey).\n *\n * Overrides replace a default by its key-signature `key|mod|shift`; a signature\n * with no default is added. A matched entry `preventDefault()`s (the target was\n * already filtered to non-editable, so no typing is swallowed).\n */\nimport { Position, guardedTransaction, selectedEntities, tools, type CanvasEngine } from \"@ice/core\";\n\nexport interface KeymapEntry {\n /** `event.key` to match (case-insensitive; e.g. \"z\", \"Backspace\", \"ArrowUp\"). */\n readonly key: string;\n /** Require ⌘/Ctrl (default false → must NOT be held). */\n readonly mod?: boolean;\n /** Require ⇧ (default false → must NOT be held). */\n readonly shift?: boolean;\n /** Resolve to an engine write path — no keyboard logic beyond dispatch. */\n run(engine: CanvasEngine): void;\n}\n\ntype KeyTarget = Pick<Window, \"addEventListener\" | \"removeEventListener\">;\n\nconst signature = (key: string, mod?: boolean, shift?: boolean): string =>\n `${key.toLowerCase()}|${mod ? 1 : 0}|${shift ? 1 : 0}`;\n\nfunction isEditableTarget(target: EventTarget | null): boolean {\n if (target === null || !(target instanceof HTMLElement)) return false;\n const tag = target.tagName;\n return tag === \"INPUT\" || tag === \"TEXTAREA\" || tag === \"SELECT\" || target.isContentEditable;\n}\n\n/** One nudge = one gesture-equivalent transaction (absolute Position writes). */\nfunction nudgeSelection(engine: CanvasEngine, dx: number, dy: number): void {\n const session = engine.docs.current();\n if (session === undefined) return;\n const selection = selectedEntities(engine.world).filter((e) => session.store.keyOf(e) !== undefined);\n if (selection.length === 0) return;\n guardedTransaction(session.store, engine.world, (tx) => {\n for (const e of selection) {\n const p = engine.world.get(e, Position);\n if (p === undefined) continue;\n tx.edit(e).set(Position, { x: p.x + dx, y: p.y + dy });\n }\n });\n}\n\nconst ARROWS: readonly [key: string, dx: number, dy: number][] = [\n [\"ArrowLeft\", -1, 0],\n [\"ArrowRight\", 1, 0],\n [\"ArrowUp\", 0, -1],\n [\"ArrowDown\", 0, 1],\n];\n\n/** The locked defaults + tool shortcuts (read from the registry at attach time). */\nfunction defaultEntries(): KeymapEntry[] {\n const entries: KeymapEntry[] = [\n { key: \"Backspace\", run: (e) => e.ops.deleteSelection() },\n { key: \"Delete\", run: (e) => e.ops.deleteSelection() },\n { key: \"z\", mod: true, run: (e) => e.docs.undo() },\n { key: \"z\", mod: true, shift: true, run: (e) => e.docs.redo() },\n { key: \"d\", mod: true, run: (e) => e.ops.duplicateSelection() },\n { key: \"a\", mod: true, run: (e) => e.ops.selectAll() },\n { key: \"Escape\", run: (e) => e.ops.cancelActiveGestures() },\n ];\n for (const [key, dx, dy] of ARROWS) {\n entries.push({ key, run: (e) => nudgeSelection(e, dx, dy) });\n entries.push({ key, shift: true, run: (e) => nudgeSelection(e, dx * 10, dy * 10) });\n }\n for (const tool of tools.all()) {\n if (tool.shortcut !== undefined) {\n const id = tool.id;\n entries.push({ key: tool.shortcut, run: (e) => e.ops.setTool(id) });\n }\n }\n return entries;\n}\n\nfunction resolveTarget(target?: KeyTarget): KeyTarget | undefined {\n if (target !== undefined) return target;\n return typeof window !== \"undefined\" ? window : undefined;\n}\n\n/**\n * Attach the default keymap (plus `overrides`) to `target` (default: `window`).\n * Returns a detach function. No-op (returns a no-op detach) when there is no\n * target — e.g. a headless environment with no `window`.\n */\nexport function attachKeymap(\n engine: CanvasEngine,\n target?: KeyTarget,\n overrides: readonly KeymapEntry[] = [],\n): () => void {\n const el = resolveTarget(target);\n if (el === undefined) return () => {};\n\n const map = new Map<string, KeymapEntry>();\n for (const entry of defaultEntries()) map.set(signature(entry.key, entry.mod, entry.shift), entry);\n for (const entry of overrides) map.set(signature(entry.key, entry.mod, entry.shift), entry);\n\n const onKeyDown = (event: KeyboardEvent): void => {\n if (isEditableTarget(event.target)) return;\n const mod = event.metaKey || event.ctrlKey;\n const entry = map.get(signature(event.key, mod, event.shiftKey));\n if (entry === undefined) return;\n event.preventDefault();\n entry.run(engine);\n };\n\n el.addEventListener(\"keydown\", onKeyDown as EventListener);\n return () => el.removeEventListener(\"keydown\", onKeyDown as EventListener);\n}\n","/**\n * `<InfiniteCanvas>` — the one-line canvas mount (design-005 §5): planes +\n * adapters + reflectors + portal root, wired in node-board's PROVEN boot order\n * (apps/nodeboard/src/app.tsx). The app supplies a constructed {@link\n * CanvasEngine} (via `createCanvasEngine`); this component owns only the DOM\n * host, the reflector registrations, the pointer/keymap adapters, the rAF loop,\n * and the viewport sync — everything that must live next to a real container.\n *\n * Boot order (registration order = reflector flush order, mirrored exactly):\n * host → planes → reflectors[ planeTransform · ground(P0, app-injected) ·\n * domWidgets(P1/P3) · chrome(P4) · cursor · remoteCursors(P5) ] →\n * pointer adapter → measurement (optional) → keymap → rAF loop → viewport RO.\n *\n * The dom-widgets reflector is BOTH a reflector and the host lookup WidgetRoot\n * portals into; it is created before mount and stored in state so WidgetRoot\n * renders once the hosts exist.\n *\n * Lifecycle: this component does NOT own the engine — it never calls\n * `engine.dispose()` (the engine outlives the mount; the app owns it). Unmount\n * detaches every adapter, unregisters every reflector, stops the loop, and\n * disposes the host.\n *\n * Not handled here (by design):\n * - GL / R3F: the `@ice/r3f` wall forbids `@ice/react` importing three, so a\n * GL layer (GLViews) mounts APP-SIDE. Use {@link onReady} to receive the host\n * + planes and mount it yourself (like apps/glboard).\n * - The GROUND layer (P0: dot grid, wires, snap guides — @ice/ground, three's\n * WebGPURenderer) rides the same wall: pass its factory through the OPAQUE\n * {@link InfiniteCanvasProps.ground} prop (`ground={ground({...})}`); this\n * component types it structurally and never imports the package. No factory\n * ⇒ no ground layer (headless/test boots).\n * - Devtools: the import wall forbids `@ice/react` importing `@ice/devtools`.\n * Wire the panel app-side against `engine.engine` (also via {@link onReady}).\n * - Measurement: auto-sized widgets need a `MeasureQueue` passed to BOTH\n * `createCanvasEngine({ measureQueue })` (ingest side) and this component's\n * `measureQueue` prop (ResizeObserver side). Absent ⇒ measurement is skipped.\n */\nimport {\n Viewport,\n writeRuntimeResource,\n type CanvasEngine,\n type GridConfig,\n type MeasureQueue,\n type ReflectorDef,\n type WirePreviewBuffer,\n type World,\n} from \"@ice/core\";\nimport {\n attachPointerAdapter,\n createCanvasHost,\n createChromeReflector,\n createCursorReflector,\n createDomWidgetsReflector,\n createPlaneTransformReflector,\n createPlanes,\n createRemoteCursorsReflector,\n startRafLoop,\n wireMeasurement,\n type CanvasHost,\n type DomWidgetsReflector,\n type GLRoute,\n type Planes,\n} from \"@ice/dom\";\nimport { useEffect, useRef, useState, type CSSProperties, type ReactElement, type ReactNode } from \"react\";\nimport { EngineProvider } from \"./engine-context\";\nimport { attachKeymap } from \"./keymap\";\nimport { WidgetRoot } from \"./widget-root\";\n\n/** Handed to {@link InfiniteCanvasProps.onReady} for app-side GL/devtools wiring. */\nexport interface InfiniteCanvasHandle {\n readonly engine: CanvasEngine;\n readonly host: CanvasHost;\n readonly planes: Planes;\n}\n\n/**\n * STRUCTURAL mirror of `@ice/ground`'s GroundLayer/GroundFactory (the\n * `WidgetDef.component`-style opaque seam: react never imports the package —\n * the wall forbids three here). `@ice/ground.ground(...)` returns a function\n * assignable to this type.\n */\nexport interface GroundLayerHandle {\n readonly reflector: ReflectorDef & { available(): boolean };\n configureGrid(cfg: Partial<GridConfig>): void;\n dispose(): void;\n}\n\nexport type GroundLayerFactory = (ctx: {\n readonly host: { readonly container: HTMLElement; readonly contentPlane: HTMLElement };\n readonly world: World;\n readonly readWirePreview: () => WirePreviewBuffer;\n}) => GroundLayerHandle;\n\nexport interface InfiniteCanvasProps {\n /** A constructed engine (`createCanvasEngine(...)`); NOT disposed on unmount. */\n readonly engine: CanvasEngine;\n /**\n * The ResizeObserver side of widget measurement. Pass the SAME queue given to\n * `createCanvasEngine({ measureQueue })`. Absent ⇒ measurement skipped.\n */\n readonly measureQueue?: MeasureQueue;\n /** Called once after the host/reflectors/loop are live (app-side GL/devtools). */\n readonly onReady?: (handle: InfiniteCanvasHandle) => void;\n /**\n * The P0 ground layer (dot grid, wires, snap guides — one WebGPU canvas).\n * Pass `ground(opts)` from `@ice/ground`; received opaquely (see\n * {@link GroundLayerFactory}). Memoize in the caller — a new identity\n * re-boots the canvas mount effect. Absent ⇒ no ground layer renders.\n */\n readonly ground?: GroundLayerFactory;\n /**\n * Dot-grid tuning (theme dot color, spacing, fades). Applied live via the\n * ground layer's `configureGrid` — changing it never re-boots the canvas\n * (memoize in the caller to avoid redundant same-value redraws). No-op\n * when {@link ground} is absent.\n */\n readonly grid?: Partial<GridConfig>;\n /**\n * GL pointer routing (event-time island pick — @ice/r3f's\n * createGLPointerRouter). Provide from the FIRST render (a stub delegating\n * to a ref is fine — the real router usually arrives in onReady); the prop\n * is read through a ref, so later identity changes take effect without\n * re-attaching the adapter.\n */\n readonly glRoute?: GLRoute;\n readonly className?: string;\n readonly style?: CSSProperties;\n /** Overlays inside the viewport (toolbars, HUD) — rendered under the EngineProvider. */\n readonly children?: ReactNode;\n}\n\nexport function InfiniteCanvas({\n engine,\n measureQueue,\n onReady,\n ground,\n grid: gridConfig,\n glRoute,\n className,\n style,\n children,\n}: InfiniteCanvasProps): ReactElement {\n const containerRef = useRef<HTMLDivElement>(null);\n const [hosts, setHosts] = useState<DomWidgetsReflector | undefined>(undefined);\n // Keep onReady out of the effect deps (identity churn must not re-boot).\n const onReadyRef = useRef(onReady);\n onReadyRef.current = onReady;\n // The live ground handle (set by the mount effect); the grid-config effect\n // below re-tunes it without re-booting the canvas.\n const groundRef = useRef<GroundLayerHandle | null>(null);\n const gridConfigRef = useRef(gridConfig);\n gridConfigRef.current = gridConfig;\n // glRoute reads through a ref: the adapter captures ONE function at attach,\n // and the app's real router typically arrives post-mount (onReady).\n const glRouteRef = useRef(glRoute);\n glRouteRef.current = glRoute;\n\n useEffect(() => {\n const container = containerRef.current;\n if (container === null) return;\n const { world, engine: core, stack, runtime } = engine;\n\n const host = createCanvasHost(container);\n const planes = createPlanes(host);\n const planeArgs = { contentPlane: planes.content, liftedPlane: planes.lifted };\n const domWidgets = createDomWidgetsReflector(planeArgs, world, runtime.store);\n const remoteCursors = createRemoteCursorsReflector(host, world);\n\n // Handles kept for teardown: unregistering only stops flushes — the DOM\n // these factories inserted (ground canvas, chrome plane) must be disposed\n // too, or a StrictMode remount stacks duplicates (the double-grid field\n // report, 2026-07-11).\n const groundLayer =\n ground?.({ host, world, readWirePreview: () => stack.wirePreview }) ?? null;\n groundRef.current = groundLayer;\n if (groundLayer !== null && gridConfigRef.current !== undefined) {\n groundLayer.configureGrid(gridConfigRef.current);\n }\n const chrome = createChromeReflector(host, world, stack.marqueeBuffer);\n\n // Registration order = flush order — node-board's proven sequence.\n const unregister = [\n core.registerReflector(createPlaneTransformReflector(planeArgs)),\n ...(groundLayer !== null ? [core.registerReflector(groundLayer.reflector)] : []),\n core.registerReflector(domWidgets),\n core.registerReflector(chrome),\n core.registerReflector(createCursorReflector(host, stack.readCursor)),\n core.registerReflector(remoteCursors.reflector),\n ];\n\n const detachPointer = attachPointerAdapter(\n host,\n stack.queue,\n glRouteRef.current !== undefined\n ? // Pass the verdict through UNCOERCED: a rich GLRouteVerdict return\n // (hover-time overInteractive, 2026-07-18) must survive this seam.\n { glRoute: (kind, x, y, e) => glRouteRef.current?.(kind, x, y, e) ?? false }\n : {},\n );\n const detachMeasure =\n measureQueue !== undefined ? wireMeasurement(runtime.store, domWidgets, measureQueue) : undefined;\n const detachKeymap = attachKeymap(engine);\n\n const syncViewport = (): void => {\n const rect = container.getBoundingClientRect();\n const dpr = typeof window !== \"undefined\" ? window.devicePixelRatio : 1;\n writeRuntimeResource(world, Viewport, { w: rect.width, h: rect.height, dpr });\n };\n syncViewport();\n let resizeObserver: ResizeObserver | undefined;\n if (typeof ResizeObserver !== \"undefined\") {\n resizeObserver = new ResizeObserver(syncViewport);\n resizeObserver.observe(container);\n }\n\n const stopLoop = startRafLoop(core);\n setHosts(domWidgets);\n onReadyRef.current?.({ engine, host, planes });\n\n return () => {\n stopLoop();\n detachKeymap();\n detachMeasure?.();\n detachPointer();\n resizeObserver?.disconnect();\n for (const unreg of unregister) unreg();\n remoteCursors.destroy();\n groundLayer?.dispose();\n groundRef.current = null;\n domWidgets.dispose();\n chrome.dispose();\n planes.dispose();\n host.dispose();\n setHosts(undefined);\n };\n }, [engine, measureQueue, ground]);\n\n // Live grid re-tune — never re-boots the canvas (the mount effect above\n // deliberately omits `gridConfig` from its deps; initial config rides the\n // ref at creation).\n useEffect(() => {\n if (gridConfig !== undefined) groundRef.current?.configureGrid(gridConfig);\n }, [gridConfig]);\n\n return (\n <EngineProvider engine={engine}>\n <div ref={containerRef} className={className} style={{ width: \"100%\", height: \"100%\", ...style }} data-ice-canvas=\"\">\n {hosts !== undefined ? (\n <WidgetRoot world={engine.world} store={engine.runtime.store} hosts={hosts} />\n ) : null}\n {children}\n </div>\n </EngineProvider>\n );\n}\n","/**\n * @ice/react — widget content layer: portals from one root + Tier-3 hooks.\n * Import wall: @ice/dom → @ice/core down only — never three/@react-three\n * (enforced). react/react-dom are peers.\n */\nexport const REACT_VERSION = \"0.0.0\";\n\nexport {\n WidgetHiddenContext,\n useBreakpoint,\n useSelected,\n useWidgetProps,\n useWorldComponent,\n} from \"./hooks\";\nexport { WidgetRoot, type WidgetComponentProps, type WidgetHosts, type WidgetRootProps } from \"./widget-root\";\n\n// M10 React facade (design-005 §5): engine context, commit seam, hooks, keymap,\n// and the <InfiniteCanvas> mount.\nexport {\n EngineProvider,\n useCanvasEngine,\n useFrameFreeze,\n useOps,\n useStageHold,\n useWorld,\n type EngineProviderProps,\n} from \"./engine-context\";\nexport { useCommit, useUndoStatus, type Commit, type UndoStatus } from \"./use-commit\";\nexport { WidgetPreview, type WidgetPreviewProps } from \"./widget-preview\";\nexport {\n getPreviewSnapshot,\n hasPreviewSnapshot,\n setPreviewSnapshot,\n subscribePreviewSnapshots,\n type PreviewImage,\n} from \"./preview-snapshots\";\nexport { useTool, useToolState } from \"./use-tool\";\nexport { usePresencePeers, type PresencePeerView } from \"./use-presence\";\nexport { attachKeymap, type KeymapEntry } from \"./keymap\";\nexport {\n InfiniteCanvas,\n type GroundLayerFactory,\n type GroundLayerHandle,\n type InfiniteCanvasHandle,\n type InfiniteCanvasProps,\n} from \"./infinite-canvas\";\n"],"mappings":"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","names":["useSyncExternalStore","useSyncExternalStore","createContext","useContext","useCallback","useRef","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useEffect","useRef","useSyncExternalStore","jsx","useRef","useEffect","jsx","useSyncExternalStore","useCallback","useSyncExternalStore","useCallback","useSyncExternalStore","useCallback","useRef","useSyncExternalStore","useRef","useCallback","useSyncExternalStore","useEffect","useRef","jsx","jsxs","useRef","useEffect"]}
|