openpencil/crates/op-web-sdk/bindings/ops.ts

600 lines
42 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JsonValue } from "./serde_json/JsonValue";
/**
* A single Action is a 1-key object: `{ "<action_name>": <body> }`.
*
* Examples (all are valid JSON `Action`s):
* - `{ "set": { "$state.count": "$state.count + 1" } }`
* - `{ "fetch": { "url": "/api/x", "into": "$state.u" } }`
* - `{ "push": "/detail/42" }`
*
* The body shape per action is NOT validated here — see `jian-core::action`.
*/
export type Action = { [key in string]?: JsonValue };
export type AlignItems = "start" | "center" | "end" | "stretch";
export type AppConfig = { name: string, version: string, id: string, entry: string | null, capabilities: Array<Capability> | null, theme: string | null, orientation: Orientation | null, backgroundColor: string | null, icon: string | null,
/**
* C19 splash-frame config.
*/
splash: SplashConfig | null,
/**
* C18 ASP web handshake postMessage origin allowlist.
* Only consulted by the web host; strict match; no wildcards.
*/
aspAllowedOrigins: Array<string> | null,
/**
* Auto-update backend descriptor — same idea as `app.icon`:
* the schema declares the source of truth (which release feed
* to consult), the host crate translates it into a concrete
* `Updater` impl. `None` (the default) means the host's own
* fallback wins (typically `NullUpdater`). Only consulted by
* hosts that compile with their respective updater feature
* (`jian-host-desktop/updater`).
*
* TS-side, the type widens to a generic discriminated record so
* third-party hosts can declare their own kinds without forking
* `ops.ts`; the Rust side is the typed `UpdaterConfig` struct
* below.
*/
updater: { kind: string; [key: string]: unknown } | null, };
export type AppLifecycleHooks = { onLaunch: Array<Action> | null, onResume: Array<Action> | null, onBackground: Array<Action> | null, onTerminate: Array<Action> | null, };
export type BlendMode = "normal" | "darken" | "multiply" | "screen" | "overlay" | "lighten" | "difference" | "hue" | "saturation" | "color" | "luminosity";
export type BlurBody = { radius: number, };
/**
* Boolean that may also be an expression.
*/
export type BoolOrExpression = boolean | string;
export type Capability = "storage" | "network" | "camera" | "microphone" | "location" | "notifications" | "clipboard" | "biometric" | "file_system" | "haptic";
/**
* Checkbox with an optional adjacent `label`. `checked` two-way binds
* via `bindings.bind:value`.
*/
export type CheckboxNode = { width: SizingBehavior | null, height: SizingBehavior | null, checked: BoolOrExpression | null, label: string | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type ConversionEntry = { kind: ConversionKind,
/**
* Caller-stable key, e.g. "src/Button.tsx#Button" or "route:/settings".
*/
key: string, sourcePath: string | null,
/**
* Content fingerprint of the source unit; used for incremental conversion.
*/
sourceHash: string | null,
/**
* Master frame id (component) or screen frame id. None for token entries.
*/
nodeId: string | null,
/**
* Source-node id to document-node id mapping for idempotent reruns.
*/
nodeIds: { [key in string]?: string } | null, };
export type ConversionKind = "token" | "component" | "screen";
/**
* Ledger of code-to-design conversion units.
*/
export type ConversionSpec = { entries: Array<ConversionEntry>, };
export type CornerRadius = number | [number, number, number, number];
/**
* One named colour from the design-md colour palette.
*/
export type DesignMdColor = {
/**
* Human label, e.g. "Primary".
*/
name: string,
/**
* `#RRGGBB` hex value.
*/
hex: string,
/**
* How the colour is used, e.g. "buttons and links".
*/
role: string, };
/**
* A structured design-system brief attached to a [`PenDocument`].
*
* [`PenDocument`]: crate::document::PenDocument
*/
export type DesignMdSpec = {
/**
* Original markdown source — kept verbatim for round-trip fidelity.
*/
raw: string, projectName: string | null, visualTheme: string | null, colorPalette: Array<DesignMdColor> | null, typography: DesignMdTypography | null, componentStyles: string | null, layoutPrinciples: string | null, generationNotes: string | null, };
/**
* Typography guidance from the design-md typography section.
*/
export type DesignMdTypography = { fontFamily: string | null, headings: string | null, body: string | null,
/**
* Free-form scale description — usually the whole section text.
*/
scale: string | null, };
export type EllipseNode = { width: SizingBehavior | null, height: SizingBehavior | null, cornerRadius: number | null, innerRadius: number | null, startAngle: number | null, sweepAngle: number | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
/**
* All supported event hook keys. Note: input events (`onChange`, `onSubmit`, `onFocus`,
* `onBlur`) apply only to input-kind nodes. `on_key` is keyboard, `on_reach_end`
* is list-scroll-end, etc.
*/
export type EventHandlers = { onTap: Array<Action> | null, onDoubleTap: Array<Action> | null, onLongPress: Array<Action> | null, onPanStart: Array<Action> | null, onPanUpdate: Array<Action> | null, onPanEnd: Array<Action> | null, onScaleStart: Array<Action> | null, onScaleUpdate: Array<Action> | null, onScaleEnd: Array<Action> | null, onRotateStart: Array<Action> | null, onRotateUpdate: Array<Action> | null, onRotateEnd: Array<Action> | null, onHoverEnter: Array<Action> | null, onHoverLeave: Array<Action> | null, onChange: Array<Action> | null, onSubmit: Array<Action> | null, onFocus: Array<Action> | null, onBlur: Array<Action> | null, onKey: Array<Action> | null, onScroll: Array<Action> | null, onReachEnd: Array<Action> | null, };
/**
* A Tier 1 expression source — represented as a raw string.
*
* Parsing and validation are the responsibility of `jian-core::expression::parser`
* (Plan 2). The schema crate only guarantees the string is present; content-level
* correctness is deferred.
*/
export type Expression = string;
export type FontStyleKind = "normal" | "italic";
export type FontWeight = number | string;
/**
* Forward declaration of PenNode union — defined in `node/mod.rs`.
* We accept `Vec<super::PenNode>` as children.
*/
export type FrameNode = { children: Array<PenNode> | null, imageSearchQuery: string | null, reusable: boolean | null, slot: Array<string> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, width: SizingBehavior | null, height: SizingBehavior | null, layout: LayoutMode | null, gap: NumberOrExpression | null, padding: Padding | null, justifyContent: JustifyContent | null, alignItems: AlignItems | null, clipContent: boolean | null, cornerRadius: CornerRadius | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, };
export type GestureOverrides = {
/**
* When true, this node and its subtree bypass the Arena and receive raw pointer events.
*/
rawPointer: boolean | null, disabled: Expression | null, scrollBehavior: ScrollBehavior | null,
/**
* Override drag threshold in logical pixels (default 8).
*/
dragThreshold: number | null,
/**
* Override long-press duration in ms (default 500).
*/
longPressDuration: number | null,
/**
* Author-explicit Tab-traversal opt-in.
*
* `Some(true)` — node enters the focus chain regardless of its
* semantic role.
* `Some(false)` — node is excluded even if its `semantics.role`
* would otherwise auto-include it (e.g. a decorative `Input`).
* `None` — falls back to the role heuristic (`Button` / `Link`
* / `Input` are auto-included; everything else is opt-in).
*/
focusable: boolean | null, };
export type GradientStop = { offset: number, color: string, };
export type GroupNode = { children: Array<PenNode> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, width: SizingBehavior | null, height: SizingBehavior | null, layout: LayoutMode | null, gap: NumberOrExpression | null, padding: Padding | null, justifyContent: JustifyContent | null, alignItems: AlignItems | null, clipContent: boolean | null, cornerRadius: CornerRadius | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, };
export type IconFontNode = { iconFontName: string, iconFontFamily: string | null, width: SizingBehavior | null, height: SizingBehavior | null, fill: Array<PenFill> | null, stroke: PenStroke | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type ImageFillBody = { url: string, mode: ImageFillMode | null, originalSize: ImageOriginalSize | null, transform: ImageTransform | null, explain: string | null, opacity: number | null, exposure: number | null, contrast: number | null, saturation: number | null, temperature: number | null, tint: number | null, highlights: number | null, shadows: number | null, };
export type ImageFillMode = "fill" | "fit" | "crop" | "tile" | "stretch";
export type ImageFitMode = "fill" | "fit" | "crop" | "tile";
export type ImageNode = { src: string, objectFit: ImageFitMode | null, width: SizingBehavior | null, height: SizingBehavior | null, cornerRadius: CornerRadius | null, effects: Array<PenEffect> | null, exposure: number | null, contrast: number | null, saturation: number | null, temperature: number | null, tint: number | null, highlights: number | null, shadows: number | null, imagePrompt: string | null, imageSearchQuery: string | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type ImageOriginalSize = { width: number, height: number, };
export type ImageTransform = { m00: number, m01: number, m02: number, m10: number, m11: number, m12: number, };
export type JustifyContent = "start" | "center" | "end" | "space_between" | "space_around";
export type LayoutMode = "none" | "vertical" | "horizontal";
export type LineNode = { x2: number | null, y2: number | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type LinearGradientBody = { angle: number | null, stops: Array<GradientStop>, explain: string | null, opacity: number | null, blendMode: BlendMode | null, };
export type LiveRegion = "off" | "polite" | "assertive";
/**
* An ABI version string for the Tier 3 WASM module. The only recognised
* value today is `jian.wasm.v1`; Jian rejects unknown ABI at load time.
*/
export type LogicAbi = string;
export type LogicModuleRef = { id: string, source: string, integrity: string | null, abi: LogicAbi, capabilities: Array<Capability> | null, };
/**
* Uniform-grid mesh gradient (v1). A `rows`×`cols` lattice of
* `MeshVertexStop`s is Gouraud-interpolated across the node's
* round-rect. Mirrors the sibling gradient bodies for the shared
* `explain`/`opacity`/`blend_mode` tail.
*/
export type MeshGradientBody = { rows: number, cols: number, stops: Array<MeshVertexStop>, explain: string | null, opacity: number | null, blendMode: BlendMode | null, };
/**
* A single vertex of a `MeshGradient` grid. `row`/`col` are 0-based
* indices into a `rows`×`cols` lattice; `color` is the Gouraud-shaded
* colour anchored at that vertex.
*/
export type MeshVertexStop = { row: number, col: number, color: string, };
/**
* Declarative per-node navigation: clicking the node pushes/replaces/pops a route.
* Equivalent to `events.on_tap = [{"push": "..."}]` but more editor-discoverable.
*/
export type NavigationRoute = { "push": string } | { "replace": string } | { "pop": null };
export type NodeLifecycleHooks = { onMount: Array<Action> | null, onUnmount: Array<Action> | null, };
/**
* Numeric input with optional +/- steppers. Precise complement to
* `slider`; `value` two-way binds via `bindings.bind:value`. When
* omitted, `min`/`max`/`step` default to none/none/1 at runtime.
*/
export type NumberInputNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Placeholder shown when `value` is empty.
*/
placeholder: string | null, value: NumberOrExpression | null,
/**
* Lucide glyph drawn at the left content edge. See `TextInputNode`.
*/
leadingIcon: string | null,
/**
* Lucide glyph drawn at the right content edge. See `TextInputNode`.
*/
trailingIcon: string | null, min: number | null, max: number | null, step: number | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
/**
* Opacity can be a number or a `$variable` reference string.
*/
export type NumberOrExpression = number | string;
export type Orientation = "portrait" | "landscape" | "auto";
export type Padding = number | [number, number] | [number, number, number, number] | string;
export type PageLifecycleHooks = { onEnter: Array<Action> | null, onLeave: Array<Action> | null, onForeground: Array<Action> | null, onBackground: Array<Action> | null, };
export type PathNode = { iconId: string | null, d: string | null, anchors: Array<PenPathAnchor> | null, closed: boolean | null, width: SizingBehavior | null, height: SizingBehavior | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type PenDocument = {
/**
* Document format version stored in files since v0.x. Always present.
*/
version: string, name: string | null,
/**
* Wire shape: axis-name → ordered theme names. Frozen from v0.x.
*/
themes: { [key in string]?: Array<string> } | null, variables: { [key in string]?: VariableDefinition } | null, pages: Array<PenPage> | null,
/**
* Default-on-deserialize so a multi-page document that carries only
* `pages` (no top-level `children`) still loads — the TS web app's
* whole-document sync (`document.post.ts`) accepts `{version, pages}`
* without a `children` array. Always serialized (even empty `[]`).
*/
children: Array<PenNode>,
/**
* "1.0" when any v1 extension is present; undefined ⇒ legacy v0.x.
*/
formatVersion: string | null,
/**
* App id (reverse-DNS). Required when `app` is set; otherwise optional.
*/
id: string | null, app: AppConfig | null, routes: RoutesConfig | null, state: { [key in string]?: StateEntry } | null, lifecycle: AppLifecycleHooks | null, logicModules: Array<LogicModuleRef> | null,
/**
* Per-document design-system brief (the "design.md"). Optional —
* absent on documents that never authored one.
*/
designMd: DesignMdSpec | null,
/**
* Code-to-design conversion ledger.
*/
conversion: ConversionSpec | null, };
export type PenEffect = { "type": "blur" } & BlurBody | { "type": "background_blur" } & BlurBody | { "type": "shadow" } & ShadowBody;
export type PenFill = { "type": "solid" } & SolidFillBody | { "type": "linear_gradient" } & LinearGradientBody | { "type": "radial_gradient" } & RadialGradientBody | { "type": "mesh_gradient" } & MeshGradientBody | { "type": "shader" } & ShaderFillBody | { "type": "image" } & ImageFillBody;
/**
* Union of all concrete node types.
* Tag is the JSON `"type"` field.
*/
export type PenNode = { "type": "frame" } & FrameNode | { "type": "group" } & GroupNode | { "type": "rectangle" } & RectangleNode | { "type": "ellipse" } & EllipseNode | { "type": "line" } & LineNode | { "type": "polygon" } & PolygonNode | { "type": "path" } & PathNode | { "type": "text" } & TextNode | { "type": "text_input" } & TextInputNode | { "type": "image" } & ImageNode | { "type": "icon_font" } & IconFontNode | { "type": "text_area" } & TextAreaNode | { "type": "select" } & SelectNode | { "type": "switch" } & SwitchNode | { "type": "checkbox" } & CheckboxNode | { "type": "slider" } & SliderNode | { "type": "radio_group" } & RadioGroupNode | { "type": "number_input" } & NumberInputNode | { "type": "progress" } & ProgressNode | { "type": "tabs" } & TabsNode | { "type": "ref" } & RefNode;
export type PenPage = { id: string, name: string, children: Array<PenNode>, state: { [key in string]?: StateEntry } | null, lifecycle: PageLifecycleHooks | null, };
export type PenPathAnchor = { x: number, y: number, handleIn: PenPathHandle | null, handleOut: PenPathHandle | null, pointType: PenPathPointType | null, };
export type PenPathHandle = { x: number, y: number, };
export type PenPathPointType = "corner" | "mirrored" | "independent";
export type PenStroke = { thickness: StrokeThickness, align: StrokeAlign | null, join: StrokeJoin | null, cap: StrokeCap | null, dashPattern: Array<number> | null, dashOffset: number | null, fill: Array<PenFill> | null, };
export type PolygonNode = { polygonCount: number, width: SizingBehavior | null, height: SizingBehavior | null, cornerRadius: number | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type PrimitiveType = "int" | "float" | "number" | "string" | "bool" | "array" | "object" | "date";
/**
* Progress indicator. Display-only (not focusable/keyboard-driven):
* `value` is read from the state graph via `bindings.value`. `max`
* defaults to 100; `indeterminate` shows an animated unknown-progress
* state and ignores `value`.
*/
export type ProgressNode = { width: SizingBehavior | null, height: SizingBehavior | null, value: NumberOrExpression | null, max: number | null, indeterminate: boolean | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type RadialGradientBody = { cx: number | null, cy: number | null, radius: number | null, stops: Array<GradientStop>, explain: string | null, opacity: number | null, blendMode: BlendMode | null, };
/**
* Single-choice radio group. Renders one radio per `option`; the
* selected option `value` two-way binds via `bindings.bind:value`.
*/
export type RadioGroupNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Currently selected option `value`.
*/
value: string | null, options: Array<SelectOption> | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type RectangleNode = { children: Array<PenNode> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, width: SizingBehavior | null, height: SizingBehavior | null, layout: LayoutMode | null, gap: NumberOrExpression | null, padding: Padding | null, justifyContent: JustifyContent | null, alignItems: AlignItems | null, clipContent: boolean | null, cornerRadius: CornerRadius | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, };
export type RefNode = { ref: string, descendants: { [key in string]?: JsonValue } | null, children: Array<PenNode> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type RouteSpec = { pageId: string, preload: boolean | null, guards: Array<Action> | null,
/**
* Path-parameter type declarations (v1.0 additive — 2026-04-24).
* Keys correspond to `:param` placeholders in the route path
* (e.g. path `/detail/:id` → key `id`). The AI Action Surface
* uses these types when synthesising the JsonSchema for the
* derived `open_*(p)` action; runtime does strict type-checking
* on incoming values rather than silent coercion.
*/
params: { [key in string]?: StateType } | null, };
export type RoutesConfig = { entry: string, routes: { [key in string]?: RouteSpec }, transitions: { [key in string]?: Transition } | null, };
export type ScrollBehavior = "auto" | "contain" | "none";
/**
* Dropdown select. The runtime pops an option list; the selected
* option `value` two-way binds via `bindings.bind:value`.
*/
export type SelectNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Shown when no option is selected.
*/
placeholder: string | null,
/**
* Currently selected option `value`.
*/
value: string | null, options: Array<SelectOption> | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
/**
* A single dropdown option: the persisted `value` and its display `label`.
*/
export type SelectOption = { value: string, label: string, };
export type SemanticAction = { name: string, label: string, handler: Array<Action>, };
export type SemanticRole = "button" | "link" | "image" | "text" | "heading" | "input" | "list" | "list_item" | "header" | "nav" | "main" | "dialog" | "alert";
export type SemanticsMeta = { role: SemanticRole | null, label: string | null, hint: string | null, liveRegion: LiveRegion | null, disabled: Expression | null, actions: Array<SemanticAction> | null,
/**
* Author-stable override for the auto-derived AI action name.
* When set, the resulting action name is `<scope>.<aiName>`
* without the auto `_<hash4>` suffix and survives slug recomputes
* across builds. See `2026-04-24-ai-action-surface.md` §3.3-3.4.
*/
aiName: string | null,
/**
* Tool description shown to external AI agents. Overrides the
* auto-generated default; lets authors steer what a model "sees"
* without changing visible UI text.
*/
aiDescription: string | null,
/**
* `true` permanently hides the node's derived action from the AI
* surface (StaticHidden). Defaults to `false`. ConfirmGated /
* StateGated availability are decided dynamically and do **not**
* require this flag — see ai-action-surface.md §4.
*/
aiHidden: boolean | null,
/**
* Historical `aiName` values still accepted by `execute_action`
* for transparent migration after a rename. Aliases are honoured
* at execute time (with `audit reason_code: "alias_used"`) but
* not surfaced by `list_available_actions`. See §9.
*/
aiAliases: Array<string> | null, };
/**
* Native SkSL shader fill (v1). The `sksl` source is stored RAW and is
* treated as untrusted: the renderer entrypoint is the SkSL signature
* `half4 main(float2 fragCoord)`. On compile failure the backend
* degrades to a visible solid fill (the first `color` uniform, else
* mid-gray) and never panics. Mirrors the sibling gradient bodies for
* the shared `opacity`/`blend_mode` tail.
*
* Pencil-flavoured WebGL-GLSL import is an explicit follow-up, NOT v1;
* v1 expects SkSL (Skia's GLSL dialect) verbatim.
*/
export type ShaderFillBody = {
/**
* RAW SkSL source. Entrypoint: `half4 main(float2 fragCoord)`.
*/
sksl: string,
/**
* Optional named-uniform map (`float` / `vec*` / `color`). A
* shader may declare none; absent or empty both mean "no uniforms".
*/
uniforms: { [key in string]?: ShaderUniformValue } | null, explain: string | null, opacity: number | null, blendMode: BlendMode | null, };
/**
* One named uniform value bound into an SkSL shader fill via
* `RuntimeShaderBuilder` at paint time. `untagged` so the wire form is
* the bare JSON scalar/array the author wrote — a number is a `float`,
* a number array is a `vec2`/`vec3`/`vec4`, and a hex string is a
* `color` mapped to a `vec4` (premultiplied RGBA). The named-uniforms
* map on `ShaderFillBody` is optional; a shader may take none.
*/
export type ShaderUniformValue = number | Array<number> | string;
export type ShadowBody = { inner: boolean | null, offsetX: number, offsetY: number, blur: number, spread: number, color: string, };
export type SidedThickness = { top: number | null, right: number | null, bottom: number | null, left: number | null, };
/**
* Sizing value: a number, a fixed keyword, or an arbitrary string (typically `$variable` ref).
*/
export type SizingBehavior = number | SizingKeyword | string;
export type SizingKeyword = "fit_content" | "fill_container";
/**
* Range slider. `value` two-way binds via `bindings.bind:value`;
* `min`/`max`/`step` default to 0/100/1 at runtime when omitted.
*/
export type SliderNode = { width: SizingBehavior | null, height: SizingBehavior | null, min: number | null, max: number | null, step: number | null, value: NumberOrExpression | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type SolidFillBody = { color: string, explain: string | null, opacity: number | null, blendMode: BlendMode | null, };
export type SplashConfig = { background: string | null, image: string | null, text: string | null, minDurationMs: number | null, };
export type StateEntry = { type: StateType, default: JsonValue | null, description: string | null, persist: boolean | null, };
/**
* Recursive state type description.
*/
export type StateType = PrimitiveType | { oneOf: Array<StateType>, } | { array: StateType, } | { object: { [key in string]?: StateType }, };
export type StrokeAlign = "inside" | "center" | "outside";
export type StrokeCap = "none" | "round" | "square";
export type StrokeJoin = "miter" | "bevel" | "round";
export type StrokeThickness = number | [number, number, number, number] | SidedThickness;
export type StyleOverride = { fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, opacity: number | null, };
export type StyledTextSegment = { text: string, fontFamily: string | null, fontSize: number | null, fontWeight: number | null, fontStyle: FontStyleKind | null, fill: string | null, underline: boolean | null, strikethrough: boolean | null, href: string | null, };
/**
* On/off toggle. `checked` two-way binds via `bindings.bind:value`.
*/
export type SwitchNode = { width: SizingBehavior | null, height: SizingBehavior | null, checked: BoolOrExpression | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
/**
* Tabbed panel switcher. Unlike the leaf widgets this is a CONTAINER:
* `children[i]` is the panel for `tabs[i]`. The active tab `value`
* two-way binds via `bindings.bind:value`; only the active panel is
* painted at runtime.
*/
export type TabsNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Tab bar entries; `value` keys the active tab, `label` is shown.
*/
tabs: Array<SelectOption> | null,
/**
* Currently active tab `value`.
*/
value: string | null,
/**
* Panel subtrees, one per tab (parallel to `tabs` by index).
*/
children: Array<PenNode> | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type TextAlign = "left" | "center" | "right" | "justify";
export type TextAlignVertical = "top" | "middle" | "bottom";
/**
* Multi-line writable text input. Like `text_input` but wraps and
* scrolls vertically; two-way binds via `bindings.bind:value`.
*/
export type TextAreaNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Placeholder shown when `value` is empty.
*/
placeholder: string | null,
/**
* Initial value. Two-way binding lives on `bindings.bind:value`.
*/
value: string | null,
/**
* Lucide glyph drawn at the left content edge. See `TextInputNode`.
*/
leadingIcon: string | null,
/**
* Lucide glyph drawn at the right content edge. See `TextInputNode`.
*/
trailingIcon: string | null,
/**
* Visible-line window before the content scrolls (chat-style).
*/
maxVisibleLines: number | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type TextContent = string | Array<StyledTextSegment>;
export type TextGrowth = "auto" | "fixed-width" | "fixed-width-height";
/**
* Single-line text input. Forms / counters need a writable input
* surface that two-way binds via `bindings.bind:value`. The walker
* renders a styled rectangle + caret placeholder; full IME and
* selection-painter wiring lands in the desktop host (Plan 8) once
* the gesture arena gains `Focus` recognizers.
*/
export type TextInputNode = { width: SizingBehavior | null, height: SizingBehavior | null,
/**
* Placeholder shown when `value` is empty. Static text — author
* `bindings.placeholder` if it needs to react to state.
*/
placeholder: string | null,
/**
* Initial value. Two-way binding lives on `bindings.bind:value`,
* which derive lifts into a `set_*` action and the runtime keeps
* in sync with the state graph.
*/
value: string | null,
/**
* Lucide glyph drawn at the left content edge (e.g. `mail`, `lock`).
* The painter insets the text/caret past it so the whole box stays
* one interactive node. `None` = no leading icon.
*/
leadingIcon: string | null,
/**
* Lucide glyph drawn at the right content edge (e.g. `eye` for a
* password reveal). Decorative in Phase 1 (no toggle behaviour).
*/
trailingIcon: string | null, fill: Array<PenFill> | null, stroke: PenStroke | null, effects: Array<PenEffect> | null, cornerRadius: CornerRadius | null, states: WidgetStates | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type TextNode = { width: SizingBehavior | null, height: SizingBehavior | null, content: TextContent, fontFamily: string | null, fontSize: number | null, fontWeight: FontWeight | null, fontStyle: FontStyleKind | null, letterSpacing: number | null, lineHeight: number | null, textAlign: TextAlign | null, textAlignVertical: TextAlignVertical | null, textGrowth: TextGrowth | null, underline: boolean | null, strikethrough: boolean | null, fill: Array<PenFill> | null, effects: Array<PenEffect> | null, state: { [key in string]?: StateEntry } | null, bindings: { [key in string]?: Expression } | null, events: EventHandlers | null, lifecycle: NodeLifecycleHooks | null, semantics: SemanticsMeta | null, gestures: GestureOverrides | null, route: NavigationRoute | null, id: string, name: string | null, role: string | null, explain: string | null, x: number | null, y: number | null, rotation: number | null, opacity: NumberOrExpression | null, enabled: BoolOrExpression | null, visible: boolean | null, locked: boolean | null, flipX: boolean | null, flipY: boolean | null, theme: { [key in string]?: string } | null, };
export type ThemedValue = { value: VariableScalar, theme: { [key in string]?: string } | null, };
export type Transition = "push" | "fade" | "modal" | "none";
export type VariableDefinition = { type: VariableKind, value: VariableValue, };
export type VariableKind = "color" | "number" | "boolean" | "string";
export type VariableScalar = boolean | number | string;
export type VariableValue = VariableScalar | Array<ThemedValue>;
/**
* Authored overrides for the four auto-derived interaction states.
*/
export type WidgetStates = { hover: StyleOverride | null, pressed: StyleOverride | null, focused: StyleOverride | null, disabled: StyleOverride | null, };