@adia-ai/adia-ui-factory 0.2.2 → 0.2.4
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/.claude-plugin/plugin.json +12 -3
- package/.mcp.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +24 -1
- package/bin/adia-scaffold +93 -13
- package/package.json +1 -1
- package/references/shell-embed.md +10 -0
- package/references/spa-architecture.md +7 -0
- package/skills/adia-ui-kit/CHANGELOG.md +41 -0
- package/skills/adia-ui-kit/SKILL.md +442 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/Guidelines.md +87 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/components.md +142 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/styles.md +118 -0
- package/skills/adia-ui-kit/assets/figma-make/guidelines/tokens.md +120 -0
- package/skills/adia-ui-kit/assets/team-scaffold/archive-README.md +30 -0
- package/skills/adia-ui-kit/assets/team-scaffold/drafts-README.md +57 -0
- package/skills/adia-ui-kit/assets/team-scaffold/inbox-README.md +76 -0
- package/skills/adia-ui-kit/assets/team-scaffold/outbox-README.md +46 -0
- package/skills/adia-ui-kit/assets/team-scaffold/team-root.README.md +51 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-README.md +208 -0
- package/skills/adia-ui-kit/assets/team-scaffold/tickets-REGISTRY.md +81 -0
- package/skills/adia-ui-kit/assets/templates/AGENTS.template.md +128 -0
- package/skills/adia-ui-kit/assets/templates/CLAUDE.template.md +1 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK-OMISSIONS-DEFENSE.template.md +150 -0
- package/skills/adia-ui-kit/assets/templates/FEEDBACK.template.md +213 -0
- package/skills/adia-ui-kit/assets/templates/RESPONSE.template.md +114 -0
- package/skills/adia-ui-kit/assets/templates/figma-make-guidelines.md +90 -0
- package/skills/adia-ui-kit/evals/adversarial-content-trust.json +83 -0
- package/skills/adia-ui-kit/evals/evals.json +55 -0
- package/skills/adia-ui-kit/evals/routing-corpus.json +121 -0
- package/skills/adia-ui-kit/evals/teach-routing-cases.json +92 -0
- package/skills/adia-ui-kit/references/capabilities.md +245 -0
- package/skills/adia-ui-kit/references/catalog-lookup.md +186 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v1.md +8 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v2.md +3585 -0
- package/skills/adia-ui-kit/references/changelog-archive/CHANGELOG-v3.md +1899 -0
- package/skills/adia-ui-kit/references/common-gotchas-consumer.md +522 -0
- package/skills/adia-ui-kit/references/component-literacy.md +291 -0
- package/skills/adia-ui-kit/references/composition.md +245 -0
- package/skills/adia-ui-kit/references/correction-loop.md +462 -0
- package/skills/adia-ui-kit/references/css-policy.md +343 -0
- package/skills/adia-ui-kit/references/decision-model.md +158 -0
- package/skills/adia-ui-kit/references/diagnostic-output.md +57 -0
- package/skills/adia-ui-kit/references/docs-catalog.md +239 -0
- package/skills/adia-ui-kit/references/exclusions.md +225 -0
- package/skills/adia-ui-kit/references/feedback-authoring.md +466 -0
- package/skills/adia-ui-kit/references/figma-make.md +244 -0
- package/skills/adia-ui-kit/references/flows-auth.md +307 -0
- package/skills/adia-ui-kit/references/flows-content.md +206 -0
- package/skills/adia-ui-kit/references/flows-data.md +348 -0
- package/skills/adia-ui-kit/references/flows-interaction.md +181 -0
- package/skills/adia-ui-kit/references/flows-shells.md +336 -0
- package/skills/adia-ui-kit/references/gap-detection.md +266 -0
- package/skills/adia-ui-kit/references/harness.md +284 -0
- package/skills/adia-ui-kit/references/loading-protocol.md +406 -0
- package/skills/adia-ui-kit/references/local-link.md +198 -0
- package/skills/adia-ui-kit/references/mcp.md +401 -0
- package/skills/adia-ui-kit/references/migration.md +116 -0
- package/skills/adia-ui-kit/references/mission.md +194 -0
- package/skills/adia-ui-kit/references/mode-routing.md +298 -0
- package/skills/adia-ui-kit/references/patterns-agent.md +223 -0
- package/skills/adia-ui-kit/references/patterns-data.md +1065 -0
- package/skills/adia-ui-kit/references/patterns-forms.md +223 -0
- package/skills/adia-ui-kit/references/patterns-layout.md +326 -0
- package/skills/adia-ui-kit/references/patterns-recipes.md +1416 -0
- package/skills/adia-ui-kit/references/patterns-shells.md +1097 -0
- package/skills/adia-ui-kit/references/patterns-traits.md +259 -0
- package/skills/adia-ui-kit/references/patterns-wiring.md +339 -0
- package/skills/adia-ui-kit/references/pev-rationale.md +137 -0
- package/skills/adia-ui-kit/references/reasoning-gate.md +146 -0
- package/skills/adia-ui-kit/references/reasoning-methodologies.md +112 -0
- package/skills/adia-ui-kit/references/recon.md +192 -0
- package/skills/adia-ui-kit/references/rendering-model.md +431 -0
- package/skills/adia-ui-kit/references/response-authoring.md +398 -0
- package/skills/adia-ui-kit/references/session-log-schema.md +203 -0
- package/skills/adia-ui-kit/references/session-log-template.md +118 -0
- package/skills/adia-ui-kit/references/setup.md +652 -0
- package/skills/adia-ui-kit/references/skill-hygiene.md +135 -0
- package/skills/adia-ui-kit/references/spec-to-ui-reasoning.md +394 -0
- package/skills/adia-ui-kit/references/state.md +229 -0
- package/skills/adia-ui-kit/references/teach-protocol.md +421 -0
- package/skills/adia-ui-kit/references/theme.md +142 -0
- package/skills/adia-ui-kit/references/version-check.md +69 -0
- package/skills/adia-ui-kit/scripts/audit-decision-trace.mjs +198 -0
- package/skills/adia-ui-kit/scripts/audit-kit-roster.mjs +366 -0
- package/skills/adia-ui-kit/scripts/changelog-archive.mjs +126 -0
- package/skills/adia-ui-kit/scripts/check-adia-versions.py +404 -0
- package/skills/adia-ui-kit/scripts/run-skill-evals.mjs +358 -0
- package/skills/adia-ui-kit/scripts/validate-session-log.mjs +242 -0
- package/skills/adia-ui-kit/skill.json +102 -0
- package/skills/adia-ui-migration/CHANGELOG.md +47 -0
- package/skills/adia-ui-migration/SKILL.md +381 -0
- package/skills/adia-ui-migration/skill.json +29 -0
- package/skills/adia-ui-spa/SKILL.md +1 -1
- package/skills/adia-ui-verify/SKILL.md +2 -2
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
# Trait Patterns
|
|
2
|
+
|
|
3
|
+
## What Traits Are
|
|
4
|
+
|
|
5
|
+
Traits are composable behaviors that attach to any HTML element via `defineTrait()`.
|
|
6
|
+
They follow a factory pattern: call the trait function to get an instance, connect it
|
|
7
|
+
to an element, disconnect when done.
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
import { pressable, focusable } from '@adiahealth/web-components/traits';
|
|
11
|
+
|
|
12
|
+
const btn = document.querySelector('#my-btn');
|
|
13
|
+
const inst = pressable();
|
|
14
|
+
inst.connect(btn);
|
|
15
|
+
btn.addEventListener('press', () => console.log('pressed!'));
|
|
16
|
+
// Later: inst.disconnect(btn);
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
On AdiaElement subclasses:
|
|
20
|
+
```js
|
|
21
|
+
class MyWidget extends AdiaElement {
|
|
22
|
+
static traits = [pressable, focusable]; // auto-connect on mount
|
|
23
|
+
}
|
|
24
|
+
// Or at runtime:
|
|
25
|
+
myElement.addTrait(draggable);
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Conventions
|
|
29
|
+
|
|
30
|
+
- Every trait sets/removes `data-{trait-name}-{state}` attributes
|
|
31
|
+
- Cleanup on `disconnect()` removes all managed attributes and listeners
|
|
32
|
+
- Traits compose — multiple traits on the same element
|
|
33
|
+
- Traits never modify innerHTML — attributes and listeners only
|
|
34
|
+
- Schema available via `trait.schema`
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Interaction Traits
|
|
39
|
+
|
|
40
|
+
### pressable
|
|
41
|
+
Dispatches `press` event. Sets `data-pressable-pressed` during press.
|
|
42
|
+
```js
|
|
43
|
+
inst.connect(el);
|
|
44
|
+
el.addEventListener('press', () => { /* handle */ });
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### hoverable
|
|
48
|
+
Sets `data-hoverable-hovered`. Fires `hover-start` / `hover-end`.
|
|
49
|
+
|
|
50
|
+
### focusable
|
|
51
|
+
Focus ring management. Sets `data-focusable-focused`.
|
|
52
|
+
|
|
53
|
+
### draggable
|
|
54
|
+
Makes element draggable. Sets `data-draggable-dragging`.
|
|
55
|
+
Events: `drag-start`, `drag-move` (with `dx`, `dy`), `drag-end`.
|
|
56
|
+
```js
|
|
57
|
+
el.addEventListener('drag-move', (e) => console.log(e.detail.dx, e.detail.dy));
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### inertia-drag
|
|
61
|
+
Like draggable but with momentum/velocity after release. Element continues moving with deceleration.
|
|
62
|
+
|
|
63
|
+
### resizable
|
|
64
|
+
Adds resize handles. Sets `data-resizable-resizing` during resize.
|
|
65
|
+
|
|
66
|
+
### tossable
|
|
67
|
+
Fling/throw physics — element can be tossed with velocity.
|
|
68
|
+
|
|
69
|
+
### scale-press
|
|
70
|
+
Scales element down on press (tactile feedback). Sets `data-scale-press-active`.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Focus & Keyboard Traits
|
|
75
|
+
|
|
76
|
+
### focus-trap
|
|
77
|
+
Traps Tab focus within a container (modals, dialogs):
|
|
78
|
+
```js
|
|
79
|
+
focusTrap().connect(modalElement);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### keyboard-nav
|
|
83
|
+
Roving focus with arrow keys inside a container:
|
|
84
|
+
```js
|
|
85
|
+
keyboardNav().connect(listElement);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### hotkey
|
|
89
|
+
Bind keyboard shortcuts:
|
|
90
|
+
```js
|
|
91
|
+
const inst = hotkey();
|
|
92
|
+
inst.connect(el, { key: 'mod+k' });
|
|
93
|
+
el.addEventListener('hotkey', () => openPalette());
|
|
94
|
+
```
|
|
95
|
+
`mod` = Cmd on Mac, Ctrl on Windows/Linux.
|
|
96
|
+
|
|
97
|
+
### roving-tabindex
|
|
98
|
+
Manages tabindex across a group of focusable elements. Only one active at a time.
|
|
99
|
+
|
|
100
|
+
### typeahead
|
|
101
|
+
Type-ahead search within a list — focuses matching item as user types.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Form Traits
|
|
106
|
+
|
|
107
|
+
### form-associated
|
|
108
|
+
Makes non-form element participate in `<form>` submission.
|
|
109
|
+
|
|
110
|
+
### dirty-state
|
|
111
|
+
Tracks if value changed from initial. Sets `data-dirty-state-dirty`.
|
|
112
|
+
|
|
113
|
+
### disabled-state
|
|
114
|
+
Manages `data-disabled-state-disabled` with pointer/keyboard blocking.
|
|
115
|
+
|
|
116
|
+
### resettable
|
|
117
|
+
Restores initial value on form reset.
|
|
118
|
+
|
|
119
|
+
### validation
|
|
120
|
+
Reads rules from `data-validate="required,minlength:3,pattern:..."`.
|
|
121
|
+
Sets `data-validation-invalid`, `data-validation-valid`, `data-validation-message`.
|
|
122
|
+
Dispatches `validated` event with `{ valid, errors }`.
|
|
123
|
+
|
|
124
|
+
### value-sync
|
|
125
|
+
Syncs attribute `value`, property `value`, and controller state.
|
|
126
|
+
Dispatches `value-change` with `{ value, oldValue, source }`.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Animation Traits
|
|
131
|
+
|
|
132
|
+
### fade-presence
|
|
133
|
+
Animate in/out with opacity transition. Set `data-fade-presence-visible` to trigger.
|
|
134
|
+
|
|
135
|
+
### count-up
|
|
136
|
+
Animate number from 0 to target:
|
|
137
|
+
```js
|
|
138
|
+
el.setAttribute('data-count-up-target', '1000');
|
|
139
|
+
el.setAttribute('data-count-up-active', '');
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### attention-pulse
|
|
143
|
+
Pulsing glow to draw attention.
|
|
144
|
+
|
|
145
|
+
### glow-focus
|
|
146
|
+
Glowing focus ring effect (alternative to default outline).
|
|
147
|
+
|
|
148
|
+
### gradient-shift
|
|
149
|
+
Animated gradient background shift.
|
|
150
|
+
|
|
151
|
+
### confetti / confetti-burst
|
|
152
|
+
Particle celebration effects:
|
|
153
|
+
```js
|
|
154
|
+
confetti().connect(el);
|
|
155
|
+
el.setAttribute('data-confetti-active', '');
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### spring-animate
|
|
159
|
+
Spring physics animation (bounce, overshoot).
|
|
160
|
+
|
|
161
|
+
### shimmer-loading
|
|
162
|
+
Shimmer/skeleton loading effect overlay.
|
|
163
|
+
|
|
164
|
+
### ripple
|
|
165
|
+
Material-style ripple on click/press.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Layout & Positioning Traits
|
|
170
|
+
|
|
171
|
+
### anchor-positioning
|
|
172
|
+
Position element relative to an anchor (popovers, tooltips):
|
|
173
|
+
```js
|
|
174
|
+
anchorPositioning().connect(popover, { anchor: triggerEl });
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### portal
|
|
178
|
+
Teleport element to a different DOM location (overlays):
|
|
179
|
+
```js
|
|
180
|
+
portal().connect(el, { target: document.body });
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### parallax
|
|
184
|
+
Scroll-linked parallax movement.
|
|
185
|
+
|
|
186
|
+
### magnetic-hover
|
|
187
|
+
Element subtly follows cursor on hover.
|
|
188
|
+
|
|
189
|
+
### snap-to-grid
|
|
190
|
+
Snaps drag position to a grid:
|
|
191
|
+
```js
|
|
192
|
+
snapToGrid().connect(el, { gridSize: 16 });
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### scroll-lock
|
|
196
|
+
Prevents body scroll (used with modals/drawers).
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Observer Traits
|
|
201
|
+
|
|
202
|
+
### intersection-observer-trait
|
|
203
|
+
Element visibility tracking:
|
|
204
|
+
```js
|
|
205
|
+
intersectionObserverTrait().connect(el);
|
|
206
|
+
el.addEventListener('intersect', (e) => console.log(e.detail.isIntersecting));
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### resize-observer-trait
|
|
210
|
+
Element resize tracking:
|
|
211
|
+
```js
|
|
212
|
+
resizeObserverTrait().connect(el);
|
|
213
|
+
el.addEventListener('resize', (e) => console.log(e.detail.width, e.detail.height));
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Visual Traits
|
|
219
|
+
|
|
220
|
+
### noise-texture
|
|
221
|
+
Subtle noise overlay on element background.
|
|
222
|
+
|
|
223
|
+
### drag-ghost
|
|
224
|
+
Custom drag ghost image during drag operations.
|
|
225
|
+
|
|
226
|
+
### haptic-feedback
|
|
227
|
+
Vibration feedback on interaction (mobile).
|
|
228
|
+
|
|
229
|
+
### tilt-hover
|
|
230
|
+
3D tilt effect following cursor position on hover.
|
|
231
|
+
|
|
232
|
+
### sound-feedback
|
|
233
|
+
Play audio on interaction events.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## State Traits
|
|
238
|
+
|
|
239
|
+
### active-state
|
|
240
|
+
Toggles `data-active-state-active`. Useful for selection:
|
|
241
|
+
```js
|
|
242
|
+
activeState().connect(el);
|
|
243
|
+
el.addEventListener('click', () => el.toggleAttribute('data-active-state-active'));
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Full Trait Inventory (44)
|
|
249
|
+
|
|
250
|
+
| Category | Traits |
|
|
251
|
+
|----------|--------|
|
|
252
|
+
| Interaction | pressable, hoverable, focusable, draggable, resizable, inertia-drag, tossable, scale-press |
|
|
253
|
+
| Focus/Keyboard | focus-trap, keyboard-nav, hotkey, roving-tabindex, typeahead |
|
|
254
|
+
| Form | form-associated, dirty-state, disabled-state, resettable, validation, value-sync |
|
|
255
|
+
| Animation | fade-presence, count-up, attention-pulse, glow-focus, gradient-shift, confetti, confetti-burst, spring-animate, shimmer-loading, ripple |
|
|
256
|
+
| Layout | anchor-positioning, portal, parallax, magnetic-hover, snap-to-grid, scroll-lock |
|
|
257
|
+
| Observer | intersection-observer-trait, resize-observer-trait |
|
|
258
|
+
| Visual | noise-texture, drag-ghost, haptic-feedback, tilt-hover, sound-feedback |
|
|
259
|
+
| State | active-state |
|
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Wiring, Manifests & Evolution Patterns
|
|
2
|
+
|
|
3
|
+
## Wiring Contract (A007)
|
|
4
|
+
|
|
5
|
+
Surfaces connect to live data, state, and actions via `wireComponents` messages.
|
|
6
|
+
Always sent AFTER `updateComponents` — hosts must exist before wiring attaches.
|
|
7
|
+
|
|
8
|
+
### Complete Wired Form
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
[
|
|
12
|
+
{ "type": "createSurface", "surfaceId": "profile" },
|
|
13
|
+
{
|
|
14
|
+
"type": "updateComponents",
|
|
15
|
+
"surfaceId": "profile",
|
|
16
|
+
"components": [
|
|
17
|
+
{ "id": "root", "component": "Card", "size": "lg", "children": ["hdr", "form", "ftr"] },
|
|
18
|
+
{ "id": "hdr", "component": "Header", "children": ["title"] },
|
|
19
|
+
{ "id": "title", "component": "Text", "variant": "h3", "textContent": "Edit Profile" },
|
|
20
|
+
{ "id": "form", "component": "FormContainer", "children": ["fields"] },
|
|
21
|
+
{ "id": "fields", "component": "Column", "gap": "md", "children": ["name", "email"] },
|
|
22
|
+
{ "id": "name", "component": "TextField", "label": "Name", "name": "name", "value": { "path": "/user/name" }, "required": true },
|
|
23
|
+
{ "id": "email", "component": "TextField", "label": "Email", "name": "email", "type": "email", "value": { "path": "/user/email" } },
|
|
24
|
+
{ "id": "ftr", "component": "Footer", "children": ["cancel", "save"] },
|
|
25
|
+
{ "id": "cancel", "component": "Button", "text": "Cancel", "variant": "outline" },
|
|
26
|
+
{ "id": "save", "component": "Button", "text": "Save", "variant": "primary" }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"type": "wireComponents",
|
|
31
|
+
"surfaceId": "profile",
|
|
32
|
+
"data": {
|
|
33
|
+
"sources": [
|
|
34
|
+
{ "id": "user", "path": "/user", "uri": "resource://users/{userId}", "refresh": "once" }
|
|
35
|
+
],
|
|
36
|
+
"params": { "userId": { "from": "route", "key": "id" } }
|
|
37
|
+
},
|
|
38
|
+
"state": {
|
|
39
|
+
"controllers": [
|
|
40
|
+
{ "id": "form-ctrl", "type": "FormController", "host": "form", "config": { "validateOn": "blur" } }
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"actions": [
|
|
44
|
+
{
|
|
45
|
+
"event": "form-submit", "source": "form",
|
|
46
|
+
"handler": "submit-resource", "method": "PUT", "uri": "resource://users/{userId}",
|
|
47
|
+
"body": { "from": "event-detail", "key": "data" },
|
|
48
|
+
"onSuccess": { "notify": "Profile saved", "refresh-source": "user" },
|
|
49
|
+
"onError": { "notify": "Save failed. Please try again." }
|
|
50
|
+
},
|
|
51
|
+
{ "event": "click", "source": "cancel", "handler": "navigate-back" }
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Four Wiring Blocks (all optional)
|
|
58
|
+
|
|
59
|
+
**data** — Where content comes from:
|
|
60
|
+
```json
|
|
61
|
+
"data": {
|
|
62
|
+
"sources": [
|
|
63
|
+
{ "id": "patient", "path": "/patient", "uri": "resource://patients/{id}", "refresh": "on-focus" },
|
|
64
|
+
{ "id": "types", "path": "/visitTypes", "uri": "resource://visit-types", "refresh": "once" }
|
|
65
|
+
],
|
|
66
|
+
"params": { "id": { "from": "route", "key": "patientId" } }
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**state** — Controllers for lifecycle management:
|
|
71
|
+
```json
|
|
72
|
+
"state": {
|
|
73
|
+
"controllers": [
|
|
74
|
+
{ "id": "form", "type": "FormController", "host": "form-root", "config": { "validateOn": "blur" } },
|
|
75
|
+
{ "id": "stream", "type": "DataStreamController", "host": "chart", "config": { "max": 60, "throttle": 500 } }
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
**actions** — Event -> handler bindings:
|
|
81
|
+
```json
|
|
82
|
+
"actions": [
|
|
83
|
+
{
|
|
84
|
+
"event": "form-submit", "source": "form-root",
|
|
85
|
+
"handler": "submit-resource", "method": "POST", "uri": "resource://checkins",
|
|
86
|
+
"body": { "from": "event-detail", "key": "data" },
|
|
87
|
+
"onSuccess": { "notify": "Done", "navigate": "/patients/{id}/visits" },
|
|
88
|
+
"onError": { "notify": "Failed" }
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**provides** — Context for child surfaces:
|
|
94
|
+
```json
|
|
95
|
+
"provides": {
|
|
96
|
+
"context": "patient-ctx",
|
|
97
|
+
"host": "root",
|
|
98
|
+
"shape": { "patient": { "path": "/patient" }, "appointments": { "path": "/appointments" } }
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Child Context Consumption
|
|
103
|
+
|
|
104
|
+
Children consume from a provider without fetching:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"type": "wireComponents",
|
|
108
|
+
"surfaceId": "insurance-card",
|
|
109
|
+
"data": {
|
|
110
|
+
"sources": [
|
|
111
|
+
{ "id": "insurance", "path": "/insurance", "from-context": "patient-ctx", "key": "patient.insurance" }
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
No fetch — reads from the nearest ancestor provider with that context name.
|
|
118
|
+
|
|
119
|
+
### Built-in Handlers
|
|
120
|
+
|
|
121
|
+
| Handler | Purpose | Required fields |
|
|
122
|
+
|---------|---------|-----------------|
|
|
123
|
+
| `submit-resource` | POST/PUT/DELETE to resource URI | `uri`, `method` |
|
|
124
|
+
| `update-model` | Update data model at a path | `path` |
|
|
125
|
+
| `navigate` | Route navigation (`{param}` templates) | `navigate` value |
|
|
126
|
+
| `navigate-back` | Browser history back | — |
|
|
127
|
+
| `controller-command` | Invoke controller command | `controllerId`, `command` |
|
|
128
|
+
| `emit-event` | Re-emit as named CustomEvent | `eventName` |
|
|
129
|
+
| `refresh-source` | Re-fetch a data source | `sourceId` |
|
|
130
|
+
| `notify` | Show toast notification | `message` |
|
|
131
|
+
|
|
132
|
+
### Built-in Controllers
|
|
133
|
+
|
|
134
|
+
| Type | Manages | Commands |
|
|
135
|
+
|------|---------|----------|
|
|
136
|
+
| `FormController` | Validation, dirty/pristine | `validate`, `reset`, `setFieldError` |
|
|
137
|
+
| `DataStreamController` | Live data buffer | `push`, `connect`, `poll`, `stop`, `clear` |
|
|
138
|
+
| `SelectionController` | Selected items | `select`, `deselect`, `toggle`, `selectAll`, `clear` |
|
|
139
|
+
| `ToggleController` | Boolean on/off | `toggle`, `set` |
|
|
140
|
+
| `AccordionController` | Panel expand/collapse | `open`, `close`, `toggle` |
|
|
141
|
+
|
|
142
|
+
### Value Extraction
|
|
143
|
+
|
|
144
|
+
```json
|
|
145
|
+
{ "from": "event-detail", "key": "data" } // e.detail.data
|
|
146
|
+
{ "from": "event-target", "key": "value" } // e.target.value
|
|
147
|
+
{ "from": "model", "path": "/patient/name" } // data model lookup
|
|
148
|
+
{ "from": "literal", "value": "scheduled" } // static value
|
|
149
|
+
{ "from": "param", "key": "patientId" } // resolved route param
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Refresh Strategies
|
|
153
|
+
|
|
154
|
+
| Strategy | Behavior |
|
|
155
|
+
|----------|----------|
|
|
156
|
+
| `once` | Fetch on initial wire |
|
|
157
|
+
| `on-focus` | Re-fetch on window focus |
|
|
158
|
+
| `interval:{ms}` | Poll at interval |
|
|
159
|
+
| `stream` | Live via DataStreamController |
|
|
160
|
+
| `on-action:{name}` | Re-fetch after action completes |
|
|
161
|
+
|
|
162
|
+
### Custom Registration
|
|
163
|
+
|
|
164
|
+
```js
|
|
165
|
+
import { registerController, registerHandler, registerResolver } from '@adiahealth/a2ui';
|
|
166
|
+
|
|
167
|
+
registerController('CheckinController', async () => (await import('./checkin-ctrl.js')).CheckinController);
|
|
168
|
+
registerHandler('show-modal', async (ctx) => { ctx.source.closest('modal-ui').show = true; });
|
|
169
|
+
registerResolver('fhir', async (uri) => (await fetch(uri.replace('fhir://', 'https://fhir.api/'))).json());
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Surface Manifests (A008)
|
|
175
|
+
|
|
176
|
+
Multiple surfaces declare relationships via a typed manifest.
|
|
177
|
+
|
|
178
|
+
### Building a Manifest
|
|
179
|
+
|
|
180
|
+
```js
|
|
181
|
+
import { SurfaceManifest, ManifestRuntime, sharedContextStore } from '@adiahealth/a2ui';
|
|
182
|
+
|
|
183
|
+
const manifest = new SurfaceManifest({ id: 'checkin-flow', name: 'Patient Check-in' });
|
|
184
|
+
|
|
185
|
+
// Add surfaces
|
|
186
|
+
manifest.addSurface('schedule', {
|
|
187
|
+
name: "Today's Schedule",
|
|
188
|
+
route: '/schedule',
|
|
189
|
+
entryPoint: true,
|
|
190
|
+
consumes: { 'patient-ctx': { keys: ['appointments'] } },
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
manifest.addSurface('checkin', {
|
|
194
|
+
name: 'Patient Check-in',
|
|
195
|
+
route: '/patients/:patientId/checkin',
|
|
196
|
+
requiredParams: ['patientId'],
|
|
197
|
+
produces: { 'checkin-result': { type: 'object' } },
|
|
198
|
+
consumes: { 'patient-ctx': { keys: ['patient'] } },
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Define shared context
|
|
202
|
+
manifest.defineSharedContext('patient-ctx', {
|
|
203
|
+
shape: { patient: { type: 'object' }, appointments: { type: 'array' } },
|
|
204
|
+
source: { uri: 'resource://patients/{patientId}', refresh: 'on-focus' },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Add associations
|
|
208
|
+
manifest.addAssociation({ type: 'routes-to', from: 'schedule', to: 'checkin', trigger: 'action:begin-checkin' });
|
|
209
|
+
manifest.addAssociation({ type: 'shares-context', from: 'schedule', to: 'checkin', context: 'patient-ctx' });
|
|
210
|
+
manifest.addAssociation({ type: 'feeds', from: 'checkin', to: 'summary', trigger: 'action:submit-checkin',
|
|
211
|
+
mapping: { '/visit': { from: 'action-result' } }
|
|
212
|
+
});
|
|
213
|
+
manifest.addAssociation({ type: 'triggers', from: 'checkin', to: 'schedule', trigger: 'action:submit-checkin',
|
|
214
|
+
effect: 'refresh-source:appointments'
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Validate
|
|
218
|
+
const { valid, issues } = manifest.validate();
|
|
219
|
+
|
|
220
|
+
// Serialize / restore
|
|
221
|
+
const json = manifest.toJSON();
|
|
222
|
+
const restored = SurfaceManifest.fromJSON(json);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### ManifestRuntime
|
|
226
|
+
|
|
227
|
+
```js
|
|
228
|
+
const runtime = new ManifestRuntime(manifest);
|
|
229
|
+
|
|
230
|
+
await runtime.onSurfaceMount('schedule', { patientId: '123' });
|
|
231
|
+
runtime.onSurfaceUnmount('schedule');
|
|
232
|
+
|
|
233
|
+
// Action fires cross-surface triggers
|
|
234
|
+
await runtime.onActionComplete('checkin', 'submit-checkin', { id: 'visit-456' });
|
|
235
|
+
// -> navigates to summary, feeds data, refreshes schedule
|
|
236
|
+
|
|
237
|
+
const targets = runtime.getNavigationTargets('schedule');
|
|
238
|
+
// -> [{ surfaceId: 'checkin', route: '/patients/:id/checkin', trigger: 'action:begin-checkin' }]
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### ContextStore
|
|
242
|
+
|
|
243
|
+
```js
|
|
244
|
+
sharedContextStore.define('patient-ctx', { shape: { patient: {} } });
|
|
245
|
+
sharedContextStore.update('patient-ctx', { patient: { name: 'Jane Doe' } });
|
|
246
|
+
sharedContextStore.read('patient-ctx', 'patient.name'); // -> 'Jane Doe'
|
|
247
|
+
|
|
248
|
+
const unsub = sharedContextStore.subscribe('patient-ctx', (data) => console.log(data));
|
|
249
|
+
await sharedContextStore.join('patient-ctx', 'checkin-surface', fetchFn, params);
|
|
250
|
+
sharedContextStore.leave('patient-ctx', 'checkin-surface');
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Association Types
|
|
254
|
+
|
|
255
|
+
| Type | Direction | When to use |
|
|
256
|
+
|------|-----------|-------------|
|
|
257
|
+
| `routes-to` | A -> B | User navigates from A to B |
|
|
258
|
+
| `feeds` | A -> B | A's output becomes B's input |
|
|
259
|
+
| `shares-context` | A <-> B | Both read the same store |
|
|
260
|
+
| `depends-on` | A -> B | A can't render until B is ready |
|
|
261
|
+
| `triggers` | A -> B | A's action causes side effect on B |
|
|
262
|
+
| `contains` | A -> B | B renders inside A |
|
|
263
|
+
| `slots-into` | A -> B | A declares placement preference in B |
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Evolution Engine
|
|
268
|
+
|
|
269
|
+
### Pattern Export & Import
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
import { savePattern, importPattern, buildPatternJSON } from '@adia-ai/a2ui-compose';
|
|
273
|
+
|
|
274
|
+
// Build pattern from generation
|
|
275
|
+
const pattern = buildPatternJSON('login-form', {
|
|
276
|
+
components: result.messages[0].components,
|
|
277
|
+
intent: 'login form with email and password',
|
|
278
|
+
domain: 'forms',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Save (downloads .json + .html in browser)
|
|
282
|
+
savePattern('login-form', {
|
|
283
|
+
messages: result.messages,
|
|
284
|
+
validation: result.validation,
|
|
285
|
+
intent: 'login form',
|
|
286
|
+
canvasHTML: canvas.getHTML(),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Import at runtime
|
|
290
|
+
importPattern(patternJSON); // -> { success: true, name: 'login-form' }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Feedback Collection
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
import { FeedbackCollector } from '@adia-ai/a2ui-compose';
|
|
297
|
+
|
|
298
|
+
const fb = new FeedbackCollector();
|
|
299
|
+
fb.initFromGeneration(execId, { intent, domain, mode, messages, validation });
|
|
300
|
+
fb.collectFeedback(execId, {
|
|
301
|
+
rating: 5, intentAlignment: 5, visualQuality: 4, componentChoice: 5, userEdited: false, notes: 'Perfect',
|
|
302
|
+
});
|
|
303
|
+
fb.collectPatternFeedback(execId, { shouldBePattern: true, suggestedName: 'login-basic' });
|
|
304
|
+
fb.exportFeedback(); // downloads training JSON
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Pattern Promotion
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
import { evaluatePromotion } from '@adia-ai/a2ui-compose';
|
|
311
|
+
|
|
312
|
+
const result = evaluatePromotion({
|
|
313
|
+
score: 98, feedback: { rating: 5, shouldBePattern: true },
|
|
314
|
+
alignment: { score: 0.9 }, componentTypes: ['Card', 'TextField', 'Button'], intent: 'login form',
|
|
315
|
+
});
|
|
316
|
+
// -> { shouldPromote: true, confidence: 0.97, suggestedName: 'login-form' }
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Promotion criteria: `score >= 95 AND rating >= 4 AND alignment >= 0.8`.
|
|
320
|
+
|
|
321
|
+
### Intent Alignment
|
|
322
|
+
|
|
323
|
+
```js
|
|
324
|
+
import { checkIntentAlignment } from '@adia-ai/a2ui-compose';
|
|
325
|
+
|
|
326
|
+
const alignment = checkIntentAlignment('login form with email, password, submit', components);
|
|
327
|
+
// -> { score: 0.83, checks: [...], gaps: ['Missing action: "submit"'] }
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Intent Decomposition
|
|
331
|
+
|
|
332
|
+
```js
|
|
333
|
+
import { decomposeIntent, composeSubtasks } from '@adia-ai/a2ui-compose';
|
|
334
|
+
|
|
335
|
+
const decomp = decomposeIntent('settings page with profile, notifications, security, billing');
|
|
336
|
+
// -> { shouldDecompose: true, subtasks: [{ intent: '...', label: 'Profile' }, ...], layout: { component: 'Tabs', child: 'Tab' } }
|
|
337
|
+
|
|
338
|
+
const composed = composeSubtasks(decomp.layout, subtaskResults);
|
|
339
|
+
```
|