@wildo-ai/saas-website 1.1.1
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/LICENSE +34 -0
- package/dist/esm/.builder.pid +9 -0
- package/dist/esm/astro/blog-post-bridge.d.ts +324 -0
- package/dist/esm/astro/blog-post-bridge.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-bridge.js +57 -0
- package/dist/esm/astro/blog-post-bridge.js.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts +129 -0
- package/dist/esm/astro/blog-post-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/blog-post-head.renderer.js +139 -0
- package/dist/esm/astro/blog-post-head.renderer.js.map +1 -0
- package/dist/esm/astro/bridge-runtime.d.ts +224 -0
- package/dist/esm/astro/bridge-runtime.d.ts.map +1 -0
- package/dist/esm/astro/bridge-runtime.js +30 -0
- package/dist/esm/astro/bridge-runtime.js.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts +65 -0
- package/dist/esm/astro/collect-expected-label-keys.d.ts.map +1 -0
- package/dist/esm/astro/collect-expected-label-keys.js +101 -0
- package/dist/esm/astro/collect-expected-label-keys.js.map +1 -0
- package/dist/esm/astro/headers-renderer.d.ts +32 -0
- package/dist/esm/astro/headers-renderer.d.ts.map +1 -0
- package/dist/esm/astro/headers-renderer.js +64 -0
- package/dist/esm/astro/headers-renderer.js.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts +91 -0
- package/dist/esm/astro/i18n-routing.helper.d.ts.map +1 -0
- package/dist/esm/astro/i18n-routing.helper.js +23 -0
- package/dist/esm/astro/i18n-routing.helper.js.map +1 -0
- package/dist/esm/astro/internal/html-escape.d.ts +31 -0
- package/dist/esm/astro/internal/html-escape.d.ts.map +1 -0
- package/dist/esm/astro/internal/html-escape.js +40 -0
- package/dist/esm/astro/internal/html-escape.js.map +1 -0
- package/dist/esm/astro/label-pack-loader.d.ts +134 -0
- package/dist/esm/astro/label-pack-loader.d.ts.map +1 -0
- package/dist/esm/astro/label-pack-loader.js +298 -0
- package/dist/esm/astro/label-pack-loader.js.map +1 -0
- package/dist/esm/astro/llms-txt-server.d.ts +28 -0
- package/dist/esm/astro/llms-txt-server.d.ts.map +1 -0
- package/dist/esm/astro/llms-txt-server.js +33 -0
- package/dist/esm/astro/llms-txt-server.js.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts +63 -0
- package/dist/esm/astro/pick-labels-for-page.d.ts.map +1 -0
- package/dist/esm/astro/pick-labels-for-page.js +73 -0
- package/dist/esm/astro/pick-labels-for-page.js.map +1 -0
- package/dist/esm/astro/robots-renderer.d.ts +85 -0
- package/dist/esm/astro/robots-renderer.d.ts.map +1 -0
- package/dist/esm/astro/robots-renderer.js +157 -0
- package/dist/esm/astro/robots-renderer.js.map +1 -0
- package/dist/esm/astro/sitemap-coverage.d.ts +101 -0
- package/dist/esm/astro/sitemap-coverage.d.ts.map +1 -0
- package/dist/esm/astro/sitemap-coverage.js +67 -0
- package/dist/esm/astro/sitemap-coverage.js.map +1 -0
- package/dist/esm/astro/structured-data.renderer.d.ts +21 -0
- package/dist/esm/astro/structured-data.renderer.d.ts.map +1 -0
- package/dist/esm/astro/structured-data.renderer.js +142 -0
- package/dist/esm/astro/structured-data.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts +93 -0
- package/dist/esm/astro/website-page-head.renderer.d.ts.map +1 -0
- package/dist/esm/astro/website-page-head.renderer.js +184 -0
- package/dist/esm/astro/website-page-head.renderer.js.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts +112 -0
- package/dist/esm/astro/website-page-runtime.helper.d.ts.map +1 -0
- package/dist/esm/astro/website-page-runtime.helper.js +60 -0
- package/dist/esm/astro/website-page-runtime.helper.js.map +1 -0
- package/dist/esm/astro/website-site-context.d.ts +126 -0
- package/dist/esm/astro/website-site-context.d.ts.map +1 -0
- package/dist/esm/astro/website-site-context.js +37 -0
- package/dist/esm/astro/website-site-context.js.map +1 -0
- package/dist/esm/astro-island.d.ts +56 -0
- package/dist/esm/astro-island.d.ts.map +1 -0
- package/dist/esm/astro-island.js +61 -0
- package/dist/esm/astro-island.js.map +1 -0
- package/dist/esm/astro.d.ts +151 -0
- package/dist/esm/astro.d.ts.map +1 -0
- package/dist/esm/astro.js +151 -0
- package/dist/esm/astro.js.map +1 -0
- package/dist/esm/companion-exports.d.ts +38 -0
- package/dist/esm/companion-exports.d.ts.map +1 -0
- package/dist/esm/companion-exports.js +38 -0
- package/dist/esm/companion-exports.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteButton.js +68 -0
- package/dist/esm/components/low-level/WebsiteButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts +78 -0
- package/dist/esm/components/low-level/WebsiteHeading.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteHeading.js +41 -0
- package/dist/esm/components/low-level/WebsiteHeading.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts +73 -0
- package/dist/esm/components/low-level/WebsiteImage.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteImage.js +21 -0
- package/dist/esm/components/low-level/WebsiteImage.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts +90 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js +47 -0
- package/dist/esm/components/low-level/WebsiteInternalButton.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts +99 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js +68 -0
- package/dist/esm/components/low-level/WebsiteInternalLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts +50 -0
- package/dist/esm/components/low-level/WebsiteLink.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteLink.js +22 -0
- package/dist/esm/components/low-level/WebsiteLink.js.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts +57 -0
- package/dist/esm/components/low-level/WebsiteText.d.ts.map +1 -0
- package/dist/esm/components/low-level/WebsiteText.js +57 -0
- package/dist/esm/components/low-level/WebsiteText.js.map +1 -0
- package/dist/esm/config/define-website-config.d.ts +83 -0
- package/dist/esm/config/define-website-config.d.ts.map +1 -0
- package/dist/esm/config/define-website-config.js +126 -0
- package/dist/esm/config/define-website-config.js.map +1 -0
- package/dist/esm/config/index.d.ts +38 -0
- package/dist/esm/config/index.d.ts.map +1 -0
- package/dist/esm/config/index.js +38 -0
- package/dist/esm/config/index.js.map +1 -0
- package/dist/esm/config/load-website-config.d.ts +136 -0
- package/dist/esm/config/load-website-config.d.ts.map +1 -0
- package/dist/esm/config/load-website-config.js +177 -0
- package/dist/esm/config/load-website-config.js.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts +251 -0
- package/dist/esm/config/wildo-website-config.schemas.d.ts.map +1 -0
- package/dist/esm/config/wildo-website-config.schemas.js +302 -0
- package/dist/esm/config/wildo-website-config.schemas.js.map +1 -0
- package/dist/esm/config-loader.d.ts +35 -0
- package/dist/esm/config-loader.d.ts.map +1 -0
- package/dist/esm/config-loader.js +35 -0
- package/dist/esm/config-loader.js.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts +38 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js +77 -0
- package/dist/esm/core/anonymous-session/InboundContactForm.js.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts +41 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js +58 -0
- package/dist/esm/core/anonymous-session/inbound-contact-form.schema.js.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts +81 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.d.ts.map +1 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js +177 -0
- package/dist/esm/core/anonymous-session/website-anonymous-session-client.js.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts +40 -0
- package/dist/esm/core/contexts/WebsitePageContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsitePageContext.js +8 -0
- package/dist/esm/core/contexts/WebsitePageContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts +162 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js +22 -0
- package/dist/esm/core/contexts/WebsiteRuntimeContext.js.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts +47 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.d.ts.map +1 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js +8 -0
- package/dist/esm/core/contexts/WebsiteSectionContext.js.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts +13 -0
- package/dist/esm/core/contexts/useWebsitePage.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsitePage.js +22 -0
- package/dist/esm/core/contexts/useWebsitePage.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts +19 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js +28 -0
- package/dist/esm/core/contexts/useWebsiteRuntime.js.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts +12 -0
- package/dist/esm/core/contexts/useWebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/contexts/useWebsiteSection.js +22 -0
- package/dist/esm/core/contexts/useWebsiteSection.js.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts +17 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.d.ts.map +1 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js +23 -0
- package/dist/esm/core/external-providers/frontend-provider-registry.website.js.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts +70 -0
- package/dist/esm/core/factories/define-website-page-manifest.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-page-manifest.js +51 -0
- package/dist/esm/core/factories/define-website-page-manifest.js.map +1 -0
- package/dist/esm/core/factories/define-website-section.d.ts +101 -0
- package/dist/esm/core/factories/define-website-section.d.ts.map +1 -0
- package/dist/esm/core/factories/define-website-section.js +71 -0
- package/dist/esm/core/factories/define-website-section.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts +23 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js +25 -0
- package/dist/esm/core/hooks/useWebsiteDesignTokens.js.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts +68 -0
- package/dist/esm/core/hooks/useWebsiteLabel.d.ts.map +1 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js +103 -0
- package/dist/esm/core/hooks/useWebsiteLabel.js.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts +87 -0
- package/dist/esm/core/layouts/WebsitePageLayout.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js +170 -0
- package/dist/esm/core/layouts/WebsitePageLayout.js.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts +91 -0
- package/dist/esm/core/layouts/WebsiteSection.d.ts.map +1 -0
- package/dist/esm/core/layouts/WebsiteSection.js +26 -0
- package/dist/esm/core/layouts/WebsiteSection.js.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts +65 -0
- package/dist/esm/core/routing/internal-routing.utils.d.ts.map +1 -0
- package/dist/esm/core/routing/internal-routing.utils.js +14 -0
- package/dist/esm/core/routing/internal-routing.utils.js.map +1 -0
- package/dist/esm/index.d.ts +129 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +129 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/mdx/BlogPost.d.ts +193 -0
- package/dist/esm/mdx/BlogPost.d.ts.map +1 -0
- package/dist/esm/mdx/BlogPost.js +161 -0
- package/dist/esm/mdx/BlogPost.js.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts +139 -0
- package/dist/esm/mdx/blog-post-collection.config.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-collection.config.js +27 -0
- package/dist/esm/mdx/blog-post-collection.config.js.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts +12 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.d.ts.map +1 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js +13 -0
- package/dist/esm/mdx/blog-post-frontmatter-source.js.map +1 -0
- package/dist/esm/mdx/index.d.ts +22 -0
- package/dist/esm/mdx/index.d.ts.map +1 -0
- package/dist/esm/mdx/index.js +22 -0
- package/dist/esm/mdx/index.js.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts +90 -0
- package/dist/esm/mdx/website-mdx-provider.d.ts.map +1 -0
- package/dist/esm/mdx/website-mdx-provider.js +8 -0
- package/dist/esm/mdx/website-mdx-provider.js.map +1 -0
- package/dist/esm/mdx.d.ts +38 -0
- package/dist/esm/mdx.d.ts.map +1 -0
- package/dist/esm/mdx.js +38 -0
- package/dist/esm/mdx.js.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts +75 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js +85 -0
- package/dist/esm/schemas/blog/blog-post-frontmatter.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts +120 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js +152 -0
- package/dist/esm/schemas/design-tokens/website-design-tokens.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts +73 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js +76 -0
- package/dist/esm/schemas/label-keys/website-label-key.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts +209 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js +218 -0
- package/dist/esm/schemas/manifests/website-page-manifest.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts +431 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js +438 -0
- package/dist/esm/schemas/manifests/website-root-config.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts +40 -0
- package/dist/esm/schemas/refs/page-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js +43 -0
- package/dist/esm/schemas/refs/page-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts +41 -0
- package/dist/esm/schemas/refs/section-ref.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js +44 -0
- package/dist/esm/schemas/refs/section-ref.schemas.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts +63 -0
- package/dist/esm/schemas/sections/website-section-category.shared.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js +64 -0
- package/dist/esm/schemas/sections/website-section-category.shared.js.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts +114 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js +69 -0
- package/dist/esm/schemas/sections/website-section-definition.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts +100 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js +355 -0
- package/dist/esm/schemas/structured-data/structured-data-reconciliation.shared.js.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts +58 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.d.ts.map +1 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js +2 -0
- package/dist/esm/schemas/structured-data/website-structured-data.shared.schemas.js.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts +144 -0
- package/dist/esm/schemas/validators/label-pack.validator.d.ts.map +1 -0
- package/dist/esm/schemas/validators/label-pack.validator.js +100 -0
- package/dist/esm/schemas/validators/label-pack.validator.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1 -0
- package/package.json +143 -0
- package/src/__tests__/bundle-isolation.test.ts +607 -0
- package/src/astro/__tests__/blog-post-bridge.test.tsx +288 -0
- package/src/astro/__tests__/blog-post-head.renderer.test.ts +249 -0
- package/src/astro/__tests__/bridge-runtime.test.tsx +198 -0
- package/src/astro/__tests__/collect-expected-label-keys.test.ts +69 -0
- package/src/astro/__tests__/headers-renderer.test.ts +143 -0
- package/src/astro/__tests__/i18n-routing.helper.test.ts +116 -0
- package/src/astro/__tests__/label-pack-loader.test.ts +305 -0
- package/src/astro/__tests__/llms-txt-server.test.ts +127 -0
- package/src/astro/__tests__/pick-labels-for-page.test.ts +60 -0
- package/src/astro/__tests__/robots-renderer.test.ts +228 -0
- package/src/astro/__tests__/sitemap-coverage.test.ts +230 -0
- package/src/astro/__tests__/structured-data.renderer.test.ts +172 -0
- package/src/astro/__tests__/website-page-head.renderer.test.ts +424 -0
- package/src/astro/__tests__/website-page-runtime.helper.test.ts +157 -0
- package/src/astro/__tests__/website-site-context.test.ts +121 -0
- package/src/astro/blog-post-bridge.tsx +404 -0
- package/src/astro/blog-post-head.renderer.ts +293 -0
- package/src/astro/bridge-runtime.tsx +270 -0
- package/src/astro/collect-expected-label-keys.ts +113 -0
- package/src/astro/headers-renderer.ts +82 -0
- package/src/astro/i18n-routing.helper.ts +116 -0
- package/src/astro/internal/html-escape.ts +41 -0
- package/src/astro/label-pack-loader.ts +385 -0
- package/src/astro/llms-txt-server.ts +56 -0
- package/src/astro/pick-labels-for-page.ts +85 -0
- package/src/astro/robots-renderer.ts +171 -0
- package/src/astro/sitemap-coverage.ts +186 -0
- package/src/astro/structured-data.renderer.ts +171 -0
- package/src/astro/website-page-head.renderer.ts +279 -0
- package/src/astro/website-page-runtime.helper.ts +188 -0
- package/src/astro/website-site-context.ts +136 -0
- package/src/astro-island.ts +61 -0
- package/src/astro.ts +151 -0
- package/src/companion-exports.ts +38 -0
- package/src/components/low-level/WebsiteButton.tsx +118 -0
- package/src/components/low-level/WebsiteHeading.tsx +96 -0
- package/src/components/low-level/WebsiteImage.tsx +96 -0
- package/src/components/low-level/WebsiteInternalButton.tsx +172 -0
- package/src/components/low-level/WebsiteInternalLink.tsx +193 -0
- package/src/components/low-level/WebsiteLink.tsx +71 -0
- package/src/components/low-level/WebsiteText.tsx +75 -0
- package/src/components/low-level/__tests__/WebsiteInternalButton.test.tsx +169 -0
- package/src/components/low-level/__tests__/WebsiteInternalLink.test.tsx +202 -0
- package/src/components/low-level/__tests__/primitives.test.tsx +141 -0
- package/src/config/__tests__/define-website-config.test.ts +231 -0
- package/src/config/__tests__/load-website-config.test.ts +215 -0
- package/src/config/define-website-config.ts +134 -0
- package/src/config/index.ts +38 -0
- package/src/config/load-website-config.ts +229 -0
- package/src/config/wildo-website-config.schemas.ts +322 -0
- package/src/config-loader.ts +35 -0
- package/src/core/__tests__/WebsitePageLayout.test.tsx +184 -0
- package/src/core/__tests__/WebsiteSection.test.tsx +85 -0
- package/src/core/__tests__/contexts.test.tsx +133 -0
- package/src/core/__tests__/core-boundary.test.ts +85 -0
- package/src/core/__tests__/define-website-page-manifest.test.tsx +111 -0
- package/src/core/__tests__/define-website-section.test.tsx +89 -0
- package/src/core/__tests__/useWebsiteDesignTokens.test.tsx +41 -0
- package/src/core/__tests__/useWebsiteLabel.test.tsx +117 -0
- package/src/core/anonymous-session/InboundContactForm.tsx +157 -0
- package/src/core/anonymous-session/__tests__/inbound-contact-form.schema.test.ts +44 -0
- package/src/core/anonymous-session/inbound-contact-form.schema.ts +62 -0
- package/src/core/anonymous-session/website-anonymous-session-client.ts +194 -0
- package/src/core/contexts/WebsitePageContext.tsx +52 -0
- package/src/core/contexts/WebsiteRuntimeContext.tsx +175 -0
- package/src/core/contexts/WebsiteSectionContext.tsx +59 -0
- package/src/core/contexts/useWebsitePage.ts +28 -0
- package/src/core/contexts/useWebsiteRuntime.ts +34 -0
- package/src/core/contexts/useWebsiteSection.ts +28 -0
- package/src/core/external-providers/frontend-provider-registry.website.ts +62 -0
- package/src/core/factories/define-website-page-manifest.ts +102 -0
- package/src/core/factories/define-website-section.ts +128 -0
- package/src/core/hooks/useWebsiteDesignTokens.ts +26 -0
- package/src/core/hooks/useWebsiteLabel.ts +121 -0
- package/src/core/layouts/WebsitePageLayout.tsx +311 -0
- package/src/core/layouts/WebsiteSection.tsx +139 -0
- package/src/core/routing/__tests__/internal-routing.utils.test.ts +134 -0
- package/src/core/routing/internal-routing.utils.ts +77 -0
- package/src/index.ts +132 -0
- package/src/mdx/BlogPost.tsx +296 -0
- package/src/mdx/__tests__/BlogPost.test.tsx +218 -0
- package/src/mdx/__tests__/blog-post-collection.config.test.ts +127 -0
- package/src/mdx/__tests__/website-mdx-provider.test.tsx +82 -0
- package/src/mdx/blog-post-collection.config.ts +177 -0
- package/src/mdx/blog-post-frontmatter-source.ts +31 -0
- package/src/mdx/index.ts +39 -0
- package/src/mdx/website-mdx-provider.tsx +95 -0
- package/src/mdx.ts +38 -0
- package/src/schemas/__tests__/blog-frontmatter.test.ts +50 -0
- package/src/schemas/__tests__/companion-exports-react-free.test.ts +51 -0
- package/src/schemas/__tests__/design-tokens.test.ts +163 -0
- package/src/schemas/__tests__/label-key.test.ts +46 -0
- package/src/schemas/__tests__/label-pack-validator.test.ts +146 -0
- package/src/schemas/__tests__/page-manifest.test.ts +66 -0
- package/src/schemas/__tests__/refs.test.ts +37 -0
- package/src/schemas/__tests__/root-config.test.ts +292 -0
- package/src/schemas/__tests__/section-definition.test.ts +47 -0
- package/src/schemas/__tests__/structured-data-reconciliation.test.ts +543 -0
- package/src/schemas/blog/blog-post-frontmatter.shared.schemas.ts +111 -0
- package/src/schemas/design-tokens/website-design-tokens.shared.schemas.ts +166 -0
- package/src/schemas/label-keys/website-label-key.schemas.ts +88 -0
- package/src/schemas/manifests/website-page-manifest.shared.schemas.ts +266 -0
- package/src/schemas/manifests/website-root-config.shared.schemas.ts +504 -0
- package/src/schemas/refs/page-ref.schemas.ts +49 -0
- package/src/schemas/refs/section-ref.schemas.ts +50 -0
- package/src/schemas/sections/website-section-category.shared.ts +65 -0
- package/src/schemas/sections/website-section-definition.shared.schemas.ts +120 -0
- package/src/schemas/structured-data/structured-data-reconciliation.shared.ts +579 -0
- package/src/schemas/structured-data/website-structured-data.shared.schemas.ts +62 -0
- package/src/schemas/validators/label-pack.validator.ts +201 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CSS-injection-safe value validator shared by every design-token field.
|
|
5
|
+
*
|
|
6
|
+
* **Threat model** — TWO escape surfaces, BOTH covered:
|
|
7
|
+
*
|
|
8
|
+
* 1. CSS *declaration* break-out (Phase 6 third audit fix H-α):
|
|
9
|
+
* the renderer `renderWebsiteDesignTokensCss` interpolates token
|
|
10
|
+
* values **raw** into a `:root { --website-<group>-<token>: <value>; }`
|
|
11
|
+
* block. A value containing any of `;`, `}`, `{`, `/*`, `*\/`, or a
|
|
12
|
+
* newline can break out of the property declaration and inject
|
|
13
|
+
* arbitrary CSS — at minimum a config typo silently corrupts the
|
|
14
|
+
* page; at worst a malicious or untrusted config author injects a
|
|
15
|
+
* `body { display: none; }` rule that blanks the entire site.
|
|
16
|
+
*
|
|
17
|
+
* 2. HTML *style-element* break-out (Phase 6 fourth audit fix H-Χ):
|
|
18
|
+
* the rendered `:root { ... }` block is itself wrapped in an inline
|
|
19
|
+
* `<style>…</style>` element by the head renderer. Per the HTML spec
|
|
20
|
+
* the `<style>` element is a *raw-text* element — its only
|
|
21
|
+
* terminator is the literal sequence `</style`. A token value
|
|
22
|
+
* containing `<` (or, for symmetry, `>`) can therefore close the
|
|
23
|
+
* `<style>` element early and follow up with `<svg onload=alert(1)>`
|
|
24
|
+
* or any other HTML / JS payload, which the H-α deny-list (which
|
|
25
|
+
* did NOT forbid `<` / `>`) failed to catch. CSS values legitimately
|
|
26
|
+
* need NEITHER `<` NOR `>` (no CSS grammar uses them — no color,
|
|
27
|
+
* length, variable reference, calc expression, or functional
|
|
28
|
+
* notation does), so denying both is conservative and correct.
|
|
29
|
+
*
|
|
30
|
+
* **Why validate at the schema layer (not inside the renderer)**:
|
|
31
|
+
* 1. Fail at config load (build-time / boot-time), not at every
|
|
32
|
+
* `<head>` render — a malformed token surfaces in the schema-parse
|
|
33
|
+
* error message instead of silently emitting broken CSS / HTML.
|
|
34
|
+
* 2. Single source of truth — every field that flows into the
|
|
35
|
+
* `<style>`-wrapped CSS block gets the same guard (impossible to
|
|
36
|
+
* add a future token field that bypasses the renderer's escape
|
|
37
|
+
* pass).
|
|
38
|
+
* 3. The renderer stays a simple interpolator, which makes its own
|
|
39
|
+
* audit surface trivially small.
|
|
40
|
+
*
|
|
41
|
+
* **Why a `.refine` over a strict color-string regex**: colors accept
|
|
42
|
+
* `currentColor`, named colors, `var(--foo)` references, oklch
|
|
43
|
+
* functional notation, etc. — a strict positive grammar is ~80 lines
|
|
44
|
+
* and high-maintenance. A negative deny-list of the eight characters
|
|
45
|
+
* that are LITERALLY the only ones that can break out of either the CSS
|
|
46
|
+
* property declaration OR the wrapping `<style>` element is small,
|
|
47
|
+
* complete, and obviously correct.
|
|
48
|
+
*/
|
|
49
|
+
const CSS_INJECTION_FORBIDDEN_PATTERN = /[;{}<>\n\r]|\/\*|\*\//;
|
|
50
|
+
const cssSafeValue = (fieldName: string): z.ZodString =>
|
|
51
|
+
z
|
|
52
|
+
.string()
|
|
53
|
+
.min(1, { error: `${fieldName} must not be empty` })
|
|
54
|
+
.refine((value) => !CSS_INJECTION_FORBIDDEN_PATTERN.test(value), {
|
|
55
|
+
error:
|
|
56
|
+
`${fieldName} must not contain CSS-injection or HTML break-out ` +
|
|
57
|
+
`metacharacters (; { } < > /* */ newline) — token values are ` +
|
|
58
|
+
`interpolated raw into <style>:root { --website-...: <value>; }</style>`,
|
|
59
|
+
}) as unknown as z.ZodString;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* @wildo_source:part:start saas.website.design-tokens.colors facet:layer:shared facet:family:website
|
|
63
|
+
*
|
|
64
|
+
* `WebsiteColorTokens` — minimal palette the marketing-site exposes as
|
|
65
|
+
* CSS custom properties (`--website-color-<token>`) on the
|
|
66
|
+
* `<WebsitePageLayout>` root.
|
|
67
|
+
*
|
|
68
|
+
* **Why a dedicated marketing palette** (vs. reusing
|
|
69
|
+
* `presets-components-models/design-tokens-config`):
|
|
70
|
+
*
|
|
71
|
+
* - The SaaS app's design tokens carry many semantic states (`muted`,
|
|
72
|
+
* `destructive`, `ring`, `sidebar-*`, …) that have no marketing-site
|
|
73
|
+
* analogue. Forcing the website to consume the full palette would
|
|
74
|
+
* leak app-internal semantics into a public surface.
|
|
75
|
+
* - Marketing pages need *display-grade* colors (heavily branded
|
|
76
|
+
* accents, gradient stops, hero-overlay tints) that the SaaS app's
|
|
77
|
+
* functional palette deliberately avoids.
|
|
78
|
+
* - Designers overriding website tokens (Phase 6 dogfood proves this
|
|
79
|
+
* path) should not be able to break the SaaS app by editing one
|
|
80
|
+
* tailwind.config — the two surfaces stay decoupled.
|
|
81
|
+
*
|
|
82
|
+
* **Token format**: every value is a CSS color string. Shape validation
|
|
83
|
+
* is intentionally permissive (accepts hex, rgb/rgba, hsl/hsla, oklch,
|
|
84
|
+
* `currentColor`, named colors, CSS variable references) — a strict
|
|
85
|
+
* color-string regex would reject legitimate values (oklch grammar
|
|
86
|
+
* alone is ~80 lines) and a malformed-but-syntactically-valid color
|
|
87
|
+
* fails loudly at render time.
|
|
88
|
+
*
|
|
89
|
+
* **What IS strictly enforced**: every value passes `cssSafeValue`,
|
|
90
|
+
* which rejects the six characters that can break out of a CSS
|
|
91
|
+
* property declaration (`;`, `{`, `}`, `/*`, `*\/`, newline). See the
|
|
92
|
+
* `cssSafeValue` JSDoc above for the full threat model.
|
|
93
|
+
*
|
|
94
|
+
* **Adding a token**: append the field here using `cssSafeValue(...)`,
|
|
95
|
+
* document its semantic intent in the JSDoc, and update the matching
|
|
96
|
+
* CSS-emit helper in Phase 2 (`emitWebsiteColorTokensCss`).
|
|
97
|
+
*/
|
|
98
|
+
const _WebsiteColorTokensSchema = z.object({
|
|
99
|
+
background: cssSafeValue('background color'),
|
|
100
|
+
foreground: cssSafeValue('foreground color'),
|
|
101
|
+
primary: cssSafeValue('primary color'),
|
|
102
|
+
primaryForeground: cssSafeValue('primaryForeground color'),
|
|
103
|
+
accent: cssSafeValue('accent color'),
|
|
104
|
+
accentForeground: cssSafeValue('accentForeground color'),
|
|
105
|
+
surface: cssSafeValue('surface color'),
|
|
106
|
+
surfaceForeground: cssSafeValue('surfaceForeground color'),
|
|
107
|
+
border: cssSafeValue('border color'),
|
|
108
|
+
muted: cssSafeValue('muted color'),
|
|
109
|
+
mutedForeground: cssSafeValue('mutedForeground color'),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
export const WebsiteColorTokensSchema: typeof _WebsiteColorTokensSchema = _WebsiteColorTokensSchema;
|
|
113
|
+
|
|
114
|
+
export type WebsiteColorTokens = z.infer<typeof _WebsiteColorTokensSchema>;
|
|
115
|
+
/** @wildo_source:part:end saas.website.design-tokens.colors */
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @wildo_source:part:start saas.website.design-tokens.radius facet:layer:shared facet:family:website
|
|
119
|
+
*
|
|
120
|
+
* `WebsiteRadiusTokens` — border-radius scale exposed as
|
|
121
|
+
* `--website-radius-<token>` CSS variables.
|
|
122
|
+
*
|
|
123
|
+
* Marketing radii are intentionally a separate token set from the
|
|
124
|
+
* SaaS app's radii: the website typically wants larger pill / hero
|
|
125
|
+
* radii than the app's compact-UI radii. Keeping them separate lets
|
|
126
|
+
* either surface evolve without coupling.
|
|
127
|
+
*
|
|
128
|
+
* Values are CSS length strings (`'0.25rem'`, `'12px'`,
|
|
129
|
+
* `'9999px'`). Same permissive-shape + strict-CSS-injection
|
|
130
|
+
* rationale as `WebsiteColorTokens` (every value runs through
|
|
131
|
+
* `cssSafeValue`).
|
|
132
|
+
*/
|
|
133
|
+
const _WebsiteRadiusTokensSchema = z.object({
|
|
134
|
+
none: cssSafeValue('none radius'),
|
|
135
|
+
sm: cssSafeValue('sm radius'),
|
|
136
|
+
md: cssSafeValue('md radius'),
|
|
137
|
+
lg: cssSafeValue('lg radius'),
|
|
138
|
+
xl: cssSafeValue('xl radius'),
|
|
139
|
+
full: cssSafeValue('full radius'),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
export const WebsiteRadiusTokensSchema: typeof _WebsiteRadiusTokensSchema = _WebsiteRadiusTokensSchema;
|
|
143
|
+
|
|
144
|
+
export type WebsiteRadiusTokens = z.infer<typeof _WebsiteRadiusTokensSchema>;
|
|
145
|
+
/** @wildo_source:part:end saas.website.design-tokens.radius */
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @wildo_source:part:start saas.website.design-tokens.bundle facet:layer:shared facet:family:website
|
|
149
|
+
*
|
|
150
|
+
* `WebsiteDesignTokens` — the complete design-token bundle attached to
|
|
151
|
+
* `WebsiteRootConfig.designTokens`. Currently colors + radii only
|
|
152
|
+
* (per plan Phase 1 scope: "WebsiteColorTokensSchema + WebsiteRadiusTokensSchema (only)").
|
|
153
|
+
*
|
|
154
|
+
* Future expansions (typography scale, motion tokens, shadow elevation)
|
|
155
|
+
* will land as additional fields here without breaking the
|
|
156
|
+
* `WebsiteRootConfig` shape.
|
|
157
|
+
*/
|
|
158
|
+
const _WebsiteDesignTokensSchema = z.object({
|
|
159
|
+
colors: _WebsiteColorTokensSchema,
|
|
160
|
+
radii: _WebsiteRadiusTokensSchema,
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
export const WebsiteDesignTokensSchema: typeof _WebsiteDesignTokensSchema = _WebsiteDesignTokensSchema;
|
|
164
|
+
|
|
165
|
+
export type WebsiteDesignTokens = z.infer<typeof _WebsiteDesignTokensSchema>;
|
|
166
|
+
/** @wildo_source:part:end saas.website.design-tokens.bundle */
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.label-keys.format facet:layer:shared facet:family:i18n
|
|
5
|
+
*
|
|
6
|
+
* `WebsiteLabelKey` — branded i18n key for marketing-site copy.
|
|
7
|
+
*
|
|
8
|
+
* **Format**: `website.<pageRef>.<sectionRef>.<slotPath>`
|
|
9
|
+
*
|
|
10
|
+
* - `website.` — fixed namespace prefix that disambiguates marketing-site
|
|
11
|
+
* labels from app i18n keys (`labels.`, `errors.`, `notifications.`).
|
|
12
|
+
* Marketing-site label packs ship as a separate file per locale
|
|
13
|
+
* (`website/src/i18n/<locale>.json`) so they never bloat the SaaS app
|
|
14
|
+
* bundle.
|
|
15
|
+
* - `<pageRef>` — kebab-case page ref (validated independently by
|
|
16
|
+
* `WebsitePageRefSchema`).
|
|
17
|
+
* - `<sectionRef>` — kebab-case section ref (validated independently by
|
|
18
|
+
* `WebsiteSectionRefSchema`).
|
|
19
|
+
* - `<slotPath>` — leaf identifier the section consumes via
|
|
20
|
+
* `useWebsiteLabel('<slotPath>')` (Phase 2). Allows dots for nested
|
|
21
|
+
* structures (`'cta.primary'`), brackets for indexed array slots
|
|
22
|
+
* (`'tiers[0].name'`), and hyphens for multi-word leaves.
|
|
23
|
+
*
|
|
24
|
+
* **Examples**:
|
|
25
|
+
* - `website.landing.hero.headline`
|
|
26
|
+
* - `website.landing.hero.cta.primary`
|
|
27
|
+
* - `website.pricing.tiers.tiers[0].name`
|
|
28
|
+
* - `website.contact.form.field.email-placeholder`
|
|
29
|
+
*
|
|
30
|
+
* **Why a brand**: prevents the "label key as bare string" failure mode
|
|
31
|
+
* where a typo in `'webiste.landing.hero.headline'` (note the typo) silently
|
|
32
|
+
* resolves to undefined at runtime. Sections must accept
|
|
33
|
+
* `WebsiteLabelKey`-typed props; consumers go through
|
|
34
|
+
* `WebsiteLabelKeySchema.parse(...)` (or the manifest's `metaLabelKeys`
|
|
35
|
+
* fields, which are typed against this schema) before a string flows into
|
|
36
|
+
* label resolution.
|
|
37
|
+
*
|
|
38
|
+
* **Why a single regex**: the three nested refs (`page`, `section`, `slot`)
|
|
39
|
+
* are validated separately by their own schemas inside the `defineWebsiteSection`
|
|
40
|
+
* factory and `WebsitePageManifestSchema`. This schema's job is to validate
|
|
41
|
+
* a fully-composed key — typically inserted directly into JSX as a string
|
|
42
|
+
* literal — without re-parsing the parts.
|
|
43
|
+
*/
|
|
44
|
+
const _WebsiteLabelKeySchema = z
|
|
45
|
+
.string()
|
|
46
|
+
.min(10, { error: 'WebsiteLabelKey is too short (must include website.<page>.<section>.<slot>)' })
|
|
47
|
+
.max(256, { error: 'WebsiteLabelKey must be at most 256 characters' })
|
|
48
|
+
.regex(
|
|
49
|
+
/^website\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.[a-zA-Z0-9.\[\]_-]+$/,
|
|
50
|
+
{
|
|
51
|
+
error:
|
|
52
|
+
'WebsiteLabelKey must match `website.<page>.<section>.<slot>` where page/section are kebab-case and slot allows dot/bracket/hyphen/underscore (e.g. "website.landing.hero.headline")',
|
|
53
|
+
},
|
|
54
|
+
)
|
|
55
|
+
.brand<'WebsiteLabelKey'>();
|
|
56
|
+
|
|
57
|
+
export const WebsiteLabelKeySchema: typeof _WebsiteLabelKeySchema = _WebsiteLabelKeySchema;
|
|
58
|
+
|
|
59
|
+
export type WebsiteLabelKey = z.infer<typeof _WebsiteLabelKeySchema>;
|
|
60
|
+
/** @wildo_source:part:end saas.website.label-keys.format */
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @wildo_source:part:start saas.website.label-keys.compose-helper facet:layer:shared facet:family:i18n
|
|
64
|
+
*
|
|
65
|
+
* `composeWebsiteLabelKey` — pure helper that composes a `WebsiteLabelKey`
|
|
66
|
+
* from its three parts. Used by `useWebsiteLabel` (Phase 2) and by the
|
|
67
|
+
* label-pack validator to materialize the keys a section commits to.
|
|
68
|
+
*
|
|
69
|
+
* Returns the key as a parsed `WebsiteLabelKey` (brand applied), throwing
|
|
70
|
+
* if the composed string fails the format check (defensive guard against
|
|
71
|
+
* stale refs / inconsistent slot paths leaking past type-system narrowing).
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* composeWebsiteLabelKey({
|
|
75
|
+
* pageRef: WebsitePageRefSchema.parse('landing'),
|
|
76
|
+
* sectionRef: WebsiteSectionRefSchema.parse('hero'),
|
|
77
|
+
* slotPath: 'headline',
|
|
78
|
+
* });
|
|
79
|
+
* // -> WebsiteLabelKey ("website.landing.hero.headline")
|
|
80
|
+
*/
|
|
81
|
+
export function composeWebsiteLabelKey(parts: {
|
|
82
|
+
pageRef: string;
|
|
83
|
+
sectionRef: string;
|
|
84
|
+
slotPath: string;
|
|
85
|
+
}): WebsiteLabelKey {
|
|
86
|
+
return WebsiteLabelKeySchema.parse(`website.${parts.pageRef}.${parts.sectionRef}.${parts.slotPath}`);
|
|
87
|
+
}
|
|
88
|
+
/** @wildo_source:part:end saas.website.label-keys.compose-helper */
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
import { WebsiteLabelKeySchema } from '../label-keys/website-label-key.schemas';
|
|
5
|
+
import { WebsitePageRefSchema } from '../refs/page-ref.schemas';
|
|
6
|
+
import { WebsiteSectionRefSchema } from '../refs/section-ref.schemas';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @wildo_source:part:start saas.website.page-manifest.meta-label-keys facet:layer:shared facet:family:website
|
|
10
|
+
*
|
|
11
|
+
* `WebsitePageMetaLabelKeys` — the i18n keys the Astro adapter (Phase 5)
|
|
12
|
+
* resolves at static-build time to populate `<title>` / `<meta>` /
|
|
13
|
+
* Open Graph tags for the page.
|
|
14
|
+
*
|
|
15
|
+
* - `titleKey` — REQUIRED. Renders into `<title>` AND `og:title`. The
|
|
16
|
+
* manifest commits to declaring this key in every locale's label
|
|
17
|
+
* pack; the validator surfaces missing entries at build start.
|
|
18
|
+
* - `descriptionKey` — REQUIRED. Renders into `<meta name="description">`,
|
|
19
|
+
* `og:description`, and `twitter:description`. Same per-locale
|
|
20
|
+
* commitment.
|
|
21
|
+
* - `ogImageUrl` — OPTIONAL. Static URL (typically a per-locale CDN
|
|
22
|
+
* path) for `og:image` / `twitter:image`. Not a label key because
|
|
23
|
+
* marketing teams overwhelmingly ship one localized asset URL per
|
|
24
|
+
* page rather than authoring image URLs in label packs. When
|
|
25
|
+
* per-locale images are needed, the consumer supplies a function
|
|
26
|
+
* in their site-root config that resolves locale → URL (Phase 5).
|
|
27
|
+
*
|
|
28
|
+
* **Why these three only**: the JSON-LD / canonical / `hreflang` /
|
|
29
|
+
* robots tags are NOT label-key-driven — they are derived
|
|
30
|
+
* mechanically from the manifest (`routePath`), the root config
|
|
31
|
+
* (`locales`, `defaultLocale`), and the section-level
|
|
32
|
+
* `getStructuredData()` hooks. Mixing them into the meta-label-keys
|
|
33
|
+
* shape would conflate human-authored copy with structural metadata.
|
|
34
|
+
*/
|
|
35
|
+
const _WebsitePageMetaLabelKeysSchema = z.object({
|
|
36
|
+
titleKey: WebsiteLabelKeySchema,
|
|
37
|
+
descriptionKey: WebsiteLabelKeySchema,
|
|
38
|
+
ogImageUrl: z.url({ error: 'ogImageUrl must be a valid URL' }).optional(),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const WebsitePageMetaLabelKeysSchema: typeof _WebsitePageMetaLabelKeysSchema =
|
|
42
|
+
_WebsitePageMetaLabelKeysSchema;
|
|
43
|
+
|
|
44
|
+
export type WebsitePageMetaLabelKeys = z.infer<typeof _WebsitePageMetaLabelKeysSchema>;
|
|
45
|
+
/** @wildo_source:part:end saas.website.page-manifest.meta-label-keys */
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @wildo_source:part:start saas.website.page-manifest.route-path facet:layer:shared facet:family:website
|
|
49
|
+
*
|
|
50
|
+
* `WebsitePageRoutePath` — the URL path the page mounts at (locale
|
|
51
|
+
* prefix stripped).
|
|
52
|
+
*
|
|
53
|
+
* **Format**:
|
|
54
|
+
* - MUST start with `/`
|
|
55
|
+
* - MAY be the root (`'/'`) for the landing page
|
|
56
|
+
* - segments are lowercase kebab-case
|
|
57
|
+
* - parameter segments use Astro's `[param]` convention
|
|
58
|
+
* (e.g. `'/blog/[slug]'`, `'/case-studies/[slug]'`)
|
|
59
|
+
*
|
|
60
|
+
* The Astro adapter (Phase 5) prepends the locale prefix
|
|
61
|
+
* (`/en`, `/fr`, …) when generating routes. The manifest stores the
|
|
62
|
+
* locale-neutral path because the same `pageComponent` renders for
|
|
63
|
+
* every locale.
|
|
64
|
+
*
|
|
65
|
+
* **Brand**: prevents accidental cross-talk with absolute URLs
|
|
66
|
+
* (`https://…`), with locale-prefixed paths (`/en/pricing`), and with
|
|
67
|
+
* arbitrary string props in the manifest factory.
|
|
68
|
+
*/
|
|
69
|
+
const _WebsitePageRoutePathSchema = z
|
|
70
|
+
.string()
|
|
71
|
+
.min(1, { error: 'routePath must not be empty' })
|
|
72
|
+
.max(256, { error: 'routePath must be at most 256 characters' })
|
|
73
|
+
.regex(
|
|
74
|
+
/^\/(?:[a-z0-9][a-z0-9-]*(?:\/[a-z0-9-]+|\/\[[a-z][a-zA-Z0-9]*\])*)?\/?$/,
|
|
75
|
+
{
|
|
76
|
+
error:
|
|
77
|
+
'routePath must start with `/`, use lowercase kebab-case segments, and may include Astro `[param]` segments (e.g. "/", "/pricing", "/blog/[slug]")',
|
|
78
|
+
},
|
|
79
|
+
)
|
|
80
|
+
.brand<'WebsitePageRoutePath'>();
|
|
81
|
+
|
|
82
|
+
export const WebsitePageRoutePathSchema: typeof _WebsitePageRoutePathSchema =
|
|
83
|
+
_WebsitePageRoutePathSchema;
|
|
84
|
+
|
|
85
|
+
export type WebsitePageRoutePath = z.infer<typeof _WebsitePageRoutePathSchema>;
|
|
86
|
+
/** @wildo_source:part:end saas.website.page-manifest.route-path */
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @wildo_source:part:start saas.website.page-manifest.schema facet:layer:shared facet:family:website
|
|
90
|
+
*
|
|
91
|
+
* `WebsitePageManifest` — the registration-time descriptor for one
|
|
92
|
+
* marketing-site page.
|
|
93
|
+
*
|
|
94
|
+
* **Authoring path**: each page-impl module exports both a
|
|
95
|
+
* `<PageName>Page` React component AND a `<PAGE_NAME>_PAGE_MANIFEST`
|
|
96
|
+
* literal. The manifest is collected into `WebsiteRootConfig.pageManifests[]`;
|
|
97
|
+
* the Astro page file (`src/pages/<page>.astro`) imports the manifest
|
|
98
|
+
* and hands it to `<WebsitePageBridge manifest={…}>` (Phase 5).
|
|
99
|
+
*
|
|
100
|
+
* **Field-by-field rationale** (semantic-weight discipline):
|
|
101
|
+
*
|
|
102
|
+
* - `ref` — the page's structural identity. Used for label-key
|
|
103
|
+
* derivation (`website.<ref>.<sectionRef>.<slot>`), for
|
|
104
|
+
* cross-page navigation lookup, and as the manifest registry key.
|
|
105
|
+
* - `routePath` — the locale-neutral URL path. Decoupled from `ref`
|
|
106
|
+
* because the same page (e.g. `ref: 'pricing'`) may mount at a
|
|
107
|
+
* localized path (`/tarifs` in French) — the locale-aware path
|
|
108
|
+
* resolution is a Phase 5 concern; this field is the canonical
|
|
109
|
+
* English-language path.
|
|
110
|
+
* - `pageComponent` — the React component the bridge renders inside
|
|
111
|
+
* `<WebsitePageContext value={{ pageRef: ref }}>`. Type relaxed to
|
|
112
|
+
* `unknown` at the schema layer for the same reasons as
|
|
113
|
+
* `WebsiteSectionDefinition.Component`; the manifest factory
|
|
114
|
+
* (`defineWebsitePageManifest`, Phase 2) preserves precise typing
|
|
115
|
+
* at the consumer call site.
|
|
116
|
+
* - `sectionRefs` — the contractual list of section refs the page
|
|
117
|
+
* declares. Used by the label-pack validator to cross-reference
|
|
118
|
+
* `expectedLabelKeys` collected from each section against the
|
|
119
|
+
* page's commitment. **Order matters** for the validator's
|
|
120
|
+
* diagnostic output (sections are reported in declaration order)
|
|
121
|
+
* but does NOT control render order — render order is whatever the
|
|
122
|
+
* `pageComponent` JSX lays out.
|
|
123
|
+
* - `metaLabelKeys` — see `WebsitePageMetaLabelKeysSchema`.
|
|
124
|
+
* - `hydration` — opt-in flag. `'lazy'` (default) corresponds to
|
|
125
|
+
* `client:idle` in the Astro adapter (deferred hydration); `'eager'`
|
|
126
|
+
* corresponds to `client:load` (immediate hydration). The page
|
|
127
|
+
* author flips this to `'eager'` only when the page hosts
|
|
128
|
+
* above-the-fold interactive sections (e.g. a contact form).
|
|
129
|
+
*
|
|
130
|
+
* **Important — informational, NOT auto-wired**: Astro's
|
|
131
|
+
* `client:*` directives MUST resolve to a literal token at compile
|
|
132
|
+
* time (the Astro compiler reads the directive from the JSX source
|
|
133
|
+
* to decide which hydration helper to bundle); a runtime-computed
|
|
134
|
+
* value will not work. The framework therefore CANNOT translate
|
|
135
|
+
* this field into the directive on the consumer's behalf — the
|
|
136
|
+
* manifest field is the single source of truth a consumer's
|
|
137
|
+
* per-page `.astro` template should USE to pick the matching
|
|
138
|
+
* `client:idle` / `client:load` directive by hand. Keeping the two
|
|
139
|
+
* in lock-step is a discipline obligation; the audit CLI (Phase 7)
|
|
140
|
+
* will surface a drift warning when a page's hydration field does
|
|
141
|
+
* not match the directive in its `.astro` file.
|
|
142
|
+
*
|
|
143
|
+
* **Refinements**:
|
|
144
|
+
* - `sectionRefs` MUST contain unique values. Duplicates indicate
|
|
145
|
+
* either a copy-paste bug (two sections pointing at the same
|
|
146
|
+
* label-key root) or an intent to share a label-key root that
|
|
147
|
+
* the framework rejects to keep the per-page validation
|
|
148
|
+
* unambiguous (consumers who need a shared label-key root
|
|
149
|
+
* across two distinct sections should pull the shared content
|
|
150
|
+
* to the page level via `WebsiteRootConfig`-level globals,
|
|
151
|
+
* Phase 2.5).
|
|
152
|
+
*/
|
|
153
|
+
export enum WebsitePageHydrationStrategy {
|
|
154
|
+
/** `client:idle` in the Astro adapter (deferred hydration). */
|
|
155
|
+
LAZY = 'lazy',
|
|
156
|
+
/** `client:load` in the Astro adapter (immediate hydration). */
|
|
157
|
+
EAGER = 'eager',
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export const WebsitePageHydrationStrategySchema: z.ZodEnum<typeof WebsitePageHydrationStrategy> =
|
|
161
|
+
z.enum(WebsitePageHydrationStrategy);
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @wildo_source:part:start saas.website.page-manifest.seo facet:layer:shared facet:family:website
|
|
165
|
+
*
|
|
166
|
+
* `WebsitePageSeoConfig` — per-page SEO/AEO/GEO knobs.
|
|
167
|
+
*
|
|
168
|
+
* The marketing-site default is to be FULLY discoverable by every
|
|
169
|
+
* crawler (search engines AND AI training crawlers). This block holds
|
|
170
|
+
* the per-page opt-out levers that flip a page out of that default.
|
|
171
|
+
*
|
|
172
|
+
* **Field rationale**:
|
|
173
|
+
*
|
|
174
|
+
* - `noaiOptOut?` — when `true`, the page emits
|
|
175
|
+
* `<meta name="robots" content="noai, noimageai">` AND a sibling
|
|
176
|
+
* `_headers` file entry signalling `X-Robots-Tag: noai, noimageai`
|
|
177
|
+
* for crawlers that don't honor in-document meta. ONE knob drives
|
|
178
|
+
* BOTH channels (`headers-renderer.ts` reads the same flag) — the
|
|
179
|
+
* framework owns the dual emission so consumers can't accidentally
|
|
180
|
+
* set one and forget the other.
|
|
181
|
+
*
|
|
182
|
+
* **Why default-IN to AI training (D-AG3)**: the marketing-site
|
|
183
|
+
* default is `false` (consent to AI training). Marketing pages
|
|
184
|
+
* benefit from AI-engine citation; opting out is a per-page
|
|
185
|
+
* editorial decision (typically pricing pages — to avoid stale
|
|
186
|
+
* price training; or legal/internal pages — to avoid free-text
|
|
187
|
+
* reproduction of contractual language). Per-blog-post opt-out
|
|
188
|
+
* would mirror this knob on a sibling blog-post manifest schema
|
|
189
|
+
* if that need materializes.
|
|
190
|
+
*/
|
|
191
|
+
const _WebsitePageSeoConfigSchema = z.object({
|
|
192
|
+
noaiOptOut: z.boolean().optional(),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
export const WebsitePageSeoConfigSchema: typeof _WebsitePageSeoConfigSchema =
|
|
196
|
+
_WebsitePageSeoConfigSchema;
|
|
197
|
+
|
|
198
|
+
export type WebsitePageSeoConfig = z.infer<typeof _WebsitePageSeoConfigSchema>;
|
|
199
|
+
/** @wildo_source:part:end saas.website.page-manifest.seo */
|
|
200
|
+
|
|
201
|
+
const _WebsitePageManifestSchema = z
|
|
202
|
+
.object({
|
|
203
|
+
ref: WebsitePageRefSchema,
|
|
204
|
+
routePath: _WebsitePageRoutePathSchema,
|
|
205
|
+
pageComponent: z.unknown(),
|
|
206
|
+
sectionRefs: z.array(WebsiteSectionRefSchema).min(1, {
|
|
207
|
+
error: 'A page manifest must declare at least one sectionRef',
|
|
208
|
+
}),
|
|
209
|
+
metaLabelKeys: _WebsitePageMetaLabelKeysSchema,
|
|
210
|
+
hydration: WebsitePageHydrationStrategySchema.optional(),
|
|
211
|
+
seo: _WebsitePageSeoConfigSchema.optional(),
|
|
212
|
+
})
|
|
213
|
+
.superRefine((manifest, ctx) => {
|
|
214
|
+
const seen = new Set<string>();
|
|
215
|
+
for (const [index, sectionRef] of manifest.sectionRefs.entries()) {
|
|
216
|
+
const key = sectionRef as unknown as string;
|
|
217
|
+
if (seen.has(key)) {
|
|
218
|
+
ctx.addIssue({
|
|
219
|
+
code: 'custom',
|
|
220
|
+
path: ['sectionRefs', index],
|
|
221
|
+
message: `Duplicate sectionRef "${key}" in page manifest "${manifest.ref as unknown as string}"`,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
seen.add(key);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const expectedPagePrefix = `website.${manifest.ref as unknown as string}.`;
|
|
228
|
+
for (const [field, key] of [
|
|
229
|
+
['titleKey', manifest.metaLabelKeys.titleKey] as const,
|
|
230
|
+
['descriptionKey', manifest.metaLabelKeys.descriptionKey] as const,
|
|
231
|
+
]) {
|
|
232
|
+
if (!(key as unknown as string).startsWith(expectedPagePrefix)) {
|
|
233
|
+
ctx.addIssue({
|
|
234
|
+
code: 'custom',
|
|
235
|
+
path: ['metaLabelKeys', field],
|
|
236
|
+
message: `metaLabelKeys.${field} ("${key as unknown as string}") must be rooted under the page ref "${manifest.ref as unknown as string}" (expected prefix "${expectedPagePrefix}")`,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
export const WebsitePageManifestSchema: typeof _WebsitePageManifestSchema =
|
|
243
|
+
_WebsitePageManifestSchema;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Runtime shape of `WebsitePageManifest`.
|
|
247
|
+
*
|
|
248
|
+
* **Why `ComponentType<any>` and NOT `ComponentType<unknown>`** —
|
|
249
|
+
* same variance reasoning as `WebsiteSectionDefinition.Component`:
|
|
250
|
+
* `ComponentType<TProps>` is CONTRAVARIANT in `TProps`, so a
|
|
251
|
+
* narrower `TypedWebsitePageManifest<TProps>` (returned by
|
|
252
|
+
* `defineWebsitePageManifest<TProps>`) was not assignable to a
|
|
253
|
+
* `WebsitePageManifest` typed with `ComponentType<unknown>`. That
|
|
254
|
+
* blocked every consumer call-site that passed a typed manifest to
|
|
255
|
+
* helpers / registries / validators expecting the bare type
|
|
256
|
+
* (root config's `pageManifests[]`, the Astro adapter's
|
|
257
|
+
* `loadWebsitePageRuntime`, and the head-renderer). Using `any`
|
|
258
|
+
* matches the React-canonical pattern for heterogeneous component
|
|
259
|
+
* containers and reflects the runtime contract: the framework only
|
|
260
|
+
* RENDERS these components — it never constructs ad-hoc props for
|
|
261
|
+
* them; the consumer's JSX call site supplies the precise props.
|
|
262
|
+
*/
|
|
263
|
+
export interface WebsitePageManifest extends z.infer<typeof _WebsitePageManifestSchema> {
|
|
264
|
+
pageComponent: ComponentType<any>;
|
|
265
|
+
}
|
|
266
|
+
/** @wildo_source:part:end saas.website.page-manifest.schema */
|