@ukiahinsure/a2ui-react-adapter 0.1.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 ADDED
@@ -0,0 +1,44 @@
1
+ # @ukiahinsure/a2ui-react-adapter
2
+
3
+ Reusable MedAid A2UI client adapter. Owns **all** protocol-specific client
4
+ code for Stage 17: the `A2UIClientAdapter` over the official A2UI v0.9.1
5
+ packages (`@a2ui/react`, `@a2ui/web_core`) with the trusted Basic Catalog,
6
+ the `A2UISurfaceHost` React component (no-surface and render-error
7
+ fallbacks), and the MedAid LiveKit envelope constants/types
8
+ (`medaid-a2ui-livekit-envelope.v1`).
9
+
10
+ `A2UISessionController` attaches that adapter to a caller-owned structural
11
+ room, filters streams through a required trusted-participant predicate,
12
+ announces exact member capabilities, validates/serializes server streams,
13
+ restores authoritative snapshots across revision gaps and reconnects, and
14
+ maps trusted Basic Catalog interactions to correlated client envelopes.
15
+ It never disconnects a shared room. `joinA2UIRoom` is the explicit ownership
16
+ alternative: it requests backend-issued `{ serverUrl, token }` material once,
17
+ connects a room, and returns an idempotent close handle without retaining or
18
+ logging the token.
19
+
20
+ Host applications (the MedAid member website; later the APAI human-agent
21
+ frontend) consume only the public API from the package entry point and the
22
+ `./styles.css` export. They never import `@a2ui/*` directly, never parse
23
+ authored YAML, and never construct envelopes themselves — so a future
24
+ official React v1 renderer can replace the internals without host changes.
25
+
26
+ Canonical envelope contract: `livekitFlowfactory/contracts/` and MediAid
27
+ `docs/aide/contracts/a2ui-livekit-envelope-contract.md`.
28
+
29
+ ## Commands
30
+
31
+ - `npm run build` — bundle to `dist/` (tsup, ESM + d.ts) and copy the
32
+ renderer structural CSS to `dist/a2ui-structural.css`.
33
+ - `npm test` — vitest (jsdom) fixture-driven adapter and host tests.
34
+ - `npm run lint` / `npm run typecheck`
35
+
36
+ ## Status
37
+
38
+ Queue-Item 024-005 plumbing complete: the Queue-Item 024-004 session behavior
39
+ plus exact `flow.submit`/`flow.skip`/`flow.revise` and safe `clientError`
40
+ publication, value-free pending/result snapshots, duplicate suppression,
41
+ delivery/ack separation, and same-request-id retry after timeout or
42
+ authoritative reconnect restoration. Server validation and deterministic
43
+ voice/UI engine serialization remain Queue-Item 024-006; provider-backed
44
+ acceptance remains 024-008.
@@ -0,0 +1,205 @@
1
+ /* src/v0_9/catalog/basic/components/Text.module.css */
2
+ .a2uiText {
3
+ display: inline-block;
4
+ color: var(--_a2ui-text-color, var(--a2ui-text-color-text, var(--a2ui-color-on-background)));
5
+ margin: var(--_a2ui-text-margin, 0);
6
+ box-sizing: border-box;
7
+ }
8
+ .a2uiCaption {
9
+ display: inline-block;
10
+ color: var(--a2ui-text-caption-color, light-dark(#666, #aaa));
11
+ text-align: left;
12
+ margin: var(--_a2ui-text-margin, 0);
13
+ box-sizing: border-box;
14
+ }
15
+ .a2uiText :global(p),
16
+ .a2uiText :global(h1),
17
+ .a2uiText :global(h2),
18
+ .a2uiText :global(h3),
19
+ .a2uiText :global(h4),
20
+ .a2uiText :global(h5),
21
+ .a2uiText :global(h6),
22
+ .a2uiText :global(ol),
23
+ .a2uiText :global(ul),
24
+ .a2uiText :global(li),
25
+ .a2uiText :global(blockquote),
26
+ .a2uiText :global(pre) {
27
+ margin: var(--_a2ui-text-margin, 0);
28
+ }
29
+ .a2uiText :global(h1),
30
+ .a2uiText :global(h2),
31
+ .a2uiText :global(h3),
32
+ .a2uiText :global(h4),
33
+ .a2uiText :global(h5) {
34
+ font-family: var(--a2ui-font-family-title, inherit);
35
+ line-height: var(--a2ui-line-height-headings, 1.2);
36
+ }
37
+ .a2uiText :global(h1) {
38
+ font-size: var(--a2ui-font-size-2xl);
39
+ }
40
+ .a2uiText :global(h2) {
41
+ font-size: var(--a2ui-font-size-xl);
42
+ }
43
+ .a2uiText :global(h3) {
44
+ font-size: var(--a2ui-font-size-l);
45
+ }
46
+ .a2uiText :global(p),
47
+ .a2uiText :global(h4) {
48
+ font-size: var(--a2ui-font-size-m);
49
+ }
50
+ .a2uiText :global(h5) {
51
+ font-size: var(--a2ui-font-size-s);
52
+ }
53
+ .a2uiText :global(p),
54
+ .a2uiText :global(ol),
55
+ .a2uiText :global(ul),
56
+ .a2uiText :global(li),
57
+ .a2uiText :global(blockquote) {
58
+ line-height: var(--a2ui-line-height-body, 1.5);
59
+ }
60
+ .a2uiText :global(a) {
61
+ color: var(--a2ui-text-a-color, inherit);
62
+ font-weight: var(--a2ui-text-a-font-weight, inherit);
63
+ }
64
+
65
+ /* src/v0_9/catalog/basic/components/Button.module.css */
66
+ .button {
67
+ --_a2ui-text-margin: 0;
68
+ --_a2ui-text-color: var(--a2ui-color-on-secondary, #333);
69
+ padding: var(--a2ui-button-padding, var(--a2ui-spacing-m, 0.5rem) var(--a2ui-spacing-l, 1rem));
70
+ margin: var(--a2ui-button-margin, var(--a2ui-spacing-m));
71
+ background: var(--a2ui-button-background, var(--a2ui-color-surface, #fff));
72
+ box-shadow: var(--a2ui-button-box-shadow, none);
73
+ font-weight: var(--a2ui-button-font-weight, normal);
74
+ color: var(--a2ui-color-on-secondary, #333);
75
+ border: var(--a2ui-button-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc));
76
+ border-radius: var(--a2ui-button-border-radius, var(--a2ui-border-radius, 8px));
77
+ cursor: pointer;
78
+ display: inline-flex;
79
+ align-items: center;
80
+ justify-content: center;
81
+ transition: background-color 0.2s;
82
+ }
83
+ .button:hover {
84
+ background-color: var(--a2ui-color-secondary-hover, #ddd);
85
+ }
86
+ .primary {
87
+ --_a2ui-text-color: var(--a2ui-color-on-primary, #fff);
88
+ background-color: var(--a2ui-color-primary, #17e);
89
+ border: none;
90
+ color: var(--_a2ui-text-color);
91
+ }
92
+ .primary:hover {
93
+ background-color: var(--a2ui-color-primary-hover, #fbd);
94
+ }
95
+ .borderless {
96
+ background: none;
97
+ padding: 0;
98
+ color: var(--a2ui-color-primary, #17e);
99
+ border: none;
100
+ }
101
+ .button:disabled {
102
+ cursor: not-allowed;
103
+ opacity: 0.6;
104
+ }
105
+
106
+ /* src/v0_9/catalog/basic/components/TextField.module.css */
107
+ .host {
108
+ display: flex;
109
+ flex-direction: column;
110
+ gap: var(--a2ui-spacing-xs, 0.25rem);
111
+ width: 100%;
112
+ }
113
+ .label {
114
+ font-size: var(--a2ui-textfield-label-font-size, var(--a2ui-label-font-size, var(--a2ui-font-size-s)));
115
+ font-weight: var(--a2ui-textfield-label-font-weight, var(--a2ui-label-font-weight, bold));
116
+ }
117
+ .input {
118
+ background-color: var(--a2ui-color-input, #fff);
119
+ color: var(--a2ui-color-on-input, #333);
120
+ border: var(--a2ui-textfield-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc));
121
+ border-radius: var(--a2ui-textfield-border-radius, var(--a2ui-spacing-m, 8px));
122
+ padding: var(--a2ui-textfield-padding, var(--a2ui-spacing-m, 0.5rem));
123
+ font-family: inherit;
124
+ box-sizing: border-box;
125
+ width: 100%;
126
+ transition: border-color 0.2s;
127
+ }
128
+ .input:hover {
129
+ border-color: var(--a2ui-textfield-color-border-hover, var(--a2ui-color-border-hover, #999));
130
+ }
131
+ .input:focus {
132
+ outline: none;
133
+ border-color: var(--a2ui-textfield-color-border-focus, var(--a2ui-color-primary, #17e));
134
+ }
135
+ .invalid {
136
+ border-color: var(--a2ui-textfield-color-error, red) !important;
137
+ }
138
+ .error {
139
+ color: var(--a2ui-textfield-color-error, red);
140
+ font-size: var(--a2ui-font-size-xs, 0.75rem);
141
+ }
142
+
143
+ /* src/v0_9/catalog/basic/components/ChoicePicker.module.css */
144
+ .host {
145
+ display: flex;
146
+ flex-direction: column;
147
+ gap: var(--a2ui-spacing-s, 0.5rem);
148
+ width: 100%;
149
+ }
150
+ .label {
151
+ font-size: var(--a2ui-choicepicker-label-font-size, var(--a2ui-label-font-size, var(--a2ui-font-size-s)));
152
+ font-weight: var(--a2ui-choicepicker-label-font-weight, var(--a2ui-label-font-weight, bold));
153
+ color: var(--a2ui-choicepicker-label-color, inherit);
154
+ }
155
+ .filterInput {
156
+ background-color: var(--a2ui-color-input, #fff);
157
+ color: var(--a2ui-color-on-input, #333);
158
+ border: var(--a2ui-textfield-border, var(--a2ui-border-width, 1px) solid var(--a2ui-color-border, #ccc));
159
+ border-radius: var(--a2ui-textfield-border-radius, var(--a2ui-spacing-m, 8px));
160
+ padding: var(--a2ui-choicepicker-filter-padding, var(--a2ui-spacing-xs, 4px) var(--a2ui-spacing-s, 8px));
161
+ font-family: inherit;
162
+ transition: border-color 0.2s;
163
+ }
164
+ .filterInput:hover {
165
+ border-color: var(--a2ui-textfield-color-border-hover, var(--a2ui-color-border-hover, #999));
166
+ }
167
+ .filterInput:focus {
168
+ outline: none;
169
+ border-color: var(--a2ui-textfield-color-border-focus, var(--a2ui-color-primary, #17e));
170
+ }
171
+ .options {
172
+ display: flex;
173
+ flex-direction: column;
174
+ gap: var(--a2ui-choicepicker-gap, var(--a2ui-spacing-xs, 0.25rem));
175
+ }
176
+ .chips {
177
+ flex-direction: row;
178
+ flex-wrap: wrap;
179
+ }
180
+ .chip {
181
+ padding: var(--a2ui-choicepicker-chip-padding, var(--a2ui-spacing-s, 4px) var(--a2ui-spacing-m, 8px));
182
+ border-radius: var(--a2ui-choicepicker-chip-border-radius, 999px);
183
+ border: 1px solid var(--a2ui-color-border, #ccc);
184
+ background-color: var(--a2ui-color-surface, #fff);
185
+ color: var(--a2ui-color-on-surface, inherit);
186
+ cursor: pointer;
187
+ font-size: var(--a2ui-font-size-xs, 0.75rem);
188
+ font-family: inherit;
189
+ transition: background-color 0.2s, border-color 0.2s;
190
+ }
191
+ .chip.selected {
192
+ background-color: var(--a2ui-color-primary, #007bff);
193
+ color: var(--a2ui-color-on-primary, #fff);
194
+ border-color: var(--a2ui-color-primary, #007bff);
195
+ }
196
+ .optionLabel {
197
+ display: flex;
198
+ align-items: center;
199
+ gap: var(--a2ui-choicepicker-gap, var(--a2ui-spacing-xs, 0.25rem));
200
+ cursor: pointer;
201
+ }
202
+ .optionText {
203
+ font-size: var(--a2ui-font-size-m, 1rem);
204
+ }
205
+ /*# sourceMappingURL=index.css.map */
@@ -0,0 +1,33 @@
1
+ // src/envelope.ts
2
+ var ENVELOPE_VERSION = "v1";
3
+ var A2UI_PROTOCOL_VERSION = "0.9.1";
4
+ var A2UI_SERVER_TOPIC = "medaid.a2ui.server.v1";
5
+ var A2UI_CLIENT_TOPIC = "medaid.a2ui.client.v1";
6
+ var A2UI_CAPABILITIES_TOPIC = "medaid.a2ui.capabilities.v1";
7
+ var MAX_ENVELOPE_BYTES = 262144;
8
+ var CLIENT_ACTIONS = [
9
+ "flow.submit",
10
+ "flow.skip",
11
+ "flow.revise",
12
+ "flow.list.add",
13
+ "flow.list.update",
14
+ "flow.list.delete"
15
+ ];
16
+ var CLIENT_ERROR_CODES = [
17
+ "render_failed",
18
+ "catalog_unsupported",
19
+ "message_malformed",
20
+ "envelope_unsupported"
21
+ ];
22
+
23
+ export {
24
+ ENVELOPE_VERSION,
25
+ A2UI_PROTOCOL_VERSION,
26
+ A2UI_SERVER_TOPIC,
27
+ A2UI_CLIENT_TOPIC,
28
+ A2UI_CAPABILITIES_TOPIC,
29
+ MAX_ENVELOPE_BYTES,
30
+ CLIENT_ACTIONS,
31
+ CLIENT_ERROR_CODES
32
+ };
33
+ //# sourceMappingURL=chunk-AOBRTDAU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/envelope.ts"],"sourcesContent":["/**\n * MedAid A2UI LiveKit envelope contract v1 — client-side constants and types.\n *\n * These declarations mirror the canonical machine-readable contract owned by\n * `livekitFlowfactory/contracts/a2ui-livekit-*-envelope.v1.schema.json`\n * (human-readable source: MediAid `docs/aide/contracts/a2ui-livekit-envelope-contract.md`).\n * They are a declared copy: Stage 17 acceptance verifies drift against the\n * canonical fixtures. The envelope contract version (\"v1\", the `.v1` topic\n * suffix) is not the A2UI protocol version; `a2uiVersion` is negotiated\n * separately.\n */\n\nexport const ENVELOPE_VERSION = \"v1\" as const;\nexport const A2UI_PROTOCOL_VERSION = \"0.9.1\" as const;\n\nexport const A2UI_SERVER_TOPIC = \"medaid.a2ui.server.v1\";\nexport const A2UI_CLIENT_TOPIC = \"medaid.a2ui.client.v1\";\nexport const A2UI_CAPABILITIES_TOPIC = \"medaid.a2ui.capabilities.v1\";\n\nexport const MAX_ENVELOPE_BYTES = 262_144;\n\nexport const CLIENT_ACTIONS = [\n \"flow.submit\",\n \"flow.skip\",\n \"flow.revise\",\n \"flow.list.add\",\n \"flow.list.update\",\n \"flow.list.delete\",\n] as const;\nexport type ClientAction = (typeof CLIENT_ACTIONS)[number];\n\nexport const CLIENT_ERROR_CODES = [\n \"render_failed\",\n \"catalog_unsupported\",\n \"message_malformed\",\n \"envelope_unsupported\",\n] as const;\nexport type ClientErrorCode = (typeof CLIENT_ERROR_CODES)[number];\n\nexport type ParticipantRole = \"member\" | \"agent\";\n\nexport interface SectionNavigation {\n orderedSectionIds: string[];\n activeSectionId: string | null;\n completedSectionIds: string[];\n futureSectionIds: string[];\n}\n\nexport interface A2UIFieldInteractionField {\n field: string;\n componentId: string;\n}\n\nexport interface A2UIFieldInteractionGroup {\n groupId: string;\n ownerStateId: string;\n sectionId?: string;\n mode: \"current\" | \"revision\";\n fields: A2UIFieldInteractionField[];\n}\n\ninterface ServerEnvelopeBase {\n envelopeVersion: typeof ENVELOPE_VERSION;\n a2uiVersion: string;\n seq: number;\n}\n\nexport interface SurfaceUpdateEnvelope extends ServerEnvelopeBase {\n kind: \"surfaceUpdate\";\n surfaceId: string;\n revision: number;\n activeStateId?: string | null;\n sectionNavigation?: SectionNavigation;\n fieldInteractions?: A2UIFieldInteractionGroup[];\n messages: unknown[];\n}\n\nexport interface SnapshotEnvelope extends ServerEnvelopeBase {\n kind: \"snapshot\";\n surfaceId: string;\n revision: number;\n activeStateId?: string | null;\n sectionNavigation?: SectionNavigation;\n fieldInteractions?: A2UIFieldInteractionGroup[];\n inResponseTo?: string;\n messages: unknown[];\n}\n\nexport interface AckFieldError {\n field: string;\n reasonCode: string;\n}\n\nexport interface AckEnvelope extends ServerEnvelopeBase {\n kind: \"ack\";\n inResponseTo: string;\n status: \"accepted\" | \"rejected\";\n reasonCode?: string;\n fieldErrors?: AckFieldError[];\n noSurface?: boolean;\n}\n\nexport type A2UIServerEnvelope =\n | SurfaceUpdateEnvelope\n | SnapshotEnvelope\n | AckEnvelope;\n\nexport interface A2UIClientActionEnvelope {\n envelopeVersion: typeof ENVELOPE_VERSION;\n a2uiVersion: string;\n kind: \"action\";\n requestId: string;\n role: ParticipantRole;\n surfaceId: string;\n stateId: string;\n revision: number;\n action: ClientAction;\n payload?: {\n fields?: Record<string, string | number | boolean | null>;\n targetStateId?: string;\n listPath?: string;\n itemId?: string;\n };\n}\n\nexport interface A2UIClientErrorEnvelope {\n envelopeVersion: typeof ENVELOPE_VERSION;\n a2uiVersion: string;\n kind: \"clientError\";\n requestId: string;\n role: ParticipantRole;\n errorCode: ClientErrorCode;\n surfaceId?: string;\n detail?: string;\n}\n\nexport interface A2UICapabilityEnvelope {\n envelopeVersion: typeof ENVELOPE_VERSION;\n kind: \"capabilities\";\n requestId: string;\n role: ParticipantRole;\n supportedA2uiVersions: string[];\n supportedCatalogIds: string[];\n renderer: { name: string; version: string };\n requestSnapshot?: boolean;\n}\n"],"mappings":";AAYO,IAAM,mBAAmB;AACzB,IAAM,wBAAwB;AAE9B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,0BAA0B;AAEhC,IAAM,qBAAqB;AAE3B,IAAM,iBAAiB;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGO,IAAM,qBAAqB;AAAA,EAChC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;","names":[]}
@@ -0,0 +1,51 @@
1
+ import { A as A2UIClientAdapter, a as A2UISurfaceMetadata, b as A2UISessionController, c as A2UIRoomLike } from './session-B_2-bK2s.js';
2
+ export { d as A2UIActionDeliveryStatus, e as A2UIActionResult, f as A2UIActionState, g as A2UICapabilityEnvelope, h as A2UIClientActionEnvelope, i as A2UIClientAdapterOptions, j as A2UIClientErrorEnvelope, k as A2UIClientErrorEvent, l as A2UIEditingSnapshot, m as A2UIFieldControl, n as A2UIFieldDescriptor, o as A2UIFieldInteractionField, p as A2UIFieldInteractionGroup, q as A2UIFieldValue, r as A2UIPendingAction, s as A2UIRendererActionEvent, t as A2UIServerEnvelope, u as A2UISessionControllerOptions, v as A2UISessionSnapshot, w as A2UISessionStatus, x as A2UIStreamParticipant, y as A2UISurfaceStructure, z as A2UITabDescriptor, B as A2UITrustedServerParticipant, C as A2UI_CAPABILITIES_TOPIC, D as A2UI_CLIENT_TOPIC, E as A2UI_PROTOCOL_VERSION, F as A2UI_SERVER_TOPIC, G as AckEnvelope, H as AckFieldError, I as AdapterSurface, J as CLIENT_ACTIONS, K as CLIENT_ERROR_CODES, L as ClientAction, M as ClientErrorCode, N as ENVELOPE_VERSION, O as MAX_ENVELOPE_BYTES, P as ParticipantRole, S as SectionNavigation, Q as SnapshotEnvelope, R as SurfaceUpdateEnvelope } from './session-B_2-bK2s.js';
3
+ import { ReactNode } from 'react';
4
+
5
+ /** Accessible host around the official A2UI v0.9.1 React surface. */
6
+
7
+ interface A2UISurfaceHostProps {
8
+ adapter: A2UIClientAdapter;
9
+ /** Render a specific surface; defaults to the adapter's current surface. */
10
+ surfaceId?: string;
11
+ /** Authoritative protocol-neutral state/revision/section metadata. */
12
+ surfaceMetadata?: A2UISurfaceMetadata | null;
13
+ /** Optional session owner used for server-authorized blur commits. */
14
+ sessionController?: A2UISessionController;
15
+ /** Stable DOM target for host validation-summary field links. */
16
+ hostId?: string;
17
+ ariaLabel?: string;
18
+ /** Makes stale/pending controls inert while voice remains available. */
19
+ disabled?: boolean;
20
+ busy?: boolean;
21
+ /** Server-rejected fields. Values and server details are never accepted. */
22
+ invalidFields?: readonly string[];
23
+ validationSummaryId?: string;
24
+ /** Shown when no surface is live (voice-only operation continues). */
25
+ noSurfaceFallback?: ReactNode;
26
+ /** Shown when the surface fails to render. */
27
+ errorFallback?: ReactNode;
28
+ }
29
+ declare function A2UISurfaceHost({ adapter, surfaceId, surfaceMetadata, sessionController, hostId, ariaLabel, disabled, busy, invalidFields, validationSummaryId, noSurfaceFallback, errorFallback, }: A2UISurfaceHostProps): ReactNode;
30
+
31
+ /** Explicit room-ownership helper backed by backend-issued runtime material. */
32
+
33
+ interface A2UIRuntimeMaterial {
34
+ readonly serverUrl: string;
35
+ readonly token: string;
36
+ }
37
+ type A2UITokenProvider = () => Promise<A2UIRuntimeMaterial>;
38
+ interface A2UIJoinedRoom {
39
+ readonly room: A2UIRoomLike;
40
+ close(): Promise<void>;
41
+ }
42
+ /**
43
+ * Create and connect a room owned by this helper.
44
+ *
45
+ * Runtime material is requested exactly once and remains local to the join
46
+ * operation. The returned handle retains the room and idempotent cleanup,
47
+ * never the token or material object.
48
+ */
49
+ declare function joinA2UIRoom(tokenProvider: A2UITokenProvider): Promise<A2UIJoinedRoom>;
50
+
51
+ export { A2UIClientAdapter, type A2UIJoinedRoom, A2UIRoomLike, type A2UIRuntimeMaterial, A2UISessionController, A2UISurfaceHost, type A2UISurfaceHostProps, A2UISurfaceMetadata, type A2UITokenProvider, joinA2UIRoom };