api_binding_ui 1.0.0 → 2.0.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 (75) hide show
  1. package/README.md +74 -384
  2. package/SKILL.md +49 -294
  3. package/dist/framework/contract.d.ts +268 -0
  4. package/dist/framework/engine.d.ts +88 -0
  5. package/dist/framework/expr.d.ts +34 -0
  6. package/dist/framework/loadCatalog.d.ts +6 -0
  7. package/dist/framework/names.d.ts +7 -0
  8. package/dist/framework/openapi.d.ts +7 -0
  9. package/dist/framework/runner.d.ts +33 -0
  10. package/dist/framework/schema.d.ts +22 -0
  11. package/dist/framework/store.d.ts +17 -0
  12. package/dist/framework/validate.d.ts +7 -0
  13. package/dist/index-e9twc25y.js +754 -0
  14. package/dist/index-e9twc25y.js.map +16 -0
  15. package/dist/index.d.ts +11 -26
  16. package/dist/index.js +345 -3790
  17. package/dist/index.js.map +14 -0
  18. package/dist/runtime/BindingProvider.d.ts +42 -0
  19. package/dist/runtime/BindingScreen.d.ts +23 -0
  20. package/dist/runtime/hooks.d.ts +22 -0
  21. package/dist/runtime/screenStore.d.ts +7 -0
  22. package/dist/studio/ApiPicker.d.ts +15 -0
  23. package/dist/studio/BindingStudio.d.ts +21 -0
  24. package/dist/studio/BindingWidget.d.ts +9 -0
  25. package/dist/studio/Collapse.d.ts +6 -0
  26. package/dist/studio/ComponentList.d.ts +10 -0
  27. package/dist/studio/FindAnything.d.ts +7 -0
  28. package/dist/studio/Inspector.d.ts +12 -0
  29. package/dist/studio/Navigator.d.ts +17 -0
  30. package/dist/studio/SettingsPanel.d.ts +15 -0
  31. package/dist/studio/Tip.d.ts +23 -0
  32. package/dist/studio/editors/ConsumeEditor.d.ts +4 -0
  33. package/dist/studio/editors/RequestInputs.d.ts +11 -0
  34. package/dist/studio/editors/ScopedEditor.d.ts +3 -0
  35. package/dist/studio/editors/SlotSection.d.ts +3 -0
  36. package/dist/studio/editors/SourceSelect.d.ts +10 -0
  37. package/dist/studio/editors/StepCard.d.ts +12 -0
  38. package/dist/studio/editors/TriggerEditor.d.ts +4 -0
  39. package/dist/studio/editors/types.d.ts +14 -0
  40. package/dist/studio/icons.d.ts +65 -0
  41. package/dist/studio/index.css +2021 -0
  42. package/dist/studio/index.d.ts +3 -0
  43. package/dist/studio/index.js +2806 -0
  44. package/dist/studio/index.js.map +33 -0
  45. package/dist/studio/useBindings.d.ts +20 -0
  46. package/dist/studio/useCatalog.d.ts +15 -0
  47. package/dist/studio/useGlide.d.ts +11 -0
  48. package/dist/studio/useSettings.d.ts +8 -0
  49. package/package.json +20 -9
  50. package/spec/FRAMEWORK.md +271 -0
  51. package/dist/api-binding-ui.css +0 -244
  52. package/dist/components/ApiBindingWorkspace.d.ts +0 -10
  53. package/dist/components/ApiMappings.d.ts +0 -21
  54. package/dist/components/BindApiButton.d.ts +0 -41
  55. package/dist/components/RequestPreview.d.ts +0 -7
  56. package/dist/components/SimulationDialog.d.ts +0 -8
  57. package/dist/components/WorkspaceContext.d.ts +0 -7
  58. package/dist/components/ui/AppGrid.d.ts +0 -27
  59. package/dist/components/ui/Badge.d.ts +0 -9
  60. package/dist/components/ui/Banner.d.ts +0 -8
  61. package/dist/components/ui/Button.d.ts +0 -31
  62. package/dist/components/ui/Card.d.ts +0 -14
  63. package/dist/components/ui/Dialog.d.ts +0 -61
  64. package/dist/components/ui/Icons.d.ts +0 -21
  65. package/dist/components/ui/Input.d.ts +0 -31
  66. package/dist/core/bindingContext.d.ts +0 -67
  67. package/dist/core/catalog.d.ts +0 -18
  68. package/dist/core/componentId.d.ts +0 -12
  69. package/dist/core/model.d.ts +0 -57
  70. package/dist/core/transport.d.ts +0 -33
  71. package/dist/core/valueCoercion.d.ts +0 -14
  72. package/dist/hooks/useAppliedBinding.d.ts +0 -6
  73. package/dist/index.css +0 -244
  74. package/dist/style.css +0 -244
  75. package/dist/types.d.ts +0 -232
package/SKILL.md CHANGED
@@ -1,294 +1,49 @@
1
- ---
2
- name: api-binding-ui
3
- description: Instructions for AI agents to integrate API Binding Studio (api_binding_ui) into React application screens, modals, drawers, and forms.
4
- ---
5
-
6
- # API Binding Studio Integration Skill
7
-
8
- This skill defines the operational standards and technical requirements for AI agents integrating `api_binding_ui` into frontend codebases. Follow these instructions whenever adding, updating, or maintaining action dialogs, flyout drawers, forms, or data grid action bars.
9
-
10
- ---
11
-
12
- ## 1. When to Apply This Skill
13
-
14
- Apply this skill whenever any of the following conditions are met:
15
- 1. The user asks to bind, map, or connect a screen, dialog, modal, drawer, or action form to backend APIs.
16
- 2. The user requests an API binding trigger button on an action modal (such as "Add Notes", "Create P2P", "Dispute Invoice", "Edit Customer", "Assign Task").
17
- 3. The application requires visual mapping, request payload previewing, or zero-network simulated validation across selected table/grid records.
18
- 4. Setting up the `/api-binding/$key` studio route in an application router.
19
-
20
- ---
21
-
22
- ## 2. Dependency Verification
23
-
24
- Verify that `package.json` contains `api_binding_ui`:
25
-
26
- ```json
27
- {
28
- "dependencies": {
29
- "api_binding_ui": "file:../api_binding_ui"
30
- }
31
- }
32
- ```
33
-
34
- If missing, add it to dependencies or prompt the user to link the local package.
35
-
36
- ---
37
-
38
- ## 3. Router Setup Requirements
39
-
40
- Every application using `api_binding_ui` must declare a route matching `/api-binding/$key`.
41
-
42
- ### TanStack Router (`src/routes/api-binding.$key.tsx`)
43
- ```tsx
44
- import { createFileRoute } from "@tanstack/react-router";
45
- import { ApiBindingWorkspace } from "api_binding_ui";
46
- import "api_binding_ui/style.css";
47
-
48
- export const Route = createFileRoute("/api-binding/$key")({
49
- head: () => ({
50
- meta: [{ title: "API Binding Studio" }],
51
- }),
52
- component: ApiBindingRouteView,
53
- });
54
-
55
- function ApiBindingRouteView() {
56
- const { key } = Route.useParams();
57
- return <ApiBindingWorkspace key={key} pageKey={key} />;
58
- }
59
- ```
60
-
61
- ### React Router (`src/routes/ApiBindingRoute.tsx`)
62
- ```tsx
63
- import { useParams } from "react-router-dom";
64
- import { ApiBindingWorkspace } from "api_binding_ui";
65
- import "api_binding_ui/style.css";
66
-
67
- export function ApiBindingRoute() {
68
- const { key = "default" } = useParams<{ key: string }>();
69
- return <ApiBindingWorkspace key={key} pageKey={key} />;
70
- }
71
- ```
72
-
73
- ---
74
-
75
- ## 4. Host Form Integration Rules
76
-
77
- When rendering forms, modals, or drawers that will be bound to APIs, AI agents MUST follow these structural rules:
78
-
79
- ### Rule 1: Form Inputs Must Have Identifiers
80
- Every form control (`<input>`, `<select>`, `<textarea>`, or custom combobox) MUST have a `name` attribute matching the semantic field name (or an `id`, `data-testid`, or `aria-label`).
81
- - Correct: `<input name="subject" required />`
82
- - Incorrect: `<input placeholder="Subject" />` (lacks identifier, will be ignored by DOM discovery).
83
-
84
- ### Rule 2: Explicit Labels
85
- Controls should be paired with standard `<label htmlFor="...">` tags or include `aria-label`. The studio extracts human-readable field labels from these associations.
86
-
87
- ### Rule 3: Pass Dropdown Options via `options` Prop
88
- Dropdown choices captured in state or constants must be passed to `<BindApiButton />` using the `options` prop:
89
- ```tsx
90
- const noteTypeOptions = [
91
- { value: "1", label: "General Follow-up" },
92
- { value: "2", label: "Promise to Pay" },
93
- ];
94
-
95
- const templateOptions = [
96
- {
97
- value: "tmpl-1",
98
- label: "Payment Reminder",
99
- subject: "Payment Reminder",
100
- description: "Followed up with customer regarding pending invoices.",
101
- },
102
- ];
103
-
104
- <BindApiButton
105
- // ...
106
- options={{
107
- notesType: noteTypeOptions,
108
- template: templateOptions,
109
- }}
110
- />
111
- ```
112
-
113
- ### Rule 4: Pass Grid Selection when Targeting Grid Rows
114
- If the action is triggered from or operates on selected table/grid rows:
115
- ```tsx
116
- <BindApiButton
117
- // ...
118
- grid={{
119
- gridId: "worklistUIGrid",
120
- rows: selectedRecords,
121
- }}
122
- />
123
- ```
124
- If the screen does not operate on a grid (such as a standalone dispute creation screen or customer profile editor), omit the `grid` prop. The studio automatically adapts its target context view and simulation engine to direct screen input.
125
-
126
- ---
127
-
128
- ## 5. Complete Implementation Patterns
129
-
130
- ### Pattern A: Modal Dialog Triggered from a Data Grid
131
- ```tsx
132
- import * as React from "react";
133
- import { BindApiButton } from "api_binding_ui";
134
-
135
- interface CustomerRecord {
136
- customerId: number;
137
- customerName: string;
138
- }
139
-
140
- export function AddNotesModal({
141
- open,
142
- onClose,
143
- selectedCustomers,
144
- }: {
145
- open: boolean;
146
- onClose: () => void;
147
- selectedCustomers: CustomerRecord[];
148
- }) {
149
- if (!open) return null;
150
-
151
- const noteTypeOptions = [
152
- { value: "1", label: "General Follow-up" },
153
- { value: "2", label: "Promise to Pay" },
154
- ];
155
-
156
- const templateOptions = [
157
- {
158
- value: "tmpl-payment",
159
- label: "Payment Reminder",
160
- subject: "Reminder: Invoice due",
161
- description: "Contacted customer regarding pending balance.",
162
- },
163
- ];
164
-
165
- return (
166
- <div role="dialog" aria-labelledby="dialog-title" className="ds-dialog">
167
- <h2 id="dialog-title">Add Notes</h2>
168
-
169
- <div className="form-group">
170
- <label htmlFor="notesType">Notes Type</label>
171
- <select id="notesType" name="notesType" required>
172
- {noteTypeOptions.map((opt) => (
173
- <option key={opt.value} value={opt.value}>{opt.label}</option>
174
- ))}
175
- </select>
176
- </div>
177
-
178
- <div className="form-group">
179
- <label htmlFor="subject">Subject</label>
180
- <input id="subject" name="subject" required />
181
- </div>
182
-
183
- <div className="form-group">
184
- <label htmlFor="notes">Description</label>
185
- <textarea id="notes" name="notes" required />
186
- </div>
187
-
188
- <div className="button-bar">
189
- <button type="button" onClick={onClose}>Cancel</button>
190
- <button type="submit">Submit</button>
191
-
192
- <BindApiButton
193
- appId="collections"
194
- windowId="worklist-add-notes"
195
- source="Add Notes"
196
- parent="Worklist Grid"
197
- grid={{
198
- gridId: "worklistUIGrid",
199
- rows: selectedCustomers,
200
- }}
201
- options={{
202
- notesType: noteTypeOptions,
203
- template: templateOptions,
204
- }}
205
- />
206
- </div>
207
- </div>
208
- );
209
- }
210
- ```
211
-
212
- ### Pattern B: Standalone Drawer or Form without Grid
213
- ```tsx
214
- import * as React from "react";
215
- import { BindApiButton } from "api_binding_ui";
216
-
217
- export function DisputeDrawer({ open, onClose }: { open: boolean; onClose: () => void }) {
218
- if (!open) return null;
219
-
220
- const disputeReasons = [
221
- { value: "DAMAGED", label: "Damaged Goods" },
222
- { value: "PRICING", label: "Incorrect Pricing" },
223
- ];
224
-
225
- return (
226
- <aside className="drawer-container" aria-label="Create Dispute">
227
- <h2>Create Dispute</h2>
228
-
229
- <form id="dispute-form">
230
- <label htmlFor="reasonCode">Dispute Reason</label>
231
- <select id="reasonCode" name="reasonCode" required>
232
- {disputeReasons.map((r) => (
233
- <option key={r.value} value={r.value}>{r.label}</option>
234
- ))}
235
- </select>
236
-
237
- <label htmlFor="amount">Disputed Amount</label>
238
- <input id="amount" name="amount" type="number" required />
239
-
240
- <label htmlFor="comments">Comments</label>
241
- <textarea id="comments" name="comments" />
242
-
243
- <div className="drawer-actions">
244
- <button type="button" onClick={onClose}>Cancel</button>
245
- <button type="submit">Submit Dispute</button>
246
-
247
- <BindApiButton
248
- appId="receivables"
249
- windowId="disputes-create"
250
- source="Create Dispute"
251
- parent="Disputes Management"
252
- options={{
253
- reasonCode: disputeReasons,
254
- }}
255
- />
256
- </div>
257
- </form>
258
- </aside>
259
- );
260
- }
261
- ```
262
-
263
- ---
264
-
265
- ## 6. Listening to Applied Configurations
266
-
267
- When the user verifies and applies an API binding inside the Studio, the configuration is stored in browser storage. The parent screen can react to this using `useAppliedBinding`:
268
-
269
- ```tsx
270
- import { useAppliedBinding } from "api_binding_ui";
271
-
272
- export function ScreenComponent() {
273
- const { isApplied, appliedConfig } = useAppliedBinding(
274
- "collections.worklistUIGrid.worklist-add-notes"
275
- );
276
-
277
- React.useEffect(() => {
278
- if (isApplied && appliedConfig) {
279
- // Configuration applied: access appliedConfig.calls or appliedConfig.fields
280
- }
281
- }, [isApplied, appliedConfig]);
282
- }
283
- ```
284
-
285
- ---
286
-
287
- ## 7. Agent Verification Checklist
288
-
289
- Before completing an integration task, the AI agent must verify:
290
- 1. `name` attributes are assigned to all form controls on the host screen.
291
- 2. The `<BindApiButton />` has accurate `appId`, `windowId`, `source`, and `parent` strings.
292
- 3. Dropdown choices are forwarded in the `options` record.
293
- 4. The application router has an active route at `/api-binding/$key` mounting `<ApiBindingWorkspace />`.
294
- 5. No emojis or informal characters are introduced into the code or documentation.
1
+ ---
2
+ name: api-binding-ui
3
+ description: "Use when adding, changing or wiring screens in an app that depends on api_binding_ui: registry entries, BindingScreen, slot hooks, the design-mode BindingWidget."
4
+ ---
5
+
6
+ # api_binding_ui: rules for agents
7
+
8
+ Contract: `node_modules/api_binding_ui/spec/FRAMEWORK.md`. Types: `import type { … } from "api_binding_ui"`.
9
+
10
+ ## Never
11
+
12
+ - Call APIs from screens (`fetch`, axios, generated clients, mock hooks).
13
+ - Add new hardcoded option lists, rows or values that an API should provide.
14
+ - Put opening, closing, navigation, toasts or reloads in bindings. They are app code.
15
+ - Invent APIs. Only operations in the catalog (generated from the OpenAPI docs) exist.
16
+ - Edit the catalog or `openapi/*.json` by hand.
17
+ - Import `api_binding_ui/studio` from production code paths. Load it with `React.lazy` behind the design-mode flag.
18
+ - Add component kinds, product names or field names to framework code.
19
+
20
+ ## Always
21
+
22
+ 1. **Registry entry per screen** (`src/bindings/registry.ts`):
23
+ ```ts
24
+ { id: "<product>.<area>.<screen>", label, path: ["<Product>", ...groups], parent?: "<screen id>",
25
+ context: [{ id, label, many?, shape: { schema } | { from: { screen, component, slot } } }],
26
+ components: [{ bindId, kind, kindVersion: 1, label, parent?: "<bindId>" }] }
27
+ ```
28
+ - `path[0]` is the product. A dialog or drawer is its own screen with `parent`.
29
+ - `bindId` is stable, unique per screen and independent of the label: `notesType`, not `select1`.
30
+ - `kind` must exist in `src/bindings/descriptors.ts`. Grid columns and row actions set `parent` to the grid's bindId.
31
+ 2. **Wrap the screen:** `<BindingScreen id="<screen id>" context={{ <context id>: value }}>`. Views kept mounted while hidden (inactive tabs) are wrapped in `<BindingActive value={isVisible}>`.
32
+ 3. **Wire every component through its slots:**
33
+
34
+ | Slot role | Hook | Use |
35
+ |---|---|---|
36
+ | consume | `const s = useSlot<T>(bindId, slot)` | render by `s.status`: `unbound` / `waiting` / `loading` / `ready` / `error`; data in `s.data`; `s.reload()` |
37
+ | consume, per row | `const cell = useCell<T>(bindId, slot)` | `cell(row)` |
38
+ | produce | `useProduce(bindId, slot, value)` | publish every value the component exposes; dropdowns publish the whole selected option object as `selected` |
39
+ | trigger | `const t = useTrigger(bindId, slot)` | `if (t.bound) { const out = await t.run(item?); if (out.every(o => o.ok)) { /* app: close, toast, reload */ } else { /* show out.find(o => !o.ok).error */ } }` |
40
+
41
+ 4. **Unbound keeps what the screen has today:** `status === "unbound"` renders the screen's existing content (its sample data during design); `ready` replaces it with API data. `t.bound === false` keeps the existing behaviour.
42
+ 5. **No per-screen Bind API buttons.** One `<BindingWidget>` near the app root opens the studio on whichever `BindingScreen` is showing.
43
+
44
+ ## New screen checklist
45
+
46
+ - [ ] Registry entry with every component; `parent` for dialogs; `context` for inputs
47
+ - [ ] `<BindingScreen>` around the screen
48
+ - [ ] Every component wired with the hooks; no API calls or static data left
49
+ - [ ] Screen opens in the studio from the floating button, in design mode
@@ -0,0 +1,268 @@
1
+ /** Binding framework contract v1. Semantics: spec/FRAMEWORK.md. */
2
+ /** JSON Schema (2020-12). */
3
+ export type JsonSchema = boolean | {
4
+ [keyword: string]: unknown;
5
+ };
6
+ /** Namespaced component kind, e.g. "acme-ds/grid". */
7
+ export type KindId = string;
8
+ /** Component instance id, unique within its screen. */
9
+ export type BindId = string;
10
+ /** Screen id, e.g. "<product>.<area>.<screen>". */
11
+ export type ScreenId = string;
12
+ /** Expression string; grammar in FRAMEWORK.md §7. */
13
+ export type Expr = string;
14
+ /** RFC 6901 JSON Pointer into a request: "/path/<name>", "/query/<name>", "/header/<name>", "/body/<pointer>". */
15
+ export type RequestPointer = string;
16
+ /** Selector into a response, e.g. "$", "$.items[*]". */
17
+ export type ResponseSelector = string;
18
+ export interface Labelled {
19
+ label: string;
20
+ description?: string;
21
+ }
22
+ /** consume: needs data. produce: exposes data. trigger: emits an event that runs API calls. */
23
+ export type SlotRole = "consume" | "produce" | "trigger";
24
+ export interface SlotDescriptor extends Labelled {
25
+ id: string;
26
+ role: SlotRole;
27
+ /**
28
+ * consume: shape the component needs (per item when `many`).
29
+ * produce: shape the component provides (per item when `many`).
30
+ * trigger: payload the event carries.
31
+ */
32
+ schema: JsonSchema;
33
+ many?: boolean;
34
+ /** The screen is incomplete until this slot is bound. */
35
+ required?: boolean;
36
+ /** Evaluated per item of a parent slot (e.g. a cell in a row); `$item` is that item. */
37
+ scope?: {
38
+ parentSlot: string;
39
+ };
40
+ /** Produce slots of the same component that may feed the request (paging, sort). */
41
+ feeds?: string[];
42
+ /** Matching hints from the component author. */
43
+ hints?: MatchHints;
44
+ }
45
+ export interface MatchHints {
46
+ /** Words describing the slot, for name matching. */
47
+ keywords?: string[];
48
+ /** Operation intents this slot usually binds to. */
49
+ intents?: string[];
50
+ }
51
+ export interface ChildPolicy {
52
+ /** Kinds allowed as children; omit for any. */
53
+ kinds?: KindId[];
54
+ /** Parent slot children are scoped to (e.g. columns to rows). */
55
+ scopedTo?: string;
56
+ }
57
+ export interface ComponentDescriptor extends Labelled {
58
+ kind: KindId;
59
+ /** Bump on incompatible slot changes; manifests record it. */
60
+ version: number;
61
+ slots: SlotDescriptor[];
62
+ children?: ChildPolicy;
63
+ /** SVG markup. */
64
+ icon?: string;
65
+ }
66
+ export interface ComponentInstance {
67
+ bindId: BindId;
68
+ kind: KindId;
69
+ kindVersion: number;
70
+ label: string;
71
+ /** Parent component's bindId. */
72
+ parent?: BindId;
73
+ /** Display-only configuration; never used for matching. */
74
+ info?: Record<string, string>;
75
+ }
76
+ export interface ContextDeclaration extends Labelled {
77
+ /** Name used in expressions: `$context.<id>`. */
78
+ id: string;
79
+ /** A schema, or another screen's produce slot whose bound shape it follows. */
80
+ shape: {
81
+ schema: JsonSchema;
82
+ } | {
83
+ from: {
84
+ screen: ScreenId;
85
+ component: BindId;
86
+ slot: string;
87
+ };
88
+ };
89
+ many?: boolean;
90
+ }
91
+ export interface ScreenEntry extends Labelled {
92
+ id: ScreenId;
93
+ /** Navigation path; the first segment is the product. */
94
+ path: string[];
95
+ /** Screen this one opens over (dialog, drawer). Grouping only. */
96
+ parent?: ScreenId;
97
+ context: ContextDeclaration[];
98
+ components: ComponentInstance[];
99
+ }
100
+ export interface ScreenRegistry {
101
+ registryVersion: 1;
102
+ screens: ScreenEntry[];
103
+ }
104
+ export interface CatalogParameter {
105
+ pointer: RequestPointer;
106
+ name: string;
107
+ required: boolean;
108
+ schema: JsonSchema;
109
+ description?: string;
110
+ }
111
+ export interface CatalogOperation {
112
+ /** operationId. */
113
+ id: string;
114
+ method: string;
115
+ path: string;
116
+ summary?: string;
117
+ description?: string;
118
+ tags: string[];
119
+ /** Product from the docs' `x-tagGroups`. */
120
+ product?: string;
121
+ /** "read" or "write": `x-binding-intent` if declared, else from the HTTP method. */
122
+ intent?: string;
123
+ parameters: CatalogParameter[];
124
+ body?: {
125
+ required: boolean;
126
+ schema: JsonSchema;
127
+ };
128
+ responses: Array<{
129
+ status: string;
130
+ schema?: JsonSchema;
131
+ }>;
132
+ /** `x-*` extensions as published. */
133
+ extensions: Record<string, unknown>;
134
+ }
135
+ export interface Catalog {
136
+ catalogVersion: 1;
137
+ source: {
138
+ title: string;
139
+ version: string;
140
+ url?: string;
141
+ };
142
+ /** Content hash; manifests pin it. */
143
+ hash: string;
144
+ operations: CatalogOperation[];
145
+ /** Shared schemas referenced by `$ref` inside operations. */
146
+ schemas: Record<string, JsonSchema>;
147
+ }
148
+ export type BindingOrigin = "manual" | "suggested";
149
+ export type BindingState = "draft" | "confirmed";
150
+ interface BindingBase {
151
+ component: BindId;
152
+ slot: string;
153
+ origin: BindingOrigin;
154
+ state: BindingState;
155
+ }
156
+ /** consume slot fed by an operation. */
157
+ export interface OperationConsumeBinding extends BindingBase {
158
+ type: "operation";
159
+ operation: string;
160
+ request: Record<RequestPointer, Expr>;
161
+ /** Where the slot's data is in the response. */
162
+ select: ResponseSelector;
163
+ /** Slot-schema property → expression over `$item` (when the slot declares properties). */
164
+ map?: Record<string, Expr>;
165
+ /** When to load: on open, on first use, or on an interval. */
166
+ load?: "open" | "demand" | {
167
+ everySeconds: number;
168
+ };
169
+ }
170
+ /** consume slot fed by an expression (row field, another component, context). */
171
+ export interface ExpressionConsumeBinding extends BindingBase {
172
+ type: "expression";
173
+ value: Expr;
174
+ }
175
+ /** One API call of a trigger. UI behaviour (open, close, navigate) stays in the app. */
176
+ export interface Step {
177
+ type: "call";
178
+ id: string;
179
+ operation: string;
180
+ request: Record<RequestPointer, Expr>;
181
+ forEach?: Expr;
182
+ onError: "stop" | "continue";
183
+ }
184
+ /** trigger slot that calls APIs in order. */
185
+ export interface TriggerBinding extends BindingBase {
186
+ type: "steps";
187
+ steps: Step[];
188
+ }
189
+ /** trigger slot reviewed as calling no API. */
190
+ export interface NoApiBinding extends BindingBase {
191
+ type: "none";
192
+ }
193
+ export type SlotBinding = OperationConsumeBinding | ExpressionConsumeBinding | TriggerBinding | NoApiBinding;
194
+ export interface BindingManifest {
195
+ $schema?: string;
196
+ manifestVersion: 1;
197
+ screen: ScreenId;
198
+ catalog: {
199
+ hash: string;
200
+ };
201
+ /** Descriptor versions the bindings were made against. */
202
+ kinds: Record<KindId, number>;
203
+ bindings: SlotBinding[];
204
+ }
205
+ /** A named, typed value transform usable as `expr | name(args)`. */
206
+ export interface TransformDefinition extends Labelled {
207
+ name: string;
208
+ input: JsonSchema;
209
+ output: JsonSchema;
210
+ apply: (value: unknown, ...args: unknown[]) => unknown;
211
+ }
212
+ /** Supplies `$<id>` values (e.g. the signed-in user). Registered by the host app. */
213
+ export interface ContextProvider extends Labelled {
214
+ id: string;
215
+ schema: JsonSchema;
216
+ read: () => unknown;
217
+ }
218
+ export interface MatchInput {
219
+ screen: ScreenEntry;
220
+ component: ComponentInstance;
221
+ descriptor: ComponentDescriptor;
222
+ slot: SlotDescriptor;
223
+ operation: CatalogOperation;
224
+ catalog: Catalog;
225
+ }
226
+ export interface MatchResult {
227
+ /** 0..1 */
228
+ score: number;
229
+ /** Reasons shown in the studio. */
230
+ reasons: string[];
231
+ /** Proposed request/map entries, if the matcher can propose them. */
232
+ proposal?: Partial<Pick<OperationConsumeBinding, "request" | "select" | "map">> | Partial<Pick<TriggerBinding, "steps">>;
233
+ }
234
+ /** Scores one operation for one slot; matchers combine by weight. */
235
+ export interface Matcher {
236
+ id: string;
237
+ weight: number;
238
+ score: (input: MatchInput) => MatchResult | null;
239
+ }
240
+ /** Per-product synonyms, loaded as data. */
241
+ export interface Glossary {
242
+ product: string;
243
+ /** Groups of words that mean the same thing in this product. */
244
+ synonyms: string[][];
245
+ }
246
+ export interface PersistenceAdapter {
247
+ load: (screen: ScreenId) => Promise<BindingManifest | null>;
248
+ save: (manifest: BindingManifest) => Promise<void>;
249
+ }
250
+ export interface FrameworkRegistry {
251
+ registerDescriptor: (descriptor: ComponentDescriptor) => void;
252
+ registerTransform: (transform: TransformDefinition) => void;
253
+ registerContext: (provider: ContextProvider) => void;
254
+ registerMatcher: (matcher: Matcher) => void;
255
+ registerGlossary: (glossary: Glossary) => void;
256
+ setPersistence: (adapter: PersistenceAdapter) => void;
257
+ }
258
+ export type IssueSeverity = "error" | "warning";
259
+ export interface ValidationIssue {
260
+ severity: IssueSeverity;
261
+ screen: ScreenId;
262
+ component?: BindId;
263
+ slot?: string;
264
+ /** Stable machine code, e.g. "slot.required-unbound", "schema.incompatible", "catalog.operation-missing". */
265
+ code: string;
266
+ message: string;
267
+ }
268
+ export {};