@agent-native/core 0.80.2 → 0.80.5
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +66 -2
- package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
- package/corpus/core/docs/content/template-design.mdx +1 -1
- package/corpus/core/package.json +3 -7
- package/corpus/core/src/action.ts +32 -0
- package/corpus/core/src/agent/durable-background.ts +32 -11
- package/corpus/core/src/agent/production-agent.ts +14 -1
- package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
- package/corpus/core/src/brand-kit/fig/index.ts +17 -37
- package/corpus/core/src/cli/index.ts +1 -1
- package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
- package/corpus/core/src/cli/plan-local.ts +445 -108
- package/corpus/core/src/cli/skills.ts +32 -8
- package/corpus/core/src/client/AssistantChat.tsx +11 -1
- package/corpus/core/src/client/agent-chat.ts +12 -1
- package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
- package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
- package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
- package/corpus/core/src/collab/presence.ts +1 -1
- package/corpus/core/src/mcp/connect-route.ts +34 -5
- package/corpus/core/src/server/action-discovery.ts +4 -0
- package/corpus/core/src/server/action-routes.ts +14 -0
- package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
- package/corpus/core/src/vite/client.ts +15 -1
- package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
- package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
- package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
- package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
- package/corpus/templates/assets/actions/generate-image.ts +3 -0
- package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
- package/corpus/templates/assets/netlify.toml +3 -3
- package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
- package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +162 -0
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
- package/corpus/templates/clips/app/i18n/ar-SA.ts +4 -0
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -0
- package/corpus/templates/clips/app/i18n/fr-FR.ts +4 -0
- package/corpus/templates/clips/app/i18n/hi-IN.ts +4 -0
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -0
- package/corpus/templates/clips/app/i18n/ko-KR.ts +4 -0
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -0
- package/corpus/templates/clips/app/i18n/zh-CN.ts +4 -0
- package/corpus/templates/clips/app/i18n/zh-TW.ts +4 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +42 -18
- package/corpus/templates/content/AGENTS.md +12 -0
- package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
- package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
- package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
- package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
- package/corpus/templates/content/app/global.css +9 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +12 -0
- package/corpus/templates/content/app/i18n-data.ts +140 -0
- package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
- package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
- package/corpus/templates/content/shared/builder-mdx.ts +141 -4
- package/corpus/templates/content/shared/nfm.ts +62 -0
- package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
- package/corpus/templates/design/AGENTS.md +5 -0
- package/corpus/templates/design/_gitignore +7 -0
- package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
- package/corpus/templates/design/actions/create-design-system.ts +15 -4
- package/corpus/templates/design/actions/create-file.ts +20 -1
- package/corpus/templates/design/actions/delete-design-system.ts +2 -2
- package/corpus/templates/design/actions/duplicate-design.ts +35 -9
- package/corpus/templates/design/actions/generate-design.ts +146 -69
- package/corpus/templates/design/actions/generate-screens.ts +51 -19
- package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
- package/corpus/templates/design/actions/get-design.ts +2 -0
- package/corpus/templates/design/actions/import-design-project.ts +28 -9
- package/corpus/templates/design/actions/import-document.ts +22 -8
- package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/design/actions/navigate.ts +105 -54
- package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
- package/corpus/templates/design/actions/show-design-questions.ts +2 -0
- package/corpus/templates/design/actions/update-design.ts +2 -1
- package/corpus/templates/design/actions/view-screen.ts +12 -6
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
- package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
- package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
- package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
- package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
- package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
- package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
- package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
- package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
- package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
- package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
- package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
- package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
- package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
- package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
- package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
- package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
- package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
- package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
- package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
- package/corpus/templates/design/app/global.css +4 -0
- package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
- package/corpus/templates/design/app/i18n-data.ts +527 -0
- package/corpus/templates/design/app/lib/design-systems.ts +6 -4
- package/corpus/templates/design/app/lib/design-templates.ts +15 -3
- package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
- package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
- package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
- package/corpus/templates/design/app/root.tsx +42 -9
- package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
- package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
- package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
- package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
- package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
- package/corpus/templates/design/e2e/README.md +71 -0
- package/corpus/templates/design/e2e/global-setup.ts +157 -0
- package/corpus/templates/design/e2e/helpers.ts +148 -0
- package/corpus/templates/design/netlify.toml +3 -4
- package/corpus/templates/design/package.json +5 -1
- package/corpus/templates/design/playwright.config.ts +51 -0
- package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
- package/corpus/templates/design/server/handlers/uploads.ts +89 -38
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
- package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
- package/corpus/templates/design/server/lib/design-export.ts +35 -8
- package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/design/server/plugins/auth.ts +10 -1
- package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/corpus/templates/design/shared/api.ts +2 -0
- package/corpus/templates/design/shared/apply-edits.ts +12 -8
- package/corpus/templates/design/shared/canvas-math.ts +19 -1
- package/corpus/templates/design/shared/code-layer.ts +87 -28
- package/corpus/templates/design/shared/generation-session.ts +1 -0
- package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
- package/corpus/templates/design/shared/shader-presets.ts +10 -0
- package/corpus/templates/design/shared/source-mode.ts +9 -0
- package/corpus/templates/design/vitest.config.ts +2 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
- package/corpus/templates/plan/.agents/skills/visual-plan/references/local-files.md +14 -5
- package/corpus/templates/plan/.agents/skills/visual-recap/references/local-files.md +14 -5
- package/corpus/templates/plan/AGENTS.md +1 -1
- package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
- package/corpus/templates/plan/actions/validate-local-plan-source.ts +110 -0
- package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
- package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
- package/corpus/templates/plan/server/lib/plan-connector-catalog.ts +1 -0
- package/corpus/templates/plan/server/lib/public-action-paths.ts +1 -0
- package/corpus/templates/plan/server/plan-content.ts +1 -1
- package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
- package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
- package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
- package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
- package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
- package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
- package/corpus/templates/slides/netlify.toml +3 -6
- package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
- package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
- package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
- package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
- package/dist/action.d.ts +23 -0
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +9 -0
- package/dist/action.js.map +1 -1
- package/dist/agent/durable-background.d.ts +9 -7
- package/dist/agent/durable-background.d.ts.map +1 -1
- package/dist/agent/durable-background.js +27 -11
- package/dist/agent/durable-background.js.map +1 -1
- package/dist/agent/production-agent.d.ts +10 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +4 -1
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/run-loop-with-resume.d.ts +2 -1
- package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
- package/dist/agent/run-loop-with-resume.js +2 -2
- package/dist/agent/run-loop-with-resume.js.map +1 -1
- package/dist/brand-kit/fig/index.d.ts +4 -4
- package/dist/brand-kit/fig/index.d.ts.map +1 -1
- package/dist/brand-kit/fig/index.js +14 -27
- package/dist/brand-kit/fig/index.js.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +15 -7
- package/dist/cli/mcp-config-writers.d.ts.map +1 -1
- package/dist/cli/mcp-config-writers.js +198 -45
- package/dist/cli/mcp-config-writers.js.map +1 -1
- package/dist/cli/plan-local.d.ts +30 -0
- package/dist/cli/plan-local.d.ts.map +1 -1
- package/dist/cli/plan-local.js +281 -56
- package/dist/cli/plan-local.js.map +1 -1
- package/dist/cli/skills.d.ts +2 -2
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +32 -8
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +11 -1
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/agent-chat.d.ts.map +1 -1
- package/dist/client/agent-chat.js +15 -2
- package/dist/client/agent-chat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +35 -1
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/org/OrgSwitcher.js +1 -1
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +2 -15
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/collab/presence.d.ts +1 -1
- package/dist/collab/presence.js +1 -1
- package/dist/collab/presence.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +34 -5
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/observability/routes.d.ts +2 -2
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/action-discovery.d.ts.map +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/action-discovery.js.map +1 -1
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +14 -0
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -0
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +12 -3
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +12 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ar-SA/template-design.mdx +1 -1
- package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
- package/docs/content/locales/de-DE/template-design.mdx +1 -1
- package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/es-ES/template-design.mdx +1 -1
- package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/fr-FR/template-design.mdx +1 -1
- package/docs/content/locales/hi-IN/template-design.mdx +1 -1
- package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ja-JP/template-design.mdx +1 -1
- package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/ko-KR/template-design.mdx +1 -1
- package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/pt-BR/template-design.mdx +1 -1
- package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-CN/template-design.mdx +1 -1
- package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
- package/docs/content/locales/zh-TW/template-design.mdx +1 -1
- package/docs/content/real-time-collaboration.mdx +2 -2
- package/docs/content/template-design.mdx +1 -1
- package/package.json +3 -7
- package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
- package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
- package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
- package/corpus/templates/design/actions/import-figma.ts +0 -55
- package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
- package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
- package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
- package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
- package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
- package/dist/brand-kit/fig/decode.d.ts +0 -33
- package/dist/brand-kit/fig/decode.d.ts.map +0 -1
- package/dist/brand-kit/fig/decode.js +0 -358
- package/dist/brand-kit/fig/decode.js.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
- package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
- package/dist/brand-kit/fig/extract-design-system.js +0 -752
- package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
- package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
- package/dist/brand-kit/fig/fig-to-html.js +0 -1506
- package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
|
@@ -141,6 +141,22 @@ export type LocalPlanBridgeServer = {
|
|
|
141
141
|
result: LocalPlanServeResult;
|
|
142
142
|
};
|
|
143
143
|
|
|
144
|
+
export type RendererValidationIssue = {
|
|
145
|
+
path: string;
|
|
146
|
+
message: string;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export type RendererValidation = {
|
|
150
|
+
/** Whether the renderer validate endpoint was reachable and answered. */
|
|
151
|
+
ran: boolean;
|
|
152
|
+
endpoint: string;
|
|
153
|
+
/** Renderer verdict — present only when `ran` is true. */
|
|
154
|
+
valid?: boolean;
|
|
155
|
+
issues?: RendererValidationIssue[];
|
|
156
|
+
/** Transport/endpoint error when `ran` is false (unreachable, 404, old deploy). */
|
|
157
|
+
error?: string;
|
|
158
|
+
};
|
|
159
|
+
|
|
144
160
|
export type LocalPlanVerifyResult = {
|
|
145
161
|
ok: boolean;
|
|
146
162
|
dir: string;
|
|
@@ -165,6 +181,7 @@ export type LocalPlanVerifyResult = {
|
|
|
165
181
|
mdxFiles?: string[];
|
|
166
182
|
error?: string;
|
|
167
183
|
};
|
|
184
|
+
validation: RendererValidation;
|
|
168
185
|
warnings: string[];
|
|
169
186
|
};
|
|
170
187
|
|
|
@@ -1023,16 +1040,21 @@ function lintColumnsBlocks(
|
|
|
1023
1040
|
const start = match.index;
|
|
1024
1041
|
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
1025
1042
|
if (openingEnd < 0) continue;
|
|
1026
|
-
const opening =
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1043
|
+
const opening = source.slice(start, openingEnd + 1);
|
|
1044
|
+
const columnsAttr = readJsxAttribute(opening, "columns");
|
|
1045
|
+
if (!columnsAttr) continue;
|
|
1046
|
+
const entries =
|
|
1047
|
+
columnsAttr.kind === "expression"
|
|
1048
|
+
? extractTopLevelObjectLiterals(columnsAttr.value)
|
|
1049
|
+
: null;
|
|
1050
|
+
if (entries && entries.length > 0) continue;
|
|
1051
|
+
addValidationIssue(
|
|
1052
|
+
issues,
|
|
1053
|
+
file,
|
|
1054
|
+
source,
|
|
1055
|
+
start,
|
|
1056
|
+
'Columns must use <Column> children or a columns={[{ id, label, blocks }]} array, not a bare columns= prop. Use <Columns><Column label="Before">...</Column><Column label="After">...</Column></Columns>.',
|
|
1057
|
+
);
|
|
1036
1058
|
}
|
|
1037
1059
|
}
|
|
1038
1060
|
|
|
@@ -1318,6 +1340,120 @@ function hasRequiredEnumLiteral(
|
|
|
1318
1340
|
return allowed.includes(trimmed.slice(1, -1).trim());
|
|
1319
1341
|
}
|
|
1320
1342
|
|
|
1343
|
+
// Shared per-item checks so the top-level `<Checklist items={[…]} />` tag and
|
|
1344
|
+
// blocks authored as JSON inside a container's `tabs={[…]}` / `columns={[…]}`
|
|
1345
|
+
// array enforce the SAME required fields. `listBase` is the absolute offset of
|
|
1346
|
+
// the items array in the original source so line numbers point near the item.
|
|
1347
|
+
function checkChecklistItemList(
|
|
1348
|
+
items: Array<{ source: string; start: number }>,
|
|
1349
|
+
listBase: number,
|
|
1350
|
+
file: string,
|
|
1351
|
+
source: string,
|
|
1352
|
+
issues: LocalPlanValidationIssue[],
|
|
1353
|
+
) {
|
|
1354
|
+
items.forEach((item, index) => {
|
|
1355
|
+
const base = listBase + item.start;
|
|
1356
|
+
if (!hasRequiredStaticId(item.source)) {
|
|
1357
|
+
addValidationIssue(
|
|
1358
|
+
issues,
|
|
1359
|
+
file,
|
|
1360
|
+
source,
|
|
1361
|
+
base,
|
|
1362
|
+
`Checklist items[${index}].id is required by the Plan renderer schema; add a stable string id.`,
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
if (!hasRequiredStaticString(item.source, "label")) {
|
|
1366
|
+
addValidationIssue(
|
|
1367
|
+
issues,
|
|
1368
|
+
file,
|
|
1369
|
+
source,
|
|
1370
|
+
base,
|
|
1371
|
+
`Checklist items[${index}].label is required by the Plan renderer schema; add a non-empty string label.`,
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function checkQuestionObjectList(
|
|
1378
|
+
questionObjects: Array<{ source: string; start: number }>,
|
|
1379
|
+
listBase: number,
|
|
1380
|
+
tag: string,
|
|
1381
|
+
file: string,
|
|
1382
|
+
source: string,
|
|
1383
|
+
issues: LocalPlanValidationIssue[],
|
|
1384
|
+
) {
|
|
1385
|
+
questionObjects.forEach((question, questionIndex) => {
|
|
1386
|
+
const questionBase = listBase + question.start;
|
|
1387
|
+
if (!hasRequiredStaticId(question.source)) {
|
|
1388
|
+
addValidationIssue(
|
|
1389
|
+
issues,
|
|
1390
|
+
file,
|
|
1391
|
+
source,
|
|
1392
|
+
questionBase,
|
|
1393
|
+
`${tag} questions[${questionIndex}].id is required by the Plan renderer schema; add a stable string id.`,
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
if (!hasRequiredStaticString(question.source, "title")) {
|
|
1397
|
+
addValidationIssue(
|
|
1398
|
+
issues,
|
|
1399
|
+
file,
|
|
1400
|
+
source,
|
|
1401
|
+
questionBase,
|
|
1402
|
+
`${tag} questions[${questionIndex}].title is required by the Plan renderer schema; add a non-empty string title.`,
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
if (
|
|
1406
|
+
!hasRequiredEnumLiteral(question.source, "mode", [
|
|
1407
|
+
"single",
|
|
1408
|
+
"multi",
|
|
1409
|
+
"freeform",
|
|
1410
|
+
])
|
|
1411
|
+
) {
|
|
1412
|
+
addValidationIssue(
|
|
1413
|
+
issues,
|
|
1414
|
+
file,
|
|
1415
|
+
source,
|
|
1416
|
+
questionBase,
|
|
1417
|
+
`${tag} questions[${questionIndex}].mode is required by the Plan renderer schema; use "single", "multi", or "freeform".`,
|
|
1418
|
+
);
|
|
1419
|
+
}
|
|
1420
|
+
const options = readTopLevelObjectProperty(question.source, "options");
|
|
1421
|
+
if (!options) return;
|
|
1422
|
+
const optionObjects = extractTopLevelObjectLiterals(options.value);
|
|
1423
|
+
if (!optionObjects) {
|
|
1424
|
+
addValidationIssue(
|
|
1425
|
+
issues,
|
|
1426
|
+
file,
|
|
1427
|
+
source,
|
|
1428
|
+
questionBase + options.valueStart,
|
|
1429
|
+
`${tag} questions[${questionIndex}].options must be an inline array of object literals.`,
|
|
1430
|
+
);
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
optionObjects.forEach((option, optionIndex) => {
|
|
1434
|
+
const optionBase = questionBase + options.valueStart + option.start;
|
|
1435
|
+
if (!hasRequiredStaticId(option.source)) {
|
|
1436
|
+
addValidationIssue(
|
|
1437
|
+
issues,
|
|
1438
|
+
file,
|
|
1439
|
+
source,
|
|
1440
|
+
optionBase,
|
|
1441
|
+
`${tag} questions[${questionIndex}].options[${optionIndex}].id is required by the Plan renderer schema; add a stable string id.`,
|
|
1442
|
+
);
|
|
1443
|
+
}
|
|
1444
|
+
if (!hasRequiredStaticString(option.source, "label")) {
|
|
1445
|
+
addValidationIssue(
|
|
1446
|
+
issues,
|
|
1447
|
+
file,
|
|
1448
|
+
source,
|
|
1449
|
+
optionBase,
|
|
1450
|
+
`${tag} questions[${questionIndex}].options[${optionIndex}].label is required by the Plan renderer schema; add a non-empty string label.`,
|
|
1451
|
+
);
|
|
1452
|
+
}
|
|
1453
|
+
});
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1321
1457
|
function lintChecklistShape(
|
|
1322
1458
|
file: string,
|
|
1323
1459
|
source: string,
|
|
@@ -1354,27 +1490,7 @@ function lintChecklistShape(
|
|
|
1354
1490
|
);
|
|
1355
1491
|
continue;
|
|
1356
1492
|
}
|
|
1357
|
-
objects.
|
|
1358
|
-
const base = start + items.start + item.start;
|
|
1359
|
-
if (!hasRequiredStaticId(item.source)) {
|
|
1360
|
-
addValidationIssue(
|
|
1361
|
-
issues,
|
|
1362
|
-
file,
|
|
1363
|
-
source,
|
|
1364
|
-
base,
|
|
1365
|
-
`Checklist items[${index}].id is required by the Plan renderer schema; add a stable string id.`,
|
|
1366
|
-
);
|
|
1367
|
-
}
|
|
1368
|
-
if (!hasRequiredStaticString(item.source, "label")) {
|
|
1369
|
-
addValidationIssue(
|
|
1370
|
-
issues,
|
|
1371
|
-
file,
|
|
1372
|
-
source,
|
|
1373
|
-
base,
|
|
1374
|
-
`Checklist items[${index}].label is required by the Plan renderer schema; add a non-empty string label.`,
|
|
1375
|
-
);
|
|
1376
|
-
}
|
|
1377
|
-
});
|
|
1493
|
+
checkChecklistItemList(objects, start + items.start, file, source, issues);
|
|
1378
1494
|
}
|
|
1379
1495
|
}
|
|
1380
1496
|
|
|
@@ -1424,81 +1540,171 @@ function lintQuestionFormShape(
|
|
|
1424
1540
|
);
|
|
1425
1541
|
continue;
|
|
1426
1542
|
}
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1543
|
+
checkQuestionObjectList(
|
|
1544
|
+
questionObjects,
|
|
1545
|
+
start + questions.start,
|
|
1546
|
+
tag,
|
|
1547
|
+
file,
|
|
1548
|
+
source,
|
|
1549
|
+
issues,
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function staticStringLiteralValue(
|
|
1555
|
+
value: string | undefined,
|
|
1556
|
+
): string | undefined {
|
|
1557
|
+
if (value === undefined) return undefined;
|
|
1558
|
+
const trimmed = value.trim();
|
|
1559
|
+
const quote = trimmed[0];
|
|
1560
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") return undefined;
|
|
1561
|
+
if (!trimmed.endsWith(quote) || trimmed.length < 2) return undefined;
|
|
1562
|
+
if (quote === "`" && /\$\{/.test(trimmed)) return undefined;
|
|
1563
|
+
return trimmed.slice(1, -1);
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
// Container blocks authored with a JSON attribute array (`<TabsBlock tabs={[…]}
|
|
1567
|
+
// />`) carry their children as nested block objects, not top-level JSX tags, so
|
|
1568
|
+
// the tag-based linters above never inspect them — the exact "false green" the
|
|
1569
|
+
// renderer catches because it validates the whole parsed block tree. Walk those
|
|
1570
|
+
// nested `blocks: [...]` arrays and enforce the same per-block required fields,
|
|
1571
|
+
// recursing through nested containers. This is still a subset of the renderer
|
|
1572
|
+
// schema (which is why `verify` is the authority), but it closes the common
|
|
1573
|
+
// nested checklist / question-form / missing-id case offline.
|
|
1574
|
+
const NESTED_CONTAINER_TAGS: ReadonlyArray<{ tag: string; attr: string }> = [
|
|
1575
|
+
{ tag: "TabsBlock", attr: "tabs" },
|
|
1576
|
+
{ tag: "Tabs", attr: "tabs" },
|
|
1577
|
+
{ tag: "Columns", attr: "columns" },
|
|
1578
|
+
];
|
|
1579
|
+
|
|
1580
|
+
function lintNestedContainerEntry(
|
|
1581
|
+
entrySource: string,
|
|
1582
|
+
entryBase: number,
|
|
1583
|
+
file: string,
|
|
1584
|
+
source: string,
|
|
1585
|
+
issues: LocalPlanValidationIssue[],
|
|
1586
|
+
) {
|
|
1587
|
+
const blocksProp = readTopLevelObjectProperty(entrySource, "blocks");
|
|
1588
|
+
if (!blocksProp) return;
|
|
1589
|
+
const blockObjects = extractTopLevelObjectLiterals(blocksProp.value);
|
|
1590
|
+
if (!blockObjects) return;
|
|
1591
|
+
const blocksBase = entryBase + blocksProp.valueStart;
|
|
1592
|
+
for (const block of blockObjects) {
|
|
1593
|
+
lintNestedBlock(
|
|
1594
|
+
block.source,
|
|
1595
|
+
blocksBase + block.start,
|
|
1596
|
+
file,
|
|
1597
|
+
source,
|
|
1598
|
+
issues,
|
|
1599
|
+
);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
function lintNestedBlock(
|
|
1604
|
+
blockSource: string,
|
|
1605
|
+
blockBase: number,
|
|
1606
|
+
file: string,
|
|
1607
|
+
source: string,
|
|
1608
|
+
issues: LocalPlanValidationIssue[],
|
|
1609
|
+
) {
|
|
1610
|
+
const typeProp = readTopLevelObjectProperty(blockSource, "type");
|
|
1611
|
+
const type = staticStringLiteralValue(typeProp?.value);
|
|
1612
|
+
if (!hasRequiredStaticId(blockSource)) {
|
|
1613
|
+
addValidationIssue(
|
|
1614
|
+
issues,
|
|
1615
|
+
file,
|
|
1616
|
+
source,
|
|
1617
|
+
blockBase,
|
|
1618
|
+
`Nested ${type ?? "block"} is missing a required string \`id\`; the Plan renderer schema validates every nested block.`,
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
const dataProp = readTopLevelObjectProperty(blockSource, "data");
|
|
1622
|
+
if (!dataProp) return;
|
|
1623
|
+
const dataBase = blockBase + dataProp.valueStart;
|
|
1624
|
+
|
|
1625
|
+
if (type === "checklist") {
|
|
1626
|
+
const itemsProp = readTopLevelObjectProperty(dataProp.value, "items");
|
|
1627
|
+
if (!itemsProp) return;
|
|
1628
|
+
const items = extractTopLevelObjectLiterals(itemsProp.value);
|
|
1629
|
+
if (items) {
|
|
1630
|
+
checkChecklistItemList(
|
|
1631
|
+
items,
|
|
1632
|
+
dataBase + itemsProp.valueStart,
|
|
1633
|
+
file,
|
|
1634
|
+
source,
|
|
1635
|
+
issues,
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
// visual-questions shares question-form's schema; lint both.
|
|
1641
|
+
if (type === "question-form" || type === "visual-questions") {
|
|
1642
|
+
const questionsProp = readTopLevelObjectProperty(
|
|
1643
|
+
dataProp.value,
|
|
1644
|
+
"questions",
|
|
1645
|
+
);
|
|
1646
|
+
if (!questionsProp) return;
|
|
1647
|
+
const questionObjects = extractTopLevelObjectLiterals(questionsProp.value);
|
|
1648
|
+
if (questionObjects) {
|
|
1649
|
+
checkQuestionObjectList(
|
|
1650
|
+
questionObjects,
|
|
1651
|
+
dataBase + questionsProp.valueStart,
|
|
1652
|
+
type,
|
|
1653
|
+
file,
|
|
1654
|
+
source,
|
|
1655
|
+
issues,
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (type === "tabs" || type === "columns") {
|
|
1661
|
+
const innerAttr = type === "tabs" ? "tabs" : "columns";
|
|
1662
|
+
const innerProp = readTopLevelObjectProperty(dataProp.value, innerAttr);
|
|
1663
|
+
if (!innerProp) return;
|
|
1664
|
+
const innerEntries = extractTopLevelObjectLiterals(innerProp.value);
|
|
1665
|
+
if (!innerEntries) return;
|
|
1666
|
+
const innerBase = dataBase + innerProp.valueStart;
|
|
1667
|
+
for (const entry of innerEntries) {
|
|
1668
|
+
lintNestedContainerEntry(
|
|
1669
|
+
entry.source,
|
|
1670
|
+
innerBase + entry.start,
|
|
1671
|
+
file,
|
|
1672
|
+
source,
|
|
1673
|
+
issues,
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function lintNestedContainerBlocks(
|
|
1680
|
+
file: string,
|
|
1681
|
+
source: string,
|
|
1682
|
+
issues: LocalPlanValidationIssue[],
|
|
1683
|
+
) {
|
|
1684
|
+
const scanSource = maskCodeRegions(source);
|
|
1685
|
+
for (const { tag, attr } of NESTED_CONTAINER_TAGS) {
|
|
1686
|
+
const re = new RegExp(`<${tag}\\b`, "g");
|
|
1687
|
+
let match: RegExpExecArray | null;
|
|
1688
|
+
while ((match = re.exec(scanSource))) {
|
|
1689
|
+
const start = match.index;
|
|
1690
|
+
const openingEnd = findJsxOpeningTagEnd(scanSource, start);
|
|
1691
|
+
if (openingEnd < 0) continue;
|
|
1692
|
+
const opening = source.slice(start, openingEnd + 1);
|
|
1693
|
+
const attrValue = readJsxAttribute(opening, attr);
|
|
1694
|
+
if (!attrValue || attrValue.kind !== "expression") continue;
|
|
1695
|
+
const entries = extractTopLevelObjectLiterals(attrValue.value);
|
|
1696
|
+
if (!entries) continue;
|
|
1697
|
+
const entriesBase = start + attrValue.start;
|
|
1698
|
+
for (const entry of entries) {
|
|
1699
|
+
lintNestedContainerEntry(
|
|
1700
|
+
entry.source,
|
|
1701
|
+
entriesBase + entry.start,
|
|
1456
1702
|
file,
|
|
1457
1703
|
source,
|
|
1458
|
-
questionBase,
|
|
1459
|
-
`${tag} questions[${questionIndex}].mode is required by the Plan renderer schema; use "single", "multi", or "freeform".`,
|
|
1460
|
-
);
|
|
1461
|
-
}
|
|
1462
|
-
const options = readTopLevelObjectProperty(question.source, "options");
|
|
1463
|
-
if (!options) return;
|
|
1464
|
-
const optionObjects = extractTopLevelObjectLiterals(options.value);
|
|
1465
|
-
if (!optionObjects) {
|
|
1466
|
-
addValidationIssue(
|
|
1467
1704
|
issues,
|
|
1468
|
-
file,
|
|
1469
|
-
source,
|
|
1470
|
-
start + questions.start + question.start + options.valueStart,
|
|
1471
|
-
`${tag} questions[${questionIndex}].options must be an inline array of object literals.`,
|
|
1472
1705
|
);
|
|
1473
|
-
return;
|
|
1474
1706
|
}
|
|
1475
|
-
|
|
1476
|
-
const optionBase =
|
|
1477
|
-
start +
|
|
1478
|
-
questions.start +
|
|
1479
|
-
question.start +
|
|
1480
|
-
options.valueStart +
|
|
1481
|
-
option.start;
|
|
1482
|
-
if (!hasRequiredStaticId(option.source)) {
|
|
1483
|
-
addValidationIssue(
|
|
1484
|
-
issues,
|
|
1485
|
-
file,
|
|
1486
|
-
source,
|
|
1487
|
-
optionBase,
|
|
1488
|
-
`${tag} questions[${questionIndex}].options[${optionIndex}].id is required by the Plan renderer schema; add a stable string id.`,
|
|
1489
|
-
);
|
|
1490
|
-
}
|
|
1491
|
-
if (!hasRequiredStaticString(option.source, "label")) {
|
|
1492
|
-
addValidationIssue(
|
|
1493
|
-
issues,
|
|
1494
|
-
file,
|
|
1495
|
-
source,
|
|
1496
|
-
optionBase,
|
|
1497
|
-
`${tag} questions[${questionIndex}].options[${optionIndex}].label is required by the Plan renderer schema; add a non-empty string label.`,
|
|
1498
|
-
);
|
|
1499
|
-
}
|
|
1500
|
-
});
|
|
1501
|
-
});
|
|
1707
|
+
}
|
|
1502
1708
|
}
|
|
1503
1709
|
}
|
|
1504
1710
|
|
|
@@ -1523,6 +1729,7 @@ export function validateLocalPlanFiles(
|
|
|
1523
1729
|
lintColumnsBlocks(entry.file, source, issues);
|
|
1524
1730
|
lintChecklistShape(entry.file, entry.source, issues);
|
|
1525
1731
|
lintQuestionFormShape(entry.file, entry.source, issues);
|
|
1732
|
+
lintNestedContainerBlocks(entry.file, entry.source, issues);
|
|
1526
1733
|
}
|
|
1527
1734
|
return issues;
|
|
1528
1735
|
}
|
|
@@ -1703,10 +1910,13 @@ function buildLocalPlanBridgePayload(input: {
|
|
|
1703
1910
|
kind?: LocalPlanKind;
|
|
1704
1911
|
title?: string;
|
|
1705
1912
|
brief?: string;
|
|
1913
|
+
// `verify` brings the bridge up only to exercise transport + the authoritative
|
|
1914
|
+
// renderer check, so it must not be short-circuited by the weaker offline lint.
|
|
1915
|
+
skipSourceValidation?: boolean;
|
|
1706
1916
|
}): LocalPlanBridgePayload {
|
|
1707
1917
|
const dir = path.resolve(input.dir);
|
|
1708
1918
|
const files = readLocalPlanFiles(dir);
|
|
1709
|
-
assertLocalPlanFilesValid(files);
|
|
1919
|
+
if (!input.skipSourceValidation) assertLocalPlanFilesValid(files);
|
|
1710
1920
|
const parsed = stripFrontmatter(files.planMdx);
|
|
1711
1921
|
const kind = input.kind || normalizeKind(parsed.frontmatter.kind);
|
|
1712
1922
|
const title =
|
|
@@ -1792,6 +2002,7 @@ export async function startLocalPlanBridge(input: {
|
|
|
1792
2002
|
open?: boolean;
|
|
1793
2003
|
urlFile?: string | false;
|
|
1794
2004
|
openUrl?: (url: string) => OpenLocalUrlResult;
|
|
2005
|
+
skipSourceValidation?: boolean;
|
|
1795
2006
|
}): Promise<LocalPlanBridgeServer> {
|
|
1796
2007
|
const dir = path.resolve(input.dir);
|
|
1797
2008
|
const initialPayload = buildLocalPlanBridgePayload({
|
|
@@ -1799,6 +2010,7 @@ export async function startLocalPlanBridge(input: {
|
|
|
1799
2010
|
kind: input.kind,
|
|
1800
2011
|
title: input.title,
|
|
1801
2012
|
brief: input.brief,
|
|
2013
|
+
skipSourceValidation: input.skipSourceValidation,
|
|
1802
2014
|
});
|
|
1803
2015
|
const token = input.token || crypto.randomBytes(24).toString("base64url");
|
|
1804
2016
|
const host = input.host || "127.0.0.1";
|
|
@@ -1832,6 +2044,7 @@ export async function startLocalPlanBridge(input: {
|
|
|
1832
2044
|
kind: input.kind,
|
|
1833
2045
|
title: input.title,
|
|
1834
2046
|
brief: input.brief,
|
|
2047
|
+
skipSourceValidation: input.skipSourceValidation,
|
|
1835
2048
|
}),
|
|
1836
2049
|
);
|
|
1837
2050
|
} catch (error) {
|
|
@@ -1918,6 +2131,107 @@ function localPlanBridgeWarnings(input: {
|
|
|
1918
2131
|
return warnings;
|
|
1919
2132
|
}
|
|
1920
2133
|
|
|
2134
|
+
const VALIDATE_LOCAL_PLAN_ACTION = "validate-local-plan-source";
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* Ask the Plan app to validate the folder against its real renderer schema
|
|
2138
|
+
* (`parsePlanMdxFolder` + `planContentSchema`) via the public, no-DB
|
|
2139
|
+
* `validate-local-plan-source` action. This is what makes `verify`
|
|
2140
|
+
* authoritative: the hand-rolled `check` lint can disagree with the renderer,
|
|
2141
|
+
* but this runs the renderer's own parser. Degrades gracefully (`ran: false`)
|
|
2142
|
+
* when the endpoint is unreachable or a Plan app predates the action, so an
|
|
2143
|
+
* old deploy never turns into a hard failure.
|
|
2144
|
+
*/
|
|
2145
|
+
export async function fetchRendererValidation(input: {
|
|
2146
|
+
appUrl: string;
|
|
2147
|
+
files: LocalPlanFiles;
|
|
2148
|
+
fetchFn?: typeof fetch;
|
|
2149
|
+
}): Promise<RendererValidation> {
|
|
2150
|
+
const fetchFn = input.fetchFn ?? fetch;
|
|
2151
|
+
const endpoint = planActionEndpoint(input.appUrl, VALIDATE_LOCAL_PLAN_ACTION);
|
|
2152
|
+
try {
|
|
2153
|
+
const response = await fetchActionWithTimeout(
|
|
2154
|
+
endpoint,
|
|
2155
|
+
{
|
|
2156
|
+
method: "POST",
|
|
2157
|
+
headers: {
|
|
2158
|
+
accept: "application/json",
|
|
2159
|
+
"content-type": "application/json",
|
|
2160
|
+
},
|
|
2161
|
+
body: JSON.stringify({ mdx: localPlanMdxFolder(input.files) }),
|
|
2162
|
+
},
|
|
2163
|
+
fetchFn,
|
|
2164
|
+
);
|
|
2165
|
+
if (!response.ok) {
|
|
2166
|
+
const detail = await response.text().catch(() => "");
|
|
2167
|
+
return {
|
|
2168
|
+
ran: false,
|
|
2169
|
+
endpoint,
|
|
2170
|
+
error: `${VALIDATE_LOCAL_PLAN_ACTION} failed ${response.status} ${
|
|
2171
|
+
response.statusText
|
|
2172
|
+
}: ${sanitizeHttpDetail(detail, 300)}`,
|
|
2173
|
+
};
|
|
2174
|
+
}
|
|
2175
|
+
const json = (await response.json().catch(() => null)) as
|
|
2176
|
+
| (Record<string, unknown> & { result?: Record<string, unknown> })
|
|
2177
|
+
| null;
|
|
2178
|
+
const body =
|
|
2179
|
+
json && isRecord(json.result) ? json.result : (json ?? undefined);
|
|
2180
|
+
if (!body || typeof body.valid !== "boolean") {
|
|
2181
|
+
return {
|
|
2182
|
+
ran: false,
|
|
2183
|
+
endpoint,
|
|
2184
|
+
error: `${VALIDATE_LOCAL_PLAN_ACTION} returned an unexpected payload.`,
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
const issues = Array.isArray(body.issues)
|
|
2188
|
+
? body.issues.filter(isRecord).map((issue) => ({
|
|
2189
|
+
path: typeof issue.path === "string" ? issue.path : "",
|
|
2190
|
+
message:
|
|
2191
|
+
typeof issue.message === "string" ? issue.message : "Invalid value",
|
|
2192
|
+
}))
|
|
2193
|
+
: [];
|
|
2194
|
+
return { ran: true, endpoint, valid: body.valid, issues };
|
|
2195
|
+
} catch (err) {
|
|
2196
|
+
return {
|
|
2197
|
+
ran: false,
|
|
2198
|
+
endpoint,
|
|
2199
|
+
error: err instanceof Error ? err.message : String(err),
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
function rendererValidationWarnings(
|
|
2205
|
+
validation: RendererValidation,
|
|
2206
|
+
offlineIssues: LocalPlanValidationIssue[],
|
|
2207
|
+
): string[] {
|
|
2208
|
+
if (!validation.ran) {
|
|
2209
|
+
const base = `Plan content was NOT validated against the renderer schema (${
|
|
2210
|
+
validation.error ?? "validate endpoint unavailable"
|
|
2211
|
+
}). Fell back to the offline lint. Run against a Plan app that exposes ${VALIDATE_LOCAL_PLAN_ACTION} (e.g. --app-url http://localhost:8096) for authoritative validation.`;
|
|
2212
|
+
if (offlineIssues.length === 0) return [base];
|
|
2213
|
+
const preview = offlineIssues
|
|
2214
|
+
.slice(0, 8)
|
|
2215
|
+
.map((issue) => `${issue.file}:${issue.line} ${issue.message}`)
|
|
2216
|
+
.join("; ");
|
|
2217
|
+
const overflow =
|
|
2218
|
+
offlineIssues.length > 8 ? ` (+${offlineIssues.length - 8} more)` : "";
|
|
2219
|
+
return [base, `Offline lint rejected this plan: ${preview}${overflow}`];
|
|
2220
|
+
}
|
|
2221
|
+
if (validation.valid) return [];
|
|
2222
|
+
const issues = validation.issues ?? [];
|
|
2223
|
+
const preview = issues
|
|
2224
|
+
.slice(0, 8)
|
|
2225
|
+
.map((issue) =>
|
|
2226
|
+
issue.path ? `${issue.path}: ${issue.message}` : issue.message,
|
|
2227
|
+
)
|
|
2228
|
+
.join("; ");
|
|
2229
|
+
const overflow = issues.length > 8 ? ` (+${issues.length - 8} more)` : "";
|
|
2230
|
+
return [
|
|
2231
|
+
`Renderer schema rejected this plan — it will not render. ${preview}${overflow}`,
|
|
2232
|
+
];
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1921
2235
|
export async function verifyLocalPlanBridge(input: {
|
|
1922
2236
|
dir: string;
|
|
1923
2237
|
kind?: LocalPlanKind;
|
|
@@ -1931,6 +2245,7 @@ export async function verifyLocalPlanBridge(input: {
|
|
|
1931
2245
|
fetchFn?: typeof fetch;
|
|
1932
2246
|
}): Promise<LocalPlanVerifyResult> {
|
|
1933
2247
|
const fetchFn = input.fetchFn ?? fetch;
|
|
2248
|
+
const files = readLocalPlanFiles(input.dir);
|
|
1934
2249
|
const bridge = await startLocalPlanBridge({
|
|
1935
2250
|
dir: input.dir,
|
|
1936
2251
|
kind: input.kind,
|
|
@@ -1941,6 +2256,9 @@ export async function verifyLocalPlanBridge(input: {
|
|
|
1941
2256
|
port: input.port,
|
|
1942
2257
|
token: input.token,
|
|
1943
2258
|
urlFile: input.urlFile,
|
|
2259
|
+
// Don't let the weaker offline lint abort the bridge before the authoritative
|
|
2260
|
+
// renderer check runs — verify reports the renderer's verdict, not the lint's.
|
|
2261
|
+
skipSourceValidation: true,
|
|
1944
2262
|
});
|
|
1945
2263
|
|
|
1946
2264
|
try {
|
|
@@ -1972,8 +2290,21 @@ export async function verifyLocalPlanBridge(input: {
|
|
|
1972
2290
|
preflight.status === 204 &&
|
|
1973
2291
|
preflight.headers.get("access-control-allow-origin") === "*" &&
|
|
1974
2292
|
preflight.headers.get("access-control-allow-private-network") === "true";
|
|
2293
|
+
const validation = await fetchRendererValidation({
|
|
2294
|
+
appUrl: bridge.result.appUrl,
|
|
2295
|
+
files,
|
|
2296
|
+
fetchFn,
|
|
2297
|
+
});
|
|
2298
|
+
// The renderer's verdict is authoritative and gates `ok`. When it could not
|
|
2299
|
+
// run (old/unreachable Plan app), fall back to the offline lint as the
|
|
2300
|
+
// content signal so verify still catches the common cases offline instead
|
|
2301
|
+
// of silently passing unvalidated content.
|
|
2302
|
+
const offlineIssues = validation.ran ? [] : validateLocalPlanFiles(files);
|
|
2303
|
+
const contentOk = validation.ran
|
|
2304
|
+
? validation.valid === true
|
|
2305
|
+
: offlineIssues.length === 0;
|
|
1975
2306
|
return {
|
|
1976
|
-
ok: bridgeOk && preflightOk,
|
|
2307
|
+
ok: bridgeOk && preflightOk && contentOk,
|
|
1977
2308
|
dir: bridge.result.dir,
|
|
1978
2309
|
url: bridge.result.url,
|
|
1979
2310
|
...(bridge.result.urlFile ? { urlFile: bridge.result.urlFile } : {}),
|
|
@@ -2000,10 +2331,14 @@ export async function verifyLocalPlanBridge(input: {
|
|
|
2000
2331
|
...(mdxFiles ? { mdxFiles } : {}),
|
|
2001
2332
|
...(payload?.error ? { error: payload.error } : {}),
|
|
2002
2333
|
},
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2334
|
+
validation,
|
|
2335
|
+
warnings: [
|
|
2336
|
+
...localPlanBridgeWarnings({
|
|
2337
|
+
appUrl: bridge.result.appUrl,
|
|
2338
|
+
bridgeUrl: bridge.result.bridgeUrl,
|
|
2339
|
+
}),
|
|
2340
|
+
...rendererValidationWarnings(validation, offlineIssues),
|
|
2341
|
+
],
|
|
2007
2342
|
};
|
|
2008
2343
|
} finally {
|
|
2009
2344
|
await new Promise<void>((resolve) => bridge.server.close(() => resolve()));
|
|
@@ -2090,7 +2425,9 @@ function runCheck(args: Record<string, string | boolean>): void {
|
|
|
2090
2425
|
const result = {
|
|
2091
2426
|
ok: true,
|
|
2092
2427
|
noDb: true,
|
|
2093
|
-
validation: "passed",
|
|
2428
|
+
validation: "lint-passed",
|
|
2429
|
+
validationScope: "offline-lint",
|
|
2430
|
+
note: "Quick offline lint only — NOT the full Plan renderer schema. A green here does not guarantee the plan renders. Run `plan local verify` against a Plan app for authoritative validation before handing off.",
|
|
2094
2431
|
dir: files.dir,
|
|
2095
2432
|
title: parsed.frontmatter.title || firstHeading(parsed.body),
|
|
2096
2433
|
kind: normalizeKind(parsed.frontmatter.kind),
|