@kitn.ai/ui 0.28.0 → 0.29.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 (376) hide show
  1. package/README.md +1 -1
  2. package/dist/agent-tooling/construct/public.d.ts +15 -0
  3. package/dist/agent-tooling/construct/schema.d.ts +111 -0
  4. package/dist/construct-cli.es.js +3 -3
  5. package/dist/construct.d.ts +5 -0
  6. package/dist/construct.js +327 -0
  7. package/dist/core-C8fzo39E.js +1 -12
  8. package/dist/engine-javascript-C1x7zo1_.js +1 -141
  9. package/dist/mcp.es.js +1 -1
  10. package/package.json +16 -16
  11. package/bin/route.test.js +0 -58
  12. package/src/agent-tooling/README.md +0 -192
  13. package/src/agent-tooling/archetypes.ts +0 -205
  14. package/src/agent-tooling/catalog/README.md +0 -676
  15. package/src/agent-tooling/catalog/catalog-types.ts +0 -231
  16. package/src/agent-tooling/catalog/fabrications.ts +0 -96
  17. package/src/agent-tooling/catalog/invariants.ts +0 -284
  18. package/src/agent-tooling/catalog/labs-titles.ts +0 -114
  19. package/src/agent-tooling/catalog/scenarios.ts +0 -87
  20. package/src/agent-tooling/catalog/surfaces.ts +0 -347
  21. package/src/agent-tooling/construct/cli-entry.ts +0 -8
  22. package/src/agent-tooling/construct/cli.ts +0 -147
  23. package/src/agent-tooling/construct/codegen.ts +0 -1635
  24. package/src/agent-tooling/construct/construct.v1.schema.json +0 -319
  25. package/src/agent-tooling/construct/dev.ts +0 -148
  26. package/src/agent-tooling/construct/fixtures/demo-widget.construct.json +0 -7
  27. package/src/agent-tooling/construct/fixtures/ops-console.construct.json +0 -44
  28. package/src/agent-tooling/construct/fixtures/owner-widget.construct.json +0 -32
  29. package/src/agent-tooling/construct/schema.ts +0 -452
  30. package/src/agent-tooling/integrations/anthropic.ts +0 -442
  31. package/src/agent-tooling/integrations/cloudflare.ts +0 -218
  32. package/src/agent-tooling/integrations/langgraph.ts +0 -133
  33. package/src/agent-tooling/integrations/mastra.ts +0 -182
  34. package/src/agent-tooling/integrations/mock.ts +0 -148
  35. package/src/agent-tooling/integrations/ollama.ts +0 -97
  36. package/src/agent-tooling/integrations/openai.ts +0 -95
  37. package/src/agent-tooling/integrations/openrouter.ts +0 -82
  38. package/src/agent-tooling/integrations/pi.ts +0 -176
  39. package/src/agent-tooling/integrations/pydantic-ai.ts +0 -108
  40. package/src/agent-tooling/integrations/vercel-ai-sdk.ts +0 -398
  41. package/src/agent-tooling/mcp/css-raw.d.ts +0 -12
  42. package/src/agent-tooling/mcp/manifest.ts +0 -422
  43. package/src/agent-tooling/mcp/server.ts +0 -116
  44. package/src/agent-tooling/mcp/stdio.ts +0 -18
  45. package/src/agent-tooling/mcp/tools/construct.ts +0 -130
  46. package/src/agent-tooling/mcp/tools/debug.ts +0 -621
  47. package/src/agent-tooling/mcp/tools/reference.ts +0 -980
  48. package/src/agent-tooling/mcp/tools/scaffold.ts +0 -6708
  49. package/src/agent-tooling/mcp/tools/theme.ts +0 -456
  50. package/src/agent-tooling/mcp/tools/types.ts +0 -14
  51. package/src/agent-tooling/mcp/validate-args.ts +0 -141
  52. package/src/agent-tooling/recipes/composed-thread.ts +0 -714
  53. package/src/agent-tooling/recipes/index.ts +0 -22
  54. package/src/agent-tooling/recipes/types.ts +0 -32
  55. package/src/agent-tooling/registry.ts +0 -132
  56. package/src/agent-tooling/route-emit.ts +0 -305
  57. package/src/agent-tooling/types.ts +0 -390
  58. package/src/components/artifact-card.tsx +0 -120
  59. package/src/components/artifact.tsx +0 -914
  60. package/src/components/attachment-types.ts +0 -66
  61. package/src/components/attachments.tsx +0 -536
  62. package/src/components/audio-visualizer/audio-visualizer.voice-fixture.ts +0 -257
  63. package/src/components/audio-visualizer/aurora.glsl.ts +0 -330
  64. package/src/components/audio-visualizer/fit-scale.ts +0 -112
  65. package/src/components/audio-visualizer/index.tsx +0 -528
  66. package/src/components/audio-visualizer/labs/lab-choreography.ts +0 -187
  67. package/src/components/audio-visualizer/labs/lab-shaders.ts +0 -363
  68. package/src/components/audio-visualizer/labs/lab-visualizer.tsx +0 -102
  69. package/src/components/audio-visualizer/shader-canvas.tsx +0 -876
  70. package/src/components/audio-visualizer/sizes.ts +0 -54
  71. package/src/components/audio-visualizer/variant-aurora.tsx +0 -305
  72. package/src/components/audio-visualizer/variant-bar.tsx +0 -175
  73. package/src/components/audio-visualizer/variant-custom.tsx +0 -344
  74. package/src/components/audio-visualizer/variant-grid.tsx +0 -175
  75. package/src/components/audio-visualizer/variant-radial.tsx +0 -163
  76. package/src/components/audio-visualizer/variant-wave.tsx +0 -130
  77. package/src/components/audio-visualizer/wave.glsl.ts +0 -130
  78. package/src/components/card-fallback.tsx +0 -60
  79. package/src/components/card-renderer.tsx +0 -220
  80. package/src/components/card.tsx +0 -212
  81. package/src/components/chain-of-thought.tsx +0 -348
  82. package/src/components/chat-container.tsx +0 -89
  83. package/src/components/chat-scope-picker.tsx +0 -57
  84. package/src/components/chat-thread.tsx +0 -1014
  85. package/src/components/checkpoint.tsx +0 -88
  86. package/src/components/choice-card.tsx +0 -743
  87. package/src/components/coachmark.tsx +0 -249
  88. package/src/components/code-block.tsx +0 -181
  89. package/src/components/composer-dom.ts +0 -159
  90. package/src/components/composer-highlight.ts +0 -242
  91. package/src/components/composer-history.ts +0 -82
  92. package/src/components/composer.tsx +0 -1075
  93. package/src/components/confirm-card.tsx +0 -402
  94. package/src/components/context.tsx +0 -377
  95. package/src/components/conversation-item.tsx +0 -241
  96. package/src/components/conversation-list.tsx +0 -429
  97. package/src/components/conversation-panel.tsx +0 -118
  98. package/src/components/dismissed-stub.tsx +0 -80
  99. package/src/components/embed.tsx +0 -200
  100. package/src/components/empty.tsx +0 -177
  101. package/src/components/feedback-bar.tsx +0 -166
  102. package/src/components/file-tree.tsx +0 -530
  103. package/src/components/file-upload.tsx +0 -167
  104. package/src/components/form-widgets.tsx +0 -525
  105. package/src/components/form.tsx +0 -1296
  106. package/src/components/home-panel.tsx +0 -159
  107. package/src/components/image.tsx +0 -65
  108. package/src/components/link-preview.tsx +0 -195
  109. package/src/components/loader.tsx +0 -367
  110. package/src/components/markdown.tsx +0 -162
  111. package/src/components/message-skills.tsx +0 -40
  112. package/src/components/message.tsx +0 -765
  113. package/src/components/model-switcher.tsx +0 -132
  114. package/src/components/prompt-input.tsx +0 -188
  115. package/src/components/prompt-suggestion.tsx +0 -150
  116. package/src/components/reasoning.tsx +0 -293
  117. package/src/components/response-compare-types.ts +0 -152
  118. package/src/components/response-compare.tsx +0 -455
  119. package/src/components/response-stream.tsx +0 -109
  120. package/src/components/screen.tsx +0 -181
  121. package/src/components/scroll-button.tsx +0 -93
  122. package/src/components/source.tsx +0 -192
  123. package/src/components/tasks-card.tsx +0 -801
  124. package/src/components/text-shimmer.tsx +0 -37
  125. package/src/components/thinking-bar.tsx +0 -50
  126. package/src/components/thread.tsx +0 -228
  127. package/src/components/toast.tsx +0 -503
  128. package/src/components/tool-classify.ts +0 -26
  129. package/src/components/tool-types.ts +0 -49
  130. package/src/components/tool.tsx +0 -190
  131. package/src/components/use-card-resolution.ts +0 -44
  132. package/src/components/voice-input.tsx +0 -222
  133. package/src/components/voice-output.tsx +0 -242
  134. package/src/components/widget-tab-bar.tsx +0 -73
  135. package/src/components/workspace-shell.tsx +0 -285
  136. package/src/diagnostics/hook.ts +0 -352
  137. package/src/diagnostics/index.ts +0 -82
  138. package/src/diagnostics/report-request.ts +0 -296
  139. package/src/elements/agent-card.tsx +0 -67
  140. package/src/elements/artifact.tsx +0 -194
  141. package/src/elements/attachments.tsx +0 -140
  142. package/src/elements/audio-visualizer.tsx +0 -227
  143. package/src/elements/autoloader.ts +0 -89
  144. package/src/elements/avatar.tsx +0 -33
  145. package/src/elements/badge.tsx +0 -32
  146. package/src/elements/button.tsx +0 -149
  147. package/src/elements/card.tsx +0 -133
  148. package/src/elements/cards.tsx +0 -266
  149. package/src/elements/chain-of-thought.tsx +0 -141
  150. package/src/elements/chat-scope-picker.tsx +0 -65
  151. package/src/elements/chat-types.ts +0 -102
  152. package/src/elements/chat-workspace.tsx +0 -188
  153. package/src/elements/chat.tsx +0 -304
  154. package/src/elements/checkbox-group.tsx +0 -164
  155. package/src/elements/checkbox.tsx +0 -134
  156. package/src/elements/checkpoint.tsx +0 -47
  157. package/src/elements/choice.tsx +0 -100
  158. package/src/elements/coachmark.tsx +0 -90
  159. package/src/elements/code-block.tsx +0 -60
  160. package/src/elements/command.tsx +0 -197
  161. package/src/elements/compare.tsx +0 -111
  162. package/src/elements/compiled.css +0 -2
  163. package/src/elements/composer.tsx +0 -162
  164. package/src/elements/confirm-card.tsx +0 -76
  165. package/src/elements/context-meter.tsx +0 -107
  166. package/src/elements/conversation-item.tsx +0 -120
  167. package/src/elements/conversation-list.tsx +0 -203
  168. package/src/elements/css.ts +0 -5
  169. package/src/elements/default-input.tsx +0 -392
  170. package/src/elements/define-entry.ts +0 -12
  171. package/src/elements/define.tsx +0 -563
  172. package/src/elements/diagnostic-events.ts +0 -114
  173. package/src/elements/dialog.tsx +0 -145
  174. package/src/elements/disclosure.ts +0 -95
  175. package/src/elements/dock.tsx +0 -215
  176. package/src/elements/dropdown.tsx +0 -123
  177. package/src/elements/editable-label.tsx +0 -140
  178. package/src/elements/element-data-types.ts +0 -152
  179. package/src/elements/element-diagnostics.ts +0 -392
  180. package/src/elements/element-manifest.json +0 -354
  181. package/src/elements/element-nonscalar.json +0 -187
  182. package/src/elements/element-types.d.ts +0 -4353
  183. package/src/elements/embed.tsx +0 -35
  184. package/src/elements/empty.tsx +0 -29
  185. package/src/elements/feedback-bar.tsx +0 -66
  186. package/src/elements/file-tree.tsx +0 -62
  187. package/src/elements/file-upload.tsx +0 -44
  188. package/src/elements/form.tsx +0 -94
  189. package/src/elements/hover-card.tsx +0 -80
  190. package/src/elements/icon.tsx +0 -41
  191. package/src/elements/image.tsx +0 -32
  192. package/src/elements/input.tsx +0 -492
  193. package/src/elements/kbd.tsx +0 -45
  194. package/src/elements/link-preview.tsx +0 -34
  195. package/src/elements/loader.tsx +0 -25
  196. package/src/elements/markdown.tsx +0 -38
  197. package/src/elements/menu.tsx +0 -241
  198. package/src/elements/message-skills.tsx +0 -83
  199. package/src/elements/message.tsx +0 -361
  200. package/src/elements/model-switcher.tsx +0 -121
  201. package/src/elements/nav.tsx +0 -87
  202. package/src/elements/notice.tsx +0 -53
  203. package/src/elements/pane-grid.tsx +0 -111
  204. package/src/elements/pane-group.tsx +0 -119
  205. package/src/elements/pane.tsx +0 -129
  206. package/src/elements/popover.tsx +0 -81
  207. package/src/elements/progress-bar.tsx +0 -39
  208. package/src/elements/prompt-dock.tsx +0 -82
  209. package/src/elements/prompt-input.tsx +0 -240
  210. package/src/elements/prompt-suggestions.tsx +0 -113
  211. package/src/elements/radio-group.tsx +0 -129
  212. package/src/elements/reasoning.tsx +0 -71
  213. package/src/elements/register-impl.ts +0 -148
  214. package/src/elements/register.ts +0 -76
  215. package/src/elements/remote.tsx +0 -225
  216. package/src/elements/resizable.globals.d.ts +0 -36
  217. package/src/elements/resizable.tsx +0 -704
  218. package/src/elements/response-stream.tsx +0 -40
  219. package/src/elements/screen.tsx +0 -90
  220. package/src/elements/scroll-area.tsx +0 -33
  221. package/src/elements/scroll-button.tsx +0 -179
  222. package/src/elements/search.tsx +0 -185
  223. package/src/elements/segmented.tsx +0 -129
  224. package/src/elements/select.tsx +0 -165
  225. package/src/elements/separator.tsx +0 -36
  226. package/src/elements/setting-item.tsx +0 -50
  227. package/src/elements/settings-group.tsx +0 -37
  228. package/src/elements/skeleton.tsx +0 -45
  229. package/src/elements/slider.tsx +0 -171
  230. package/src/elements/slot-text.ts +0 -73
  231. package/src/elements/slots.ts +0 -787
  232. package/src/elements/source.tsx +0 -126
  233. package/src/elements/status.tsx +0 -42
  234. package/src/elements/styles.css +0 -328
  235. package/src/elements/switch.tsx +0 -118
  236. package/src/elements/tabs.tsx +0 -95
  237. package/src/elements/tasks.tsx +0 -103
  238. package/src/elements/text-shimmer.tsx +0 -28
  239. package/src/elements/thinking-bar.tsx +0 -43
  240. package/src/elements/thread.tsx +0 -139
  241. package/src/elements/toast.tsx +0 -106
  242. package/src/elements/tool.tsx +0 -78
  243. package/src/elements/tooltip.tsx +0 -85
  244. package/src/elements/validate-messages.ts +0 -76
  245. package/src/elements/voice-input.tsx +0 -100
  246. package/src/elements/voice-output.tsx +0 -82
  247. package/src/index.ts +0 -430
  248. package/src/primitives/audio-bands.ts +0 -174
  249. package/src/primitives/card-component-types.ts +0 -63
  250. package/src/primitives/card-contract.ts +0 -80
  251. package/src/primitives/card-data-types.ts +0 -361
  252. package/src/primitives/card-host.tsx +0 -37
  253. package/src/primitives/card-recovery.ts +0 -160
  254. package/src/primitives/card-registry.tsx +0 -78
  255. package/src/primitives/card-resolution.ts +0 -36
  256. package/src/primitives/card-routing.ts +0 -85
  257. package/src/primitives/card-schemas/artifact.schema.json +0 -92
  258. package/src/primitives/card-schemas/card-envelope.schema.json +0 -14
  259. package/src/primitives/card-schemas/card-event.schema.json +0 -12
  260. package/src/primitives/card-schemas/choice.schema.json +0 -75
  261. package/src/primitives/card-schemas/confirm.schema.json +0 -65
  262. package/src/primitives/card-schemas/embed.schema.json +0 -63
  263. package/src/primitives/card-schemas/form.result.schema.json +0 -7
  264. package/src/primitives/card-schemas/form.schema.json +0 -53
  265. package/src/primitives/card-schemas/link.schema.json +0 -54
  266. package/src/primitives/card-schemas/tasks.result.schema.json +0 -16
  267. package/src/primitives/card-schemas/tasks.schema.json +0 -83
  268. package/src/primitives/card-tags.ts +0 -61
  269. package/src/primitives/card-validate-cards.ts +0 -255
  270. package/src/primitives/card-validate-schemas.ts +0 -58
  271. package/src/primitives/card-validate.ts +0 -131
  272. package/src/primitives/chat-config.tsx +0 -103
  273. package/src/primitives/composer-model.ts +0 -49
  274. package/src/primitives/composer-triggers.ts +0 -35
  275. package/src/primitives/controllable.ts +0 -21
  276. package/src/primitives/conversation-store.ts +0 -248
  277. package/src/primitives/create-kai-chat.ts +0 -66
  278. package/src/primitives/create-tween.ts +0 -266
  279. package/src/primitives/embed-providers.ts +0 -254
  280. package/src/primitives/field-mask.ts +0 -256
  281. package/src/primitives/field-semantics.ts +0 -115
  282. package/src/primitives/highlighter.ts +0 -157
  283. package/src/primitives/input-mask.ts +0 -853
  284. package/src/primitives/link-preview.ts +0 -87
  285. package/src/primitives/message-feedback.ts +0 -124
  286. package/src/primitives/pdf-preview.ts +0 -121
  287. package/src/primitives/toast-store.ts +0 -338
  288. package/src/primitives/url-scheme-policy.ts +0 -70
  289. package/src/primitives/use-audio-analysis.ts +0 -340
  290. package/src/primitives/use-auto-resize.ts +0 -31
  291. package/src/primitives/use-resize-observer.ts +0 -45
  292. package/src/primitives/use-sequencer.ts +0 -72
  293. package/src/primitives/use-speech-recognition.ts +0 -146
  294. package/src/primitives/use-stick-to-bottom.ts +0 -75
  295. package/src/primitives/use-text-stream.ts +0 -143
  296. package/src/primitives/use-voice-recorder.ts +0 -62
  297. package/src/primitives/visualizer-sequences.ts +0 -350
  298. package/src/remote/host-embed.ts +0 -345
  299. package/src/remote/index.ts +0 -2
  300. package/src/remote/origin.ts +0 -30
  301. package/src/remote/provider-runtime.ts +0 -262
  302. package/src/remote/provider.ts +0 -2
  303. package/src/remote/validate.ts +0 -22
  304. package/src/remote/version.ts +0 -12
  305. package/src/remote/wire.ts +0 -48
  306. package/src/schemas/from-tool-call.ts +0 -151
  307. package/src/schemas/index.ts +0 -341
  308. package/src/schemas/provider-subsets.ts +0 -538
  309. package/src/schemas/registry.ts +0 -355
  310. package/src/schemas/tool-defs.ts +0 -793
  311. package/src/solid.ts +0 -173
  312. package/src/state/index.ts +0 -44
  313. package/src/state/messages.ts +0 -53
  314. package/src/state/mock.ts +0 -297
  315. package/src/state/parts.ts +0 -295
  316. package/src/state/persistence.ts +0 -180
  317. package/src/state/stream.ts +0 -246
  318. package/src/state/suggestions.ts +0 -9
  319. package/src/state/threads.ts +0 -142
  320. package/src/test-utils/fake-clock.ts +0 -88
  321. package/src/types.ts +0 -95
  322. package/src/ui/action-icons.ts +0 -53
  323. package/src/ui/agent-card.tsx +0 -218
  324. package/src/ui/avatar.tsx +0 -23
  325. package/src/ui/badge.tsx +0 -21
  326. package/src/ui/button.tsx +0 -49
  327. package/src/ui/card.tsx +0 -245
  328. package/src/ui/checkbox-group.tsx +0 -153
  329. package/src/ui/checkbox.tsx +0 -63
  330. package/src/ui/collapsible.tsx +0 -163
  331. package/src/ui/command.tsx +0 -103
  332. package/src/ui/dialog.tsx +0 -223
  333. package/src/ui/dock.tsx +0 -630
  334. package/src/ui/dropdown.tsx +0 -636
  335. package/src/ui/editable-label.tsx +0 -112
  336. package/src/ui/hover-card.tsx +0 -345
  337. package/src/ui/icon.tsx +0 -167
  338. package/src/ui/input.tsx +0 -391
  339. package/src/ui/kbd.tsx +0 -118
  340. package/src/ui/nav.tsx +0 -282
  341. package/src/ui/notice.tsx +0 -84
  342. package/src/ui/overlay.tsx +0 -277
  343. package/src/ui/pane-grid.tsx +0 -117
  344. package/src/ui/pane-group.tsx +0 -291
  345. package/src/ui/pane.tsx +0 -193
  346. package/src/ui/popover.tsx +0 -106
  347. package/src/ui/progress-bar.tsx +0 -83
  348. package/src/ui/prompt-dock.tsx +0 -145
  349. package/src/ui/radio.tsx +0 -150
  350. package/src/ui/resizable.tsx +0 -693
  351. package/src/ui/scroll-area.tsx +0 -26
  352. package/src/ui/segmented.tsx +0 -109
  353. package/src/ui/select.tsx +0 -168
  354. package/src/ui/separator.tsx +0 -10
  355. package/src/ui/settings-group.tsx +0 -67
  356. package/src/ui/skeleton.tsx +0 -73
  357. package/src/ui/slider.tsx +0 -178
  358. package/src/ui/status.tsx +0 -48
  359. package/src/ui/switch.tsx +0 -119
  360. package/src/ui/tabs.tsx +0 -168
  361. package/src/ui/textarea.tsx +0 -21
  362. package/src/ui/tooltip.tsx +0 -118
  363. package/src/utils/cn.ts +0 -30
  364. package/src/wire/chunk.ts +0 -248
  365. package/src/wire/consume.ts +0 -599
  366. package/src/wire/diagnostics.ts +0 -727
  367. package/src/wire/encode-probe.ts +0 -214
  368. package/src/wire/encode.ts +0 -947
  369. package/src/wire/files.ts +0 -342
  370. package/src/wire/formats/anthropic.ts +0 -274
  371. package/src/wire/formats/openai.ts +0 -228
  372. package/src/wire/index.ts +0 -95
  373. package/src/wire/media-types.ts +0 -344
  374. package/src/wire/read.ts +0 -396
  375. package/src/wire/sink-helpers.ts +0 -50
  376. package/src/wire/sse.ts +0 -153
@@ -1,350 +0,0 @@
1
- /**
2
- * Pure `state -> highlight set` mappers for the audio visualizer variants.
3
- *
4
- * Ported from livekit/components-js `packages/shadcn/hooks/agents-ui/`
5
- * (Apache License 2.0). Upstream runs four separate animator hooks, each with
6
- * its own requestAnimationFrame loop. We keep the sequences (verbatim) but move
7
- * the timing into one shared driver, `use-sequencer.ts`.
8
- *
9
- * Every generator returns a NON-EMPTY array so callers can index with
10
- * `sequence[tick % sequence.length]` without a guard.
11
- */
12
-
13
- export type VisualizerState =
14
- | 'idle'
15
- | 'connecting'
16
- | 'listening'
17
- | 'thinking'
18
- | 'speaking'
19
- | 'disconnected';
20
-
21
- export interface Coordinate {
22
- x: number;
23
- y: number;
24
- }
25
-
26
- const KNOWN: readonly VisualizerState[] = [
27
- 'idle',
28
- 'connecting',
29
- 'listening',
30
- 'thinking',
31
- 'speaking',
32
- // First-class since 2026-08-10 (Rob): the dead-connection look (flat wave,
33
- // nothing lit) upstream renders for its 'disconnected' state. It was an
34
- // alias for 'idle' until idle adopted upstream's gentle wave, which left
35
- // the flat line unreachable.
36
- 'disconnected',
37
- ];
38
-
39
- /**
40
- * LiveKit's AgentState carries room-lifecycle values that mean nothing here.
41
- * Accept them so markup ported from LiveKit works unchanged.
42
- */
43
- const ALIASES: Record<string, VisualizerState> = {
44
- // Both are dead-connection looks upstream, so 'failed' rides with
45
- // 'disconnected' rather than 'idle'.
46
- failed: 'disconnected',
47
- initializing: 'connecting',
48
- 'pre-connect-buffering': 'connecting',
49
- };
50
-
51
- export function normalizeState(input: string | undefined): VisualizerState {
52
- if (!input) return 'idle';
53
- if ((KNOWN as readonly string[]).includes(input)) return input as VisualizerState;
54
- return ALIASES[input] ?? 'idle';
55
- }
56
-
57
- // ---------------------------------------------------------------- bar
58
-
59
- export function barSequence(state: VisualizerState, barCount: number): number[][] {
60
- switch (state) {
61
- case 'connecting': {
62
- // A mirrored pair sweeping outward: [0, n-1], [1, n-2], ...
63
- const seq: number[][] = [];
64
- for (let x = 0; x < barCount; x++) seq.push([x, barCount - 1 - x]);
65
- return seq.length ? seq : [[]];
66
- }
67
- case 'listening':
68
- case 'thinking': {
69
- // Blink the center bar. -1 is a deliberate no-index frame (the "off" beat).
70
- return [[Math.floor(barCount / 2)], [-1]];
71
- }
72
- case 'speaking':
73
- return [Array.from({ length: barCount }, (_, i) => i)];
74
- // Dead-connection look: mirrors idle (nothing lit) for now. A dedicated
75
- // arm so the pending LiveKit disconnected-state measurement can adjust
76
- // it in one line.
77
- case 'disconnected':
78
- return [[]];
79
- case 'idle':
80
- default:
81
- return [[]];
82
- }
83
- }
84
-
85
- export function barInterval(state: VisualizerState, barCount: number): number {
86
- switch (state) {
87
- // The whole sweep should take 2s regardless of how many bars it crosses.
88
- case 'connecting':
89
- return 2000 / Math.max(1, barCount);
90
- case 'listening':
91
- return 500;
92
- case 'thinking':
93
- return 150;
94
- default:
95
- return 1000;
96
- }
97
- }
98
-
99
- // ---------------------------------------------------------------- grid
100
-
101
- /**
102
- * Walk the perimeter of a ring `spread` cells out from center, clockwise.
103
- *
104
- * NOTE: This diverges from upstream use-agent-audio-visualizer-grid.ts in two ways:
105
- * (1) We use separate centerX and centerY (upstream uses row-center for both axes).
106
- * Upstream never ships non-square grids, so the bug never manifested. Our public
107
- * API is square-only too now (the grid's single `count` prop, 2026-08-09), but
108
- * this function keeps taking rows/columns as plain params, so the correct
109
- * arbitrary-shape handling stays -- it costs nothing and guards any future caller.
110
- * (2) We distinguish spread === 0 from spread === undefined (upstream treats 0 as falsy).
111
- * Again, upstream never ships spread=0 explicitly; we do for API completeness.
112
- */
113
- function gridRing(rows: number, columns: number, spread: number): Coordinate[] {
114
- const seq: Coordinate[] = [];
115
- const centerX = Math.floor(columns / 2);
116
- const centerY = Math.floor(rows / 2);
117
- const topLeft = { x: Math.max(0, centerX - spread), y: Math.max(0, centerY - spread) };
118
- const bottomRight = {
119
- x: Math.min(columns - 1, centerX + spread),
120
- y: Math.min(rows - 1, centerY + spread),
121
- };
122
-
123
- for (let x = topLeft.x; x <= bottomRight.x; x++) seq.push({ x, y: topLeft.y });
124
- for (let y = topLeft.y + 1; y <= bottomRight.y; y++) seq.push({ x: bottomRight.x, y });
125
- for (let x = bottomRight.x - 1; x >= topLeft.x; x--) seq.push({ x, y: bottomRight.y });
126
- for (let y = bottomRight.y - 1; y > topLeft.y; y--) seq.push({ x: topLeft.x, y });
127
-
128
- return seq;
129
- }
130
-
131
- export function gridSequence(
132
- state: VisualizerState,
133
- rows: number,
134
- columns: number,
135
- spread?: number,
136
- ): Coordinate[] {
137
- const center = { x: Math.floor(columns / 2), y: Math.floor(rows / 2) };
138
-
139
- switch (state) {
140
- case 'connecting': {
141
- const maxSpread = Math.floor(Math.max(rows, columns) / 2);
142
- const clamped = spread !== undefined ? Math.min(spread, maxSpread) : maxSpread;
143
- const ring = gridRing(rows, columns, clamped);
144
- return ring.length ? ring : [center];
145
- }
146
- case 'listening': {
147
- // One lit frame followed by eight dark ones: a slow heartbeat.
148
- const off = { x: -1, y: -1 };
149
- return [center, off, off, off, off, off, off, off, off];
150
- }
151
- case 'thinking': {
152
- // Sweep the middle row left to right, then back.
153
- const y = Math.floor(rows / 2);
154
- const seq: Coordinate[] = [];
155
- for (let x = 0; x < columns; x++) seq.push({ x, y });
156
- for (let x = columns - 1; x >= 0; x--) seq.push({ x, y });
157
- return seq.length ? seq : [center];
158
- }
159
- // Dead-connection look: the same centre resting frame as idle
160
- // (variant-grid suppresses the highlight for both, so nothing lights).
161
- // A dedicated arm so the pending LiveKit measurement can adjust it in
162
- // one line.
163
- case 'disconnected':
164
- return [center];
165
- default:
166
- return [center];
167
- }
168
- }
169
-
170
- // ---------------------------------------------------------------- radial
171
-
172
- /** Largest divisor of `n` that is at most `max`. Always at least 1. */
173
- function largestDivisorAtMost(n: number, max: number): number {
174
- const gcd = (a: number, b: number): number => {
175
- while (b !== 0) {
176
- const t = b;
177
- b = a % b;
178
- a = t;
179
- }
180
- return a;
181
- };
182
- for (let i = max; i >= 1; i--) if (gcd(n, i) === i) return i;
183
- return 1;
184
- }
185
-
186
- /**
187
- * Partition the ring into interleaved groups. Lighting one group per tick reads
188
- * as a rotating pattern rather than a single travelling dot. Every index appears
189
- * in exactly one group.
190
- */
191
- function radialGroups(barCount: number): number[][] {
192
- const divisor =
193
- barCount > 8
194
- ? barCount / largestDivisorAtMost(barCount, 4)
195
- : largestDivisorAtMost(barCount, 2);
196
- const safe = Math.max(1, Math.floor(divisor));
197
- const perGroup = Math.floor(barCount / safe);
198
- return Array.from({ length: safe }, (_, i) =>
199
- Array.from({ length: perGroup }, (_, j) => j * safe + i),
200
- );
201
- }
202
-
203
- export function radialSequence(state: VisualizerState, barCount: number): number[][] {
204
- switch (state) {
205
- case 'connecting': {
206
- // Pair each bar with the one opposite it, sweeping around the circle.
207
- const center = Math.floor(barCount / 2);
208
- const seq: number[][] = [];
209
- for (let x = 0; x < barCount; x++) seq.push([x, (x + center) % barCount]);
210
- return seq.length ? seq : [[]];
211
- }
212
- case 'listening':
213
- case 'thinking': {
214
- const groups = radialGroups(barCount);
215
- return groups.length ? groups : [[]];
216
- }
217
- case 'speaking':
218
- return [Array.from({ length: barCount }, (_, i) => i)];
219
- // Dead-connection look: mirrors idle (nothing lit) for now, one-line
220
- // adjustable once the LiveKit measurement lands.
221
- case 'disconnected':
222
- return [[]];
223
- case 'idle':
224
- default:
225
- return [[]];
226
- }
227
- }
228
-
229
- export function radialInterval(state: VisualizerState): number {
230
- switch (state) {
231
- case 'connecting':
232
- case 'listening':
233
- return 500;
234
- // Infinity parks the sequencer: `thinking` spins the whole container in CSS
235
- // with every bar lit, so ticking would fight the animation.
236
- case 'thinking':
237
- return Infinity;
238
- default:
239
- return 1000;
240
- }
241
- }
242
-
243
- // ---------------------------------------------------------------- shader
244
-
245
- /**
246
- * Uniform targets for the aura and custom shaders. An array intensity means
247
- * "ping-pong between these two" (see create-tween). `speaking` intensity is
248
- * omitted from the pulse because live volume drives it instantly instead.
249
- */
250
- export function shaderTargets(
251
- state: VisualizerState,
252
- ): { intensity: number | [number, number]; speed: number } {
253
- switch (state) {
254
- case 'listening':
255
- return { intensity: [0.5, 0.8], speed: 2.5 };
256
- case 'thinking':
257
- return { intensity: [0.25, 0.5], speed: 4.0 };
258
- // Same pulse as thinking, its own speed: uSpeed is the only
259
- // state-correlated scalar besides intensity that reaches a custom
260
- // shader's GLSL (see customUniforms in variant-custom.tsx), so the two
261
- // transitional states sharing 4.0 made them indistinguishable to any
262
- // consumer shader keying off the kit's uniforms -- the Custom story's
263
- // per-state looks are the concrete consumer. Every state now maps to a
264
- // distinct speed: idle 1, listening/speaking 2.5 (told apart by band
265
- // energy), thinking 4, connecting 6.
266
- case 'connecting':
267
- return { intensity: [0.25, 0.5], speed: 6.0 };
268
- case 'speaking':
269
- return { intensity: 0.3, speed: 2.5 };
270
- // Dead-connection look: idle's dim intensity but its OWN speed, so a
271
- // shader keying off uSpeed can draw a dead line distinct from idle's
272
- // calm ridge (see the connecting arm's comment: uSpeed is the only
273
- // other state-correlated scalar reaching consumer GLSL). Still
274
- // one-line adjustable once the LiveKit measurement lands.
275
- case 'disconnected':
276
- return { intensity: 0.3, speed: 0.5 };
277
- case 'idle':
278
- default:
279
- return { intensity: 0.3, speed: 1 };
280
- }
281
- }
282
-
283
- const WAVE_SPEED = 5;
284
- const WAVE_AMPLITUDE = 0.025;
285
- const WAVE_FREQUENCY = 10;
286
-
287
- /** Uniform targets for the wave shader. Same shape, different curve. */
288
- export function waveTargets(state: VisualizerState): {
289
- speed: number;
290
- amplitude: number;
291
- frequency: number;
292
- opacity: number | [number, number];
293
- pulseDuration: number;
294
- } {
295
- switch (state) {
296
- case 'listening':
297
- return {
298
- speed: WAVE_SPEED,
299
- amplitude: WAVE_AMPLITUDE,
300
- frequency: WAVE_FREQUENCY,
301
- opacity: [1.0, 0.3],
302
- pulseDuration: 0.75,
303
- };
304
- case 'thinking':
305
- case 'connecting':
306
- return {
307
- speed: WAVE_SPEED * 4,
308
- amplitude: WAVE_AMPLITUDE / 4,
309
- frequency: WAVE_FREQUENCY * 4,
310
- opacity: [1.0, 0.3],
311
- pulseDuration: 0.4,
312
- };
313
- case 'speaking':
314
- // Upstream's wave switch has NO 'idle' arm: idle falls through to this
315
- // same speaking/default arm (speed 10, amplitude 0.025, frequency 10,
316
- // opacity 1), a gentle undulation (~1.05px centreline waviness, ~1.5px/s
317
- // phase drift, measured on the md tile). Upstream's explicitly FLAT
318
- // state is 'disconnected' -- the dedicated arm below. We had flattened
319
- // idle instead; Rob wants upstream's look (2026-08-09).
320
- case 'idle':
321
- return {
322
- speed: WAVE_SPEED * 2,
323
- amplitude: WAVE_AMPLITUDE,
324
- frequency: WAVE_FREQUENCY,
325
- opacity: 1.0,
326
- pulseDuration: 0,
327
- };
328
- // Upstream's EXPLICIT flat state, measured: 'disconnected' is the one
329
- // arm of their wave switch that zeroes the line. First-class here since
330
- // 2026-08-10 (Rob: 'the disconnected wave needs to be that kind of flat
331
- // line'). `default` keeps the same flat targets for out-of-union
332
- // strings, which normalizeState prevents real callers from reaching.
333
- case 'disconnected':
334
- return {
335
- speed: WAVE_SPEED,
336
- amplitude: 0,
337
- frequency: 0,
338
- opacity: 1.0,
339
- pulseDuration: 0,
340
- };
341
- default:
342
- return {
343
- speed: WAVE_SPEED,
344
- amplitude: 0,
345
- frequency: 0,
346
- opacity: 1.0,
347
- pulseDuration: 0,
348
- };
349
- }
350
- }
@@ -1,345 +0,0 @@
1
- // src/remote/host-embed.ts
2
- // The host embed SDK: mountRemoteCard() + the RemoteCardHandle. Drops one sandboxed
3
- // cross-origin iframe, runs the handshake (origin+source+nonce+version), pushes
4
- // CardContext + CardEnvelope down, validates inbound up-frames, and routes every
5
- // CardEvent through the SAME routeCardEvent the native transport uses. Auto-sizes the
6
- // iframe from `resize`, intercepts `focus-edge`.
7
- //
8
- // Security-critical, stateful, vanilla DOM ONLY — NO SolidJS imports. See the
9
- // iframe-transport design spec ("Host embed SDK surface" + addendum H-A/H-C/H-D/H-E/
10
- // H-F/H-G/H-O).
11
-
12
- import {
13
- type CardEnvelope,
14
- type CardContext,
15
- type CardEvent,
16
- type CardPolicy,
17
- } from '../primitives/card-contract';
18
- import { createPacker, isCardWireFrame, type WireMessage } from './wire';
19
- import { assertCrossOrigin, redactFrame } from './origin';
20
- import { isValidVersion } from './version';
21
- import { hasPollutionKey, isKnownEventKind } from './validate';
22
- import { routeCardEvent } from '../primitives/card-routing';
23
-
24
- export interface MountRemoteCardOptions {
25
- /** Where to mount the iframe. */
26
- container: HTMLElement;
27
- /** The card to render (carried down as a `render` frame). */
28
- envelope: CardEnvelope;
29
- /** EXACT provider origin (https). Pinned for all origin checks. */
30
- providerOrigin: string;
31
- /** The provider URL the iframe frames (must be same-origin as providerOrigin). */
32
- src: string;
33
- /** Ambient context; theme/locale/conversationId/short-lived authToken. */
34
- context: CardContext;
35
- /** The SAME routing policy native cards use. resize/focus-edge are handled by the SDK. */
36
- policy?: CardPolicy;
37
- /** Override sandbox. Default: allow-scripts allow-forms allow-same-origin (NO allow-popups — H-G). */
38
- sandbox?: string;
39
- /** Cap auto-height (px); frame scrolls internally beyond it. */
40
- maxHeight?: number;
41
- /** Handshake timeout (ms). Default 5000. */
42
- handshakeTimeoutMs?: number;
43
- }
44
-
45
- export interface RemoteCardHandle {
46
- /** Push new ambient context (theme toggle, token refresh). Shallow top-level merge;
47
- * `theme`/`a11y` replaced wholesale. Always sends a FULL resolved CardContext. */
48
- updateContext(context: Partial<CardContext>): void;
49
- /** Re-render with a new/updated envelope (same id = update). */
50
- update(envelope: CardEnvelope): void;
51
- /** Send `teardown`, stop listeners, remove the iframe. Idempotent. */
52
- destroy(): void;
53
- /** Current bridge state for the host to reflect in UI. */
54
- state(): 'connecting' | 'open' | 'error' | 'closed';
55
- }
56
-
57
- type BridgeState = 'connecting' | 'open' | 'error' | 'closed';
58
-
59
- const HOST_SUPPORTED_VERSIONS = ['1'];
60
- const DEFAULT_SANDBOX = 'allow-scripts allow-forms allow-same-origin';
61
- const DEFAULT_HANDSHAKE_TIMEOUT_MS = 5000;
62
-
63
- /** Provider host for the iframe title (best-effort; never throws). */
64
- function providerHost(providerOrigin: string): string {
65
- try { return new URL(providerOrigin).host; } catch { return providerOrigin; }
66
- }
67
-
68
- export function mountRemoteCard(options: MountRemoteCardOptions): RemoteCardHandle {
69
- const {
70
- container,
71
- providerOrigin,
72
- src,
73
- policy,
74
- maxHeight,
75
- } = options;
76
-
77
- // ── Cross-origin precondition (H-F): fail closed BEFORE mounting. ──
78
- const hostOrigin = typeof window !== 'undefined' ? window.location.origin : '';
79
- assertCrossOrigin(src, providerOrigin, hostOrigin);
80
-
81
- const handshakeTimeoutMs = options.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
82
-
83
- // ── Bridge identity + generation (H-A). A new nonce per generation; stale frames are
84
- // rejected by construction (generation capture + nonce equality). ──
85
- let generation = 0;
86
- let nonce = mintNonce();
87
- let packer = createPacker('1', nonce); // provisional until `ready` negotiates the version.
88
-
89
- // ── State machine (H-E). ──
90
- let state: BridgeState = 'connecting';
91
-
92
- // ── Latest-wins pre-OPEN buffer (H-E). Always the newest pending context/render. ──
93
- let currentContext: CardContext = resolveContext(options.context);
94
- let currentEnvelope: CardEnvelope = options.envelope;
95
- let pendingContext: CardContext | null = null;
96
- let pendingRender: CardEnvelope | null = null;
97
-
98
- let iframe: HTMLIFrameElement | null = null;
99
- let timer: ReturnType<typeof setTimeout> | undefined;
100
- let listener: ((e: MessageEvent) => void) | null = null;
101
-
102
- function warn(data: unknown): void {
103
- try { console.warn('[kai-remote]', redactFrame(data)); } catch { /* best-effort */ }
104
- }
105
-
106
- function postDown(message: WireMessage): void {
107
- const cw = iframe?.contentWindow;
108
- if (!cw) return;
109
- try { cw.postMessage(packer(message), providerOrigin); } catch { /* best-effort */ }
110
- }
111
-
112
- function clearTimer(): void {
113
- if (timer !== undefined) { clearTimeout(timer); timer = undefined; }
114
- }
115
-
116
- function sendContext(ctx: CardContext): void {
117
- if (state === 'open') postDown({ dir: 'down', kind: 'context', context: ctx });
118
- else if (state === 'connecting') pendingContext = ctx; // latest-wins; no buffer in error/closed
119
- }
120
-
121
- function sendRender(envelope: CardEnvelope): void {
122
- if (state === 'open') postDown({ dir: 'down', kind: 'render', envelope });
123
- else if (state === 'connecting') pendingRender = envelope; // latest-wins; no buffer in error/closed
124
- }
125
-
126
- function flushBuffer(): void {
127
- // Order: context → render (H-E), so the runtime themes before first paint.
128
- if (pendingContext) { postDown({ dir: 'down', kind: 'context', context: pendingContext }); pendingContext = null; }
129
- if (pendingRender) { postDown({ dir: 'down', kind: 'render', envelope: pendingRender }); pendingRender = null; }
130
- }
131
-
132
- function renderFallback(message: string): void {
133
- // Replace the iframe with an inline accessible fallback + a Retry button.
134
- const fallback = document.createElement('div');
135
- fallback.setAttribute('role', 'alert');
136
- fallback.dataset.kaiRemoteFallback = '';
137
- const msg = document.createElement('p');
138
- msg.textContent = message;
139
- fallback.appendChild(msg);
140
- const retry = document.createElement('button');
141
- retry.type = 'button';
142
- retry.textContent = 'Retry';
143
- retry.addEventListener('click', () => retryMount());
144
- fallback.appendChild(retry);
145
- if (iframe && iframe.parentNode === container) container.replaceChild(fallback, iframe);
146
- else container.appendChild(fallback);
147
- iframe = null;
148
- }
149
-
150
- function toError(message: string): void {
151
- if (state === 'closed') return;
152
- clearTimer();
153
- state = 'error';
154
- pendingContext = null;
155
- pendingRender = null; // discard the coalesced buffer (H-O)
156
- renderFallback(message);
157
- }
158
-
159
- /** Build the iframe, wire the listener BEFORE setting src (race closure), append, set src. */
160
- function createIframe(): void {
161
- const myGen = generation;
162
- const el = document.createElement('iframe');
163
- el.setAttribute('sandbox', options.sandbox ?? DEFAULT_SANDBOX);
164
- el.setAttribute('referrerpolicy', 'no-referrer');
165
- el.setAttribute('allow', '');
166
- const titleBase = currentEnvelope.title ?? currentEnvelope.type;
167
- el.setAttribute('title', `${titleBase} — provided by ${providerHost(providerOrigin)}`);
168
- el.style.border = '0';
169
- el.style.width = '100%';
170
-
171
- el.addEventListener('load', () => {
172
- if (generation !== myGen || state !== 'connecting') return;
173
- // Provisional packer (negotiated version is set on `ready`).
174
- packer = createPacker('1', nonce);
175
- postDown({ dir: 'down', kind: 'hello', supportedVersions: HOST_SUPPORTED_VERSIONS });
176
- clearTimer();
177
- timer = setTimeout(() => {
178
- if (generation !== myGen || state !== 'connecting') return;
179
- toError('[timeout] The card took too long to load.');
180
- policy?.onError?.(currentEnvelope.id, '[timeout] handshake timed out');
181
- }, handshakeTimeoutMs);
182
- });
183
-
184
- // Listener BEFORE src (H — load-before-listener race closure).
185
- listener = (event: MessageEvent) => onMessage(event, myGen);
186
- window.addEventListener('message', listener);
187
-
188
- iframe = el;
189
- container.appendChild(el);
190
- el.setAttribute('src', src); // set LAST.
191
- }
192
-
193
- function retryMount(): void {
194
- // Guard: only retry from error state; a double-click (or stray call) is a no-op.
195
- if (state !== 'error') return;
196
- // New generation + new nonce; stale frames from the dead iframe are rejected.
197
- generation += 1;
198
- nonce = mintNonce();
199
- packer = createPacker('1', nonce);
200
- state = 'connecting';
201
- pendingContext = null;
202
- pendingRender = null;
203
- clearTimer();
204
- if (listener) { window.removeEventListener('message', listener); listener = null; }
205
- // Defensively remove any lingering iframe before creating a fresh one.
206
- if (iframe) { iframe.remove(); iframe = null; }
207
- // Always re-push the latest resolved context + envelope after the new handshake.
208
- pendingContext = currentContext;
209
- pendingRender = currentEnvelope;
210
- // Remove any existing fallback before re-mounting.
211
- container.querySelectorAll('[data-kai-remote-fallback]').forEach((n) => n.remove());
212
- createIframe();
213
- }
214
-
215
- function sizeIframe(height: number): void {
216
- if (!iframe) return;
217
- const clamped = Math.min(height, maxHeight ?? Infinity);
218
- iframe.style.height = `${clamped}px`;
219
- }
220
-
221
- function moveFocusPastIframe(): void {
222
- // v1 focus-edge handling: blur the iframe so default browser tab order moves past it.
223
- // (A richer "next host focusable" mover is a later refinement; blur is deterministic
224
- // and never routes through CardPolicy.)
225
- if (iframe) { try { iframe.blur(); } catch { /* best-effort */ } }
226
- }
227
-
228
- function onMessage(event: MessageEvent, myGen: number): void {
229
- // ── Stale-generation defense (H-A): ignore frames from a superseded/closed bridge. ──
230
- if (myGen !== generation || state === 'closed' || state === 'error') return;
231
- const data = event.data;
232
-
233
- // ── Inbound security gates, in order (H-A/H-C/H-D): ──
234
- // 1. origin pin 2. source-window pin 3. structural + direction guard
235
- // 4. nonce equality (bridge-instance binding)
236
- if (event.origin !== providerOrigin) return;
237
- if (event.source !== iframe?.contentWindow) return;
238
- if (!isCardWireFrame(data, 'up')) return;
239
- if (data.nonce !== nonce) return;
240
-
241
- // A throw past the gate must NOT kill the listener.
242
- try {
243
- const message = data.message;
244
- switch (message.kind) {
245
- case 'ready': {
246
- if (state !== 'connecting') return;
247
- const accepted = message.acceptedVersion;
248
- // 5. version: accepted MUST be a host-supported version (H-C).
249
- if (!isValidVersion(accepted) || !HOST_SUPPORTED_VERSIONS.includes(accepted)) {
250
- toError('[version-unsupported] This card needs a different host version.');
251
- policy?.onError?.(currentEnvelope.id, '[version-unsupported] no compatible protocol version');
252
- return;
253
- }
254
- // Rebuild the packer with the negotiated version (H-C).
255
- packer = createPacker(accepted, nonce);
256
- clearTimer();
257
- state = 'open';
258
- // Seed the buffer with the current resolved context + envelope, then flush.
259
- if (!pendingContext) pendingContext = currentContext;
260
- if (!pendingRender) pendingRender = currentEnvelope;
261
- flushBuffer();
262
- return;
263
- }
264
- case 'event': {
265
- if (state !== 'open') return;
266
- const ev: CardEvent = message.event;
267
- // cardId fence (H-E): drop in-flight events for a replaced/stale card.
268
- if (ev.cardId !== currentEnvelope.id) return;
269
- // resize is transport plumbing — intercept, never route (H-J / spec UP table).
270
- if (ev.kind === 'resize') { sizeIframe(ev.height); return; }
271
- // Schema/pollution gate before routing (H-D).
272
- if (!isKnownEventKind(ev.kind) || hasPollutionKey(ev)) { warn(data); return; }
273
- routeCardEvent(policy ?? {}, ev);
274
- return;
275
- }
276
- case 'focus-edge': {
277
- // a11y plumbing — intercept, never route (H-O).
278
- moveFocusPastIframe();
279
- return;
280
- }
281
- case 'fault': {
282
- const code = message.code;
283
- const reason = message.message;
284
- toError(`[${code}] The card reported a problem.`);
285
- policy?.onError?.(currentEnvelope.id, `[${code}] ${reason}`);
286
- return;
287
- }
288
- default:
289
- return;
290
- }
291
- } catch {
292
- warn(data);
293
- }
294
- }
295
-
296
- // ── Public handle ──────────────────────────────────────────────────────────
297
- const handle: RemoteCardHandle = {
298
- updateContext(partial: Partial<CardContext>): void {
299
- if (state === 'closed') return;
300
- currentContext = mergeContext(currentContext, partial);
301
- sendContext(currentContext);
302
- },
303
- update(envelope: CardEnvelope): void {
304
- if (state === 'closed') return;
305
- currentEnvelope = envelope;
306
- sendRender(envelope);
307
- },
308
- destroy(): void {
309
- if (state === 'closed') return;
310
- generation += 1; // tombstone the current generation (rejects stale inbound).
311
- if (state === 'open') postDown({ dir: 'down', kind: 'teardown' });
312
- state = 'closed';
313
- clearTimer();
314
- if (listener) { window.removeEventListener('message', listener); listener = null; }
315
- if (iframe) { iframe.remove(); iframe = null; }
316
- container.querySelectorAll('[data-kai-remote-fallback]').forEach((n) => n.remove());
317
- },
318
- state(): BridgeState {
319
- return state;
320
- },
321
- };
322
-
323
- createIframe();
324
- return handle;
325
- }
326
-
327
- /** Crypto-random hex nonce (H-A): 16 bytes → 32 hex chars. */
328
- function mintNonce(): string {
329
- const bytes = new Uint8Array(16);
330
- crypto.getRandomValues(bytes);
331
- let out = '';
332
- for (const b of bytes) out += b.toString(16).padStart(2, '0');
333
- return out;
334
- }
335
-
336
- /** A shallow copy so the caller's context object isn't mutated by later merges. */
337
- function resolveContext(ctx: CardContext): CardContext {
338
- return { ...ctx };
339
- }
340
-
341
- /** Shallow top-level merge (H-E): `theme` and `a11y` are replaced wholesale, not
342
- * deep-merged. Returns a new full resolved CardContext. */
343
- function mergeContext(base: CardContext, partial: Partial<CardContext>): CardContext {
344
- return { ...base, ...partial };
345
- }
@@ -1,2 +0,0 @@
1
- export { mountRemoteCard } from './host-embed';
2
- export type { MountRemoteCardOptions, RemoteCardHandle } from './host-embed';