@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,51 @@
|
|
|
1
|
+
import { WebsitePageManifestSchema, } from '../../schemas/manifests/website-page-manifest.shared.schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* `defineWebsitePageManifest<TProps>` — factory that materializes a
|
|
4
|
+
* `WebsitePageManifest` from authored input.
|
|
5
|
+
*
|
|
6
|
+
* Same two responsibilities as `defineWebsiteSection`: runtime
|
|
7
|
+
* validation through the schema (incl. the schema's `superRefine`
|
|
8
|
+
* for unique sectionRefs and meta-key prefixing) plus type
|
|
9
|
+
* preservation for `pageComponent`.
|
|
10
|
+
*
|
|
11
|
+
* **Why a separate factory** (and not a single `defineWebsite*` API):
|
|
12
|
+
* a section and a page have different validation semantics
|
|
13
|
+
* (`expectedLabelKeys` discipline vs. `metaLabelKeys` prefix
|
|
14
|
+
* discipline) and different override paths (sections via L2
|
|
15
|
+
* import-swap; pages via root-config registration). Conflating them
|
|
16
|
+
* would force one of the two contracts onto the other's authors.
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* export const LANDING_PAGE_MANIFEST = defineWebsitePageManifest({
|
|
20
|
+
* ref: 'landing',
|
|
21
|
+
* routePath: '/',
|
|
22
|
+
* pageComponent: LandingPage,
|
|
23
|
+
* sectionRefs: ['hero', 'social-proof', 'pricing-teaser', 'final-cta'],
|
|
24
|
+
* metaLabelKeys: {
|
|
25
|
+
* titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),
|
|
26
|
+
* descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
export function defineWebsitePageManifest(input) {
|
|
31
|
+
const parsed = WebsitePageManifestSchema.parse({
|
|
32
|
+
ref: input.ref,
|
|
33
|
+
routePath: input.routePath,
|
|
34
|
+
pageComponent: input.pageComponent,
|
|
35
|
+
sectionRefs: input.sectionRefs,
|
|
36
|
+
metaLabelKeys: input.metaLabelKeys,
|
|
37
|
+
hydration: input.hydration,
|
|
38
|
+
seo: input.seo,
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
ref: parsed.ref,
|
|
42
|
+
routePath: parsed.routePath,
|
|
43
|
+
pageComponent: input.pageComponent,
|
|
44
|
+
sectionRefs: parsed.sectionRefs,
|
|
45
|
+
metaLabelKeys: parsed.metaLabelKeys,
|
|
46
|
+
...(parsed.hydration !== undefined ? { hydration: parsed.hydration } : {}),
|
|
47
|
+
...(parsed.seo !== undefined ? { seo: parsed.seo } : {}),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** @wildo_source:part:end saas.website.factories.define-page-manifest */
|
|
51
|
+
//# sourceMappingURL=define-website-page-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-website-page-manifest.js","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-page-manifest.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,yBAAyB,GAM1B,MAAM,8DAA8D,CAAC;AA0CtE;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,yBAAyB,CACvC,KAA6C;IAE7C,MAAM,MAAM,GAAG,yBAAyB,CAAC,KAAK,CAAC;QAC7C,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,GAAG,EAAE,KAAK,CAAC,GAAG;KACf,CAAC,CAAC;IACH,OAAO;QACL,GAAG,EAAE,MAAM,CAAC,GAAG;QACf,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACzD,CAAC;AACJ,CAAC;AACD,yEAAyE","sourcesContent":["import type { ComponentType } from 'react';\n\nimport {\n WebsitePageManifestSchema,\n type WebsitePageManifest,\n type WebsitePageMetaLabelKeys,\n type WebsitePageRoutePath,\n type WebsitePageSeoConfig,\n WebsitePageHydrationStrategy,\n} from '../../schemas/manifests/website-page-manifest.shared.schemas';\nimport type { WebsitePageRef } from '../../schemas/refs/page-ref.schemas';\nimport type { WebsiteSectionRef } from '../../schemas/refs/section-ref.schemas';\n\n/**\n * @wildo_source:part:start saas.website.factories.define-page-manifest facet:layer:core facet:family:website\n *\n * Input shape consumed by `defineWebsitePageManifest<TProps>`.\n *\n * `TProps` is rarely non-empty for top-level marketing pages (which\n * are usually rendered by Astro with no React-side props), but the\n * generic exists for two legitimate cases:\n * - templated pages (eg. `/blog/[slug]` whose page component\n * accepts `{ slug, frontmatter }` from the Astro adapter),\n * - pages whose host adapter wants to inject server-side resolved\n * props (eg. an A/B-test variant id) before hydration.\n *\n * **String inputs vs. branded inputs**: `ref`, `routePath`, and\n * `sectionRefs` accept raw strings — the factory parses them through\n * the underlying schemas. Same ergonomic + safety trade-off as\n * `defineWebsiteSection`.\n */\nexport interface DefineWebsitePageManifestInput<TProps> {\n ref: WebsitePageRef | string;\n routePath: WebsitePageRoutePath | string;\n pageComponent: ComponentType<TProps>;\n sectionRefs: readonly (WebsiteSectionRef | string)[];\n metaLabelKeys: WebsitePageMetaLabelKeys;\n hydration?: WebsitePageHydrationStrategy;\n seo?: WebsitePageSeoConfig;\n}\n\n/**\n * Return shape of `defineWebsitePageManifest<TProps>`. Identical to\n * `WebsitePageManifest` except `pageComponent` retains the\n * caller-supplied generic prop shape.\n */\nexport interface TypedWebsitePageManifest<TProps>\n extends Omit<WebsitePageManifest, 'pageComponent'> {\n pageComponent: ComponentType<TProps>;\n}\n\n/**\n * `defineWebsitePageManifest<TProps>` — factory that materializes a\n * `WebsitePageManifest` from authored input.\n *\n * Same two responsibilities as `defineWebsiteSection`: runtime\n * validation through the schema (incl. the schema's `superRefine`\n * for unique sectionRefs and meta-key prefixing) plus type\n * preservation for `pageComponent`.\n *\n * **Why a separate factory** (and not a single `defineWebsite*` API):\n * a section and a page have different validation semantics\n * (`expectedLabelKeys` discipline vs. `metaLabelKeys` prefix\n * discipline) and different override paths (sections via L2\n * import-swap; pages via root-config registration). Conflating them\n * would force one of the two contracts onto the other's authors.\n *\n * @example\n * export const LANDING_PAGE_MANIFEST = defineWebsitePageManifest({\n * ref: 'landing',\n * routePath: '/',\n * pageComponent: LandingPage,\n * sectionRefs: ['hero', 'social-proof', 'pricing-teaser', 'final-cta'],\n * metaLabelKeys: {\n * titleKey: WebsiteLabelKeySchema.parse('website.landing.meta.title'),\n * descriptionKey: WebsiteLabelKeySchema.parse('website.landing.meta.description'),\n * },\n * });\n */\nexport function defineWebsitePageManifest<TProps = Record<string, never>>(\n input: DefineWebsitePageManifestInput<TProps>,\n): TypedWebsitePageManifest<TProps> {\n const parsed = WebsitePageManifestSchema.parse({\n ref: input.ref,\n routePath: input.routePath,\n pageComponent: input.pageComponent,\n sectionRefs: input.sectionRefs,\n metaLabelKeys: input.metaLabelKeys,\n hydration: input.hydration,\n seo: input.seo,\n });\n return {\n ref: parsed.ref,\n routePath: parsed.routePath,\n pageComponent: input.pageComponent,\n sectionRefs: parsed.sectionRefs,\n metaLabelKeys: parsed.metaLabelKeys,\n ...(parsed.hydration !== undefined ? { hydration: parsed.hydration } : {}),\n ...(parsed.seo !== undefined ? { seo: parsed.seo } : {}),\n };\n}\n/** @wildo_source:part:end saas.website.factories.define-page-manifest */\n"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { WebsiteSectionDefinition } from '../../schemas/sections/website-section-definition.shared.schemas';
|
|
3
|
+
import type { WebsiteSectionCategory } from '../../schemas/sections/website-section-category.shared';
|
|
4
|
+
import type { WebsiteSectionRef } from '../../schemas/refs/section-ref.schemas';
|
|
5
|
+
import type { WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';
|
|
6
|
+
import type { WebsiteStructuredDataReturn } from '../../schemas/structured-data/website-structured-data.shared.schemas';
|
|
7
|
+
/**
|
|
8
|
+
* @wildo_source:part:start saas.website.factories.define-section facet:layer:core facet:family:website
|
|
9
|
+
*
|
|
10
|
+
* Input shape consumed by `defineWebsiteSection<TProps>`. Mirrors
|
|
11
|
+
* `WebsiteSectionDefinition` but keeps `Component` typed against the
|
|
12
|
+
* caller-supplied generic so the precise prop shape survives end-to-end
|
|
13
|
+
* (the consumer's `<HeroSection />` JSX can keep its strongly-typed
|
|
14
|
+
* props after going through the factory).
|
|
15
|
+
*
|
|
16
|
+
* **String inputs vs. branded inputs**: `sectionRef` and
|
|
17
|
+
* `expectedLabelKeys` accept raw strings as well as already-branded
|
|
18
|
+
* values. The factory parses them through the underlying schemas, so
|
|
19
|
+
* either path is safe — accepting strings directly removes the
|
|
20
|
+
* ergonomic drag of forcing every section author to remember to call
|
|
21
|
+
* `WebsiteSectionRefSchema.parse(...)` in their definition.
|
|
22
|
+
*/
|
|
23
|
+
export interface DefineWebsiteSectionInput<TProps> {
|
|
24
|
+
sectionRef: WebsiteSectionRef | string;
|
|
25
|
+
category: WebsiteSectionCategory;
|
|
26
|
+
expectedLabelKeys: readonly (WebsiteLabelKey | string)[];
|
|
27
|
+
Component: ComponentType<TProps>;
|
|
28
|
+
/**
|
|
29
|
+
* See `WebsiteSectionDefinition.getStructuredData?` JSDoc for the
|
|
30
|
+
* full authoring contract. The factory passes the function through
|
|
31
|
+
* unchanged; the framework invokes it at static-build time and
|
|
32
|
+
* forwards the return value to `renderPageStructuredData()`.
|
|
33
|
+
*/
|
|
34
|
+
getStructuredData?: () => WebsiteStructuredDataReturn;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Return shape of `defineWebsiteSection<TProps>`. Identical to
|
|
38
|
+
* `WebsiteSectionDefinition` except `Component` retains the
|
|
39
|
+
* caller-supplied generic prop shape rather than the schema's
|
|
40
|
+
* lowest-common-denominator `ComponentType<unknown>`.
|
|
41
|
+
*
|
|
42
|
+
* **Why this matters**: a consumer's `pageComponent` JSX composes
|
|
43
|
+
* sections like `<HERO_SECTION.Component {...heroProps} />`. Without
|
|
44
|
+
* preserving `TProps`, the spread would be untyped and a renamed prop
|
|
45
|
+
* would silently miss the type system. With `TProps` preserved, the
|
|
46
|
+
* call site fails to compile the moment the prop contract drifts.
|
|
47
|
+
*/
|
|
48
|
+
export interface TypedWebsiteSectionDefinition<TProps> extends Omit<WebsiteSectionDefinition, 'Component' | 'getStructuredData'> {
|
|
49
|
+
Component: ComponentType<TProps>;
|
|
50
|
+
getStructuredData?: () => WebsiteStructuredDataReturn;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* `defineWebsiteSection<TProps>` — factory that materializes a
|
|
54
|
+
* `WebsiteSectionDefinition` from authored input.
|
|
55
|
+
*
|
|
56
|
+
* **Two responsibilities**, one entry point:
|
|
57
|
+
* 1. Runtime validation — parses the input through
|
|
58
|
+
* `WebsiteSectionDefinitionSchema` so brand checks, regex
|
|
59
|
+
* validations, and array refinements run at module load time
|
|
60
|
+
* (not at render time, not at build-output time).
|
|
61
|
+
* 2. Type preservation — returns an object whose `Component` field
|
|
62
|
+
* keeps the caller-supplied `TProps`, so JSX usage of the
|
|
63
|
+
* returned section retains compile-time prop checking.
|
|
64
|
+
*
|
|
65
|
+
* **Why a factory instead of constructing the literal directly**:
|
|
66
|
+
* - Consumers don't have to remember to parse refs / keys
|
|
67
|
+
* themselves before assembling the literal.
|
|
68
|
+
* - The schema parse provides a single audited validation chokepoint
|
|
69
|
+
* so `wildo website audit` (Phase 7) can re-validate definitions
|
|
70
|
+
* without re-entering the React graph.
|
|
71
|
+
* - The factory is the documented authoring API the
|
|
72
|
+
* `website_factory_authoring` skill targets — keeping the
|
|
73
|
+
* authoring surface narrow lets the framework evolve the
|
|
74
|
+
* internal schema (add fields, refinements) without churning
|
|
75
|
+
* every section definition in every consumer app.
|
|
76
|
+
*
|
|
77
|
+
* @param input section authoring input (see `DefineWebsiteSectionInput`).
|
|
78
|
+
* @returns a `TypedWebsiteSectionDefinition<TProps>` with `Component`
|
|
79
|
+
* preserved at the precise prop type.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* interface HeroProps {
|
|
83
|
+
* headlineKey: WebsiteLabelKey;
|
|
84
|
+
* subheadlineKey: WebsiteLabelKey;
|
|
85
|
+
* }
|
|
86
|
+
* function HeroSection({ headlineKey, subheadlineKey }: HeroProps) {
|
|
87
|
+
* // …
|
|
88
|
+
* }
|
|
89
|
+
* export const HERO_SECTION = defineWebsiteSection<HeroProps>({
|
|
90
|
+
* sectionRef: 'hero',
|
|
91
|
+
* category: WebsiteSectionCategory.HERO,
|
|
92
|
+
* expectedLabelKeys: [
|
|
93
|
+
* 'website.landing.hero.headline',
|
|
94
|
+
* 'website.landing.hero.subheadline',
|
|
95
|
+
* ],
|
|
96
|
+
* Component: HeroSection,
|
|
97
|
+
* });
|
|
98
|
+
*/
|
|
99
|
+
export declare function defineWebsiteSection<TProps>(input: DefineWebsiteSectionInput<TProps>): TypedWebsiteSectionDefinition<TProps>;
|
|
100
|
+
/** @wildo_source:part:end saas.website.factories.define-section */
|
|
101
|
+
//# sourceMappingURL=define-website-section.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-website-section.d.ts","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAG3C,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kEAAkE,CAAC;AACjH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wDAAwD,CAAC;AACrG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wCAAwC,CAAC;AAChF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oDAAoD,CAAC;AAC1F,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,sEAAsE,CAAC;AAExH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,yBAAyB,CAAC,MAAM;IAC/C,UAAU,EAAE,iBAAiB,GAAG,MAAM,CAAC;IACvC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,iBAAiB,EAAE,SAAS,CAAC,eAAe,GAAG,MAAM,CAAC,EAAE,CAAC;IACzD,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,6BAA6B,CAAC,MAAM,CACnD,SAAQ,IAAI,CAAC,wBAAwB,EAAE,WAAW,GAAG,mBAAmB,CAAC;IACzE,SAAS,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACjC,iBAAiB,CAAC,EAAE,MAAM,2BAA2B,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EACzC,KAAK,EAAE,yBAAyB,CAAC,MAAM,CAAC,GACvC,6BAA6B,CAAC,MAAM,CAAC,CAoBvC;AACD,mEAAmE"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { WebsiteSectionDefinitionSchema } from '../../schemas/sections/website-section-definition.shared.schemas.js';
|
|
2
|
+
/**
|
|
3
|
+
* `defineWebsiteSection<TProps>` — factory that materializes a
|
|
4
|
+
* `WebsiteSectionDefinition` from authored input.
|
|
5
|
+
*
|
|
6
|
+
* **Two responsibilities**, one entry point:
|
|
7
|
+
* 1. Runtime validation — parses the input through
|
|
8
|
+
* `WebsiteSectionDefinitionSchema` so brand checks, regex
|
|
9
|
+
* validations, and array refinements run at module load time
|
|
10
|
+
* (not at render time, not at build-output time).
|
|
11
|
+
* 2. Type preservation — returns an object whose `Component` field
|
|
12
|
+
* keeps the caller-supplied `TProps`, so JSX usage of the
|
|
13
|
+
* returned section retains compile-time prop checking.
|
|
14
|
+
*
|
|
15
|
+
* **Why a factory instead of constructing the literal directly**:
|
|
16
|
+
* - Consumers don't have to remember to parse refs / keys
|
|
17
|
+
* themselves before assembling the literal.
|
|
18
|
+
* - The schema parse provides a single audited validation chokepoint
|
|
19
|
+
* so `wildo website audit` (Phase 7) can re-validate definitions
|
|
20
|
+
* without re-entering the React graph.
|
|
21
|
+
* - The factory is the documented authoring API the
|
|
22
|
+
* `website_factory_authoring` skill targets — keeping the
|
|
23
|
+
* authoring surface narrow lets the framework evolve the
|
|
24
|
+
* internal schema (add fields, refinements) without churning
|
|
25
|
+
* every section definition in every consumer app.
|
|
26
|
+
*
|
|
27
|
+
* @param input section authoring input (see `DefineWebsiteSectionInput`).
|
|
28
|
+
* @returns a `TypedWebsiteSectionDefinition<TProps>` with `Component`
|
|
29
|
+
* preserved at the precise prop type.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* interface HeroProps {
|
|
33
|
+
* headlineKey: WebsiteLabelKey;
|
|
34
|
+
* subheadlineKey: WebsiteLabelKey;
|
|
35
|
+
* }
|
|
36
|
+
* function HeroSection({ headlineKey, subheadlineKey }: HeroProps) {
|
|
37
|
+
* // …
|
|
38
|
+
* }
|
|
39
|
+
* export const HERO_SECTION = defineWebsiteSection<HeroProps>({
|
|
40
|
+
* sectionRef: 'hero',
|
|
41
|
+
* category: WebsiteSectionCategory.HERO,
|
|
42
|
+
* expectedLabelKeys: [
|
|
43
|
+
* 'website.landing.hero.headline',
|
|
44
|
+
* 'website.landing.hero.subheadline',
|
|
45
|
+
* ],
|
|
46
|
+
* Component: HeroSection,
|
|
47
|
+
* });
|
|
48
|
+
*/
|
|
49
|
+
export function defineWebsiteSection(input) {
|
|
50
|
+
const parsed = WebsiteSectionDefinitionSchema.parse({
|
|
51
|
+
sectionRef: input.sectionRef,
|
|
52
|
+
category: input.category,
|
|
53
|
+
expectedLabelKeys: input.expectedLabelKeys,
|
|
54
|
+
Component: input.Component,
|
|
55
|
+
getStructuredData: input.getStructuredData,
|
|
56
|
+
});
|
|
57
|
+
/**
|
|
58
|
+
* Re-attach `Component` and `getStructuredData` from the original
|
|
59
|
+
* input so the precise generic types survive — `parsed.Component`
|
|
60
|
+
* is `unknown` per the schema's `z.unknown()` contract.
|
|
61
|
+
*/
|
|
62
|
+
return {
|
|
63
|
+
sectionRef: parsed.sectionRef,
|
|
64
|
+
category: parsed.category,
|
|
65
|
+
expectedLabelKeys: parsed.expectedLabelKeys,
|
|
66
|
+
Component: input.Component,
|
|
67
|
+
...(input.getStructuredData ? { getStructuredData: input.getStructuredData } : {}),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/** @wildo_source:part:end saas.website.factories.define-section */
|
|
71
|
+
//# sourceMappingURL=define-website-section.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"define-website-section.js","sourceRoot":"","sources":["../../../../../src/core/factories/define-website-section.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,8BAA8B,EAAE,MAAM,kEAAkE,CAAC;AAuDlH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,UAAU,oBAAoB,CAClC,KAAwC;IAExC,MAAM,MAAM,GAAG,8BAA8B,CAAC,KAAK,CAAC;QAClD,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;QAC1C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,iBAAiB,EAAE,KAAK,CAAC,iBAAiB;KAC3C,CAAC,CAAC;IACH;;;;OAIG;IACH,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,iBAAiB,EAAE,MAAM,CAAC,iBAAiB;QAC3C,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,KAAK,CAAC,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACnF,CAAC;AACJ,CAAC;AACD,mEAAmE","sourcesContent":["import type { ComponentType } from 'react';\n\nimport { WebsiteSectionDefinitionSchema } from '../../schemas/sections/website-section-definition.shared.schemas';\nimport type { WebsiteSectionDefinition } from '../../schemas/sections/website-section-definition.shared.schemas';\nimport type { WebsiteSectionCategory } from '../../schemas/sections/website-section-category.shared';\nimport type { WebsiteSectionRef } from '../../schemas/refs/section-ref.schemas';\nimport type { WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';\nimport type { WebsiteStructuredDataReturn } from '../../schemas/structured-data/website-structured-data.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.factories.define-section facet:layer:core facet:family:website\n *\n * Input shape consumed by `defineWebsiteSection<TProps>`. Mirrors\n * `WebsiteSectionDefinition` but keeps `Component` typed against the\n * caller-supplied generic so the precise prop shape survives end-to-end\n * (the consumer's `<HeroSection />` JSX can keep its strongly-typed\n * props after going through the factory).\n *\n * **String inputs vs. branded inputs**: `sectionRef` and\n * `expectedLabelKeys` accept raw strings as well as already-branded\n * values. The factory parses them through the underlying schemas, so\n * either path is safe — accepting strings directly removes the\n * ergonomic drag of forcing every section author to remember to call\n * `WebsiteSectionRefSchema.parse(...)` in their definition.\n */\nexport interface DefineWebsiteSectionInput<TProps> {\n sectionRef: WebsiteSectionRef | string;\n category: WebsiteSectionCategory;\n expectedLabelKeys: readonly (WebsiteLabelKey | string)[];\n Component: ComponentType<TProps>;\n /**\n * See `WebsiteSectionDefinition.getStructuredData?` JSDoc for the\n * full authoring contract. The factory passes the function through\n * unchanged; the framework invokes it at static-build time and\n * forwards the return value to `renderPageStructuredData()`.\n */\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n\n/**\n * Return shape of `defineWebsiteSection<TProps>`. Identical to\n * `WebsiteSectionDefinition` except `Component` retains the\n * caller-supplied generic prop shape rather than the schema's\n * lowest-common-denominator `ComponentType<unknown>`.\n *\n * **Why this matters**: a consumer's `pageComponent` JSX composes\n * sections like `<HERO_SECTION.Component {...heroProps} />`. Without\n * preserving `TProps`, the spread would be untyped and a renamed prop\n * would silently miss the type system. With `TProps` preserved, the\n * call site fails to compile the moment the prop contract drifts.\n */\nexport interface TypedWebsiteSectionDefinition<TProps>\n extends Omit<WebsiteSectionDefinition, 'Component' | 'getStructuredData'> {\n Component: ComponentType<TProps>;\n getStructuredData?: () => WebsiteStructuredDataReturn;\n}\n\n/**\n * `defineWebsiteSection<TProps>` — factory that materializes a\n * `WebsiteSectionDefinition` from authored input.\n *\n * **Two responsibilities**, one entry point:\n * 1. Runtime validation — parses the input through\n * `WebsiteSectionDefinitionSchema` so brand checks, regex\n * validations, and array refinements run at module load time\n * (not at render time, not at build-output time).\n * 2. Type preservation — returns an object whose `Component` field\n * keeps the caller-supplied `TProps`, so JSX usage of the\n * returned section retains compile-time prop checking.\n *\n * **Why a factory instead of constructing the literal directly**:\n * - Consumers don't have to remember to parse refs / keys\n * themselves before assembling the literal.\n * - The schema parse provides a single audited validation chokepoint\n * so `wildo website audit` (Phase 7) can re-validate definitions\n * without re-entering the React graph.\n * - The factory is the documented authoring API the\n * `website_factory_authoring` skill targets — keeping the\n * authoring surface narrow lets the framework evolve the\n * internal schema (add fields, refinements) without churning\n * every section definition in every consumer app.\n *\n * @param input section authoring input (see `DefineWebsiteSectionInput`).\n * @returns a `TypedWebsiteSectionDefinition<TProps>` with `Component`\n * preserved at the precise prop type.\n *\n * @example\n * interface HeroProps {\n * headlineKey: WebsiteLabelKey;\n * subheadlineKey: WebsiteLabelKey;\n * }\n * function HeroSection({ headlineKey, subheadlineKey }: HeroProps) {\n * // …\n * }\n * export const HERO_SECTION = defineWebsiteSection<HeroProps>({\n * sectionRef: 'hero',\n * category: WebsiteSectionCategory.HERO,\n * expectedLabelKeys: [\n * 'website.landing.hero.headline',\n * 'website.landing.hero.subheadline',\n * ],\n * Component: HeroSection,\n * });\n */\nexport function defineWebsiteSection<TProps>(\n input: DefineWebsiteSectionInput<TProps>,\n): TypedWebsiteSectionDefinition<TProps> {\n const parsed = WebsiteSectionDefinitionSchema.parse({\n sectionRef: input.sectionRef,\n category: input.category,\n expectedLabelKeys: input.expectedLabelKeys,\n Component: input.Component,\n getStructuredData: input.getStructuredData,\n });\n /**\n * Re-attach `Component` and `getStructuredData` from the original\n * input so the precise generic types survive — `parsed.Component`\n * is `unknown` per the schema's `z.unknown()` contract.\n */\n return {\n sectionRef: parsed.sectionRef,\n category: parsed.category,\n expectedLabelKeys: parsed.expectedLabelKeys,\n Component: input.Component,\n ...(input.getStructuredData ? { getStructuredData: input.getStructuredData } : {}),\n };\n}\n/** @wildo_source:part:end saas.website.factories.define-section */\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { WebsiteDesignTokens } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.use-tokens.hook facet:layer:core facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* `useWebsiteDesignTokens()` — narrow read of the runtime context's
|
|
6
|
+
* `designTokens` slice.
|
|
7
|
+
*
|
|
8
|
+
* Existence rationale (vs. asking section authors to call
|
|
9
|
+
* `useWebsiteRuntime().designTokens` directly): hook discoverability
|
|
10
|
+
* + intent clarity. A section needing tokens has a single hook to
|
|
11
|
+
* import; refactoring the runtime context to add unrelated fields
|
|
12
|
+
* (eg. a future `analytics` slice) does NOT broaden the API surface
|
|
13
|
+
* the section consumes. Keeps the dependency graph between sections
|
|
14
|
+
* and runtime state explicit and minimal.
|
|
15
|
+
*
|
|
16
|
+
* Read-by-reference — the returned object is the same instance held
|
|
17
|
+
* by the runtime context, so memoization (`useMemo` /
|
|
18
|
+
* `React.memo` deps) using the returned reference works correctly
|
|
19
|
+
* across the static build's single render lifecycle.
|
|
20
|
+
*/
|
|
21
|
+
export declare function useWebsiteDesignTokens(): WebsiteDesignTokens;
|
|
22
|
+
/** @wildo_source:part:end saas.website.use-tokens.hook */
|
|
23
|
+
//# sourceMappingURL=useWebsiteDesignTokens.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWebsiteDesignTokens.d.ts","sourceRoot":"","sources":["../../../../../src/core/hooks/useWebsiteDesignTokens.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kEAAkE,CAAC;AAE5G;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,IAAI,mBAAmB,CAE5D;AACD,0DAA0D"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime.js';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.use-tokens.hook facet:layer:core facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* `useWebsiteDesignTokens()` — narrow read of the runtime context's
|
|
6
|
+
* `designTokens` slice.
|
|
7
|
+
*
|
|
8
|
+
* Existence rationale (vs. asking section authors to call
|
|
9
|
+
* `useWebsiteRuntime().designTokens` directly): hook discoverability
|
|
10
|
+
* + intent clarity. A section needing tokens has a single hook to
|
|
11
|
+
* import; refactoring the runtime context to add unrelated fields
|
|
12
|
+
* (eg. a future `analytics` slice) does NOT broaden the API surface
|
|
13
|
+
* the section consumes. Keeps the dependency graph between sections
|
|
14
|
+
* and runtime state explicit and minimal.
|
|
15
|
+
*
|
|
16
|
+
* Read-by-reference — the returned object is the same instance held
|
|
17
|
+
* by the runtime context, so memoization (`useMemo` /
|
|
18
|
+
* `React.memo` deps) using the returned reference works correctly
|
|
19
|
+
* across the static build's single render lifecycle.
|
|
20
|
+
*/
|
|
21
|
+
export function useWebsiteDesignTokens() {
|
|
22
|
+
return useWebsiteRuntime().designTokens;
|
|
23
|
+
}
|
|
24
|
+
/** @wildo_source:part:end saas.website.use-tokens.hook */
|
|
25
|
+
//# sourceMappingURL=useWebsiteDesignTokens.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWebsiteDesignTokens.js","sourceRoot":"","sources":["../../../../../src/core/hooks/useWebsiteDesignTokens.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAGlE;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB;IACpC,OAAO,iBAAiB,EAAE,CAAC,YAAY,CAAC;AAC1C,CAAC;AACD,0DAA0D","sourcesContent":["import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';\nimport type { WebsiteDesignTokens } from '../../schemas/design-tokens/website-design-tokens.shared.schemas';\n\n/**\n * @wildo_source:part:start saas.website.use-tokens.hook facet:layer:core facet:family:website\n *\n * `useWebsiteDesignTokens()` — narrow read of the runtime context's\n * `designTokens` slice.\n *\n * Existence rationale (vs. asking section authors to call\n * `useWebsiteRuntime().designTokens` directly): hook discoverability\n * + intent clarity. A section needing tokens has a single hook to\n * import; refactoring the runtime context to add unrelated fields\n * (eg. a future `analytics` slice) does NOT broaden the API surface\n * the section consumes. Keeps the dependency graph between sections\n * and runtime state explicit and minimal.\n *\n * Read-by-reference — the returned object is the same instance held\n * by the runtime context, so memoization (`useMemo` /\n * `React.memo` deps) using the returned reference works correctly\n * across the static build's single render lifecycle.\n */\nexport function useWebsiteDesignTokens(): WebsiteDesignTokens {\n return useWebsiteRuntime().designTokens;\n}\n/** @wildo_source:part:end saas.website.use-tokens.hook */\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { type WebsiteLabelKey } from '../../schemas/label-keys/website-label-key.schemas';
|
|
2
|
+
/**
|
|
3
|
+
* @wildo_source:part:start saas.website.use-label.hook facet:layer:core facet:family:website
|
|
4
|
+
*
|
|
5
|
+
* `useWebsiteLabel(slotPath)` — leaf-relative label resolution.
|
|
6
|
+
*
|
|
7
|
+
* Composes the slot leaf with the active page+section context to
|
|
8
|
+
* derive the full `WebsiteLabelKey` (`website.{pageRef}.{sectionRef}.{slotPath}`),
|
|
9
|
+
* looks it up in the active locale's label pack, and returns the
|
|
10
|
+
* resolved string.
|
|
11
|
+
*
|
|
12
|
+
* **Author ergonomics**: section authors call
|
|
13
|
+
* `useWebsiteLabel('headline')` from inside their section component
|
|
14
|
+
* without ever typing the page or section ref — the hook reads them
|
|
15
|
+
* from context. This keeps section components portable across pages
|
|
16
|
+
* (the same `<HeroSection />` can be reused on `landing` and on a
|
|
17
|
+
* dedicated `/about` page; only the surrounding `<WebsiteSection
|
|
18
|
+
* sectionRef="hero">` differs, and the label keys re-root accordingly).
|
|
19
|
+
*
|
|
20
|
+
* **Fail-loud diagnostics** for missing keys: returns the composed key
|
|
21
|
+
* itself (visible in the rendered DOM as e.g. `website.landing.hero.headline`)
|
|
22
|
+
* AND emits a `console.warn`. The visible-key fallback is preferred
|
|
23
|
+
* over an empty string or a thrown error because:
|
|
24
|
+
* - empty string silently breaks layout (zero-height heading) and
|
|
25
|
+
* leaks past visual review,
|
|
26
|
+
* - throwing would crash the entire page on a single missing label
|
|
27
|
+
* while the SEO crawler indexes a 500 page,
|
|
28
|
+
* - the rendered key is a clear authoring signal ("ship the missing
|
|
29
|
+
* pack entry") that designers and copywriters can spot without
|
|
30
|
+
* opening DevTools.
|
|
31
|
+
*
|
|
32
|
+
* **Hard guards** (each enforced by the underlying context hooks):
|
|
33
|
+
* - MUST be called inside `<WebsiteRuntimeContextProvider>`,
|
|
34
|
+
* - MUST be called inside `<WebsitePageLayout>`,
|
|
35
|
+
* - MUST be called inside `<WebsiteSection>`.
|
|
36
|
+
*
|
|
37
|
+
* @param slotPath leaf identifier (eg. `'headline'`, `'cta.primary'`,
|
|
38
|
+
* `'tiers[0].name'`). Anything that survives the slot
|
|
39
|
+
* portion of `WebsiteLabelKeySchema`'s regex.
|
|
40
|
+
* @returns the resolved label string, or the composed key itself when
|
|
41
|
+
* the pack lacks an entry (with a `console.warn`).
|
|
42
|
+
*/
|
|
43
|
+
export declare function useWebsiteLabel(slotPath: string): string;
|
|
44
|
+
/** @wildo_source:part:end saas.website.use-label.hook */
|
|
45
|
+
/**
|
|
46
|
+
* @wildo_source:part:start saas.website.use-label-by-key.hook facet:layer:core facet:family:website
|
|
47
|
+
*
|
|
48
|
+
* `useWebsiteLabelByKey(key)` — absolute key lookup.
|
|
49
|
+
*
|
|
50
|
+
* Use when the consumer already holds a fully-composed label key and
|
|
51
|
+
* does NOT need page+section composition. Two main call sites:
|
|
52
|
+
* - the navigation header/footer rendering page meta titles
|
|
53
|
+
* (`useWebsiteLabelByKey(manifest.metaLabelKeys.titleKey)`),
|
|
54
|
+
* - section components that accept a `*Key` prop from their parent
|
|
55
|
+
* page (`<HeroSection headlineKey="website.landing.hero.headline" />`)
|
|
56
|
+
* instead of using the leaf-relative hook.
|
|
57
|
+
*
|
|
58
|
+
* Same fail-loud diagnostics as `useWebsiteLabel`: returns the key
|
|
59
|
+
* string and emits a `console.warn` on miss.
|
|
60
|
+
*
|
|
61
|
+
* Defensively re-parses the key through `WebsiteLabelKeySchema` so a
|
|
62
|
+
* caller that bypassed the type system (eg. an `as WebsiteLabelKey`
|
|
63
|
+
* cast on a malformed string) gets caught at the hook boundary
|
|
64
|
+
* instead of silently looking up garbage.
|
|
65
|
+
*/
|
|
66
|
+
export declare function useWebsiteLabelByKey(key: WebsiteLabelKey): string;
|
|
67
|
+
/** @wildo_source:part:end saas.website.use-label-by-key.hook */
|
|
68
|
+
//# sourceMappingURL=useWebsiteLabel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWebsiteLabel.d.ts","sourceRoot":"","sources":["../../../../../src/core/hooks/useWebsiteLabel.ts"],"names":[],"mappings":"AAKA,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,oDAAoD,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA8BxD;AACD,yDAAyD;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,CAajE;AACD,gEAAgE"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { useWebsitePage } from '../contexts/useWebsitePage.js';
|
|
3
|
+
import { useWebsiteRuntime } from '../contexts/useWebsiteRuntime.js';
|
|
4
|
+
import { useWebsiteSection } from '../contexts/useWebsiteSection.js';
|
|
5
|
+
import { WebsiteLabelKeySchema, composeWebsiteLabelKey, } from '../../schemas/label-keys/website-label-key.schemas.js';
|
|
6
|
+
/**
|
|
7
|
+
* @wildo_source:part:start saas.website.use-label.hook facet:layer:core facet:family:website
|
|
8
|
+
*
|
|
9
|
+
* `useWebsiteLabel(slotPath)` — leaf-relative label resolution.
|
|
10
|
+
*
|
|
11
|
+
* Composes the slot leaf with the active page+section context to
|
|
12
|
+
* derive the full `WebsiteLabelKey` (`website.{pageRef}.{sectionRef}.{slotPath}`),
|
|
13
|
+
* looks it up in the active locale's label pack, and returns the
|
|
14
|
+
* resolved string.
|
|
15
|
+
*
|
|
16
|
+
* **Author ergonomics**: section authors call
|
|
17
|
+
* `useWebsiteLabel('headline')` from inside their section component
|
|
18
|
+
* without ever typing the page or section ref — the hook reads them
|
|
19
|
+
* from context. This keeps section components portable across pages
|
|
20
|
+
* (the same `<HeroSection />` can be reused on `landing` and on a
|
|
21
|
+
* dedicated `/about` page; only the surrounding `<WebsiteSection
|
|
22
|
+
* sectionRef="hero">` differs, and the label keys re-root accordingly).
|
|
23
|
+
*
|
|
24
|
+
* **Fail-loud diagnostics** for missing keys: returns the composed key
|
|
25
|
+
* itself (visible in the rendered DOM as e.g. `website.landing.hero.headline`)
|
|
26
|
+
* AND emits a `console.warn`. The visible-key fallback is preferred
|
|
27
|
+
* over an empty string or a thrown error because:
|
|
28
|
+
* - empty string silently breaks layout (zero-height heading) and
|
|
29
|
+
* leaks past visual review,
|
|
30
|
+
* - throwing would crash the entire page on a single missing label
|
|
31
|
+
* while the SEO crawler indexes a 500 page,
|
|
32
|
+
* - the rendered key is a clear authoring signal ("ship the missing
|
|
33
|
+
* pack entry") that designers and copywriters can spot without
|
|
34
|
+
* opening DevTools.
|
|
35
|
+
*
|
|
36
|
+
* **Hard guards** (each enforced by the underlying context hooks):
|
|
37
|
+
* - MUST be called inside `<WebsiteRuntimeContextProvider>`,
|
|
38
|
+
* - MUST be called inside `<WebsitePageLayout>`,
|
|
39
|
+
* - MUST be called inside `<WebsiteSection>`.
|
|
40
|
+
*
|
|
41
|
+
* @param slotPath leaf identifier (eg. `'headline'`, `'cta.primary'`,
|
|
42
|
+
* `'tiers[0].name'`). Anything that survives the slot
|
|
43
|
+
* portion of `WebsiteLabelKeySchema`'s regex.
|
|
44
|
+
* @returns the resolved label string, or the composed key itself when
|
|
45
|
+
* the pack lacks an entry (with a `console.warn`).
|
|
46
|
+
*/
|
|
47
|
+
export function useWebsiteLabel(slotPath) {
|
|
48
|
+
const { labelPack } = useWebsiteRuntime();
|
|
49
|
+
const { pageRef } = useWebsitePage();
|
|
50
|
+
const { sectionRef } = useWebsiteSection();
|
|
51
|
+
/**
|
|
52
|
+
* Compose + parse memoized on the three identity inputs. The parse
|
|
53
|
+
* itself is cheap (regex + brand) but executing it on every render
|
|
54
|
+
* of every label-bound primitive in a section adds up across a
|
|
55
|
+
* 12-section landing page.
|
|
56
|
+
*/
|
|
57
|
+
const fullKey = useMemo(() => composeWebsiteLabelKey({
|
|
58
|
+
pageRef: pageRef,
|
|
59
|
+
sectionRef: sectionRef,
|
|
60
|
+
slotPath,
|
|
61
|
+
}), [pageRef, sectionRef, slotPath]);
|
|
62
|
+
const resolved = labelPack[fullKey];
|
|
63
|
+
if (resolved === undefined) {
|
|
64
|
+
console.warn(`[useWebsiteLabel] Missing label for key "${fullKey}". ` +
|
|
65
|
+
`Add it to the active locale's pack (or remove the section's claim on this slot).`);
|
|
66
|
+
return fullKey;
|
|
67
|
+
}
|
|
68
|
+
return resolved;
|
|
69
|
+
}
|
|
70
|
+
/** @wildo_source:part:end saas.website.use-label.hook */
|
|
71
|
+
/**
|
|
72
|
+
* @wildo_source:part:start saas.website.use-label-by-key.hook facet:layer:core facet:family:website
|
|
73
|
+
*
|
|
74
|
+
* `useWebsiteLabelByKey(key)` — absolute key lookup.
|
|
75
|
+
*
|
|
76
|
+
* Use when the consumer already holds a fully-composed label key and
|
|
77
|
+
* does NOT need page+section composition. Two main call sites:
|
|
78
|
+
* - the navigation header/footer rendering page meta titles
|
|
79
|
+
* (`useWebsiteLabelByKey(manifest.metaLabelKeys.titleKey)`),
|
|
80
|
+
* - section components that accept a `*Key` prop from their parent
|
|
81
|
+
* page (`<HeroSection headlineKey="website.landing.hero.headline" />`)
|
|
82
|
+
* instead of using the leaf-relative hook.
|
|
83
|
+
*
|
|
84
|
+
* Same fail-loud diagnostics as `useWebsiteLabel`: returns the key
|
|
85
|
+
* string and emits a `console.warn` on miss.
|
|
86
|
+
*
|
|
87
|
+
* Defensively re-parses the key through `WebsiteLabelKeySchema` so a
|
|
88
|
+
* caller that bypassed the type system (eg. an `as WebsiteLabelKey`
|
|
89
|
+
* cast on a malformed string) gets caught at the hook boundary
|
|
90
|
+
* instead of silently looking up garbage.
|
|
91
|
+
*/
|
|
92
|
+
export function useWebsiteLabelByKey(key) {
|
|
93
|
+
const { labelPack } = useWebsiteRuntime();
|
|
94
|
+
const safeKey = useMemo(() => WebsiteLabelKeySchema.parse(key), [key]);
|
|
95
|
+
const resolved = labelPack[safeKey];
|
|
96
|
+
if (resolved === undefined) {
|
|
97
|
+
console.warn(`[useWebsiteLabelByKey] Missing label for key "${safeKey}".`);
|
|
98
|
+
return safeKey;
|
|
99
|
+
}
|
|
100
|
+
return resolved;
|
|
101
|
+
}
|
|
102
|
+
/** @wildo_source:part:end saas.website.use-label-by-key.hook */
|
|
103
|
+
//# sourceMappingURL=useWebsiteLabel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useWebsiteLabel.js","sourceRoot":"","sources":["../../../../../src/core/hooks/useWebsiteLabel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GAEvB,MAAM,oDAAoD,CAAC;AAE5D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,eAAe,CAAC,QAAgB;IAC9C,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAC1C,MAAM,EAAE,OAAO,EAAE,GAAG,cAAc,EAAE,CAAC;IACrC,MAAM,EAAE,UAAU,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAE3C;;;;;OAKG;IACH,MAAM,OAAO,GAAG,OAAO,CACrB,GAAG,EAAE,CACH,sBAAsB,CAAC;QACrB,OAAO,EAAE,OAA4B;QACrC,UAAU,EAAE,UAA+B;QAC3C,QAAQ;KACT,CAAC,EACJ,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,CAAC,CAChC,CAAC;IAEF,MAAM,QAAQ,GAAG,SAAS,CAAC,OAA4B,CAAC,CAAC;IACzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CACV,4CAA4C,OAA4B,KAAK;YAC3E,kFAAkF,CACrF,CAAC;QACF,OAAO,OAA4B,CAAC;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AACD,yDAAyD;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,oBAAoB,CAAC,GAAoB;IACvD,MAAM,EAAE,SAAS,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAE1C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAEvE,MAAM,QAAQ,GAAG,SAAS,CAAC,OAA4B,CAAC,CAAC;IACzD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,CAAC,IAAI,CACV,iDAAiD,OAA4B,IAAI,CAClF,CAAC;QACF,OAAO,OAA4B,CAAC;IACtC,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AACD,gEAAgE","sourcesContent":["import { useMemo } from 'react';\n\nimport { useWebsitePage } from '../contexts/useWebsitePage';\nimport { useWebsiteRuntime } from '../contexts/useWebsiteRuntime';\nimport { useWebsiteSection } from '../contexts/useWebsiteSection';\nimport {\n WebsiteLabelKeySchema,\n composeWebsiteLabelKey,\n type WebsiteLabelKey,\n} from '../../schemas/label-keys/website-label-key.schemas';\n\n/**\n * @wildo_source:part:start saas.website.use-label.hook facet:layer:core facet:family:website\n *\n * `useWebsiteLabel(slotPath)` — leaf-relative label resolution.\n *\n * Composes the slot leaf with the active page+section context to\n * derive the full `WebsiteLabelKey` (`website.{pageRef}.{sectionRef}.{slotPath}`),\n * looks it up in the active locale's label pack, and returns the\n * resolved string.\n *\n * **Author ergonomics**: section authors call\n * `useWebsiteLabel('headline')` from inside their section component\n * without ever typing the page or section ref — the hook reads them\n * from context. This keeps section components portable across pages\n * (the same `<HeroSection />` can be reused on `landing` and on a\n * dedicated `/about` page; only the surrounding `<WebsiteSection\n * sectionRef=\"hero\">` differs, and the label keys re-root accordingly).\n *\n * **Fail-loud diagnostics** for missing keys: returns the composed key\n * itself (visible in the rendered DOM as e.g. `website.landing.hero.headline`)\n * AND emits a `console.warn`. The visible-key fallback is preferred\n * over an empty string or a thrown error because:\n * - empty string silently breaks layout (zero-height heading) and\n * leaks past visual review,\n * - throwing would crash the entire page on a single missing label\n * while the SEO crawler indexes a 500 page,\n * - the rendered key is a clear authoring signal (\"ship the missing\n * pack entry\") that designers and copywriters can spot without\n * opening DevTools.\n *\n * **Hard guards** (each enforced by the underlying context hooks):\n * - MUST be called inside `<WebsiteRuntimeContextProvider>`,\n * - MUST be called inside `<WebsitePageLayout>`,\n * - MUST be called inside `<WebsiteSection>`.\n *\n * @param slotPath leaf identifier (eg. `'headline'`, `'cta.primary'`,\n * `'tiers[0].name'`). Anything that survives the slot\n * portion of `WebsiteLabelKeySchema`'s regex.\n * @returns the resolved label string, or the composed key itself when\n * the pack lacks an entry (with a `console.warn`).\n */\nexport function useWebsiteLabel(slotPath: string): string {\n const { labelPack } = useWebsiteRuntime();\n const { pageRef } = useWebsitePage();\n const { sectionRef } = useWebsiteSection();\n\n /**\n * Compose + parse memoized on the three identity inputs. The parse\n * itself is cheap (regex + brand) but executing it on every render\n * of every label-bound primitive in a section adds up across a\n * 12-section landing page.\n */\n const fullKey = useMemo<WebsiteLabelKey>(\n () =>\n composeWebsiteLabelKey({\n pageRef: pageRef as unknown as string,\n sectionRef: sectionRef as unknown as string,\n slotPath,\n }),\n [pageRef, sectionRef, slotPath],\n );\n\n const resolved = labelPack[fullKey as unknown as string];\n if (resolved === undefined) {\n console.warn(\n `[useWebsiteLabel] Missing label for key \"${fullKey as unknown as string}\". ` +\n `Add it to the active locale's pack (or remove the section's claim on this slot).`,\n );\n return fullKey as unknown as string;\n }\n return resolved;\n}\n/** @wildo_source:part:end saas.website.use-label.hook */\n\n/**\n * @wildo_source:part:start saas.website.use-label-by-key.hook facet:layer:core facet:family:website\n *\n * `useWebsiteLabelByKey(key)` — absolute key lookup.\n *\n * Use when the consumer already holds a fully-composed label key and\n * does NOT need page+section composition. Two main call sites:\n * - the navigation header/footer rendering page meta titles\n * (`useWebsiteLabelByKey(manifest.metaLabelKeys.titleKey)`),\n * - section components that accept a `*Key` prop from their parent\n * page (`<HeroSection headlineKey=\"website.landing.hero.headline\" />`)\n * instead of using the leaf-relative hook.\n *\n * Same fail-loud diagnostics as `useWebsiteLabel`: returns the key\n * string and emits a `console.warn` on miss.\n *\n * Defensively re-parses the key through `WebsiteLabelKeySchema` so a\n * caller that bypassed the type system (eg. an `as WebsiteLabelKey`\n * cast on a malformed string) gets caught at the hook boundary\n * instead of silently looking up garbage.\n */\nexport function useWebsiteLabelByKey(key: WebsiteLabelKey): string {\n const { labelPack } = useWebsiteRuntime();\n\n const safeKey = useMemo(() => WebsiteLabelKeySchema.parse(key), [key]);\n\n const resolved = labelPack[safeKey as unknown as string];\n if (resolved === undefined) {\n console.warn(\n `[useWebsiteLabelByKey] Missing label for key \"${safeKey as unknown as string}\".`,\n );\n return safeKey as unknown as string;\n }\n return resolved;\n}\n/** @wildo_source:part:end saas.website.use-label-by-key.hook */\n"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type WebsitePageRef } from '../../schemas/refs/page-ref.schemas';
|
|
3
|
+
/**
|
|
4
|
+
* @wildo_source:part:start saas.website.page-layout.component facet:layer:core facet:family:website
|
|
5
|
+
*
|
|
6
|
+
* `WebsitePageLayout` — the per-page structural primitive.
|
|
7
|
+
*
|
|
8
|
+
* **Three responsibilities** (each carrying its own semantic weight):
|
|
9
|
+
*
|
|
10
|
+
* 1. **Provides `WebsitePageContext`** — registers the active page
|
|
11
|
+
* ref so child sections (`<WebsiteSection>`) and label hooks
|
|
12
|
+
* (`useWebsiteLabel`) can derive label keys without re-typing
|
|
13
|
+
* the page ref at every leaf.
|
|
14
|
+
* 2. **Renders the site-wide chrome** — pulls
|
|
15
|
+
* `headerComponent` and `footerComponent` from the runtime
|
|
16
|
+
* navigation config and wraps the page's main content in the
|
|
17
|
+
* site's standard header/main/footer scaffold. Per-page
|
|
18
|
+
* overrides (`header={…}` / `footer={…}` props) take precedence
|
|
19
|
+
* over the runtime defaults so individual pages can opt into a
|
|
20
|
+
* custom chrome (eg. a campaign landing page running with
|
|
21
|
+
* header-only minimal chrome).
|
|
22
|
+
* 3. **Stamps DOM landmarks** — emits `data-website-page-ref` on
|
|
23
|
+
* the layout root for analytics / SEO crawlers / overlay
|
|
24
|
+
* tooling that needs to identify the active page from the DOM
|
|
25
|
+
* without booting React. When the website facet hydrates
|
|
26
|
+
* frontend-provider bootstraps, the same root also carries the
|
|
27
|
+
* active public provider refs for analytics / browser-monitoring /
|
|
28
|
+
* captcha so non-React tooling can detect the configured vendor
|
|
29
|
+
* without reverse-engineering app code.
|
|
30
|
+
*
|
|
31
|
+
* **Why semantic `<header>` / `<main>` / `<footer>` elements**:
|
|
32
|
+
* marketing-site SEO scoring penalizes pages that ship a single
|
|
33
|
+
* unstructured `<div>` tree. Using semantic landmarks costs nothing
|
|
34
|
+
* and improves crawler accessibility AND screen-reader
|
|
35
|
+
* navigability — both are first-class concerns for a public
|
|
36
|
+
* marketing surface.
|
|
37
|
+
*
|
|
38
|
+
* **Why this lives in `core/`** (not `components/`): the layout is
|
|
39
|
+
* structural framework machinery (it owns the page-context
|
|
40
|
+
* provisioning + the navigation slot resolution), not a styled
|
|
41
|
+
* primitive. Designers override `header` / `footer` via runtime
|
|
42
|
+
* config or per-page props, NOT by replacing this component.
|
|
43
|
+
*/
|
|
44
|
+
export interface WebsitePageLayoutProps {
|
|
45
|
+
/**
|
|
46
|
+
* The active page's ref. Accepts either an already-branded
|
|
47
|
+
* `WebsitePageRef` or a raw string — the layout parses through
|
|
48
|
+
* `WebsitePageRefSchema` so a malformed ref fails loud at render.
|
|
49
|
+
*
|
|
50
|
+
* Typically sourced from the page's `WebsitePageManifest.ref`
|
|
51
|
+
* (eg. `<WebsitePageLayout pageRef={LANDING_PAGE_MANIFEST.ref}>`).
|
|
52
|
+
*/
|
|
53
|
+
pageRef: WebsitePageRef | string;
|
|
54
|
+
/**
|
|
55
|
+
* Per-page header override. When supplied, takes precedence over
|
|
56
|
+
* `WebsiteRuntimeContext.designTokens` navigation config's
|
|
57
|
+
* `headerComponent`. Pass `null` explicitly to suppress the header
|
|
58
|
+
* entirely (campaign landing pages without site-wide nav).
|
|
59
|
+
*/
|
|
60
|
+
header?: ReactNode | null;
|
|
61
|
+
/**
|
|
62
|
+
* Per-page footer override. Same semantics as `header`.
|
|
63
|
+
*/
|
|
64
|
+
footer?: ReactNode | null;
|
|
65
|
+
/**
|
|
66
|
+
* Optional className applied to the root `<div data-website-page-ref="…">`
|
|
67
|
+
* wrapper. Use for page-level layout tweaks (eg. `min-h-screen` on
|
|
68
|
+
* landing pages).
|
|
69
|
+
*/
|
|
70
|
+
className?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Optional className applied to the inner `<main>` element. Use for
|
|
73
|
+
* vertical-rhythm overrides on long-scroll landing pages.
|
|
74
|
+
*/
|
|
75
|
+
mainClassName?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The page's section composition. Typically a sequence of
|
|
78
|
+
* `<WebsiteSection>` wrappers around concrete section components.
|
|
79
|
+
*/
|
|
80
|
+
children: ReactNode;
|
|
81
|
+
}
|
|
82
|
+
export declare function WebsitePageLayout({ pageRef, header, footer, className, mainClassName, children, }: WebsitePageLayoutProps): ReactNode;
|
|
83
|
+
export declare namespace WebsitePageLayout {
|
|
84
|
+
var displayName: string;
|
|
85
|
+
}
|
|
86
|
+
/** @wildo_source:part:end saas.website.page-layout.component */
|
|
87
|
+
//# sourceMappingURL=WebsitePageLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"WebsitePageLayout.d.ts","sourceRoot":"","sources":["../../../../../src/core/layouts/WebsitePageLayout.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAW,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAOvD,OAAO,EAEL,KAAK,cAAc,EACpB,MAAM,qCAAqC,CAAC;AA6F7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,OAAO,EAAE,cAAc,GAAG,MAAM,CAAC;IACjC;;;;;OAKG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,SAAS,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,MAAM,EACN,MAAM,EACN,SAAS,EACT,aAAa,EACb,QAAQ,GACT,EAAE,sBAAsB,GAAG,SAAS,CAsHpC;yBA7He,iBAAiB;;;AA+HjC,gEAAgE"}
|