api_binding_ui 1.1.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.
- package/README.md +74 -394
- package/SKILL.md +49 -350
- package/dist/framework/contract.d.ts +268 -0
- package/dist/framework/engine.d.ts +88 -0
- package/dist/framework/expr.d.ts +34 -0
- package/dist/framework/loadCatalog.d.ts +6 -0
- package/dist/framework/names.d.ts +7 -0
- package/dist/framework/openapi.d.ts +7 -0
- package/dist/framework/runner.d.ts +33 -0
- package/dist/framework/schema.d.ts +22 -0
- package/dist/framework/store.d.ts +17 -0
- package/dist/framework/validate.d.ts +7 -0
- package/dist/index-e9twc25y.js +754 -0
- package/dist/index-e9twc25y.js.map +16 -0
- package/dist/index.d.ts +11 -29
- package/dist/index.js +345 -11932
- package/dist/index.js.map +14 -0
- package/dist/runtime/BindingProvider.d.ts +42 -0
- package/dist/runtime/BindingScreen.d.ts +23 -0
- package/dist/runtime/hooks.d.ts +22 -0
- package/dist/runtime/screenStore.d.ts +7 -0
- package/dist/studio/ApiPicker.d.ts +15 -0
- package/dist/studio/BindingStudio.d.ts +21 -0
- package/dist/studio/BindingWidget.d.ts +9 -0
- package/dist/studio/Collapse.d.ts +6 -0
- package/dist/studio/ComponentList.d.ts +10 -0
- package/dist/studio/FindAnything.d.ts +7 -0
- package/dist/studio/Inspector.d.ts +12 -0
- package/dist/studio/Navigator.d.ts +17 -0
- package/dist/studio/SettingsPanel.d.ts +15 -0
- package/dist/studio/Tip.d.ts +23 -0
- package/dist/studio/editors/ConsumeEditor.d.ts +4 -0
- package/dist/studio/editors/RequestInputs.d.ts +11 -0
- package/dist/studio/editors/ScopedEditor.d.ts +3 -0
- package/dist/studio/editors/SlotSection.d.ts +3 -0
- package/dist/studio/editors/SourceSelect.d.ts +10 -0
- package/dist/studio/editors/StepCard.d.ts +12 -0
- package/dist/studio/editors/TriggerEditor.d.ts +4 -0
- package/dist/studio/editors/types.d.ts +14 -0
- package/dist/studio/icons.d.ts +65 -0
- package/dist/studio/index.css +2021 -0
- package/dist/studio/index.d.ts +3 -0
- package/dist/studio/index.js +2806 -0
- package/dist/studio/index.js.map +33 -0
- package/dist/studio/useBindings.d.ts +20 -0
- package/dist/studio/useCatalog.d.ts +15 -0
- package/dist/studio/useGlide.d.ts +11 -0
- package/dist/studio/useSettings.d.ts +8 -0
- package/package.json +20 -14
- package/spec/FRAMEWORK.md +271 -0
- package/dist/api-binding-ui.css +0 -248
- package/dist/components/ApiBindingWorkspace.d.ts +0 -10
- package/dist/components/ApiMappings.d.ts +0 -21
- package/dist/components/BindApiButton.d.ts +0 -41
- package/dist/components/RequestPreview.d.ts +0 -7
- package/dist/components/SimulationDialog.d.ts +0 -8
- package/dist/components/ValidationDialog.d.ts +0 -8
- package/dist/components/WorkspaceContext.d.ts +0 -7
- package/dist/components/ui/AppGrid.d.ts +0 -27
- package/dist/components/ui/Badge.d.ts +0 -9
- package/dist/components/ui/Banner.d.ts +0 -8
- package/dist/components/ui/Button.d.ts +0 -31
- package/dist/components/ui/Card.d.ts +0 -14
- package/dist/components/ui/Dialog.d.ts +0 -61
- package/dist/components/ui/Icons.d.ts +0 -21
- package/dist/components/ui/Input.d.ts +0 -31
- package/dist/core/bindingContext.d.ts +0 -67
- package/dist/core/catalog.d.ts +0 -18
- package/dist/core/componentId.d.ts +0 -12
- package/dist/core/model.d.ts +0 -63
- package/dist/core/openapi.d.ts +0 -2
- package/dist/core/transport.d.ts +0 -33
- package/dist/core/validation.d.ts +0 -2
- package/dist/core/valueCoercion.d.ts +0 -17
- package/dist/hooks/useAppliedBinding.d.ts +0 -6
- package/dist/index.css +0 -248
- package/dist/style.css +0 -244
- package/dist/types.d.ts +0 -245
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# API Binding Framework — v1 (draft)
|
|
2
|
+
|
|
3
|
+
Status: draft for review. Normative types: [`contract.ts`](./contract.ts).
|
|
4
|
+
|
|
5
|
+
This framework connects **UI components** to **documented API operations** during the design phase, and produces a reviewed, versioned **binding manifest** for every screen. Any product can adopt it. It contains no knowledge of any component kind, product, operation or field: all of that arrives as data or through registration.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Principles
|
|
10
|
+
|
|
11
|
+
1. **Nothing concrete in the framework.** No component kinds, product names, operation ids, field names or vocabularies are written into framework code. If something has to be known, it is declared as data (descriptor, registry entry, catalog, glossary) or registered as an extension.
|
|
12
|
+
2. **The API docs are the only source of operations.** The catalog is generated from the published OpenAPI documents. Nothing is supplied by the app instead: no local hooks, hardcoded option lists or free-text overrides.
|
|
13
|
+
3. **UI first.** The unit of work is a screen and its components. APIs are attached to component slots, not the other way round.
|
|
14
|
+
4. **Components describe themselves.** Every bindable component ships a descriptor stating its slots. A component built next year works with the same studio, the same manifest and the same runtime, unchanged.
|
|
15
|
+
5. **Types flow through bindings.** The shape of a selection, a row or a dialog's context is derived from what the source slot is bound to, never typed in by hand.
|
|
16
|
+
6. **Design-time and runtime are separate.** The studio never ships to production. The runtime reads manifests only.
|
|
17
|
+
7. **Every contract is versioned.** Descriptors, registry, catalog and manifest each carry a version, and changes are detected, not discovered in production.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 2. The pieces and who owns them
|
|
22
|
+
|
|
23
|
+
| Piece | What it is | Owned by | Format |
|
|
24
|
+
|---|---|---|---|
|
|
25
|
+
| Component descriptor | A kind's slots, with JSON Schema shapes | Component library (design system) | `ComponentDescriptor` |
|
|
26
|
+
| Screen registry | Every screen in the app, with its component instances | Each product | `ScreenRegistry` (JSON) |
|
|
27
|
+
| Catalog | Normalized operations from the API docs | Generated from OpenAPI with `catalogFromOpenApi` | `Catalog` (JSON) |
|
|
28
|
+
| Binding manifest | Which operation or expression feeds each slot | Designers in the studio, reviewed by engineers | `BindingManifest` (JSON, one per screen) |
|
|
29
|
+
| Extensions | Transforms, context providers, matchers, glossaries, persistence | Framework defaults + products | `FrameworkRegistry` |
|
|
30
|
+
| Studio | Design-time editor that reads all of the above | This package | UI, design mode only |
|
|
31
|
+
| Runtime | Executes manifests in the app | This package | Small library, production |
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
OpenAPI docs ──► Catalog ─┐
|
|
35
|
+
Descriptors ──────────────┼──► Studio ──► Manifests (repo) ──► Runtime (production)
|
|
36
|
+
Screen registry ──────────┘
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 3. Component descriptors
|
|
42
|
+
|
|
43
|
+
A descriptor is published once per component kind, next to the component, by whoever builds the component. The framework only understands three slot **roles**:
|
|
44
|
+
|
|
45
|
+
| Role | Meaning | Bound to |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `consume` | The component needs data | An operation (via request + response selection) or an expression |
|
|
48
|
+
| `produce` | The component exposes data | Nothing; it is a **source** others can read |
|
|
49
|
+
| `trigger` | The component emits an event | An ordered list of steps |
|
|
50
|
+
|
|
51
|
+
Each slot carries a JSON Schema (`schema`) and an optional `many`. That is all the studio needs to render an editor, propose matches and validate.
|
|
52
|
+
|
|
53
|
+
**Scoped slots.** A consume slot can be evaluated per item of a parent slot (`scope.parentSlot`). Inside it, `$item` is the parent's current item. This covers cells in rows, items in a list, cards in a lane, without the framework knowing what a row, list or lane is.
|
|
54
|
+
|
|
55
|
+
**Feeding requests.** A consume slot can name produce slots of the same component in `feeds` (for example, paging or sorting state). The studio offers them as sources when mapping that slot's request.
|
|
56
|
+
|
|
57
|
+
### Illustrations (not part of the framework)
|
|
58
|
+
|
|
59
|
+
These show three unrelated kinds expressed with the same vocabulary. Names are examples.
|
|
60
|
+
|
|
61
|
+
```jsonc
|
|
62
|
+
// A data grid
|
|
63
|
+
{ "kind": "example-ds/grid", "version": 1, "label": "Grid",
|
|
64
|
+
"children": { "scopedTo": "rows" },
|
|
65
|
+
"slots": [
|
|
66
|
+
{ "id": "rows", "role": "consume", "many": true, "required": true, "label": "Rows",
|
|
67
|
+
"schema": { "type": "object" }, "feeds": ["page", "sort", "filter"] },
|
|
68
|
+
{ "id": "total", "role": "consume", "label": "Total", "schema": { "type": "integer" } },
|
|
69
|
+
{ "id": "page", "role": "produce", "label": "Page", "schema": { "type": "object", "properties": { "offset": { "type": "integer" }, "limit": { "type": "integer" } } } },
|
|
70
|
+
{ "id": "sort", "role": "produce", "label": "Sort", "schema": { "type": "object", "properties": { "field": { "type": "string" }, "direction": { "enum": ["asc", "desc"] } } } },
|
|
71
|
+
{ "id": "filter", "role": "produce", "label": "Filter", "schema": { "type": "object" } },
|
|
72
|
+
{ "id": "selection", "role": "produce", "many": true, "label": "Selected rows", "schema": { "$ref": "#slot/rows" } },
|
|
73
|
+
{ "id": "rowClick", "role": "trigger", "label": "Row clicked", "schema": { "$ref": "#slot/rows" } }
|
|
74
|
+
] }
|
|
75
|
+
|
|
76
|
+
// A single-choice input
|
|
77
|
+
{ "kind": "example-ds/select", "version": 1, "label": "Dropdown",
|
|
78
|
+
"slots": [
|
|
79
|
+
{ "id": "options", "role": "consume", "many": true, "label": "Options",
|
|
80
|
+
"schema": { "type": "object", "required": ["value", "label"], "properties": { "value": {}, "label": { "type": "string" } } } },
|
|
81
|
+
{ "id": "selected", "role": "produce", "label": "Selected option", "schema": { "$ref": "#slot/options" } },
|
|
82
|
+
{ "id": "change", "role": "trigger", "label": "Changed", "schema": { "$ref": "#slot/options" } }
|
|
83
|
+
] }
|
|
84
|
+
|
|
85
|
+
// A dropdown publishes the whole chosen option, not only its display value: every field the options API
|
|
86
|
+
// returns (an id, a code, a name) is then available as a separate, correctly typed value.
|
|
87
|
+
|
|
88
|
+
// A component that does not exist yet
|
|
89
|
+
{ "kind": "example-ds/timeline", "version": 1, "label": "Timeline",
|
|
90
|
+
"slots": [
|
|
91
|
+
{ "id": "events", "role": "consume", "many": true, "label": "Events",
|
|
92
|
+
"schema": { "type": "object", "required": ["at", "title"], "properties": { "at": { "type": "string", "format": "date-time" }, "title": { "type": "string" }, "detail": { "type": "string" } } } },
|
|
93
|
+
{ "id": "eventClick", "role": "trigger", "label": "Event clicked", "schema": { "$ref": "#slot/events" } }
|
|
94
|
+
] }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`"$ref": "#slot/<id>"` means "the same shape as that slot after binding". This is how a grid's selection gets the row shape of whatever operation feeds its rows, and how a dropdown's selected option gets the fields of the operation that feeds its options. Until that slot is bound, the shape is unknown and the studio offers nothing from it; it shows which slot to connect first.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 4. Screen registry
|
|
102
|
+
|
|
103
|
+
The registry lists the screens that exist in the app, and only those. It is the studio's inventory, so thousands of screens can be bound without opening the app.
|
|
104
|
+
|
|
105
|
+
- Each screen ships its registry entry next to its code (a JSON file or an exported constant). The app's build collects the entries into one registry file.
|
|
106
|
+
- A screen that is not in the app has no entry. Nothing is listed ahead of time.
|
|
107
|
+
- Every component in an entry must have a registered descriptor. An unknown kind fails validation at build time, so the studio never shows a component it cannot bind.
|
|
108
|
+
- `context` declares what a screen receives from outside. Its shape is either a schema or a reference to another screen's produce slot (`shape.from`), so a dialog opened from a grid toolbar automatically knows the selected rows' fields.
|
|
109
|
+
- `parent` groups a dialog or drawer under the screen it appears over, for studio navigation only. How it opens and closes is the app's own UI code.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## 5. Catalog
|
|
114
|
+
|
|
115
|
+
`api-binding catalog pull <url-or-file>` normalizes one or more OpenAPI 3.x documents into a `Catalog`:
|
|
116
|
+
|
|
117
|
+
- Every operation, with parameters as request pointers (`/path/…`, `/query/…`, `/header/…`, `/body/…`), body schema and response schemas.
|
|
118
|
+
- `intent` comes from HTTP semantics (safe methods are reads) unless the docs declare it with an extension. It is never inferred from names.
|
|
119
|
+
- All `x-*` extensions are kept verbatim, so new docs metadata reaches matchers without a framework change.
|
|
120
|
+
- A content hash pins the catalog. Manifests record the hash they were made with.
|
|
121
|
+
|
|
122
|
+
List locations in responses are discovered from schemas (any array of objects is a candidate selector). Envelope shapes are never assumed.
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 6. Binding manifest
|
|
127
|
+
|
|
128
|
+
One JSON file per screen in the product repository (for example `bindings/<screen-id>.binding.json`). It references only: bindIds and slot ids from the registry and descriptors, operation ids from the catalog, and expressions.
|
|
129
|
+
|
|
130
|
+
| Slot role | Binding type | Contents |
|
|
131
|
+
|---|---|---|
|
|
132
|
+
| consume | `operation` | operation, request mapping (pointer → expression), response `select`, optional `map` to the slot's properties, load policy |
|
|
133
|
+
| consume | `expression` | a single expression (scoped item field, another component's produce slot, context) |
|
|
134
|
+
| trigger | `steps` | ordered API `call` steps; a call may run `forEach` over a list |
|
|
135
|
+
| trigger | `none` | reviewed: this event calls no API |
|
|
136
|
+
| produce | — | never bound; produce slots are sources |
|
|
137
|
+
|
|
138
|
+
Bindings describe API calls and nothing else. Opening and closing screens, navigation, reloading after a save and every other UI behaviour belong to the app's code, never to a manifest.
|
|
139
|
+
|
|
140
|
+
Every binding records `origin` (manual or suggested) and `state` (draft or confirmed). Only confirmed bindings run in production.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 7. Expressions
|
|
145
|
+
|
|
146
|
+
One grammar for every source. Nothing outside it is allowed.
|
|
147
|
+
|
|
148
|
+
| Form | Resolves to |
|
|
149
|
+
|---|---|
|
|
150
|
+
| `<bindId>.<slot>` | A produce slot of a component on this screen, e.g. `subject.value`, `grid.selection`, `status.selected` |
|
|
151
|
+
| `<bindId>.<slot>.<path>` | A property inside it |
|
|
152
|
+
| `<expr>[*].<path>` | A property of every item, as a list |
|
|
153
|
+
| `$item.<path>` | The current item in a scoped slot or `forEach` |
|
|
154
|
+
| `$context.<id>.<path>` | A value declared in the screen's `context` |
|
|
155
|
+
| `$<provider>.<path>` | A registered context provider |
|
|
156
|
+
| `$steps.<stepId>.response.<path>` | The response of an earlier `call` step |
|
|
157
|
+
| `<expr> \| <transform>(<args>)` | A registered transform |
|
|
158
|
+
|
|
159
|
+
Literal values are allowed only when the target schema defines them (`enum`, `const` or `default`). There is no free-text source.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 8. Suggestions
|
|
164
|
+
|
|
165
|
+
The studio ranks **all** catalog operations for each slot and shows the best ones first. The user can always choose any operation. Ranking is done by registered `Matcher`s, combined by weight. The default matchers use only metadata already present in the inputs:
|
|
166
|
+
|
|
167
|
+
| Matcher | Signal |
|
|
168
|
+
|---|---|
|
|
169
|
+
| Shape | Compatibility between the slot's schema and the operation's response (consume) or request (trigger), including `many` and scalar types |
|
|
170
|
+
| Intent | Slot role and `hints.intents` against the operation's `intent` |
|
|
171
|
+
| Names | Token similarity between screen, component and slot labels and the operation's summary, tags and description |
|
|
172
|
+
| Glossary | Synonym groups from registered glossaries (data files per product) |
|
|
173
|
+
| Fields | For request and `map` entries: name and type similarity between available sources and target fields |
|
|
174
|
+
|
|
175
|
+
Rules for matchers: no product words in code, every score comes with plain reasons, and proposals stay `origin: "suggested"` until a person confirms them.
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 9. Validation
|
|
180
|
+
|
|
181
|
+
The studio and CI (a script calling `validateRegistry` and `validateBindings`) apply the same rules. All are generic:
|
|
182
|
+
|
|
183
|
+
- A `required` slot is unbound.
|
|
184
|
+
- A binding refers to a bindId, slot, operation or descriptor version that no longer exists.
|
|
185
|
+
- Schema incompatibility between a source expression and its target (type, `many` vs single, required request fields unmapped).
|
|
186
|
+
- An expression uses a source not available on this screen.
|
|
187
|
+
- A literal is not defined by the target schema.
|
|
188
|
+
- The catalog hash changed: each affected binding is listed with what changed (field removed, renamed or newly required).
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 10. Studio rules
|
|
193
|
+
|
|
194
|
+
The studio renders entirely from descriptors, registry, catalog and manifest:
|
|
195
|
+
|
|
196
|
+
- Navigation comes from `ScreenEntry.path` and `parent`. It scales to any number of screens.
|
|
197
|
+
- Component lists come from `ScreenEntry.components`. Icons come from descriptors.
|
|
198
|
+
- There are exactly three slot editors, chosen by role, and their forms are generated from the slot's schema. A new kind never needs studio code.
|
|
199
|
+
- Every consume and trigger slot offers the full catalog in a searchable picker, with matcher suggestions on top.
|
|
200
|
+
- The studio shows only what validates. A component with an unknown kind, or a binding that points at an operation no longer in the catalog, is reported by validation and kept out of the studio until it is fixed.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 11. Runtime
|
|
205
|
+
|
|
206
|
+
Entry `api_binding_ui`. Production-safe: it contains no studio code and no styles.
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
<BindingProvider catalog={catalog} manifests={manifests} fetcher={fetcher} transforms={…} providers={…}>
|
|
210
|
+
<BindingScreen id="collections.worklist.add-notes" context={{ selection: selectedRows }}>
|
|
211
|
+
…
|
|
212
|
+
</BindingScreen>
|
|
213
|
+
</BindingProvider>
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
| Hook | Slot role | Returns |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| `useSlot(bindId, slot)` | consume | `{ status, data, error, reload }`; status is `unbound`, `waiting` (a required input has no value yet), `loading`, `ready` or `error` |
|
|
219
|
+
| `useCell(bindId, slot)` | scoped consume | `(item) => value` for one row or item |
|
|
220
|
+
| `useProduce(bindId, slot, value)` | produce | publishes the component's current value to the screen |
|
|
221
|
+
| `useTrigger(bindId, slot)` | trigger | `{ bound, running, run(item?) }`; `run` performs the API calls and returns each step's outcome |
|
|
222
|
+
|
|
223
|
+
- Only **confirmed** bindings run. Drafts exist only in the studio.
|
|
224
|
+
- Requests are built from request pointers and sent through the host's `fetcher`; base URL, authentication and error display stay in the app.
|
|
225
|
+
- A component re-renders only when the values its own binding reads change. Identical requests on one screen share one call.
|
|
226
|
+
- Without a confirmed binding a consume slot is `unbound` and a trigger has `bound: false`. What the component shows or does then is the app's decision.
|
|
227
|
+
- Bindings never open, close, navigate or reload anything. After `run()` resolves, the app decides what happens next (for example close a dialog and call `reload()` on a grid).
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## 12. Design-time integration
|
|
232
|
+
|
|
233
|
+
Entry `api_binding_ui/studio` (plus `api_binding_ui/studio.css`). Import it only from design-time code so production bundles never contain it.
|
|
234
|
+
|
|
235
|
+
- The app renders `<BindingWidget framework={{ descriptors, registry, transforms, providers }} />` once, inside `BindingProvider`, in design mode only. It is a floating button that opens the studio in a modal over the app, on the most recently shown `BindingScreen` (an open dialog wins over its page). Views that stay mounted while hidden (inactive tabs) are wrapped in `<BindingActive value={isVisible}>` so their screens are skipped. When the studio closes, the runtime reloads the saved manifests of mounted screens, so edits apply without a page reload.
|
|
236
|
+
- `<BindingStudio>` is the same studio as a plain component, for hosts that mount it on their own page.
|
|
237
|
+
- The studio loads the catalog from the published docs (`docsUrl`, default `https://apidocs.highradius.com/documentation-ui/index.yaml`; JSON or YAML; products from `x-tagGroups`). Each user can change the URL in Settings. A `catalog` passed in `framework` is used instead.
|
|
238
|
+
- Settings also download and import manifests as JSON, so bindings made in the studio can be committed with the app.
|
|
239
|
+
- The widget saves manifests through `BindingProvider persistence` (browser storage by default), so the app runs them immediately. Committed manifests come from Settings → Download.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 13. Versioning
|
|
244
|
+
|
|
245
|
+
| Contract | Version field | On change |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| Descriptor | `version` per kind | Manifests record `kinds`; the studio offers a migration when a kind's version rises |
|
|
248
|
+
| Registry | `registryVersion` | Bumped when the shape changes; a migration ships with the package version that changes it |
|
|
249
|
+
| Catalog | `catalogVersion`, `hash` | bindings to operations no longer in the catalog are reported by validation and hidden in the studio |
|
|
250
|
+
| Manifest | `manifestVersion` | Bumped when the shape changes; a migration ships with the package version that changes it |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 14. Conformance rules for an app (for example, an app built with Lovable)
|
|
255
|
+
|
|
256
|
+
1. Every interactive or data-showing component comes from a library that publishes a descriptor for its kind.
|
|
257
|
+
2. Every component instance has a stable `bindId`, unique within its screen and independent of its label.
|
|
258
|
+
3. Every screen is wrapped in `BindingScreen` with a stable screen id and declares its `context`.
|
|
259
|
+
4. Components get data and call APIs only through their slots. No direct API calls, no hardcoded option lists, no local data hooks in screens. Opening, closing and navigation stay in the app's UI code.
|
|
260
|
+
5. Dialogs and drawers are separate screens with `parent` set to the screen they appear over.
|
|
261
|
+
6. Manifests are committed to the repository and reviewed like code.
|
|
262
|
+
7. The catalog is regenerated from the docs, never edited by hand.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## 15. Out of scope for the framework
|
|
267
|
+
|
|
268
|
+
- Any list of component kinds or slots (they belong to descriptors).
|
|
269
|
+
- Any product, module, operation or field name (they belong to the registry, catalog and manifests).
|
|
270
|
+
- Any vocabulary or naming rule (it belongs to glossaries and docs extensions).
|
|
271
|
+
- Business behavior that is not expressed as slots and steps.
|
package/dist/api-binding-ui.css
DELETED
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
/* src/styles/highradius-bridge.css */
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
/* src/styles/index.css */
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/* src/styles/workspace.css */
|
|
8
|
-
.binding-workspace, .binding-workspace * {
|
|
9
|
-
box-sizing: border-box;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
.binding-workspace, .api-binding, .binding-v2 {
|
|
13
|
-
display: flex;
|
|
14
|
-
background: var(--ds-color-background, #fff);
|
|
15
|
-
color: var(--ds-color-foreground, #0f172a);
|
|
16
|
-
font-family: var(--ds-font-family-sans, sans-serif);
|
|
17
|
-
flex-direction: column;
|
|
18
|
-
height: 100vh;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.binding-workspace h1, .api-binding h1, .binding-v2 h1 {
|
|
22
|
-
font-size: var(--ds-font-size-2xl, 1.5rem);
|
|
23
|
-
font-weight: var(--ds-font-weight-bold, 700);
|
|
24
|
-
margin: 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
.binding-workspace h2, .api-binding h2, .binding-v2 h2 {
|
|
28
|
-
font-size: var(--ds-font-size-lg, 1.125rem);
|
|
29
|
-
font-weight: var(--ds-font-weight-semibold, 600);
|
|
30
|
-
margin: 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.binding-workspace h3, .api-binding h3, .binding-v2 h3 {
|
|
34
|
-
font-size: var(--ds-font-size-base, .875rem);
|
|
35
|
-
font-weight: var(--ds-font-weight-semibold, 600);
|
|
36
|
-
margin: 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.binding-workspace p, .api-binding p, .binding-v2 p {
|
|
40
|
-
line-height: var(--ds-line-height-normal, 1.5);
|
|
41
|
-
margin: 0;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.binding-header, .binding-footer, .binding-v2-header, .binding-v2-footer {
|
|
45
|
-
padding: var(--ds-page-padding, 1rem 1.5rem);
|
|
46
|
-
display: flex;
|
|
47
|
-
gap: var(--ds-space-4, 1rem);
|
|
48
|
-
flex-wrap: wrap;
|
|
49
|
-
justify-content: space-between;
|
|
50
|
-
align-items: center;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.binding-header, .binding-v2-header {
|
|
54
|
-
border-bottom-width: var(--ds-screen-surface-border-width, 1px);
|
|
55
|
-
border-bottom-style: solid;
|
|
56
|
-
border-bottom-color: var(--ds-color-border, #e2e8f0);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.binding-body, .binding-v2-body {
|
|
60
|
-
overflow: auto;
|
|
61
|
-
position: relative;
|
|
62
|
-
padding: var(--ds-page-padding, 1rem 1.5rem);
|
|
63
|
-
flex: 1;
|
|
64
|
-
min-height: 0;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.binding-columns, .binding-v2-columns {
|
|
68
|
-
isolation: isolate;
|
|
69
|
-
display: flex;
|
|
70
|
-
gap: var(--ds-space-8, 2rem);
|
|
71
|
-
flex-wrap: wrap;
|
|
72
|
-
align-items: start;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.binding-main, .binding-v2-main {
|
|
76
|
-
flex-grow: 1;
|
|
77
|
-
flex-basis: var(--ds-dialog-form-min-width, 32rem);
|
|
78
|
-
padding-bottom: var(--ds-space-12, 3rem);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
.binding-main, .binding-stack, .binding-v2-main, .binding-v2-stack {
|
|
82
|
-
display: flex;
|
|
83
|
-
gap: var(--ds-space-4, 1rem);
|
|
84
|
-
flex-direction: column;
|
|
85
|
-
min-width: 0;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
.binding-section, .binding-v2-section {
|
|
89
|
-
padding-bottom: var(--ds-space-6, 1.5rem);
|
|
90
|
-
display: flex;
|
|
91
|
-
gap: var(--ds-space-4, 1rem);
|
|
92
|
-
border-bottom-width: var(--ds-screen-surface-border-width, 1px);
|
|
93
|
-
border-bottom-style: solid;
|
|
94
|
-
border-bottom-color: var(--ds-color-border, #e2e8f0);
|
|
95
|
-
flex-direction: column;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
.binding-section-title, .binding-v2-section-title {
|
|
99
|
-
display: flex;
|
|
100
|
-
justify-content: space-between;
|
|
101
|
-
align-items: center;
|
|
102
|
-
gap: var(--ds-space-4, 1rem);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
.binding-muted, .binding-v2-muted {
|
|
106
|
-
color: var(--ds-color-muted-foreground, #64748b);
|
|
107
|
-
font-size: var(--ds-font-size-sm, .8125rem);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.binding-preview, .binding-v2-preview {
|
|
111
|
-
flex-basis: var(--ds-dialog-min-width, 24rem);
|
|
112
|
-
position: sticky;
|
|
113
|
-
background: var(--ds-color-surface, #fff);
|
|
114
|
-
border-width: var(--ds-screen-surface-border-width, 1px);
|
|
115
|
-
border-style: solid;
|
|
116
|
-
border-color: var(--ds-color-border, #e2e8f0);
|
|
117
|
-
border-radius: var(--ds-radius-md, 6px);
|
|
118
|
-
padding: var(--ds-space-6, 1.5rem);
|
|
119
|
-
display: flex;
|
|
120
|
-
gap: var(--ds-space-4, 1rem);
|
|
121
|
-
flex-direction: column;
|
|
122
|
-
flex-grow: 0;
|
|
123
|
-
max-width: 100%;
|
|
124
|
-
top: 0;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
.binding-panel, .binding-v2-panel {
|
|
128
|
-
position: absolute;
|
|
129
|
-
width: var(--ds-dialog-form-min-width, 32rem);
|
|
130
|
-
background: var(--ds-color-background, #fff);
|
|
131
|
-
border-left-width: var(--ds-screen-surface-border-width, 1px);
|
|
132
|
-
border-left-style: solid;
|
|
133
|
-
border-left-color: var(--ds-color-border, #e2e8f0);
|
|
134
|
-
box-shadow: var(--ds-shadow-md, 0 4px 6px -1px #0000001a);
|
|
135
|
-
padding: var(--ds-space-6, 1.5rem);
|
|
136
|
-
display: flex;
|
|
137
|
-
gap: var(--ds-space-6, 1.5rem);
|
|
138
|
-
z-index: var(--ds-focus-z-index, 50);
|
|
139
|
-
flex-direction: column;
|
|
140
|
-
max-width: 100%;
|
|
141
|
-
min-height: 100%;
|
|
142
|
-
top: 0;
|
|
143
|
-
right: 0;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.binding-details, .binding-v2-details {
|
|
147
|
-
display: flex;
|
|
148
|
-
gap: var(--ds-space-2, .5rem);
|
|
149
|
-
font-size: var(--ds-font-size-sm, .8125rem);
|
|
150
|
-
overflow-wrap: anywhere;
|
|
151
|
-
flex-direction: column;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
.binding-details summary, .binding-v2-details summary {
|
|
155
|
-
cursor: pointer;
|
|
156
|
-
color: var(--ds-color-muted-foreground, #64748b);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.binding-footer, .binding-v2-footer {
|
|
160
|
-
border-top-width: var(--ds-screen-surface-border-width, 1px);
|
|
161
|
-
border-top-style: solid;
|
|
162
|
-
border-top-color: var(--ds-color-border, #e2e8f0);
|
|
163
|
-
background: var(--ds-color-background, #fff);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.binding-simulation, .binding-v2-simulation {
|
|
167
|
-
display: flex;
|
|
168
|
-
gap: var(--ds-space-4, 1rem);
|
|
169
|
-
flex-direction: column;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
.binding-empty, .binding-v2-empty {
|
|
173
|
-
padding: var(--ds-space-12, 3rem);
|
|
174
|
-
margin: auto;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.binding-error, .binding-v2-error {
|
|
178
|
-
color: var(--ds-color-destructive, #ef4444);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.binding-stack-tight, .binding-v2-stack-tight {
|
|
182
|
-
display: flex;
|
|
183
|
-
align-items: flex-start;
|
|
184
|
-
gap: var(--ds-space-1, .25rem);
|
|
185
|
-
flex-direction: column;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
.binding-list, .binding-v2-list {
|
|
189
|
-
padding-left: var(--ds-space-4, 1rem);
|
|
190
|
-
margin: 0;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
.binding-panel-wide, .binding-v2-panel-wide {
|
|
194
|
-
width: var(--ds-dialog-width-large, 48rem);
|
|
195
|
-
overflow: auto;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.binding-call, .binding-v2-call {
|
|
199
|
-
display: flex;
|
|
200
|
-
gap: var(--ds-space-3, .75rem);
|
|
201
|
-
padding: var(--ds-space-4, 1rem);
|
|
202
|
-
border-width: var(--ds-screen-surface-border-width, 1px);
|
|
203
|
-
border-style: solid;
|
|
204
|
-
border-color: var(--ds-color-border, #e2e8f0);
|
|
205
|
-
border-radius: var(--ds-radius-md, 6px);
|
|
206
|
-
flex-direction: column;
|
|
207
|
-
min-width: 0;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
.binding-call-grid, .binding-v2-call-grid {
|
|
211
|
-
display: grid;
|
|
212
|
-
grid-template-columns: repeat(auto-fill, minmax(var(--ds-menu-min-width, 12rem), 1fr));
|
|
213
|
-
gap: var(--ds-space-3, .75rem);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
.binding-params, .binding-v2-params {
|
|
217
|
-
display: flex;
|
|
218
|
-
gap: var(--ds-space-2, .5rem);
|
|
219
|
-
flex-direction: column;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.binding-param, .binding-v2-param {
|
|
223
|
-
display: grid;
|
|
224
|
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
225
|
-
gap: var(--ds-space-3, .75rem);
|
|
226
|
-
overflow-wrap: anywhere;
|
|
227
|
-
align-items: start;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
.binding-endpoint, .binding-v2-endpoint {
|
|
231
|
-
font-family: var(--ds-font-family-mono, monospace);
|
|
232
|
-
font-size: var(--ds-font-size-sm, .8125rem);
|
|
233
|
-
overflow-wrap: anywhere;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
.binding-preview-parts, .binding-v2-preview-parts {
|
|
237
|
-
display: grid;
|
|
238
|
-
grid-template-columns: repeat(auto-fill, minmax(var(--ds-menu-min-width, 12rem), 1fr));
|
|
239
|
-
gap: var(--ds-space-3, .75rem);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
.binding-pre, .binding-v2-pre {
|
|
243
|
-
font-family: var(--ds-font-family-mono, monospace);
|
|
244
|
-
font-size: var(--ds-font-size-sm, .8125rem);
|
|
245
|
-
white-space: pre-wrap;
|
|
246
|
-
overflow-wrap: anywhere;
|
|
247
|
-
margin: 0;
|
|
248
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { ApiBindingContext, WorkspaceComponents } from "../types";
|
|
3
|
-
import "../styles/workspace.css";
|
|
4
|
-
export interface ApiBindingWorkspaceProps {
|
|
5
|
-
pageKey: string;
|
|
6
|
-
initialContext?: ApiBindingContext;
|
|
7
|
-
components?: Partial<WorkspaceComponents>;
|
|
8
|
-
}
|
|
9
|
-
export declare function ApiBindingWorkspace({ pageKey, initialContext, components, }: ApiBindingWorkspaceProps): React.JSX.Element;
|
|
10
|
-
export declare const ApiBindingStudio: typeof ApiBindingWorkspace;
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { ApiBindingConfig, ApiBindingContext } from "../types";
|
|
3
|
-
export declare const TIMING_OPTIONS: {
|
|
4
|
-
value: string;
|
|
5
|
-
label: string;
|
|
6
|
-
}[];
|
|
7
|
-
export declare const FRESHNESS_OPTIONS: {
|
|
8
|
-
value: string;
|
|
9
|
-
label: string;
|
|
10
|
-
}[];
|
|
11
|
-
/**
|
|
12
|
-
* The single canonical API-mapping editor. The global entry shows every call;
|
|
13
|
-
* a field or action entry passes `focus` and sees only calls targeting it or sending its value.
|
|
14
|
-
*/
|
|
15
|
-
export declare function ApiMappings({ config, context, focus, defaultTarget, onChange, }: {
|
|
16
|
-
config: ApiBindingConfig;
|
|
17
|
-
context: ApiBindingContext;
|
|
18
|
-
focus?: string;
|
|
19
|
-
defaultTarget?: string;
|
|
20
|
-
onChange: (updater: (old: ApiBindingConfig) => ApiBindingConfig) => void;
|
|
21
|
-
}): React.JSX.Element;
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { ApiDefinition, BindingGrid, BindingOption, MappingFile, SnapshotComponent, SnapshotContext, WindowKind, WindowTrigger } from "../types";
|
|
3
|
-
export interface BindApiButtonProps {
|
|
4
|
-
appId?: string;
|
|
5
|
-
windowId: string;
|
|
6
|
-
/** Source window, screen, or action name, e.g. "Add Notes", "Create P2P", "Invoice Details" */
|
|
7
|
-
source: string;
|
|
8
|
-
/** Parent grid, panel, or screen name, e.g. "Worklist Grid", "Dashboard", "Main Screen" */
|
|
9
|
-
parent: string;
|
|
10
|
-
/** Optional Grid context (selected rows and columns) if triggered from or acting upon a grid */
|
|
11
|
-
grid?: BindingGrid;
|
|
12
|
-
/** Parent windows / screens context hierarchy */
|
|
13
|
-
parents?: SnapshotContext[];
|
|
14
|
-
/** UI presentation kind (modal, drawer, screen, form, etc.) */
|
|
15
|
-
kind?: WindowKind;
|
|
16
|
-
/** Action trigger info */
|
|
17
|
-
trigger?: WindowTrigger;
|
|
18
|
-
/** Captured dropdown/combobox options from the host form */
|
|
19
|
-
options?: Record<string, BindingOption[]>;
|
|
20
|
-
/** Registered API definitions catalog */
|
|
21
|
-
definitions?: ApiDefinition[];
|
|
22
|
-
/** Existing saved mapping file */
|
|
23
|
-
mappingFile?: MappingFile | null;
|
|
24
|
-
/** Base route for the Studio in the router (defaults to `/api-binding`) */
|
|
25
|
-
routeBase?: string;
|
|
26
|
-
/** Optional container ref to override automatic DOM discovery */
|
|
27
|
-
containerRef?: React.RefObject<HTMLElement | null>;
|
|
28
|
-
/** Optional container CSS selector to locate screen root */
|
|
29
|
-
containerSelector?: string;
|
|
30
|
-
/** Explicit root DOM element */
|
|
31
|
-
rootElement?: HTMLElement | null;
|
|
32
|
-
/** Optional HTML form id */
|
|
33
|
-
formId?: string;
|
|
34
|
-
/** Explicit components / fields override if not discovered via DOM */
|
|
35
|
-
components?: SnapshotComponent[];
|
|
36
|
-
/** Explicit action buttons override */
|
|
37
|
-
buttons?: string[];
|
|
38
|
-
className?: string;
|
|
39
|
-
children?: React.ReactNode;
|
|
40
|
-
}
|
|
41
|
-
export declare function BindApiButton({ appId, windowId, source, parent, grid, parents, kind, trigger, options, definitions, mappingFile, routeBase, containerRef, containerSelector, rootElement, formId, components: explicitComponents, buttons: explicitButtons, className, children, }: BindApiButtonProps): React.JSX.Element;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { RequestPreviewItem } from "../types";
|
|
3
|
-
/** Simulated requests in call order. Nothing here is sent over network. */
|
|
4
|
-
export declare function RequestPreview({ title, requests }: {
|
|
5
|
-
title: string;
|
|
6
|
-
requests: RequestPreviewItem[];
|
|
7
|
-
}): React.JSX.Element;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { ApiBindingConfig, ApiBindingContext } from "../types";
|
|
3
|
-
export declare function SimulationDialog({ config, context, onClose, onVerified, }: {
|
|
4
|
-
config: ApiBindingConfig;
|
|
5
|
-
context: ApiBindingContext;
|
|
6
|
-
onClose: () => void;
|
|
7
|
-
onVerified: (signature: string) => void;
|
|
8
|
-
}): React.JSX.Element;
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { ApiBindingConfig, ApiBindingContext } from "../types";
|
|
3
|
-
export declare function ValidationDialog({ config, context, onClose, onVerified }: {
|
|
4
|
-
config: ApiBindingConfig;
|
|
5
|
-
context: ApiBindingContext;
|
|
6
|
-
onClose: () => void;
|
|
7
|
-
onVerified: (signature: string) => void;
|
|
8
|
-
}): React.JSX.Element;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import type { WorkspaceComponents } from "../types";
|
|
3
|
-
export declare function WorkspaceComponentsProvider({ components, children, }: {
|
|
4
|
-
components?: Partial<WorkspaceComponents>;
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
}): React.JSX.Element;
|
|
7
|
-
export declare function useWorkspaceComponents(): Required<WorkspaceComponents>;
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export interface AppGridColDef<T = unknown> {
|
|
3
|
-
field?: keyof T | string;
|
|
4
|
-
headerName: string;
|
|
5
|
-
fill?: boolean;
|
|
6
|
-
type?: "number" | "badge" | "action";
|
|
7
|
-
badgeVariants?: Record<string, "success" | "warning" | "danger" | "neutral">;
|
|
8
|
-
labelField?: keyof T | string;
|
|
9
|
-
icon?: string;
|
|
10
|
-
onClick?: (row: T) => void;
|
|
11
|
-
render?: (row: T) => React.ReactNode;
|
|
12
|
-
}
|
|
13
|
-
export declare function createActionColumn<T>(spec: {
|
|
14
|
-
headerName: string;
|
|
15
|
-
labelField: keyof T | string;
|
|
16
|
-
icon?: string;
|
|
17
|
-
onClick: (row: T) => void;
|
|
18
|
-
}): AppGridColDef<T>;
|
|
19
|
-
export interface AppGridProps<T> {
|
|
20
|
-
rows: T[];
|
|
21
|
-
columns: AppGridColDef<T>[];
|
|
22
|
-
getRowKey?: (row: T, index: number) => string | number;
|
|
23
|
-
className?: string;
|
|
24
|
-
filtering?: boolean;
|
|
25
|
-
height?: string;
|
|
26
|
-
}
|
|
27
|
-
export declare function AppGrid<T extends Record<string, any>>({ rows, columns, getRowKey, className, }: AppGridProps<T>): React.JSX.Element;
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
export type BadgeVariant = "neutral" | "info" | "success" | "warning" | "danger" | "brand";
|
|
3
|
-
export interface BadgeProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
4
|
-
variant?: BadgeVariant;
|
|
5
|
-
children: React.ReactNode;
|
|
6
|
-
className?: string;
|
|
7
|
-
style?: React.CSSProperties;
|
|
8
|
-
}
|
|
9
|
-
export declare function Badge({ variant, children, className, style, ...rest }: BadgeProps): React.JSX.Element;
|