@vibecook/ice 0.3.0 → 0.6.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 +4 -4
- package/dist/{chunk-2GUYATUM.js → chunk-BFT7YWS2.js} +44 -11
- package/dist/chunk-BFT7YWS2.js.map +1 -0
- package/dist/{chunk-PKK6GLSA.js → chunk-ELCBCTKY.js} +138 -13
- package/dist/chunk-ELCBCTKY.js.map +1 -0
- package/dist/{chunk-5QY7HK6G.js → chunk-JLXETATP.js} +5594 -2712
- package/dist/chunk-JLXETATP.js.map +1 -0
- package/dist/devtools.js +5 -2
- package/dist/devtools.js.map +1 -1
- package/dist/dom.js +16 -2
- package/dist/ground.js +1 -1
- package/dist/index.js +39 -1
- package/dist/r3f.js +3 -3
- package/dist/react.js +5 -3
- package/dist/types/packages/core/src/behavior/compile.d.ts +74 -0
- package/dist/types/packages/core/src/behavior/define-behavior.d.ts +55 -0
- package/dist/types/packages/core/src/behavior/differ.d.ts +91 -0
- package/dist/types/packages/core/src/behavior/harness.d.ts +65 -0
- package/dist/types/packages/core/src/behavior/migrate.d.ts +87 -0
- package/dist/types/packages/core/src/behavior/public-reads.d.ts +31 -0
- package/dist/types/packages/core/src/behavior/runtime.d.ts +88 -0
- package/dist/types/packages/core/src/behavior/types.d.ts +312 -0
- package/dist/types/packages/core/src/behavior/world-view.d.ts +36 -0
- package/dist/types/packages/core/src/catalog/pointer.d.ts +11 -0
- package/dist/types/packages/core/src/catalog/selection-presence.d.ts +10 -0
- package/dist/types/packages/core/src/doc/rename-sweep.d.ts +3 -0
- package/dist/types/packages/core/src/doc/rename.d.ts +7 -0
- package/dist/types/packages/core/src/doc/version-gate.d.ts +14 -1
- package/dist/types/packages/core/src/engine/engine.d.ts +38 -1
- package/dist/types/packages/core/src/engine/guests.d.ts +196 -0
- package/dist/types/packages/core/src/engine/reflectors.d.ts +8 -0
- package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +16 -0
- package/dist/types/packages/core/src/guards/guarded-tx.d.ts +57 -0
- package/dist/types/packages/core/src/index.d.ts +10 -3
- package/dist/types/packages/core/src/input/queue.d.ts +9 -0
- package/dist/types/packages/core/src/schema/meta.d.ts +12 -0
- package/dist/types/packages/core/src/widget/define-widget.d.ts +89 -0
- package/dist/types/packages/core/src/widget/props.d.ts +35 -1
- package/dist/types/packages/core/src/widget/spawn.d.ts +14 -0
- package/dist/types/packages/dom/src/index.d.ts +2 -0
- package/dist/types/packages/dom/src/input-ownership.d.ts +52 -0
- package/dist/types/packages/dom/src/loop.d.ts +9 -0
- package/dist/types/packages/dom/src/pointer-adapter.d.ts +16 -3
- package/dist/types/packages/dom/src/widget-focus.d.ts +58 -0
- package/dist/types/packages/react/src/hooks.d.ts +12 -1
- package/dist/types/packages/react/src/index.d.ts +1 -1
- package/dist/types/packages/react/src/infinite-canvas.d.ts +20 -2
- package/dist/types/packages/react/src/keymap.d.ts +23 -4
- package/package.json +5 -5
- package/dist/chunk-2GUYATUM.js.map +0 -1
- package/dist/chunk-5QY7HK6G.js.map +0 -1
- package/dist/chunk-PKK6GLSA.js.map +0 -1
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `defineBehavior` — the type surface (design-009 §3–§4, the pinned API).
|
|
3
|
+
*
|
|
4
|
+
* ICE's userland surface is a triad: `defineWidget` (faces) · **behaviors**
|
|
5
|
+
* (logic + state) · `defineTool` (input policy). A behavior is the A-Frame-class
|
|
6
|
+
* unit — ONE named declaration carrying a data schema, a REQUIRED store class,
|
|
7
|
+
* and lifecycle hooks the engine runs on a curated phase set.
|
|
8
|
+
*
|
|
9
|
+
* `store:` is the load-bearing word: it routes where data lives, who sees it,
|
|
10
|
+
* what survives, WHICH WRITE VOCABULARY the hooks receive, at which cadence.
|
|
11
|
+
* That last routing is the reason the ctx types below are three interfaces and
|
|
12
|
+
* not one with optional members — a durable behavior must not see `ctx.write`
|
|
13
|
+
* in autocomplete at all, or "store routes everything" is a doc claim rather
|
|
14
|
+
* than a property of the product.
|
|
15
|
+
*/
|
|
16
|
+
import type { Component, Entity, Relation, Resource, Tag } from "@vibecook/strata-ecs";
|
|
17
|
+
import type { GuardedTx } from "../guards/guarded-tx";
|
|
18
|
+
import type { BooleanSpec, EntityKeySpec, EnumSpec, JsonSpec, NumberSpec, PropSpec, StringSpec } from "../widget/props";
|
|
19
|
+
export type BehaviorStore = "durable" | "runtime" | "ephemeral";
|
|
20
|
+
/**
|
|
21
|
+
* The CURATED phase set (design-009 §4.4, BF-D8) — a deliberate subset of the
|
|
22
|
+
* eleven pipeline groups. Authors get the four that mean something to logic;
|
|
23
|
+
* the control sweep (`ctl:*`), `input`, `react` and `cleanup` stay engine
|
|
24
|
+
* vocabulary. `publish` is not a pipeline group at all — it is the post-tick
|
|
25
|
+
* slot where ephemeral mutation is legal.
|
|
26
|
+
*/
|
|
27
|
+
export type BehaviorPhase = "simulate" | "derive" | "present" | "publish";
|
|
28
|
+
/** Legal `phase` per store class — the §4.1 validation table, as data. */
|
|
29
|
+
export declare const BEHAVIOR_PHASES: Readonly<Record<BehaviorStore, readonly BehaviorPhase[]>>;
|
|
30
|
+
/** Default phase per store (the one most declarations want). */
|
|
31
|
+
export declare const BEHAVIOR_DEFAULT_PHASE: Readonly<Record<BehaviorStore, BehaviorPhase>>;
|
|
32
|
+
/** A behavior's declared data: the SAME `p.*` DSL `defineWidget` uses. */
|
|
33
|
+
export type BehaviorSchema = Readonly<Record<string, PropSpec>>;
|
|
34
|
+
type ValueOfSpec<S extends PropSpec> = S extends NumberSpec ? number : S extends BooleanSpec ? boolean : S extends EnumSpec ? string : S extends JsonSpec ? unknown : S extends StringSpec | EntityKeySpec ? string : never;
|
|
35
|
+
/**
|
|
36
|
+
* The hook-facing data view. `p.json` fields arrive PARSED (the cell is a
|
|
37
|
+
* string; the snapshot carries the value) — parsing happens once per delivery,
|
|
38
|
+
* and delivery only runs on change, so the cost is keyed to churn rather than
|
|
39
|
+
* to frame rate. `ctx.write`/`tx.write` serialize back.
|
|
40
|
+
*/
|
|
41
|
+
export type DataOf<Sch extends BehaviorSchema> = {
|
|
42
|
+
readonly [K in keyof Sch]: ValueOfSpec<Sch[K]>;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Anything a behavior may declare in `reads:` — components, tags, relations,
|
|
46
|
+
* resources, or another behavior (its data component). Each kind is routed to
|
|
47
|
+
* a different change mechanism by the compiler (§5.2), which is why the
|
|
48
|
+
* partition is compiler work and not an author concern.
|
|
49
|
+
*/
|
|
50
|
+
export type BehaviorRead = Component | Tag | Relation | Resource | AnyBehaviorDef;
|
|
51
|
+
/** The drained journal summary, valid only for the duration of the hook call. */
|
|
52
|
+
export interface BehaviorChanges {
|
|
53
|
+
/** Rebuild everything: first run, `world.reset()`, coarse writes, or a poll fired. */
|
|
54
|
+
readonly full: boolean;
|
|
55
|
+
/** Journaled entities — re-read current state to act. */
|
|
56
|
+
readonly changed: readonly Entity[];
|
|
57
|
+
/** DESTROYED entities (packed handles; safe as removal keys). */
|
|
58
|
+
readonly removed: readonly Entity[];
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `ctx.world` is a HOST-BUILT WRAPPER, not a `World` alias and not
|
|
62
|
+
* `ReadonlyWorld` (BF-D19): that type is `Omit<World, WorldMutatorName>`, which
|
|
63
|
+
* keeps `.runtime` — the full mutable store — so it is a naming convention, not
|
|
64
|
+
* a sandbox. This interface exposes exactly the read surface and nothing else.
|
|
65
|
+
*
|
|
66
|
+
* Honesty note: point reads through this wrapper are ATTRIBUTION-ONLY — strata
|
|
67
|
+
* enforces declared reads solely at `batch.col()`, so the compiled delivery
|
|
68
|
+
* walk (`ctx.query`) is where enforcement is real.
|
|
69
|
+
*/
|
|
70
|
+
export interface BehaviorWorld {
|
|
71
|
+
read<S>(e: Entity, c: Component<S>): S;
|
|
72
|
+
get<S>(e: Entity, c: Component<S>): S | undefined;
|
|
73
|
+
readField<S, K extends keyof S & string>(e: Entity, c: Component<S>, field: K): S[K] | undefined;
|
|
74
|
+
has(e: Entity, c: Component): boolean;
|
|
75
|
+
hasTag(e: Entity, t: Tag): boolean;
|
|
76
|
+
getRelation(e: Entity, r: Relation): Entity | undefined;
|
|
77
|
+
getRelations(e: Entity, r: Relation): Entity[];
|
|
78
|
+
getReverse(e: Entity, r: Relation): Entity[];
|
|
79
|
+
/** Sibling-sequence version for one parent — ordered relations (petition 8). */
|
|
80
|
+
orderStamp(parent: Entity, r: Relation): number;
|
|
81
|
+
getResource<S>(res: Resource<S>): S | undefined;
|
|
82
|
+
componentsOf(e: Entity): readonly Component[];
|
|
83
|
+
tagsOf(e: Entity): readonly Tag[];
|
|
84
|
+
isAlive(e: Entity): boolean;
|
|
85
|
+
}
|
|
86
|
+
/** A reads-scoped query walk. Terms are restricted to the behavior's `reads`. */
|
|
87
|
+
export interface BehaviorQuery {
|
|
88
|
+
/** Every matching entity. The array is FRESH per call — retaining it is legal. */
|
|
89
|
+
entities(): Entity[];
|
|
90
|
+
/** Iterate matches one at a time (no array allocated). */
|
|
91
|
+
each(fn: (e: Entity) => void): void;
|
|
92
|
+
/** First match, or undefined. */
|
|
93
|
+
first(): Entity | undefined;
|
|
94
|
+
}
|
|
95
|
+
/** Query terms a behavior may build — its declared reads, positively or negatively. */
|
|
96
|
+
export interface BehaviorQuerySpec {
|
|
97
|
+
/** Entities must carry ALL of these (components/tags from `reads`). */
|
|
98
|
+
readonly all?: readonly (Component | Tag | AnyBehaviorDef)[];
|
|
99
|
+
/** Entities must carry NONE of these. */
|
|
100
|
+
readonly none?: readonly (Component | Tag | AnyBehaviorDef)[];
|
|
101
|
+
}
|
|
102
|
+
/** Frame facts a `tick` hook reads. */
|
|
103
|
+
export interface BehaviorFrame {
|
|
104
|
+
readonly dtMs: number;
|
|
105
|
+
readonly tick: number;
|
|
106
|
+
/** Accumulated CLAMPED dt — under-reports across freezes; never wall time. */
|
|
107
|
+
readonly clock: number;
|
|
108
|
+
}
|
|
109
|
+
/** One remote peer's facet of an ephemeral behavior (`ctx.peers()`). */
|
|
110
|
+
export interface PeerFacet<Data> {
|
|
111
|
+
/** The remote presence-peer entity (runtime handle — do not persist). */
|
|
112
|
+
readonly entity: Entity;
|
|
113
|
+
readonly data: Readonly<Data>;
|
|
114
|
+
}
|
|
115
|
+
export interface BehaviorCtxBase<Data> {
|
|
116
|
+
/** The read wrapper (above). */
|
|
117
|
+
readonly world: BehaviorWorld;
|
|
118
|
+
/**
|
|
119
|
+
* A reads-SCOPED query. The framework compiles and caches one Query per
|
|
120
|
+
* distinct spec and walks it ATTRIBUTED — strata's only enforced read path,
|
|
121
|
+
* and its only zero-allocation bulk path. Without this, navigation was
|
|
122
|
+
* instances-outward only and `changed` got abused as a discovery channel.
|
|
123
|
+
*
|
|
124
|
+
* The navigation model, stated: behaviors navigate from their instances
|
|
125
|
+
* outward via relations, the change journal, and reads-scoped queries. There
|
|
126
|
+
* is no global search.
|
|
127
|
+
*/
|
|
128
|
+
query(spec: BehaviorQuerySpec): BehaviorQuery;
|
|
129
|
+
/** This behavior's own instances, this frame (snapshot — do not retain). */
|
|
130
|
+
entities(): readonly Entity[];
|
|
131
|
+
/** The drained journal summary. Meaningful during `changed`; do not retain. */
|
|
132
|
+
changes(): BehaviorChanges;
|
|
133
|
+
/** The journal ∩ this behavior's instances — the exact set. Do not retain. */
|
|
134
|
+
changedEntities(): readonly Entity[];
|
|
135
|
+
/** Durable doc key for an entity (undefined when not doc-bound). */
|
|
136
|
+
keyOf(e: Entity): string | undefined;
|
|
137
|
+
/** The entity for a durable key — undefined while it is not yet projected. */
|
|
138
|
+
entityFor(key: string): Entity | undefined;
|
|
139
|
+
/** Aborts at generation teardown (`world.reset`), detach, or engine dispose. */
|
|
140
|
+
readonly signal: AbortSignal;
|
|
141
|
+
/** Namespaced logging — routed to the host's diagnostics, never raw console. */
|
|
142
|
+
log(message: string, ...rest: readonly unknown[]): void;
|
|
143
|
+
}
|
|
144
|
+
export interface DurableBehaviorCtx<Data> extends BehaviorCtxBase<Data> {
|
|
145
|
+
/**
|
|
146
|
+
* ONE labeled transaction. The transaction opens INSIDE the hook, in-system
|
|
147
|
+
* (design-002-blessed): values land synchronously and this frame's `notify`
|
|
148
|
+
* sees them — never queued past the tick. `label` is a stable MACHINE id
|
|
149
|
+
* (namespaced), never display text: it feeds undo and in-CRDT provenance.
|
|
150
|
+
*
|
|
151
|
+
* Refuses on a read-only document. `tx.setResource` is MASKED (the §9
|
|
152
|
+
* resource-write fence, made mechanical) — a behavior calling it DEV-throws
|
|
153
|
+
* naming the `singleton` v1.1 seam.
|
|
154
|
+
*/
|
|
155
|
+
commit(label: string, fn: (tx: BehaviorTx<Data>) => void, opts?: {
|
|
156
|
+
undoable?: boolean;
|
|
157
|
+
}): void;
|
|
158
|
+
}
|
|
159
|
+
export interface RuntimeBehaviorCtx<Data> extends BehaviorCtxBase<Data> {
|
|
160
|
+
/** Write this behavior's OWN data on `e` (immediate; session-local). */
|
|
161
|
+
write(e: Entity, patch: Partial<Data>): void;
|
|
162
|
+
/** Write a declared runtime-store `writes:` target (immediate). */
|
|
163
|
+
set<S>(e: Entity, c: Component<S>, v: S): void;
|
|
164
|
+
attach(e: Entity, data?: Partial<Data>): void;
|
|
165
|
+
detach(e: Entity): void;
|
|
166
|
+
/** A runtime behavior may also commit durable state (a settle writing finals once). */
|
|
167
|
+
commit(label: string, fn: (tx: BehaviorTx<Data>) => void, opts?: {
|
|
168
|
+
undoable?: boolean;
|
|
169
|
+
}): void;
|
|
170
|
+
}
|
|
171
|
+
export interface EphemeralBehaviorCtx<Data> extends BehaviorCtxBase<Data> {
|
|
172
|
+
/**
|
|
173
|
+
* Publish THIS PEER's facet. No entity argument — the instance IS the local
|
|
174
|
+
* presence peer, and there is exactly one. Legal here because ephemeral hooks
|
|
175
|
+
* run at the publish slot, post-tick, where eph mutation is allowed.
|
|
176
|
+
*/
|
|
177
|
+
write(patch: Partial<Data>): void;
|
|
178
|
+
/** REMOTE peers' facets of this behavior (read-only; `Not(Local)` projections). */
|
|
179
|
+
peers(): readonly PeerFacet<Data>[];
|
|
180
|
+
}
|
|
181
|
+
export type BehaviorCtxFor<S extends BehaviorStore, Data> = S extends "durable" ? DurableBehaviorCtx<Data> : S extends "runtime" ? RuntimeBehaviorCtx<Data> : EphemeralBehaviorCtx<Data>;
|
|
182
|
+
/**
|
|
183
|
+
* The door's transaction: `GuardedTx` plus behavior attachment and typed
|
|
184
|
+
* own-data writes. `setResource` is absent by construction (§4.4's mask).
|
|
185
|
+
*/
|
|
186
|
+
export interface BehaviorTx<Data> extends Omit<GuardedTx, "setResource"> {
|
|
187
|
+
/** Write this behavior's own data on `e` (absolute, whole-value). */
|
|
188
|
+
write(e: Entity, patch: Partial<Data>): void;
|
|
189
|
+
/** Attach a behavior as a DOCUMENT op — syncs, undoes, runs on every peer. */
|
|
190
|
+
attach<D>(e: Entity, behavior: BehaviorHandle<D>, data?: Partial<D>): void;
|
|
191
|
+
detach(e: Entity, behavior: AnyBehaviorDef): void;
|
|
192
|
+
}
|
|
193
|
+
export interface BehaviorHooks<S extends BehaviorStore, Data> {
|
|
194
|
+
/**
|
|
195
|
+
* Instance appeared: attached locally, projected in (REMOTE included), or
|
|
196
|
+
* existing at generation start. Runs before that instance's first `tick`.
|
|
197
|
+
* A throw here marks the INSTANCE failed — no further hooks until re-attach.
|
|
198
|
+
*/
|
|
199
|
+
init?(e: Entity, data: Readonly<Data>, ctx: BehaviorCtxFor<S, Data>): void;
|
|
200
|
+
/**
|
|
201
|
+
* OWN declared data changed since the last delivery — by ANY writer: this
|
|
202
|
+
* behavior's commit, another plugin's tx, UNDO, or a remote peer.
|
|
203
|
+
* Per-instance; `prev` is the last-delivered snapshot. "update is about you."
|
|
204
|
+
*/
|
|
205
|
+
update?(e: Entity, data: Readonly<Data>, prev: Readonly<Data>, ctx: BehaviorCtxFor<S, Data>): void;
|
|
206
|
+
/**
|
|
207
|
+
* The reads-set changed. ONCE PER BEHAVIOR per frame, not per instance —
|
|
208
|
+
* N instances × one tag flip must not mean N journal scans.
|
|
209
|
+
* "changed is about the world."
|
|
210
|
+
*/
|
|
211
|
+
changed?(ctx: BehaviorCtxFor<S, Data>): void;
|
|
212
|
+
/**
|
|
213
|
+
* Per frame per instance — OPT-IN cost. Default is ALL instances (Law 14: an
|
|
214
|
+
* off-screen spring still settles); `tick: { while: "visible" }` is the
|
|
215
|
+
* author's DECLARED suspension. `init`/`update`/`changed`/`dispose` never
|
|
216
|
+
* suspend: culling is not lifecycle, and state must be right the moment it
|
|
217
|
+
* becomes visible again.
|
|
218
|
+
*/
|
|
219
|
+
tick?(e: Entity, data: Readonly<Data>, frame: BehaviorFrame, ctx: BehaviorCtxFor<S, Data>): void;
|
|
220
|
+
/**
|
|
221
|
+
* Instance gone: detached, despawned, disabled, or generation end. `e` may
|
|
222
|
+
* already be dead (world access is DEV-guarded) — this is for cache cleanup.
|
|
223
|
+
* Runs for ALL instances BEFORE `ctx.signal` aborts.
|
|
224
|
+
*/
|
|
225
|
+
dispose?(e: Entity, ctx: BehaviorCtxFor<S, Data>): void;
|
|
226
|
+
}
|
|
227
|
+
export interface BehaviorSpec<S extends BehaviorStore, Sch extends BehaviorSchema> {
|
|
228
|
+
/** REQUIRED — routes everything (§3). */
|
|
229
|
+
readonly store: S;
|
|
230
|
+
/**
|
|
231
|
+
* This behavior's durable writes are COMPUTED from other state. Durable only.
|
|
232
|
+
* Bundles three protections: non-undoable output, the differ, and
|
|
233
|
+
* claim-scoped delivery suppression (§5.3).
|
|
234
|
+
*/
|
|
235
|
+
readonly derived?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* Opt out of suppression: derive live during gestures (costs accepted).
|
|
238
|
+
* `derived` only.
|
|
239
|
+
*/
|
|
240
|
+
readonly deriveDuringGesture?: boolean;
|
|
241
|
+
/** Durable only — the `engine.behavior.<name>.<v>` marker family (§5.6). */
|
|
242
|
+
readonly version?: number;
|
|
243
|
+
readonly schema?: Sch;
|
|
244
|
+
/** Durable only: fromVersion → idempotent absolute transform. */
|
|
245
|
+
readonly migrate?: Readonly<Record<number, (prev: Record<string, unknown>) => Record<string, unknown>>>;
|
|
246
|
+
/** Defaults per store (§4.4); validated against the legal set. */
|
|
247
|
+
readonly phase?: BehaviorPhase;
|
|
248
|
+
/** Per-frame budget request, ms. The breaker caps it at the seam ceiling. */
|
|
249
|
+
readonly budgetMs?: number;
|
|
250
|
+
readonly reads?: readonly BehaviorRead[];
|
|
251
|
+
readonly writes?: readonly Component[];
|
|
252
|
+
/**
|
|
253
|
+
* `tick` scope. `{ while: "visible" }` compiles a `Not(Culled)` term into the
|
|
254
|
+
* chunked tick system — declared suspension, never discovered.
|
|
255
|
+
*/
|
|
256
|
+
readonly tick?: {
|
|
257
|
+
readonly while?: "visible" | "all";
|
|
258
|
+
};
|
|
259
|
+
readonly on?: BehaviorHooks<S, DataOf<Sch>>;
|
|
260
|
+
}
|
|
261
|
+
/** A behavior pre-attachment: `Layout.with({ gapX: 32 })`. */
|
|
262
|
+
export interface BehaviorAttachSpec<Data = Record<string, unknown>> {
|
|
263
|
+
readonly behavior: BehaviorHandle<Data>;
|
|
264
|
+
readonly data: Partial<Data>;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* The compiled declaration. Usable wherever a behavior is expected —
|
|
268
|
+
* `reads: [Glow]`, `behaviors: [Layout]`, `tx.attach(e, Layout)` — plus
|
|
269
|
+
* `.with(data)` for pre-attachment and `.component` for advanced read paths.
|
|
270
|
+
*/
|
|
271
|
+
export interface BehaviorHandle<Data> {
|
|
272
|
+
/** `<namespace>:<name>` — the process-global identity. */
|
|
273
|
+
readonly name: string;
|
|
274
|
+
readonly namespace: string;
|
|
275
|
+
readonly store: BehaviorStore;
|
|
276
|
+
readonly derived: boolean;
|
|
277
|
+
readonly deriveDuringGesture: boolean;
|
|
278
|
+
readonly version: number;
|
|
279
|
+
readonly phase: BehaviorPhase;
|
|
280
|
+
readonly budgetMs: number | undefined;
|
|
281
|
+
readonly schema: BehaviorSchema;
|
|
282
|
+
/** The generated data component (`behavior:<name>`). Advanced read paths. */
|
|
283
|
+
readonly component: Component<Record<string, string | number | boolean>>;
|
|
284
|
+
/** Every field at its declared default (the attach fill). */
|
|
285
|
+
readonly defaults: Readonly<Data>;
|
|
286
|
+
readonly reads: readonly BehaviorRead[];
|
|
287
|
+
readonly writes: readonly Component[];
|
|
288
|
+
readonly migrate: Readonly<Record<number, (prev: Record<string, unknown>) => Record<string, unknown>>>;
|
|
289
|
+
readonly tickWhile: "all" | "visible";
|
|
290
|
+
/**
|
|
291
|
+
* The hook set. MUTABLE by design: a same-shape re-definition (hot reload,
|
|
292
|
+
* a test re-importing a module) refreshes these in place, so the cached
|
|
293
|
+
* handle keeps its component and collector identity while the new code
|
|
294
|
+
* actually runs. Stale-hook HMR is the wart this avoids.
|
|
295
|
+
*/
|
|
296
|
+
on: BehaviorHooks<BehaviorStore, Data>;
|
|
297
|
+
/** Pre-attachment with data: `behaviors: [Layout.with({ gapX: 32 })]`. */
|
|
298
|
+
with(data: Partial<Data>): BehaviorAttachSpec<Data>;
|
|
299
|
+
/** @internal brand — how `reads:` tells a behavior from a component. */
|
|
300
|
+
readonly __behavior: true;
|
|
301
|
+
}
|
|
302
|
+
/** A behavior handle of unknown data shape (registries, `reads:` lists). */
|
|
303
|
+
export type AnyBehaviorDef = BehaviorHandle<any>;
|
|
304
|
+
/**
|
|
305
|
+
* A pre-attachment of unknown data shape — what a LIST of them has to be typed
|
|
306
|
+
* as. `BehaviorAttachSpec<never>` looks like the shape-agnostic form and is
|
|
307
|
+
* not: `Layout.with({gapX: 32})` produces `BehaviorAttachSpec<{gapX: number}>`,
|
|
308
|
+
* which is not assignable to it, so the documented `with()` form would not
|
|
309
|
+
* typecheck in `defineWidget({behaviors})` at all.
|
|
310
|
+
*/
|
|
311
|
+
export type AnyBehaviorAttachSpec = BehaviorAttachSpec<any>;
|
|
312
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Component, Entity, Query, Relation, Resource, SystemCtx, Tag, World } from "@vibecook/strata-ecs";
|
|
2
|
+
import type { AnyBehaviorDef, BehaviorQuery, BehaviorQuerySpec, BehaviorWorld } from "./types";
|
|
3
|
+
export declare function createBehaviorWorld(world: World): BehaviorWorld;
|
|
4
|
+
/**
|
|
5
|
+
* The compiled-query cache for one behavior. Keyed on the term identity list,
|
|
6
|
+
* so the common case (a behavior calling `ctx.query` with the same spec every
|
|
7
|
+
* frame) compiles ONCE — `defineQuery` identity is strata's own cache key for
|
|
8
|
+
* the matched-archetype list, so re-compiling per call would defeat two caches.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createQueryScope(behavior: AnyBehaviorDef, allowed: ReadonlySet<Component | Tag>): (walker: BehaviorWalker, spec: BehaviorQuerySpec) => BehaviorQuery;
|
|
11
|
+
/**
|
|
12
|
+
* Where a behavior's walks and structural ops go, which depends on WHERE it is
|
|
13
|
+
* running — and the difference is not cosmetic.
|
|
14
|
+
*
|
|
15
|
+
* In a pipeline phase the behavior is inside a system: walks go through
|
|
16
|
+
* `SystemCtx` (attributed — strata's enforced read path), and structural ops
|
|
17
|
+
* are DEFERRED to the phase boundary because immediate `world.*` structural
|
|
18
|
+
* mutation throws at iteration depth > 0.
|
|
19
|
+
*
|
|
20
|
+
* At the publish slot there is no running system and no iteration bracket:
|
|
21
|
+
* walks are plain `world.query` (unattributed — nothing to attribute them to),
|
|
22
|
+
* and structural ops are immediate and legal. Ephemeral behaviors live here,
|
|
23
|
+
* which is exactly why eph structural writes are legal for them and illegal
|
|
24
|
+
* for everyone else.
|
|
25
|
+
*/
|
|
26
|
+
export interface BehaviorWalker {
|
|
27
|
+
each(q: Query, fn: (e: Entity) => void): void;
|
|
28
|
+
first(q: Query): Entity | undefined;
|
|
29
|
+
addComponent(e: Entity, c: Component, v: unknown): void;
|
|
30
|
+
removeComponent(e: Entity, c: Component): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function systemWalker(ctx: SystemCtx): BehaviorWalker;
|
|
33
|
+
export declare function publishWalker(world: World): BehaviorWalker;
|
|
34
|
+
/** The term set a behavior may query: its own component ∪ declared components/tags. */
|
|
35
|
+
export declare function allowedQueryTerms(behavior: AnyBehaviorDef, readComponents: readonly Component[], readTags: readonly Tag[]): ReadonlySet<Component | Tag>;
|
|
36
|
+
export type { Relation, Resource };
|
|
@@ -66,6 +66,17 @@ export declare const WentUp: import("@vibecook/strata-ecs").Tag;
|
|
|
66
66
|
export declare const WentCancelled: import("@vibecook/strata-ecs").Tag;
|
|
67
67
|
/** The down was consumed by a widget's own DOM handler — recognizers skip it. */
|
|
68
68
|
export declare const HandledByWidget: import("@vibecook/strata-ecs").Tag;
|
|
69
|
+
/**
|
|
70
|
+
* ONE-TICK: this tick's wheel deltas were ceded to scrollable widget content
|
|
71
|
+
* (design-007 §3.5, petition I4) — the adapter let native scroll proceed and
|
|
72
|
+
* flagged the fact `wheelHandled`. Deliberately NOT the shared `HandledByWidget`
|
|
73
|
+
* latch: that tag rides the persistent mouse-pointer entity, and a same-tick
|
|
74
|
+
* wheel-over-widget + down-over-canvas collision would silently drop the
|
|
75
|
+
* legitimate canvas gesture (design-007 F6). Both wheel consumers honor it —
|
|
76
|
+
* `wheelSpawn` skips the tick, `wheelSystem` treats it as wheel silence.
|
|
77
|
+
* Cleared in cleanup like the Went* transition tags.
|
|
78
|
+
*/
|
|
79
|
+
export declare const WheelHandled: import("@vibecook/strata-ecs").Tag;
|
|
69
80
|
/**
|
|
70
81
|
* PERSISTENT (not one-tick): the pointer is hovering content that would opt a
|
|
71
82
|
* down out — a native interactive / `[data-canvas-interactive]` (DOM) or
|
|
@@ -12,6 +12,16 @@ export declare const SnapTarget: import("@vibecook/strata-ecs").Tag;
|
|
|
12
12
|
* path). Drag a member out and it simply stops being inside.
|
|
13
13
|
*/
|
|
14
14
|
export declare const SweepsContained: import("@vibecook/strata-ecs").Tag;
|
|
15
|
+
/**
|
|
16
|
+
* `interaction.keyboard: "exclusive"` (design-007 §3.1, petitions I1/I4): while
|
|
17
|
+
* a node inside this widget holds browser focus, the engine's keyboard surfaces
|
|
18
|
+
* (keymap shortcuts, the adapter's Space pan modifier) stand down. The ROUTING
|
|
19
|
+
* truth is the DOM — the dom-widgets reflector writes a `data-canvas-keyboard`
|
|
20
|
+
* marker on the widget host and the keymap/adapter read the event-target chain
|
|
21
|
+
* against `document.activeElement`; this tag is the ECS-side mirror of the
|
|
22
|
+
* declaration (queryable by devtools/presence), never a routing input.
|
|
23
|
+
*/
|
|
24
|
+
export declare const KeyboardExclusive: import("@vibecook/strata-ecs").Tag;
|
|
15
25
|
/**
|
|
16
26
|
* Marks an ephemeral presence-peer entity (design-001 §5.6). Rides the presence
|
|
17
27
|
* blob so remote peers project it — a peer query is `[PresencePeer, Not(Local)]`
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MigrationCtx } from "./migrate";
|
|
2
|
+
import type { DocVersionReport } from "./version-gate";
|
|
3
|
+
export interface RenameOutcome {
|
|
4
|
+
/** Old ids folded and tombstoned this open (marker-only docs still stamp). */
|
|
5
|
+
readonly renamed: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function runRenames(ctx: MigrationCtx, report: DocVersionReport): RenameOutcome;
|
|
@@ -36,6 +36,14 @@ export interface DocVersionReport {
|
|
|
36
36
|
readonly newerInDoc: readonly string[];
|
|
37
37
|
/** Prefab ids whose doc marker is OLDER than local (M9 migration input). */
|
|
38
38
|
readonly olderInDoc: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Old ids with a REGISTERED rename, present in the doc and not yet
|
|
41
|
+
* tombstoned → the doc pack version their markers carry (design-008 §4).
|
|
42
|
+
* The rename runner's exact input; non-empty forces at least "migrate".
|
|
43
|
+
* Meta has no delete, so a stale old marker without this classification
|
|
44
|
+
* would brick the doc `readOnly` via `newerInDoc` — the I5 failure mode.
|
|
45
|
+
*/
|
|
46
|
+
readonly renamedInDoc: Readonly<Record<string, number>>;
|
|
39
47
|
}
|
|
40
48
|
export type GateVerdict = "ok" | "readOnly" | "migrate" | "reject";
|
|
41
49
|
/**
|
|
@@ -46,7 +54,12 @@ export type GateVerdict = "ok" | "readOnly" | "migrate" | "reject";
|
|
|
46
54
|
export declare function stampEngineMeta(store: DurableStore): void;
|
|
47
55
|
/** Read the marker keys off a raw (imported, unattached) doc and compare. */
|
|
48
56
|
export declare function readDocVersionReport(doc: LoroDoc): DocVersionReport;
|
|
49
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Build the same comparison report from an envelope header, before Loro
|
|
59
|
+
* import. The header carries no meta, so no tombstone knowledge here —
|
|
60
|
+
* registry aliasing only. This preflight is coarse by design; the
|
|
61
|
+
* authoritative in-document gate re-runs right after import.
|
|
62
|
+
*/
|
|
50
63
|
export declare function readEnvelopeVersionReport(header: EnvelopeHeader): DocVersionReport;
|
|
51
64
|
/** The default M5 policy. A facade policy hook may override (design-005 §6.3). */
|
|
52
65
|
export declare function gateVerdict(report: DocVersionReport): GateVerdict;
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
* setFrameInfo FrameInfo resource — value write, pre-tick
|
|
6
6
|
* world.sync() durable + presence drains land; projections place
|
|
7
7
|
* world.tick(...) the phase-group pipeline (pipeline.ts)
|
|
8
|
+
* guests [2026-08-15, petition I14] the NAMED guest slot:
|
|
9
|
+
* plugin/host frame work under a circuit breaker
|
|
10
|
+
* (guests.ts). BEFORE publish so derived state has
|
|
11
|
+
* settled when presence I/O reads it, and before
|
|
12
|
+
* notify so the same frame reflects it. Guests are
|
|
13
|
+
* fault-ISOLATED — the one tier here that is.
|
|
8
14
|
* publish hooks POST-TICK, PRE-NOTIFY: presence I/O home — eph
|
|
9
15
|
* structural ops are illegal for the whole tick, this
|
|
10
16
|
* step is theirs; `world.*` is legal here (outside the
|
|
@@ -22,6 +28,7 @@
|
|
|
22
28
|
import type { World } from "@vibecook/strata-ecs";
|
|
23
29
|
import type { AnySystem } from "./pipeline";
|
|
24
30
|
import { type FrameControl } from "./frame-control";
|
|
31
|
+
import { type GuestRegistry } from "./guests";
|
|
25
32
|
import { type PhaseGroup } from "./pipeline";
|
|
26
33
|
import { type ReflectorDef } from "./reflectors";
|
|
27
34
|
export type PublishHook = (world: World) => void;
|
|
@@ -38,10 +45,29 @@ export interface FrameTelemetry {
|
|
|
38
45
|
readonly systems: readonly SystemRunRecord[];
|
|
39
46
|
readonly phaseFlushMicros: ReadonlyMap<string, number>;
|
|
40
47
|
readonly reflectorsFlushed: readonly string[];
|
|
48
|
+
/**
|
|
49
|
+
* Total reflector-flush cost, µs (post-notify — OUTSIDE the tick, which is
|
|
50
|
+
* why it can never appear in `phaseFlushMicros`: that map is keyed by strata
|
|
51
|
+
* PHASE names and reflect is not a phase. Devtools' "reflect" lane read that
|
|
52
|
+
* map until 2026-08-15 and therefore never reported at all).
|
|
53
|
+
*/
|
|
54
|
+
readonly reflectMicros: number;
|
|
55
|
+
/** Per-reflector flush cost, µs, for the same frame (registration order). */
|
|
56
|
+
readonly reflectorMicros: ReadonlyMap<string, number>;
|
|
41
57
|
}
|
|
42
58
|
export interface EngineOpts {
|
|
43
59
|
/** Reflector fault sink (default: console.error, skip this frame). */
|
|
44
60
|
readonly onReflectorFault?: (name: string, err: unknown) => void;
|
|
61
|
+
/** Guest fault sink (default: console.error). A guest throwing is contained
|
|
62
|
+
* by the breaker; hosts route this to their own diagnostics. */
|
|
63
|
+
readonly onGuestFault?: (id: string, err: unknown) => void;
|
|
64
|
+
/**
|
|
65
|
+
* Guest breaker notices — suspensions and lenient-mode overruns (default:
|
|
66
|
+
* console.warn). Hosts SHOULD route this: a suspended derived guest means
|
|
67
|
+
* derived state silently stops updating, which reads to a user as a broken
|
|
68
|
+
* document rather than a broken plugin (design-009 §7).
|
|
69
|
+
*/
|
|
70
|
+
readonly onGuestNotice?: (message: string) => void;
|
|
45
71
|
}
|
|
46
72
|
export interface Engine {
|
|
47
73
|
readonly world: World;
|
|
@@ -53,6 +79,13 @@ export interface Engine {
|
|
|
53
79
|
reflectorNames(): readonly string[];
|
|
54
80
|
/** Register a publish-step hook (post-tick, pre-notify presence I/O). Returns a remover. */
|
|
55
81
|
onPublish(hook: PublishHook): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* The guest runtime (petition I14): frame-cadence work the engine did not
|
|
84
|
+
* write — plugin systems, host derived-state jobs, compiled behaviors — run
|
|
85
|
+
* in a named slot between the tick and the publish hooks, each fault-isolated
|
|
86
|
+
* and budgeted by a circuit breaker (guests.ts).
|
|
87
|
+
*/
|
|
88
|
+
readonly guests: GuestRegistry;
|
|
56
89
|
/** Run one full frame. `now` in ms (rAF timestamp or a test counter). */
|
|
57
90
|
step(now: number): void;
|
|
58
91
|
/**
|
|
@@ -65,8 +98,12 @@ export interface Engine {
|
|
|
65
98
|
* hand exactly as before.
|
|
66
99
|
*/
|
|
67
100
|
readonly frame: FrameControl;
|
|
68
|
-
/** Arm per-system run/skip + µs collection (idempotent; costs timer calls per system).
|
|
101
|
+
/** Arm per-system run/skip + µs collection (idempotent; costs timer calls per system).
|
|
102
|
+
* Also puts the guest breaker in LENIENT mode — devtools attached means a
|
|
103
|
+
* debugger breakpoint must not suspend a healthy guest. */
|
|
69
104
|
enableTelemetry(): void;
|
|
105
|
+
/** Tear down guest instances (the facade calls this from `dispose()`). */
|
|
106
|
+
disposeGuests(): void;
|
|
70
107
|
/** The most recent completed frame's readout (undefined until telemetry armed + one step). */
|
|
71
108
|
lastFrame(): FrameTelemetry | undefined;
|
|
72
109
|
}
|