@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
@@ -39,14 +39,58 @@ export interface IslandRenderState {
39
39
  * every pass (5 Hot vs cap 4 froze the shapes card, 2026-07-12).
40
40
  */
41
41
  lastPaintSeq: number;
42
+ /**
43
+ * Animation time OWED to this island's `useIslandFrame` callbacks: every
44
+ * pass a Hot island is paint-eligible adds the pass dtMs; the paint that
45
+ * finally runs delivers the whole balance and zeroes it. Without this, a
46
+ * stagger-deferred island simply LOST the deferred passes' time — 5 Hot vs
47
+ * cap 4 ran every animation at 4/5 speed (2026-07-20 rate probe), violating
48
+ * design-004's "the cap changes cadence, never speed". Freeze semantics are
49
+ * preserved: frozen islands are not paint-eligible, so a stage hold still
50
+ * PAUSES animation rather than banking it.
51
+ */
52
+ pendingDtMs: number;
42
53
  /**
43
54
  * Composite-quad scale (lift-on-hold): scales the CARD on the canvas —
44
55
  * texture, rounded alpha corners and all — instead of the scene content
45
56
  * (which the card-sized frustum would crop into hard corners, 2026-07-12
46
57
  * field report). ≠1 also pops render order over neighbors.
58
+ *
59
+ * This is the DRAWN value: the compositor pass eases it toward
60
+ * `liftTarget` (dtMs-driven spring). GL widgets have no CSS transitions, so
61
+ * the DOM-lift parity (2026-07-13 field report: "GL lift snaps") lives in
62
+ * the pass, not in userland.
47
63
  */
48
64
  compositeScale: number;
65
+ /** Where the lift is headed; `setCompositeScale` retargets from the drawn value. */
66
+ liftTarget: number;
67
+ /** Drawn value at (re)target time — the ease runs liftFrom → liftTarget. */
68
+ liftFrom: number;
69
+ /** Accumulated pass dtMs since the last retarget; ≥ liftMs ⇒ settled. */
70
+ liftElapsedMs: number;
71
+ /** Transition duration in ms; 0 = snap (the pre-ease behavior). */
72
+ liftMs: number;
73
+ /**
74
+ * Composite-quad opacity channel (the lift's fade twin): the DRAWN value the
75
+ * pass eases toward `opacityTarget` and MULTIPLIES with the widget's durable
76
+ * `Opacity` cell — presentation-transient (chrome-grade, e.g. the drag-lift
77
+ * fade) stacked over the doc-level fact, never written to ECS.
78
+ */
79
+ compositeOpacity: number;
80
+ /** Where the fade is headed; `setCompositeOpacity` retargets from the drawn value. */
81
+ opacityTarget: number;
82
+ /** Drawn value at (re)target time — the ease runs opacityFrom → opacityTarget. */
83
+ opacityFrom: number;
84
+ /** Accumulated pass dtMs since the last retarget; ≥ opacityMs ⇒ settled. */
85
+ opacityElapsedMs: number;
86
+ /** Transition duration in ms; 0 = snap. */
87
+ opacityMs: number;
49
88
  }
89
+ /**
90
+ * Default lift transition — the GL twin of the DOM card-lift idiom (180ms
91
+ * springy pop; the curve itself lives in the compositor pass).
92
+ */
93
+ export declare const LIFT_DEFAULT_MS = 180;
50
94
  export interface IslandStateStore {
51
95
  /** Get-or-create (Cold, unpainted). */
52
96
  ensure(key: number): IslandRenderState;
@@ -0,0 +1,19 @@
1
+ import type { Texture, WebGLRenderer } from "three";
2
+ export interface CapturePreviewOpts {
3
+ /** Types to capture (default: every registered gl-surface widget). */
4
+ readonly types?: readonly string[];
5
+ /**
6
+ * Shared IBL. Prefer the FACTORY form: PMREM textures live on the GPU of
7
+ * the renderer that generated them (no CPU image), so the app's main-canvas
8
+ * env is EMPTY on the capture renderer (field bug 2026-07-19 — black
9
+ * metals). The factory receives the capture renderer and builds the env
10
+ * there (e.g. `(gl) => new PMREMGenerator(gl).fromScene(new RoomEnvironment(), 0.04).texture`).
11
+ * A plain Texture is accepted for same-renderer/CPU-backed cases.
12
+ */
13
+ readonly environment?: Texture | ((gl: WebGLRenderer) => Texture | null) | null;
14
+ /** Pixel density of the capture (default: fit 512px on the long side, ≤2). */
15
+ readonly scale?: number;
16
+ /** Frames advanced before the capture (animations reach a live pose). */
17
+ readonly settleFrames?: number;
18
+ }
19
+ export declare function captureWidgetPreviews(opts?: CapturePreviewOpts): Promise<string[]>;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Self-sustain scheduling for the demand loop (design-004 §3 animation-rate
3
+ * amendment, 2026-07-14).
4
+ *
5
+ * When the ONLY reason to run another frame is ambient animation (Hot islands
6
+ * or a stagger backlog), the loop no longer re-invalidates at native refresh:
7
+ * on a 120 Hz panel that was 5 always-Hot islands × 120 paints/sec ≈ 96 % GPU
8
+ * duty at idle — the fan. Instead the next pass is scheduled `1000/maxFps` ms
9
+ * after the last pass STARTED, so ambient animation runs at ~`maxFps` and the
10
+ * GPU idles between frames.
11
+ *
12
+ * What stays at native refresh:
13
+ * - lift eases (`liftAnimating`) — 180 ms of interaction feedback;
14
+ * - every EXTERNALLY invalidated frame (camera, drag, props dirt): the
15
+ * bridge's `requestFrame` path is untouched, so interaction still runs at
16
+ * display rate and Hot islands ride along uncapped while it lasts.
17
+ *
18
+ * The cap is approximate: the wake-up is a timeout and the actual frame lands
19
+ * on the next vsync after it. {@link RATE_CAP_SLACK_MS} pulls the timeout half
20
+ * a 120 Hz vsync early so the following vsync is the intended one, not one
21
+ * past it (naive `interval` scheduling on a 120 Hz grid yields 40 fps, not 60).
22
+ */
23
+ /** Timeout lead so the post-timeout vsync lands on the target boundary. */
24
+ export declare const RATE_CAP_SLACK_MS = 4;
25
+ export interface SelfSustainStats {
26
+ readonly anyHot: boolean;
27
+ readonly pendingPaints: number;
28
+ readonly liftAnimating: boolean;
29
+ }
30
+ /**
31
+ * Decide how the demand loop continues after a pass.
32
+ *
33
+ * @param stats The pass's self-sustain signals.
34
+ * @param elapsedMs ms since the pass started (schedule anchor).
35
+ * @param maxFps Animation rate cap; `Infinity` restores uncapped.
36
+ * @returns `"none"` (park), `"now"` (invalidate immediately), or the timeout
37
+ * delay in ms before invalidating.
38
+ */
39
+ export declare function selfSustainPlan(stats: SelfSustainStats, elapsedMs: number, maxFps: number): "none" | "now" | number;
@@ -20,5 +20,20 @@ export declare function useIslandInvalidate(): () => void;
20
20
  * corners together) and pops it over neighbors while ≠1. Scaling scene content
21
21
  * instead crops the corners at the card-sized frustum — the 2026-07-12
22
22
  * hard-corner field report. Resets to 1 on unmount.
23
+ *
24
+ * The scale EASES to each new value (default 180ms, the DOM card-lift
25
+ * spring — GL has no CSS transitions, so the compositor runs the curve;
26
+ * 2026-07-13 field report: "GL lift snaps while DOM lifts smooth").
27
+ * `durationMs: 0` snaps.
23
28
  */
24
- export declare function useIslandLift(scale: number): void;
29
+ export declare function useIslandLift(scale: number, durationMs?: number): void;
30
+ /**
31
+ * Composite-quad fade — the lift's opacity twin (e.g. the drag-lift 75 %
32
+ * fade): eases the quad's drawn opacity to `opacity` (default 180ms; 0
33
+ * snaps) and MULTIPLIES the widget's durable `Opacity` cell, so a transient
34
+ * fade stacks over a doc-level opacity instead of fighting it. The DOM
35
+ * chrome runs its own CSS `opacity … ease` transition — same duration/curve
36
+ * family, so card body and GL content fade in lockstep. Resets to 1 on
37
+ * unmount.
38
+ */
39
+ export declare function useIslandOpacity(opacity: number, durationMs?: number): void;
@@ -22,6 +22,27 @@ export declare function EngineProvider({ engine, children }: EngineProviderProps
22
22
  export declare function useCanvasEngine(): CanvasEngine;
23
23
  /** The engine-owned write paths (design-005 §4): setTool, deleteSelection, etc. */
24
24
  export declare function useOps(): CanvasOps;
25
+ /**
26
+ * Take a stage background hold while `active` (ce.stage, 2026-07-19): the
27
+ * overlay-lifecycle sugar — the disposer releases on deactivate AND unmount,
28
+ * so a crashed/unmounted overlay can never wedge the canvas backgrounded.
29
+ * `engine` is an explicit param (not context): overlays typically render as
30
+ * SIBLINGS of <InfiniteCanvas>, outside the provider.
31
+ */
32
+ export declare function useStageHold(engine: CanvasEngine, active: boolean, name: string): void;
33
+ /**
34
+ * Freeze the engine while `active` (ce.frame, 2026-08-04) — {@link useStageHold}'s
35
+ * stricter sibling, same lifecycle sugar: the effect cleanup IS the thaw, so an
36
+ * overlay that unmounts (or crashes) can never wedge the engine parked.
37
+ *
38
+ * WHICH ONE. A stage hold quiets discretionary rendering over a world that
39
+ * keeps stepping — right for chrome that RECEDES the canvas, where undo,
40
+ * collab and tweens must still land visibly behind a partial overlay. A freeze
41
+ * stops the loop outright — right for chrome that COVERS the canvas, where
42
+ * every frame behind it is work nobody can see. Reaching for a freeze under a
43
+ * partial overlay will look broken: the canvas is a photograph until thaw.
44
+ */
45
+ export declare function useFrameFreeze(engine: CanvasEngine, active: boolean, name: string): void;
25
46
  /**
26
47
  * Escape hatch — the raw {@link World} for READS and Tier-3 OBSERVATION only.
27
48
  * Mutation goes through {@link useOps}/{@link useCommit} (a gesture-claimed,
@@ -6,9 +6,11 @@
6
6
  export declare const REACT_VERSION = "0.0.0";
7
7
  export { WidgetHiddenContext, useBreakpoint, useSelected, useWidgetProps, useWorldComponent, } from "./hooks";
8
8
  export { WidgetRoot, type WidgetComponentProps, type WidgetHosts, type WidgetRootProps } from "./widget-root";
9
- export { EngineProvider, useCanvasEngine, useOps, useWorld, type EngineProviderProps, } from "./engine-context";
9
+ export { EngineProvider, useCanvasEngine, useFrameFreeze, useOps, useStageHold, useWorld, type EngineProviderProps, } from "./engine-context";
10
10
  export { useCommit, useUndoStatus, type Commit, type UndoStatus } from "./use-commit";
11
+ export { WidgetPreview, type WidgetPreviewProps } from "./widget-preview";
12
+ export { getPreviewSnapshot, hasPreviewSnapshot, setPreviewSnapshot, subscribePreviewSnapshots, type PreviewImage, } from "./preview-snapshots";
11
13
  export { useTool, useToolState } from "./use-tool";
12
14
  export { usePresencePeers, type PresencePeerView } from "./use-presence";
13
15
  export { attachKeymap, type KeymapEntry } from "./keymap";
14
- export { InfiniteCanvas, type InfiniteCanvasHandle, type InfiniteCanvasProps, } from "./infinite-canvas";
16
+ export { InfiniteCanvas, type GroundLayerFactory, type GroundLayerHandle, type InfiniteCanvasHandle, type InfiniteCanvasProps, } from "./infinite-canvas";
@@ -7,7 +7,7 @@
7
7
  * and the viewport sync — everything that must live next to a real container.
8
8
  *
9
9
  * Boot order (registration order = reflector flush order, mirrored exactly):
10
- * host → planes → reflectors[ planeTransform · grid(P0) · wires(P0) ·
10
+ * host → planes → reflectors[ planeTransform · ground(P0, app-injected) ·
11
11
  * domWidgets(P1/P3) · chrome(P4) · cursor · remoteCursors(P5) ] →
12
12
  * pointer adapter → measurement (optional) → keymap → rAF loop → viewport RO.
13
13
  *
@@ -24,14 +24,19 @@
24
24
  * - GL / R3F: the `@ice/r3f` wall forbids `@ice/react` importing three, so a
25
25
  * GL layer (GLViews) mounts APP-SIDE. Use {@link onReady} to receive the host
26
26
  * + planes and mount it yourself (like apps/glboard).
27
+ * - The GROUND layer (P0: dot grid, wires, snap guides — @ice/ground, three's
28
+ * WebGPURenderer) rides the same wall: pass its factory through the OPAQUE
29
+ * {@link InfiniteCanvasProps.ground} prop (`ground={ground({...})}`); this
30
+ * component types it structurally and never imports the package. No factory
31
+ * ⇒ no ground layer (headless/test boots).
27
32
  * - Devtools: the import wall forbids `@ice/react` importing `@ice/devtools`.
28
33
  * Wire the panel app-side against `engine.engine` (also via {@link onReady}).
29
34
  * - Measurement: auto-sized widgets need a `MeasureQueue` passed to BOTH
30
35
  * `createCanvasEngine({ measureQueue })` (ingest side) and this component's
31
36
  * `measureQueue` prop (ResizeObserver side). Absent ⇒ measurement is skipped.
32
37
  */
33
- import { type CanvasEngine, type MeasureQueue } from "@ice/core";
34
- import { type CanvasHost, type GLRoute, type GridConfig, type Planes } from "@ice/dom";
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";
35
40
  import { type CSSProperties, type ReactElement, type ReactNode } from "react";
36
41
  /** Handed to {@link InfiniteCanvasProps.onReady} for app-side GL/devtools wiring. */
37
42
  export interface InfiniteCanvasHandle {
@@ -39,6 +44,27 @@ export interface InfiniteCanvasHandle {
39
44
  readonly host: CanvasHost;
40
45
  readonly planes: Planes;
41
46
  }
47
+ /**
48
+ * STRUCTURAL mirror of `@ice/ground`'s GroundLayer/GroundFactory (the
49
+ * `WidgetDef.component`-style opaque seam: react never imports the package —
50
+ * the wall forbids three here). `@ice/ground.ground(...)` returns a function
51
+ * assignable to this type.
52
+ */
53
+ export interface GroundLayerHandle {
54
+ readonly reflector: ReflectorDef & {
55
+ available(): boolean;
56
+ };
57
+ configureGrid(cfg: Partial<GridConfig>): void;
58
+ dispose(): void;
59
+ }
60
+ export type GroundLayerFactory = (ctx: {
61
+ readonly host: {
62
+ readonly container: HTMLElement;
63
+ readonly contentPlane: HTMLElement;
64
+ };
65
+ readonly world: World;
66
+ readonly readWirePreview: () => WirePreviewBuffer;
67
+ }) => GroundLayerHandle;
42
68
  export interface InfiniteCanvasProps {
43
69
  /** A constructed engine (`createCanvasEngine(...)`); NOT disposed on unmount. */
44
70
  readonly engine: CanvasEngine;
@@ -49,10 +75,18 @@ export interface InfiniteCanvasProps {
49
75
  readonly measureQueue?: MeasureQueue;
50
76
  /** Called once after the host/reflectors/loop are live (app-side GL/devtools). */
51
77
  readonly onReady?: (handle: InfiniteCanvasHandle) => void;
78
+ /**
79
+ * The P0 ground layer (dot grid, wires, snap guides — one WebGPU canvas).
80
+ * Pass `ground(opts)` from `@ice/ground`; received opaquely (see
81
+ * {@link GroundLayerFactory}). Memoize in the caller — a new identity
82
+ * re-boots the canvas mount effect. Absent ⇒ no ground layer renders.
83
+ */
84
+ readonly ground?: GroundLayerFactory;
52
85
  /**
53
86
  * Dot-grid tuning (theme dot color, spacing, fades). Applied live via the
54
- * grid reflector's `configure` — changing it never re-boots the canvas
55
- * (memoize in the caller to avoid redundant same-value redraws).
87
+ * ground layer's `configureGrid` — changing it never re-boots the canvas
88
+ * (memoize in the caller to avoid redundant same-value redraws). No-op
89
+ * when {@link ground} is absent.
56
90
  */
57
91
  readonly grid?: Partial<GridConfig>;
58
92
  /**
@@ -68,4 +102,4 @@ export interface InfiniteCanvasProps {
68
102
  /** Overlays inside the viewport (toolbars, HUD) — rendered under the EngineProvider. */
69
103
  readonly children?: ReactNode;
70
104
  }
71
- export declare function InfiniteCanvas({ engine, measureQueue, onReady, grid: gridConfig, glRoute, className, style, children, }: InfiniteCanvasProps): ReactElement;
105
+ export declare function InfiniteCanvas({ engine, measureQueue, onReady, ground, grid: gridConfig, glRoute, className, style, children, }: InfiniteCanvasProps): ReactElement;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The GL preview snapshot store (design-005 §2 preview contract, P2 —
3
+ * 2026-07-19). react stays three-free by LAW, so the capture pipeline lives
4
+ * in @ice/r3f and meets `<WidgetPreview>` HERE, at a dumb data seam: a
5
+ * module-level map of widget type → captured image, with subscription so an
6
+ * open palette re-renders the moment a capture lands.
7
+ *
8
+ * `PreviewImage` accepts a canvas alongside ImageBitmap so tests (and any
9
+ * app-side prebuilt-cache policy — the P3 seam) can inject without a GPU.
10
+ */
11
+ export type PreviewImage = ImageBitmap | HTMLCanvasElement;
12
+ export declare function setPreviewSnapshot(type: string, image: PreviewImage): void;
13
+ export declare function getPreviewSnapshot(type: string): PreviewImage | undefined;
14
+ export declare function hasPreviewSnapshot(type: string): boolean;
15
+ export declare function subscribePreviewSnapshots(listener: () => void): () => void;
16
+ /** TEST-ONLY wipe (not on the barrel). */
17
+ export declare function __resetPreviewSnapshotsForTests(): void;
@@ -0,0 +1,14 @@
1
+ import { type CSSProperties, type ReactElement, type ReactNode } from "react";
2
+ /** TEST-ONLY sandbox wipe (mirrors __resetWidgetsForTests; not on the barrel). */
3
+ export declare function __resetPreviewSandboxForTests(): void;
4
+ export interface WidgetPreviewProps {
5
+ readonly type: string;
6
+ /** The available box; the preview renders aspect-FITTED inside it. */
7
+ readonly width: number;
8
+ readonly height: number;
9
+ /** App silhouette shown when no preview path applies (or one throws). */
10
+ readonly fallback?: ReactNode;
11
+ readonly className?: string;
12
+ readonly style?: CSSProperties;
13
+ }
14
+ export declare function WidgetPreview({ type, width, height, fallback, className, style }: WidgetPreviewProps): ReactElement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibecook/ice",
3
- "version": "0.1.0",
3
+ "version": "0.3.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,70 +26,51 @@
26
26
  "three",
27
27
  "gestures"
28
28
  ],
29
- "files": [
30
- "dist"
31
- ],
29
+ "files": ["dist"],
32
30
  "main": "./dist/index.js",
33
31
  "types": "./dist/types/packages/ice/src/index.d.ts",
34
32
  "exports": {
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
- "./devtools": {
56
- "types": "./dist/types/packages/ice/src/devtools.d.ts",
57
- "default": "./dist/devtools.js"
58
- },
33
+ ".": { "types": "./dist/types/packages/ice/src/index.d.ts", "default": "./dist/index.js" },
34
+ "./kernel": { "types": "./dist/types/packages/ice/src/kernel.d.ts", "default": "./dist/kernel.js" },
35
+ "./dom": { "types": "./dist/types/packages/ice/src/dom.d.ts", "default": "./dist/dom.js" },
36
+ "./react": { "types": "./dist/types/packages/ice/src/react.d.ts", "default": "./dist/react.js" },
37
+ "./r3f": { "types": "./dist/types/packages/ice/src/r3f.d.ts", "default": "./dist/r3f.js" },
38
+ "./ground": { "types": "./dist/types/packages/ice/src/ground.d.ts", "default": "./dist/ground.js" },
39
+ "./devtools": { "types": "./dist/types/packages/ice/src/devtools.d.ts", "default": "./dist/devtools.js" },
59
40
  "./package.json": "./package.json"
60
41
  },
61
- "engines": {
62
- "node": ">=20"
63
- },
64
- "publishConfig": {
65
- "access": "public"
42
+ "engines": { "node": ">=20" },
43
+ "publishConfig": { "access": "public" },
44
+ "scripts": {
45
+ "typecheck": "tsc --noEmit",
46
+ "build": "tsup && tsc -p tsconfig.dts.json",
47
+ "prepack": "pnpm run build"
66
48
  },
67
49
  "dependencies": {
68
- "@vibecook/strata-ecs": "0.5.0",
69
- "loro-crdt": "^1.13.6",
50
+ "@vibecook/strata-ecs": "0.11.0",
51
+ "loro-crdt": "^1.13.8",
52
+ "stats-gl": "^4.2.3",
70
53
  "rbush": "^3.0.1"
71
54
  },
72
55
  "peerDependencies": {
73
56
  "@react-three/fiber": ">=9",
74
- "react": ">=18",
75
- "react-dom": ">=18",
57
+ "react": ">=19",
58
+ "react-dom": ">=19",
76
59
  "three": ">=0.160.0"
77
60
  },
78
61
  "peerDependenciesMeta": {
79
- "@react-three/fiber": {
80
- "optional": true
81
- },
82
- "react": {
83
- "optional": true
84
- },
85
- "react-dom": {
86
- "optional": true
87
- },
88
- "three": {
89
- "optional": true
90
- }
62
+ "@react-three/fiber": { "optional": true },
63
+ "react": { "optional": true },
64
+ "react-dom": { "optional": true },
65
+ "three": { "optional": true }
91
66
  },
92
67
  "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:*",
93
74
  "@react-three/fiber": "^9",
94
75
  "@types/rbush": "^3.0.3",
95
76
  "@types/react": "^19",
@@ -97,16 +78,6 @@
97
78
  "@types/three": "^0.185.0",
98
79
  "react": "^19",
99
80
  "react-dom": "^19",
100
- "three": "^0.185.0",
101
- "@ice/core": "0.0.0",
102
- "@ice/dom": "0.0.0",
103
- "@ice/kernel": "0.0.0",
104
- "@ice/r3f": "0.0.0",
105
- "@ice/devtools": "0.0.0",
106
- "@ice/react": "0.0.0"
107
- },
108
- "scripts": {
109
- "typecheck": "tsc --noEmit",
110
- "build": "tsup && tsc -p tsconfig.dts.json"
81
+ "three": "^0.185.0"
111
82
  }
112
- }
83
+ }