//#region src/describe.d.ts /** * `describe()` — a machine-readable API manifest (0.18), the structural antidote * to discoverability: an AI consumer reads GROUND TRUTH from the artifact instead * of reverse-engineering the surface. PURE INTROSPECTION — it instantiates each * built-in node once, reads its registered track targets, and enumerates the core * registries; it never touches `evaluate()` or any cross-frame state, so it has * zero determinism impact. * * The whole point is NO-DRIFT: every section is GENERATED from the live registry * it documents (`Node.listTargets()` → the real `registerTarget` calls, * `listValueTypes()` → the ValueType registry, `easings` → the easing registry), * so the manifest can't fall out of sync with what the framework actually does. * The builder method list and the subpath map are the lone curated parts (runtime * signatures aren't introspectable); a test pins the builder names to the * `TimelineBuilder` interface so even those can't silently drift. * * Tree-shakeable: this lives on its OWN module (re-exported on the * `@glissade/scene/describe` subpath / the `@glissade/browser` bundle), so it is * never pulled onto the base embed path — a scene that never calls `describe()` * pays zero bytes for it. */ /** * One prop in the manifest. The `animatable` flag is the load-bearing * distinction an AI reads to tell "animate this" from "set at construction": * * - An ANIMATABLE prop (`animatable: true`) carries a `target` — a real track * target you bind via `to`/`fromTo`/`set`. Generated from `listTargets()`, * so it is a registered target by construction. * - A CONSTRUCTION prop (`animatable: false`) has NO `target` — it is passed to * the node constructor and NEVER bindable (binding it is rejected by the bind * guard). `required: true` marks one you can't omit (e.g. Image `assetId`). * * This negative space is the manifest's point: it prevents an AI from * attempting to animate a construction-only prop (an `assetId` track, a * `fontFamily` tween) — those are construction-time decisions, not tracks. */ interface DescribedProp { /** The §2.2 value-type id this prop accepts (e.g. `'vec2'`, `'number'`, `'color'`). */ type: string; /** Whether a Track can drive it. `true` ⇒ a `target` is present; `false` ⇒ construction-only, no `target`. */ animatable: boolean; /** The track-target template, `'/'` — present ONLY on animatable props; substitute the node's real id. */ target?: string; /** Component count of the value (`vec2` → 2, scalar → 1); omitted for non-numeric reprs. */ arity?: number; /** Construction-only props: `true` when the constructor REQUIRES it (e.g. Image/Video `assetId`). */ required?: boolean; /** * 0.59 F "manifest conventions": the physical UNIT of the value, when one * applies — e.g. `'degrees'` for rotation, `'seconds'` for a Video time offset. * ADDITIVE + curated (a small per-prop table, like `positionAnchor`), present * ONLY where a unit is meaningful; absent for unitless/px props. Lets a * consumer stop guessing whether `rotation` is degrees or radians. */ unit?: string; } interface DescribedNode { props: { [prop: string]: DescribedProp; }; /** * Runnable, drift-guarded code snippets for this node — present ONLY when * `describe({ examples: true })` is called AND `@glissade/scene/examples` has * been imported (it registers the corpus). Each string is an executable example * the doctest harness runs, so it can't go stale (§0.24 onboarding). */ examples?: readonly string[]; /** * What this node's `position` points at WITHOUT an explicit `anchor` (its * legacy origin): 'center' for shapes, 'baseline-left' for Text, etc. Override * with the base `anchor` prop. Surfaced so consumers stop discovering the * shape-vs-Text anchor mismatch by pixel-measuring. */ positionAnchor: string; /** * 0.59 F ride-along "type-level bindable discovery aid": the prop names on this * node a Track CAN drive (i.e. the animatable ones) — a flat, at-a-glance list * so a consumer sees the bindable surface without filtering `props`. GENERATED * from the same `listTargets()` the animatable props are, so it can't drift. * This is the TYPE-level "can be animated" aid; the INSTANCE-level "is CURRENTLY * bound on THIS node" truth (the anti-false-conclusion guard) is * `instanceProps(node).bound` on `@glissade/scene/diagnostics`. Optional so a * manifest captured before 0.59 (no `bindable`) still type-checks. */ bindable?: string[]; /** * The tree-shakeable subpath this node is imported from, when not the base * `@glissade/scene` index (e.g. the Layout family lives on * `@glissade/scene/layout`). Omitted for base-index nodes. */ subpath?: string; /** * Cut 3 — public INSTANCE methods on this node worth discovering (beyond the * construction `props` + animatable `bindable` surface): the read-accessors an * agent calls on a built node instance (e.g. `Layout.computedSize()` / * `computedBoxes()`). Curated + machine-discoverable so these aren't invisible in * the manifest. Present only where a node exposes such methods (absent for the * plain shapes); a manifest captured before Cut 3 omits it. */ methods?: readonly DescribedMethod[]; } /** * Cut 3 — one public instance method surfaced on a {@link DescribedNode.methods} * list: its `name`, a one-line `purpose`, and the `returns` value shape. These are * READ-accessors on a node instance (not `window.glissade.` globals), so they * are discovered here rather than on the helper/surface tables. */ interface DescribedMethod { /** The method name (call it on the node instance, e.g. `layout.computedBoxes()`). */ name: string; /** One line: what it returns / what it's for. */ purpose: string; /** The return-value shape (e.g. `{ w, h }`, `readonly { x, y, w, h }[]`, `number`). */ returns: string; } /** * One user-defined `defineComponent()` in the manifest (0.36) — a reusable * animated subscene's public prop surface, so an agent/studio sees what it * accepts. Generated from the LIVE component registry, so it can't drift. */ interface DescribedComponent { name: string; /** the component's public props: name → { type, required? } (construction-time). */ props: { [prop: string]: { type: string; required?: boolean; }; }; } interface DescribedBuilderMethod { name: string; signature: string; /** Runnable example snippets — see {@link DescribedNode.examples}. */ examples?: readonly string[]; } /** * One whole-scene starter RECIPE in the manifest (0.63) — a PATTERN an agent * discovers the way it discovers node PRIMITIVES, then instantiates via * `recipe(name, props)`. Its typed props carry a `default` (every recipe is * clean-by-construction at defaults). Populated from the LIVE recipe registry (the * `@glissade/scene/recipes` subpath registers on import), so it can't drift; empty * on a manifest captured without that subpath loaded. */ interface DescribedRecipe { /** The recipe name — pass to `recipe(name, props)`. */ name: string; /** One line: what scaffold it lays down. */ summary?: string; /** the recipe's typed props: name → { type, required?, default? }. */ props: { [prop: string]: { type: string; required?: boolean; default?: unknown; }; }; } /** * One helper/factory in the manifest — the broader builder API beyond the node * taxonomy and the timeline builder. These are the free functions an AI consumer * reaches for (transport, motion-path, clips, snapshot, text-splitting); several * live ABOVE `scene` in the dep graph (player/backend), so describe() can't * import them — this is a CURATED literal, drift-guarded by a test that runs in a * package above scene (`@glissade/browser`'s smoke test) and asserts every name * resolves to a real `window.glissade.` function. */ interface DescribedHelper { /** The exported function name — also the `window.glissade.` global on the IIFE. */ name: string; /** One line: what it's for. */ summary: string; /** The npm subpath to import it from (e.g. `@glissade/player`). On the IIFE it's `window.glissade.`. */ import: string; /** A minimal signature/usage string showing the call shape. */ usage: string; /** Runnable example snippets — see {@link DescribedNode.examples}. */ examples?: readonly string[]; /** * 0.59 F/E "manifest conventions": `true` when this helper needs a real text * MEASURER for correct geometry (splitText/fitText/…). measurer-fail-loud: with * NO real measurer it THROWS `MeasurerRequiredError` BY DEFAULT — pass a real * `{ measurer }` / call setDefaultMeasurer() first, or `{ estimate: true }` to * accept the rough per-character estimate (surfaced as the `estimate` option). * Absent (⇒ not measurer-dependent) for every other helper. */ requiresMeasurer?: boolean; } /** * One entry in the {@link ApiManifest.surface} taxonomy (0.47 "verifiable * ground-truth"): a single window.glissade export, tagged with how a consumer * reaches it. This is the ONE machine-readable truth for the IIFE surface that * both `gs describe --lint` (the drift guard vs the real `@glissade/browser` * bundle) and `gs types --global` (the ambient `window.glissade` `.d.ts`) read — * so the curated helper/node lists can't silently drift from what actually ships * on `window.glissade`, and the no-build author gets a typed global surface. */ interface SurfaceEntry { /** The export name — also the `window.glissade.` global on the IIFE. */ name: string; /** * `'value'` = a runtime binding on the bundle (a class / function / object); * `'type'` = a TS type-only name that erases at runtime (opaque, referenced by * signatures); `'diagnostic'` = a runtime AUTHORING-DIAGNOSTIC function * (`critique`/`validateScene`/`resolveAt`/`instanceProps`/`exportFidelity`) — a * real `window.glissade.` callable that reports PROBLEMS (self-check tooling), * not scene-building surface; `'tool'` (0.61) = a runtime OPERATION function * (`diff`) that transforms/compares scenes and returns a RESULT (a ChangeSet), not * a problem list — distinct from a diagnostic so a consumer never misuses its * output as a defect report. A scalable CATEGORY (diagnostics=problems / * tools=operations / values=authoring surface): an agent BUILDING a scene filters * `kind === 'value'`; a self-check agent filters `kind === 'diagnostic'`. */ kind: 'value' | 'type' | 'diagnostic' | 'tool'; /** `true` when it is reachable as `window.glissade.` on the single-file IIFE bundle. */ iife: boolean; /** How to consume it: `'constructor'` needs `new`, `'function'` is a plain call, `'object'` is a value namespace (e.g. `easings`), `'type'` is type-only. */ form: 'constructor' | 'function' | 'object' | 'type'; /** Documented positional-arg count for a callable (parsed from its usage), when known — absent for value objects and types. */ arity?: number; /** * 0.63.1 — the OPTIONS schema for an opts-taking callable (the `opts` arg is * otherwise opaque, so a no-build agent can't discover `minLegiblePx` / * `exportBound` / …). Present only where a curated schema exists (`assess`, * `critique`). Each entry: the option `name`, its `type` string (a value-type id * or `'string[]'` / an opaque type name), an optional literal `default`, and a * one-line `summary`. ADDITIVE — a manifest captured before 0.63.1 omits it. */ options?: SurfaceOption[]; } /** One entry in a {@link SurfaceEntry.options} schema — a single documented `opts` * field on an options-taking callable. */ interface SurfaceOption { name: string; type: string; default?: string | number | boolean; summary: string; } /** The full machine-readable manifest `describe()` returns. */ interface ApiManifest { version: string; nodes: { [typeName: string]: DescribedNode; }; valueTypes: string[]; /** * 0.64 — a STRUCTURED-TYPE registry: the field shape of the opaque option/return * types an option schema references by name (e.g. `SafeArea[]` in the `critique`/ * `assess` options schema). Each entry maps a type name to its `field → typeString` * shape, so a no-build agent can BUILD a valid value (the 0.63.1 options-schema * lesson, one level down — at the type). ADDITIVE — a manifest captured before 0.64 * omits it. Curated, drift-guarded by `describe.test.ts`. */ types?: { [typeName: string]: { [field: string]: string; }; }; easings: string[]; builder: { methods: DescribedBuilderMethod[]; }; /** * The curated helper/factory surface (0.20) — `createPlayer`/`mount`, * `motionPath`/`followPath`, `clip`/`clipList`, `renderToDataURL`/`snapshotCanvas`, * `splitText`, `Grid`, and the `Stack`/`Row`/`Column` layout factories. Every * `name` is also a `window.glissade.` global on the IIFE. */ helpers: DescribedHelper[]; /** user-defined components registered via defineComponent() (0.36); present * from describe() (possibly empty), absent on manifests captured before 0.36. */ components?: DescribedComponent[]; /** * 0.63 starter-scaffold RECIPES an agent instantiates via `recipe(name, props)`. * Populated from the live registry the `@glissade/scene/recipes` subpath registers * on import (the examples-corpus pattern) — empty until that subpath is loaded, so * a manifest captured without it omits the recipes. ADDITIVE + off the base embed. */ recipes?: DescribedRecipe[]; createScene: string; subpaths: { [entry: string]: string; }; /** * (0.47 "verifiable ground-truth") The window.glissade runtime SURFACE taxonomy: * one machine-readable enumeration of every export a no-build `