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