@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
@@ -4,19 +4,27 @@
4
4
  *
5
5
  * - `cullSystem` (derive): viewport test over equipped widgets' EFFECTIVE size
6
6
  * (MeasuredSize where present, else Size), overscan scaled by zoom; flips
7
- * Visible/Culled CHANGE-ONLY.
7
+ * Visible/Culled CHANGE-ONLY. GATED (2026-07-15, the activeMembership
8
+ * playbook): a real `runIf` — camera/viewport window compare as the `extra`
9
+ * trigger (full pass; the window moved under everyone) ∨ a petition-7
10
+ * collector on Position/Size/MeasuredSize + Active flips (delta pass —
11
+ * drags/spawns/measures re-test only the journaled entities). Idle frames
12
+ * skip; camera frames stay O(active) by the widgetQ Active scope.
8
13
  * - `mountSystem` (derive, after cull): maintains the mount list — every
9
14
  * Visible widget is mounted; Culled widgets stay mounted-but-hidden
10
15
  * (`display:none` at the host; React state preserved) within a count LRU
11
16
  * budget (default RUNTIME_BUDGETS.keepMountedWidgets); beyond it the
12
17
  * least-recently-visible unmount for real. Decisions are ENGINE-side; the
13
18
  * dom host reflector and the React portal hook both consume the snapshot.
19
+ * GATED the same way on Visible/Culled flips (+ destroys of tagged
20
+ * widgets); LRU recency is stamped at the flip (identical eviction order —
21
+ * "last tick seen visible" = the tick it stopped being visible).
14
22
  * - The store implements the `useSyncExternalStore` contract: `subscribe` /
15
23
  * `getSnapshot` with snapshot identity changing IFF membership or a hidden
16
24
  * flag changed. Listener notification is deferred to post-notify (the
17
25
  * engine flush reflector) — never from inside the tick.
18
26
  */
19
- import { type Entity, type System, type TickSystem, type World } from "@vibecook/strata-ecs";
27
+ import { type Entity, type TickSystem, type World } from "@vibecook/strata-ecs";
20
28
  import type { Engine } from "../engine/engine";
21
29
  import type { MeasureQueue } from "../input/measure-queue";
22
30
  export interface MountEntry {
@@ -30,7 +38,7 @@ export interface WidgetMountStore {
30
38
  }
31
39
  export interface WidgetRuntime {
32
40
  readonly store: WidgetMountStore;
33
- readonly cullSystem: System;
41
+ readonly cullSystem: TickSystem;
34
42
  readonly mountSystem: TickSystem;
35
43
  /** Post-notify listener flush — install registers it as a reflector. */
36
44
  flush(): void;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * setWidgetProps — the paved-road widget prop UPDATE op (2026-07-13 review:
3
+ * spawn validated through each prop's Standard Schema, but the update path —
4
+ * raw `useCommit` writes — bypassed bounds/enum/json-shape checks entirely).
5
+ *
6
+ * Mirrors `widgetSpawnInits` semantics exactly: unknown prop names throw,
7
+ * each given value runs `spec["~standard"].validate` (an invalid value throws
8
+ * BEFORE anything is written), `p.json` values are validated as parsed shapes
9
+ * then serialized into their string cell. Writes are whole-group absolute
10
+ * values (the current cell merged with the validated updates — design-001
11
+ * §5.2 conflict-group granularity), one guardedTransaction = one undo step.
12
+ */
13
+ import type { Entity, World } from "@vibecook/strata-ecs";
14
+ import type { DurableStore } from "@vibecook/strata-ecs/durable";
15
+ import { type GuardedTxOpts } from "../guards/guarded-tx";
16
+ export declare function setWidgetProps(store: DurableStore, world: World, entity: Entity, props: Readonly<Record<string, unknown>>, opts?: GuardedTxOpts): void;
@@ -6,7 +6,7 @@
6
6
  * design-001 §5.2's conflict-group granularity). Validation runs through each
7
7
  * prop's Standard Schema before the write.
8
8
  */
9
- import type { Entity, World } from "@vibecook/strata-ecs";
9
+ import type { Entity, OrderPlace, Relation, World } from "@vibecook/strata-ecs";
10
10
  import type { DurableStore } from "@vibecook/strata-ecs/durable";
11
11
  import { type ComponentInit } from "../schema/prefab";
12
12
  export interface SpawnWidgetOpts {
@@ -14,10 +14,30 @@ export interface SpawnWidgetOpts {
14
14
  readonly y: number;
15
15
  readonly w?: number;
16
16
  readonly h?: number;
17
+ /**
18
+ * Sibling placement in the spawn parent's `ChildOf` sequence (petition 8);
19
+ * default "last" = painted last = on top. "first" spawns UNDER everything in
20
+ * the frame (e.g. a comment box spawning behind its members).
21
+ */
22
+ readonly order?: OrderPlace;
23
+ /** Explicit `ChildOf` parent; default = the frame parent (nav frame ?? board root). */
24
+ readonly parent?: Entity;
17
25
  readonly props?: Readonly<Record<string, unknown>>;
18
26
  /** false ⇒ the spawn never enters the local undo stack (batch seeds). */
19
27
  readonly undoable?: boolean;
20
28
  }
29
+ /**
30
+ * Hang the spawn's `ChildOf` edge (petition 8) — shared by `spawnWidget` and
31
+ * the doc sink's create-intent execution so frame placement can never diverge
32
+ * between the two spawn paths. A legacy world (no BoardRoot, no nav frame,
33
+ * no explicit parent) gets NO edge: the entity stays legacy-fallback-ordered.
34
+ */
35
+ export declare function attachSpawnParent(tx: {
36
+ setRelation(e: Entity, r: Relation, target: Entity, place?: OrderPlace): void;
37
+ }, world: World, spawned: Entity, opts?: {
38
+ readonly parent?: Entity;
39
+ readonly order?: OrderPlace;
40
+ }): void;
21
41
  /**
22
42
  * The prefab-override list for a widget spawn (Position/Size + validated
23
43
  * whole-group prop values). Shared by `spawnWidget` and the doc sink's
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @ice/devtools — strata's first-party tools, engine-flavored (rebuilt
3
+ * 2026-07-13, superseding the M10 hand-rolled 4-tab panel: the standing rule
4
+ * is observers/inspectors WRAP `@vibecook/strata-ecs/tools`, never
5
+ * re-implement them).
6
+ *
7
+ * `attachDevtools(engine)` = strata's observer panel + FPS profiler, plus the
8
+ * engine glue strata cannot know:
9
+ *
10
+ * DESCRIBE PrefabId → widget type; recognizers → kind + live phase;
11
+ * pointers/ports/wires/peers/chrome labeled as such.
12
+ * DURABLE TAB fed from the facade's CURRENT DocSession (docId, converged
13
+ * snapshot, attachment baseline) — the getter re-reads per
14
+ * poll, so it tracks docs.create/open/join/close live.
15
+ * EPHEMERAL TAB fed from an app-provided PresenceSession getter (the
16
+ * facade keeps presence private; collab demos pass theirs).
17
+ * "reflect" LANE the engine's post-notify reflector flush cost — the one
18
+ * frame slice strata's world hooks cannot see — reported
19
+ * into the profiler via an onPublish hook reading
20
+ * `lastFrame()` telemetry (one frame late by construction:
21
+ * publish runs before this frame's reflect).
22
+ *
23
+ * By default all three tools mount into ONE draggable dock (dock.ts —
24
+ * 2026-07-13, James: "make the 3 tools into one draggable panel"); pass
25
+ * `dock: false` for the classic scattered corners.
26
+ *
27
+ * Reads only, outside the tick (verified-unrestricted); never a reflector,
28
+ * never writes ECS. Nobody imports this package (depcruise-enforced leaf).
29
+ */
30
+ import type { Entity, World } from "@vibecook/strata-ecs";
31
+ import { type DescribeFn, type EntityDescription, type ObserverHandle, type ObserverOptions, type ProfilerHandle, type ProfilerOptions } from "@vibecook/strata-ecs/tools";
32
+ import { type DockOptions } from "./dock";
33
+ import { type GlPanelOptions, type GlPanelStats } from "./gl-panel";
34
+ import { type CanvasEngine, type Engine, type PresenceSession } from "@ice/core";
35
+ export interface DevtoolsOpts {
36
+ /** Where the dock (or, with `dock: false`, each panel) mounts (default: document.body). */
37
+ readonly container?: HTMLElement;
38
+ /**
39
+ * ONE draggable dock hosting all three tools (the default). `false` scatters
40
+ * them to their classic fixed corners; an object sets initial placement.
41
+ * The dock mounts lazily with the first tool that needs it.
42
+ */
43
+ readonly dock?: boolean | Pick<DockOptions, "corner" | "title" | "width">;
44
+ /** Observer panel: `false` to omit, or strata passthrough options. */
45
+ readonly observer?: boolean | Pick<ObserverOptions, "defaultTab" | "tab" | "recorderCap">;
46
+ /** Profiler HUD: `false` to omit, or strata passthrough options. */
47
+ readonly profiler?: boolean | Pick<ProfilerOptions, "budgetMs" | "windowSize" | "corner" | "expanded" | "lanes">;
48
+ /** GL metrics panel: `false` to omit, or placement/budget options. Mounts lazily on the first `glStats` push. */
49
+ readonly glPanel?: boolean | Pick<GlPanelOptions, "corner" | "budgetMs" | "expanded">;
50
+ /** Collab demos: the presence session whose store feeds the ephemeral tab. */
51
+ readonly presence?: () => PresenceSession | null | undefined;
52
+ /** Override entity labeling (default: the engine describe below). */
53
+ readonly describe?: DescribeFn;
54
+ }
55
+ export interface DevtoolsHandle {
56
+ readonly observer: ObserverHandle | null;
57
+ readonly profiler: ProfilerHandle | null;
58
+ /** Host-cost lane passthrough (paint, decode…); no-op when the profiler is off. */
59
+ lane(name: string, ms: number): void;
60
+ /**
61
+ * Feed one GL frame's metrics (wire `<GLViews onFrameStats>` here). The GL
62
+ * panel mounts on the first push — renderer counts, virtual-texture census,
63
+ * LOD banding, cull population. No-op when opts.glPanel === false.
64
+ */
65
+ glStats(stats: GlPanelStats): void;
66
+ /** Tear all panels + the telemetry hook down. Idempotent. */
67
+ detach(): void;
68
+ }
69
+ /**
70
+ * Engine-aware entity labeling for the observer's lists + timeline: identity
71
+ * off ROLE (widget type, recognizer kind + phase, pointer device), not off
72
+ * raw component-name composition. Falls back to strata's defaultDescribe.
73
+ */
74
+ export declare function engineDescribe(world: World, e: Entity): EntityDescription;
75
+ /** Accepts the facade (durable tab included) or a bare core engine. */
76
+ export type DevtoolsEngine = CanvasEngine | Engine;
77
+ export declare function attachDevtools(engine: DevtoolsEngine, opts?: DevtoolsOpts): DevtoolsHandle;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The devtools dock — ONE draggable window hosting all three tools
3
+ * (2026-07-13, James: "make the 3 tools into one draggable panel").
4
+ *
5
+ * strata's observer + profiler and our GL panel each mount `position: fixed`
6
+ * into their own corner. The dock does NOT re-implement any of them (standing
7
+ * rule: wrap first-party tools): it is a fixed, draggable shell with three
8
+ * stacked slots; each tool mounts into its slot via its own `container`
9
+ * option, and dock-scoped CSS de-windows them (`position: static`, full
10
+ * width, no border/shadow) so they read as sections of one panel. Their own
11
+ * behavior is untouched — the observer keeps its tabs/collapse/vertical
12
+ * resize (its header drag goes inert: left/top are ignored at static), the
13
+ * profiler + GL strips keep their click-to-expand.
14
+ *
15
+ * Slot order is fixed by the dock, not by mount order: profiler and GL are
16
+ * one-line summary strips, the observer is the deep-dive — strips on top.
17
+ * Layout (x/y/width/open) persists to localStorage, same as the observer:
18
+ * schema edits full-reload the page by design; the dock must stay put.
19
+ */
20
+ export type DockCorner = "tl" | "tr" | "bl" | "br";
21
+ export type DockSlotId = "profiler" | "gl" | "observer";
22
+ export interface DockOptions {
23
+ /** Where the dock mounts (default: document.body). */
24
+ readonly container?: HTMLElement;
25
+ /** Initial corner when nothing is persisted yet (default "tr"). */
26
+ readonly corner?: DockCorner;
27
+ /** Header title (default "ice devtools"). */
28
+ readonly title?: string;
29
+ /** Initial width in px when nothing is persisted yet (default 420). */
30
+ readonly width?: number;
31
+ }
32
+ export interface Dock {
33
+ readonly el: HTMLElement;
34
+ /** The mount element for one tool. Empty slots are display:none via CSS. */
35
+ slot(id: DockSlotId): HTMLElement;
36
+ dispose(): void;
37
+ }
38
+ export declare function createDock(opts?: DockOptions): Dock;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The GL metrics panel — the engine-specific renderer readout strata has no
3
+ * counterpart for (2026-07-13, James: "comprehensive r3f profiler ... and our
4
+ * engine specific optimization metrics ... with nice visuals").
5
+ *
6
+ * Pure DOM, strata's visual language (same palette/typography as the observer
7
+ * + profiler HUD so the three read as one toolset). Fed by pushes of
8
+ * `GlPanelStats` — the STRUCTURAL MIRROR of @ice/r3f's `GlFrameStats`
9
+ * (devtools cannot import r3f under the walls; keep the shapes aligned):
10
+ *
11
+ * head fps · gpu ms + a gpu sparkline against the frame budget
12
+ * frame cpu / gpu bars vs budget, with session maxima
13
+ * render draw calls · triangles · points/lines · programs · geoms · textures
14
+ * virtual live render targets + total resolution, FBO bytes vs budget,
15
+ * textures the island phase census as a segmented bar, repaint/evict counters
16
+ * lod zoom → hysteresis band → effective DPR, live targets per band
17
+ * widgets Visible vs Culled population
18
+ *
19
+ * All values land via `textContent` (never innerHTML with data). Rendering is
20
+ * throttled — pushes arrive per composited frame, the DOM refreshes at ~8 Hz.
21
+ */
22
+ /** Mirror of @ice/r3f `GlFrameStats` (structural — see the header). */
23
+ export interface GlPanelStats {
24
+ readonly cpuMs: number;
25
+ readonly gpuMs: number;
26
+ readonly fps: number;
27
+ readonly drawCalls: number;
28
+ readonly triangles: number;
29
+ readonly points: number;
30
+ readonly lines: number;
31
+ readonly programs: number;
32
+ readonly geometries: number;
33
+ readonly textures: number;
34
+ readonly renderTargets: number;
35
+ readonly renderMegaPixels: number;
36
+ readonly fboBytes: number;
37
+ readonly fboBudgetBytes: number;
38
+ readonly islands: {
39
+ readonly total: number;
40
+ readonly hot: number;
41
+ readonly warm: number;
42
+ readonly waking: number;
43
+ readonly cold: number;
44
+ readonly dormant: number;
45
+ };
46
+ readonly bandHistogram: Readonly<Record<string, number>>;
47
+ readonly repainted: number;
48
+ readonly pendingPaints: number;
49
+ readonly evicted: number;
50
+ readonly zoom: number;
51
+ readonly band: number;
52
+ readonly effectiveDpr: number;
53
+ readonly visibleWidgets: number;
54
+ readonly culledWidgets: number;
55
+ }
56
+ export type GlPanelCorner = "tl" | "tr" | "bl" | "br";
57
+ export interface GlPanelOptions {
58
+ readonly container?: HTMLElement;
59
+ readonly corner?: GlPanelCorner;
60
+ /** Frame budget in ms — the sparkline's reference line (default 16.7). */
61
+ readonly budgetMs?: number;
62
+ /** Start with the detail body open (default false). */
63
+ readonly expanded?: boolean;
64
+ }
65
+ export interface GlPanel {
66
+ push(stats: GlPanelStats): void;
67
+ dispose(): void;
68
+ }
69
+ export declare function createGlPanel(opts?: GlPanelOptions): GlPanel;
@@ -1,8 +1,10 @@
1
1
  /**
2
- * @ice/devtools — the engine devtools panel (design-005 §4): pointers/recognizers,
3
- * planes, sovereignty badges, loop telemetry. A standalone DOM panel that reads
4
- * the world outside the tick — it is NOT a reflector and writes no ECS.
5
- * Import wall: @ice/core (+kernel) only; nobody imports us (depcruise-enforced).
2
+ * @ice/devtools — strata's observer panel + FPS profiler, engine-flavored
3
+ * (describe/durable/ephemeral/reflect-lane glue). Reads the world outside the
4
+ * tick; never a reflector, never writes ECS. Nobody imports us
5
+ * (depcruise-enforced leaf).
6
6
  */
7
- export declare const DEVTOOLS_VERSION = "0.0.0";
8
- export { attachDevtools, type DevtoolsHandle, type DevtoolsOpts } from "./panel";
7
+ export declare const DEVTOOLS_VERSION = "0.1.0";
8
+ export { attachDevtools, engineDescribe, type DevtoolsEngine, type DevtoolsHandle, type DevtoolsOpts, } from "./attach";
9
+ export { createDock, type Dock, type DockCorner, type DockOptions, type DockSlotId } from "./dock";
10
+ export { createGlPanel, type GlPanel, type GlPanelCorner, type GlPanelOptions, type GlPanelStats } from "./gl-panel";
@@ -9,11 +9,9 @@ export { createPlaneTransformReflector, type CameraPlanes } from "./reflectors/p
9
9
  export { createGrayboxReflector } from "./reflectors/graybox";
10
10
  export { createDomWidgetsReflector, type DomWidgetsHost, type DomWidgetsReflector, } from "./reflectors/dom-widgets";
11
11
  export { startRafLoop } from "./loop";
12
- export { attachPointerAdapter, type GLRoute, type PointerAdapterOpts } from "./pointer-adapter";
12
+ export { attachPointerAdapter, type GLRoute, type GLRouteVerdict, type PointerAdapterOpts } from "./pointer-adapter";
13
13
  export { createCursorReflector } from "./reflectors/cursor";
14
14
  export { createRemoteCursorsReflector, type RemoteCursorsReflector } from "./reflectors/remote-cursors";
15
- export { createGridReflector, DEFAULT_GRID_CONFIG, type GridConfig } from "./reflectors/grid";
16
- export { createWiresReflector, DEFAULT_WIRES_CONFIG, type WirePreview, type WiresConfig, type WiresReflector, } from "./reflectors/wires";
17
15
  export { createChromeReflector } from "./reflectors/chrome";
18
16
  export { attachMeasureAdapter, type MeasureAdapter } from "./measure-adapter";
19
17
  export { wireMeasurement, type MeasureWiringHosts, type MeasureWiringOpts } from "./measure-wiring";
@@ -6,6 +6,17 @@
6
6
  * timestamp straight into the frame clock (so FrameInfo.dt is real wall time,
7
7
  * clamped in core). The returned stop fn cancels the pending frame AND latches
8
8
  * so a frame already in flight cannot re-schedule — calling it is idempotent.
9
+ *
10
+ * THE FREEZE GATE (2026-08-04). Before each frame the loop claims a step from
11
+ * `engine.frame`. While a freeze is held the gate walks its settle and then
12
+ * refuses, at which point the loop PARKS: it stops scheduling rAF at all
13
+ * rather than spinning a callback that does nothing — "the engine is stopped"
14
+ * should mean the browser has no work queued for it either. Thaw arrives
15
+ * through the gate's change subscription, which schedules the next frame.
16
+ *
17
+ * The dt clamp is what makes resume safe: a freeze that lasted an hour hands
18
+ * `step` an enormous `now`, and core clamps the delta (frame-info.ts), so
19
+ * tweens advance by one ordinary frame instead of teleporting to their end.
9
20
  */
10
21
  import type { Engine } from "@ice/core";
11
22
  export declare function startRafLoop(engine: Engine): () => void;
@@ -33,8 +33,8 @@ export interface MeasureWiringHosts {
33
33
  }
34
34
  export interface MeasureWiringOpts {
35
35
  /**
36
- * Border-box read for the forced re-measure on show. Defaults to
37
- * `getBoundingClientRect`; tests inject a deterministic stub.
36
+ * Border-box read for the forced re-measure on show. Defaults to layout
37
+ * `offsetWidth`/`offsetHeight`; tests inject a deterministic stub.
38
38
  */
39
39
  readonly measure?: (el: HTMLElement) => {
40
40
  w: number;
@@ -28,6 +28,15 @@
28
28
  * through L0). `stopPropagation()` inside widget content is the OTHER boundary:
29
29
  * a stopped event never reaches the container listener, so it never becomes a
30
30
  * canvas fact at all — that is the design, not a gap.
31
+ *
32
+ * Hover-time amendment (design-002 §8, 2026-07-18): down/move facts ALSO carry
33
+ * `overInteractive` — the same `crossesInteractive` check, run at hover time,
34
+ * OR'd with the GL router's hover verdict — so the world can telegraph the
35
+ * opt-out BEFORE a down (cursor affordances). It never gates gestures:
36
+ * `surfaceHandled` stays down-only and keeps its exact meaning. During a
37
+ * container-captured drag every event retargets to the container, so
38
+ * `overInteractive` reads false mid-gesture — correct: the gesture owns the
39
+ * pointer, not the widget under it.
31
40
  */
32
41
  import { type InputQueue } from "@ice/core";
33
42
  import type { CanvasHost } from "./host";
@@ -41,7 +50,18 @@ import type { CanvasHost } from "./host";
41
50
  * recognizers skip it via `HandledByWidget` — the same contract as the
42
51
  * native-interactive opt-out.
43
52
  */
44
- export type GLRoute = (kind: "down" | "move" | "up" | "cancel", screenX: number, screenY: number, native: PointerEvent) => boolean;
53
+ export type GLRoute = (kind: "down" | "move" | "up" | "cancel", screenX: number, screenY: number, native: PointerEvent) => boolean | GLRouteVerdict;
54
+ /**
55
+ * The richer GL verdict (hover-time amendment, 2026-07-18): `handled` keeps the
56
+ * boolean's exact meaning (island content claimed the event); `overInteractive`
57
+ * adds the hover fact — the pick chain holds claim-capable content (a mesh with
58
+ * a down/click handler), so a down HERE would be the island's. Plain `boolean`
59
+ * returns stay legal (≡ `{ handled }`), so existing routes never break.
60
+ */
61
+ export interface GLRouteVerdict {
62
+ readonly handled: boolean;
63
+ readonly overInteractive?: boolean;
64
+ }
45
65
  export interface PointerAdapterOpts {
46
66
  readonly glRoute?: GLRoute;
47
67
  }
@@ -24,6 +24,11 @@
24
24
  * dedicated observer; the note below is historical:
25
25
  * picked up on the next Position/Size stamp or membership change; the ResizeObserver
26
26
  * measurement path — a later slice — will arm its own dirt.)
27
+ * - **opacity**: a private observer on `[Opacity]` (attach/detach/value) sets a
28
+ * dirty flag; the change-only pass writes `style.opacity` on the host (absent
29
+ * component = 1 = property cleared). The GL twin is the composite quad's
30
+ * `uOpacity` — a gl widget's host here carries only its DOM chrome, so the
31
+ * two halves fade from the same cell.
27
32
  * - **drag-promote** (P3): a private observer on `[Grab]` membership sets a dirty
28
33
  * flag; a host whose entity holds `Grab` re-parents content→lifted plane and back
29
34
  * on release. Re-parenting MOVES the host node (with its content child), which
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @ice/ground — the P0 ground stratum as ONE WebGPU canvas (design-004 §1
3
+ * as-built amendment, 2026-07-16). three's WebGPURenderer + TSL, WebGL2
4
+ * fallback automatic. Off the react chain: react receives the layer as an
5
+ * OPAQUE factory (`<InfiniteCanvas ground={ground(...)}>`); imperative apps
6
+ * register `layer.reflector` themselves. Passes: dot grid, wires, snap
7
+ * guides — future ground shaders/effects join the same pass registry.
8
+ * (Selection chrome moved OFF the ground 2026-07-17: rings live with the
9
+ * cards, the multi-select union box is P4 dom chrome — always on top.)
10
+ */
11
+ import type { GridConfig, SnapGuidesConfig, WirePreviewBuffer, WiresConfig, World } from "@ice/core";
12
+ import { type GroundLayerHost, type GroundReflector } from "./layer";
13
+ import type { GroundPass } from "./pass";
14
+ import { type GroundRendererLike } from "./renderer";
15
+ export type { GroundFrame, GroundPass } from "./pass";
16
+ export type { GroundReflector } from "./layer";
17
+ export type { GroundRendererLike } from "./renderer";
18
+ export { SoupBuilder, parseCssColor, type Rgba, type TriSoup } from "./passes/soup-collect";
19
+ export { collectGuides } from "./passes/guides-collect";
20
+ export { collectWires } from "./passes/wires-collect";
21
+ export { DEFAULT_GRID_CONFIG, DEFAULT_SNAP_GUIDES_CONFIG, DEFAULT_WIRES_CONFIG, type GridConfig, type SnapGuidesConfig, type WiresConfig, } from "@ice/core";
22
+ export interface GroundContext {
23
+ readonly host: GroundLayerHost;
24
+ readonly world: World;
25
+ /** Connect-drag preview reader (graph boards); omit on boards without wires. */
26
+ readonly readWirePreview?: () => WirePreviewBuffer;
27
+ }
28
+ export interface GroundLayer {
29
+ readonly reflector: GroundReflector;
30
+ /** Live grid re-tune (the react `grid` prop forwards here). */
31
+ configureGrid(cfg: Partial<GridConfig>): void;
32
+ dispose(): void;
33
+ }
34
+ export interface GroundOptions {
35
+ readonly grid?: Partial<GridConfig>;
36
+ readonly wires?: Partial<WiresConfig>;
37
+ readonly guides?: Partial<SnapGuidesConfig>;
38
+ /** Extra app passes, rendered after the built-ins (renderOrder ≥ 3 is yours). */
39
+ readonly passes?: readonly GroundPass[];
40
+ /** Force the WebGL2 backend (debug / e2e A-B runs). */
41
+ readonly forceWebGL?: boolean;
42
+ /** TEST seam: inject a fake renderer (headless orchestration tests). */
43
+ readonly rendererOverride?: GroundRendererLike;
44
+ }
45
+ export type GroundFactory = (ctx: GroundContext) => GroundLayer;
46
+ /**
47
+ * Build the ground layer factory. Usage:
48
+ * react apps: <InfiniteCanvas ground={ground({ grid: {...} })} …>
49
+ * imperative apps: const layer = ground()( { host, world, readWirePreview } );
50
+ * engine.registerReflector(layer.reflector);
51
+ */
52
+ export declare function ground(opts?: GroundOptions): GroundFactory;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The ground LAYER — one reflector, one canvas, one render (design-004 §1
3
+ * as-built 2026-07-16: P0 consolidated from three stacked canvases into a
4
+ * single WebGPU canvas; grid, wires and snap-guides are now PASSES of this
5
+ * layer, and future ground effects join the same registry).
6
+ *
7
+ * Coordinate model: the layer renders in SCREEN CSS-px space — an ortho
8
+ * camera maps [0..w]×[0..h] (y-down) to clip, and passes build their geometry
9
+ * through the kernel `worldToScreen` seam per collect (the proven 2D-canvas
10
+ * semantics: geometry rebuilds on the same dirt that re-stroked the old
11
+ * canvases; the grid stays a per-pixel screen-space shader). The Y-flip
12
+ * question never arises here — `worldToScreen` IS the one conversion (L13).
13
+ *
14
+ * Reflector contract (design-002 §5): post-notify, output-only, never reads
15
+ * layout in flush (the ResizeObserver owns sizing), never writes ECS.
16
+ * `always: true` with a private dirty union — an idle scene renders zero
17
+ * frames; ANY dirty pass re-renders the whole canvas (one draw, same cost
18
+ * class as a pan frame under the old grid). WebGPU init is async: flushes
19
+ * before ready are deferred (flags stay armed), the first ready flush paints.
20
+ */
21
+ import { type ReflectorDef, type World } from "@ice/core";
22
+ import type { GroundPass } from "./pass";
23
+ import type { GroundRendererLike } from "./renderer";
24
+ export interface GroundLayerHost {
25
+ readonly container: HTMLElement;
26
+ readonly contentPlane: HTMLElement;
27
+ }
28
+ export interface GroundReflector extends ReflectorDef {
29
+ /** False while init is pending or after a device/context failure. */
30
+ available(): boolean;
31
+ /** Whole-canvas renders so far (the churn instrument — 0 on an idle scene). */
32
+ redraws(): number;
33
+ }
34
+ export interface GroundLayerInternals {
35
+ reflector: GroundReflector;
36
+ /** Wake every pass + render (config changes, external invalidation). */
37
+ invalidateAll(): void;
38
+ dispose(): void;
39
+ }
40
+ export declare function createLayer(host: GroundLayerHost, world: World, renderer: GroundRendererLike, passes: readonly GroundPass[]): GroundLayerInternals;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * The ground-pass contract (design-004 §1, as-built 2026-07-16: P0 is ONE
3
+ * WebGPU canvas; passes are its draw layers).
4
+ *
5
+ * A pass owns a scene `object` (mesh/group with a TSL node material), arms its
6
+ * own dirty observers, and rebuilds its geometry/uniforms in `collect` — PURE
7
+ * world reads in the layer's SCREEN-px coordinate space (the ortho camera maps
8
+ * CSS px → clip; world→screen goes through the kernel `worldToScreen` seam,
9
+ * Law 13). Heavy per-pass math (tessellation, triangle-soup emission) lives in
10
+ * sibling `*-collect.ts` modules that import ONLY core/kernel, so collectors
11
+ * unit-test under happy-dom with no GPU and no three import.
12
+ */
13
+ import type { World } from "@ice/core";
14
+ import type { Object3D } from "three/webgpu";
15
+ /** Per-flush frame facts every pass collects against. */
16
+ export interface GroundFrame {
17
+ /** CSS-px viewport size. */
18
+ readonly width: number;
19
+ readonly height: number;
20
+ readonly dpr: number;
21
+ /** Engine camera: world top-left + zoom (grid.ts P0 contract). */
22
+ readonly camera: {
23
+ readonly x: number;
24
+ readonly y: number;
25
+ readonly zoom: number;
26
+ };
27
+ }
28
+ export interface GroundPass {
29
+ readonly name: string;
30
+ /** The pass's scene object; the layer adds it once and disposes with the pass. */
31
+ readonly object: Object3D;
32
+ /**
33
+ * Arm dirty observers on the world; `wake()` marks this pass for a
34
+ * re-collect on the next flush. Camera/resize dirt is LAYER-owned (it wakes
35
+ * every pass) — arm only pass-specific sources here. Returns unsubscribers.
36
+ */
37
+ arm(world: World, wake: () => void): Array<() => void>;
38
+ /** Rebuild geometry/uniforms from the world (read-only; screen-px space). */
39
+ collect(world: World, frame: GroundFrame): void;
40
+ dispose(): void;
41
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * The dot-grid pass — TSL port of the raw-WebGL grid (itself verbatim from v1
3
+ * GridRenderer.ts; the fragment math below is that shader re-expressed as a
4
+ * node graph, level loop unrolled in JS). Screen-space: a fullscreen triangle
5
+ * whose vertex stage passes clip coords through; the fragment reconstructs
6
+ * world position from `screenCoordinate` (top-left device px on BOTH backends
7
+ * — three normalizes, so the old manual y-flip is gone) + camera uniforms.
8
+ *
9
+ * One deliberate re-expression: the GLSL if/else fade ladder became the
10
+ * closed form `clamp(min(rise, fall), 0, 1)` — identical piecewise output
11
+ * (rise 0→1 across fadeIn, plateau 1, fall 1→0 across fadeOut) with no
12
+ * branching. Everything else (fract-distance dots, 0.5-device-px AA,
13
+ * max-composited levels) matches term for term.
14
+ */
15
+ import { type GridConfig } from "@ice/core";
16
+ import type { GroundPass } from "../pass";
17
+ export interface GridPass extends GroundPass {
18
+ /** Live re-tune (the react `grid` prop seam) — partial merge over current values. */
19
+ configure(cfg: Partial<GridConfig>): void;
20
+ }
21
+ export declare function createGridPass(initial?: Partial<GridConfig>): GridPass;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Snap-guides collector — the pure ECS→triangles half of the guides pass
3
+ * (v1 SnapGuideRenderer look, carried over from the 2D-canvas reflector it
4
+ * replaces): full-viewport magenta alignment lines (`GuideLine.from == to` ⇒
5
+ * full span — design-003 §6), bounded spans, and equal-spacing bars with 4px
6
+ * end ticks. Screen-px output via the kernel `worldToScreen` seam. NO guide
7
+ * count caps (the pool grows; so does the soup).
8
+ */
9
+ import { type SnapGuidesConfig, type World } from "@ice/core";
10
+ import type { GroundFrame } from "../pass";
11
+ import { type TriSoup } from "./soup-collect";
12
+ export declare const guideQ: import("@vibecook/strata-ecs").Query;
13
+ export declare const barQ: import("@vibecook/strata-ecs").Query;
14
+ export declare function collectGuides(world: World, frame: GroundFrame, config?: SnapGuidesConfig): TriSoup;
@@ -0,0 +1,3 @@
1
+ import { type SnapGuidesConfig } from "@ice/core";
2
+ import type { GroundPass } from "../pass";
3
+ export declare function createGuidesPass(initial?: Partial<SnapGuidesConfig>): GroundPass;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Triangle-soup builders — the PURE half of the geometry passes (no three
3
+ * import; unit-tested under happy-dom). Collectors emit screen-px triangles
4
+ * with per-vertex rgba into a growable soup; the three-side `soup-mesh`
5
+ * uploads them. MSAA (renderer antialias) smooths edges, matching the old 2D
6
+ * canvases' stroke AA closely enough at 1–2 px widths.
7
+ */
8
+ export interface TriSoup {
9
+ /** xyz per vertex (z = 0 — the layer renders flat in screen space). */
10
+ positions: Float32Array;
11
+ /** rgba per vertex, 0-1. */
12
+ colors: Float32Array;
13
+ vertexCount: number;
14
+ }
15
+ export type Rgba = readonly [number, number, number, number];
16
+ export declare class SoupBuilder {
17
+ private pos;
18
+ private col;
19
+ private vert;
20
+ /** Axis-aligned rect (two triangles). */
21
+ rect(x: number, y: number, w: number, h: number, c: Rgba): void;
22
+ /** Stroke segment (a→b) of `width`, flat caps — a quad across the segment normal. */
23
+ segment(ax: number, ay: number, bx: number, by: number, width: number, c: Rgba): void;
24
+ /** Stroke a polyline (interleaved xy) as per-segment quads (smooth-curve joins). */
25
+ polyline(points: ArrayLike<number>, width: number, c: Rgba): void;
26
+ /** Filled disc as a triangle fan (MSAA smooths the rim; 16 segs ≈ round at ≤8 px). */
27
+ disc(cx: number, cy: number, r: number, c: Rgba, segments?: number): void;
28
+ build(): TriSoup;
29
+ }
30
+ /**
31
+ * `rgba(r, g, b, a)` / `rgb(…)` / `#rrggbb` → [r,g,b,a] 0-1 (the WiresConfig
32
+ * colors are CSS strings — v1/2D-canvas heritage). Unknown formats fall back
33
+ * to opaque mid-gray rather than throwing in a reflector path.
34
+ */
35
+ export declare function parseCssColor(css: string): Rgba;
@@ -0,0 +1,8 @@
1
+ import { Mesh } from "three/webgpu";
2
+ import type { TriSoup } from "./soup-collect";
3
+ export interface SoupMesh {
4
+ readonly mesh: Mesh;
5
+ update(soup: TriSoup): void;
6
+ dispose(): void;
7
+ }
8
+ export declare function createSoupMesh(renderOrder: number): SoupMesh;