@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,85 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.blog.frontmatter facet:layer:shared facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `BlogPostFrontmatter` — the YAML frontmatter contract every blog
|
|
7
|
+
* post MDX file under `examples/<app>/website/src/content/blog/<locale>/`
|
|
8
|
+
* MUST satisfy.
|
|
9
|
+
*
|
|
10
|
+
* **Q16=B context**: blogs are authored as full per-locale MDX files
|
|
11
|
+
* (one MDX per (slug, locale) pair), NOT as label-pack-keyed content.
|
|
12
|
+
* The frontmatter therefore carries strings directly (`title`,
|
|
13
|
+
* `summary`) rather than label keys — there is no shared
|
|
14
|
+
* label-pack-driven authoring surface for blog content. This is
|
|
15
|
+
* intentional: blog copy is long-form and locale-divergent; forcing
|
|
16
|
+
* it through a structural label-pack would either inflate the pack
|
|
17
|
+
* or fragment the prose.
|
|
18
|
+
*
|
|
19
|
+
* **Field rationale**:
|
|
20
|
+
*
|
|
21
|
+
* - `title` — REQUIRED. Renders into `<title>`, `og:title`, and the
|
|
22
|
+
* in-page H1. Authored as a direct string, deliberately.
|
|
23
|
+
* - `summary` — REQUIRED. Renders into `<meta name="description">`,
|
|
24
|
+
* `og:description`, and listing-card descriptions. Direct string.
|
|
25
|
+
* - `slug` — REQUIRED. URL slug (lowercase kebab-case). Brand prevents
|
|
26
|
+
* accidental swap with `WebsitePageRef` (same shape, different role).
|
|
27
|
+
* - `locale` — REQUIRED. The post's locale. Drives the URL prefix
|
|
28
|
+
* (`/<locale>/blog/<slug>`) and `<html lang>`. Cross-locale
|
|
29
|
+
* correspondence is established by sharing the `slug` across
|
|
30
|
+
* per-locale MDX files (e.g. `en/why-context.md` and
|
|
31
|
+
* `fr/why-context.md` are the same post in two locales).
|
|
32
|
+
* - `publishedAt` — REQUIRED. ISO-8601 date-time string. Renders into
|
|
33
|
+
* `og:article:published_time` and the listing-card date label.
|
|
34
|
+
* Stored as string (not `z.coerce.date`) because YAML frontmatter
|
|
35
|
+
* parsers preserve the original string and the SEO emitters need
|
|
36
|
+
* the original ISO formatting (no Date.toISOString() round-trip
|
|
37
|
+
* loss).
|
|
38
|
+
* - `authorRef` — REQUIRED. Identifier of the author resolved by the
|
|
39
|
+
* consumer's author registry (typically a `src/content/authors/<ref>.ts`
|
|
40
|
+
* file). Brand keeps it distinct from page/section refs.
|
|
41
|
+
* - `tags` — REQUIRED, possibly empty. Lowercase kebab-case tag
|
|
42
|
+
* identifiers. Drives `og:article:tag` and the listing-page facets.
|
|
43
|
+
* - `coverImageUrl` — OPTIONAL. CDN URL for the blog post hero image
|
|
44
|
+
* and `og:image`.
|
|
45
|
+
*
|
|
46
|
+
* **Why a separate file from the page-manifest**: a blog post is NOT
|
|
47
|
+
* a `WebsitePageManifest` (no React `pageComponent` to mount — the
|
|
48
|
+
* MDX file IS the content). The Astro adapter emits one MDX route
|
|
49
|
+
* per post via `content collections`, with `BlogPostFrontmatter`
|
|
50
|
+
* driving the route generation and the SEO emission.
|
|
51
|
+
*/
|
|
52
|
+
const _BlogPostSlugSchema = z
|
|
53
|
+
.string()
|
|
54
|
+
.min(1, { error: 'BlogPostSlug must not be empty' })
|
|
55
|
+
.max(96, { error: 'BlogPostSlug must be at most 96 characters' })
|
|
56
|
+
.regex(/^[a-z][a-z0-9-]*$/, { error: 'BlogPostSlug must be lowercase kebab-case starting with a letter' })
|
|
57
|
+
.brand();
|
|
58
|
+
export const BlogPostSlugSchema = _BlogPostSlugSchema;
|
|
59
|
+
const _BlogAuthorRefSchema = z
|
|
60
|
+
.string()
|
|
61
|
+
.min(1, { error: 'BlogAuthorRef must not be empty' })
|
|
62
|
+
.max(64, { error: 'BlogAuthorRef must be at most 64 characters' })
|
|
63
|
+
.regex(/^[a-z][a-z0-9-]*$/, { error: 'BlogAuthorRef must be lowercase kebab-case starting with a letter' })
|
|
64
|
+
.brand();
|
|
65
|
+
export const BlogAuthorRefSchema = _BlogAuthorRefSchema;
|
|
66
|
+
const _BlogPostTagSchema = z
|
|
67
|
+
.string()
|
|
68
|
+
.min(1, { error: 'BlogPostTag must not be empty' })
|
|
69
|
+
.max(48, { error: 'BlogPostTag must be at most 48 characters' })
|
|
70
|
+
.regex(/^[a-z][a-z0-9-]*$/, { error: 'BlogPostTag must be lowercase kebab-case starting with a letter' })
|
|
71
|
+
.brand();
|
|
72
|
+
export const BlogPostTagSchema = _BlogPostTagSchema;
|
|
73
|
+
const _BlogPostFrontmatterSchema = z.object({
|
|
74
|
+
title: z.string().min(1, { error: 'title must not be empty' }).max(200, { error: 'title must be at most 200 characters' }),
|
|
75
|
+
summary: z.string().min(1, { error: 'summary must not be empty' }).max(400, { error: 'summary must be at most 400 characters' }),
|
|
76
|
+
slug: _BlogPostSlugSchema,
|
|
77
|
+
locale: z.enum(AvailableLanguage),
|
|
78
|
+
publishedAt: z.iso.datetime({ error: 'publishedAt must be an ISO-8601 datetime string' }),
|
|
79
|
+
authorRef: _BlogAuthorRefSchema,
|
|
80
|
+
tags: z.array(_BlogPostTagSchema),
|
|
81
|
+
coverImageUrl: z.url({ error: 'coverImageUrl must be a valid URL' }).optional(),
|
|
82
|
+
});
|
|
83
|
+
export const BlogPostFrontmatterSchema = _BlogPostFrontmatterSchema;
|
|
84
|
+
/** @wildo_source:part:end saas.website.blog.frontmatter */
|
|
85
|
+
//# sourceMappingURL=blog-post-frontmatter.shared.schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog-post-frontmatter.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/blog/blog-post-frontmatter.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,iBAAiB,EAAE,MAAM,sCAAsC,CAAC;AAEzE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,mBAAmB,GAAG,CAAC;KAC1B,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,gCAAgC,EAAE,CAAC;KACnD,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,4CAA4C,EAAE,CAAC;KAChE,KAAK,CACJ,mBAAmB,EACnB,EAAE,KAAK,EAAE,kEAAkE,EAAE,CAC9E;KACA,KAAK,EAAkB,CAAC;AAE3B,MAAM,CAAC,MAAM,kBAAkB,GAA+B,mBAAmB,CAAC;AAIlF,MAAM,oBAAoB,GAAG,CAAC;KAC3B,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,iCAAiC,EAAE,CAAC;KACpD,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,6CAA6C,EAAE,CAAC;KACjE,KAAK,CACJ,mBAAmB,EACnB,EAAE,KAAK,EAAE,mEAAmE,EAAE,CAC/E;KACA,KAAK,EAAmB,CAAC;AAE5B,MAAM,CAAC,MAAM,mBAAmB,GAAgC,oBAAoB,CAAC;AAIrF,MAAM,kBAAkB,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;KAClD,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,2CAA2C,EAAE,CAAC;KAC/D,KAAK,CACJ,mBAAmB,EACnB,EAAE,KAAK,EAAE,iEAAiE,EAAE,CAC7E;KACA,KAAK,EAAiB,CAAC;AAE1B,MAAM,CAAC,MAAM,iBAAiB,GAA8B,kBAAkB,CAAC;AAI/E,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,sCAAsC,EAAE,CAAC;IAC1H,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,wCAAwC,EAAE,CAAC;IAChI,IAAI,EAAE,mBAAmB;IACzB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC;IACjC,WAAW,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,iDAAiD,EAAE,CAAC;IACzF,SAAS,EAAE,oBAAoB;IAC/B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC;IACjC,aAAa,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC,CAAC,QAAQ,EAAE;CAChF,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GACpC,0BAA0B,CAAC;AAG7B,2DAA2D","sourcesContent":["import { z } from 'zod';\n\nimport { AvailableLanguage } from '@wildo-ai/saas-models/public-runtime';\n\n/**\n * @wildo_source:part:start saas.website.blog.frontmatter facet:layer:shared facet:family:website\n *\n * `BlogPostFrontmatter` — the YAML frontmatter contract every blog\n * post MDX file under `examples/<app>/website/src/content/blog/<locale>/`\n * MUST satisfy.\n *\n * **Q16=B context**: blogs are authored as full per-locale MDX files\n * (one MDX per (slug, locale) pair), NOT as label-pack-keyed content.\n * The frontmatter therefore carries strings directly (`title`,\n * `summary`) rather than label keys — there is no shared\n * label-pack-driven authoring surface for blog content. This is\n * intentional: blog copy is long-form and locale-divergent; forcing\n * it through a structural label-pack would either inflate the pack\n * or fragment the prose.\n *\n * **Field rationale**:\n *\n * - `title` — REQUIRED. Renders into `<title>`, `og:title`, and the\n * in-page H1. Authored as a direct string, deliberately.\n * - `summary` — REQUIRED. Renders into `<meta name=\"description\">`,\n * `og:description`, and listing-card descriptions. Direct string.\n * - `slug` — REQUIRED. URL slug (lowercase kebab-case). Brand prevents\n * accidental swap with `WebsitePageRef` (same shape, different role).\n * - `locale` — REQUIRED. The post's locale. Drives the URL prefix\n * (`/<locale>/blog/<slug>`) and `<html lang>`. Cross-locale\n * correspondence is established by sharing the `slug` across\n * per-locale MDX files (e.g. `en/why-context.md` and\n * `fr/why-context.md` are the same post in two locales).\n * - `publishedAt` — REQUIRED. ISO-8601 date-time string. Renders into\n * `og:article:published_time` and the listing-card date label.\n * Stored as string (not `z.coerce.date`) because YAML frontmatter\n * parsers preserve the original string and the SEO emitters need\n * the original ISO formatting (no Date.toISOString() round-trip\n * loss).\n * - `authorRef` — REQUIRED. Identifier of the author resolved by the\n * consumer's author registry (typically a `src/content/authors/<ref>.ts`\n * file). Brand keeps it distinct from page/section refs.\n * - `tags` — REQUIRED, possibly empty. Lowercase kebab-case tag\n * identifiers. Drives `og:article:tag` and the listing-page facets.\n * - `coverImageUrl` — OPTIONAL. CDN URL for the blog post hero image\n * and `og:image`.\n *\n * **Why a separate file from the page-manifest**: a blog post is NOT\n * a `WebsitePageManifest` (no React `pageComponent` to mount — the\n * MDX file IS the content). The Astro adapter emits one MDX route\n * per post via `content collections`, with `BlogPostFrontmatter`\n * driving the route generation and the SEO emission.\n */\nconst _BlogPostSlugSchema = z\n .string()\n .min(1, { error: 'BlogPostSlug must not be empty' })\n .max(96, { error: 'BlogPostSlug must be at most 96 characters' })\n .regex(\n /^[a-z][a-z0-9-]*$/,\n { error: 'BlogPostSlug must be lowercase kebab-case starting with a letter' },\n )\n .brand<'BlogPostSlug'>();\n\nexport const BlogPostSlugSchema: typeof _BlogPostSlugSchema = _BlogPostSlugSchema;\n\nexport type BlogPostSlug = z.infer<typeof _BlogPostSlugSchema>;\n\nconst _BlogAuthorRefSchema = z\n .string()\n .min(1, { error: 'BlogAuthorRef must not be empty' })\n .max(64, { error: 'BlogAuthorRef must be at most 64 characters' })\n .regex(\n /^[a-z][a-z0-9-]*$/,\n { error: 'BlogAuthorRef must be lowercase kebab-case starting with a letter' },\n )\n .brand<'BlogAuthorRef'>();\n\nexport const BlogAuthorRefSchema: typeof _BlogAuthorRefSchema = _BlogAuthorRefSchema;\n\nexport type BlogAuthorRef = z.infer<typeof _BlogAuthorRefSchema>;\n\nconst _BlogPostTagSchema = z\n .string()\n .min(1, { error: 'BlogPostTag must not be empty' })\n .max(48, { error: 'BlogPostTag must be at most 48 characters' })\n .regex(\n /^[a-z][a-z0-9-]*$/,\n { error: 'BlogPostTag must be lowercase kebab-case starting with a letter' },\n )\n .brand<'BlogPostTag'>();\n\nexport const BlogPostTagSchema: typeof _BlogPostTagSchema = _BlogPostTagSchema;\n\nexport type BlogPostTag = z.infer<typeof _BlogPostTagSchema>;\n\nconst _BlogPostFrontmatterSchema = z.object({\n title: z.string().min(1, { error: 'title must not be empty' }).max(200, { error: 'title must be at most 200 characters' }),\n summary: z.string().min(1, { error: 'summary must not be empty' }).max(400, { error: 'summary must be at most 400 characters' }),\n slug: _BlogPostSlugSchema,\n locale: z.enum(AvailableLanguage),\n publishedAt: z.iso.datetime({ error: 'publishedAt must be an ISO-8601 datetime string' }),\n authorRef: _BlogAuthorRefSchema,\n tags: z.array(_BlogPostTagSchema),\n coverImageUrl: z.url({ error: 'coverImageUrl must be a valid URL' }).optional(),\n});\n\nexport const BlogPostFrontmatterSchema: typeof _BlogPostFrontmatterSchema =\n _BlogPostFrontmatterSchema;\n\nexport type BlogPostFrontmatter = z.infer<typeof _BlogPostFrontmatterSchema>;\n/** @wildo_source:part:end saas.website.blog.frontmatter */\n"]}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.design-tokens.colors facet:layer:shared facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* `WebsiteColorTokens` — minimal palette the marketing-site exposes as
|
|
6
|
+
* CSS custom properties (`--website-color-<token>`) on the
|
|
7
|
+
* `<WebsitePageLayout>` root.
|
|
8
|
+
*
|
|
9
|
+
* **Why a dedicated marketing palette** (vs. reusing
|
|
10
|
+
* `presets-components-models/design-tokens-config`):
|
|
11
|
+
*
|
|
12
|
+
* - The SaaS app's design tokens carry many semantic states (`muted`,
|
|
13
|
+
* `destructive`, `ring`, `sidebar-*`, …) that have no marketing-site
|
|
14
|
+
* analogue. Forcing the website to consume the full palette would
|
|
15
|
+
* leak app-internal semantics into a public surface.
|
|
16
|
+
* - Marketing pages need *display-grade* colors (heavily branded
|
|
17
|
+
* accents, gradient stops, hero-overlay tints) that the SaaS app's
|
|
18
|
+
* functional palette deliberately avoids.
|
|
19
|
+
* - Designers overriding website tokens (Phase 6 dogfood proves this
|
|
20
|
+
* path) should not be able to break the SaaS app by editing one
|
|
21
|
+
* tailwind.config — the two surfaces stay decoupled.
|
|
22
|
+
*
|
|
23
|
+
* **Token format**: every value is a CSS color string. Shape validation
|
|
24
|
+
* is intentionally permissive (accepts hex, rgb/rgba, hsl/hsla, oklch,
|
|
25
|
+
* `currentColor`, named colors, CSS variable references) — a strict
|
|
26
|
+
* color-string regex would reject legitimate values (oklch grammar
|
|
27
|
+
* alone is ~80 lines) and a malformed-but-syntactically-valid color
|
|
28
|
+
* fails loudly at render time.
|
|
29
|
+
*
|
|
30
|
+
* **What IS strictly enforced**: every value passes `cssSafeValue`,
|
|
31
|
+
* which rejects the six characters that can break out of a CSS
|
|
32
|
+
* property declaration (`;`, `{`, `}`, `/*`, `*\/`, newline). See the
|
|
33
|
+
* `cssSafeValue` JSDoc above for the full threat model.
|
|
34
|
+
*
|
|
35
|
+
* **Adding a token**: append the field here using `cssSafeValue(...)`,
|
|
36
|
+
* document its semantic intent in the JSDoc, and update the matching
|
|
37
|
+
* CSS-emit helper in Phase 2 (`emitWebsiteColorTokensCss`).
|
|
38
|
+
*/
|
|
39
|
+
declare const _WebsiteColorTokensSchema: z.ZodObject<{
|
|
40
|
+
background: z.ZodString;
|
|
41
|
+
foreground: z.ZodString;
|
|
42
|
+
primary: z.ZodString;
|
|
43
|
+
primaryForeground: z.ZodString;
|
|
44
|
+
accent: z.ZodString;
|
|
45
|
+
accentForeground: z.ZodString;
|
|
46
|
+
surface: z.ZodString;
|
|
47
|
+
surfaceForeground: z.ZodString;
|
|
48
|
+
border: z.ZodString;
|
|
49
|
+
muted: z.ZodString;
|
|
50
|
+
mutedForeground: z.ZodString;
|
|
51
|
+
}, z.core.$strip>;
|
|
52
|
+
export declare const WebsiteColorTokensSchema: typeof _WebsiteColorTokensSchema;
|
|
53
|
+
export type WebsiteColorTokens = z.infer<typeof _WebsiteColorTokensSchema>;
|
|
54
|
+
/** @wildo_source:part:end saas.website.design-tokens.colors */
|
|
55
|
+
/**
|
|
56
|
+
* @wildo_source:part:start saas.website.design-tokens.radius facet:layer:shared facet:family:website
|
|
57
|
+
*
|
|
58
|
+
* `WebsiteRadiusTokens` — border-radius scale exposed as
|
|
59
|
+
* `--website-radius-<token>` CSS variables.
|
|
60
|
+
*
|
|
61
|
+
* Marketing radii are intentionally a separate token set from the
|
|
62
|
+
* SaaS app's radii: the website typically wants larger pill / hero
|
|
63
|
+
* radii than the app's compact-UI radii. Keeping them separate lets
|
|
64
|
+
* either surface evolve without coupling.
|
|
65
|
+
*
|
|
66
|
+
* Values are CSS length strings (`'0.25rem'`, `'12px'`,
|
|
67
|
+
* `'9999px'`). Same permissive-shape + strict-CSS-injection
|
|
68
|
+
* rationale as `WebsiteColorTokens` (every value runs through
|
|
69
|
+
* `cssSafeValue`).
|
|
70
|
+
*/
|
|
71
|
+
declare const _WebsiteRadiusTokensSchema: z.ZodObject<{
|
|
72
|
+
none: z.ZodString;
|
|
73
|
+
sm: z.ZodString;
|
|
74
|
+
md: z.ZodString;
|
|
75
|
+
lg: z.ZodString;
|
|
76
|
+
xl: z.ZodString;
|
|
77
|
+
full: z.ZodString;
|
|
78
|
+
}, z.core.$strip>;
|
|
79
|
+
export declare const WebsiteRadiusTokensSchema: typeof _WebsiteRadiusTokensSchema;
|
|
80
|
+
export type WebsiteRadiusTokens = z.infer<typeof _WebsiteRadiusTokensSchema>;
|
|
81
|
+
/** @wildo_source:part:end saas.website.design-tokens.radius */
|
|
82
|
+
/**
|
|
83
|
+
* @wildo_source:part:start saas.website.design-tokens.bundle facet:layer:shared facet:family:website
|
|
84
|
+
*
|
|
85
|
+
* `WebsiteDesignTokens` — the complete design-token bundle attached to
|
|
86
|
+
* `WebsiteRootConfig.designTokens`. Currently colors + radii only
|
|
87
|
+
* (per plan Phase 1 scope: "WebsiteColorTokensSchema + WebsiteRadiusTokensSchema (only)").
|
|
88
|
+
*
|
|
89
|
+
* Future expansions (typography scale, motion tokens, shadow elevation)
|
|
90
|
+
* will land as additional fields here without breaking the
|
|
91
|
+
* `WebsiteRootConfig` shape.
|
|
92
|
+
*/
|
|
93
|
+
declare const _WebsiteDesignTokensSchema: z.ZodObject<{
|
|
94
|
+
colors: z.ZodObject<{
|
|
95
|
+
background: z.ZodString;
|
|
96
|
+
foreground: z.ZodString;
|
|
97
|
+
primary: z.ZodString;
|
|
98
|
+
primaryForeground: z.ZodString;
|
|
99
|
+
accent: z.ZodString;
|
|
100
|
+
accentForeground: z.ZodString;
|
|
101
|
+
surface: z.ZodString;
|
|
102
|
+
surfaceForeground: z.ZodString;
|
|
103
|
+
border: z.ZodString;
|
|
104
|
+
muted: z.ZodString;
|
|
105
|
+
mutedForeground: z.ZodString;
|
|
106
|
+
}, z.core.$strip>;
|
|
107
|
+
radii: z.ZodObject<{
|
|
108
|
+
none: z.ZodString;
|
|
109
|
+
sm: z.ZodString;
|
|
110
|
+
md: z.ZodString;
|
|
111
|
+
lg: z.ZodString;
|
|
112
|
+
xl: z.ZodString;
|
|
113
|
+
full: z.ZodString;
|
|
114
|
+
}, z.core.$strip>;
|
|
115
|
+
}, z.core.$strip>;
|
|
116
|
+
export declare const WebsiteDesignTokensSchema: typeof _WebsiteDesignTokensSchema;
|
|
117
|
+
export type WebsiteDesignTokens = z.infer<typeof _WebsiteDesignTokensSchema>;
|
|
118
|
+
export {};
|
|
119
|
+
/** @wildo_source:part:end saas.website.design-tokens.bundle */
|
|
120
|
+
//# sourceMappingURL=website-design-tokens.shared.schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website-design-tokens.shared.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/design-tokens/website-design-tokens.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AA4DxB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,QAAA,MAAM,yBAAyB;;;;;;;;;;;;iBAY7B,CAAC;AAEH,eAAO,MAAM,wBAAwB,EAAE,OAAO,yBAAqD,CAAC;AAEpG,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAC3E,+DAA+D;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,QAAA,MAAM,0BAA0B;;;;;;;iBAO9B,CAAC;AAEH,eAAO,MAAM,yBAAyB,EAAE,OAAO,0BAAuD,CAAC;AAEvG,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAC7E,+DAA+D;AAE/D;;;;;;;;;;GAUG;AACH,QAAA,MAAM,0BAA0B;;;;;;;;;;;;;;;;;;;;;;iBAG9B,CAAC;AAEH,eAAO,MAAM,yBAAyB,EAAE,OAAO,0BAAuD,CAAC;AAEvG,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;;AAC7E,+DAA+D"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* CSS-injection-safe value validator shared by every design-token field.
|
|
4
|
+
*
|
|
5
|
+
* **Threat model** — TWO escape surfaces, BOTH covered:
|
|
6
|
+
*
|
|
7
|
+
* 1. CSS *declaration* break-out (Phase 6 third audit fix H-α):
|
|
8
|
+
* the renderer `renderWebsiteDesignTokensCss` interpolates token
|
|
9
|
+
* values **raw** into a `:root { --website-<group>-<token>: <value>; }`
|
|
10
|
+
* block. A value containing any of `;`, `}`, `{`, `/*`, `*\/`, or a
|
|
11
|
+
* newline can break out of the property declaration and inject
|
|
12
|
+
* arbitrary CSS — at minimum a config typo silently corrupts the
|
|
13
|
+
* page; at worst a malicious or untrusted config author injects a
|
|
14
|
+
* `body { display: none; }` rule that blanks the entire site.
|
|
15
|
+
*
|
|
16
|
+
* 2. HTML *style-element* break-out (Phase 6 fourth audit fix H-Χ):
|
|
17
|
+
* the rendered `:root { ... }` block is itself wrapped in an inline
|
|
18
|
+
* `<style>…</style>` element by the head renderer. Per the HTML spec
|
|
19
|
+
* the `<style>` element is a *raw-text* element — its only
|
|
20
|
+
* terminator is the literal sequence `</style`. A token value
|
|
21
|
+
* containing `<` (or, for symmetry, `>`) can therefore close the
|
|
22
|
+
* `<style>` element early and follow up with `<svg onload=alert(1)>`
|
|
23
|
+
* or any other HTML / JS payload, which the H-α deny-list (which
|
|
24
|
+
* did NOT forbid `<` / `>`) failed to catch. CSS values legitimately
|
|
25
|
+
* need NEITHER `<` NOR `>` (no CSS grammar uses them — no color,
|
|
26
|
+
* length, variable reference, calc expression, or functional
|
|
27
|
+
* notation does), so denying both is conservative and correct.
|
|
28
|
+
*
|
|
29
|
+
* **Why validate at the schema layer (not inside the renderer)**:
|
|
30
|
+
* 1. Fail at config load (build-time / boot-time), not at every
|
|
31
|
+
* `<head>` render — a malformed token surfaces in the schema-parse
|
|
32
|
+
* error message instead of silently emitting broken CSS / HTML.
|
|
33
|
+
* 2. Single source of truth — every field that flows into the
|
|
34
|
+
* `<style>`-wrapped CSS block gets the same guard (impossible to
|
|
35
|
+
* add a future token field that bypasses the renderer's escape
|
|
36
|
+
* pass).
|
|
37
|
+
* 3. The renderer stays a simple interpolator, which makes its own
|
|
38
|
+
* audit surface trivially small.
|
|
39
|
+
*
|
|
40
|
+
* **Why a `.refine` over a strict color-string regex**: colors accept
|
|
41
|
+
* `currentColor`, named colors, `var(--foo)` references, oklch
|
|
42
|
+
* functional notation, etc. — a strict positive grammar is ~80 lines
|
|
43
|
+
* and high-maintenance. A negative deny-list of the eight characters
|
|
44
|
+
* that are LITERALLY the only ones that can break out of either the CSS
|
|
45
|
+
* property declaration OR the wrapping `<style>` element is small,
|
|
46
|
+
* complete, and obviously correct.
|
|
47
|
+
*/
|
|
48
|
+
const CSS_INJECTION_FORBIDDEN_PATTERN = /[;{}<>\n\r]|\/\*|\*\//;
|
|
49
|
+
const cssSafeValue = (fieldName) => z
|
|
50
|
+
.string()
|
|
51
|
+
.min(1, { error: `${fieldName} must not be empty` })
|
|
52
|
+
.refine((value) => !CSS_INJECTION_FORBIDDEN_PATTERN.test(value), {
|
|
53
|
+
error: `${fieldName} must not contain CSS-injection or HTML break-out ` +
|
|
54
|
+
`metacharacters (; { } < > /* */ newline) — token values are ` +
|
|
55
|
+
`interpolated raw into <style>:root { --website-...: <value>; }</style>`,
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* @wildo_source:part:start saas.website.design-tokens.colors facet:layer:shared facet:family:website
|
|
59
|
+
*
|
|
60
|
+
* `WebsiteColorTokens` — minimal palette the marketing-site exposes as
|
|
61
|
+
* CSS custom properties (`--website-color-<token>`) on the
|
|
62
|
+
* `<WebsitePageLayout>` root.
|
|
63
|
+
*
|
|
64
|
+
* **Why a dedicated marketing palette** (vs. reusing
|
|
65
|
+
* `presets-components-models/design-tokens-config`):
|
|
66
|
+
*
|
|
67
|
+
* - The SaaS app's design tokens carry many semantic states (`muted`,
|
|
68
|
+
* `destructive`, `ring`, `sidebar-*`, …) that have no marketing-site
|
|
69
|
+
* analogue. Forcing the website to consume the full palette would
|
|
70
|
+
* leak app-internal semantics into a public surface.
|
|
71
|
+
* - Marketing pages need *display-grade* colors (heavily branded
|
|
72
|
+
* accents, gradient stops, hero-overlay tints) that the SaaS app's
|
|
73
|
+
* functional palette deliberately avoids.
|
|
74
|
+
* - Designers overriding website tokens (Phase 6 dogfood proves this
|
|
75
|
+
* path) should not be able to break the SaaS app by editing one
|
|
76
|
+
* tailwind.config — the two surfaces stay decoupled.
|
|
77
|
+
*
|
|
78
|
+
* **Token format**: every value is a CSS color string. Shape validation
|
|
79
|
+
* is intentionally permissive (accepts hex, rgb/rgba, hsl/hsla, oklch,
|
|
80
|
+
* `currentColor`, named colors, CSS variable references) — a strict
|
|
81
|
+
* color-string regex would reject legitimate values (oklch grammar
|
|
82
|
+
* alone is ~80 lines) and a malformed-but-syntactically-valid color
|
|
83
|
+
* fails loudly at render time.
|
|
84
|
+
*
|
|
85
|
+
* **What IS strictly enforced**: every value passes `cssSafeValue`,
|
|
86
|
+
* which rejects the six characters that can break out of a CSS
|
|
87
|
+
* property declaration (`;`, `{`, `}`, `/*`, `*\/`, newline). See the
|
|
88
|
+
* `cssSafeValue` JSDoc above for the full threat model.
|
|
89
|
+
*
|
|
90
|
+
* **Adding a token**: append the field here using `cssSafeValue(...)`,
|
|
91
|
+
* document its semantic intent in the JSDoc, and update the matching
|
|
92
|
+
* CSS-emit helper in Phase 2 (`emitWebsiteColorTokensCss`).
|
|
93
|
+
*/
|
|
94
|
+
const _WebsiteColorTokensSchema = z.object({
|
|
95
|
+
background: cssSafeValue('background color'),
|
|
96
|
+
foreground: cssSafeValue('foreground color'),
|
|
97
|
+
primary: cssSafeValue('primary color'),
|
|
98
|
+
primaryForeground: cssSafeValue('primaryForeground color'),
|
|
99
|
+
accent: cssSafeValue('accent color'),
|
|
100
|
+
accentForeground: cssSafeValue('accentForeground color'),
|
|
101
|
+
surface: cssSafeValue('surface color'),
|
|
102
|
+
surfaceForeground: cssSafeValue('surfaceForeground color'),
|
|
103
|
+
border: cssSafeValue('border color'),
|
|
104
|
+
muted: cssSafeValue('muted color'),
|
|
105
|
+
mutedForeground: cssSafeValue('mutedForeground color'),
|
|
106
|
+
});
|
|
107
|
+
export const WebsiteColorTokensSchema = _WebsiteColorTokensSchema;
|
|
108
|
+
/** @wildo_source:part:end saas.website.design-tokens.colors */
|
|
109
|
+
/**
|
|
110
|
+
* @wildo_source:part:start saas.website.design-tokens.radius facet:layer:shared facet:family:website
|
|
111
|
+
*
|
|
112
|
+
* `WebsiteRadiusTokens` — border-radius scale exposed as
|
|
113
|
+
* `--website-radius-<token>` CSS variables.
|
|
114
|
+
*
|
|
115
|
+
* Marketing radii are intentionally a separate token set from the
|
|
116
|
+
* SaaS app's radii: the website typically wants larger pill / hero
|
|
117
|
+
* radii than the app's compact-UI radii. Keeping them separate lets
|
|
118
|
+
* either surface evolve without coupling.
|
|
119
|
+
*
|
|
120
|
+
* Values are CSS length strings (`'0.25rem'`, `'12px'`,
|
|
121
|
+
* `'9999px'`). Same permissive-shape + strict-CSS-injection
|
|
122
|
+
* rationale as `WebsiteColorTokens` (every value runs through
|
|
123
|
+
* `cssSafeValue`).
|
|
124
|
+
*/
|
|
125
|
+
const _WebsiteRadiusTokensSchema = z.object({
|
|
126
|
+
none: cssSafeValue('none radius'),
|
|
127
|
+
sm: cssSafeValue('sm radius'),
|
|
128
|
+
md: cssSafeValue('md radius'),
|
|
129
|
+
lg: cssSafeValue('lg radius'),
|
|
130
|
+
xl: cssSafeValue('xl radius'),
|
|
131
|
+
full: cssSafeValue('full radius'),
|
|
132
|
+
});
|
|
133
|
+
export const WebsiteRadiusTokensSchema = _WebsiteRadiusTokensSchema;
|
|
134
|
+
/** @wildo_source:part:end saas.website.design-tokens.radius */
|
|
135
|
+
/**
|
|
136
|
+
* @wildo_source:part:start saas.website.design-tokens.bundle facet:layer:shared facet:family:website
|
|
137
|
+
*
|
|
138
|
+
* `WebsiteDesignTokens` — the complete design-token bundle attached to
|
|
139
|
+
* `WebsiteRootConfig.designTokens`. Currently colors + radii only
|
|
140
|
+
* (per plan Phase 1 scope: "WebsiteColorTokensSchema + WebsiteRadiusTokensSchema (only)").
|
|
141
|
+
*
|
|
142
|
+
* Future expansions (typography scale, motion tokens, shadow elevation)
|
|
143
|
+
* will land as additional fields here without breaking the
|
|
144
|
+
* `WebsiteRootConfig` shape.
|
|
145
|
+
*/
|
|
146
|
+
const _WebsiteDesignTokensSchema = z.object({
|
|
147
|
+
colors: _WebsiteColorTokensSchema,
|
|
148
|
+
radii: _WebsiteRadiusTokensSchema,
|
|
149
|
+
});
|
|
150
|
+
export const WebsiteDesignTokensSchema = _WebsiteDesignTokensSchema;
|
|
151
|
+
/** @wildo_source:part:end saas.website.design-tokens.bundle */
|
|
152
|
+
//# sourceMappingURL=website-design-tokens.shared.schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website-design-tokens.shared.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/design-tokens/website-design-tokens.shared.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,MAAM,+BAA+B,GAAG,uBAAuB,CAAC;AAChE,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAe,EAAE,CACtD,CAAC;KACE,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,SAAS,oBAAoB,EAAE,CAAC;KACnD,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,+BAA+B,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;IAC/D,KAAK,EACH,GAAG,SAAS,oDAAoD;QAChE,8DAA8D;QAC9D,wEAAwE;CAC3E,CAA2B,CAAC;AAEjC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC;IAC5C,UAAU,EAAE,YAAY,CAAC,kBAAkB,CAAC;IAC5C,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC;IACtC,iBAAiB,EAAE,YAAY,CAAC,yBAAyB,CAAC;IAC1D,MAAM,EAAE,YAAY,CAAC,cAAc,CAAC;IACpC,gBAAgB,EAAE,YAAY,CAAC,wBAAwB,CAAC;IACxD,OAAO,EAAE,YAAY,CAAC,eAAe,CAAC;IACtC,iBAAiB,EAAE,YAAY,CAAC,yBAAyB,CAAC;IAC1D,MAAM,EAAE,YAAY,CAAC,cAAc,CAAC;IACpC,KAAK,EAAE,YAAY,CAAC,aAAa,CAAC;IAClC,eAAe,EAAE,YAAY,CAAC,uBAAuB,CAAC;CACvD,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAqC,yBAAyB,CAAC;AAGpG,+DAA+D;AAE/D;;;;;;;;;;;;;;;GAeG;AACH,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,IAAI,EAAE,YAAY,CAAC,aAAa,CAAC;IACjC,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC;IAC7B,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC;IAC7B,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC;IAC7B,EAAE,EAAE,YAAY,CAAC,WAAW,CAAC;IAC7B,IAAI,EAAE,YAAY,CAAC,aAAa,CAAC;CAClC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAsC,0BAA0B,CAAC;AAGvG,+DAA+D;AAE/D;;;;;;;;;;GAUG;AACH,MAAM,0BAA0B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,MAAM,EAAE,yBAAyB;IACjC,KAAK,EAAE,0BAA0B;CAClC,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,yBAAyB,GAAsC,0BAA0B,CAAC;AAGvG,+DAA+D","sourcesContent":["import { z } from 'zod';\n\n/**\n * CSS-injection-safe value validator shared by every design-token field.\n *\n * **Threat model** — TWO escape surfaces, BOTH covered:\n *\n * 1. CSS *declaration* break-out (Phase 6 third audit fix H-α):\n * the renderer `renderWebsiteDesignTokensCss` interpolates token\n * values **raw** into a `:root { --website-<group>-<token>: <value>; }`\n * block. A value containing any of `;`, `}`, `{`, `/*`, `*\\/`, or a\n * newline can break out of the property declaration and inject\n * arbitrary CSS — at minimum a config typo silently corrupts the\n * page; at worst a malicious or untrusted config author injects a\n * `body { display: none; }` rule that blanks the entire site.\n *\n * 2. HTML *style-element* break-out (Phase 6 fourth audit fix H-Χ):\n * the rendered `:root { ... }` block is itself wrapped in an inline\n * `<style>…</style>` element by the head renderer. Per the HTML spec\n * the `<style>` element is a *raw-text* element — its only\n * terminator is the literal sequence `</style`. A token value\n * containing `<` (or, for symmetry, `>`) can therefore close the\n * `<style>` element early and follow up with `<svg onload=alert(1)>`\n * or any other HTML / JS payload, which the H-α deny-list (which\n * did NOT forbid `<` / `>`) failed to catch. CSS values legitimately\n * need NEITHER `<` NOR `>` (no CSS grammar uses them — no color,\n * length, variable reference, calc expression, or functional\n * notation does), so denying both is conservative and correct.\n *\n * **Why validate at the schema layer (not inside the renderer)**:\n * 1. Fail at config load (build-time / boot-time), not at every\n * `<head>` render — a malformed token surfaces in the schema-parse\n * error message instead of silently emitting broken CSS / HTML.\n * 2. Single source of truth — every field that flows into the\n * `<style>`-wrapped CSS block gets the same guard (impossible to\n * add a future token field that bypasses the renderer's escape\n * pass).\n * 3. The renderer stays a simple interpolator, which makes its own\n * audit surface trivially small.\n *\n * **Why a `.refine` over a strict color-string regex**: colors accept\n * `currentColor`, named colors, `var(--foo)` references, oklch\n * functional notation, etc. — a strict positive grammar is ~80 lines\n * and high-maintenance. A negative deny-list of the eight characters\n * that are LITERALLY the only ones that can break out of either the CSS\n * property declaration OR the wrapping `<style>` element is small,\n * complete, and obviously correct.\n */\nconst CSS_INJECTION_FORBIDDEN_PATTERN = /[;{}<>\\n\\r]|\\/\\*|\\*\\//;\nconst cssSafeValue = (fieldName: string): z.ZodString =>\n z\n .string()\n .min(1, { error: `${fieldName} must not be empty` })\n .refine((value) => !CSS_INJECTION_FORBIDDEN_PATTERN.test(value), {\n error:\n `${fieldName} must not contain CSS-injection or HTML break-out ` +\n `metacharacters (; { } < > /* */ newline) — token values are ` +\n `interpolated raw into <style>:root { --website-...: <value>; }</style>`,\n }) as unknown as z.ZodString;\n\n/**\n * @wildo_source:part:start saas.website.design-tokens.colors facet:layer:shared facet:family:website\n *\n * `WebsiteColorTokens` — minimal palette the marketing-site exposes as\n * CSS custom properties (`--website-color-<token>`) on the\n * `<WebsitePageLayout>` root.\n *\n * **Why a dedicated marketing palette** (vs. reusing\n * `presets-components-models/design-tokens-config`):\n *\n * - The SaaS app's design tokens carry many semantic states (`muted`,\n * `destructive`, `ring`, `sidebar-*`, …) that have no marketing-site\n * analogue. Forcing the website to consume the full palette would\n * leak app-internal semantics into a public surface.\n * - Marketing pages need *display-grade* colors (heavily branded\n * accents, gradient stops, hero-overlay tints) that the SaaS app's\n * functional palette deliberately avoids.\n * - Designers overriding website tokens (Phase 6 dogfood proves this\n * path) should not be able to break the SaaS app by editing one\n * tailwind.config — the two surfaces stay decoupled.\n *\n * **Token format**: every value is a CSS color string. Shape validation\n * is intentionally permissive (accepts hex, rgb/rgba, hsl/hsla, oklch,\n * `currentColor`, named colors, CSS variable references) — a strict\n * color-string regex would reject legitimate values (oklch grammar\n * alone is ~80 lines) and a malformed-but-syntactically-valid color\n * fails loudly at render time.\n *\n * **What IS strictly enforced**: every value passes `cssSafeValue`,\n * which rejects the six characters that can break out of a CSS\n * property declaration (`;`, `{`, `}`, `/*`, `*\\/`, newline). See the\n * `cssSafeValue` JSDoc above for the full threat model.\n *\n * **Adding a token**: append the field here using `cssSafeValue(...)`,\n * document its semantic intent in the JSDoc, and update the matching\n * CSS-emit helper in Phase 2 (`emitWebsiteColorTokensCss`).\n */\nconst _WebsiteColorTokensSchema = z.object({\n background: cssSafeValue('background color'),\n foreground: cssSafeValue('foreground color'),\n primary: cssSafeValue('primary color'),\n primaryForeground: cssSafeValue('primaryForeground color'),\n accent: cssSafeValue('accent color'),\n accentForeground: cssSafeValue('accentForeground color'),\n surface: cssSafeValue('surface color'),\n surfaceForeground: cssSafeValue('surfaceForeground color'),\n border: cssSafeValue('border color'),\n muted: cssSafeValue('muted color'),\n mutedForeground: cssSafeValue('mutedForeground color'),\n});\n\nexport const WebsiteColorTokensSchema: typeof _WebsiteColorTokensSchema = _WebsiteColorTokensSchema;\n\nexport type WebsiteColorTokens = z.infer<typeof _WebsiteColorTokensSchema>;\n/** @wildo_source:part:end saas.website.design-tokens.colors */\n\n/**\n * @wildo_source:part:start saas.website.design-tokens.radius facet:layer:shared facet:family:website\n *\n * `WebsiteRadiusTokens` — border-radius scale exposed as\n * `--website-radius-<token>` CSS variables.\n *\n * Marketing radii are intentionally a separate token set from the\n * SaaS app's radii: the website typically wants larger pill / hero\n * radii than the app's compact-UI radii. Keeping them separate lets\n * either surface evolve without coupling.\n *\n * Values are CSS length strings (`'0.25rem'`, `'12px'`,\n * `'9999px'`). Same permissive-shape + strict-CSS-injection\n * rationale as `WebsiteColorTokens` (every value runs through\n * `cssSafeValue`).\n */\nconst _WebsiteRadiusTokensSchema = z.object({\n none: cssSafeValue('none radius'),\n sm: cssSafeValue('sm radius'),\n md: cssSafeValue('md radius'),\n lg: cssSafeValue('lg radius'),\n xl: cssSafeValue('xl radius'),\n full: cssSafeValue('full radius'),\n});\n\nexport const WebsiteRadiusTokensSchema: typeof _WebsiteRadiusTokensSchema = _WebsiteRadiusTokensSchema;\n\nexport type WebsiteRadiusTokens = z.infer<typeof _WebsiteRadiusTokensSchema>;\n/** @wildo_source:part:end saas.website.design-tokens.radius */\n\n/**\n * @wildo_source:part:start saas.website.design-tokens.bundle facet:layer:shared facet:family:website\n *\n * `WebsiteDesignTokens` — the complete design-token bundle attached to\n * `WebsiteRootConfig.designTokens`. Currently colors + radii only\n * (per plan Phase 1 scope: \"WebsiteColorTokensSchema + WebsiteRadiusTokensSchema (only)\").\n *\n * Future expansions (typography scale, motion tokens, shadow elevation)\n * will land as additional fields here without breaking the\n * `WebsiteRootConfig` shape.\n */\nconst _WebsiteDesignTokensSchema = z.object({\n colors: _WebsiteColorTokensSchema,\n radii: _WebsiteRadiusTokensSchema,\n});\n\nexport const WebsiteDesignTokensSchema: typeof _WebsiteDesignTokensSchema = _WebsiteDesignTokensSchema;\n\nexport type WebsiteDesignTokens = z.infer<typeof _WebsiteDesignTokensSchema>;\n/** @wildo_source:part:end saas.website.design-tokens.bundle */\n"]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.label-keys.format facet:layer:shared facet:family:i18n
|
|
4
|
+
*
|
|
5
|
+
* `WebsiteLabelKey` — branded i18n key for marketing-site copy.
|
|
6
|
+
*
|
|
7
|
+
* **Format**: `website.<pageRef>.<sectionRef>.<slotPath>`
|
|
8
|
+
*
|
|
9
|
+
* - `website.` — fixed namespace prefix that disambiguates marketing-site
|
|
10
|
+
* labels from app i18n keys (`labels.`, `errors.`, `notifications.`).
|
|
11
|
+
* Marketing-site label packs ship as a separate file per locale
|
|
12
|
+
* (`website/src/i18n/<locale>.json`) so they never bloat the SaaS app
|
|
13
|
+
* bundle.
|
|
14
|
+
* - `<pageRef>` — kebab-case page ref (validated independently by
|
|
15
|
+
* `WebsitePageRefSchema`).
|
|
16
|
+
* - `<sectionRef>` — kebab-case section ref (validated independently by
|
|
17
|
+
* `WebsiteSectionRefSchema`).
|
|
18
|
+
* - `<slotPath>` — leaf identifier the section consumes via
|
|
19
|
+
* `useWebsiteLabel('<slotPath>')` (Phase 2). Allows dots for nested
|
|
20
|
+
* structures (`'cta.primary'`), brackets for indexed array slots
|
|
21
|
+
* (`'tiers[0].name'`), and hyphens for multi-word leaves.
|
|
22
|
+
*
|
|
23
|
+
* **Examples**:
|
|
24
|
+
* - `website.landing.hero.headline`
|
|
25
|
+
* - `website.landing.hero.cta.primary`
|
|
26
|
+
* - `website.pricing.tiers.tiers[0].name`
|
|
27
|
+
* - `website.contact.form.field.email-placeholder`
|
|
28
|
+
*
|
|
29
|
+
* **Why a brand**: prevents the "label key as bare string" failure mode
|
|
30
|
+
* where a typo in `'webiste.landing.hero.headline'` (note the typo) silently
|
|
31
|
+
* resolves to undefined at runtime. Sections must accept
|
|
32
|
+
* `WebsiteLabelKey`-typed props; consumers go through
|
|
33
|
+
* `WebsiteLabelKeySchema.parse(...)` (or the manifest's `metaLabelKeys`
|
|
34
|
+
* fields, which are typed against this schema) before a string flows into
|
|
35
|
+
* label resolution.
|
|
36
|
+
*
|
|
37
|
+
* **Why a single regex**: the three nested refs (`page`, `section`, `slot`)
|
|
38
|
+
* are validated separately by their own schemas inside the `defineWebsiteSection`
|
|
39
|
+
* factory and `WebsitePageManifestSchema`. This schema's job is to validate
|
|
40
|
+
* a fully-composed key — typically inserted directly into JSX as a string
|
|
41
|
+
* literal — without re-parsing the parts.
|
|
42
|
+
*/
|
|
43
|
+
declare const _WebsiteLabelKeySchema: z.core.$ZodBranded<z.ZodString, "WebsiteLabelKey", "out">;
|
|
44
|
+
export declare const WebsiteLabelKeySchema: typeof _WebsiteLabelKeySchema;
|
|
45
|
+
export type WebsiteLabelKey = z.infer<typeof _WebsiteLabelKeySchema>;
|
|
46
|
+
/** @wildo_source:part:end saas.website.label-keys.format */
|
|
47
|
+
/**
|
|
48
|
+
* @wildo_source:part:start saas.website.label-keys.compose-helper facet:layer:shared facet:family:i18n
|
|
49
|
+
*
|
|
50
|
+
* `composeWebsiteLabelKey` — pure helper that composes a `WebsiteLabelKey`
|
|
51
|
+
* from its three parts. Used by `useWebsiteLabel` (Phase 2) and by the
|
|
52
|
+
* label-pack validator to materialize the keys a section commits to.
|
|
53
|
+
*
|
|
54
|
+
* Returns the key as a parsed `WebsiteLabelKey` (brand applied), throwing
|
|
55
|
+
* if the composed string fails the format check (defensive guard against
|
|
56
|
+
* stale refs / inconsistent slot paths leaking past type-system narrowing).
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* composeWebsiteLabelKey({
|
|
60
|
+
* pageRef: WebsitePageRefSchema.parse('landing'),
|
|
61
|
+
* sectionRef: WebsiteSectionRefSchema.parse('hero'),
|
|
62
|
+
* slotPath: 'headline',
|
|
63
|
+
* });
|
|
64
|
+
* // -> WebsiteLabelKey ("website.landing.hero.headline")
|
|
65
|
+
*/
|
|
66
|
+
export declare function composeWebsiteLabelKey(parts: {
|
|
67
|
+
pageRef: string;
|
|
68
|
+
sectionRef: string;
|
|
69
|
+
slotPath: string;
|
|
70
|
+
}): WebsiteLabelKey;
|
|
71
|
+
export {};
|
|
72
|
+
/** @wildo_source:part:end saas.website.label-keys.compose-helper */
|
|
73
|
+
//# sourceMappingURL=website-label-key.schemas.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website-label-key.schemas.d.ts","sourceRoot":"","sources":["../../../../../src/schemas/label-keys/website-label-key.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,QAAA,MAAM,sBAAsB,2DAWC,CAAC;AAE9B,eAAO,MAAM,qBAAqB,EAAE,OAAO,sBAA+C,CAAC;AAE3F,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AACrE,4DAA4D;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE;IAC5C,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,eAAe,CAElB;;AACD,oEAAoE"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.label-keys.format facet:layer:shared facet:family:i18n
|
|
4
|
+
*
|
|
5
|
+
* `WebsiteLabelKey` — branded i18n key for marketing-site copy.
|
|
6
|
+
*
|
|
7
|
+
* **Format**: `website.<pageRef>.<sectionRef>.<slotPath>`
|
|
8
|
+
*
|
|
9
|
+
* - `website.` — fixed namespace prefix that disambiguates marketing-site
|
|
10
|
+
* labels from app i18n keys (`labels.`, `errors.`, `notifications.`).
|
|
11
|
+
* Marketing-site label packs ship as a separate file per locale
|
|
12
|
+
* (`website/src/i18n/<locale>.json`) so they never bloat the SaaS app
|
|
13
|
+
* bundle.
|
|
14
|
+
* - `<pageRef>` — kebab-case page ref (validated independently by
|
|
15
|
+
* `WebsitePageRefSchema`).
|
|
16
|
+
* - `<sectionRef>` — kebab-case section ref (validated independently by
|
|
17
|
+
* `WebsiteSectionRefSchema`).
|
|
18
|
+
* - `<slotPath>` — leaf identifier the section consumes via
|
|
19
|
+
* `useWebsiteLabel('<slotPath>')` (Phase 2). Allows dots for nested
|
|
20
|
+
* structures (`'cta.primary'`), brackets for indexed array slots
|
|
21
|
+
* (`'tiers[0].name'`), and hyphens for multi-word leaves.
|
|
22
|
+
*
|
|
23
|
+
* **Examples**:
|
|
24
|
+
* - `website.landing.hero.headline`
|
|
25
|
+
* - `website.landing.hero.cta.primary`
|
|
26
|
+
* - `website.pricing.tiers.tiers[0].name`
|
|
27
|
+
* - `website.contact.form.field.email-placeholder`
|
|
28
|
+
*
|
|
29
|
+
* **Why a brand**: prevents the "label key as bare string" failure mode
|
|
30
|
+
* where a typo in `'webiste.landing.hero.headline'` (note the typo) silently
|
|
31
|
+
* resolves to undefined at runtime. Sections must accept
|
|
32
|
+
* `WebsiteLabelKey`-typed props; consumers go through
|
|
33
|
+
* `WebsiteLabelKeySchema.parse(...)` (or the manifest's `metaLabelKeys`
|
|
34
|
+
* fields, which are typed against this schema) before a string flows into
|
|
35
|
+
* label resolution.
|
|
36
|
+
*
|
|
37
|
+
* **Why a single regex**: the three nested refs (`page`, `section`, `slot`)
|
|
38
|
+
* are validated separately by their own schemas inside the `defineWebsiteSection`
|
|
39
|
+
* factory and `WebsitePageManifestSchema`. This schema's job is to validate
|
|
40
|
+
* a fully-composed key — typically inserted directly into JSX as a string
|
|
41
|
+
* literal — without re-parsing the parts.
|
|
42
|
+
*/
|
|
43
|
+
const _WebsiteLabelKeySchema = z
|
|
44
|
+
.string()
|
|
45
|
+
.min(10, { error: 'WebsiteLabelKey is too short (must include website.<page>.<section>.<slot>)' })
|
|
46
|
+
.max(256, { error: 'WebsiteLabelKey must be at most 256 characters' })
|
|
47
|
+
.regex(/^website\.[a-z][a-z0-9-]*\.[a-z][a-z0-9-]*\.[a-zA-Z0-9.\[\]_-]+$/, {
|
|
48
|
+
error: '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")',
|
|
49
|
+
})
|
|
50
|
+
.brand();
|
|
51
|
+
export const WebsiteLabelKeySchema = _WebsiteLabelKeySchema;
|
|
52
|
+
/** @wildo_source:part:end saas.website.label-keys.format */
|
|
53
|
+
/**
|
|
54
|
+
* @wildo_source:part:start saas.website.label-keys.compose-helper facet:layer:shared facet:family:i18n
|
|
55
|
+
*
|
|
56
|
+
* `composeWebsiteLabelKey` — pure helper that composes a `WebsiteLabelKey`
|
|
57
|
+
* from its three parts. Used by `useWebsiteLabel` (Phase 2) and by the
|
|
58
|
+
* label-pack validator to materialize the keys a section commits to.
|
|
59
|
+
*
|
|
60
|
+
* Returns the key as a parsed `WebsiteLabelKey` (brand applied), throwing
|
|
61
|
+
* if the composed string fails the format check (defensive guard against
|
|
62
|
+
* stale refs / inconsistent slot paths leaking past type-system narrowing).
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* composeWebsiteLabelKey({
|
|
66
|
+
* pageRef: WebsitePageRefSchema.parse('landing'),
|
|
67
|
+
* sectionRef: WebsiteSectionRefSchema.parse('hero'),
|
|
68
|
+
* slotPath: 'headline',
|
|
69
|
+
* });
|
|
70
|
+
* // -> WebsiteLabelKey ("website.landing.hero.headline")
|
|
71
|
+
*/
|
|
72
|
+
export function composeWebsiteLabelKey(parts) {
|
|
73
|
+
return WebsiteLabelKeySchema.parse(`website.${parts.pageRef}.${parts.sectionRef}.${parts.slotPath}`);
|
|
74
|
+
}
|
|
75
|
+
/** @wildo_source:part:end saas.website.label-keys.compose-helper */
|
|
76
|
+
//# sourceMappingURL=website-label-key.schemas.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"website-label-key.schemas.js","sourceRoot":"","sources":["../../../../../src/schemas/label-keys/website-label-key.schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,sBAAsB,GAAG,CAAC;KAC7B,MAAM,EAAE;KACR,GAAG,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,6EAA6E,EAAE,CAAC;KACjG,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,gDAAgD,EAAE,CAAC;KACrE,KAAK,CACJ,kEAAkE,EAClE;IACE,KAAK,EACH,qLAAqL;CACxL,CACF;KACA,KAAK,EAAqB,CAAC;AAE9B,MAAM,CAAC,MAAM,qBAAqB,GAAkC,sBAAsB,CAAC;AAG3F,4DAA4D;AAE5D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAItC;IACC,OAAO,qBAAqB,CAAC,KAAK,CAAC,WAAW,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;AACvG,CAAC;AACD,oEAAoE","sourcesContent":["import { z } from 'zod';\n\n/**\n * @wildo_source:part:start saas.website.label-keys.format facet:layer:shared facet:family:i18n\n *\n * `WebsiteLabelKey` — branded i18n key for marketing-site copy.\n *\n * **Format**: `website.<pageRef>.<sectionRef>.<slotPath>`\n *\n * - `website.` — fixed namespace prefix that disambiguates marketing-site\n * labels from app i18n keys (`labels.`, `errors.`, `notifications.`).\n * Marketing-site label packs ship as a separate file per locale\n * (`website/src/i18n/<locale>.json`) so they never bloat the SaaS app\n * bundle.\n * - `<pageRef>` — kebab-case page ref (validated independently by\n * `WebsitePageRefSchema`).\n * - `<sectionRef>` — kebab-case section ref (validated independently by\n * `WebsiteSectionRefSchema`).\n * - `<slotPath>` — leaf identifier the section consumes via\n * `useWebsiteLabel('<slotPath>')` (Phase 2). Allows dots for nested\n * structures (`'cta.primary'`), brackets for indexed array slots\n * (`'tiers[0].name'`), and hyphens for multi-word leaves.\n *\n * **Examples**:\n * - `website.landing.hero.headline`\n * - `website.landing.hero.cta.primary`\n * - `website.pricing.tiers.tiers[0].name`\n * - `website.contact.form.field.email-placeholder`\n *\n * **Why a brand**: prevents the \"label key as bare string\" failure mode\n * where a typo in `'webiste.landing.hero.headline'` (note the typo) silently\n * resolves to undefined at runtime. Sections must accept\n * `WebsiteLabelKey`-typed props; consumers go through\n * `WebsiteLabelKeySchema.parse(...)` (or the manifest's `metaLabelKeys`\n * fields, which are typed against this schema) before a string flows into\n * label resolution.\n *\n * **Why a single regex**: the three nested refs (`page`, `section`, `slot`)\n * are validated separately by their own schemas inside the `defineWebsiteSection`\n * factory and `WebsitePageManifestSchema`. This schema's job is to validate\n * a fully-composed key — typically inserted directly into JSX as a string\n * literal — without re-parsing the parts.\n */\nconst _WebsiteLabelKeySchema = z\n .string()\n .min(10, { error: 'WebsiteLabelKey is too short (must include website.<page>.<section>.<slot>)' })\n .max(256, { error: 'WebsiteLabelKey must be at most 256 characters' })\n .regex(\n /^website\\.[a-z][a-z0-9-]*\\.[a-z][a-z0-9-]*\\.[a-zA-Z0-9.\\[\\]_-]+$/,\n {\n error:\n '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\")',\n },\n )\n .brand<'WebsiteLabelKey'>();\n\nexport const WebsiteLabelKeySchema: typeof _WebsiteLabelKeySchema = _WebsiteLabelKeySchema;\n\nexport type WebsiteLabelKey = z.infer<typeof _WebsiteLabelKeySchema>;\n/** @wildo_source:part:end saas.website.label-keys.format */\n\n/**\n * @wildo_source:part:start saas.website.label-keys.compose-helper facet:layer:shared facet:family:i18n\n *\n * `composeWebsiteLabelKey` — pure helper that composes a `WebsiteLabelKey`\n * from its three parts. Used by `useWebsiteLabel` (Phase 2) and by the\n * label-pack validator to materialize the keys a section commits to.\n *\n * Returns the key as a parsed `WebsiteLabelKey` (brand applied), throwing\n * if the composed string fails the format check (defensive guard against\n * stale refs / inconsistent slot paths leaking past type-system narrowing).\n *\n * @example\n * composeWebsiteLabelKey({\n * pageRef: WebsitePageRefSchema.parse('landing'),\n * sectionRef: WebsiteSectionRefSchema.parse('hero'),\n * slotPath: 'headline',\n * });\n * // -> WebsiteLabelKey (\"website.landing.hero.headline\")\n */\nexport function composeWebsiteLabelKey(parts: {\n pageRef: string;\n sectionRef: string;\n slotPath: string;\n}): WebsiteLabelKey {\n return WebsiteLabelKeySchema.parse(`website.${parts.pageRef}.${parts.sectionRef}.${parts.slotPath}`);\n}\n/** @wildo_source:part:end saas.website.label-keys.compose-helper */\n"]}
|