@honua/app-platform 0.1.4-beta.0 → 0.1.7-beta.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 (38) hide show
  1. package/app/index.d.ts.map +1 -1
  2. package/app/index.js +4 -0
  3. package/contract/types.d.ts +1 -1
  4. package/contract/types.d.ts.map +1 -1
  5. package/contract/types.js +4 -0
  6. package/core/client.d.ts +6 -0
  7. package/core/client.d.ts.map +1 -1
  8. package/core/client.js +11 -9
  9. package/core/error-classifications.d.ts +17 -0
  10. package/core/error-classifications.d.ts.map +1 -1
  11. package/core/error-classifications.js +17 -0
  12. package/core/error-code-registry.d.ts +18 -1
  13. package/core/error-code-registry.d.ts.map +1 -1
  14. package/core/error-code-registry.js +17 -0
  15. package/core/errors.d.ts +1 -1
  16. package/core/errors.d.ts.map +1 -1
  17. package/core/geoparquet-sql.d.ts +1 -1
  18. package/core/geoparquet-sql.js +2 -2
  19. package/core/stac.d.ts +2 -1
  20. package/core/stac.d.ts.map +1 -1
  21. package/core/stac.js +29 -11
  22. package/core/types.d.ts +15 -2
  23. package/core/types.d.ts.map +1 -1
  24. package/interactions/declarative-contract.d.ts +149 -0
  25. package/interactions/declarative-contract.d.ts.map +1 -0
  26. package/interactions/declarative-contract.js +293 -0
  27. package/interactions/declarative.d.ts +198 -0
  28. package/interactions/declarative.d.ts.map +1 -0
  29. package/interactions/declarative.js +470 -0
  30. package/interactions/feature-state.d.ts +7 -0
  31. package/interactions/feature-state.d.ts.map +1 -1
  32. package/interactions/feature-state.js +10 -2
  33. package/map/maplibre-target.js +1 -1
  34. package/package.json +2 -2
  35. package/realtime/sse.d.ts.map +1 -1
  36. package/realtime/sse.js +7 -1
  37. package/runtime/style-compose.d.ts.map +1 -1
  38. package/runtime/style-compose.js +78 -9
@@ -0,0 +1,293 @@
1
+ /**
2
+ * The declarative interaction vocabulary from the open geospatial MCP
3
+ * standard (geospatial-mcp ADR-0030, `spec/schemas/common/interactions.schema.json`).
4
+ *
5
+ * This module owns ONLY the data shapes, the standard's closed sets, and the
6
+ * pure validation rules — no binding, no DOM, no imports — so the same rules
7
+ * can be reused by the agent-tool vocabulary (`@honua/sdk-js/agent-tools`'
8
+ * `bindInteraction` verb) without dragging the compiler
9
+ * (`./declarative.js`) and its exploration primitives into that bundle.
10
+ *
11
+ * The normative rules encoded here:
12
+ *
13
+ * - **Closed event set** — `featureSelect`/`featureHover` (layer sources),
14
+ * `selection` (widget sources), `change` (control sources),
15
+ * `viewportChange` (map source). Extending it requires a standard ADR.
16
+ * - **Closed verb set** — `setFilter`, `setViewport`, `selectFeature`,
17
+ * `runWidgetQuery`, `setVisibility`. Verbs mutate presentation/exploration
18
+ * state only, never source records.
19
+ * - **Arguments are static JSON plus `$event.*` path substitution.** There is
20
+ * no expression language: no arithmetic, no conditionals, no function
21
+ * calls. A string that starts with `$event` MUST be a well-formed
22
+ * `$event.<segment>[.<segment>…]` path.
23
+ * - **Unique ids** within one interactions block.
24
+ * - **Fan-out cap** — at most {@link HONUA_INTERACTION_FANOUT_CAP} (8,
25
+ * the standard's RECOMMENDED cap) interactions may share one
26
+ * (`on.ref`, `on.event`) pair. Blocks over the cap are rejected, never
27
+ * truncated.
28
+ *
29
+ * Reference resolution ("does `layer:parcels` exist in this document?") is a
30
+ * validation-gate responsibility the standard deliberately leaves to the
31
+ * host; the compiler in `./declarative.js` performs it against the concrete
32
+ * components it is handed.
33
+ *
34
+ * @module
35
+ */
36
+ /** Closed event vocabulary (`$defs/eventName`). */
37
+ export const HONUA_INTERACTION_EVENTS = [
38
+ "featureSelect",
39
+ "featureHover",
40
+ "selection",
41
+ "change",
42
+ "viewportChange",
43
+ ];
44
+ /** Closed action-verb vocabulary (`$defs/actionVerb`). */
45
+ export const HONUA_INTERACTION_VERBS = [
46
+ "setFilter",
47
+ "setViewport",
48
+ "selectFeature",
49
+ "runWidgetQuery",
50
+ "setVisibility",
51
+ ];
52
+ /** Component-reference kinds addressable by an interaction (`$defs/componentRef`). */
53
+ export const HONUA_INTERACTION_REF_KINDS = ["map", "layer", "widget", "control"];
54
+ /**
55
+ * The standard's RECOMMENDED per-(`on.ref`, `on.event`) fan-out cap. A block
56
+ * that exceeds it is rejected whole rather than truncated.
57
+ */
58
+ export const HONUA_INTERACTION_FANOUT_CAP = 8;
59
+ /** The `$event.` prefix that marks the only dynamic element of an argument value. */
60
+ export const HONUA_INTERACTION_EVENT_PATH_PREFIX = "$event.";
61
+ const REF_PATTERN = /^(map|layer:.+|widget:.+|control:.+)$/;
62
+ const EVENT_PATH_PATTERN = /^\$event(\.[^.\s]+)+$/;
63
+ function isRecord(value) {
64
+ return typeof value === "object" && value !== null && !Array.isArray(value);
65
+ }
66
+ /** True for a syntactically valid `map` | `layer:{id}` | `widget:{id}` | `control:{id}` reference. */
67
+ export function isHonuaInteractionRef(value) {
68
+ return typeof value === "string" && REF_PATTERN.test(value);
69
+ }
70
+ /** Splits a component reference into its kind and id. Returns `undefined` for a malformed reference. */
71
+ export function parseHonuaInteractionRef(ref) {
72
+ if (ref === "map")
73
+ return { kind: "map" };
74
+ const separator = ref.indexOf(":");
75
+ if (separator <= 0)
76
+ return undefined;
77
+ const kind = ref.slice(0, separator);
78
+ const id = ref.slice(separator + 1);
79
+ if (id.length === 0)
80
+ return undefined;
81
+ if (kind !== "layer" && kind !== "widget" && kind !== "control")
82
+ return undefined;
83
+ return { kind, id };
84
+ }
85
+ export function isHonuaInteractionEvent(value) {
86
+ return typeof value === "string" && HONUA_INTERACTION_EVENTS.includes(value);
87
+ }
88
+ export function isHonuaInteractionVerb(value) {
89
+ return typeof value === "string" && HONUA_INTERACTION_VERBS.includes(value);
90
+ }
91
+ /**
92
+ * True when a string value is a `$event.*` substitution path. A string that
93
+ * starts with `$event` but is not a well-formed path (`"$event"`,
94
+ * `"$event."`, `"$eventfoo"`) is NOT a path — it is a validation failure,
95
+ * caught by {@link validateHonuaInteraction}.
96
+ */
97
+ export function isHonuaInteractionEventPath(value) {
98
+ return EVENT_PATH_PATTERN.test(value);
99
+ }
100
+ /**
101
+ * Reads the value at a `$event.a.b` path out of an event payload. Returns
102
+ * `undefined` when any segment is missing — there is no error channel and no
103
+ * default, matching the standard's "substitution, not expression" posture.
104
+ */
105
+ export function readHonuaInteractionEventPath(payload, path) {
106
+ const segments = path.slice("$event".length).split(".").filter(Boolean);
107
+ let current = payload;
108
+ for (const segment of segments) {
109
+ if (Array.isArray(current)) {
110
+ const index = Number(segment);
111
+ if (!Number.isInteger(index))
112
+ return undefined;
113
+ current = current[index];
114
+ continue;
115
+ }
116
+ if (!isRecord(current))
117
+ return undefined;
118
+ current = current[segment];
119
+ }
120
+ return current;
121
+ }
122
+ /**
123
+ * Substitutes every `$event.*` string in `args` with the value at that path
124
+ * in `payload`, recursing through nested objects and arrays. Non-string
125
+ * values and strings without the prefix are copied through unchanged — this
126
+ * is the whole of the dynamic surface.
127
+ */
128
+ export function resolveHonuaInteractionArgs(args, payload) {
129
+ if (!args)
130
+ return {};
131
+ return substitute(args, payload);
132
+ }
133
+ function substitute(value, payload) {
134
+ if (typeof value === "string") {
135
+ return value.startsWith(HONUA_INTERACTION_EVENT_PATH_PREFIX)
136
+ ? readHonuaInteractionEventPath(payload, value)
137
+ : value;
138
+ }
139
+ if (Array.isArray(value))
140
+ return value.map((entry) => substitute(entry, payload));
141
+ if (isRecord(value)) {
142
+ const next = {};
143
+ for (const [key, child] of Object.entries(value))
144
+ next[key] = substitute(child, payload);
145
+ return next;
146
+ }
147
+ return value;
148
+ }
149
+ /**
150
+ * Validates ONE interaction against the standard's schema-level rules
151
+ * (shape, closed sets, reference grammar, `$event.*` argument discipline).
152
+ * Fan-out and id uniqueness are block-level rules — see
153
+ * {@link validateHonuaInteractions}.
154
+ */
155
+ export function validateHonuaInteraction(value, path = "interaction") {
156
+ const issues = [];
157
+ if (!isRecord(value)) {
158
+ return [{ code: "invalid-shape", path, message: "An interaction must be a JSON object." }];
159
+ }
160
+ const interactionId = typeof value.id === "string" ? value.id : undefined;
161
+ const issue = (code, field, message) => {
162
+ issues.push({ code, ...(interactionId ? { interactionId } : {}), path: `${path}.${field}`, message });
163
+ };
164
+ if (typeof value.id !== "string" || value.id.length === 0 || value.id.length > 200) {
165
+ issue("invalid-shape", "id", "`id` must be a non-empty string of at most 200 characters.");
166
+ }
167
+ if (value.disabled !== undefined && typeof value.disabled !== "boolean") {
168
+ issue("invalid-shape", "disabled", "`disabled` must be a boolean when present.");
169
+ }
170
+ if (!isRecord(value.on)) {
171
+ issue("invalid-shape", "on", "`on` must be an object with `ref` and `event`.");
172
+ }
173
+ else {
174
+ if (!isHonuaInteractionRef(value.on.ref)) {
175
+ issue("invalid-ref", "on.ref", "`on.ref` must be `map`, `layer:{id}`, `widget:{id}`, or `control:{id}`.");
176
+ }
177
+ if (!isHonuaInteractionEvent(value.on.event)) {
178
+ issue("unknown-event", "on.event", `\`on.event\` must be one of: ${HONUA_INTERACTION_EVENTS.join(", ")}.`);
179
+ }
180
+ for (const key of Object.keys(value.on)) {
181
+ if (key !== "ref" && key !== "event") {
182
+ issue("invalid-shape", `on.${key}`, "`on` accepts only `ref` and `event`.");
183
+ }
184
+ }
185
+ }
186
+ if (!isRecord(value.do)) {
187
+ issue("invalid-shape", "do", "`do` must be an object with `ref` and `verb`.");
188
+ }
189
+ else {
190
+ if (!isHonuaInteractionRef(value.do.ref)) {
191
+ issue("invalid-ref", "do.ref", "`do.ref` must be `map`, `layer:{id}`, `widget:{id}`, or `control:{id}`.");
192
+ }
193
+ if (!isHonuaInteractionVerb(value.do.verb)) {
194
+ issue("unknown-verb", "do.verb", `\`do.verb\` must be one of: ${HONUA_INTERACTION_VERBS.join(", ")}.`);
195
+ }
196
+ if (value.do.args !== undefined) {
197
+ if (!isRecord(value.do.args)) {
198
+ issue("invalid-shape", "do.args", "`do.args` must be a JSON object when present.");
199
+ }
200
+ else {
201
+ collectEventPathIssues(value.do.args, `${path}.do.args`, interactionId, issues);
202
+ }
203
+ }
204
+ for (const key of Object.keys(value.do)) {
205
+ if (key !== "ref" && key !== "verb" && key !== "args") {
206
+ issue("invalid-shape", `do.${key}`, "`do` accepts only `ref`, `verb`, and `args`.");
207
+ }
208
+ }
209
+ }
210
+ for (const key of Object.keys(value)) {
211
+ if (key !== "id" && key !== "on" && key !== "do" && key !== "disabled") {
212
+ issue("invalid-shape", key, "An interaction accepts only `id`, `on`, `do`, and `disabled`.");
213
+ }
214
+ }
215
+ return issues;
216
+ }
217
+ function collectEventPathIssues(value, path, interactionId, issues) {
218
+ if (typeof value === "string") {
219
+ if (value.startsWith("$event") && !isHonuaInteractionEventPath(value)) {
220
+ issues.push({
221
+ code: "invalid-event-path",
222
+ ...(interactionId ? { interactionId } : {}),
223
+ path,
224
+ message: "A `$event` argument must be a `$event.<path>` substitution — the vocabulary has no expression language.",
225
+ });
226
+ }
227
+ return;
228
+ }
229
+ if (Array.isArray(value)) {
230
+ value.forEach((entry, index) => collectEventPathIssues(entry, `${path}[${index}]`, interactionId, issues));
231
+ return;
232
+ }
233
+ if (isRecord(value)) {
234
+ for (const [key, child] of Object.entries(value)) {
235
+ collectEventPathIssues(child, `${path}.${key}`, interactionId, issues);
236
+ }
237
+ }
238
+ }
239
+ /**
240
+ * Validates a whole `interactions` block: every member's schema-level rules
241
+ * plus the two block-level ones (unique ids, fan-out cap). Disabled
242
+ * interactions are validated like any other — they are retained in the
243
+ * document, only skipped at dispatch — but they do NOT count toward the
244
+ * fan-out cap, which bounds live dispatch work.
245
+ */
246
+ export function validateHonuaInteractions(interactions, options = {}) {
247
+ const cap = options.fanOutCap ?? HONUA_INTERACTION_FANOUT_CAP;
248
+ if (!Array.isArray(interactions)) {
249
+ return {
250
+ ok: false,
251
+ issues: [{ code: "invalid-shape", path: "interactions", message: "`interactions` must be an array." }],
252
+ };
253
+ }
254
+ const issues = [];
255
+ const seenIds = new Set();
256
+ const fanOut = new Map();
257
+ interactions.forEach((interaction, index) => {
258
+ const path = `interactions[${index}]`;
259
+ issues.push(...validateHonuaInteraction(interaction, path));
260
+ if (!isRecord(interaction))
261
+ return;
262
+ const id = typeof interaction.id === "string" ? interaction.id : undefined;
263
+ if (id !== undefined) {
264
+ if (seenIds.has(id)) {
265
+ issues.push({
266
+ code: "duplicate-id",
267
+ interactionId: id,
268
+ path: `${path}.id`,
269
+ message: `Interaction id "${id}" is declared more than once in this block.`,
270
+ });
271
+ }
272
+ seenIds.add(id);
273
+ }
274
+ if (interaction.disabled === true)
275
+ return;
276
+ const on = isRecord(interaction.on) ? interaction.on : undefined;
277
+ if (!isHonuaInteractionRef(on?.ref) || !isHonuaInteractionEvent(on?.event))
278
+ return;
279
+ const key = `${on.ref}${on.event}`;
280
+ fanOut.set(key, (fanOut.get(key) ?? 0) + 1);
281
+ });
282
+ for (const [key, count] of fanOut) {
283
+ if (count <= cap)
284
+ continue;
285
+ const [ref, event] = key.split("");
286
+ issues.push({
287
+ code: "fan-out-exceeded",
288
+ path: "interactions",
289
+ message: `${count} interactions share (${ref}, ${event}); the fan-out cap is ${cap}. The block is rejected, not truncated.`,
290
+ });
291
+ }
292
+ return { ok: issues.length === 0, issues };
293
+ }
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `@honua/sdk-js/interactions/declarative` — compiles a standard
3
+ * `interactions[]` block (geospatial-mcp ADR-0030) onto this SDK's existing
4
+ * imperative binding primitives.
5
+ *
6
+ * The standard constrains the *document*, not the dispatcher: "client
7
+ * runtimes implement dispatch by compiling bindings onto whatever imperative
8
+ * primitives they already have". That is exactly what this module does — it
9
+ * owns no event plumbing of its own, only a compile table from the closed
10
+ * (`on.event`, `do.verb`) vocabulary onto `createSelectionHandler`,
11
+ * `createHoverHandler`, `bindTableSelectionToExploration`,
12
+ * `bindFilterControlsToExploration`, and `subscribeExplorationSelector`.
13
+ *
14
+ * Three properties are load-bearing and asserted by the suite:
15
+ *
16
+ * 1. **Validate before binding.** `compileHonuaInteractions` runs the whole
17
+ * block through {@link validateHonuaInteractions} plus reference
18
+ * resolution against the components it was handed. A block with issues
19
+ * binds *nothing* — the standard rejects over-cap and malformed documents
20
+ * rather than truncating them.
21
+ * 2. **Actions never emit events.** A verb-driven state change must not
22
+ * re-enter the dispatcher. Two mechanisms enforce it: every compiled
23
+ * handler is skipped while an action is in flight (`#dispatching`), and
24
+ * exploration-backed subscriptions run with the view controller's default
25
+ * `includeSelf: false`, so a change this compiler originated is never
26
+ * delivered back to this compiler. There is no cycle detection because
27
+ * there can be no cycle.
28
+ * 3. **Unsupported pairs are named, never dropped.** When a binding's
29
+ * (`on.event`, `do.verb`) pair — or the component behind a ref — has no
30
+ * primitive to compile onto, it lands in
31
+ * {@link HonuaCompiledInteractions.unsupported} with the pair spelled out,
32
+ * so a host can surface a real "this runtime cannot honor that binding"
33
+ * message instead of silently rendering an inert app.
34
+ *
35
+ * @experimental The compiler surface tracks geospatial-mcp ADR-0030 while
36
+ * that ADR is Proposed; the component-adapter shapes may gain fields.
37
+ *
38
+ * @module
39
+ */
40
+ import type { SourceId } from "../contract/types.js";
41
+ import type { ExplorationViewController, FilterClause } from "../exploration/types.js";
42
+ import type { InteractiveMap } from "./feature-state.js";
43
+ import { type HonuaInteractionEvent, type HonuaInteractionIssue, type HonuaInteractionVerb } from "./declarative-contract.js";
44
+ export * from "./declarative-contract.js";
45
+ /**
46
+ * A layer addressed as `layer:{id}`. `map` is what the
47
+ * `featureSelect`/`featureHover` primitives bind to; the optional methods are
48
+ * the layer-scoped action targets.
49
+ */
50
+ export interface HonuaInteractionLayerComponent {
51
+ /** MapLibre-style interactive map this layer renders on — the event source for `featureSelect`/`featureHover`. */
52
+ readonly map?: InteractiveMap;
53
+ /** Source id feature-state is written to. Defaults to the ref's id. */
54
+ readonly sourceId?: SourceId;
55
+ /** Rendered layer id clicks/hovers are observed on. Defaults to the ref's id. */
56
+ readonly layerId?: string;
57
+ /** Source layer, for vector-tile sources. */
58
+ readonly sourceLayer?: string;
59
+ /** Select multiple features rather than replacing on each click. @default false */
60
+ readonly multiSelect?: boolean;
61
+ /** `setVisibility` target. */
62
+ setVisibility?(visible: boolean): void;
63
+ /** `setFilter` target. When absent, `setFilter` falls back to the shared exploration filter keyed by this ref's id. */
64
+ setFilter?(clause: FilterClause | undefined): void;
65
+ }
66
+ export interface HonuaInteractionViewport {
67
+ readonly bbox?: readonly [number, number, number, number];
68
+ readonly center?: readonly [number, number];
69
+ readonly zoom?: number;
70
+ readonly pitch?: number;
71
+ readonly bearing?: number;
72
+ }
73
+ /** The `map` component. */
74
+ export interface HonuaInteractionMapComponent {
75
+ /**
76
+ * `setViewport` target. When absent, `setViewport` falls back to the
77
+ * exploration view's `setExtent` for `bbox`-shaped arguments.
78
+ */
79
+ setViewport?(viewport: HonuaInteractionViewport): void;
80
+ }
81
+ /** A widget addressed as `widget:{id}`. */
82
+ export interface HonuaInteractionWidgetComponent {
83
+ /** `runWidgetQuery` target. There is no exploration-level fallback for this verb. */
84
+ runQuery?(request: Readonly<Record<string, unknown>>): void;
85
+ /** `setFilter` target. When absent, falls back to the shared exploration filter keyed by this ref's id. */
86
+ setFilter?(clause: FilterClause | undefined): void;
87
+ /** `setVisibility` target. */
88
+ setVisibility?(visible: boolean): void;
89
+ }
90
+ /**
91
+ * A control addressed as `control:{id}`. A control's `change` event is read
92
+ * off the shared exploration filter slice keyed by the control's id — that is
93
+ * the same seam `bindFilterControlsToExploration` writes through, so a
94
+ * filter bar wired with that primitive needs no extra adapter here.
95
+ */
96
+ export interface HonuaInteractionControlComponent {
97
+ /** `setVisibility` target. */
98
+ setVisibility?(visible: boolean): void;
99
+ /** `setFilter` target. When absent, falls back to the shared exploration filter keyed by this ref's id. */
100
+ setFilter?(clause: FilterClause | undefined): void;
101
+ }
102
+ /**
103
+ * The components an `interactions[]` block's refs resolve against. A ref that
104
+ * resolves to no component is a validation failure (`unresolved-ref`), matching
105
+ * the standard's "an implementation MUST reject a binding whose `ref` does not
106
+ * resolve within the document".
107
+ *
108
+ * `control:` and `widget:` refs may be declared with an empty object when the
109
+ * component needs no adapter — their events and their `setFilter` action both
110
+ * run through the shared exploration view.
111
+ */
112
+ export interface HonuaInteractionComponents {
113
+ readonly map?: HonuaInteractionMapComponent;
114
+ readonly layers?: Readonly<Record<string, HonuaInteractionLayerComponent>>;
115
+ readonly widgets?: Readonly<Record<string, HonuaInteractionWidgetComponent>>;
116
+ readonly controls?: Readonly<Record<string, HonuaInteractionControlComponent>>;
117
+ }
118
+ /** One binding this runtime cannot honor, with the pair named. */
119
+ export interface HonuaUnsupportedInteraction {
120
+ readonly interactionId: string;
121
+ readonly on: {
122
+ readonly ref: string;
123
+ readonly event: HonuaInteractionEvent;
124
+ };
125
+ readonly do: {
126
+ readonly ref: string;
127
+ readonly verb: HonuaInteractionVerb;
128
+ };
129
+ /** `"{event} -> {verb}"`, the pair as the standard spells it. */
130
+ readonly pair: string;
131
+ readonly reason: string;
132
+ }
133
+ /** One binding that compiled onto a primitive. */
134
+ export interface HonuaCompiledInteractionBinding {
135
+ readonly interactionId: string;
136
+ readonly on: {
137
+ readonly ref: string;
138
+ readonly event: HonuaInteractionEvent;
139
+ };
140
+ readonly do: {
141
+ readonly ref: string;
142
+ readonly verb: HonuaInteractionVerb;
143
+ };
144
+ readonly pair: string;
145
+ /** Disposes just this binding. Idempotent. */
146
+ dispose(): void;
147
+ }
148
+ export interface HonuaCompiledInteractions {
149
+ /** True when the block validated and every non-disabled binding compiled. */
150
+ readonly ok: boolean;
151
+ /** Validation failures. When non-empty, nothing was bound. */
152
+ readonly issues: ReadonlyArray<HonuaInteractionIssue>;
153
+ readonly bindings: ReadonlyArray<HonuaCompiledInteractionBinding>;
154
+ readonly unsupported: ReadonlyArray<HonuaUnsupportedInteraction>;
155
+ /** Interaction ids skipped because `disabled: true`. */
156
+ readonly disabled: ReadonlyArray<string>;
157
+ /** Disposes every compiled binding. Idempotent. */
158
+ dispose(): void;
159
+ }
160
+ export interface CompileHonuaInteractionsOptions {
161
+ /**
162
+ * The exploration view this compiler owns — every action verb writes
163
+ * through it, and every exploration-backed event source subscribes on it.
164
+ *
165
+ * **Pass a view bound for the compiler alone** (`ctx.connectView({ id:
166
+ * "interactions", role: "custom" })`), not a view a widget also dispatches
167
+ * through. That is what makes "actions never emit events" structural rather
168
+ * than best-effort: an `ExplorationViewController` delivers a bound view its
169
+ * own notifications only under `includeSelf`, so a state change this
170
+ * compiler originated is never routed back into this compiler, while a real
171
+ * user gesture published by any *other* view is.
172
+ */
173
+ readonly view: ExplorationViewController;
174
+ readonly components?: HonuaInteractionComponents;
175
+ /** Per-(`on.ref`, `on.event`) fan-out cap. @default 8 */
176
+ readonly fanOutCap?: number;
177
+ /**
178
+ * Observes each dispatched action. Called after the verb ran; never called
179
+ * for a suppressed (action-originated) event, which is the whole point of
180
+ * "actions never emit events".
181
+ */
182
+ readonly onDispatch?: (dispatch: HonuaInteractionDispatch) => void;
183
+ }
184
+ export interface HonuaInteractionDispatch {
185
+ readonly interactionId: string;
186
+ readonly event: HonuaInteractionEvent;
187
+ readonly verb: HonuaInteractionVerb;
188
+ readonly ref: string;
189
+ /** Arguments after `$event.*` substitution. */
190
+ readonly args: Readonly<Record<string, unknown>>;
191
+ }
192
+ /**
193
+ * Compiles an `interactions[]` block onto live components. Returns disposers;
194
+ * never throws for a bad document — a malformed or over-cap block comes back
195
+ * as `{ ok: false, issues }` with nothing bound.
196
+ */
197
+ export declare function compileHonuaInteractions(interactions: unknown, options: CompileHonuaInteractionsOptions): HonuaCompiledInteractions;
198
+ //# sourceMappingURL=declarative.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"declarative.d.ts","sourceRoot":"","sources":["../../../src/interactions/declarative.ts"],"names":[],"mappings":"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"}