@adia-ai/gen-ui 0.8.37

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 (631) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +77 -0
  3. package/compose/CHANGELOG.md +1707 -0
  4. package/compose/README.md +215 -0
  5. package/compose/core/artifacts.js +262 -0
  6. package/compose/core/context-store.js +218 -0
  7. package/compose/core/generator.d.ts +4 -0
  8. package/compose/core/generator.js +602 -0
  9. package/compose/core/pipeline/engine.js +289 -0
  10. package/compose/core/pipeline/types.js +91 -0
  11. package/compose/core/plan-primitives.js +120 -0
  12. package/compose/core/plan-turn.js +145 -0
  13. package/compose/core/reference.js +150 -0
  14. package/compose/core/state.js +15 -0
  15. package/compose/evals/harness.d.ts +4 -0
  16. package/compose/evals/harness.mjs +193 -0
  17. package/compose/index.d.ts +4 -0
  18. package/compose/index.js +16 -0
  19. package/compose/shared/validate-and-repair.js +122 -0
  20. package/compose/strategies/_shared/chunk-loader.js +282 -0
  21. package/compose/strategies/cascade.d.ts +67 -0
  22. package/compose/strategies/cascade.js +165 -0
  23. package/compose/strategies/escalation.d.ts +13 -0
  24. package/compose/strategies/escalation.js +132 -0
  25. package/compose/strategies/free-form-composer/index.js +254 -0
  26. package/compose/strategies/free-form-composer/system-prompt.js +205 -0
  27. package/compose/strategies/free-form-composer/transpile.js +279 -0
  28. package/compose/strategies/monolithic/_shared.js +1619 -0
  29. package/compose/strategies/monolithic/generate-instant.js +228 -0
  30. package/compose/strategies/monolithic/generate-pro.js +490 -0
  31. package/compose/strategies/monolithic/generate-thinking.js +206 -0
  32. package/compose/strategies/registry.d.ts +20 -0
  33. package/compose/strategies/registry.js +495 -0
  34. package/compose/strategies/zettel/_smoke.js +27 -0
  35. package/compose/strategies/zettel/chunk-composer.js +214 -0
  36. package/compose/strategies/zettel/chunk-refiner.js +582 -0
  37. package/compose/strategies/zettel/chunk-synthesizer.js +458 -0
  38. package/compose/strategies/zettel/composer.js +78 -0
  39. package/compose/strategies/zettel/composition-library.js +418 -0
  40. package/compose/strategies/zettel/generate.js +15 -0
  41. package/compose/strategies/zettel/generator-adapter.d.ts +4 -0
  42. package/compose/strategies/zettel/generator-adapter.js +242 -0
  43. package/compose/strategies/zettel/issue-reporter.js +607 -0
  44. package/compose/strategies/zettel/session-store.js +121 -0
  45. package/compose/strategies/zettel/state-cache.js +158 -0
  46. package/compose/transpiler/transpiler-maps.js +675 -0
  47. package/compose/transpiler/transpiler.d.ts +4 -0
  48. package/compose/transpiler/transpiler.js +962 -0
  49. package/corpus/CHANGELOG.md +1957 -0
  50. package/corpus/README.md +279 -0
  51. package/corpus/catalog-a2ui_0_9.d.ts +4 -0
  52. package/corpus/catalog-a2ui_0_9.json +29571 -0
  53. package/corpus/catalog-a2ui_0_9_rules.txt +1215 -0
  54. package/corpus/chunk-embeddings.d.ts +4 -0
  55. package/corpus/chunk-embeddings.json +1 -0
  56. package/corpus/chunks/_index.json +16658 -0
  57. package/corpus/chunks/_source-hashes.json +571 -0
  58. package/corpus/chunks/access-denied-with-request.json +124 -0
  59. package/corpus/chunks/access-request-form.json +166 -0
  60. package/corpus/chunks/access-request-pending.json +133 -0
  61. package/corpus/chunks/accordion-settings.json +176 -0
  62. package/corpus/chunks/achievement-badges.json +413 -0
  63. package/corpus/chunks/achievement-unlock.json +104 -0
  64. package/corpus/chunks/action-list-record-actions.json +84 -0
  65. package/corpus/chunks/activity-feed.json +390 -0
  66. package/corpus/chunks/admin-page-header-flush.json +83 -0
  67. package/corpus/chunks/admin-page-header-title-description.json +80 -0
  68. package/corpus/chunks/admin-page-header-with-actions.json +107 -0
  69. package/corpus/chunks/admin-page-header-with-tabs.json +144 -0
  70. package/corpus/chunks/admin-shell-rounded-borderless.json +287 -0
  71. package/corpus/chunks/admin-shell-standard.json +310 -0
  72. package/corpus/chunks/admin-sidebar-brand-signin.json +158 -0
  73. package/corpus/chunks/admin-sidebar-brand-version.json +130 -0
  74. package/corpus/chunks/admin-sidebar-flat-nav.json +131 -0
  75. package/corpus/chunks/admin-sidebar-nav-dividers.json +150 -0
  76. package/corpus/chunks/admin-sidebar-nav-groups.json +171 -0
  77. package/corpus/chunks/admin-sidebar-workspace-switcher.json +139 -0
  78. package/corpus/chunks/admin-topbar-breadcrumb-actions.json +130 -0
  79. package/corpus/chunks/admin-topbar-breadcrumb-minimal.json +79 -0
  80. package/corpus/chunks/admin-topbar-deep-breadcrumb.json +129 -0
  81. package/corpus/chunks/admin-topbar-search.json +104 -0
  82. package/corpus/chunks/admin-topbar-status-indicator.json +122 -0
  83. package/corpus/chunks/admin-topbar-trailing-toggle.json +114 -0
  84. package/corpus/chunks/agent-activity-feed.json +163 -0
  85. package/corpus/chunks/agent-artifact-stub-card.json +115 -0
  86. package/corpus/chunks/agent-canvas-shell.json +305 -0
  87. package/corpus/chunks/agent-citations-source-chips.json +82 -0
  88. package/corpus/chunks/agent-command-palette-search.json +120 -0
  89. package/corpus/chunks/agent-cost-detailed.json +260 -0
  90. package/corpus/chunks/agent-cost-inline.json +78 -0
  91. package/corpus/chunks/agent-memory-detail.json +181 -0
  92. package/corpus/chunks/agent-memory-editor.json +173 -0
  93. package/corpus/chunks/agent-memory-list.json +207 -0
  94. package/corpus/chunks/agent-pipeline-status-stages.json +61 -0
  95. package/corpus/chunks/agent-prompt-card.json +179 -0
  96. package/corpus/chunks/agent-prompt-library-grid.json +341 -0
  97. package/corpus/chunks/agent-prompt-picker.json +282 -0
  98. package/corpus/chunks/agent-rate-limit.json +100 -0
  99. package/corpus/chunks/agent-reasoning-collapsed.json +128 -0
  100. package/corpus/chunks/agent-tool-call-accordion.json +109 -0
  101. package/corpus/chunks/agent-tool-call-collapsed.json +98 -0
  102. package/corpus/chunks/agent-tool-call-error.json +112 -0
  103. package/corpus/chunks/agent-tool-call-result.json +145 -0
  104. package/corpus/chunks/agent-tool-call-running.json +85 -0
  105. package/corpus/chunks/agent-tool-call-success.json +137 -0
  106. package/corpus/chunks/agent-trace-timeline.json +244 -0
  107. package/corpus/chunks/ai-streaming-response.json +147 -0
  108. package/corpus/chunks/alert-banner-stack.json +78 -0
  109. package/corpus/chunks/alert-banner.json +55 -0
  110. package/corpus/chunks/alert-dialog-simple.json +72 -0
  111. package/corpus/chunks/alert-dialog-typed.json +107 -0
  112. package/corpus/chunks/alert-dialog-unsaved.json +81 -0
  113. package/corpus/chunks/analytics-chart.json +213 -0
  114. package/corpus/chunks/analytics-sparklines-dashboard.json +259 -0
  115. package/corpus/chunks/anchor-bar-selection-bar.json +226 -0
  116. package/corpus/chunks/api-key-table.json +148 -0
  117. package/corpus/chunks/api-keys-create.json +140 -0
  118. package/corpus/chunks/api-keys-list.json +271 -0
  119. package/corpus/chunks/app-nav-badges-counts.json +77 -0
  120. package/corpus/chunks/app-nav-badges-group-rollup-badges.json +100 -0
  121. package/corpus/chunks/app-nav-badges-plan-tiers.json +76 -0
  122. package/corpus/chunks/app-nav-badges-status-callouts.json +75 -0
  123. package/corpus/chunks/app-nav-full-example.json +195 -0
  124. package/corpus/chunks/app-nav-groups.json +103 -0
  125. package/corpus/chunks/app-nav-items.json +74 -0
  126. package/corpus/chunks/app-nav-labels-and-dividers.json +92 -0
  127. package/corpus/chunks/app-nav-spacer.json +77 -0
  128. package/corpus/chunks/app-product-listing-card.json +214 -0
  129. package/corpus/chunks/approvals-detail.json +240 -0
  130. package/corpus/chunks/approvals-queue.json +321 -0
  131. package/corpus/chunks/audit-log-basic.json +184 -0
  132. package/corpus/chunks/audit-log.json +174 -0
  133. package/corpus/chunks/auth-account-deleted-actions.json +51 -0
  134. package/corpus/chunks/auth-account-deleted.json +202 -0
  135. package/corpus/chunks/auth-card-content.json +260 -0
  136. package/corpus/chunks/auth-card-header.json +205 -0
  137. package/corpus/chunks/auth-email-entry.json +65 -0
  138. package/corpus/chunks/auth-email-verify-confirm.json +52 -0
  139. package/corpus/chunks/auth-email-verify.json +196 -0
  140. package/corpus/chunks/auth-forbidden-actions.json +59 -0
  141. package/corpus/chunks/auth-forbidden.json +203 -0
  142. package/corpus/chunks/auth-invite-actions.json +49 -0
  143. package/corpus/chunks/auth-invite-team-card.json +86 -0
  144. package/corpus/chunks/auth-link-expired-form.json +65 -0
  145. package/corpus/chunks/auth-link-expired.json +196 -0
  146. package/corpus/chunks/auth-locked-account.json +211 -0
  147. package/corpus/chunks/auth-locked-recovery-options.json +60 -0
  148. package/corpus/chunks/auth-mfa-enrollment-submit.json +54 -0
  149. package/corpus/chunks/auth-mfa-enrollment.json +284 -0
  150. package/corpus/chunks/auth-mfa-fallback-actions.json +52 -0
  151. package/corpus/chunks/auth-mfa-recovery.json +61 -0
  152. package/corpus/chunks/auth-new-password-form.json +75 -0
  153. package/corpus/chunks/auth-new-password.json +217 -0
  154. package/corpus/chunks/auth-oauth-fallback-actions.json +52 -0
  155. package/corpus/chunks/auth-oauth-interstitial.json +160 -0
  156. package/corpus/chunks/auth-password-challenge.json +94 -0
  157. package/corpus/chunks/auth-password-reset-form.json +64 -0
  158. package/corpus/chunks/auth-password-reset.json +195 -0
  159. package/corpus/chunks/auth-profile-form.json +72 -0
  160. package/corpus/chunks/auth-profile-setup.json +194 -0
  161. package/corpus/chunks/auth-reset-sent.json +188 -0
  162. package/corpus/chunks/auth-session-expired-actions.json +52 -0
  163. package/corpus/chunks/auth-session-expired.json +201 -0
  164. package/corpus/chunks/auth-signin-card-email.json +245 -0
  165. package/corpus/chunks/auth-signin-card-magic-link.json +147 -0
  166. package/corpus/chunks/auth-signin-card-mfa.json +231 -0
  167. package/corpus/chunks/auth-signin-card-otp.json +205 -0
  168. package/corpus/chunks/auth-signin-card-password.json +264 -0
  169. package/corpus/chunks/auth-signin-card-recovery.json +218 -0
  170. package/corpus/chunks/auth-signout-actions.json +51 -0
  171. package/corpus/chunks/auth-signout.json +184 -0
  172. package/corpus/chunks/auth-signup-email-entry.json +66 -0
  173. package/corpus/chunks/auth-signup-entry.json +281 -0
  174. package/corpus/chunks/auth-signup-social-auths.json +54 -0
  175. package/corpus/chunks/auth-signup-verify.json +227 -0
  176. package/corpus/chunks/auth-social-auths.json +62 -0
  177. package/corpus/chunks/auth-sso-providers.json +55 -0
  178. package/corpus/chunks/auth-sso-required.json +213 -0
  179. package/corpus/chunks/auth-team-invite.json +288 -0
  180. package/corpus/chunks/avatar-group-overflow.json +137 -0
  181. package/corpus/chunks/avatar-stack-overflow.json +124 -0
  182. package/corpus/chunks/banner-announcement-beta.json +99 -0
  183. package/corpus/chunks/banner-announcement-promo.json +102 -0
  184. package/corpus/chunks/banner-live-update.json +102 -0
  185. package/corpus/chunks/banner-system-maintenance.json +101 -0
  186. package/corpus/chunks/banner-system-offline.json +85 -0
  187. package/corpus/chunks/banner-system-update.json +102 -0
  188. package/corpus/chunks/bell-menu-empty.json +107 -0
  189. package/corpus/chunks/bell-menu-unread.json +395 -0
  190. package/corpus/chunks/billing-cancel-subscription-danger.json +61 -0
  191. package/corpus/chunks/billing-invoice-history-table.json +75 -0
  192. package/corpus/chunks/billing-overview.json +202 -0
  193. package/corpus/chunks/billing-payment-method-card-on-file.json +157 -0
  194. package/corpus/chunks/billing-progress-label.json +101 -0
  195. package/corpus/chunks/blog-post-card.json +212 -0
  196. package/corpus/chunks/breadcrumb-navigation.json +72 -0
  197. package/corpus/chunks/bulk-action-toolbar-floating.json +199 -0
  198. package/corpus/chunks/bulk-action-toolbar-swap.json +158 -0
  199. package/corpus/chunks/bulk-selection-actions-bar.json +162 -0
  200. package/corpus/chunks/business-address-form.json +953 -0
  201. package/corpus/chunks/calendar-month-view.json +74 -0
  202. package/corpus/chunks/card-grid-images.json +459 -0
  203. package/corpus/chunks/card-header-with-description.json +47 -0
  204. package/corpus/chunks/card-with-empty-state.json +96 -0
  205. package/corpus/chunks/card-with-form.json +143 -0
  206. package/corpus/chunks/card-with-sparkline.json +90 -0
  207. package/corpus/chunks/card-with-stat-kpi.json +86 -0
  208. package/corpus/chunks/changelog-feed-basic.json +470 -0
  209. package/corpus/chunks/chart-in-card-chart-plus-breakdown.json +188 -0
  210. package/corpus/chunks/chart-in-card-chart-plus-stat.json +89 -0
  211. package/corpus/chunks/chart-in-card-chart-with-filter-pills.json +124 -0
  212. package/corpus/chunks/chart-in-card-full-bleed-legend.json +116 -0
  213. package/corpus/chunks/chart-in-card-full-chart.json +107 -0
  214. package/corpus/chunks/chart-in-card-sparkline-tile.json +93 -0
  215. package/corpus/chunks/chart-in-card-stat-with-sparkline.json +73 -0
  216. package/corpus/chunks/chart-in-card-trend-footer.json +115 -0
  217. package/corpus/chunks/chart-with-filter-pills.json +169 -0
  218. package/corpus/chunks/chat-empty-state-hero.json +102 -0
  219. package/corpus/chunks/chat-message-assistant-byline.json +85 -0
  220. package/corpus/chunks/chat-message-user-byline.json +84 -0
  221. package/corpus/chunks/chat-page-shell.json +379 -0
  222. package/corpus/chunks/chat-streaming-surface.json +169 -0
  223. package/corpus/chunks/checkout-order-summary-card.json +140 -0
  224. package/corpus/chunks/code-snippet.json +107 -0
  225. package/corpus/chunks/col-form-stack.json +93 -0
  226. package/corpus/chunks/col-heading-body-action.json +79 -0
  227. package/corpus/chunks/col-stat-label.json +63 -0
  228. package/corpus/chunks/color-picker-swatches.json +153 -0
  229. package/corpus/chunks/command-palette.json +327 -0
  230. package/corpus/chunks/comments-composer.json +158 -0
  231. package/corpus/chunks/comments-mention-menu.json +237 -0
  232. package/corpus/chunks/comments-thread.json +344 -0
  233. package/corpus/chunks/commerce-pricing-tiers.json +401 -0
  234. package/corpus/chunks/comparison-table.json +246 -0
  235. package/corpus/chunks/contact-form-card.json +197 -0
  236. package/corpus/chunks/contact-form.json +157 -0
  237. package/corpus/chunks/contact-support-form.json +702 -0
  238. package/corpus/chunks/conversion-funnel-4-step-funnel.json +139 -0
  239. package/corpus/chunks/conversion-funnel-6step.json +151 -0
  240. package/corpus/chunks/conversion-funnel-wide-summary.json +192 -0
  241. package/corpus/chunks/conversion-funnel-with-segment-compare.json +202 -0
  242. package/corpus/chunks/cta-banner-centered.json +102 -0
  243. package/corpus/chunks/dashboard-acquisition-panel.json +212 -0
  244. package/corpus/chunks/dashboard-admin-page.json +104 -0
  245. package/corpus/chunks/dashboard-audience-kpis.json +165 -0
  246. package/corpus/chunks/dashboard-audience-panel.json +756 -0
  247. package/corpus/chunks/dashboard-behavior-panel.json +114 -0
  248. package/corpus/chunks/dashboard-chart-recent.json +566 -0
  249. package/corpus/chunks/dashboard-cohort-retention.json +83 -0
  250. package/corpus/chunks/dashboard-conversion-panel.json +234 -0
  251. package/corpus/chunks/dashboard-country-list.json +304 -0
  252. package/corpus/chunks/dashboard-filter-bar.json +118 -0
  253. package/corpus/chunks/dashboard-funnel.json +142 -0
  254. package/corpus/chunks/dashboard-notifications-panel.json +423 -0
  255. package/corpus/chunks/dashboard-overview-panel.json +1644 -0
  256. package/corpus/chunks/dashboard-page-header.json +166 -0
  257. package/corpus/chunks/dashboard-pages-table.json +86 -0
  258. package/corpus/chunks/dashboard-quick-actions.json +116 -0
  259. package/corpus/chunks/dashboard-reports-panel.json +166 -0
  260. package/corpus/chunks/dashboard-reports-table.json +138 -0
  261. package/corpus/chunks/dashboard-storage-card.json +117 -0
  262. package/corpus/chunks/dashboard-tabs.json +86 -0
  263. package/corpus/chunks/dashboard-team-actions-storage.json +605 -0
  264. package/corpus/chunks/dashboard-team-list.json +334 -0
  265. package/corpus/chunks/dashboard-traffic-channels.json +184 -0
  266. package/corpus/chunks/dashboard-transactions-table.json +145 -0
  267. package/corpus/chunks/data-export-history.json +192 -0
  268. package/corpus/chunks/data-export-request.json +197 -0
  269. package/corpus/chunks/data-import-wizard.json +391 -0
  270. package/corpus/chunks/date-range-picker.json +156 -0
  271. package/corpus/chunks/date-time-picker-form.json +222 -0
  272. package/corpus/chunks/departments-checkbox-form.json +390 -0
  273. package/corpus/chunks/destructive-confirm-modal.json +149 -0
  274. package/corpus/chunks/diff-inline.json +84 -0
  275. package/corpus/chunks/diff-side-by-side.json +131 -0
  276. package/corpus/chunks/diff-structured.json +216 -0
  277. package/corpus/chunks/digest-email-preview.json +374 -0
  278. package/corpus/chunks/divider-text-label.json +87 -0
  279. package/corpus/chunks/doc-editor-shell.json +356 -0
  280. package/corpus/chunks/drawer-2fa-key.json +273 -0
  281. package/corpus/chunks/drawer-2fa-sms.json +261 -0
  282. package/corpus/chunks/drawer-2fa-totp.json +385 -0
  283. package/corpus/chunks/drawer-cancel-sub.json +636 -0
  284. package/corpus/chunks/drawer-change-plan.json +561 -0
  285. package/corpus/chunks/drawer-custom-roles.json +696 -0
  286. package/corpus/chunks/drawer-data-start.json +171 -0
  287. package/corpus/chunks/drawer-delete-account.json +341 -0
  288. package/corpus/chunks/drawer-delete-workspace.json +362 -0
  289. package/corpus/chunks/drawer-discord.json +434 -0
  290. package/corpus/chunks/drawer-figma.json +401 -0
  291. package/corpus/chunks/drawer-first-dashboard.json +220 -0
  292. package/corpus/chunks/drawer-gcal.json +475 -0
  293. package/corpus/chunks/drawer-github.json +589 -0
  294. package/corpus/chunks/drawer-invite.json +397 -0
  295. package/corpus/chunks/drawer-record-detail.json +44 -0
  296. package/corpus/chunks/drawer-report.json +528 -0
  297. package/corpus/chunks/drawer-revoke-session.json +331 -0
  298. package/corpus/chunks/drawer-role.json +190 -0
  299. package/corpus/chunks/drawer-slack.json +429 -0
  300. package/corpus/chunks/drawer-smtp.json +476 -0
  301. package/corpus/chunks/drawer-source.json +294 -0
  302. package/corpus/chunks/drawer-transaction.json +493 -0
  303. package/corpus/chunks/drawer-with-create-form.json +43 -0
  304. package/corpus/chunks/editor-code-pane.json +204 -0
  305. package/corpus/chunks/editor-page-shell.json +149 -0
  306. package/corpus/chunks/editor-preview-pane.json +136 -0
  307. package/corpus/chunks/editor-statusbar-ready.json +99 -0
  308. package/corpus/chunks/editor-toolbar-import-export.json +195 -0
  309. package/corpus/chunks/email-inbox-list.json +678 -0
  310. package/corpus/chunks/empty-state-danger.json +58 -0
  311. package/corpus/chunks/empty-state-minimal.json +44 -0
  312. package/corpus/chunks/empty-state-warning.json +53 -0
  313. package/corpus/chunks/empty-state.json +65 -0
  314. package/corpus/chunks/error-404-actions.json +54 -0
  315. package/corpus/chunks/error-404.json +204 -0
  316. package/corpus/chunks/error-500-actions.json +61 -0
  317. package/corpus/chunks/error-500.json +220 -0
  318. package/corpus/chunks/error-maintenance-actions.json +53 -0
  319. package/corpus/chunks/error-maintenance.json +203 -0
  320. package/corpus/chunks/error-page-shell.json +132 -0
  321. package/corpus/chunks/export-flow-async.json +210 -0
  322. package/corpus/chunks/export-flow-delivery.json +163 -0
  323. package/corpus/chunks/export-flow-sync.json +162 -0
  324. package/corpus/chunks/extended-profile-form.json +512 -0
  325. package/corpus/chunks/faq-accordion.json +123 -0
  326. package/corpus/chunks/feature-grid-icons.json +314 -0
  327. package/corpus/chunks/feature-grid-three-cards.json +187 -0
  328. package/corpus/chunks/feature-toggle-switch-grid.json +213 -0
  329. package/corpus/chunks/file-upload-dnd.json +166 -0
  330. package/corpus/chunks/file-upload-field.json +97 -0
  331. package/corpus/chunks/file-upload-form.json +251 -0
  332. package/corpus/chunks/filter-bar-active-filters.json +176 -0
  333. package/corpus/chunks/filter-bar-filter-bar-with-table.json +113 -0
  334. package/corpus/chunks/filter-bar-saved-views.json +117 -0
  335. package/corpus/chunks/filter-popover-form.json +137 -0
  336. package/corpus/chunks/footer-multi-column.json +270 -0
  337. package/corpus/chunks/footer-primary-only.json +41 -0
  338. package/corpus/chunks/form-checkbox-group.json +65 -0
  339. package/corpus/chunks/form-drawer-edit-form.json +178 -0
  340. package/corpus/chunks/form-drawer-simple-form.json +151 -0
  341. package/corpus/chunks/form-drawer-tall-form.json +325 -0
  342. package/corpus/chunks/form-field-email-hint.json +53 -0
  343. package/corpus/chunks/form-field-range.json +54 -0
  344. package/corpus/chunks/form-field-required.json +52 -0
  345. package/corpus/chunks/form-field-select.json +66 -0
  346. package/corpus/chunks/form-field-textarea.json +51 -0
  347. package/corpus/chunks/form-field-with-error.json +151 -0
  348. package/corpus/chunks/form-fields-two-column.json +69 -0
  349. package/corpus/chunks/form-page-shell.json +146 -0
  350. package/corpus/chunks/form-switch-toggle.json +41 -0
  351. package/corpus/chunks/form-system-live-demo.json +123 -0
  352. package/corpus/chunks/forms-address-autocomplete.json +277 -0
  353. package/corpus/chunks/forms-address-fields.json +221 -0
  354. package/corpus/chunks/forms-conditional-reveal.json +195 -0
  355. package/corpus/chunks/gallery-page-shell.json +444 -0
  356. package/corpus/chunks/grid-2col-entry-cards.json +140 -0
  357. package/corpus/chunks/grid-kpi-4col.json +164 -0
  358. package/corpus/chunks/grid-responsive-kpi.json +165 -0
  359. package/corpus/chunks/hero-cta-simple.json +107 -0
  360. package/corpus/chunks/hero-cta-split.json +183 -0
  361. package/corpus/chunks/hero-section-split.json +111 -0
  362. package/corpus/chunks/icon-text-row.json +60 -0
  363. package/corpus/chunks/image-caption-tile.json +99 -0
  364. package/corpus/chunks/image-carousel.json +156 -0
  365. package/corpus/chunks/image-thumbnail-grid.json +77 -0
  366. package/corpus/chunks/image-upload-preview.json +118 -0
  367. package/corpus/chunks/import-mapping-flow.json +972 -0
  368. package/corpus/chunks/inline-dialog-rename.json +128 -0
  369. package/corpus/chunks/inline-dialog-schedule.json +154 -0
  370. package/corpus/chunks/inline-dialog-status.json +179 -0
  371. package/corpus/chunks/integrations-api-keys-card-list.json +475 -0
  372. package/corpus/chunks/integrations-connect-form.json +550 -0
  373. package/corpus/chunks/integrations-grid.json +141 -0
  374. package/corpus/chunks/integrations-webhooks-empty-state.json +66 -0
  375. package/corpus/chunks/inventory-list-stock.json +137 -0
  376. package/corpus/chunks/invite-members.json +153 -0
  377. package/corpus/chunks/invoice-line-items.json +493 -0
  378. package/corpus/chunks/kanban-board-3col.json +713 -0
  379. package/corpus/chunks/kanban-page-shell.json +671 -0
  380. package/corpus/chunks/key-value-detail-list.json +242 -0
  381. package/corpus/chunks/keyboard-shortcut-display.json +250 -0
  382. package/corpus/chunks/labeled-textarea.json +45 -0
  383. package/corpus/chunks/leaderboard-table.json +140 -0
  384. package/corpus/chunks/library-browser-search-filter.json +109 -0
  385. package/corpus/chunks/linked-record-row.json +61 -0
  386. package/corpus/chunks/loading-skeleton-placeholder.json +387 -0
  387. package/corpus/chunks/login-form-card.json +207 -0
  388. package/corpus/chunks/logo-cloud-trusted-by.json +235 -0
  389. package/corpus/chunks/map-with-location-list.json +293 -0
  390. package/corpus/chunks/marketing-hero-cta.json +102 -0
  391. package/corpus/chunks/marketing-page-shell.json +352 -0
  392. package/corpus/chunks/masonry-gallery.json +154 -0
  393. package/corpus/chunks/member-edit-drawer.json +236 -0
  394. package/corpus/chunks/members-active-table-search-filter.json +184 -0
  395. package/corpus/chunks/members-pending-invites-cards.json +251 -0
  396. package/corpus/chunks/members-roles-permissions-list.json +146 -0
  397. package/corpus/chunks/menu-context-actions.json +113 -0
  398. package/corpus/chunks/metadata-description-list.json +178 -0
  399. package/corpus/chunks/modal-create-project-form.json +133 -0
  400. package/corpus/chunks/modal-preferences-tabs.json +147 -0
  401. package/corpus/chunks/multi-field-form-validation.json +394 -0
  402. package/corpus/chunks/nav-menu-icons.json +74 -0
  403. package/corpus/chunks/newsletter-subscription.json +190 -0
  404. package/corpus/chunks/notification-badges-feed.json +411 -0
  405. package/corpus/chunks/notification-preferences-panel.json +238 -0
  406. package/corpus/chunks/onb-completion.json +139 -0
  407. package/corpus/chunks/onb-extension-install.json +172 -0
  408. package/corpus/chunks/onb-hero-welcome.json +156 -0
  409. package/corpus/chunks/onb-import-picker.json +86 -0
  410. package/corpus/chunks/onb-mobile-handoff.json +121 -0
  411. package/corpus/chunks/onb-notification-prefs.json +83 -0
  412. package/corpus/chunks/onb-persona-picker.json +82 -0
  413. package/corpus/chunks/onb-provider-tiles.json +137 -0
  414. package/corpus/chunks/onb-settings-review.json +257 -0
  415. package/corpus/chunks/onb-step-footer.json +121 -0
  416. package/corpus/chunks/onb-step-header.json +121 -0
  417. package/corpus/chunks/onb-step-progress.json +140 -0
  418. package/corpus/chunks/onb-step-shell.json +380 -0
  419. package/corpus/chunks/onb-story-pane.json +121 -0
  420. package/corpus/chunks/onb-tutorial-steps.json +193 -0
  421. package/corpus/chunks/order-confirmation-success-card.json +177 -0
  422. package/corpus/chunks/order-tracking-timeline.json +131 -0
  423. package/corpus/chunks/pagination-controls.json +82 -0
  424. package/corpus/chunks/pane-settings-sidebar.json +204 -0
  425. package/corpus/chunks/patient-visit-shell.json +191 -0
  426. package/corpus/chunks/payment-checkout-form.json +341 -0
  427. package/corpus/chunks/payment-method-form.json +328 -0
  428. package/corpus/chunks/period-comparison.json +236 -0
  429. package/corpus/chunks/permission-matrix-table.json +438 -0
  430. package/corpus/chunks/permissions-matrix-grid.json +111 -0
  431. package/corpus/chunks/permissions-matrix-summary.json +157 -0
  432. package/corpus/chunks/permissions-role-picker-radio.json +107 -0
  433. package/corpus/chunks/permissions-role-picker-select.json +101 -0
  434. package/corpus/chunks/permissions-share-link.json +256 -0
  435. package/corpus/chunks/permissions-share-people.json +297 -0
  436. package/corpus/chunks/permissions-sharing-scopes.json +109 -0
  437. package/corpus/chunks/plan-selection-form.json +529 -0
  438. package/corpus/chunks/playground-a2ui.json +181 -0
  439. package/corpus/chunks/playground-agent-admin.json +117 -0
  440. package/corpus/chunks/playground-agent-chat.json +126 -0
  441. package/corpus/chunks/playground-app-shell.json +488 -0
  442. package/corpus/chunks/playground-chat.json +265 -0
  443. package/corpus/chunks/playground-construct-canvas.json +118 -0
  444. package/corpus/chunks/playground-css-channel.json +487 -0
  445. package/corpus/chunks/playground-gen-ui.json +224 -0
  446. package/corpus/chunks/playground-render-preview.json +369 -0
  447. package/corpus/chunks/playground-streams-bridge.json +437 -0
  448. package/corpus/chunks/playground-table-toolbar.json +38 -0
  449. package/corpus/chunks/popover-with-content.json +118 -0
  450. package/corpus/chunks/pricing-tiers.json +561 -0
  451. package/corpus/chunks/product-tour.json +158 -0
  452. package/corpus/chunks/profile-public.json +297 -0
  453. package/corpus/chunks/progress-tracker-milestones.json +294 -0
  454. package/corpus/chunks/radio-button-group.json +83 -0
  455. package/corpus/chunks/rating-stars-review-summary.json +259 -0
  456. package/corpus/chunks/reactions-bar.json +75 -0
  457. package/corpus/chunks/reactions-picker.json +245 -0
  458. package/corpus/chunks/real-time-metrics-dashboard.json +344 -0
  459. package/corpus/chunks/record-detail-drawer-simple-detail.json +296 -0
  460. package/corpus/chunks/record-detail-drawer-tabbed-detail.json +443 -0
  461. package/corpus/chunks/referral-share-link.json +128 -0
  462. package/corpus/chunks/referral-status.json +243 -0
  463. package/corpus/chunks/reg-address-form.json +825 -0
  464. package/corpus/chunks/reg-billing-card.json +210 -0
  465. package/corpus/chunks/reg-brand-scrape.json +599 -0
  466. package/corpus/chunks/reg-departments-toggle.json +259 -0
  467. package/corpus/chunks/reg-extended-profile.json +356 -0
  468. package/corpus/chunks/reg-final-done.json +457 -0
  469. package/corpus/chunks/reg-helpdesk-grid.json +571 -0
  470. package/corpus/chunks/reg-import-picker.json +112 -0
  471. package/corpus/chunks/reg-integrations-grid.json +418 -0
  472. package/corpus/chunks/reg-invite-form.json +245 -0
  473. package/corpus/chunks/reg-legal-entity.json +260 -0
  474. package/corpus/chunks/reg-org-chart-review.json +891 -0
  475. package/corpus/chunks/reg-profile-identity.json +188 -0
  476. package/corpus/chunks/reg-step-footer.json +201 -0
  477. package/corpus/chunks/reg-step-header.json +201 -0
  478. package/corpus/chunks/reg-step-progress.json +236 -0
  479. package/corpus/chunks/reg-step-shell.json +605 -0
  480. package/corpus/chunks/reg-story-pane.json +201 -0
  481. package/corpus/chunks/reg-success-summary.json +257 -0
  482. package/corpus/chunks/reg-team-size.json +128 -0
  483. package/corpus/chunks/reg-usecase-picker.json +104 -0
  484. package/corpus/chunks/reg-workspace-name.json +159 -0
  485. package/corpus/chunks/responsive-grid-pattern.json +71 -0
  486. package/corpus/chunks/responsive-kpi-grid.json +166 -0
  487. package/corpus/chunks/responsive-row-pattern.json +71 -0
  488. package/corpus/chunks/responsive-shell-sidebar.json +308 -0
  489. package/corpus/chunks/responsive-wrap-at-pattern.json +124 -0
  490. package/corpus/chunks/retention-cohort-default-retention.json +80 -0
  491. package/corpus/chunks/retention-cohort-stat-pair.json +163 -0
  492. package/corpus/chunks/retention-cohort-with-legend.json +135 -0
  493. package/corpus/chunks/revenue-heatmap.json +82 -0
  494. package/corpus/chunks/role-management.json +272 -0
  495. package/corpus/chunks/row-action-cluster.json +54 -0
  496. package/corpus/chunks/row-avatar-name-role.json +71 -0
  497. package/corpus/chunks/row-icon-label.json +52 -0
  498. package/corpus/chunks/row-spread-label-value.json +54 -0
  499. package/corpus/chunks/row-wrap-tags.json +72 -0
  500. package/corpus/chunks/schedule-meeting-form-card.json +229 -0
  501. package/corpus/chunks/search-recents-menu.json +195 -0
  502. package/corpus/chunks/search-results-grouped.json +357 -0
  503. package/corpus/chunks/search-results-list.json +202 -0
  504. package/corpus/chunks/search-saved-views.json +356 -0
  505. package/corpus/chunks/search-with-filters.json +311 -0
  506. package/corpus/chunks/searchable-picker-modal.json +164 -0
  507. package/corpus/chunks/section-with-stack.json +48 -0
  508. package/corpus/chunks/security-active-sessions-devices.json +287 -0
  509. package/corpus/chunks/security-change-password-form.json +102 -0
  510. package/corpus/chunks/security-delete-account-danger.json +79 -0
  511. package/corpus/chunks/segmented-control-billing-tabs.json +164 -0
  512. package/corpus/chunks/select-multiple-preselected.json +70 -0
  513. package/corpus/chunks/settings-2fa-setup.json +307 -0
  514. package/corpus/chunks/settings-admin-page.json +1205 -0
  515. package/corpus/chunks/settings-appearance-primitives.json +145 -0
  516. package/corpus/chunks/settings-appearance-theme-panel.json +55 -0
  517. package/corpus/chunks/settings-appearance.json +81 -0
  518. package/corpus/chunks/settings-danger-zone-delete-workspace.json +93 -0
  519. package/corpus/chunks/settings-general-form.json +69 -0
  520. package/corpus/chunks/settings-integrations.json +298 -0
  521. package/corpus/chunks/settings-members-invite.json +112 -0
  522. package/corpus/chunks/settings-notification-activity-checkboxes.json +108 -0
  523. package/corpus/chunks/settings-notification-channels-switches.json +101 -0
  524. package/corpus/chunks/settings-page-shell.json +190 -0
  525. package/corpus/chunks/settings-privacy.json +83 -0
  526. package/corpus/chunks/settings-profile-security.json +122 -0
  527. package/corpus/chunks/settings-toggle-switches.json +66 -0
  528. package/corpus/chunks/sidebar-nav.json +120 -0
  529. package/corpus/chunks/sidebar-navigation-panel.json +165 -0
  530. package/corpus/chunks/signup-form-card.json +218 -0
  531. package/corpus/chunks/signup-team-size-form.json +259 -0
  532. package/corpus/chunks/single-select-card-form.json +231 -0
  533. package/corpus/chunks/slider-range-controls.json +119 -0
  534. package/corpus/chunks/social-media-post-card.json +159 -0
  535. package/corpus/chunks/sortable-list.json +307 -0
  536. package/corpus/chunks/sortable-search-paginate-table.json +162 -0
  537. package/corpus/chunks/split-pane-editor.json +146 -0
  538. package/corpus/chunks/stack-avatar-badge-overlay.json +53 -0
  539. package/corpus/chunks/stack-image-shimmer.json +51 -0
  540. package/corpus/chunks/stat-cards-kpi-dashboard.json +178 -0
  541. package/corpus/chunks/stats-band-big-numbers.json +175 -0
  542. package/corpus/chunks/status-dashboard.json +282 -0
  543. package/corpus/chunks/stepper-form-wizard.json +165 -0
  544. package/corpus/chunks/subscription-status-renewal-card.json +285 -0
  545. package/corpus/chunks/survey-rating-form.json +165 -0
  546. package/corpus/chunks/swiper-product-carousel.json +349 -0
  547. package/corpus/chunks/swiper-testimonial-carousel.json +279 -0
  548. package/corpus/chunks/table-empty-state.json +116 -0
  549. package/corpus/chunks/table-inline-edit-row.json +269 -0
  550. package/corpus/chunks/table-spreadsheet-cells.json +334 -0
  551. package/corpus/chunks/table-toolbar-no-search.json +42 -0
  552. package/corpus/chunks/table-toolbar-with-table.json +208 -0
  553. package/corpus/chunks/table-with-toolbar-card.json +80 -0
  554. package/corpus/chunks/tabs-with-panels.json +187 -0
  555. package/corpus/chunks/tag-chips-filter.json +100 -0
  556. package/corpus/chunks/team-invite-form.json +380 -0
  557. package/corpus/chunks/team-members-list.json +341 -0
  558. package/corpus/chunks/testimonial-grid.json +269 -0
  559. package/corpus/chunks/testimonial-pull-quote.json +137 -0
  560. package/corpus/chunks/testimonials-avatar-cards.json +286 -0
  561. package/corpus/chunks/timeline-events.json +89 -0
  562. package/corpus/chunks/timezone-selector-basic.json +99 -0
  563. package/corpus/chunks/timezone-selector-preview.json +104 -0
  564. package/corpus/chunks/toolbar-icons.json +216 -0
  565. package/corpus/chunks/toolbar-rich-text-editor.json +230 -0
  566. package/corpus/chunks/toolbar-tooltips.json +191 -0
  567. package/corpus/chunks/tooltip-hover-button.json +106 -0
  568. package/corpus/chunks/tree-file-explorer.json +136 -0
  569. package/corpus/chunks/tree-table-basic.json +171 -0
  570. package/corpus/chunks/trial-banner.json +158 -0
  571. package/corpus/chunks/url-import-form.json +728 -0
  572. package/corpus/chunks/usage-quota-meter-card.json +217 -0
  573. package/corpus/chunks/user-identity-row.json +81 -0
  574. package/corpus/chunks/user-onboarding-checklist.json +301 -0
  575. package/corpus/chunks/user-profile-card.json +192 -0
  576. package/corpus/chunks/users-table-badge.json +135 -0
  577. package/corpus/chunks/verification-document-upload.json +179 -0
  578. package/corpus/chunks/verification-status.json +97 -0
  579. package/corpus/chunks/verification-stepper.json +111 -0
  580. package/corpus/chunks/video-player-controls.json +120 -0
  581. package/corpus/chunks/webhook-config.json +209 -0
  582. package/corpus/chunks/webhooks-list.json +204 -0
  583. package/corpus/chunks/wizard-drawer-three-step-wizard.json +443 -0
  584. package/corpus/manifest.d.ts +4 -0
  585. package/corpus/manifest.json +6 -0
  586. package/corpus/scripts/chunk-library.d.ts +4 -0
  587. package/corpus/scripts/chunk-library.js +335 -0
  588. package/node-shims.d.ts +48 -0
  589. package/package.json +215 -0
  590. package/retrieval/CHANGELOG.md +1073 -0
  591. package/retrieval/README.md +71 -0
  592. package/retrieval/anti-patterns.js +189 -0
  593. package/retrieval/authoring/index.d.ts +4 -0
  594. package/retrieval/authoring/index.js +9 -0
  595. package/retrieval/authoring/web-research.js +220 -0
  596. package/retrieval/catalog.js +250 -0
  597. package/retrieval/component-catalog.js +168 -0
  598. package/retrieval/component-entry.js +80 -0
  599. package/retrieval/context-assembler.js +174 -0
  600. package/retrieval/domain-router.js +417 -0
  601. package/retrieval/embedding/chunk-embedding-retriever.js +117 -0
  602. package/retrieval/embedding/embedding-provider.js +72 -0
  603. package/retrieval/embedding/index.d.ts +4 -0
  604. package/retrieval/embedding/index.js +2 -0
  605. package/retrieval/feedback/dialog-recorder.js +107 -0
  606. package/retrieval/feedback/feedback-analyzer.js +180 -0
  607. package/retrieval/feedback/feedback-store.js +194 -0
  608. package/retrieval/feedback/feedback.js +117 -0
  609. package/retrieval/feedback/gap-registry.js +80 -0
  610. package/retrieval/feedback/human-signal.js +113 -0
  611. package/retrieval/feedback/index.d.ts +4 -0
  612. package/retrieval/feedback/index.js +24 -0
  613. package/retrieval/feedback/submit-feedback.js +45 -0
  614. package/retrieval/index.d.ts +4 -0
  615. package/retrieval/index.js +53 -0
  616. package/retrieval/intent/clarity.js +139 -0
  617. package/retrieval/intent/decomposer.js +124 -0
  618. package/retrieval/intent/index.d.ts +4 -0
  619. package/retrieval/intent/index.js +18 -0
  620. package/retrieval/intent/intent-alignment.js +172 -0
  621. package/retrieval/intent/intent-categorizer.js +69 -0
  622. package/retrieval/intent/intent-gate.js +96 -0
  623. package/retrieval/intent/prompt-analyzer.js +162 -0
  624. package/retrieval/wiring-catalog.js +168 -0
  625. package/validate/catalog-validator.d.ts +4 -0
  626. package/validate/catalog-validator.js +327 -0
  627. package/validate/semantic/cache.js +54 -0
  628. package/validate/semantic/classify-intent.js +128 -0
  629. package/validate/semantic/index.js +163 -0
  630. package/validate/semantic/intent-specs.js +162 -0
  631. package/validate/semantic/judge.js +180 -0
@@ -0,0 +1,962 @@
1
+ /**
2
+ * HTML → A2UI Transpiler
3
+ *
4
+ * Converts arbitrary HTML/DOM into A2UI flat adjacency component messages.
5
+ * Three layers:
6
+ * Layer 1: Deterministic tag mapping (HTML tag → A2UI type)
7
+ * Layer 2: Structure inference (div/span → Column/Row/Card via styles)
8
+ * Layer 3: LLM reasoning (optional, for ambiguous structures)
9
+ *
10
+ * @module transpiler
11
+ */
12
+
13
+ import {
14
+ reverseRegistry, HTML_TAG_MAP, INPUT_TYPE_MAP, ARIA_ROLE_MAP,
15
+ SKIP_TAGS, extractProps, inferGap,
16
+ } from './transpiler-maps.js';
17
+ import { validateSchema } from '@adia-ai/a2ui/validate';
18
+ import { getContext } from '../core/reference.js';
19
+
20
+ // ═══════════════════════════════════════════════════════════════
21
+ // PUBLIC API
22
+ // ═══════════════════════════════════════════════════════════════
23
+
24
+ /**
25
+ * Transpile an HTML string to A2UI messages.
26
+ *
27
+ * @param {string} html — Raw HTML markup
28
+ * @param {object} [options]
29
+ * @param {'instant'|'reasoning'} [options.mode='instant']
30
+ * @param {object} [options.llmAdapter] — Required for reasoning mode
31
+ * @param {string} [options.surfaceId='default']
32
+ * @param {'silent'|'placeholder'} [options.unknownTagPolicy='silent'] — how an
33
+ * unregistered custom-element tag degrades. `silent` keeps the historical
34
+ * bare-container conversion (children preserved, props dropped); the caller
35
+ * is expected to act on the returned `unmappedTags`. `placeholder` ALSO
36
+ * emits a visible, semantically-tokened notice naming the tag — for
37
+ * surfaces whose only channel to a human is the rendered page. See gh#848.
38
+ * @returns {Promise<{ messages: object[], validation: object, suggestions: string[], unmappedTags: string[] }>}
39
+ */
40
+ export async function transpileHTML(html, options = {}) {
41
+ const { mode = 'instant', llmAdapter, surfaceId = 'default', unknownTagPolicy = 'silent' } = options;
42
+
43
+ if (!html || typeof html !== 'string' || !html.trim()) {
44
+ return {
45
+ messages: [...fallbackMessage('No HTML content to convert')],
46
+ validation: { score: 0, valid: false, checks: [] },
47
+ suggestions: ['Provide HTML markup to convert'],
48
+ unmappedTags: [],
49
+ };
50
+ }
51
+
52
+ // Parse HTML into a walkable tree
53
+ const root = parseHTMLMinimal(html);
54
+
55
+ // Walk the tree — Layer 1 + Layer 2
56
+ const context = {
57
+ usedIds: new Set(),
58
+ components: [],
59
+ isRoot: true,
60
+ // gh#848 — every tag that reached the Unknown fallback, so NO caller has
61
+ // to rediscover the degrade by eyeballing the output.
62
+ unmappedTags: new Set(),
63
+ unknownTagPolicy,
64
+ };
65
+
66
+ const rootId = walkNode(root, context);
67
+
68
+ // Ensure root component has id "root"
69
+ if (rootId && context.components.length > 0) {
70
+ const rootComp = context.components.find(c => c.id === rootId);
71
+ if (rootComp && rootComp.id !== 'root') {
72
+ // Rename to "root" and update any parent references
73
+ const oldId = rootComp.id;
74
+ rootComp.id = 'root';
75
+ for (const comp of context.components) {
76
+ if (comp.children) {
77
+ comp.children = comp.children.map(id => id === oldId ? 'root' : id);
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ const messages = [{
84
+ type: 'updateComponents',
85
+ surfaceId,
86
+ components: context.components,
87
+ }];
88
+
89
+ let validation = validateSchema(messages);
90
+ const suggestions = generateSuggestions(html, context.components, validation);
91
+ const unmappedTags = [...context.unmappedTags].sort();
92
+
93
+ // Layer 3: LLM reasoning if instant mode scored low
94
+ if (mode === 'reasoning' && llmAdapter && validation.score < 85) {
95
+ try {
96
+ const improved = await reasoningPass(html, messages, validation, llmAdapter);
97
+ if (improved) {
98
+ const improvedValidation = validateSchema(improved);
99
+ if (improvedValidation.score > validation.score) {
100
+ return {
101
+ messages: improved,
102
+ validation: improvedValidation,
103
+ suggestions: generateSuggestions(html, improved[0]?.components || [], improvedValidation),
104
+ unmappedTags,
105
+ };
106
+ }
107
+ }
108
+ } catch {
109
+ // Keep instant result on LLM failure
110
+ }
111
+ }
112
+
113
+ return { messages, validation, suggestions, unmappedTags };
114
+ }
115
+
116
+ /**
117
+ * Transpile a live DOM element to A2UI messages (browser-only).
118
+ * Uses getComputedStyle for better structure inference.
119
+ *
120
+ * @param {HTMLElement} rootElement
121
+ * @param {object} [options]
122
+ * @returns {Promise<{ messages: object[], validation: object, suggestions: string[] }>}
123
+ */
124
+ export async function transpileDOM(rootElement, options = {}) {
125
+ const html = rootElement.outerHTML;
126
+ return transpileHTML(html, options);
127
+ }
128
+
129
+ // ═══════════════════════════════════════════════════════════════
130
+ // TREE WALKER
131
+ // ═══════════════════════════════════════════════════════════════
132
+
133
+ /**
134
+ * Recursively walk a DOM node and produce A2UI components.
135
+ *
136
+ * @param {MinimalElement} el
137
+ * @param {object} context — { usedIds, components, isRoot }
138
+ * @returns {string|null} — The generated ID for this node, or null if skipped
139
+ */
140
+ function walkNode(el, context) {
141
+ const tag = (el.tagName || '').toLowerCase();
142
+
143
+ // Skip non-element nodes, skip tags, empty text
144
+ if (!tag) {
145
+ // Text node
146
+ const text = (el.textContent || '').trim();
147
+ if (!text) return null;
148
+ // Create a Text component for bare text
149
+ const id = generateId('Text', el, context.usedIds);
150
+ context.components.push({ id, component: 'Text', textContent: text, variant: 'body' });
151
+ return id;
152
+ }
153
+
154
+ if (SKIP_TAGS.has(tag)) return null;
155
+
156
+ // ── Resolve A2UI type ──
157
+
158
+ let a2uiType = null;
159
+ let defaultProps = {};
160
+ let isUnknown = false;
161
+
162
+ // 1. Already an A2UI component (*-n tag)?
163
+ if (reverseRegistry.has(tag)) {
164
+ a2uiType = reverseRegistry.get(tag);
165
+ }
166
+ // 2. ARIA role?
167
+ else if (el.getAttribute('role') && ARIA_ROLE_MAP[el.getAttribute('role')]) {
168
+ a2uiType = ARIA_ROLE_MAP[el.getAttribute('role')];
169
+ }
170
+ // 3. Input element? Use type sub-map
171
+ else if (tag === 'input') {
172
+ const inputType = el.getAttribute('type') || 'text';
173
+ const mapped = INPUT_TYPE_MAP[inputType];
174
+ if (mapped) {
175
+ a2uiType = mapped.type;
176
+ defaultProps = { ...mapped.props };
177
+ } else {
178
+ a2uiType = 'TextField';
179
+ }
180
+ // Hidden inputs are skipped
181
+ if (inputType === 'hidden') return null;
182
+ }
183
+ // 4. HTML tag map?
184
+ else if (HTML_TAG_MAP[tag]) {
185
+ a2uiType = HTML_TAG_MAP[tag].type;
186
+ defaultProps = { ...HTML_TAG_MAP[tag].props };
187
+ }
188
+ // 5. div/span → Layer 2 structure inference
189
+ else if (tag === 'div' || tag === 'span') {
190
+ const inferred = inferStructure(el);
191
+ a2uiType = inferred.type;
192
+ defaultProps = inferred.props || {};
193
+ }
194
+ // 6. Unknown → Column (container), NEVER silently (gh#848)
195
+ else {
196
+ a2uiType = 'Column';
197
+ isUnknown = true;
198
+ }
199
+
200
+ // ── Generate ID ──
201
+ const id = context.isRoot ? 'root' : generateId(a2uiType, el, context.usedIds);
202
+ context.isRoot = false;
203
+
204
+ // ── Extract props ──
205
+ const extractedProps = extractProps(el, a2uiType);
206
+ const props = { ...defaultProps, ...extractedProps };
207
+
208
+ // ── Unknown tag: report always, degrade visibly on demand (gh#848) ──
209
+ // The old behavior was a bare Column with the element's props dropped and
210
+ // NOTHING said anywhere — a childless custom element (the common case for
211
+ // a data-driven composite like `<admin-roster-ui>`) became a literally
212
+ // empty box, and 24 site pages shipped blank sections for four days.
213
+ // Reporting is unconditional; the visible placeholder is opt-in because
214
+ // the corpus harvester and the zettel chunk-synthesizer feed REUSABLE
215
+ // composition templates, where an injected developer notice would travel
216
+ // into generated product UI.
217
+ if (isUnknown && el.tagName) {
218
+ context.unmappedTags?.add(tag);
219
+ if (context.unknownTagPolicy === 'placeholder') {
220
+ return emitUnmappedPlaceholder(el, id, tag, props, context);
221
+ }
222
+ }
223
+
224
+ // ── Handle children ──
225
+
226
+ // ChoicePicker / Select, Combobox, Command carry their <option>/<optgroup>
227
+ // children as an `options` prop (built in extractProps), not as child
228
+ // components. Recursing the <option>s would emit Text nodes that
229
+ // <select-ui>/<combobox-ui>/<command-ui> reject — renders empty +
230
+ // "ignoring unrecognized child" warning (FB-79; extended to Combobox per
231
+ // gh#860, to Command per gh#887). Treat as a self-contained leaf.
232
+ if (a2uiType === 'ChoicePicker' || a2uiType === 'Combobox' || a2uiType === 'Command') {
233
+ context.components.push({ id, component: a2uiType, ...props });
234
+ return id;
235
+ }
236
+
237
+ // Leaf types: these render their own text, don't recurse into children.
238
+ // Tag joined 2026-07-12 (TKT-0004): tag-ui renders its label via CSS
239
+ // attr(text) — as a container, its light-DOM label became a child Text
240
+ // at body size, inflating every converted chip site-wide (581 instances
241
+ // across 233 pages measured).
242
+ const LEAF_TYPES = new Set([
243
+ 'Text', 'Button', 'Kbd', 'Code', 'Badge', 'Tag', 'Image', 'Divider',
244
+ 'TextField', 'CheckBox', 'Toggle', 'Radio', 'Slider', 'Search',
245
+ 'Upload', 'ColorPicker', 'DateTimeInput', 'TextArea', 'OtpInput',
246
+ ]);
247
+
248
+ if (LEAF_TYPES.has(a2uiType)) {
249
+ // Leaf types still need to carry slotted or container children in two
250
+ // shapes:
251
+ //
252
+ // (1) Trailing icons / kbd shortcuts inside Button:
253
+ // `<button-ui text="Next"><icon-ui slot="trailing" name="caret-right">`
254
+ // (2) Slotted container wrappers that themselves transpile to a leaf
255
+ // type but hold inline child elements:
256
+ // `<span slot="heading"><text-ui strong>Title</text-ui> <badge-ui>...`
257
+ // The span maps to Text via HTML_TAG_MAP, but it functions as a
258
+ // flex container (per card.css `> [slot="heading"]`) and needs
259
+ // to keep its children.
260
+ //
261
+ // Rule: if the element has ANY element children, recurse all of them.
262
+ // Pure text leaves (no element children) keep the text-prop path.
263
+ const elementChildren = (el.children || []).filter(c => c.tagName);
264
+ if (elementChildren.length === 0) {
265
+ context.components.push({ id, component: a2uiType, ...props });
266
+ return id;
267
+ }
268
+ // When a leaf has element children, the auto-extracted textContent prop
269
+ // would be the CONCATENATED descendant text (e.g.
270
+ // <text-ui>$49<text-ui>/month</text-ui></text-ui>
271
+ // has textContent "$49/month"), causing every label to render twice
272
+ // alongside the child components. Replace it with the element's DIRECT
273
+ // text nodes only — text the element OWNS, not what its children own.
274
+ // The minimal HTML parser represents text nodes as pseudo-elements
275
+ // with tagName === '' inside the children array.
276
+ const directText = (el.children || [])
277
+ .filter(n => !n.tagName)
278
+ .map(n => n.textContent || '')
279
+ .join(' ').replace(/\s+/g, ' ').trim();
280
+ const containerProps = { ...props };
281
+ if (directText) {
282
+ // Preserve the direct text on whichever prop the type uses
283
+ if (a2uiType === 'Text' || a2uiType === 'Kbd' || a2uiType === 'Code') containerProps.textContent = directText;
284
+ else if (a2uiType === 'Button' || a2uiType === 'Badge' || a2uiType === 'Tag') {
285
+ if (containerProps.text === undefined) containerProps.text = directText;
286
+ }
287
+ } else {
288
+ delete containerProps.textContent;
289
+ delete containerProps.text;
290
+ }
291
+ const childIds = [];
292
+ for (const child of elementChildren) {
293
+ const childId = walkNode(child, context);
294
+ if (childId) childIds.push(childId);
295
+ }
296
+ context.components.push({ id, component: a2uiType, ...containerProps, children: childIds });
297
+ return id;
298
+ }
299
+
300
+ const children = el.children || [];
301
+ const childElements = children.filter(c => c.tagName || (c.textContent || '').trim());
302
+
303
+ // Container with no children
304
+ if (childElements.length === 0) {
305
+ context.components.push({ id, component: a2uiType, ...props });
306
+ return id;
307
+ }
308
+
309
+ const childIds = [];
310
+ for (const child of childElements) {
311
+ const childId = walkNode(child, context);
312
+ if (childId) childIds.push(childId);
313
+ }
314
+
315
+ // Flatten: single-child container chains (div > div > div > content → just content)
316
+ if (childIds.length === 1 && ['Column', 'Row'].includes(a2uiType) && !Object.keys(props).length) {
317
+ // This container adds nothing — return the child directly
318
+ return childIds[0];
319
+ }
320
+
321
+ // ── Card content model enforcement ──
322
+ // Card must follow: Card > Header + Section > Column + Footer
323
+ if (a2uiType === 'Card') {
324
+ return buildCardStructure(id, childIds, props, context);
325
+ }
326
+
327
+ const component = { id, component: a2uiType, ...props };
328
+ if (childIds.length > 0) component.children = childIds;
329
+ context.components.push(component);
330
+
331
+ return id;
332
+ }
333
+
334
+ /**
335
+ * gh#848 — the VISIBLE degrade for an unregistered component tag.
336
+ *
337
+ * Emits `Column[data-unmapped-tag] > Alert(warning) + <the element's own
338
+ * converted children>`. Three properties this shape must keep:
339
+ * - it is built from REGISTERED constructs only (Column, Alert), so the
340
+ * wire payload stays valid — validator.js's `allTypesRegistered` check
341
+ * passes and the renderer resolves both through `resolveTag`;
342
+ * - it carries NO raw CSS: alert-ui's `warning` variant is entirely
343
+ * semantic-token driven (alert.css), so the notice themes with the page;
344
+ * - it preserves the element's children, so nothing authored is lost —
345
+ * only the unmapped element's OWN props are (they always were).
346
+ *
347
+ * `data-unmapped-tag` is the machine-readable half: a build gate or a
348
+ * browser probe can select `[data-unmapped-tag]` without parsing prose.
349
+ */
350
+ function emitUnmappedPlaceholder(el, id, tag, props, context) {
351
+ const noticeId = generateId('Alert', el, context.usedIds);
352
+
353
+ const childIds = [];
354
+ for (const child of (el.children || [])) {
355
+ if (!child.tagName && !(child.textContent || '').trim()) continue;
356
+ const childId = walkNode(child, context);
357
+ if (childId) childIds.push(childId);
358
+ }
359
+
360
+ context.components.push({
361
+ id,
362
+ component: 'Column',
363
+ gap: 'sm',
364
+ 'data-unmapped-tag': tag,
365
+ ...props,
366
+ children: [noticeId, ...childIds],
367
+ });
368
+ context.components.push({
369
+ id: noticeId,
370
+ component: 'Alert',
371
+ variant: 'warning',
372
+ icon: 'warning',
373
+ // title + description is alert.yaml's documented two-line shape
374
+ // ("headline + body"); `text` is the single-line alternative, not a
375
+ // companion to `title`.
376
+ title: `Unmapped component: <${tag}>`,
377
+ description: `No entry in the A2UI runtime registry (packages/a2ui/registry.js), so this element's own props and behaviour were not converted. Register the tag, or remove it from the source.`,
378
+ });
379
+
380
+ return id;
381
+ }
382
+
383
+ /**
384
+ * Build a proper Card structure: Card > Header + Section > Column + Footer.
385
+ * Classifies child components into header (headings), footer (buttons), and
386
+ * body (everything else wrapped in Section > Column).
387
+ *
388
+ * @param {string} cardId
389
+ * @param {string[]} childIds — IDs of already-created child components
390
+ * @param {Record<string, unknown>} cardProps
391
+ * @param {object} context
392
+ * @returns {string} — The card's ID
393
+ */
394
+ function buildCardStructure(cardId, childIds, cardProps, context) {
395
+ const headerIds = [];
396
+ const footerIds = [];
397
+ const bodyIds = [];
398
+
399
+ for (const childId of childIds) {
400
+ const child = context.components.find(c => c.id === childId);
401
+ if (!child) { bodyIds.push(childId); continue; }
402
+
403
+ const type = child.component;
404
+
405
+ // Header and Footer pass through directly (already correct type)
406
+ if (type === 'Header') { headerIds.push(childId); continue; }
407
+ if (type === 'Footer') { footerIds.push(childId); continue; }
408
+
409
+ // Headings (Text h1-h5) go into a synthesized Header
410
+ if (type === 'Text' && child.variant && /^h[1-5]$/.test(child.variant)) {
411
+ headerIds.push(childId);
412
+ continue;
413
+ }
414
+
415
+ // Buttons go into a synthesized Footer
416
+ if (type === 'Button') { footerIds.push(childId); continue; }
417
+
418
+ // Section passes through (but needs Column wrapping check)
419
+ if (type === 'Section') { bodyIds.push(childId); continue; }
420
+
421
+ // Everything else → body
422
+ bodyIds.push(childId);
423
+ }
424
+
425
+ const cardChildren = [];
426
+
427
+ // ── Build Header ──
428
+ if (headerIds.length > 0) {
429
+ // Check if there's already a Header component among them
430
+ const existingHeader = headerIds.find(id => context.components.find(c => c.id === id)?.component === 'Header');
431
+ if (existingHeader) {
432
+ cardChildren.push(existingHeader);
433
+ // Add any remaining header items as body instead
434
+ for (const hid of headerIds) {
435
+ if (hid !== existingHeader) bodyIds.unshift(hid);
436
+ }
437
+ } else {
438
+ // Wrap headings in a new Header — ensure data-heading is set
439
+ for (const hid of headerIds) {
440
+ const child = context.components.find(c => c.id === hid);
441
+ if (child && child.variant && /^h[1-5]$/.test(child.variant) && !child['data-heading']) {
442
+ child['data-heading'] = 'section';
443
+ }
444
+ }
445
+ const hdrId = generateId('Header', null, context.usedIds);
446
+ context.components.push({ id: hdrId, component: 'Header', children: headerIds });
447
+ cardChildren.push(hdrId);
448
+ }
449
+ }
450
+
451
+ // ── Build Section > Column for body content ──
452
+ if (bodyIds.length > 0) {
453
+ // Check if there's already a Section among them
454
+ const existingSection = bodyIds.find(id => context.components.find(c => c.id === id)?.component === 'Section');
455
+ if (existingSection && bodyIds.length === 1) {
456
+ // Single Section — ensure it has a Column wrapper
457
+ ensureSectionColumn(existingSection, context);
458
+ cardChildren.push(existingSection);
459
+ } else {
460
+ // Wrap body in Section > Column
461
+ const colId = generateId('Column', null, context.usedIds);
462
+ context.components.push({ id: colId, component: 'Column', children: bodyIds });
463
+
464
+ const secId = generateId('Section', null, context.usedIds);
465
+ context.components.push({ id: secId, component: 'Section', children: [colId] });
466
+ cardChildren.push(secId);
467
+ }
468
+ }
469
+
470
+ // ── Build Footer ──
471
+ if (footerIds.length > 0) {
472
+ // Check if there's already a Footer component among them
473
+ const existingFooter = footerIds.find(id => context.components.find(c => c.id === id)?.component === 'Footer');
474
+ if (existingFooter) {
475
+ cardChildren.push(existingFooter);
476
+ } else {
477
+ const ftrId = generateId('Footer', null, context.usedIds);
478
+ context.components.push({ id: ftrId, component: 'Footer', children: footerIds });
479
+ cardChildren.push(ftrId);
480
+ }
481
+ }
482
+
483
+ const card = { id: cardId, component: 'Card', ...cardProps };
484
+ if (cardChildren.length > 0) card.children = cardChildren;
485
+ context.components.push(card);
486
+ return cardId;
487
+ }
488
+
489
+ /**
490
+ * Ensure a Section component has a Column wrapper for its children.
491
+ * If the Section's children are not wrapped in a Column, insert one.
492
+ */
493
+ function ensureSectionColumn(sectionId, context) {
494
+ const section = context.components.find(c => c.id === sectionId);
495
+ if (!section || !section.children?.length) return;
496
+
497
+ // Already has a Column as direct child — good
498
+ const firstChild = context.components.find(c => c.id === section.children[0]);
499
+ if (firstChild?.component === 'Column') return;
500
+
501
+ // Wrap all children in a Column
502
+ const colId = generateId('Column', null, context.usedIds);
503
+ context.components.push({ id: colId, component: 'Column', children: [...section.children] });
504
+ section.children = [colId];
505
+ }
506
+
507
+ // ═══════════════════════════════════════════════════════════════
508
+ // LAYER 2: STRUCTURE INFERENCE
509
+ // ═══════════════════════════════════════════════════════════════
510
+
511
+ /**
512
+ * Infer the A2UI type for a div/span based on its structure and inline styles.
513
+ *
514
+ * @param {MinimalElement} el
515
+ * @returns {{ type: string, props?: Record<string, unknown> }}
516
+ */
517
+ function inferStructure(el) {
518
+ const style = parseInlineStyle(el.getAttribute('style') || '');
519
+ const display = style.get('display') || '';
520
+ const flexDir = style.get('flex-direction') || '';
521
+ const flexWrap = style.get('flex-wrap') || '';
522
+ const tag = (el.tagName || '').toLowerCase();
523
+
524
+ // Grid
525
+ if (display.includes('grid')) {
526
+ const cols = style.get('grid-template-columns') || '';
527
+ const colCount = cols ? cols.split(/\s+/).filter(s => s && s !== 'auto').length : 0;
528
+ return { type: 'Grid', props: colCount > 0 ? { columns: String(colCount) } : {} };
529
+ }
530
+
531
+ // Flex row
532
+ if (display.includes('flex') && (flexDir === 'row' || flexDir === '' || !flexDir)) {
533
+ const gap = style.get('gap') || style.get('column-gap') || '';
534
+ const props = {};
535
+ if (gap) props.gap = inferGap(gap);
536
+ if (flexWrap === 'wrap') props.wrap = true;
537
+ return { type: 'Row', props };
538
+ }
539
+
540
+ // Flex column
541
+ if (display.includes('flex') && flexDir === 'column') {
542
+ const gap = style.get('gap') || style.get('row-gap') || '';
543
+ const props = {};
544
+ if (gap) props.gap = inferGap(gap);
545
+ return { type: 'Column', props };
546
+ }
547
+
548
+ // Has header + footer children → Card
549
+ const children = el.children || [];
550
+ const childTags = children.map(c => (c.tagName || '').toLowerCase());
551
+ if (childTags.includes('header') && childTags.includes('footer')) {
552
+ return { type: 'Card' };
553
+ }
554
+ if (childTags.includes('header') || childTags.some(t => /^h[1-6]$/.test(t))) {
555
+ return { type: 'Card' };
556
+ }
557
+
558
+ // Text-only div
559
+ const hasElementChildren = children.some(c => c.tagName);
560
+ if (!hasElementChildren) {
561
+ const text = (el.textContent || '').trim();
562
+ if (text) return { type: 'Text', props: { variant: 'body' } };
563
+ }
564
+
565
+ // Span defaults to Text
566
+ if (tag === 'span') {
567
+ return { type: 'Text', props: { variant: 'body' } };
568
+ }
569
+
570
+ // Default div → Column
571
+ return { type: 'Column' };
572
+ }
573
+
574
+ // ═══════════════════════════════════════════════════════════════
575
+ // LAYER 3: LLM REASONING
576
+ // ═══════════════════════════════════════════════════════════════
577
+
578
+ /**
579
+ * Run the LLM reasoning pass on HTML that scored low in instant mode.
580
+ *
581
+ * @param {string} html — Original HTML
582
+ * @param {object[]} instantMessages — Instant mode result
583
+ * @param {object} validation — Instant mode validation
584
+ * @param {object} llmAdapter — LLM adapter instance
585
+ * @returns {Promise<object[]|null>} — Improved A2UI messages, or null
586
+ */
587
+ async function reasoningPass(html, instantMessages, validation, llmAdapter) {
588
+ const context = getContext('convert html', 2);
589
+ const failedChecks = validation.checks?.filter(c => !c.passed) || [];
590
+
591
+ const systemPrompt = buildTranspilerPrompt(context);
592
+ const userPrompt = `Convert this HTML to A2UI flat adjacency components:\n\n${html}\n\n`
593
+ + (failedChecks.length > 0
594
+ ? `The instant conversion had these issues:\n${failedChecks.map(c => `- ${c.name}: ${c.detail}`).join('\n')}\n\nFix these issues in your output.`
595
+ : '');
596
+
597
+ const response = await llmAdapter.complete({
598
+ messages: [{ role: 'user', content: userPrompt }],
599
+ systemPrompt,
600
+ });
601
+
602
+ return parseA2UIResponse(response.content);
603
+ }
604
+
605
+ /**
606
+ * Build the LLM system prompt for HTML → A2UI conversion.
607
+ */
608
+ function buildTranspilerPrompt(context) {
609
+ const parts = [];
610
+
611
+ parts.push(`You are an A2UI transpiler. Convert HTML markup into A2UI flat adjacency components.
612
+ Output ONLY a valid JSON array. No markdown fences, no explanation.
613
+
614
+ Output: [{ "type": "updateComponents", "surfaceId": "default", "components": [...] }]
615
+ Each component: { "id": "<unique>", "component": "<A2UIType>", "children": ["<id>", ...], ...props }
616
+ Root must have id "root".
617
+
618
+ RULES:
619
+ - Use ONLY registered A2UI types (never raw HTML tags like div, span, input, form)
620
+ - Card anatomy: Card > Header + Section + Footer. Section > Column > content.
621
+ - Header children: use Text with variant h3/h4 for title (+ data-heading="section"), variant caption for subtitle. Card CSS auto-targets native heading/small — no slot="heading" needed. Only action buttons need slot="action".
622
+ - Text: variant h1-h6 for headings, body for paragraphs, caption for small text
623
+ - TextField for text inputs, ChoicePicker for selects, CheckBox for checkboxes
624
+ - Button: text prop for label, variant primary/outline/ghost/danger
625
+ - Preserve all text content, form field names, placeholders, image src/alt`);
626
+
627
+ if (context.components?.length > 0) {
628
+ const catalog = context.components.map(c => {
629
+ const props = Array.isArray(c.properties)
630
+ ? c.properties.map(p => typeof p === 'string' ? p : p.name).join(', ')
631
+ : '';
632
+ return `- ${c.type}: ${props || '(no props)'}`;
633
+ }).join('\n');
634
+ parts.push(`AVAILABLE TYPES:\n${catalog}`);
635
+ }
636
+
637
+ return parts.join('\n\n');
638
+ }
639
+
640
+ // ═══════════════════════════════════════════════════════════════
641
+ // HTML PARSER (lightweight, Node.js compatible)
642
+ // ═══════════════════════════════════════════════════════════════
643
+
644
+ /**
645
+ * @typedef {object} MinimalElement
646
+ * @property {string} tagName
647
+ * @property {Map<string, string>} attributes
648
+ * @property {MinimalElement[]} children
649
+ * @property {string} textContent
650
+ * @property {(name: string) => string|null} getAttribute
651
+ */
652
+
653
+ const SELF_CLOSING = new Set([
654
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
655
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
656
+ ]);
657
+
658
+ /**
659
+ * Parse an HTML string into a minimal element tree.
660
+ * Not a full HTML parser — handles common patterns for transpilation.
661
+ *
662
+ * @param {string} html
663
+ * @returns {MinimalElement}
664
+ */
665
+ export function parseHTMLMinimal(html) {
666
+ // Strip doctype, comments
667
+ let src = html.replace(/<!DOCTYPE[^>]*>/gi, '').replace(/<!--[\s\S]*?-->/g, '').trim();
668
+
669
+ // If wrapped in <html>, extract <body> content
670
+ const bodyMatch = src.match(/<body[^>]*>([\s\S]*)<\/body>/i);
671
+ if (bodyMatch) src = bodyMatch[1].trim();
672
+
673
+ // Parse into tokens
674
+ const tokens = tokenize(src);
675
+
676
+ // Build tree
677
+ const root = { tagName: '', attributes: new Map(), children: [], textContent: '' };
678
+ root.getAttribute = (name) => root.attributes.get(name) ?? null;
679
+
680
+ const stack = [root];
681
+
682
+ for (const token of tokens) {
683
+ const parent = stack[stack.length - 1];
684
+
685
+ if (token.type === 'text') {
686
+ const text = token.value.trim();
687
+ if (text) {
688
+ parent.children.push({
689
+ tagName: '',
690
+ attributes: new Map(),
691
+ children: [],
692
+ textContent: text,
693
+ getAttribute: () => null,
694
+ });
695
+ }
696
+ } else if (token.type === 'open') {
697
+ const el = {
698
+ tagName: token.tag,
699
+ attributes: token.attrs,
700
+ children: [],
701
+ textContent: '',
702
+ getAttribute(name) { return this.attributes.get(name) ?? null; },
703
+ };
704
+ parent.children.push(el);
705
+
706
+ if (!SELF_CLOSING.has(token.tag) && !token.selfClose) {
707
+ stack.push(el);
708
+ }
709
+ } else if (token.type === 'close') {
710
+ // Pop stack until we find the matching open tag
711
+ for (let i = stack.length - 1; i > 0; i--) {
712
+ if (stack[i].tagName === token.tag) {
713
+ // Compute textContent for the closed element
714
+ stack[i].textContent = getTextContent(stack[i]);
715
+ stack.length = i;
716
+ break;
717
+ }
718
+ }
719
+ }
720
+ }
721
+
722
+ // Compute textContent for root
723
+ root.textContent = getTextContent(root);
724
+
725
+ // If root has exactly one child, return it directly
726
+ if (root.children.length === 1 && root.children[0].tagName) {
727
+ return root.children[0];
728
+ }
729
+
730
+ return root;
731
+ }
732
+
733
+ /**
734
+ * Tokenize HTML into open/close/text tokens.
735
+ */
736
+ function tokenize(html) {
737
+ const tokens = [];
738
+ let i = 0;
739
+
740
+ while (i < html.length) {
741
+ if (html[i] === '<') {
742
+ // Closing tag
743
+ if (html[i + 1] === '/') {
744
+ const end = html.indexOf('>', i);
745
+ if (end === -1) break;
746
+ const tag = html.slice(i + 2, end).trim().toLowerCase();
747
+ tokens.push({ type: 'close', tag });
748
+ i = end + 1;
749
+ } else {
750
+ // Opening tag
751
+ const end = html.indexOf('>', i);
752
+ if (end === -1) break;
753
+ const content = html.slice(i + 1, end);
754
+ const selfClose = content.endsWith('/');
755
+ const clean = selfClose ? content.slice(0, -1).trim() : content.trim();
756
+
757
+ // Parse tag name and attributes
758
+ const spaceIdx = clean.search(/[\s/]/);
759
+ const tag = (spaceIdx === -1 ? clean : clean.slice(0, spaceIdx)).toLowerCase();
760
+ const attrStr = spaceIdx === -1 ? '' : clean.slice(spaceIdx).trim();
761
+ const attrs = parseAttributes(attrStr);
762
+
763
+ tokens.push({ type: 'open', tag, attrs, selfClose });
764
+ i = end + 1;
765
+ }
766
+ } else {
767
+ // Text content
768
+ const nextTag = html.indexOf('<', i);
769
+ const text = nextTag === -1 ? html.slice(i) : html.slice(i, nextTag);
770
+ if (text) tokens.push({ type: 'text', value: text });
771
+ i = nextTag === -1 ? html.length : nextTag;
772
+ }
773
+ }
774
+
775
+ return tokens;
776
+ }
777
+
778
+ /**
779
+ * Parse HTML attribute string into a Map.
780
+ */
781
+ function parseAttributes(str) {
782
+ const attrs = new Map();
783
+ if (!str) return attrs;
784
+
785
+ const re = /([a-zA-Z_:][\w:.-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
786
+ let match;
787
+ while ((match = re.exec(str))) {
788
+ const name = match[1].toLowerCase();
789
+ const value = match[2] ?? match[3] ?? match[4] ?? '';
790
+ attrs.set(name, value);
791
+ }
792
+
793
+ return attrs;
794
+ }
795
+
796
+ /**
797
+ * Recursively get text content from a minimal element.
798
+ */
799
+ function getTextContent(el) {
800
+ if (!el.children || el.children.length === 0) {
801
+ return el.textContent || '';
802
+ }
803
+ return el.children.map(c => c.textContent || getTextContent(c)).join(' ').replace(/\s+/g, ' ').trim();
804
+ }
805
+
806
+ // ═══════════════════════════════════════════════════════════════
807
+ // HELPERS
808
+ // ═══════════════════════════════════════════════════════════════
809
+
810
+ /**
811
+ * Parse an inline style attribute into a Map.
812
+ */
813
+ function parseInlineStyle(style) {
814
+ const map = new Map();
815
+ if (!style) return map;
816
+ for (const decl of style.split(';')) {
817
+ const colon = decl.indexOf(':');
818
+ if (colon === -1) continue;
819
+ const prop = decl.slice(0, colon).trim().toLowerCase();
820
+ const value = decl.slice(colon + 1).trim();
821
+ if (prop && value) map.set(prop, value);
822
+ }
823
+ return map;
824
+ }
825
+
826
+ /**
827
+ * Generate a unique, semantic ID for an A2UI component.
828
+ */
829
+ function generateId(type, el, usedIds) {
830
+ // Try semantic hints
831
+ const candidates = [
832
+ el?.getAttribute?.('id'),
833
+ el?.getAttribute?.('name'),
834
+ el?.getAttribute?.('aria-label'),
835
+ ].filter(Boolean);
836
+
837
+ for (const hint of candidates) {
838
+ const id = slugify(hint);
839
+ if (id && !usedIds.has(id)) {
840
+ usedIds.add(id);
841
+ return id;
842
+ }
843
+ }
844
+
845
+ // Fallback: type-based with counter
846
+ const base = slugify(type);
847
+ let id = base;
848
+ let counter = 1;
849
+ while (usedIds.has(id)) {
850
+ id = `${base}-${++counter}`;
851
+ }
852
+ usedIds.add(id);
853
+ return id;
854
+ }
855
+
856
+ /**
857
+ * Convert a string to a kebab-case slug suitable for IDs.
858
+ */
859
+ function slugify(str) {
860
+ if (!str) return '';
861
+ return str
862
+ .replace(/([a-z])([A-Z])/g, '$1-$2')
863
+ .toLowerCase()
864
+ .replace(/[^a-z0-9]+/g, '-')
865
+ .replace(/^-|-$/g, '')
866
+ .slice(0, 30);
867
+ }
868
+
869
+ /**
870
+ * Create a fallback A2UI message array with wired retry button.
871
+ * @returns {object[]} Array of A2UI messages (updateComponents + wireComponents)
872
+ */
873
+ function fallbackMessage(reason) {
874
+ const updateMsg = {
875
+ type: 'updateComponents',
876
+ surfaceId: 'default',
877
+ components: [
878
+ { id: 'root', component: 'Card', children: ['sec', 'ftr'] },
879
+ { id: 'sec', component: 'Section', children: ['alert'] },
880
+ { id: 'alert', component: 'Alert', variant: 'error', title: 'Conversion Error', description: reason },
881
+ { id: 'ftr', component: 'Footer', children: ['retry'] },
882
+ { id: 'retry', component: 'Button', text: 'Try Again', icon: 'arrow-clockwise', variant: 'primary' },
883
+ ],
884
+ };
885
+
886
+ const wireMsg = {
887
+ type: 'wireComponents',
888
+ surfaceId: 'default',
889
+ actions: [
890
+ {
891
+ event: { event: 'press', target: 'retry' },
892
+ handler: 'emit-event',
893
+ config: { eventName: 'a2ui-retry', detail: { source: 'transpiler' } },
894
+ },
895
+ ],
896
+ };
897
+
898
+ return [updateMsg, wireMsg];
899
+ }
900
+
901
+ /**
902
+ * Parse an LLM response string into A2UI messages (reused from generator pattern).
903
+ */
904
+ function parseA2UIResponse(content) {
905
+ if (!content || typeof content !== 'string') return null;
906
+
907
+ let json = content.trim();
908
+ const fenceMatch = json.match(/```(?:json)?\s*\n?([\s\S]*?)```/);
909
+ if (fenceMatch) json = fenceMatch[1].trim();
910
+
911
+ try {
912
+ const parsed = JSON.parse(json);
913
+ if (Array.isArray(parsed)) {
914
+ if (parsed[0]?.type === 'updateComponents') return parsed;
915
+ if (parsed[0]?.id && parsed[0]?.component) {
916
+ return [{ type: 'updateComponents', surfaceId: 'default', components: parsed }];
917
+ }
918
+ }
919
+ if (parsed?.type === 'updateComponents') return [parsed];
920
+ return null;
921
+ } catch {
922
+ return null;
923
+ }
924
+ }
925
+
926
+ /**
927
+ * Generate suggestions based on the transpilation result.
928
+ */
929
+ function generateSuggestions(html, components, validation) {
930
+ const suggestions = [];
931
+ const types = new Set(components.map(c => c.component));
932
+
933
+ // Check for raw HTML that couldn't be converted
934
+ if (html.includes('<svg')) {
935
+ suggestions.push('SVG elements were skipped — use icon-ui for icons or image-ui for illustrations');
936
+ }
937
+
938
+ if (!types.has('Card') && components.length > 3) {
939
+ suggestions.push('Consider wrapping in a Card for proper container semantics');
940
+ }
941
+
942
+ if (types.has('Section') && !components.some(c => c.component === 'Section' && c.children?.some(id => {
943
+ const child = components.find(x => x.id === id);
944
+ return child?.component === 'Column';
945
+ }))) {
946
+ suggestions.push('Section content should be wrapped in Column');
947
+ }
948
+
949
+ if (html.includes('style=') && (html.includes('#') || html.includes('rgb'))) {
950
+ suggestions.push('Source HTML contained hardcoded colors — A2UI output uses design tokens');
951
+ }
952
+
953
+ if (validation.score < 85) {
954
+ suggestions.push('Try "reasoning" mode for better structure inference');
955
+ }
956
+
957
+ if (components.some(c => ['TextField', 'CheckBox', 'Radio'].includes(c.component) && !c.label)) {
958
+ suggestions.push('Add label prop to form fields for accessibility');
959
+ }
960
+
961
+ return suggestions.slice(0, 4);
962
+ }