@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.
- package/CHANGELOG.md +26 -0
- package/README.md +77 -0
- package/compose/CHANGELOG.md +1707 -0
- package/compose/README.md +215 -0
- package/compose/core/artifacts.js +262 -0
- package/compose/core/context-store.js +218 -0
- package/compose/core/generator.d.ts +4 -0
- package/compose/core/generator.js +602 -0
- package/compose/core/pipeline/engine.js +289 -0
- package/compose/core/pipeline/types.js +91 -0
- package/compose/core/plan-primitives.js +120 -0
- package/compose/core/plan-turn.js +145 -0
- package/compose/core/reference.js +150 -0
- package/compose/core/state.js +15 -0
- package/compose/evals/harness.d.ts +4 -0
- package/compose/evals/harness.mjs +193 -0
- package/compose/index.d.ts +4 -0
- package/compose/index.js +16 -0
- package/compose/shared/validate-and-repair.js +122 -0
- package/compose/strategies/_shared/chunk-loader.js +282 -0
- package/compose/strategies/cascade.d.ts +67 -0
- package/compose/strategies/cascade.js +165 -0
- package/compose/strategies/escalation.d.ts +13 -0
- package/compose/strategies/escalation.js +132 -0
- package/compose/strategies/free-form-composer/index.js +254 -0
- package/compose/strategies/free-form-composer/system-prompt.js +205 -0
- package/compose/strategies/free-form-composer/transpile.js +279 -0
- package/compose/strategies/monolithic/_shared.js +1619 -0
- package/compose/strategies/monolithic/generate-instant.js +228 -0
- package/compose/strategies/monolithic/generate-pro.js +490 -0
- package/compose/strategies/monolithic/generate-thinking.js +206 -0
- package/compose/strategies/registry.d.ts +20 -0
- package/compose/strategies/registry.js +495 -0
- package/compose/strategies/zettel/_smoke.js +27 -0
- package/compose/strategies/zettel/chunk-composer.js +214 -0
- package/compose/strategies/zettel/chunk-refiner.js +582 -0
- package/compose/strategies/zettel/chunk-synthesizer.js +458 -0
- package/compose/strategies/zettel/composer.js +78 -0
- package/compose/strategies/zettel/composition-library.js +418 -0
- package/compose/strategies/zettel/generate.js +15 -0
- package/compose/strategies/zettel/generator-adapter.d.ts +4 -0
- package/compose/strategies/zettel/generator-adapter.js +242 -0
- package/compose/strategies/zettel/issue-reporter.js +607 -0
- package/compose/strategies/zettel/session-store.js +121 -0
- package/compose/strategies/zettel/state-cache.js +158 -0
- package/compose/transpiler/transpiler-maps.js +675 -0
- package/compose/transpiler/transpiler.d.ts +4 -0
- package/compose/transpiler/transpiler.js +962 -0
- package/corpus/CHANGELOG.md +1957 -0
- package/corpus/README.md +279 -0
- package/corpus/catalog-a2ui_0_9.d.ts +4 -0
- package/corpus/catalog-a2ui_0_9.json +29571 -0
- package/corpus/catalog-a2ui_0_9_rules.txt +1215 -0
- package/corpus/chunk-embeddings.d.ts +4 -0
- package/corpus/chunk-embeddings.json +1 -0
- package/corpus/chunks/_index.json +16658 -0
- package/corpus/chunks/_source-hashes.json +571 -0
- package/corpus/chunks/access-denied-with-request.json +124 -0
- package/corpus/chunks/access-request-form.json +166 -0
- package/corpus/chunks/access-request-pending.json +133 -0
- package/corpus/chunks/accordion-settings.json +176 -0
- package/corpus/chunks/achievement-badges.json +413 -0
- package/corpus/chunks/achievement-unlock.json +104 -0
- package/corpus/chunks/action-list-record-actions.json +84 -0
- package/corpus/chunks/activity-feed.json +390 -0
- package/corpus/chunks/admin-page-header-flush.json +83 -0
- package/corpus/chunks/admin-page-header-title-description.json +80 -0
- package/corpus/chunks/admin-page-header-with-actions.json +107 -0
- package/corpus/chunks/admin-page-header-with-tabs.json +144 -0
- package/corpus/chunks/admin-shell-rounded-borderless.json +287 -0
- package/corpus/chunks/admin-shell-standard.json +310 -0
- package/corpus/chunks/admin-sidebar-brand-signin.json +158 -0
- package/corpus/chunks/admin-sidebar-brand-version.json +130 -0
- package/corpus/chunks/admin-sidebar-flat-nav.json +131 -0
- package/corpus/chunks/admin-sidebar-nav-dividers.json +150 -0
- package/corpus/chunks/admin-sidebar-nav-groups.json +171 -0
- package/corpus/chunks/admin-sidebar-workspace-switcher.json +139 -0
- package/corpus/chunks/admin-topbar-breadcrumb-actions.json +130 -0
- package/corpus/chunks/admin-topbar-breadcrumb-minimal.json +79 -0
- package/corpus/chunks/admin-topbar-deep-breadcrumb.json +129 -0
- package/corpus/chunks/admin-topbar-search.json +104 -0
- package/corpus/chunks/admin-topbar-status-indicator.json +122 -0
- package/corpus/chunks/admin-topbar-trailing-toggle.json +114 -0
- package/corpus/chunks/agent-activity-feed.json +163 -0
- package/corpus/chunks/agent-artifact-stub-card.json +115 -0
- package/corpus/chunks/agent-canvas-shell.json +305 -0
- package/corpus/chunks/agent-citations-source-chips.json +82 -0
- package/corpus/chunks/agent-command-palette-search.json +120 -0
- package/corpus/chunks/agent-cost-detailed.json +260 -0
- package/corpus/chunks/agent-cost-inline.json +78 -0
- package/corpus/chunks/agent-memory-detail.json +181 -0
- package/corpus/chunks/agent-memory-editor.json +173 -0
- package/corpus/chunks/agent-memory-list.json +207 -0
- package/corpus/chunks/agent-pipeline-status-stages.json +61 -0
- package/corpus/chunks/agent-prompt-card.json +179 -0
- package/corpus/chunks/agent-prompt-library-grid.json +341 -0
- package/corpus/chunks/agent-prompt-picker.json +282 -0
- package/corpus/chunks/agent-rate-limit.json +100 -0
- package/corpus/chunks/agent-reasoning-collapsed.json +128 -0
- package/corpus/chunks/agent-tool-call-accordion.json +109 -0
- package/corpus/chunks/agent-tool-call-collapsed.json +98 -0
- package/corpus/chunks/agent-tool-call-error.json +112 -0
- package/corpus/chunks/agent-tool-call-result.json +145 -0
- package/corpus/chunks/agent-tool-call-running.json +85 -0
- package/corpus/chunks/agent-tool-call-success.json +137 -0
- package/corpus/chunks/agent-trace-timeline.json +244 -0
- package/corpus/chunks/ai-streaming-response.json +147 -0
- package/corpus/chunks/alert-banner-stack.json +78 -0
- package/corpus/chunks/alert-banner.json +55 -0
- package/corpus/chunks/alert-dialog-simple.json +72 -0
- package/corpus/chunks/alert-dialog-typed.json +107 -0
- package/corpus/chunks/alert-dialog-unsaved.json +81 -0
- package/corpus/chunks/analytics-chart.json +213 -0
- package/corpus/chunks/analytics-sparklines-dashboard.json +259 -0
- package/corpus/chunks/anchor-bar-selection-bar.json +226 -0
- package/corpus/chunks/api-key-table.json +148 -0
- package/corpus/chunks/api-keys-create.json +140 -0
- package/corpus/chunks/api-keys-list.json +271 -0
- package/corpus/chunks/app-nav-badges-counts.json +77 -0
- package/corpus/chunks/app-nav-badges-group-rollup-badges.json +100 -0
- package/corpus/chunks/app-nav-badges-plan-tiers.json +76 -0
- package/corpus/chunks/app-nav-badges-status-callouts.json +75 -0
- package/corpus/chunks/app-nav-full-example.json +195 -0
- package/corpus/chunks/app-nav-groups.json +103 -0
- package/corpus/chunks/app-nav-items.json +74 -0
- package/corpus/chunks/app-nav-labels-and-dividers.json +92 -0
- package/corpus/chunks/app-nav-spacer.json +77 -0
- package/corpus/chunks/app-product-listing-card.json +214 -0
- package/corpus/chunks/approvals-detail.json +240 -0
- package/corpus/chunks/approvals-queue.json +321 -0
- package/corpus/chunks/audit-log-basic.json +184 -0
- package/corpus/chunks/audit-log.json +174 -0
- package/corpus/chunks/auth-account-deleted-actions.json +51 -0
- package/corpus/chunks/auth-account-deleted.json +202 -0
- package/corpus/chunks/auth-card-content.json +260 -0
- package/corpus/chunks/auth-card-header.json +205 -0
- package/corpus/chunks/auth-email-entry.json +65 -0
- package/corpus/chunks/auth-email-verify-confirm.json +52 -0
- package/corpus/chunks/auth-email-verify.json +196 -0
- package/corpus/chunks/auth-forbidden-actions.json +59 -0
- package/corpus/chunks/auth-forbidden.json +203 -0
- package/corpus/chunks/auth-invite-actions.json +49 -0
- package/corpus/chunks/auth-invite-team-card.json +86 -0
- package/corpus/chunks/auth-link-expired-form.json +65 -0
- package/corpus/chunks/auth-link-expired.json +196 -0
- package/corpus/chunks/auth-locked-account.json +211 -0
- package/corpus/chunks/auth-locked-recovery-options.json +60 -0
- package/corpus/chunks/auth-mfa-enrollment-submit.json +54 -0
- package/corpus/chunks/auth-mfa-enrollment.json +284 -0
- package/corpus/chunks/auth-mfa-fallback-actions.json +52 -0
- package/corpus/chunks/auth-mfa-recovery.json +61 -0
- package/corpus/chunks/auth-new-password-form.json +75 -0
- package/corpus/chunks/auth-new-password.json +217 -0
- package/corpus/chunks/auth-oauth-fallback-actions.json +52 -0
- package/corpus/chunks/auth-oauth-interstitial.json +160 -0
- package/corpus/chunks/auth-password-challenge.json +94 -0
- package/corpus/chunks/auth-password-reset-form.json +64 -0
- package/corpus/chunks/auth-password-reset.json +195 -0
- package/corpus/chunks/auth-profile-form.json +72 -0
- package/corpus/chunks/auth-profile-setup.json +194 -0
- package/corpus/chunks/auth-reset-sent.json +188 -0
- package/corpus/chunks/auth-session-expired-actions.json +52 -0
- package/corpus/chunks/auth-session-expired.json +201 -0
- package/corpus/chunks/auth-signin-card-email.json +245 -0
- package/corpus/chunks/auth-signin-card-magic-link.json +147 -0
- package/corpus/chunks/auth-signin-card-mfa.json +231 -0
- package/corpus/chunks/auth-signin-card-otp.json +205 -0
- package/corpus/chunks/auth-signin-card-password.json +264 -0
- package/corpus/chunks/auth-signin-card-recovery.json +218 -0
- package/corpus/chunks/auth-signout-actions.json +51 -0
- package/corpus/chunks/auth-signout.json +184 -0
- package/corpus/chunks/auth-signup-email-entry.json +66 -0
- package/corpus/chunks/auth-signup-entry.json +281 -0
- package/corpus/chunks/auth-signup-social-auths.json +54 -0
- package/corpus/chunks/auth-signup-verify.json +227 -0
- package/corpus/chunks/auth-social-auths.json +62 -0
- package/corpus/chunks/auth-sso-providers.json +55 -0
- package/corpus/chunks/auth-sso-required.json +213 -0
- package/corpus/chunks/auth-team-invite.json +288 -0
- package/corpus/chunks/avatar-group-overflow.json +137 -0
- package/corpus/chunks/avatar-stack-overflow.json +124 -0
- package/corpus/chunks/banner-announcement-beta.json +99 -0
- package/corpus/chunks/banner-announcement-promo.json +102 -0
- package/corpus/chunks/banner-live-update.json +102 -0
- package/corpus/chunks/banner-system-maintenance.json +101 -0
- package/corpus/chunks/banner-system-offline.json +85 -0
- package/corpus/chunks/banner-system-update.json +102 -0
- package/corpus/chunks/bell-menu-empty.json +107 -0
- package/corpus/chunks/bell-menu-unread.json +395 -0
- package/corpus/chunks/billing-cancel-subscription-danger.json +61 -0
- package/corpus/chunks/billing-invoice-history-table.json +75 -0
- package/corpus/chunks/billing-overview.json +202 -0
- package/corpus/chunks/billing-payment-method-card-on-file.json +157 -0
- package/corpus/chunks/billing-progress-label.json +101 -0
- package/corpus/chunks/blog-post-card.json +212 -0
- package/corpus/chunks/breadcrumb-navigation.json +72 -0
- package/corpus/chunks/bulk-action-toolbar-floating.json +199 -0
- package/corpus/chunks/bulk-action-toolbar-swap.json +158 -0
- package/corpus/chunks/bulk-selection-actions-bar.json +162 -0
- package/corpus/chunks/business-address-form.json +953 -0
- package/corpus/chunks/calendar-month-view.json +74 -0
- package/corpus/chunks/card-grid-images.json +459 -0
- package/corpus/chunks/card-header-with-description.json +47 -0
- package/corpus/chunks/card-with-empty-state.json +96 -0
- package/corpus/chunks/card-with-form.json +143 -0
- package/corpus/chunks/card-with-sparkline.json +90 -0
- package/corpus/chunks/card-with-stat-kpi.json +86 -0
- package/corpus/chunks/changelog-feed-basic.json +470 -0
- package/corpus/chunks/chart-in-card-chart-plus-breakdown.json +188 -0
- package/corpus/chunks/chart-in-card-chart-plus-stat.json +89 -0
- package/corpus/chunks/chart-in-card-chart-with-filter-pills.json +124 -0
- package/corpus/chunks/chart-in-card-full-bleed-legend.json +116 -0
- package/corpus/chunks/chart-in-card-full-chart.json +107 -0
- package/corpus/chunks/chart-in-card-sparkline-tile.json +93 -0
- package/corpus/chunks/chart-in-card-stat-with-sparkline.json +73 -0
- package/corpus/chunks/chart-in-card-trend-footer.json +115 -0
- package/corpus/chunks/chart-with-filter-pills.json +169 -0
- package/corpus/chunks/chat-empty-state-hero.json +102 -0
- package/corpus/chunks/chat-message-assistant-byline.json +85 -0
- package/corpus/chunks/chat-message-user-byline.json +84 -0
- package/corpus/chunks/chat-page-shell.json +379 -0
- package/corpus/chunks/chat-streaming-surface.json +169 -0
- package/corpus/chunks/checkout-order-summary-card.json +140 -0
- package/corpus/chunks/code-snippet.json +107 -0
- package/corpus/chunks/col-form-stack.json +93 -0
- package/corpus/chunks/col-heading-body-action.json +79 -0
- package/corpus/chunks/col-stat-label.json +63 -0
- package/corpus/chunks/color-picker-swatches.json +153 -0
- package/corpus/chunks/command-palette.json +327 -0
- package/corpus/chunks/comments-composer.json +158 -0
- package/corpus/chunks/comments-mention-menu.json +237 -0
- package/corpus/chunks/comments-thread.json +344 -0
- package/corpus/chunks/commerce-pricing-tiers.json +401 -0
- package/corpus/chunks/comparison-table.json +246 -0
- package/corpus/chunks/contact-form-card.json +197 -0
- package/corpus/chunks/contact-form.json +157 -0
- package/corpus/chunks/contact-support-form.json +702 -0
- package/corpus/chunks/conversion-funnel-4-step-funnel.json +139 -0
- package/corpus/chunks/conversion-funnel-6step.json +151 -0
- package/corpus/chunks/conversion-funnel-wide-summary.json +192 -0
- package/corpus/chunks/conversion-funnel-with-segment-compare.json +202 -0
- package/corpus/chunks/cta-banner-centered.json +102 -0
- package/corpus/chunks/dashboard-acquisition-panel.json +212 -0
- package/corpus/chunks/dashboard-admin-page.json +104 -0
- package/corpus/chunks/dashboard-audience-kpis.json +165 -0
- package/corpus/chunks/dashboard-audience-panel.json +756 -0
- package/corpus/chunks/dashboard-behavior-panel.json +114 -0
- package/corpus/chunks/dashboard-chart-recent.json +566 -0
- package/corpus/chunks/dashboard-cohort-retention.json +83 -0
- package/corpus/chunks/dashboard-conversion-panel.json +234 -0
- package/corpus/chunks/dashboard-country-list.json +304 -0
- package/corpus/chunks/dashboard-filter-bar.json +118 -0
- package/corpus/chunks/dashboard-funnel.json +142 -0
- package/corpus/chunks/dashboard-notifications-panel.json +423 -0
- package/corpus/chunks/dashboard-overview-panel.json +1644 -0
- package/corpus/chunks/dashboard-page-header.json +166 -0
- package/corpus/chunks/dashboard-pages-table.json +86 -0
- package/corpus/chunks/dashboard-quick-actions.json +116 -0
- package/corpus/chunks/dashboard-reports-panel.json +166 -0
- package/corpus/chunks/dashboard-reports-table.json +138 -0
- package/corpus/chunks/dashboard-storage-card.json +117 -0
- package/corpus/chunks/dashboard-tabs.json +86 -0
- package/corpus/chunks/dashboard-team-actions-storage.json +605 -0
- package/corpus/chunks/dashboard-team-list.json +334 -0
- package/corpus/chunks/dashboard-traffic-channels.json +184 -0
- package/corpus/chunks/dashboard-transactions-table.json +145 -0
- package/corpus/chunks/data-export-history.json +192 -0
- package/corpus/chunks/data-export-request.json +197 -0
- package/corpus/chunks/data-import-wizard.json +391 -0
- package/corpus/chunks/date-range-picker.json +156 -0
- package/corpus/chunks/date-time-picker-form.json +222 -0
- package/corpus/chunks/departments-checkbox-form.json +390 -0
- package/corpus/chunks/destructive-confirm-modal.json +149 -0
- package/corpus/chunks/diff-inline.json +84 -0
- package/corpus/chunks/diff-side-by-side.json +131 -0
- package/corpus/chunks/diff-structured.json +216 -0
- package/corpus/chunks/digest-email-preview.json +374 -0
- package/corpus/chunks/divider-text-label.json +87 -0
- package/corpus/chunks/doc-editor-shell.json +356 -0
- package/corpus/chunks/drawer-2fa-key.json +273 -0
- package/corpus/chunks/drawer-2fa-sms.json +261 -0
- package/corpus/chunks/drawer-2fa-totp.json +385 -0
- package/corpus/chunks/drawer-cancel-sub.json +636 -0
- package/corpus/chunks/drawer-change-plan.json +561 -0
- package/corpus/chunks/drawer-custom-roles.json +696 -0
- package/corpus/chunks/drawer-data-start.json +171 -0
- package/corpus/chunks/drawer-delete-account.json +341 -0
- package/corpus/chunks/drawer-delete-workspace.json +362 -0
- package/corpus/chunks/drawer-discord.json +434 -0
- package/corpus/chunks/drawer-figma.json +401 -0
- package/corpus/chunks/drawer-first-dashboard.json +220 -0
- package/corpus/chunks/drawer-gcal.json +475 -0
- package/corpus/chunks/drawer-github.json +589 -0
- package/corpus/chunks/drawer-invite.json +397 -0
- package/corpus/chunks/drawer-record-detail.json +44 -0
- package/corpus/chunks/drawer-report.json +528 -0
- package/corpus/chunks/drawer-revoke-session.json +331 -0
- package/corpus/chunks/drawer-role.json +190 -0
- package/corpus/chunks/drawer-slack.json +429 -0
- package/corpus/chunks/drawer-smtp.json +476 -0
- package/corpus/chunks/drawer-source.json +294 -0
- package/corpus/chunks/drawer-transaction.json +493 -0
- package/corpus/chunks/drawer-with-create-form.json +43 -0
- package/corpus/chunks/editor-code-pane.json +204 -0
- package/corpus/chunks/editor-page-shell.json +149 -0
- package/corpus/chunks/editor-preview-pane.json +136 -0
- package/corpus/chunks/editor-statusbar-ready.json +99 -0
- package/corpus/chunks/editor-toolbar-import-export.json +195 -0
- package/corpus/chunks/email-inbox-list.json +678 -0
- package/corpus/chunks/empty-state-danger.json +58 -0
- package/corpus/chunks/empty-state-minimal.json +44 -0
- package/corpus/chunks/empty-state-warning.json +53 -0
- package/corpus/chunks/empty-state.json +65 -0
- package/corpus/chunks/error-404-actions.json +54 -0
- package/corpus/chunks/error-404.json +204 -0
- package/corpus/chunks/error-500-actions.json +61 -0
- package/corpus/chunks/error-500.json +220 -0
- package/corpus/chunks/error-maintenance-actions.json +53 -0
- package/corpus/chunks/error-maintenance.json +203 -0
- package/corpus/chunks/error-page-shell.json +132 -0
- package/corpus/chunks/export-flow-async.json +210 -0
- package/corpus/chunks/export-flow-delivery.json +163 -0
- package/corpus/chunks/export-flow-sync.json +162 -0
- package/corpus/chunks/extended-profile-form.json +512 -0
- package/corpus/chunks/faq-accordion.json +123 -0
- package/corpus/chunks/feature-grid-icons.json +314 -0
- package/corpus/chunks/feature-grid-three-cards.json +187 -0
- package/corpus/chunks/feature-toggle-switch-grid.json +213 -0
- package/corpus/chunks/file-upload-dnd.json +166 -0
- package/corpus/chunks/file-upload-field.json +97 -0
- package/corpus/chunks/file-upload-form.json +251 -0
- package/corpus/chunks/filter-bar-active-filters.json +176 -0
- package/corpus/chunks/filter-bar-filter-bar-with-table.json +113 -0
- package/corpus/chunks/filter-bar-saved-views.json +117 -0
- package/corpus/chunks/filter-popover-form.json +137 -0
- package/corpus/chunks/footer-multi-column.json +270 -0
- package/corpus/chunks/footer-primary-only.json +41 -0
- package/corpus/chunks/form-checkbox-group.json +65 -0
- package/corpus/chunks/form-drawer-edit-form.json +178 -0
- package/corpus/chunks/form-drawer-simple-form.json +151 -0
- package/corpus/chunks/form-drawer-tall-form.json +325 -0
- package/corpus/chunks/form-field-email-hint.json +53 -0
- package/corpus/chunks/form-field-range.json +54 -0
- package/corpus/chunks/form-field-required.json +52 -0
- package/corpus/chunks/form-field-select.json +66 -0
- package/corpus/chunks/form-field-textarea.json +51 -0
- package/corpus/chunks/form-field-with-error.json +151 -0
- package/corpus/chunks/form-fields-two-column.json +69 -0
- package/corpus/chunks/form-page-shell.json +146 -0
- package/corpus/chunks/form-switch-toggle.json +41 -0
- package/corpus/chunks/form-system-live-demo.json +123 -0
- package/corpus/chunks/forms-address-autocomplete.json +277 -0
- package/corpus/chunks/forms-address-fields.json +221 -0
- package/corpus/chunks/forms-conditional-reveal.json +195 -0
- package/corpus/chunks/gallery-page-shell.json +444 -0
- package/corpus/chunks/grid-2col-entry-cards.json +140 -0
- package/corpus/chunks/grid-kpi-4col.json +164 -0
- package/corpus/chunks/grid-responsive-kpi.json +165 -0
- package/corpus/chunks/hero-cta-simple.json +107 -0
- package/corpus/chunks/hero-cta-split.json +183 -0
- package/corpus/chunks/hero-section-split.json +111 -0
- package/corpus/chunks/icon-text-row.json +60 -0
- package/corpus/chunks/image-caption-tile.json +99 -0
- package/corpus/chunks/image-carousel.json +156 -0
- package/corpus/chunks/image-thumbnail-grid.json +77 -0
- package/corpus/chunks/image-upload-preview.json +118 -0
- package/corpus/chunks/import-mapping-flow.json +972 -0
- package/corpus/chunks/inline-dialog-rename.json +128 -0
- package/corpus/chunks/inline-dialog-schedule.json +154 -0
- package/corpus/chunks/inline-dialog-status.json +179 -0
- package/corpus/chunks/integrations-api-keys-card-list.json +475 -0
- package/corpus/chunks/integrations-connect-form.json +550 -0
- package/corpus/chunks/integrations-grid.json +141 -0
- package/corpus/chunks/integrations-webhooks-empty-state.json +66 -0
- package/corpus/chunks/inventory-list-stock.json +137 -0
- package/corpus/chunks/invite-members.json +153 -0
- package/corpus/chunks/invoice-line-items.json +493 -0
- package/corpus/chunks/kanban-board-3col.json +713 -0
- package/corpus/chunks/kanban-page-shell.json +671 -0
- package/corpus/chunks/key-value-detail-list.json +242 -0
- package/corpus/chunks/keyboard-shortcut-display.json +250 -0
- package/corpus/chunks/labeled-textarea.json +45 -0
- package/corpus/chunks/leaderboard-table.json +140 -0
- package/corpus/chunks/library-browser-search-filter.json +109 -0
- package/corpus/chunks/linked-record-row.json +61 -0
- package/corpus/chunks/loading-skeleton-placeholder.json +387 -0
- package/corpus/chunks/login-form-card.json +207 -0
- package/corpus/chunks/logo-cloud-trusted-by.json +235 -0
- package/corpus/chunks/map-with-location-list.json +293 -0
- package/corpus/chunks/marketing-hero-cta.json +102 -0
- package/corpus/chunks/marketing-page-shell.json +352 -0
- package/corpus/chunks/masonry-gallery.json +154 -0
- package/corpus/chunks/member-edit-drawer.json +236 -0
- package/corpus/chunks/members-active-table-search-filter.json +184 -0
- package/corpus/chunks/members-pending-invites-cards.json +251 -0
- package/corpus/chunks/members-roles-permissions-list.json +146 -0
- package/corpus/chunks/menu-context-actions.json +113 -0
- package/corpus/chunks/metadata-description-list.json +178 -0
- package/corpus/chunks/modal-create-project-form.json +133 -0
- package/corpus/chunks/modal-preferences-tabs.json +147 -0
- package/corpus/chunks/multi-field-form-validation.json +394 -0
- package/corpus/chunks/nav-menu-icons.json +74 -0
- package/corpus/chunks/newsletter-subscription.json +190 -0
- package/corpus/chunks/notification-badges-feed.json +411 -0
- package/corpus/chunks/notification-preferences-panel.json +238 -0
- package/corpus/chunks/onb-completion.json +139 -0
- package/corpus/chunks/onb-extension-install.json +172 -0
- package/corpus/chunks/onb-hero-welcome.json +156 -0
- package/corpus/chunks/onb-import-picker.json +86 -0
- package/corpus/chunks/onb-mobile-handoff.json +121 -0
- package/corpus/chunks/onb-notification-prefs.json +83 -0
- package/corpus/chunks/onb-persona-picker.json +82 -0
- package/corpus/chunks/onb-provider-tiles.json +137 -0
- package/corpus/chunks/onb-settings-review.json +257 -0
- package/corpus/chunks/onb-step-footer.json +121 -0
- package/corpus/chunks/onb-step-header.json +121 -0
- package/corpus/chunks/onb-step-progress.json +140 -0
- package/corpus/chunks/onb-step-shell.json +380 -0
- package/corpus/chunks/onb-story-pane.json +121 -0
- package/corpus/chunks/onb-tutorial-steps.json +193 -0
- package/corpus/chunks/order-confirmation-success-card.json +177 -0
- package/corpus/chunks/order-tracking-timeline.json +131 -0
- package/corpus/chunks/pagination-controls.json +82 -0
- package/corpus/chunks/pane-settings-sidebar.json +204 -0
- package/corpus/chunks/patient-visit-shell.json +191 -0
- package/corpus/chunks/payment-checkout-form.json +341 -0
- package/corpus/chunks/payment-method-form.json +328 -0
- package/corpus/chunks/period-comparison.json +236 -0
- package/corpus/chunks/permission-matrix-table.json +438 -0
- package/corpus/chunks/permissions-matrix-grid.json +111 -0
- package/corpus/chunks/permissions-matrix-summary.json +157 -0
- package/corpus/chunks/permissions-role-picker-radio.json +107 -0
- package/corpus/chunks/permissions-role-picker-select.json +101 -0
- package/corpus/chunks/permissions-share-link.json +256 -0
- package/corpus/chunks/permissions-share-people.json +297 -0
- package/corpus/chunks/permissions-sharing-scopes.json +109 -0
- package/corpus/chunks/plan-selection-form.json +529 -0
- package/corpus/chunks/playground-a2ui.json +181 -0
- package/corpus/chunks/playground-agent-admin.json +117 -0
- package/corpus/chunks/playground-agent-chat.json +126 -0
- package/corpus/chunks/playground-app-shell.json +488 -0
- package/corpus/chunks/playground-chat.json +265 -0
- package/corpus/chunks/playground-construct-canvas.json +118 -0
- package/corpus/chunks/playground-css-channel.json +487 -0
- package/corpus/chunks/playground-gen-ui.json +224 -0
- package/corpus/chunks/playground-render-preview.json +369 -0
- package/corpus/chunks/playground-streams-bridge.json +437 -0
- package/corpus/chunks/playground-table-toolbar.json +38 -0
- package/corpus/chunks/popover-with-content.json +118 -0
- package/corpus/chunks/pricing-tiers.json +561 -0
- package/corpus/chunks/product-tour.json +158 -0
- package/corpus/chunks/profile-public.json +297 -0
- package/corpus/chunks/progress-tracker-milestones.json +294 -0
- package/corpus/chunks/radio-button-group.json +83 -0
- package/corpus/chunks/rating-stars-review-summary.json +259 -0
- package/corpus/chunks/reactions-bar.json +75 -0
- package/corpus/chunks/reactions-picker.json +245 -0
- package/corpus/chunks/real-time-metrics-dashboard.json +344 -0
- package/corpus/chunks/record-detail-drawer-simple-detail.json +296 -0
- package/corpus/chunks/record-detail-drawer-tabbed-detail.json +443 -0
- package/corpus/chunks/referral-share-link.json +128 -0
- package/corpus/chunks/referral-status.json +243 -0
- package/corpus/chunks/reg-address-form.json +825 -0
- package/corpus/chunks/reg-billing-card.json +210 -0
- package/corpus/chunks/reg-brand-scrape.json +599 -0
- package/corpus/chunks/reg-departments-toggle.json +259 -0
- package/corpus/chunks/reg-extended-profile.json +356 -0
- package/corpus/chunks/reg-final-done.json +457 -0
- package/corpus/chunks/reg-helpdesk-grid.json +571 -0
- package/corpus/chunks/reg-import-picker.json +112 -0
- package/corpus/chunks/reg-integrations-grid.json +418 -0
- package/corpus/chunks/reg-invite-form.json +245 -0
- package/corpus/chunks/reg-legal-entity.json +260 -0
- package/corpus/chunks/reg-org-chart-review.json +891 -0
- package/corpus/chunks/reg-profile-identity.json +188 -0
- package/corpus/chunks/reg-step-footer.json +201 -0
- package/corpus/chunks/reg-step-header.json +201 -0
- package/corpus/chunks/reg-step-progress.json +236 -0
- package/corpus/chunks/reg-step-shell.json +605 -0
- package/corpus/chunks/reg-story-pane.json +201 -0
- package/corpus/chunks/reg-success-summary.json +257 -0
- package/corpus/chunks/reg-team-size.json +128 -0
- package/corpus/chunks/reg-usecase-picker.json +104 -0
- package/corpus/chunks/reg-workspace-name.json +159 -0
- package/corpus/chunks/responsive-grid-pattern.json +71 -0
- package/corpus/chunks/responsive-kpi-grid.json +166 -0
- package/corpus/chunks/responsive-row-pattern.json +71 -0
- package/corpus/chunks/responsive-shell-sidebar.json +308 -0
- package/corpus/chunks/responsive-wrap-at-pattern.json +124 -0
- package/corpus/chunks/retention-cohort-default-retention.json +80 -0
- package/corpus/chunks/retention-cohort-stat-pair.json +163 -0
- package/corpus/chunks/retention-cohort-with-legend.json +135 -0
- package/corpus/chunks/revenue-heatmap.json +82 -0
- package/corpus/chunks/role-management.json +272 -0
- package/corpus/chunks/row-action-cluster.json +54 -0
- package/corpus/chunks/row-avatar-name-role.json +71 -0
- package/corpus/chunks/row-icon-label.json +52 -0
- package/corpus/chunks/row-spread-label-value.json +54 -0
- package/corpus/chunks/row-wrap-tags.json +72 -0
- package/corpus/chunks/schedule-meeting-form-card.json +229 -0
- package/corpus/chunks/search-recents-menu.json +195 -0
- package/corpus/chunks/search-results-grouped.json +357 -0
- package/corpus/chunks/search-results-list.json +202 -0
- package/corpus/chunks/search-saved-views.json +356 -0
- package/corpus/chunks/search-with-filters.json +311 -0
- package/corpus/chunks/searchable-picker-modal.json +164 -0
- package/corpus/chunks/section-with-stack.json +48 -0
- package/corpus/chunks/security-active-sessions-devices.json +287 -0
- package/corpus/chunks/security-change-password-form.json +102 -0
- package/corpus/chunks/security-delete-account-danger.json +79 -0
- package/corpus/chunks/segmented-control-billing-tabs.json +164 -0
- package/corpus/chunks/select-multiple-preselected.json +70 -0
- package/corpus/chunks/settings-2fa-setup.json +307 -0
- package/corpus/chunks/settings-admin-page.json +1205 -0
- package/corpus/chunks/settings-appearance-primitives.json +145 -0
- package/corpus/chunks/settings-appearance-theme-panel.json +55 -0
- package/corpus/chunks/settings-appearance.json +81 -0
- package/corpus/chunks/settings-danger-zone-delete-workspace.json +93 -0
- package/corpus/chunks/settings-general-form.json +69 -0
- package/corpus/chunks/settings-integrations.json +298 -0
- package/corpus/chunks/settings-members-invite.json +112 -0
- package/corpus/chunks/settings-notification-activity-checkboxes.json +108 -0
- package/corpus/chunks/settings-notification-channels-switches.json +101 -0
- package/corpus/chunks/settings-page-shell.json +190 -0
- package/corpus/chunks/settings-privacy.json +83 -0
- package/corpus/chunks/settings-profile-security.json +122 -0
- package/corpus/chunks/settings-toggle-switches.json +66 -0
- package/corpus/chunks/sidebar-nav.json +120 -0
- package/corpus/chunks/sidebar-navigation-panel.json +165 -0
- package/corpus/chunks/signup-form-card.json +218 -0
- package/corpus/chunks/signup-team-size-form.json +259 -0
- package/corpus/chunks/single-select-card-form.json +231 -0
- package/corpus/chunks/slider-range-controls.json +119 -0
- package/corpus/chunks/social-media-post-card.json +159 -0
- package/corpus/chunks/sortable-list.json +307 -0
- package/corpus/chunks/sortable-search-paginate-table.json +162 -0
- package/corpus/chunks/split-pane-editor.json +146 -0
- package/corpus/chunks/stack-avatar-badge-overlay.json +53 -0
- package/corpus/chunks/stack-image-shimmer.json +51 -0
- package/corpus/chunks/stat-cards-kpi-dashboard.json +178 -0
- package/corpus/chunks/stats-band-big-numbers.json +175 -0
- package/corpus/chunks/status-dashboard.json +282 -0
- package/corpus/chunks/stepper-form-wizard.json +165 -0
- package/corpus/chunks/subscription-status-renewal-card.json +285 -0
- package/corpus/chunks/survey-rating-form.json +165 -0
- package/corpus/chunks/swiper-product-carousel.json +349 -0
- package/corpus/chunks/swiper-testimonial-carousel.json +279 -0
- package/corpus/chunks/table-empty-state.json +116 -0
- package/corpus/chunks/table-inline-edit-row.json +269 -0
- package/corpus/chunks/table-spreadsheet-cells.json +334 -0
- package/corpus/chunks/table-toolbar-no-search.json +42 -0
- package/corpus/chunks/table-toolbar-with-table.json +208 -0
- package/corpus/chunks/table-with-toolbar-card.json +80 -0
- package/corpus/chunks/tabs-with-panels.json +187 -0
- package/corpus/chunks/tag-chips-filter.json +100 -0
- package/corpus/chunks/team-invite-form.json +380 -0
- package/corpus/chunks/team-members-list.json +341 -0
- package/corpus/chunks/testimonial-grid.json +269 -0
- package/corpus/chunks/testimonial-pull-quote.json +137 -0
- package/corpus/chunks/testimonials-avatar-cards.json +286 -0
- package/corpus/chunks/timeline-events.json +89 -0
- package/corpus/chunks/timezone-selector-basic.json +99 -0
- package/corpus/chunks/timezone-selector-preview.json +104 -0
- package/corpus/chunks/toolbar-icons.json +216 -0
- package/corpus/chunks/toolbar-rich-text-editor.json +230 -0
- package/corpus/chunks/toolbar-tooltips.json +191 -0
- package/corpus/chunks/tooltip-hover-button.json +106 -0
- package/corpus/chunks/tree-file-explorer.json +136 -0
- package/corpus/chunks/tree-table-basic.json +171 -0
- package/corpus/chunks/trial-banner.json +158 -0
- package/corpus/chunks/url-import-form.json +728 -0
- package/corpus/chunks/usage-quota-meter-card.json +217 -0
- package/corpus/chunks/user-identity-row.json +81 -0
- package/corpus/chunks/user-onboarding-checklist.json +301 -0
- package/corpus/chunks/user-profile-card.json +192 -0
- package/corpus/chunks/users-table-badge.json +135 -0
- package/corpus/chunks/verification-document-upload.json +179 -0
- package/corpus/chunks/verification-status.json +97 -0
- package/corpus/chunks/verification-stepper.json +111 -0
- package/corpus/chunks/video-player-controls.json +120 -0
- package/corpus/chunks/webhook-config.json +209 -0
- package/corpus/chunks/webhooks-list.json +204 -0
- package/corpus/chunks/wizard-drawer-three-step-wizard.json +443 -0
- package/corpus/manifest.d.ts +4 -0
- package/corpus/manifest.json +6 -0
- package/corpus/scripts/chunk-library.d.ts +4 -0
- package/corpus/scripts/chunk-library.js +335 -0
- package/node-shims.d.ts +48 -0
- package/package.json +215 -0
- package/retrieval/CHANGELOG.md +1073 -0
- package/retrieval/README.md +71 -0
- package/retrieval/anti-patterns.js +189 -0
- package/retrieval/authoring/index.d.ts +4 -0
- package/retrieval/authoring/index.js +9 -0
- package/retrieval/authoring/web-research.js +220 -0
- package/retrieval/catalog.js +250 -0
- package/retrieval/component-catalog.js +168 -0
- package/retrieval/component-entry.js +80 -0
- package/retrieval/context-assembler.js +174 -0
- package/retrieval/domain-router.js +417 -0
- package/retrieval/embedding/chunk-embedding-retriever.js +117 -0
- package/retrieval/embedding/embedding-provider.js +72 -0
- package/retrieval/embedding/index.d.ts +4 -0
- package/retrieval/embedding/index.js +2 -0
- package/retrieval/feedback/dialog-recorder.js +107 -0
- package/retrieval/feedback/feedback-analyzer.js +180 -0
- package/retrieval/feedback/feedback-store.js +194 -0
- package/retrieval/feedback/feedback.js +117 -0
- package/retrieval/feedback/gap-registry.js +80 -0
- package/retrieval/feedback/human-signal.js +113 -0
- package/retrieval/feedback/index.d.ts +4 -0
- package/retrieval/feedback/index.js +24 -0
- package/retrieval/feedback/submit-feedback.js +45 -0
- package/retrieval/index.d.ts +4 -0
- package/retrieval/index.js +53 -0
- package/retrieval/intent/clarity.js +139 -0
- package/retrieval/intent/decomposer.js +124 -0
- package/retrieval/intent/index.d.ts +4 -0
- package/retrieval/intent/index.js +18 -0
- package/retrieval/intent/intent-alignment.js +172 -0
- package/retrieval/intent/intent-categorizer.js +69 -0
- package/retrieval/intent/intent-gate.js +96 -0
- package/retrieval/intent/prompt-analyzer.js +162 -0
- package/retrieval/wiring-catalog.js +168 -0
- package/validate/catalog-validator.d.ts +4 -0
- package/validate/catalog-validator.js +327 -0
- package/validate/semantic/cache.js +54 -0
- package/validate/semantic/classify-intent.js +128 -0
- package/validate/semantic/index.js +163 -0
- package/validate/semantic/intent-specs.js +162 -0
- 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
|
+
}
|