@vibecook/ice 0.4.0 → 0.7.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 (39) hide show
  1. package/README.md +4 -4
  2. package/dist/{chunk-3PHPWRB2.js → chunk-55QCOE74.js} +7 -4
  3. package/dist/chunk-55QCOE74.js.map +1 -0
  4. package/dist/{chunk-ENMRSITH.js → chunk-5OU3OWDH.js} +16 -3
  5. package/dist/chunk-5OU3OWDH.js.map +1 -0
  6. package/dist/{chunk-7LZAXQAO.js → chunk-LATXYFMO.js} +6823 -3926
  7. package/dist/chunk-LATXYFMO.js.map +1 -0
  8. package/dist/devtools.js +5 -2
  9. package/dist/devtools.js.map +1 -1
  10. package/dist/dom.js +2 -2
  11. package/dist/ground.js +1 -1
  12. package/dist/index.js +35 -1
  13. package/dist/r3f.js +3 -3
  14. package/dist/react.js +5 -3
  15. package/dist/types/packages/core/src/behavior/compile.d.ts +74 -0
  16. package/dist/types/packages/core/src/behavior/define-behavior.d.ts +56 -0
  17. package/dist/types/packages/core/src/behavior/differ.d.ts +91 -0
  18. package/dist/types/packages/core/src/behavior/harness.d.ts +65 -0
  19. package/dist/types/packages/core/src/behavior/migrate.d.ts +87 -0
  20. package/dist/types/packages/core/src/behavior/public-reads.d.ts +31 -0
  21. package/dist/types/packages/core/src/behavior/runtime.d.ts +102 -0
  22. package/dist/types/packages/core/src/behavior/types.d.ts +358 -0
  23. package/dist/types/packages/core/src/behavior/world-view.d.ts +36 -0
  24. package/dist/types/packages/core/src/engine/engine.d.ts +38 -1
  25. package/dist/types/packages/core/src/engine/guests.d.ts +199 -0
  26. package/dist/types/packages/core/src/engine/reflectors.d.ts +8 -0
  27. package/dist/types/packages/core/src/facade/create-canvas-engine.d.ts +26 -0
  28. package/dist/types/packages/core/src/guards/guarded-tx.d.ts +57 -0
  29. package/dist/types/packages/core/src/index.d.ts +11 -3
  30. package/dist/types/packages/core/src/widget/define-widget.d.ts +28 -0
  31. package/dist/types/packages/core/src/widget/props.d.ts +35 -1
  32. package/dist/types/packages/core/src/widget/spawn.d.ts +14 -0
  33. package/dist/types/packages/dom/src/loop.d.ts +9 -0
  34. package/dist/types/packages/react/src/hooks.d.ts +12 -1
  35. package/dist/types/packages/react/src/index.d.ts +1 -1
  36. package/package.json +31 -65
  37. package/dist/chunk-3PHPWRB2.js.map +0 -1
  38. package/dist/chunk-7LZAXQAO.js.map +0 -1
  39. package/dist/chunk-ENMRSITH.js.map +0 -1
@@ -0,0 +1,91 @@
1
+ import type { Component, Entity, OrderPlace, Relation, Tag, World } from "@vibecook/strata-ecs";
2
+ import type { GuardedTx } from "../guards/guarded-tx";
3
+ export type RecOp = {
4
+ readonly kind: "set";
5
+ readonly e: Entity;
6
+ readonly c: Component;
7
+ readonly v: unknown;
8
+ } | {
9
+ readonly kind: "add";
10
+ readonly e: Entity;
11
+ readonly c: Component;
12
+ readonly v: unknown;
13
+ } | {
14
+ readonly kind: "removeComponent";
15
+ readonly e: Entity;
16
+ readonly c: Component;
17
+ } | {
18
+ readonly kind: "destroy";
19
+ readonly e: Entity;
20
+ } | {
21
+ readonly kind: "addTag";
22
+ readonly e: Entity;
23
+ readonly t: Tag;
24
+ } | {
25
+ readonly kind: "removeTag";
26
+ readonly e: Entity;
27
+ readonly t: Tag;
28
+ } | {
29
+ readonly kind: "setRelation";
30
+ readonly e: Entity;
31
+ readonly r: Relation;
32
+ readonly target: Entity;
33
+ readonly place?: OrderPlace;
34
+ } | {
35
+ readonly kind: "addRelation";
36
+ readonly e: Entity;
37
+ readonly r: Relation;
38
+ readonly target: Entity;
39
+ } | {
40
+ readonly kind: "removeRelation";
41
+ readonly e: Entity;
42
+ readonly r: Relation;
43
+ readonly target?: Entity;
44
+ } | {
45
+ readonly kind: "moveRelation";
46
+ readonly e: Entity;
47
+ readonly r: Relation;
48
+ readonly place: OrderPlace;
49
+ } | {
50
+ readonly kind: "move";
51
+ readonly e: Entity;
52
+ readonly to: {
53
+ x: number;
54
+ y: number;
55
+ };
56
+ readonly animateMs: number;
57
+ };
58
+ export interface DiffDeps {
59
+ readonly world: World;
60
+ /** The DOC's value for a cell — what a commit would actually be compared to. */
61
+ docValue(e: Entity, c: Component): unknown;
62
+ /** The behavior's declared `writes:` targets (the derived-output test). */
63
+ readonly writeTargets: ReadonlySet<Component>;
64
+ }
65
+ export interface DiffResult {
66
+ readonly ops: readonly RecOp[];
67
+ /**
68
+ * The commit touches a declared `writes:` target — it is derived OUTPUT, so
69
+ * it is forced non-undoable. A commit that only touches the behavior's OWN
70
+ * data is an authorial config edit and keeps normal undo.
71
+ */
72
+ readonly derivedOutput: boolean;
73
+ /** Entities this commit wrote — the next drain's subtraction set. */
74
+ readonly writtenEntities: readonly Entity[];
75
+ /**
76
+ * The VALUES the surviving ops commit, per entity per component. The echo
77
+ * snapshot needs these rather than a post-seal world read: a durable write
78
+ * lands in the doc now but projects into the world at the NEXT `world.sync()`,
79
+ * so reading the world here would capture the value the commit is replacing.
80
+ */
81
+ readonly writtenValues: ReadonlyMap<Entity, ReadonlyMap<Component, unknown>>;
82
+ }
83
+ /**
84
+ * Record a DERIVED commit body's ops without performing any of them. Only the
85
+ * derived path buffers: a plain durable behavior commits straight through, so
86
+ * it keeps `tx.spawn` and pays no recording cost.
87
+ */
88
+ export declare function recordDerivedOps(behaviorName: string, build: (rec: GuardedTx) => void): RecOp[];
89
+ export declare function diffOps(ops: readonly RecOp[], deps: DiffDeps): DiffResult;
90
+ /** Replay diffed ops onto the real transaction. */
91
+ export declare function replayOps(ops: readonly RecOp[], tx: GuardedTx): void;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * `createBehaviorHarness` — the testing story, shipped WITH the framework
3
+ * rather than after it (design-009 §7).
4
+ *
5
+ * A behavior is frame-cadence, collaboration-native, transaction-writing code.
6
+ * Testing that without a rig means standing up an engine, a document, a
7
+ * pipeline and a hand-driven clock — enough ceremony that most authors would
8
+ * simply not test the interesting parts. The interesting parts are exactly the
9
+ * ones they will get wrong:
10
+ *
11
+ * - `claim(e)` fakes a live gesture claim, so an author can see their derived
12
+ * behavior go quiet during a drag and coalesce at the settle. Suppression
13
+ * is the single most surprising rule in the framework.
14
+ * - `pair()` gives a SECOND engine on the same document. Convergence bugs are
15
+ * invisible on one peer by definition, and "it works for me" is what a
16
+ * collaborative editor's bug reports look like.
17
+ * - `commits` records what actually reached the store, which is how you tell
18
+ * "the differ dropped it" from "it never ran".
19
+ */
20
+ import { type Entity, type World } from "@vibecook/strata-ecs";
21
+ import { type Engine } from "../engine/engine";
22
+ import { type DocSession } from "../doc/doc-kit";
23
+ import { type BehaviorRuntime } from "./runtime";
24
+ import type { AnyBehaviorDef } from "./types";
25
+ /** One commit the harness observed reaching the store. */
26
+ export interface HarnessCommit {
27
+ readonly undoable: boolean;
28
+ /** `{ behavior, label }` for a `ctx.commit`; undefined for anything else. */
29
+ readonly meta: Record<string, unknown> | undefined;
30
+ }
31
+ export interface BehaviorHarnessOpts {
32
+ /**
33
+ * Attach a document. Default TRUE — durable behaviors need one, and a
34
+ * runtime behavior is unharmed by having it. `false` exercises the doc-less
35
+ * path deliberately (where `ctx.commit` refuses, by design).
36
+ */
37
+ readonly docs?: boolean;
38
+ /** Extra behaviors to register beside the one under test. */
39
+ readonly also?: readonly AnyBehaviorDef[];
40
+ }
41
+ export interface BehaviorHarness {
42
+ readonly world: World;
43
+ readonly engine: Engine;
44
+ readonly runtime: BehaviorRuntime;
45
+ doc(): DocSession | undefined;
46
+ /** Drive `n` frames (default 1). The clock advances 16ms per frame. */
47
+ step(n?: number): void;
48
+ /** A bare durable node with Position + Size, for behaviors to ride. */
49
+ spawn(x?: number, y?: number): Entity;
50
+ /** Attach the behavior under test — durable through a transaction, else direct. */
51
+ attach(e: Entity, data?: Record<string, unknown>): void;
52
+ detach(e: Entity): void;
53
+ /** The behavior's live instances, as the runtime sees them. */
54
+ instances(): readonly Entity[];
55
+ /** Every commit that reached the store since construction. */
56
+ readonly commits: readonly HarnessCommit[];
57
+ /** Simulate a live gesture claim on `e`. Returns the release. */
58
+ claim(e: Entity): () => void;
59
+ /** A SECOND engine on this document — the two-peer rig. */
60
+ pair(): BehaviorHarness;
61
+ /** Exchange updates with every paired harness, both ways. */
62
+ sync(): void;
63
+ dispose(): void;
64
+ }
65
+ export declare function createBehaviorHarness(b: AnyBehaviorDef, opts?: BehaviorHarnessOpts): BehaviorHarness;
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Behavior schema evolution (design-009 §5.5–§5.6, BF-D14) — M13g.
3
+ *
4
+ * Markers are `engine.behavior.<name>.<v>` write-once keys: a monotone key SET,
5
+ * never a mutated cell, so concurrent peers converge on the union instead of
6
+ * racing a single slot. They are a SEPARATE FAMILY from `engine.pack.*`, and
7
+ * that separation is the whole point of this file.
8
+ *
9
+ * **Why not fold them into the pack compare.** The pack compare classifies a
10
+ * doc marker with no local counterpart as `newerInDoc`, which read-onlys the
11
+ * document. For prefabs that is right — an unknown prefab means the doc was
12
+ * written by a newer build. For behaviors it is catastrophic: behaviors ship in
13
+ * PLUGINS, so "no local counterpart" is the ordinary state of every document
14
+ * whose author had a plugin this reader does not. Folding them in would brick a
15
+ * document because a plugin is missing — design-008's exact failure shape,
16
+ * which cost us a release to learn.
17
+ *
18
+ * So behaviors get their own compare with the **absent-is-not-newer** rule:
19
+ * - a locally-unregistered behavior name is IGNORED ENTIRELY (its cells sit
20
+ * in the doc un-projected and dormant — strata's projection is
21
+ * name-registry-driven, so they are inert, not corrupt);
22
+ * - `v > installed` for an INSTALLED behavior triggers per-behavior DORMANCY
23
+ * (refuse hook delivery — projection is version-blind, so newer-shaped data
24
+ * WOULD otherwise flow into older hooks), never a doc verdict;
25
+ * - `v < installed` runs the migration chain.
26
+ *
27
+ * Behavior version state NEVER affects the document's gate verdict. That is a
28
+ * standing guarantee, and the anti-brick test in CI is what keeps it one.
29
+ */
30
+ import type { Entity, World } from "@vibecook/strata-ecs";
31
+ import type { LiveWriter } from "../guards/live-writer";
32
+ import type { AnyBehaviorDef } from "./types";
33
+ export declare const BEHAVIOR_MARKER_PREFIX = "engine.behavior.";
34
+ export declare function behaviorMarkerKey(name: string, version: number): string;
35
+ export interface BehaviorMeta {
36
+ readMeta(key: string): string | undefined;
37
+ metaTransaction(fn: (meta: {
38
+ get(key: string): unknown;
39
+ set(key: string, value: string): void;
40
+ }) => void): void;
41
+ }
42
+ /** The highest marker version present for `name` (0 = never stamped). */
43
+ export declare function docBehaviorVersion(meta: BehaviorMeta, name: string, installed: number): number;
44
+ /** Stamp the marker for `version` if it is not already present (write-once). */
45
+ export declare function stampBehaviorMarker(meta: BehaviorMeta, name: string, version: number): void;
46
+ export type BehaviorVerdict = {
47
+ readonly kind: "ok";
48
+ }
49
+ /** The doc is at a NEWER version than this build installs — refuse delivery. */
50
+ | {
51
+ readonly kind: "dormant";
52
+ readonly docVersion: number;
53
+ readonly installed: number;
54
+ }
55
+ /** The doc is OLDER — the chain has work to do. */
56
+ | {
57
+ readonly kind: "migrate";
58
+ readonly from: number;
59
+ readonly installed: number;
60
+ }
61
+ /** Older, but the chain cannot reach `installed` from there. */
62
+ | {
63
+ readonly kind: "unreachable";
64
+ readonly from: number;
65
+ readonly installed: number;
66
+ };
67
+ export declare function classifyBehaviorVersion(b: AnyBehaviorDef, docVersion: number): BehaviorVerdict;
68
+ export interface MigrateDeps {
69
+ readonly world: World;
70
+ readonly meta: BehaviorMeta;
71
+ /** The doc store, for the one `{undoable: false}` transaction. */
72
+ readonly store: any;
73
+ readonly liveWriter?: LiveWriter;
74
+ /** Every entity currently carrying the behavior's component. */
75
+ carriers(): readonly Entity[];
76
+ }
77
+ /**
78
+ * Run `b`'s migrate chain over every carrier, in ONE non-undoable transaction,
79
+ * then stamp the new marker.
80
+ *
81
+ * A SECOND plan+write pass, not a reuse of the M9 prefab runner: that one
82
+ * filters by `PrefabId`, and a behavior rides entities of any kind — its own
83
+ * component is the only thing that identifies its cells.
84
+ *
85
+ * Returns the number of migrated carriers.
86
+ */
87
+ export declare function runBehaviorMigration(b: AnyBehaviorDef, from: number, deps: MigrateDeps): number;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The CURATED public read surface for behaviors (design-009 §9).
3
+ *
4
+ * The engine catalog is exposed to behaviors DELIBERATELY, not accidentally.
5
+ * Every name on this list is a published contract: it is what a behavior may
6
+ * declare in `reads:` and expect to keep working across engine versions. Adding
7
+ * a name here is a product decision — it is a promise about a shape, not a
8
+ * convenience.
9
+ *
10
+ * Everything else in the catalog is ENGINE VOCABULARY: recognizers, claims,
11
+ * gesture bookkeeping, one-tick markers. Reading it from a behavior is not
12
+ * forbidden (the escape hatch stays open for engine-grade code), but it
13
+ * dev-WARNS, because those shapes change whenever the interaction stack does
14
+ * and no one will think to tell a plugin author.
15
+ *
16
+ * Two notes authors need and will not guess:
17
+ * - One-tick markers (`WentDown`, the `Just*` family) are already CLEARED by
18
+ * the time the publish slot runs, so an ephemeral behavior can never see
19
+ * them. That is not a bug to route around; it is what "one tick" means.
20
+ * - `Grab` and the claim relations are deliberately OFF the list. A behavior
21
+ * that wants to know "is this being dragged right now" is usually asking
22
+ * for claim-scoped suppression, which it already gets for free.
23
+ */
24
+ import type { Component, Relation, Resource, Tag } from "@vibecook/strata-ecs";
25
+ /**
26
+ * The published set. Ordered by what an author reaches for first: geometry,
27
+ * then structure, then state, then the view.
28
+ */
29
+ export declare const PUBLIC_READS: ReadonlySet<Component | Tag | Relation | Resource>;
30
+ /** Is this handle part of the published behavior read surface? */
31
+ export declare function isPublicRead(handle: Component | Tag | Relation | Resource): boolean;
@@ -0,0 +1,102 @@
1
+ import type { Component, Entity, World } from "@vibecook/strata-ecs";
2
+ import type { GuestLedgerRecord, GuestRegistry } from "../engine/guests";
3
+ import type { PhaseGroup } from "../engine/pipeline";
4
+ import type { LiveWriter } from "../guards/live-writer";
5
+ import { type BehaviorMeta } from "./migrate";
6
+ import type { AnyBehaviorDef } from "./types";
7
+ /** The slice of a doc session the runtime needs (structural: tests fake it). */
8
+ export interface BehaviorSession {
9
+ readonly readOnly: boolean;
10
+ readonly liveWriter: LiveWriter;
11
+ readonly store: {
12
+ transaction(fn: (tx: never) => void, opts?: {
13
+ undoable?: boolean;
14
+ }): void;
15
+ keyOf(e: Entity): unknown;
16
+ resolve(key: never): Entity | undefined;
17
+ getComponent<S>(e: Entity, c: Component<S>): S | undefined;
18
+ /** The version-marker surface (behavior/migrate.ts). Absent = no markers. */
19
+ readonly snapshot?: BehaviorMeta;
20
+ metaTransaction?(fn: (meta: {
21
+ get(key: string): unknown;
22
+ set(key: string, value: string): void;
23
+ }) => void): void;
24
+ };
25
+ }
26
+ /**
27
+ * The slice of a presence session an ephemeral behavior needs. Structural so
28
+ * tests can fake it, and a FUNCTION on the opts so it can swap with the doc —
29
+ * presence attaches and detaches with `docs.join`, and a behavior installed
30
+ * before either must simply lie dormant until one exists.
31
+ */
32
+ export interface BehaviorPresence {
33
+ /** The local peer entity. A GETTER upstream: a world reset re-mints it. */
34
+ readonly localPeer: Entity;
35
+ readonly eph: {
36
+ addComponent(e: Entity, c: Component<unknown>, v: unknown): void;
37
+ removeComponent(e: Entity, c: Component): void;
38
+ edit(e: Entity): {
39
+ set(c: Component<unknown>, v: unknown): unknown;
40
+ };
41
+ };
42
+ }
43
+ export interface BehaviorRuntimeOpts {
44
+ readonly world: World;
45
+ readonly engine: {
46
+ addSystems(group: PhaseGroup, ...systems: readonly never[]): () => void;
47
+ readonly guests: GuestRegistry;
48
+ onPublish(hook: (world: World) => void): () => void;
49
+ };
50
+ /** The FORWARDING session seam — the target swaps per doc open/close. */
51
+ readonly session?: () => BehaviorSession | undefined;
52
+ /**
53
+ * The presence seam. Absent (or returning undefined) leaves every ephemeral
54
+ * behavior DORMANT — installed, costing nothing, publishing nothing. That is
55
+ * honest state, not a failure: a presence-less engine has no peer to be.
56
+ */
57
+ readonly presence?: () => BehaviorPresence | undefined;
58
+ readonly onLog?: (behavior: string, message: string, rest: readonly unknown[]) => void;
59
+ /**
60
+ * A contained hook fault. Defaults to `console.error`. Hosts route this: a
61
+ * quarantined instance in a derived behavior means part of the document
62
+ * silently stops updating, which reads to a user as a broken document.
63
+ */
64
+ readonly onFault?: (behavior: string, hook: string, entity: Entity | undefined, err: unknown) => void;
65
+ }
66
+ export interface BehaviorStatus {
67
+ readonly name: string;
68
+ readonly store: string;
69
+ readonly phase: string;
70
+ readonly instances: number;
71
+ /** Instances quarantined by repeated hook throws (BF-D18). */
72
+ readonly failed: number;
73
+ readonly ticking: number;
74
+ readonly suspended: boolean;
75
+ /**
76
+ * The EPHEMERAL singleton was quarantined this generation (I17): its facet
77
+ * is withdrawn and stays withdrawn until a fresh registration. Without this
78
+ * field a quarantined singleton is indistinguishable from a dormant one —
79
+ * withdrawal makes the instance depart, so `failed` reads 0. Always false
80
+ * for durable/runtime behaviors (their quarantine stays visible in `failed`).
81
+ */
82
+ readonly quarantined: boolean;
83
+ }
84
+ export interface BehaviorRegistrationOptions {
85
+ /** Stable host policy key. Explicitly ordered registrations run before unkeyed registrations. */
86
+ readonly orderKey?: string;
87
+ /** Prior breaker state, normally persisted by a host across engine generations. */
88
+ readonly ledger?: GuestLedgerRecord;
89
+ }
90
+ export interface BehaviorRuntime {
91
+ /** Compile + install a behavior. Returns an uninstaller. */
92
+ register(b: AnyBehaviorDef, options?: BehaviorRegistrationOptions): () => void;
93
+ registered(): readonly AnyBehaviorDef[];
94
+ /** Runtime/ephemeral attach from OUTSIDE a hook (the facade surface). */
95
+ attach(e: Entity, b: AnyBehaviorDef, data?: Record<string, unknown>): void;
96
+ detach(e: Entity, b: AnyBehaviorDef): void;
97
+ has(e: Entity, b: AnyBehaviorDef): boolean;
98
+ read(e: Entity, b: AnyBehaviorDef): Record<string, unknown> | undefined;
99
+ list(): readonly BehaviorStatus[];
100
+ dispose(): void;
101
+ }
102
+ export declare function createBehaviorRuntime(opts: BehaviorRuntimeOpts): BehaviorRuntime;