diff --git a/Agents.md b/Agents.md index d3f69e6b..4adac4fa 100644 --- a/Agents.md +++ b/Agents.md @@ -944,6 +944,7 @@ ## Active Technologies - Tailwind CSS v4 - TypeScript 5.9, Astro 6 static components, HTML, CSS + Astro 6.0.0, Tailwind CSS 4.2.2 through CSS-first `@theme` and `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, Playwright 1.59.1 (400-tenantial-homepage-visual-rebuild) - TypeScript 5.9.3, Astro 6.0.0, Tailwind CSS v4.2.2 via `@tailwindcss/vite`, `astro-icon`, `@iconify-json/lucide`, and Playwright smoke tests for the static website; no database, CMS, API, customer data, tenant data, or runtime persistence. (401-tenantial-platform-page) +- TypeScript 6.0.3, Astro 6.3.3, Node.js >=20.0.0, pnpm 10.33.0 + Astro, `@astrojs/starlight`, `@astrojs/sitemap`, `@astrojs/mdx`, Tailwind CSS v4, `@tailwindcss/vite`, Preline 4, Lenis, GSAP, Sharp, Playwright; static website content only, no database or product persistence. (feat/403-public-website-launch-readiness) ## Recent Changes - 066-rbac-ui-enforcement-helper-v2-session-1769732329: Planned UiEnforcement v2 (spec + plan + design artifacts) diff --git a/apps/website/astro.config.mjs b/apps/website/astro.config.mjs index d038fca8..7bf81bd2 100644 --- a/apps/website/astro.config.mjs +++ b/apps/website/astro.config.mjs @@ -11,6 +11,11 @@ const redirectOnlyPaths = new Set([ '/services/', '/blog/', '/insights/', + '/en/product/', + '/en/products/', + '/en/services/', + '/en/blog/', + '/en/insights/', ]); const isRedirectOnlySitemapPath = page => { @@ -30,23 +35,14 @@ export default defineConfig({ image: { domains: ['images.unsplash.com'], }, - // i18n: { - // defaultLocale: "en", - // locales: ["en", "fr"], - // fallback: { - // fr: "en", - // }, - // routing: { - // prefixDefaultLocale: false, - // }, - // }, prefetch: true, integrations: [ sitemap({ filter: page => !isRedirectOnlySitemapPath(page), i18n: { - defaultLocale: 'en', + defaultLocale: 'de', locales: { + de: 'de', en: 'en', }, }, @@ -60,6 +56,10 @@ export default defineConfig({ // If both an Astro and Starlight i18n configurations are provided, an error is thrown. locales: { root: { + label: 'Deutsch', + lang: 'de', + }, + en: { label: 'English', lang: 'en', }, @@ -67,20 +67,20 @@ export default defineConfig({ // https://starlight.astro.build/guides/sidebar/ sidebar: [ { - label: 'Evaluation Guides', + label: 'Evaluierungsleitfaeden', translations: { - de: 'Schnellstartanleitungen', - es: 'Guías de Inicio Rápido', - fa: 'راهنمای شروع سریع', - fr: 'Guides de Démarrage Rapide', - ja: 'クイックスタートガイド', - 'zh-cn': '快速入门指南', + en: 'Evaluation Guides', }, items: [{ autogenerate: { directory: 'guides' } }], }, { - label: 'Platform Notes', - items: [{ label: 'Evidence Review', link: 'platform/evidence-review/' }], + label: 'Plattform-Notizen', + translations: { + en: 'Platform Notes', + }, + items: [ + { label: 'Evidence Review', link: 'platform/evidence-review/' }, + ], }, ], social: [], @@ -94,22 +94,6 @@ export default defineConfig({ './src/components/ui/starlight/MobileMenuFooter.astro', ThemeSelect: './src/components/ui/starlight/ThemeSelect.astro', }, - head: [ - { - tag: 'meta', - attrs: { - property: 'og:image', - content: 'https://tenantial.com' + '/social.webp', - }, - }, - { - tag: 'meta', - attrs: { - property: 'twitter:image', - content: 'https://tenantial.com' + '/social.webp', - }, - }, - ], }), mdx(), ], diff --git a/apps/website/src/components/BrandLogo.astro b/apps/website/src/components/BrandLogo.astro index cf1ed9f1..a90fbdae 100644 --- a/apps/website/src/components/BrandLogo.astro +++ b/apps/website/src/components/BrandLogo.astro @@ -1,13 +1,54 @@ +--- +import logoLockupMask from '@images/tenantial-logo-lockup-mask.png'; + +const { + class: className, + style: inlineStyle, + 'aria-label': ariaLabel = 'Tenantial', + ...attrs +} = Astro.props; + +const logoMaskUrl = + typeof logoLockupMask === 'string' ? logoLockupMask : logoLockupMask.src; +const logoStyle = `--tenantial-logo-mask: url("${logoMaskUrl}");${inlineStyle ?? ''}`; +--- + - T - Tenantial + + + diff --git a/apps/website/src/components/Meta.astro b/apps/website/src/components/Meta.astro index 5c078eae..31204278 100644 --- a/apps/website/src/components/Meta.astro +++ b/apps/website/src/components/Meta.astro @@ -3,6 +3,14 @@ import { getImage } from 'astro:assets'; import { OG, SEO, SITE } from '@data/constants'; import faviconSvgSrc from '@images/icon.svg'; import faviconSrc from '@images/icon.png'; +import { + getLocaleFromPath, + isLocale, + localeOg, + localizedPath, + stripLocalePrefix, + type Locale, +} from '@/i18n'; // Default properties for the Meta component. These values are used if props are not provided. // 'meta' sets a default description meta tag to describe the page content. @@ -22,8 +30,11 @@ const { structuredData = defaultProps.structuredData, customDescription = defaultProps.customDescription, customOgTitle = defaultProps.customOgTitle, + locale: rawLocale = getLocaleFromPath(Astro.url.pathname), } = Astro.props; +const locale: Locale = isLocale(rawLocale) ? rawLocale : 'de'; + // Use custom description if provided, otherwise use default meta const description = customDescription || meta; // Use custom OG title if provided, otherwise use default OG title @@ -33,32 +44,22 @@ const ogDescription = customDescription || OG.description; // Define the metadata for your website and individual pages const siteURL = `${Astro.site}`; // Set the website URL in astro.config.mjs const author = SITE.author; -const canonical = new URL(Astro.url.pathname, Astro.site || Astro.url.origin) - .href; -const basePath = Astro.url.pathname; +const cleanPath = stripLocalePrefix(Astro.url.pathname); +const canonical = new URL( + localizedPath(cleanPath, locale), + Astro.site || Astro.url.origin +).href; const socialImageRes = await getImage({ src: OG.image, width: 1200, height: 600, + format: 'png', }); -const socialImage = Astro.url.origin + socialImageRes.src; // Get the full URL of the image (https://stackoverflow.com/a/9858694) +const socialImage = new URL(socialImageRes.src, Astro.site || Astro.url.origin) + .href; +const twitterDomain = new URL(siteURL).hostname; -const languages: { [key: string]: string } = { - en: '', -}; - -function createHref(lang: string, prefix: string, path: string): string { - // Remove any existing language prefix - const cleanPath = path.replace(/^\/(en)\//, '/'); - - // Add the correct language prefix if needed - const basePath = prefix ? `/${prefix}${cleanPath}` : cleanPath; - const normalizedBasePath = basePath.replace(/\/\/+/g, '/'); - - return `${siteURL.slice(0, -1)}${normalizedBasePath}`; -} - -const fullPath: string = Astro.url.pathname; +const alternateLocales: Locale[] = ['de', 'en']; // Generate and optimize the favicon images const faviconSvg = await getImage({ @@ -93,19 +94,24 @@ const appleTouchIcon = await getImage({ { - Object.entries(languages).map(([lang, prefix]) => { - const cleanPath = fullPath.replace(/^\/(en)\//, '/'); - const href = createHref(lang, prefix, cleanPath); + alternateLocales.map(lang => { + const href = new URL( + localizedPath(cleanPath, lang), + Astro.site || Astro.url.origin + ).href; return ; }) } + {/* Facebook Meta Tags */} - - + + @@ -117,8 +123,8 @@ const appleTouchIcon = await getImage({ {/* Twitter Meta Tags */} - - + + diff --git a/apps/website/src/components/pages/ContactPage.astro b/apps/website/src/components/pages/ContactPage.astro new file mode 100644 index 00000000..092835f8 --- /dev/null +++ b/apps/website/src/components/pages/ContactPage.astro @@ -0,0 +1,41 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import ContactSection from '@components/sections/misc/ContactSection.astro'; +import { SITE } from '@data/constants'; +import { siteCopy } from '@data/site-copy'; +import { localeHtmlLang, localizedPath, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].contact; +const siteDescription = siteCopy[locale].site.description; +const canonicalPath = localizedPath('/contact', locale); +--- + + + + diff --git a/apps/website/src/components/pages/HomePage.astro b/apps/website/src/components/pages/HomePage.astro new file mode 100644 index 00000000..98677c06 --- /dev/null +++ b/apps/website/src/components/pages/HomePage.astro @@ -0,0 +1,65 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import HeroSection from '@components/sections/landing/HeroSection.astro'; +import FeaturesGeneral from '@components/sections/features/FeaturesGeneral.astro'; +import FeaturesNavs from '@components/sections/features/FeaturesNavs.astro'; +import PricingSection from '@components/sections/pricing/PricingSection.astro'; +import FAQ from '@components/sections/misc/FAQ.astro'; +import heroImage from '@images/tenantial-dashboard.avif'; +import featureImage from '@images/tenantial-review-board.avif'; +import reviewImage from '@images/tenantial-evidence-intake.avif'; +import evidenceImage from '@images/tenantial-decision-review.avif'; +import governanceImage from '@images/tenantial-restore-plan.avif'; +import { + faqsByLocale, + featuresByLocale, + pricingByLocale, + siteCopy, +} from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].home; +const tabs = copy.tabs.map((tab: any, index: number) => ({ + ...tab, + src: [reviewImage, evidenceImage, governanceImage][index], +})); +--- + + + + + + + + + + + + diff --git a/apps/website/src/components/pages/LegalPage.astro b/apps/website/src/components/pages/LegalPage.astro new file mode 100644 index 00000000..4798d5f3 --- /dev/null +++ b/apps/website/src/components/pages/LegalPage.astro @@ -0,0 +1,35 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; +import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro'; +import { siteCopy } from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].legal; +--- + + + +
+
+ + + +
+
+
diff --git a/apps/website/src/components/pages/PlatformPage.astro b/apps/website/src/components/pages/PlatformPage.astro new file mode 100644 index 00000000..609fd9cb --- /dev/null +++ b/apps/website/src/components/pages/PlatformPage.astro @@ -0,0 +1,98 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import LeftSection from '@components/ui/blocks/LeftSection.astro'; +import RightSection from '@components/ui/blocks/RightSection.astro'; +import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; +import dashboard from '@images/tenantial-dashboard.avif'; +import evidenceImage from '@images/tenantial-evidence-panel.avif'; +import reviewImage from '@images/tenantial-drift-workflow.avif'; +import restoreImage from '@images/tenantial-rollout-plan.avif'; +import { SITE } from '@data/constants'; +import { siteCopy } from '@data/site-copy'; +import { + localeHtmlLang, + localizeHref, + localizedPath, + type Locale, +} from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].platform; +const siteDescription = siteCopy[locale].site.description; +const canonicalPath = localizedPath('/platform', locale); +--- + + + + + + + + + + + + diff --git a/apps/website/src/components/pages/PricingPage.astro b/apps/website/src/components/pages/PricingPage.astro new file mode 100644 index 00000000..c02292f7 --- /dev/null +++ b/apps/website/src/components/pages/PricingPage.astro @@ -0,0 +1,25 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import PricingSection from '@components/sections/pricing/PricingSection.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import { pricingByLocale, siteCopy } from '@data/site-copy'; +import type { Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].pricingIntro; +--- + + + + + diff --git a/apps/website/src/components/pages/TextPage.astro b/apps/website/src/components/pages/TextPage.astro new file mode 100644 index 00000000..a4614357 --- /dev/null +++ b/apps/website/src/components/pages/TextPage.astro @@ -0,0 +1,24 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import { siteCopy } from '@data/site-copy'; +import type { Locale } from '@/i18n'; + +const { locale, page } = Astro.props; + +interface Props { + locale: Locale; + page: 'privacy' | 'terms' | 'imprint'; +} + +const copy = siteCopy[locale][page]; +--- + + + + diff --git a/apps/website/src/components/pages/TrustPage.astro b/apps/website/src/components/pages/TrustPage.astro new file mode 100644 index 00000000..90b604a8 --- /dev/null +++ b/apps/website/src/components/pages/TrustPage.astro @@ -0,0 +1,37 @@ +--- +import MainLayout from '@/layouts/MainLayout.astro'; +import MainSection from '@components/ui/blocks/MainSection.astro'; +import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; +import { siteCopy } from '@data/site-copy'; +import { localizeHref, type Locale } from '@/i18n'; + +const { locale } = Astro.props; + +interface Props { + locale: Locale; +} + +const copy = siteCopy[locale].trust; +--- + + + + + diff --git a/apps/website/src/components/sections/landing/ClientsSection.astro b/apps/website/src/components/sections/landing/ClientsSection.astro index 33b0a3e4..1d9d2924 100644 --- a/apps/website/src/components/sections/landing/ClientsSection.astro +++ b/apps/website/src/components/sections/landing/ClientsSection.astro @@ -14,6 +14,10 @@ interface Props { subTitle?: string; partners: Partner[]; } + +const visiblePartners = partners.filter( + partner => partner.href && partner.icon +); ---
{/* Clients Group SVGs */} { - partners.map(partner => ( - + visiblePartners.map(partner => ( +
)) diff --git a/apps/website/src/components/sections/landing/HeroSection.astro b/apps/website/src/components/sections/landing/HeroSection.astro index 5ea47f48..27359661 100644 --- a/apps/website/src/components/sections/landing/HeroSection.astro +++ b/apps/website/src/components/sections/landing/HeroSection.astro @@ -15,7 +15,6 @@ const { secondaryBtnURL, withReview, avatars, - starCount, rating, reviews, src, @@ -32,7 +31,6 @@ interface Props { secondaryBtnURL?: string; withReview?: boolean; avatars?: Array; - starCount?: number; rating?: string; reviews?: string; src?: any; @@ -81,12 +79,7 @@ interface Props { } { withReview ? ( - + ) : ( '' ) diff --git a/apps/website/src/components/sections/misc/Authentication.astro b/apps/website/src/components/sections/misc/Authentication.astro index 537d9af6..6be8c104 100644 --- a/apps/website/src/components/sections/misc/Authentication.astro +++ b/apps/website/src/components/sections/misc/Authentication.astro @@ -1,5 +1,36 @@ --- -import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; +import { localizeHref, type Locale } from '@/i18n'; +import { siteCopy } from '@data/site-copy'; + +const { locale = 'de' } = Astro.props; + +interface Props { + locale?: Locale; +} + +const userSVG = ` + + + `; + +const copy = siteCopy[locale].auth; --- - + + + {copy.walkthrough} + diff --git a/apps/website/src/components/sections/misc/ContactSection.astro b/apps/website/src/components/sections/misc/ContactSection.astro index 1d648a96..7dd532b6 100644 --- a/apps/website/src/components/sections/misc/ContactSection.astro +++ b/apps/website/src/components/sections/misc/ContactSection.astro @@ -7,14 +7,16 @@ import EmailContactInput from '@components/ui/forms/input/EmailContactInput.astr import PhoneInput from '@components/ui/forms/input/PhoneInput.astro'; import TextAreaInput from '@components/ui/forms/input/TextAreaInput.astro'; import Icon from '@components/ui/icons/Icon.astro'; +import { localizeHref, type Locale } from '@/i18n'; +import { siteCopy } from '@data/site-copy'; -// Define the variables that will be used in this component -const title: string = 'Contact Tenantial'; -const subTitle: string = - 'Request a walkthrough or start a scoped rollout conversation. Do not send secrets, credentials, or tenant exports through this public website.'; -const formTitle: string = 'Prepare a walkthrough request'; -const formSubTitle: string = - 'The public website form is static; use email for business contact context.'; +const { locale = 'de' } = Astro.props; + +interface Props { + locale?: Locale; +} + +const copy = siteCopy[locale].contact; --- {/* Contact Us */} @@ -24,10 +26,10 @@ const formSubTitle: string =

- {title} + {copy.title}

- {subTitle} + {copy.subtitle}

@@ -36,7 +38,7 @@ const formSubTitle: string =

- {formTitle} + {copy.formTitle}

{ /* Form for user input with various input fields.--> @@ -47,31 +49,31 @@ const formSubTitle: string =
- - + +
- +

- {formSubTitle} + {copy.formSubtitle}

@@ -82,35 +84,35 @@ const formSubTitle: string = }
; +}; ---
diff --git a/apps/website/src/components/sections/testimonials/TestimonialItem.astro b/apps/website/src/components/sections/testimonials/TestimonialItem.astro index 6c6d92bc..87796f77 100644 --- a/apps/website/src/components/sections/testimonials/TestimonialItem.astro +++ b/apps/website/src/components/sections/testimonials/TestimonialItem.astro @@ -27,7 +27,7 @@ interface Props { Avatar Description diff --git a/apps/website/src/components/ui/LanguagePicker.astro b/apps/website/src/components/ui/LanguagePicker.astro new file mode 100644 index 00000000..84829489 --- /dev/null +++ b/apps/website/src/components/ui/LanguagePicker.astro @@ -0,0 +1,58 @@ +--- +import { + getLocaleFromPath, + localeLabels, + localizedPath, + stripLocalePrefix, +} from '@/i18n'; +import Icon from './icons/Icon.astro'; + +const currentLocale = getLocaleFromPath(Astro.url.pathname); +const currentPath = stripLocalePrefix(Astro.url.pathname); +--- + +
+ + + +
diff --git a/apps/website/src/components/ui/blocks/MainSection.astro b/apps/website/src/components/ui/blocks/MainSection.astro index 5ecf462b..b450cc8d 100644 --- a/apps/website/src/components/ui/blocks/MainSection.astro +++ b/apps/website/src/components/ui/blocks/MainSection.astro @@ -18,16 +18,16 @@ interface Props {
-
+
{/* Section title */}

{title}

{/* Section subtitle */}

{subTitle}

diff --git a/apps/website/src/components/ui/blocks/ReviewComponent.astro b/apps/website/src/components/ui/blocks/ReviewComponent.astro index 1d78bc23..5c133e6d 100644 --- a/apps/website/src/components/ui/blocks/ReviewComponent.astro +++ b/apps/website/src/components/ui/blocks/ReviewComponent.astro @@ -1,13 +1,10 @@ --- import Avatar from '@components/ui/avatars/Avatar.astro'; -import FullStar from '@components/ui/stars/FullStar.astro'; -import HalfStar from '@components/ui/stars/HalfStar.astro'; -const { avatars, starCount = 0, rating, reviews } = Astro.props; +const { avatars, rating, reviews } = Astro.props; interface Props { avatars?: Array; - starCount?: number; rating?: string; reviews?: string; } @@ -19,12 +16,16 @@ interface Props {
{/* Avatar Group */}
- {avatars?.map(src => )} + { + avatars?.map(src => ( + + )) + } 7k+Demo
@@ -33,26 +34,15 @@ interface Props { class="mx-auto h-px w-32 border-t border-neutral-400 sm:mx-0 sm:h-8 sm:w-auto sm:border-s sm:border-t-0 dark:border-neutral-500" >
- {/* Review Ratings */}
-
- {/* Your star ratings */} - { - Array(starCount) - .fill(0) - .map((_, i) => ) - } - {/* Adding additional half-star */} - -

- +

- +

diff --git a/apps/website/src/components/ui/forms/input/EmailContactInput.astro b/apps/website/src/components/ui/forms/input/EmailContactInput.astro index 69e42d00..bfe6215b 100644 --- a/apps/website/src/components/ui/forms/input/EmailContactInput.astro +++ b/apps/website/src/components/ui/forms/input/EmailContactInput.astro @@ -21,6 +21,6 @@ interface Props { id={id} autocomplete="email" class="block w-full rounded-lg border border-neutral-200 bg-neutral-50 px-4 py-3 text-sm text-neutral-700 placeholder:text-neutral-500 focus:border-neutral-200 focus:ring-3 focus:ring-neutral-400 focus:outline-hidden disabled:pointer-events-none disabled:opacity-50 dark:border-neutral-600 dark:bg-neutral-700/30 dark:text-neutral-300 dark:placeholder:text-neutral-400 dark:focus:ring-1" - placeholder="Email" + placeholder={label} />
diff --git a/apps/website/src/components/ui/forms/input/EmailFooterInput.astro b/apps/website/src/components/ui/forms/input/EmailFooterInput.astro index 54e8ab1d..b0c3b045 100644 --- a/apps/website/src/components/ui/forms/input/EmailFooterInput.astro +++ b/apps/website/src/components/ui/forms/input/EmailFooterInput.astro @@ -1,36 +1,18 @@ --- -const { - label = 'Search', - title = Astro.currentLocale === 'fr' ? "S'abonner" : 'Subscribe', - id = 'footer-input', -} = Astro.props; +const { title = 'Kontaktseite oeffnen', url = '/contact' } = Astro.props; interface Props { - label?: string; title?: string; - id?: string; + url?: string; } - -const placeholder = - Astro.currentLocale === 'fr' ? 'Entrez votre email' : 'Enter your email'; ---
-
- - -
{title} diff --git a/apps/website/src/components/ui/starlight/Head.astro b/apps/website/src/components/ui/starlight/Head.astro index c53ed163..e5527d42 100644 --- a/apps/website/src/components/ui/starlight/Head.astro +++ b/apps/website/src/components/ui/starlight/Head.astro @@ -1,11 +1,33 @@ --- import type { Props } from '@astrojs/starlight/props'; +import { getImage } from 'astro:assets'; import StarlightHead from '@astrojs/starlight/components/Head.astro'; import VtbotStarlight from 'astro-vtbot/components/starlight/Base.astro'; +import { OG } from '@data/constants'; + +const canonical = new URL(Astro.url.pathname, Astro.site || Astro.url.origin) + .href; +const socialImageRes = await getImage({ + src: OG.image, + width: 1200, + height: 600, + format: 'png', +}); +const socialImage = new URL(socialImageRes.src, Astro.site || Astro.url.origin) + .href; --- - + + + + + + + + + + diff --git a/apps/website/src/components/ui/starlight/SiteTitle.astro b/apps/website/src/components/ui/starlight/SiteTitle.astro index afad17d8..b0881f1d 100644 --- a/apps/website/src/components/ui/starlight/SiteTitle.astro +++ b/apps/website/src/components/ui/starlight/SiteTitle.astro @@ -1,9 +1,13 @@ --- -const docs = '/welcome-to-docs/'; +import { getLocaleFromPath, localizeHref } from '@/i18n'; + +const locale = getLocaleFromPath(Astro.url.pathname); +const home = localizeHref('/', locale); +const docs = localizeHref('/welcome-to-docs/', locale); --- - + diff --git a/apps/website/src/content/docs/en/guides/first-project-checklist.mdx b/apps/website/src/content/docs/en/guides/first-project-checklist.mdx new file mode 100644 index 00000000..4244ed70 --- /dev/null +++ b/apps/website/src/content/docs/en/guides/first-project-checklist.mdx @@ -0,0 +1,15 @@ +--- +title: Evaluation Checklist +description: A conservative checklist for early Tenantial evaluation. +sidebar: + label: Evaluation Checklist + order: 3 +--- + +Use this checklist before a product walkthrough: + +- Confirm which Microsoft tenant workflows are in scope. +- Separate read-only review needs from any future write or restore needs. +- Identify audit and approval expectations. +- Plan staging validation before production rollout. +- Keep private tenant data out of public website communication. diff --git a/apps/website/src/content/docs/en/guides/getting-started.mdx b/apps/website/src/content/docs/en/guides/getting-started.mdx new file mode 100644 index 00000000..cf7cadf1 --- /dev/null +++ b/apps/website/src/content/docs/en/guides/getting-started.mdx @@ -0,0 +1,18 @@ +--- +title: Getting Started +description: How evaluators should approach a Tenantial walkthrough. +sidebar: + label: Getting Started + order: 2 +--- + +Start with a scoped conversation about the tenant governance problem you want to solve. + +Useful preparation: + +- Identify the Microsoft tenant administration workflows that need review. +- List which policy families, backup flows, or restore planning scenarios matter first. +- Decide who needs to participate in evidence review and approval. +- Avoid sending private tenant exports or credentials through public website contact paths. + +Tenantial walkthroughs should stay focused on review clarity, least-privilege rollout planning, and staging validation before production use. diff --git a/apps/website/src/content/docs/en/guides/intro.mdx b/apps/website/src/content/docs/en/guides/intro.mdx new file mode 100644 index 00000000..371dce1d --- /dev/null +++ b/apps/website/src/content/docs/en/guides/intro.mdx @@ -0,0 +1,18 @@ +--- +title: Introduction to Tenantial +description: Public introduction to Tenantial's evidence-first Microsoft tenant governance model. +sidebar: + label: Introduction + order: 1 +--- + +Tenantial is positioned around careful Microsoft tenant governance. Public materials focus on observed inventory, immutable policy snapshots, drift review, findings, exceptions, audit context, and defensive restore planning. + +The website documentation is intentionally public and static. It does not expose live tenant data, operational evidence, private support material, credentials, or connected Microsoft accounts. + +## What to review first + +- How policy evidence is collected and normalized for review. +- How snapshots support comparison and restore planning. +- How findings and exceptions make decisions attributable. +- How restore is treated as preview-first work. diff --git a/apps/website/src/content/docs/en/platform/evidence-review.mdx b/apps/website/src/content/docs/en/platform/evidence-review.mdx new file mode 100644 index 00000000..216ca749 --- /dev/null +++ b/apps/website/src/content/docs/en/platform/evidence-review.mdx @@ -0,0 +1,17 @@ +--- +title: Evidence Review +description: Public platform note for Tenantial evidence review workflows. +sidebar: + label: Evidence Review + order: 1 +--- + +Evidence review is the public framing for Tenantial's product direction. + +The model is intentionally cautious: + +- Inventory is treated as observed state. +- Snapshots are explicit records for comparison and planning. +- Drift and findings become review work. +- Exceptions and audit notes keep decisions attributable. +- Restore is planned through preview, validation, selective scope, and explicit confirmation. diff --git a/apps/website/src/content/docs/en/welcome-to-docs.mdx b/apps/website/src/content/docs/en/welcome-to-docs.mdx new file mode 100644 index 00000000..8a3cb6be --- /dev/null +++ b/apps/website/src/content/docs/en/welcome-to-docs.mdx @@ -0,0 +1,31 @@ +--- +title: Tenantial Docs +head: + - tag: title + content: Tenantial Docs +description: Public notes for understanding Tenantial's evidence-first Microsoft tenant governance model. +editUrl: false +lastUpdated: false +next: false +hero: + title: Tenantial documentation + tagline: Public notes for evidence review, snapshot discipline, drift review, and cautious restore planning. + actions: + - text: Get started + icon: right-arrow + variant: primary + link: /en/guides/getting-started/ +--- + +import '@styles/starlight_main.css'; +import { Card, CardGrid } from '@astrojs/starlight/components'; + + + + Understand the public product model before planning a rollout conversation. + + + + Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning. + + diff --git a/apps/website/src/content/docs/guides/first-project-checklist.mdx b/apps/website/src/content/docs/guides/first-project-checklist.mdx index 4244ed70..7a44399a 100644 --- a/apps/website/src/content/docs/guides/first-project-checklist.mdx +++ b/apps/website/src/content/docs/guides/first-project-checklist.mdx @@ -1,15 +1,15 @@ --- -title: Evaluation Checklist -description: A conservative checklist for early Tenantial evaluation. +title: Evaluierungscheckliste +description: Eine konservative Checkliste für eine frühe Tenantial-Evaluierung. sidebar: - label: Evaluation Checklist + label: Evaluierungscheckliste order: 3 --- -Use this checklist before a product walkthrough: +Nutze diese Checkliste vor einem Produkt-Walkthrough: -- Confirm which Microsoft tenant workflows are in scope. -- Separate read-only review needs from any future write or restore needs. -- Identify audit and approval expectations. -- Plan staging validation before production rollout. -- Keep private tenant data out of public website communication. +- Bestätige, welche Microsoft-Tenant-Workflows im Scope sind. +- Trenne Read-only-Review-Bedarf von künftigem Write- oder Restore-Bedarf. +- Identifiziere Audit- und Approval-Erwartungen. +- Plane Staging-Validierung vor einem Produktionsrollout. +- Halte private Tenant-Daten aus öffentlicher Website-Kommunikation heraus. diff --git a/apps/website/src/content/docs/guides/getting-started.mdx b/apps/website/src/content/docs/guides/getting-started.mdx index cf7cadf1..0ca9ed85 100644 --- a/apps/website/src/content/docs/guides/getting-started.mdx +++ b/apps/website/src/content/docs/guides/getting-started.mdx @@ -1,18 +1,18 @@ --- title: Getting Started -description: How evaluators should approach a Tenantial walkthrough. +description: Wie Evaluatoren einen Tenantial Walkthrough vorbereiten sollten. sidebar: label: Getting Started order: 2 --- -Start with a scoped conversation about the tenant governance problem you want to solve. +Starte mit einem scoped Gespräch über das Tenant-Governance-Problem, das gelöst werden soll. -Useful preparation: +Sinnvolle Vorbereitung: -- Identify the Microsoft tenant administration workflows that need review. -- List which policy families, backup flows, or restore planning scenarios matter first. -- Decide who needs to participate in evidence review and approval. -- Avoid sending private tenant exports or credentials through public website contact paths. +- Identifiziere die Microsoft-Tenant-Administrationsworkflows, die Review brauchen. +- Liste die Policy-Familien, Backup-Flows oder Restore-Planungsszenarien, die zuerst relevant sind. +- Entscheide, wer an Evidence Review und Approval teilnehmen muss. +- Sende keine privaten Tenant-Exporte oder Credentials über öffentliche Website-Kontaktpfade. -Tenantial walkthroughs should stay focused on review clarity, least-privilege rollout planning, and staging validation before production use. +Tenantial Walkthroughs sollten auf Review-Klarheit, Least-Privilege-Rollout-Planung und Staging-Validierung vor Produktionseinsatz fokussiert bleiben. diff --git a/apps/website/src/content/docs/guides/intro.mdx b/apps/website/src/content/docs/guides/intro.mdx index 371dce1d..f37e0bab 100644 --- a/apps/website/src/content/docs/guides/intro.mdx +++ b/apps/website/src/content/docs/guides/intro.mdx @@ -1,18 +1,18 @@ --- -title: Introduction to Tenantial -description: Public introduction to Tenantial's evidence-first Microsoft tenant governance model. +title: Einführung in Tenantial +description: Öffentliche Einführung in Tenantials evidenzbasiertes Microsoft-Tenant-Governance-Modell. sidebar: - label: Introduction + label: Einführung order: 1 --- -Tenantial is positioned around careful Microsoft tenant governance. Public materials focus on observed inventory, immutable policy snapshots, drift review, findings, exceptions, audit context, and defensive restore planning. +Tenantial ist auf vorsichtige Microsoft-Tenant-Governance ausgerichtet. Öffentliche Materialien fokussieren beobachtetes Inventory, unveränderliche Policy-Snapshots, Drift Review, Findings, Exceptions, Audit-Kontext und defensive Restore-Planung. -The website documentation is intentionally public and static. It does not expose live tenant data, operational evidence, private support material, credentials, or connected Microsoft accounts. +Die Website-Dokumentation ist bewusst öffentlich und statisch. Sie legt keine Live-Tenant-Daten, operative Evidence, private Support-Materialien, Credentials oder verbundenen Microsoft-Konten offen. -## What to review first +## Was zuerst geprüft werden sollte -- How policy evidence is collected and normalized for review. -- How snapshots support comparison and restore planning. -- How findings and exceptions make decisions attributable. -- How restore is treated as preview-first work. +- Wie Policy Evidence für Reviews gesammelt und normalisiert wird. +- Wie Snapshots Vergleich und Restore-Planung unterstützen. +- Wie Findings und Exceptions Entscheidungen zuordenbar machen. +- Wie Restore als preview-first Arbeit behandelt wird. diff --git a/apps/website/src/content/docs/platform/evidence-review.mdx b/apps/website/src/content/docs/platform/evidence-review.mdx index 216ca749..8dfdaa9a 100644 --- a/apps/website/src/content/docs/platform/evidence-review.mdx +++ b/apps/website/src/content/docs/platform/evidence-review.mdx @@ -1,17 +1,17 @@ --- title: Evidence Review -description: Public platform note for Tenantial evidence review workflows. +description: Öffentliche Plattform-Notiz für Tenantial Evidence-Review-Workflows. sidebar: label: Evidence Review order: 1 --- -Evidence review is the public framing for Tenantial's product direction. +Evidence Review ist die öffentliche Rahmung für Tenantials Produktrichtung. -The model is intentionally cautious: +Das Modell ist bewusst vorsichtig: -- Inventory is treated as observed state. -- Snapshots are explicit records for comparison and planning. -- Drift and findings become review work. -- Exceptions and audit notes keep decisions attributable. -- Restore is planned through preview, validation, selective scope, and explicit confirmation. +- Inventory wird als beobachteter Zustand behandelt. +- Snapshots sind explizite Records für Vergleich und Planung. +- Drift und Findings werden zu Review-Arbeit. +- Exceptions und Audit-Notizen halten Entscheidungen zuordenbar. +- Restore wird über Preview, Validierung, selektiven Scope und explizite Bestätigung geplant. diff --git a/apps/website/src/content/docs/welcome-to-docs.mdx b/apps/website/src/content/docs/welcome-to-docs.mdx index 57cf6773..b81cb6f6 100644 --- a/apps/website/src/content/docs/welcome-to-docs.mdx +++ b/apps/website/src/content/docs/welcome-to-docs.mdx @@ -3,28 +3,29 @@ title: Tenantial Docs head: - tag: title content: Tenantial Docs -description: Public notes for understanding Tenantial's evidence-first Microsoft tenant governance model. +description: Öffentliche Notizen zum evidenzbasierten Microsoft-Tenant-Governance-Modell von Tenantial. editUrl: false lastUpdated: false next: false hero: - title: Tenantial documentation - tagline: Public notes for evidence review, snapshot discipline, drift review, and cautious restore planning. + title: Tenantial Dokumentation + tagline: Öffentliche Notizen zu Evidence Review, Snapshot-Disziplin, Drift Review und vorsichtiger Restore-Planung. actions: - - text: Get started + - text: Starten icon: right-arrow variant: primary link: /guides/getting-started/ --- -import "@styles/starlight_main.css"; + +import '@styles/starlight_main.css'; import { Card, CardGrid } from '@astrojs/starlight/components'; - - Understand the public product model before planning a rollout conversation. + + Verstehe das öffentliche Produktmodell, bevor ein Rollout-Gespräch geplant wird. - - Review how Tenantial frames inventory evidence, snapshots, findings, and restore planning. + + Prüfe, wie Tenantial Inventory Evidence, Snapshots, Findings und Restore-Planung rahmt. diff --git a/apps/website/src/data_files/constants.ts b/apps/website/src/data_files/constants.ts index 21a80165..f9b0a3da 100644 --- a/apps/website/src/data_files/constants.ts +++ b/apps/website/src/data_files/constants.ts @@ -2,11 +2,11 @@ import ogImageSrc from '@images/social.png'; export const SITE = { title: 'Tenantial', - tagline: 'Evidence-first Microsoft tenant governance', + tagline: 'Evidenzbasierte Microsoft-Tenant-Governance', description: - 'Tenantial helps Microsoft tenant administrators review policy evidence, snapshots, drift, findings, and restore plans before high-impact changes move forward.', + 'Tenantial hilft Microsoft-Tenant-Administratoren, Policy-Evidence, Snapshots, Drift, Findings und Restore-Pläne vor kritischen Änderungen zu prüfen.', description_short: - 'Evidence-first governance workflows for Microsoft tenant review, backup, drift, and restore planning.', + 'Evidenzbasierte Governance-Workflows für Microsoft-Tenant-Review, Backup, Drift und Restore-Planung.', url: 'https://tenantial.com', author: 'Tenantial', }; @@ -17,7 +17,7 @@ export const SEO = { structuredData: { '@context': 'https://schema.org', '@type': 'WebPage', - inLanguage: 'en-US', + inLanguage: 'de', '@id': SITE.url, url: SITE.url, name: SITE.title, @@ -32,12 +32,12 @@ export const SEO = { }; export const OG = { - locale: 'en_US', + locale: 'de_DE', type: 'website', url: SITE.url, - title: `${SITE.title}: Evidence-first Microsoft tenant governance`, + title: `${SITE.title}: Evidenzbasierte Microsoft-Tenant-Governance`, description: - 'Review tenant inventory, snapshots, drift, findings, exceptions, and restore plans with conservative, audit-friendly workflows.', + 'Prüfe Tenant Inventory, Snapshots, Drift, Findings, Exceptions und Restore-Pläne mit konservativen, auditfreundlichen Workflows.', image: ogImageSrc, }; diff --git a/apps/website/src/data_files/faqs.json b/apps/website/src/data_files/faqs.json index 69a8349d..a409fc17 100644 --- a/apps/website/src/data_files/faqs.json +++ b/apps/website/src/data_files/faqs.json @@ -1,6 +1,10 @@ { "subTitle": "Straight answers for public website visitors.", "faqs": [ + { + "question": "What does Tenantial help teams understand?", + "answer": "Tenantial is positioned around backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews for Microsoft tenant configuration." + }, { "question": "Does this public website connect to a live tenant?", "answer": "No. Product previews and examples are static demonstrations and do not authenticate visitors or read tenant data." @@ -9,10 +13,6 @@ "question": "Is restore positioned as an automatic outcome?", "answer": "No. Tenantial describes restore as a cautious workflow with preview, validation, selective scope, explicit confirmation, and review context." }, - { - "question": "Are pricing plans self-serve?", - "answer": "Not from this website. Pricing is framed around evaluation and scoped rollout conversations until commercial packaging is finalized." - }, { "question": "Does Tenantial publish customer proof here?", "answer": "No customer logos, third-party endorsements, external assurance statements, service-level commitments, or recovery promises are published without supplied and reviewed evidence." diff --git a/apps/website/src/data_files/features.json b/apps/website/src/data_files/features.json index a822fdfe..c7c7337f 100644 --- a/apps/website/src/data_files/features.json +++ b/apps/website/src/data_files/features.json @@ -1,22 +1,22 @@ [ { - "heading": "Inventory evidence", - "content": "Normalize observed policy metadata so reviews start from what was last seen, not scattered exports.", + "heading": "Backup evidence", + "content": "Review observed policy metadata and backup snapshots from one readable source before decisions start.", "svg": "frame" }, { - "heading": "Immutable snapshots", - "content": "Keep point-in-time policy records that can be compared, reviewed, and used for restore planning.", + "heading": "Restore planning", + "content": "Compare immutable snapshots and plan restore work with validation, selective scope, and explicit review.", "svg": "dashboard" }, { - "heading": "Drift review", - "content": "Turn differences and findings into readable review work before administrators decide what to change.", + "heading": "Drift detection", + "content": "Turn differences, findings, and evidence into readable review work before administrators decide what to change.", "svg": "verified" }, { - "heading": "Restore caution", - "content": "Frame restore around preview, validation, conflict awareness, selective scope, and explicit confirmation.", + "heading": "Auditability and exceptions", + "content": "Keep exceptions, review notes, and auditability visible so decisions stay attributable.", "svg": "checkCircle" } ] diff --git a/apps/website/src/data_files/pricing.json b/apps/website/src/data_files/pricing.json index 40977f80..392cdd6a 100644 --- a/apps/website/src/data_files/pricing.json +++ b/apps/website/src/data_files/pricing.json @@ -1,12 +1,12 @@ { "title": "Evaluation and rollout options", - "subTitle": "Commercial packaging is intentionally conservative on the public website. No self-serve checkout or fixed entitlement claim is implied.", + "subTitle": "Commercial packaging is intentionally conservative on the public website. No instant payment path or fixed access package is implied.", "badge": "Scoped", - "thirdOption": "Need a narrower advisory conversation?", - "btnText": "Contact us", + "thirdOption": "Need a narrower evaluation conversation?", + "btnText": "Contact Tenantial", "starterKit": { "name": "Evaluation", - "description": "For teams reviewing fit before rollout.", + "description": "For teams reviewing fit before any rollout commitment.", "price": "Contact", "cents": "", "billingFrequency": "for scope", @@ -20,7 +20,7 @@ }, "professionalToolbox": { "name": "Managed rollout", - "description": "For planned staging and operator review.", + "description": "For planned staging and operator review conversations.", "price": "Scoped", "cents": "", "billingFrequency": "quote", diff --git a/apps/website/src/data_files/site-copy.ts b/apps/website/src/data_files/site-copy.ts new file mode 100644 index 00000000..a9307cd7 --- /dev/null +++ b/apps/website/src/data_files/site-copy.ts @@ -0,0 +1,651 @@ +import type { Locale } from '@/i18n'; + +type Feature = { + heading: string; + content: string; + svg: string; +}; + +type PricingProduct = { + name: string; + description: string; + price: string; + cents: string; + billingFrequency: string; + features: string[]; + purchaseBtnTitle: string; + purchaseLink: string; +}; + +type Pricing = { + title: string; + subTitle: string; + badge: string; + thirdOption: string; + btnText: string; + starterKit: PricingProduct; + professionalToolbox: PricingProduct; +}; + +type FaqGroup = { + subTitle: string; + faqs: Array<{ question: string; answer: string }>; +}; + +export const siteCopy: Record = { + de: { + site: { + tagline: 'Evidenzbasierte Microsoft-Tenant-Governance', + description: + 'Tenantial hilft Microsoft-Tenant-Administratoren, Policy-Evidence, Snapshots, Drift, Findings und Restore-Pläne vor kritischen Änderungen zu prüfen.', + descriptionShort: + 'Evidenzbasierte Governance-Workflows für Microsoft-Tenant-Reviews, Backup, Drift und Restore-Planung.', + }, + nav: [ + { name: 'Start', url: '/' }, + { name: 'Plattform', url: '/platform' }, + { name: 'Preise', url: '/pricing' }, + { name: 'Vertrauen', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + { name: 'Kontakt', url: '/contact' }, + ], + footer: { + sections: [ + { + section: 'Produkt', + links: [ + { name: 'Plattform', url: '/platform' }, + { name: 'Preise', url: '/pricing' }, + { name: 'Vertrauen', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + ], + }, + { + section: 'Rechtliches', + links: [ + { name: 'Datenschutz', url: '/privacy' }, + { name: 'Nutzungsbedingungen', url: '/terms' }, + { name: 'Rechtliches', url: '/legal' }, + { name: 'Impressum', url: '/imprint' }, + ], + }, + ], + conversationTitle: 'Scoped Conversation starten', + conversationContent: + 'Nutze die Kontaktseite oder E-Mail nur für Business-Kontext. Sende keine Secrets, Credentials oder Tenant-Exporte über die öffentliche Website.', + contactButton: 'Kontaktseite öffnen', + copyrightSuffix: 'Nur öffentliche Website-Inhalte.', + }, + auth: { + walkthrough: 'Walkthrough anfragen', + }, + home: { + pageTitle: 'Tenantial | Evidenzbasierte Microsoft-Tenant-Governance', + metaDescription: + 'Tenantial ist evidenzbasierte Governance für Microsoft-Tenant-Konfiguration mit Backup, Restore, Drift Detection, Findings, Evidence, Auditability, Exceptions und Reviews.', + heroTitle: + 'Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration', + heroSubtitle: + 'Tenantial hilft Administratoren, Backup-Evidence, unveränderliche Snapshots, Drift Detection, Findings, Exceptions, Auditability und Restore-Pläne zu prüfen, bevor kritische Änderungen umgesetzt werden.', + primaryCta: 'Walkthrough anfragen', + secondaryCta: 'Plattform ansehen', + heroAlt: 'Statische Tenantial Governance-Dashboard-Vorschau', + featureTitle: 'Backup, Restore und Drift Detection mit Review-Kontext', + featureSubtitle: + 'Tenantial hält Backup-Snapshots, Findings, Evidence, Auditability, Exceptions und Reviews sichtbar, damit Restore-Entscheidungen preview-first bleiben. Die öffentliche Vorschau ist statisch und verbindet sich nicht mit Live-Tenant-Daten.', + featureAlt: 'Statische Tenantial Review-Board-Vorschau', + workflowTitle: + 'Ein Review-Workflow für evidenzbasierte Tenant-Governance.', + tabs: [ + { + heading: 'Evidence Intake', + content: + 'Normalisiere Inventory-, Backup- und Snapshot-Kontext, damit Reviews mit einer lesbaren Sicht auf den beobachteten Policy-Zustand starten.', + svg: 'frame', + alt: 'Statische Tenantial Evidence-Intake-Vorschau', + first: true, + }, + { + heading: 'Decision Review', + content: + 'Mache Drift Detection, Findings, Exceptions, Auditability und Review-Notizen sichtbar, bevor Administratoren die nächste Aktion wählen.', + svg: 'dashboard', + alt: 'Statische Tenantial Decision-Review-Vorschau', + second: true, + }, + { + heading: 'Restore-Planung', + content: + 'Behandle Restore als preview-first Arbeit mit Validierung, Konfliktbewusstsein, selektivem Scope und expliziter Bestätigung.', + svg: 'verified', + alt: 'Statische Tenantial Restore-Planungs-Vorschau', + }, + ], + faqTitle: 'Häufige
Fragen', + }, + platform: { + pageTitle: 'Plattform | Tenantial', + metaDescription: + 'Tenantial öffentliches Produktmodell für Microsoft-Tenant Backup-Evidence, Restore-Planung, Drift Detection, Findings, Auditability, Exceptions und Reviews.', + heading: 'Plattform-Review-Modell', + subtitle: + 'Tenantial ist auf Backup-Evidence, unveränderliche Policy-Snapshots, strukturierte Diffs, Drift Detection, Findings, Exceptions, Auditability, Governance-Reviews und vorsichtige Restore-Workflows für Microsoft-Tenant-Administratoren ausgerichtet.', + backupTitle: 'Backup- und Snapshot-Evidence', + backupSubtitle: + 'Administratoren brauchen einen reproduzierbaren Nachweis dessen, was beobachtet wurde, bevor entschieden wird, ob eine Änderung sicher ist. Tenantial rahmt Backup-Snapshots als Review-Evidence, nicht als versteckte Automation.', + dashboardAlt: 'Statische Tenantial Inventory-Dashboard-Vorschau', + evidenceAlt: 'Statische Tenantial Evidence-Review-Vorschau', + driftTitle: 'Drift Detection, Findings und Exceptions', + driftSubtitle: + 'Unterschiede und Findings sollen lesbare Entscheidungsarbeit werden. Exceptions, Evidence, Review-Notizen und Auditability halten die Begründung sichtbar, ohne zu behaupten, dass die öffentliche Website mit Live-Tenant-Daten verbunden ist.', + driftAlt: 'Statische Tenantial Drift-Workflow-Vorschau', + trustCta: 'Trust-Posture ansehen', + restoreTitle: 'Restore-Planung bleibt defensiv', + restoreSubtitle: + 'Restore-Pfade werden als preview-first Workflows mit Validierung, Konfliktbewusstsein, selektivem Scope und expliziter Bestätigung vor sensiblen Aktionen beschrieben.', + restoreAlt: 'Statische Tenantial Rollout-Readiness-Plan-Vorschau', + rolloutCta: 'Rollout besprechen', + boundaryTitle: 'Grenzen der öffentlichen Vorschau', + boundarySubtitle: + 'Die Website nutzt statische Demo-Previews, um Richtung und visuelle Grundlage zu zeigen. Sie authentifiziert keine Besucher, liest keinen Microsoft Tenant, führt keine Operationen aus und speichert keine Tenant-Exporte.', + stats: [ + { stat: 'Statisch', description: 'Produkt-Previews' }, + { stat: 'Preview', description: 'vor Restore-Ausführung' }, + { stat: 'Review', description: 'vor kritischen Änderungen' }, + ], + mainStatTitle: '0', + mainStatSubTitle: + 'Live-Tenant-Datensätze werden von dieser öffentlichen Website genutzt', + }, + pricingIntro: { + pageTitle: 'Preise | Tenantial', + metaDescription: + 'Tenantial Preise nutzen konservative Evaluierungs- und Rollout-Gespräche statt sofortiger Zahlung oder Access-Claims.', + heading: 'Preise bleiben scoped', + subtitle: + 'Tenantial behauptet auf der öffentlichen Website keine Sofortzahlung, keinen festen Paket-Zugang und keine automatische Tenant-Verbindung.', + }, + contact: { + pageTitle: 'Kontakt | Tenantial', + metaDescription: + 'Tenantial Walkthrough anfragen oder ein scoped Rollout-Gespräch starten. Die öffentliche Website sammelt keine Live-Tenant-Daten.', + title: 'Kontakt zu Tenantial', + subtitle: + 'Fordere einen Walkthrough an oder starte ein scoped Rollout-Gespräch. Sende keine Secrets, Credentials oder Tenant-Exporte über diese öffentliche Website.', + formTitle: 'Walkthrough-Anfrage vorbereiten', + formSubtitle: + 'Dieses statische Formular sendet nicht an ein Backend. Nutze es, um Kontext vor einer E-Mail an Tenantial vorzubereiten.', + firstName: 'Vorname', + lastName: 'Nachname', + email: 'E-Mail', + phone: 'Telefonnummer', + details: 'Details', + submit: 'E-Mail-Entwurf vorbereiten', + blocks: { + docsHeading: 'Knowledgebase', + docsContent: 'Lies öffentliche Notizen zum Tenantial-Review-Modell.', + docsLink: 'Docs besuchen', + faqHeading: 'FAQ', + faqContent: + 'Durchsuche die FAQ für kurze, klare Antworten auf häufige Fragen.', + faqLink: 'FAQ besuchen', + boundaryHeading: 'Grenze der Produktvorschau', + boundaryContent: + 'Die Website verbindet sich nicht mit Live-Tenant-Daten und startet keine Backend-Workflows.', + emailHeading: 'Kontakt per E-Mail', + emailContent: 'Lieber schriftlich? Schreibe uns eine E-Mail an', + }, + }, + trust: { + pageTitle: 'Vertrauen | Tenantial', + metaDescription: + 'Tenantial öffentliche Trust-Posture mit konservativen Claims und klaren Grenzen für statische Previews.', + heading: 'Vertrauen beginnt mit klaren Grenzen', + subtitle: + 'Tenantial Public Copy vermeidet Customer-Logo-Proof, Third-Party-Endorsements, externe Assurance-Aussagen, Service-Level-Commitments und Recovery-Versprechen, solange sie nicht geliefert und geprüft wurden.', + cta: 'Tenantial kontaktieren', + statsTitle: 'Public Website Posture', + statsSubtitle: + 'Die Website beschreibt eine vorsichtige Produktrichtung. Sie legt keine privaten Tenant-Daten offen, führt keine Tenant-Operationen aus und ist kein Support-Evidence-Portal.', + mainStatTitle: 'Statisch', + mainStatSubTitle: 'nur Produkt-Previews', + stats: [ + { stat: 'Kein', description: 'Customer-Logo-Proof' }, + { stat: 'Kein', description: 'externer Endorsement-Claim' }, + { stat: 'Keine', description: 'Live-Tenant-Verbindung' }, + ], + }, + legal: { + pageTitle: 'Rechtliches | Tenantial', + metaDescription: + 'Tenantial öffentliche rechtliche Website-Informationen.', + heading: 'Öffentliche rechtliche Informationen', + subtitle: + 'Diese Seiten enthalten konservative Informationen zur öffentlichen Website und sollten vor einer produktiven Veröffentlichung in regulierten Kontexten geprüft werden.', + privacy: 'Datenschutz', + terms: 'Nutzungsbedingungen', + imprint: 'Impressum', + }, + privacy: { + pageTitle: 'Datenschutz | Tenantial', + metaDescription: 'Tenantial öffentlicher Website-Datenschutzhinweis.', + heading: 'Datenschutzhinweis', + subtitle: + 'Diese statische öffentliche Website beschreibt Tenantial und die Produktrichtung. Produkt-Previews verbinden sich nicht mit einem Live-Tenant. Kontaktlinks können deinen E-Mail-Client öffnen; sende keine Secrets, Credentials oder privaten Tenant-Exporte.', + }, + terms: { + pageTitle: 'Nutzungsbedingungen | Tenantial', + metaDescription: 'Tenantial öffentliche Website-Nutzungsbedingungen.', + heading: 'Website-Nutzungsbedingungen', + subtitle: + 'Die öffentliche Website beschreibt Tenantial-Positionierung und statische Produktkonzepte. Sie stellt keinen Produktionsservice bereit, verbindet sich nicht mit einem Tenant und führt keine administrativen Aktionen aus. Kommerzielle Bedingungen erfordern eine separate schriftliche Vereinbarung.', + }, + imprint: { + pageTitle: 'Impressum | Tenantial', + metaDescription: + 'Tenantial öffentliche Website-Veröffentlichungsinformationen.', + heading: 'Veröffentlichungsinformationen', + subtitle: + 'Publisher: Tenantial. Business-Kontakt: hello@tenantial.com. Formale Registrierungs- und Verantwortlichkeitsangaben sollten im schriftlichen Evaluierungsprozess bestätigt werden, bevor diese Seite für regulierte Publikationsanforderungen genutzt wird.', + }, + }, + en: { + site: { + tagline: 'Evidence-first Microsoft tenant governance', + description: + 'Tenantial helps Microsoft tenant administrators review policy evidence, snapshots, drift, findings, and restore plans before high-impact changes move forward.', + descriptionShort: + 'Evidence-first governance workflows for Microsoft tenant review, backup, drift, and restore planning.', + }, + nav: [ + { name: 'Home', url: '/' }, + { name: 'Platform', url: '/platform' }, + { name: 'Pricing', url: '/pricing' }, + { name: 'Trust', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + { name: 'Contact', url: '/contact' }, + ], + footer: { + sections: [ + { + section: 'Product', + links: [ + { name: 'Platform', url: '/platform' }, + { name: 'Pricing', url: '/pricing' }, + { name: 'Trust', url: '/trust' }, + { name: 'Docs', url: '/welcome-to-docs/' }, + ], + }, + { + section: 'Legal', + links: [ + { name: 'Privacy', url: '/privacy' }, + { name: 'Terms', url: '/terms' }, + { name: 'Legal', url: '/legal' }, + { name: 'Imprint', url: '/imprint' }, + ], + }, + ], + conversationTitle: 'Start a scoped conversation', + conversationContent: + 'Use the contact page or email for business context only. Do not send secrets, credentials, or tenant exports through the public website.', + contactButton: 'Open contact page', + copyrightSuffix: 'Public website content only.', + }, + auth: { + walkthrough: 'Request walkthrough', + }, + home: { + pageTitle: 'Tenantial | Evidence-first Microsoft tenant governance', + metaDescription: + 'Tenantial is evidence-first governance for Microsoft tenant configuration, with backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews.', + heroTitle: + 'Evidence-first governance for Microsoft tenant configuration', + heroSubtitle: + 'Tenantial helps administrators review backup evidence, immutable snapshots, drift detection, findings, exceptions, auditability, and restore plans before high-impact changes move forward.', + primaryCta: 'Request walkthrough', + secondaryCta: 'Explore platform', + heroAlt: 'Static Tenantial governance dashboard preview', + featureTitle: 'Backup, restore, and drift detection with review context', + featureSubtitle: + 'Tenantial keeps backup snapshots, findings, evidence, auditability, exceptions, and reviews visible so restore decisions stay preview-first. The public preview is static and does not connect to live tenant data.', + featureAlt: 'Static Tenantial review board preview', + workflowTitle: + 'A review workflow for evidence-first tenant governance.', + tabs: [ + { + heading: 'Evidence intake', + content: + 'Normalize inventory, backup, and snapshot context so reviews start from one readable view of observed policy state.', + svg: 'frame', + alt: 'Static Tenantial evidence intake preview', + first: true, + }, + { + heading: 'Decision review', + content: + 'Surface drift detection, findings, exceptions, auditability, and review notes before administrators choose the next action.', + svg: 'dashboard', + alt: 'Static Tenantial decision review preview', + second: true, + }, + { + heading: 'Restore planning', + content: + 'Treat restore as preview-first work with validation, conflict awareness, selective scope, and explicit confirmation.', + svg: 'verified', + alt: 'Static Tenantial restore planning preview', + }, + ], + faqTitle: 'Frequently
asked questions', + }, + platform: { + pageTitle: 'Platform | Tenantial', + metaDescription: + 'Tenantial public product model for Microsoft tenant backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews.', + heading: 'Platform review model', + subtitle: + 'Tenantial is positioned around backup evidence, immutable policy snapshots, structured diffs, drift detection, findings, exceptions, auditability, governance reviews, and cautious restore workflows for Microsoft tenant administrators.', + backupTitle: 'Backup and snapshot evidence', + backupSubtitle: + 'Administrators need a reproducible record of what was observed before deciding whether a change is safe. Tenantial frames backup snapshots as review evidence, not as hidden automation.', + dashboardAlt: 'Static Tenantial inventory dashboard preview', + evidenceAlt: 'Static Tenantial evidence review panel preview', + driftTitle: 'Drift detection, findings, and exceptions', + driftSubtitle: + 'Differences and findings should become readable decision work. Exceptions, evidence, review notes, and auditability keep the reasoning visible without claiming that the public website is connected to live tenant data.', + driftAlt: 'Static Tenantial drift workflow preview', + trustCta: 'Review trust posture', + restoreTitle: 'Restore planning stays defensive', + restoreSubtitle: + 'Restore paths are described as preview-first workflows with validation, conflict awareness, selective scope, and explicit confirmation before any sensitive action.', + restoreAlt: 'Static Tenantial rollout readiness plan preview', + rolloutCta: 'Discuss rollout', + boundaryTitle: 'Public preview boundaries', + boundarySubtitle: + 'The website uses static/demo previews to demonstrate the direction and visual foundation. It does not authenticate visitors, read a Microsoft tenant, execute operations, or store tenant exports.', + stats: [ + { stat: 'Static', description: 'product previews' }, + { stat: 'Preview', description: 'before restore execution' }, + { stat: 'Review', description: 'before high-impact changes' }, + ], + mainStatTitle: '0', + mainStatSubTitle: 'live tenant records used by this public website', + }, + pricingIntro: { + pageTitle: 'Pricing | Tenantial', + metaDescription: + 'Tenantial pricing uses conservative evaluation and rollout conversation paths, not instant public payment or access claims.', + heading: 'Pricing stays scoped', + subtitle: + 'Tenantial does not claim instant payment, fixed package access, or automatic tenant connection from the public website.', + }, + contact: { + pageTitle: 'Contact | Tenantial', + metaDescription: + 'Request a Tenantial walkthrough or start a scoped rollout conversation. The public website does not collect live tenant data.', + title: 'Contact Tenantial', + subtitle: + 'Request a walkthrough or start a scoped rollout conversation. Do not send secrets, credentials, or tenant exports through this public website.', + formTitle: 'Draft a walkthrough request', + formSubtitle: + 'This static form does not submit to a backend. Use it to draft context before emailing Tenantial.', + firstName: 'First Name', + lastName: 'Last Name', + email: 'Email', + phone: 'Phone Number', + details: 'Details', + submit: 'Prepare email draft', + blocks: { + docsHeading: 'Knowledgebase', + docsContent: 'Read public notes about the Tenantial review model.', + docsLink: 'Visit docs', + faqHeading: 'FAQ', + faqContent: + 'Explore our FAQ for quick, clear answers to common queries.', + faqLink: 'Visit FAQ', + boundaryHeading: 'Product preview boundary', + boundaryContent: + 'The website does not connect to live tenant data or start backend workflows.', + emailHeading: 'Contact us by email', + emailContent: 'Prefer the written word? Drop us an email at', + }, + }, + trust: { + pageTitle: 'Trust | Tenantial', + metaDescription: + 'Tenantial public trust posture with conservative claims and clear static preview boundaries.', + heading: 'Trust starts with clear boundaries', + subtitle: + 'Tenantial public copy avoids customer-logo proof, third-party endorsement language, external assurance statements, service-level commitments, and recovery promises unless supplied and reviewed.', + cta: 'Contact Tenantial', + statsTitle: 'Public website posture', + statsSubtitle: + 'The website describes a cautious product direction. It does not expose private tenant data, run tenant operations, or act as a support evidence portal.', + mainStatTitle: 'Static', + mainStatSubTitle: 'product previews only', + stats: [ + { stat: 'No', description: 'customer-logo proof' }, + { stat: 'No', description: 'external endorsement claim' }, + { stat: 'No', description: 'live tenant connection' }, + ], + }, + legal: { + pageTitle: 'Legal | Tenantial', + metaDescription: 'Tenantial public website legal information.', + heading: 'Public legal information', + subtitle: + 'These pages provide conservative public website information and should be reviewed before production publication in any regulated context.', + privacy: 'Privacy', + terms: 'Terms', + imprint: 'Imprint', + }, + privacy: { + pageTitle: 'Privacy | Tenantial', + metaDescription: 'Tenantial public website privacy notice.', + heading: 'Privacy notice', + subtitle: + 'This static public website describes Tenantial and its product direction. Product previews do not connect to a live tenant. Contact links may open your email client; do not send secrets, credentials, or private tenant exports.', + }, + terms: { + pageTitle: 'Terms | Tenantial', + metaDescription: 'Tenantial public website terms.', + heading: 'Website terms', + subtitle: + 'The public website describes Tenantial positioning and static product concepts. It does not provide a production service, connect to a tenant, or execute administrative actions. Commercial terms require a separate written agreement.', + }, + imprint: { + pageTitle: 'Imprint | Tenantial', + metaDescription: 'Tenantial public website publication information.', + heading: 'Publication information', + subtitle: + 'Publisher: Tenantial. Business contact: hello@tenantial.com. Formal registration and responsible-party details should be confirmed through the written evaluation process before relying on this page for regulated publication needs.', + }, + }, +}; + +export const featuresByLocale: Record = { + de: [ + { + heading: 'Backup-Evidence', + content: + 'Prüfe beobachtete Policy-Metadaten und Backup-Snapshots aus einer lesbaren Quelle, bevor Entscheidungen beginnen.', + svg: 'frame', + }, + { + heading: 'Restore-Planung', + content: + 'Vergleiche unveränderliche Snapshots und plane Restore-Arbeit mit Validierung, selektivem Scope und explizitem Review.', + svg: 'dashboard', + }, + { + heading: 'Drift Detection', + content: + 'Verwandle Unterschiede, Findings und Evidence in lesbare Review-Arbeit, bevor Administratoren Änderungen entscheiden.', + svg: 'verified', + }, + { + heading: 'Auditability und Exceptions', + content: + 'Halte Exceptions, Review-Notizen und Auditability sichtbar, damit Entscheidungen zuordenbar bleiben.', + svg: 'checkCircle', + }, + ], + en: [ + { + heading: 'Backup evidence', + content: + 'Review observed policy metadata and backup snapshots from one readable source before decisions start.', + svg: 'frame', + }, + { + heading: 'Restore planning', + content: + 'Compare immutable snapshots and plan restore work with validation, selective scope, and explicit review.', + svg: 'dashboard', + }, + { + heading: 'Drift detection', + content: + 'Turn differences, findings, and evidence into readable review work before administrators decide what to change.', + svg: 'verified', + }, + { + heading: 'Auditability and exceptions', + content: + 'Keep exceptions, review notes, and auditability visible so decisions stay attributable.', + svg: 'checkCircle', + }, + ], +}; + +export const pricingByLocale: Record = { + de: { + title: 'Evaluierungs- und Rollout-Optionen', + subTitle: + 'Kommerzielle Pakete bleiben auf der öffentlichen Website bewusst konservativ. Es wird kein Sofortkauf oder fester Access-Plan behauptet.', + badge: 'Scoped', + thirdOption: 'Brauchst du ein engeres Evaluierungsgespräch?', + btnText: 'Tenantial kontaktieren', + starterKit: { + name: 'Evaluation', + description: 'Für Teams, die Fit vor einer Rollout-Zusage prüfen.', + price: 'Kontakt', + cents: '', + billingFrequency: 'für Scope', + features: [ + 'Geführter Produkt-Walkthrough', + 'Statische Demo-Review', + 'Deployment-Fit-Gespräch', + ], + purchaseBtnTitle: 'Walkthrough anfragen', + purchaseLink: '/contact', + }, + professionalToolbox: { + name: 'Managed Rollout', + description: 'Für geplante Staging- und Operator-Review-Gespräche.', + price: 'Scoped', + cents: '', + billingFrequency: 'Angebot', + features: [ + 'Readiness Review', + 'Staging-Validierungsplan', + 'Operational Handoff Notes', + 'Trust- und Legal-Copy-Review', + ], + purchaseBtnTitle: 'Rollout besprechen', + purchaseLink: '/contact', + }, + }, + en: { + title: 'Evaluation and rollout options', + subTitle: + 'Commercial packaging is intentionally conservative on the public website. No instant payment path or fixed access package is implied.', + badge: 'Scoped', + thirdOption: 'Need a narrower evaluation conversation?', + btnText: 'Contact Tenantial', + starterKit: { + name: 'Evaluation', + description: 'For teams reviewing fit before any rollout commitment.', + price: 'Contact', + cents: '', + billingFrequency: 'for scope', + features: [ + 'Guided public product walkthrough', + 'Static demo review', + 'Deployment-fit discussion', + ], + purchaseBtnTitle: 'Request walkthrough', + purchaseLink: '/contact', + }, + professionalToolbox: { + name: 'Managed rollout', + description: 'For planned staging and operator review conversations.', + price: 'Scoped', + cents: '', + billingFrequency: 'quote', + features: [ + 'Readiness review', + 'Staging validation plan', + 'Operational handoff notes', + 'Trust and legal copy review', + ], + purchaseBtnTitle: 'Discuss rollout', + purchaseLink: '/contact', + }, + }, +}; + +export const faqsByLocale: Record = { + de: { + subTitle: 'Klare Antworten für Besucher der öffentlichen Website.', + faqs: [ + { + question: 'Was hilft Tenantial Teams zu verstehen?', + answer: + 'Tenantial ist auf Backup-Evidence, Restore-Planung, Drift Detection, Findings, Auditability, Exceptions und Reviews für Microsoft-Tenant-Konfiguration ausgerichtet.', + }, + { + question: + 'Verbindet sich diese öffentliche Website mit einem Live-Tenant?', + answer: + 'Nein. Produkt-Previews und Beispiele sind statische Demonstrationen und authentifizieren keine Besucher oder lesen Tenant-Daten.', + }, + { + question: 'Wird Restore als automatisches Ergebnis dargestellt?', + answer: + 'Nein. Tenantial beschreibt Restore als vorsichtigen Workflow mit Preview, Validierung, selektivem Scope, expliziter Bestätigung und Review-Kontext.', + }, + { + question: 'Veröffentlicht Tenantial hier Customer Proof?', + answer: + 'Ohne gelieferte und geprüfte Evidence werden keine Kundenlogos, Third-Party-Endorsements, externen Assurance-Aussagen, Service-Level-Commitments oder Recovery-Versprechen veröffentlicht.', + }, + ], + }, + en: { + subTitle: 'Straight answers for public website visitors.', + faqs: [ + { + question: 'What does Tenantial help teams understand?', + answer: + 'Tenantial is positioned around backup evidence, restore planning, drift detection, findings, auditability, exceptions, and reviews for Microsoft tenant configuration.', + }, + { + question: 'Does this public website connect to a live tenant?', + answer: + 'No. Product previews and examples are static demonstrations and do not authenticate visitors or read tenant data.', + }, + { + question: 'Is restore positioned as an automatic outcome?', + answer: + 'No. Tenantial describes restore as a cautious workflow with preview, validation, selective scope, explicit confirmation, and review context.', + }, + { + question: 'Does Tenantial publish customer proof here?', + answer: + 'No customer logos, third-party endorsements, external assurance statements, service-level commitments, or recovery promises are published without supplied and reviewed evidence.', + }, + ], + }, +}; diff --git a/apps/website/src/i18n.ts b/apps/website/src/i18n.ts new file mode 100644 index 00000000..9cb92569 --- /dev/null +++ b/apps/website/src/i18n.ts @@ -0,0 +1,75 @@ +export const defaultLocale = 'de'; +export const supportedLocales = ['de', 'en'] as const; + +export type Locale = (typeof supportedLocales)[number]; + +export const localeLabels: Record = { + de: 'Deutsch', + en: 'English', +}; + +export const localeHtmlLang: Record = { + de: 'de', + en: 'en', +}; + +export const localeOg: Record = { + de: 'de_DE', + en: 'en_US', +}; + +export function isLocale(value: string | undefined): value is Locale { + return supportedLocales.includes(value as Locale); +} + +export function getLocaleFromPath(pathname: string): Locale { + const firstSegment = pathname.split('/').filter(Boolean)[0]; + + return isLocale(firstSegment) ? firstSegment : defaultLocale; +} + +export function stripLocalePrefix(pathname: string): string { + const segments = pathname.split('/').filter(Boolean); + + if (isLocale(segments[0])) { + segments.shift(); + } + + const stripped = `/${segments.join('/')}`; + + return stripped === '/' ? '/' : stripped.replace(/\/+$/, ''); +} + +export function localizedPath(pathname: string, locale: Locale): string { + const basePath = stripLocalePrefix(pathname); + const keepTrailingSlash = pathname !== '/' && pathname.endsWith('/'); + const withTrailingSlash = (path: string) => + keepTrailingSlash && !path.endsWith('/') ? `${path}/` : path; + + if (locale === defaultLocale) { + return withTrailingSlash(basePath === '' ? '/' : basePath); + } + + if (basePath === '/' || basePath === '') { + return `/${locale}/`; + } + + return withTrailingSlash(`/${locale}${basePath}`); +} + +export function localizeHref(href: string, locale: Locale): string { + if ( + href.startsWith('http://') || + href.startsWith('https://') || + href.startsWith('mailto:') || + href.startsWith('tel:') || + href.startsWith('#') + ) { + return href; + } + + const [path, hash] = href.split('#'); + const localized = localizedPath(path || '/', locale); + + return hash ? `${localized}#${hash}` : localized; +} diff --git a/apps/website/src/images/tenantial-logo-lockup-mask.png b/apps/website/src/images/tenantial-logo-lockup-mask.png new file mode 100644 index 00000000..154f1f9b Binary files /dev/null and b/apps/website/src/images/tenantial-logo-lockup-mask.png differ diff --git a/apps/website/src/layouts/MainLayout.astro b/apps/website/src/layouts/MainLayout.astro index 847457f9..b196ab18 100644 --- a/apps/website/src/layouts/MainLayout.astro +++ b/apps/website/src/layouts/MainLayout.astro @@ -4,6 +4,7 @@ import Meta from '@components/Meta.astro'; import Navbar from '@components/sections/navbar&footer/Navbar.astro'; import FooterSection from '@components/sections/navbar&footer/FooterSection.astro'; import { SITE } from '@data/constants'; +import { getLocaleFromPath, localeHtmlLang, type Locale } from '@/i18n'; import '@styles/global.css'; // Setting expected props @@ -11,28 +12,32 @@ const { title = SITE.title, meta, structuredData, - lang = 'en', + lang, customDescription = null, customOgTitle = null, } = Astro.props; +const locale = (lang || getLocaleFromPath(Astro.url.pathname)) as Locale; +const htmlLang = localeHtmlLang[locale] || localeHtmlLang.de; + // Interface to type-check the properties interface Props { title?: string; meta?: string; structuredData?: object; - lang?: string; + lang?: Locale; customDescription?: string | null; customOgTitle?: string | null; } --- - + {/* Adding metadata to the HTML document */} diff --git a/apps/website/src/pages/contact.astro b/apps/website/src/pages/contact.astro index 65c1786f..a89d03f1 100644 --- a/apps/website/src/pages/contact.astro +++ b/apps/website/src/pages/contact.astro @@ -1,34 +1,5 @@ --- -// Import the necessary components -import MainLayout from '@/layouts/MainLayout.astro'; -import ContactSection from '@components/sections/misc/ContactSection.astro'; -import { SITE } from '@data/constants'; - -const pageTitle: string = `Contact | ${SITE.title}`; -const metaDescription = - 'Request a Tenantial walkthrough or start a scoped rollout conversation. The public website does not collect live tenant data.'; -const ogTitle = 'Contact | Tenantial'; +import ContactPage from '@components/pages/ContactPage.astro'; --- - - - + diff --git a/apps/website/src/pages/en/blog/index.astro b/apps/website/src/pages/en/blog/index.astro new file mode 100644 index 00000000..4f89caa4 --- /dev/null +++ b/apps/website/src/pages/en/blog/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 302); +--- diff --git a/apps/website/src/pages/en/contact.astro b/apps/website/src/pages/en/contact.astro new file mode 100644 index 00000000..2da4723c --- /dev/null +++ b/apps/website/src/pages/en/contact.astro @@ -0,0 +1,5 @@ +--- +import ContactPage from '@components/pages/ContactPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/imprint.astro b/apps/website/src/pages/en/imprint.astro new file mode 100644 index 00000000..ca4d8ed0 --- /dev/null +++ b/apps/website/src/pages/en/imprint.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/index.astro b/apps/website/src/pages/en/index.astro new file mode 100644 index 00000000..24804b32 --- /dev/null +++ b/apps/website/src/pages/en/index.astro @@ -0,0 +1,5 @@ +--- +import HomePage from '@components/pages/HomePage.astro'; +--- + + diff --git a/apps/website/src/pages/en/insights/index.astro b/apps/website/src/pages/en/insights/index.astro new file mode 100644 index 00000000..4f89caa4 --- /dev/null +++ b/apps/website/src/pages/en/insights/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 302); +--- diff --git a/apps/website/src/pages/en/legal.astro b/apps/website/src/pages/en/legal.astro new file mode 100644 index 00000000..5fdccf08 --- /dev/null +++ b/apps/website/src/pages/en/legal.astro @@ -0,0 +1,5 @@ +--- +import LegalPage from '@components/pages/LegalPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/platform.astro b/apps/website/src/pages/en/platform.astro new file mode 100644 index 00000000..b721543c --- /dev/null +++ b/apps/website/src/pages/en/platform.astro @@ -0,0 +1,5 @@ +--- +import PlatformPage from '@components/pages/PlatformPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/pricing.astro b/apps/website/src/pages/en/pricing.astro new file mode 100644 index 00000000..20118b67 --- /dev/null +++ b/apps/website/src/pages/en/pricing.astro @@ -0,0 +1,5 @@ +--- +import PricingPage from '@components/pages/PricingPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/privacy.astro b/apps/website/src/pages/en/privacy.astro new file mode 100644 index 00000000..5a37e88c --- /dev/null +++ b/apps/website/src/pages/en/privacy.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/product.astro b/apps/website/src/pages/en/product.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/product.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/products/index.astro b/apps/website/src/pages/en/products/index.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/products/index.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/services.astro b/apps/website/src/pages/en/services.astro new file mode 100644 index 00000000..4bfb0c43 --- /dev/null +++ b/apps/website/src/pages/en/services.astro @@ -0,0 +1,3 @@ +--- +return Astro.redirect('/en/platform', 301); +--- diff --git a/apps/website/src/pages/en/terms.astro b/apps/website/src/pages/en/terms.astro new file mode 100644 index 00000000..36116caf --- /dev/null +++ b/apps/website/src/pages/en/terms.astro @@ -0,0 +1,5 @@ +--- +import TextPage from '@components/pages/TextPage.astro'; +--- + + diff --git a/apps/website/src/pages/en/trust.astro b/apps/website/src/pages/en/trust.astro new file mode 100644 index 00000000..f6208fb9 --- /dev/null +++ b/apps/website/src/pages/en/trust.astro @@ -0,0 +1,5 @@ +--- +import TrustPage from '@components/pages/TrustPage.astro'; +--- + + diff --git a/apps/website/src/pages/imprint.astro b/apps/website/src/pages/imprint.astro index fcc59108..8dde866c 100644 --- a/apps/website/src/pages/imprint.astro +++ b/apps/website/src/pages/imprint.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/index.astro b/apps/website/src/pages/index.astro index 815818dd..ae6646ea 100644 --- a/apps/website/src/pages/index.astro +++ b/apps/website/src/pages/index.astro @@ -1,74 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import HeroSection from '@components/sections/landing/HeroSection.astro'; -import FeaturesGeneral from '@components/sections/features/FeaturesGeneral.astro'; -import FeaturesNavs from '@components/sections/features/FeaturesNavs.astro'; -import PricingSection from '@components/sections/pricing/PricingSection.astro'; -import FAQ from '@components/sections/misc/FAQ.astro'; -import heroImage from '@images/tenantial-dashboard.avif'; -import faqs from '@data/faqs.json'; -import features from '@data/features.json'; -import pricing from '@data/pricing.json'; -import featureImage from '@images/tenantial-review-board.avif'; -import reviewImage from '@images/tenantial-evidence-intake.avif'; -import evidenceImage from '@images/tenantial-decision-review.avif'; -import governanceImage from '@images/tenantial-restore-plan.avif'; +import HomePage from '@components/pages/HomePage.astro'; --- - - Microsoft tenants` - subTitle="Tenantial helps administrators review policy evidence, snapshots, drift, findings, exceptions, and restore plans before high-impact changes move forward." - primaryBtn="Request walkthrough" - primaryBtnURL="/contact" - secondaryBtn="Explore platform" - secondaryBtnURL="/platform" - withReview={false} - src={heroImage} - alt="Static Tenantial governance dashboard preview" - /> - - - - stable website foundation adapted for Tenantial review workflows.` - tabs={[ - { - heading: 'Evidence intake', - content: - 'Normalize inventory and snapshot context so reviews start from one readable view of observed policy state.', - svg: 'frame', - src: reviewImage, - alt: 'Static Tenantial evidence intake preview', - first: true, - }, - { - heading: 'Decision review', - content: - 'Surface drift, findings, exceptions, and audit context before administrators choose the next action.', - svg: 'dashboard', - src: evidenceImage, - alt: 'Static Tenantial decision review preview', - second: true, - }, - { - heading: 'Restore planning', - content: - 'Treat restore as preview-first work with validation, conflict awareness, selective scope, and explicit confirmation.', - svg: 'verified', - src: governanceImage, - alt: 'Static Tenantial restore planning preview', - }, - ]} - /> - - - - - + diff --git a/apps/website/src/pages/legal.astro b/apps/website/src/pages/legal.astro index 49c076da..c74e34c6 100644 --- a/apps/website/src/pages/legal.astro +++ b/apps/website/src/pages/legal.astro @@ -1,27 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; -import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro'; -import { SITE } from '@data/constants'; - -const pageTitle = `Legal | ${SITE.title}`; +import LegalPage from '@components/pages/LegalPage.astro'; --- - - -
-
- - - -
-
-
+ diff --git a/apps/website/src/pages/platform.astro b/apps/website/src/pages/platform.astro index f66eade5..27b691bf 100644 --- a/apps/website/src/pages/platform.astro +++ b/apps/website/src/pages/platform.astro @@ -1,98 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import LeftSection from '@components/ui/blocks/LeftSection.astro'; -import RightSection from '@components/ui/blocks/RightSection.astro'; -import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; -import dashboard from '@images/tenantial-dashboard.avif'; -import evidenceImage from '@images/tenantial-evidence-panel.avif'; -import reviewImage from '@images/tenantial-drift-workflow.avif'; -import restoreImage from '@images/tenantial-rollout-plan.avif'; -import { SITE } from '@data/constants'; - -const pageTitle: string = `Platform | ${SITE.title}`; -const metaDescription = - 'Tenantial platform overview for Microsoft tenant evidence, snapshots, drift review, findings, audit context, exceptions, and restore planning.'; -const ogTitle = 'Platform | Tenantial'; +import PlatformPage from '@components/pages/PlatformPage.astro'; --- - - - - - - - - - - - + diff --git a/apps/website/src/pages/pricing.astro b/apps/website/src/pages/pricing.astro index cfb3aee7..04fc0112 100644 --- a/apps/website/src/pages/pricing.astro +++ b/apps/website/src/pages/pricing.astro @@ -1,23 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import PricingSection from '@components/sections/pricing/PricingSection.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import pricing from '@data/pricing.json'; -import { SITE } from '@data/constants'; - -const pageTitle = `Pricing | ${SITE.title}`; -const metaDescription = - 'Tenantial pricing is presented as conservative evaluation and rollout conversation paths, not unsupported self-serve billing.'; +import PricingPage from '@components/pages/PricingPage.astro'; --- - - - - + diff --git a/apps/website/src/pages/privacy.astro b/apps/website/src/pages/privacy.astro index 31ed1cd7..e3315552 100644 --- a/apps/website/src/pages/privacy.astro +++ b/apps/website/src/pages/privacy.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/terms.astro b/apps/website/src/pages/terms.astro index ff3b5927..249750fb 100644 --- a/apps/website/src/pages/terms.astro +++ b/apps/website/src/pages/terms.astro @@ -1,16 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import { SITE } from '@data/constants'; +import TextPage from '@components/pages/TextPage.astro'; --- - - - + diff --git a/apps/website/src/pages/trust.astro b/apps/website/src/pages/trust.astro index a6b871dc..07c15764 100644 --- a/apps/website/src/pages/trust.astro +++ b/apps/website/src/pages/trust.astro @@ -1,44 +1,5 @@ --- -import MainLayout from '@/layouts/MainLayout.astro'; -import MainSection from '@components/ui/blocks/MainSection.astro'; -import FeaturesStats from '@components/sections/features/FeaturesStats.astro'; -import { SITE } from '@data/constants'; - -const pageTitle = `Trust | ${SITE.title}`; -const metaDescription = - 'Tenantial public trust posture with conservative claims and clear static preview boundaries.'; +import TrustPage from '@components/pages/TrustPage.astro'; --- - - - - + diff --git a/apps/website/src/utils/navigation.ts b/apps/website/src/utils/navigation.ts index 2df373fc..9dc10942 100644 --- a/apps/website/src/utils/navigation.ts +++ b/apps/website/src/utils/navigation.ts @@ -1,39 +1,10 @@ -const navBarLinks = [ - { name: 'Home', url: '/' }, - { name: 'Platform', url: '/platform' }, - { name: 'Pricing', url: '/pricing' }, - { name: 'Trust', url: '/trust' }, - { name: 'Contact', url: '/contact' }, -]; +import { siteCopy } from '@data/site-copy'; -const footerLinks = [ - { - section: 'Product', - links: [ - { name: 'Platform', url: '/platform' }, - { name: 'Pricing', url: '/pricing' }, - { name: 'Trust', url: '/trust' }, - { name: 'Docs', url: '/welcome-to-docs/' }, - ], - }, - { - section: 'Legal', - links: [ - { name: 'Privacy', url: '/privacy' }, - { name: 'Terms', url: '/terms' }, - { name: 'Legal', url: '/legal' }, - { name: 'Imprint', url: '/imprint' }, - ], - }, -]; +const navBarLinks = siteCopy.de.nav; -const socialLinks = { - facebook: '#', - x: '#', - github: '#', - google: '#', - slack: '#', -}; +const footerLinks = siteCopy.de.footer.sections; + +const socialLinks = {}; export default { navBarLinks, diff --git a/apps/website/src/utils/ui.ts b/apps/website/src/utils/ui.ts index b4cd8123..005a2b9d 100644 --- a/apps/website/src/utils/ui.ts +++ b/apps/website/src/utils/ui.ts @@ -1,4 +1,4 @@ export const languages = { + de: 'Deutsch', en: 'English', - fr: 'Français', }; diff --git a/apps/website/tests/smoke/interaction.spec.ts b/apps/website/tests/smoke/interaction.spec.ts index 1bc9b113..002a2e17 100644 --- a/apps/website/tests/smoke/interaction.spec.ts +++ b/apps/website/tests/smoke/interaction.spec.ts @@ -1,5 +1,8 @@ import { expect, test } from '@playwright/test'; -import { expectNoHorizontalOverflow } from './smoke-helpers'; +import { + expectCoreCapabilitiesVisible, + expectNoHorizontalOverflow, +} from './smoke-helpers'; test('mobile navigation opens with vendored foundation behavior', async ({ page, @@ -13,10 +16,13 @@ test('mobile navigation opens with vendored foundation behavior', async ({ const mobilePanel = page.locator('#navbar-collapse-with-animation'); await expect( - mobilePanel.getByRole('link', { name: 'Platform', exact: true }), + mobilePanel.getByRole('link', { name: 'Plattform', exact: true }) ).toBeVisible(); await expect( - mobilePanel.getByRole('link', { name: 'Request walkthrough' }), + mobilePanel.getByRole('link', { name: 'Docs', exact: true }) + ).toHaveAttribute('href', '/welcome-to-docs/'); + await expect( + mobilePanel.getByRole('link', { name: 'Walkthrough anfragen' }) ).toBeVisible(); await expectNoHorizontalOverflow(page); }); @@ -25,10 +31,137 @@ test('theme toggle keeps page content visible', async ({ page }) => { await page.goto('/'); await page.locator('button[aria-label="Dark Theme Toggle"]:visible').click(); - await expect(page.getByRole('heading', { name: /Evidence-first governance/ })) - .toBeVisible(); + await expect( + page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/, + }) + ).toBeVisible(); await page.locator('button[aria-label="Light Theme Toggle"]:visible').click(); - await expect(page.getByRole('heading', { name: /Evidence-first governance/ })) - .toBeVisible(); + await expect( + page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/, + }) + ).toBeVisible(); +}); + +test('homepage mobile first viewport remains readable', async ({ + page, + isMobile, +}) => { + test.skip(!isMobile, 'mobile viewport is covered by the mobile project'); + + await page.goto('/'); + + const heading = page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/i, + }); + await expect(heading).toBeVisible(); + + const box = await heading.boundingBox(); + expect(box?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(360); + await expectCoreCapabilitiesVisible(page); + await expectNoHorizontalOverflow(page); +}); + +test('desktop keyboard reaches navigation, CTAs, footer, and contact controls', async ({ + page, + isMobile, +}) => { + test.skip( + isMobile, + 'desktop keyboard order is covered by the desktop project' + ); + + await page.goto('/contact'); + + const reached = new Set(); + + for (let index = 0; index < 80; index += 1) { + await page.keyboard.press('Tab'); + + const active = await page.evaluate(() => { + const element = document.activeElement as HTMLElement | null; + + return { + id: element?.id || '', + href: element?.getAttribute('href') || '', + text: element?.textContent?.replace(/\s+/g, ' ').trim() || '', + aria: element?.getAttribute('aria-label') || '', + tag: element?.tagName || '', + }; + }); + + for (const value of [ + active.id, + active.href, + active.text, + active.aria, + active.tag, + ]) { + if (value) { + reached.add(value); + } + } + } + + expect([...reached].join('\n')).toContain('/platform'); + expect([...reached].join('\n')).toContain('/welcome-to-docs/'); + expect([...reached].join('\n')).toContain('Kontakt'); + expect([...reached].join('\n')).toContain('/privacy'); + expect([...reached].join('\n')).toContain('hs-firstname-contacts'); + expect([...reached].join('\n')).toContain('E-Mail-Entwurf vorbereiten'); +}); + +test('reduced motion keeps preview pages understandable', async ({ page }) => { + await page.emulateMedia({ reducedMotion: 'reduce' }); + + for (const route of ['/', '/platform', '/pricing'] as const) { + await page.goto(route); + + await expect(page.locator('h1').first()).toBeVisible(); + await expect(page.locator('main')).toContainText('Tenantial'); + await expectNoHorizontalOverflow(page); + } +}); + +test.describe('without JavaScript', () => { + test.use({ javaScriptEnabled: false }); + + test('primary content and links remain usable', async ({ page }) => { + for (const route of ['/', '/platform', '/contact'] as const) { + await page.goto(route); + + await expect(page.locator('h1').first()).toBeVisible(); + await expect( + page.getByRole('link', { name: /Kontakt|Walkthrough/i }).first() + ).toBeVisible(); + await expectNoHorizontalOverflow(page); + } + }); +}); + +test('language picker switches between German default and English routes', async ({ + page, + isMobile, +}) => { + test.skip(isMobile, 'desktop language picker is covered by this interaction'); + + await page.goto('/platform'); + + await page.getByLabel('Sprache wechseln').click(); + await page.getByRole('link', { name: 'English', exact: true }).click(); + + await expect(page).toHaveURL(/\/en\/platform\/?$/); + await expect( + page.getByRole('heading', { name: /Platform review model/i }) + ).toBeVisible(); + + await page.getByLabel('Sprache wechseln').click(); + await page.getByRole('link', { name: 'Deutsch', exact: true }).click(); + + await expect(page).toHaveURL(/\/platform\/?$/); + await expect( + page.getByRole('heading', { name: /Plattform-Review-Modell/i }) + ).toBeVisible(); }); diff --git a/apps/website/tests/smoke/public-routes.spec.ts b/apps/website/tests/smoke/public-routes.spec.ts index 3da9b140..5ba9a544 100644 --- a/apps/website/tests/smoke/public-routes.spec.ts +++ b/apps/website/tests/smoke/public-routes.spec.ts @@ -1,42 +1,288 @@ import { expect, test } from '@playwright/test'; import { - expectNoForbiddenPublicText, + canonicalSiteUrl, + docsRoutes, + expectCoreCapabilitiesVisible, + expectMetadataForRoute, + expectNoForbiddenPublicClaims, expectNoHorizontalOverflow, + expectNoPlaceholderLinks, + expectPublicLinksAreIntentional, + expectSitemapExcludesRoutes, + expectSitemapIncludesRoutes, + readSitemapUrls, + redirectRouteExpectations, redirectRoutes, renderedRoutes, } from './smoke-helpers'; +import { readFile } from 'node:fs/promises'; +import { globby } from 'globby'; + +const routeMetadata = { + '/': { + title: /Tenantial.*Evidenzbasierte Microsoft-Tenant-Governance/i, + description: /evidenzbasierte Governance/i, + }, + '/platform': { + title: /Plattform \| Tenantial/i, + description: /öffentliches Produktmodell/i, + }, + '/pricing': { + title: /Preise \| Tenantial/i, + description: /Evaluierungs.*Rollout/i, + }, + '/contact': { + title: /Kontakt \| Tenantial/i, + description: /Walkthrough.*Rollout/i, + }, + '/trust': { + title: /Vertrauen \| Tenantial/i, + description: /Trust-Posture.*statische.*Previews/i, + }, + '/legal': { + title: /Rechtliches \| Tenantial/i, + description: /rechtliche Website-Informationen/i, + }, + '/privacy': { + title: /Datenschutz \| Tenantial/i, + description: /Website-Datenschutzhinweis/i, + }, + '/terms': { + title: /Nutzungsbedingungen \| Tenantial/i, + description: /Website-Nutzungsbedingungen/i, + }, + '/imprint': { + title: /Impressum \| Tenantial/i, + description: /Veröffentlichungsinformationen/i, + }, + '/welcome-to-docs/': { + title: /Tenantial Docs/i, + description: /Microsoft-Tenant-Governance-Modell/i, + }, + '/guides/intro/': { + title: /Einführung in Tenantial/i, + description: /Microsoft-Tenant-Governance-Modell/i, + }, + '/guides/getting-started/': { + title: /Getting Started/i, + description: /Tenantial Walkthrough/i, + }, + '/guides/first-project-checklist/': { + title: /Evaluierungscheckliste/i, + description: /frühe Tenantial-Evaluierung/i, + }, + '/platform/evidence-review/': { + title: /Evidence Review/i, + description: /Evidence-Review-Workflows/i, + }, + '/en/': { + title: /Tenantial.*Evidence-first Microsoft tenant governance/i, + description: + /evidence-first governance for Microsoft tenant configuration/i, + }, + '/en/platform': { + title: /Platform \| Tenantial/i, + description: /public product model for Microsoft tenant/i, + }, + '/en/pricing': { + title: /Pricing \| Tenantial/i, + description: /evaluation and rollout conversation paths/i, + }, + '/en/contact': { + title: /Contact \| Tenantial/i, + description: /walkthrough.*scoped rollout conversation/i, + }, + '/en/trust': { + title: /Trust \| Tenantial/i, + description: /conservative claims.*static preview boundaries/i, + }, + '/en/legal': { + title: /Legal \| Tenantial/i, + description: /public website legal information/i, + }, + '/en/privacy': { + title: /Privacy \| Tenantial/i, + description: /public website privacy notice/i, + }, + '/en/terms': { + title: /Terms \| Tenantial/i, + description: /public website terms/i, + }, + '/en/imprint': { + title: /Imprint \| Tenantial/i, + description: /public website publication information/i, + }, + '/en/welcome-to-docs/': { + title: /Tenantial Docs/i, + description: /evidence-first Microsoft tenant governance model/i, + }, + '/en/guides/intro/': { + title: /Introduction to Tenantial/i, + description: /evidence-first Microsoft tenant governance model/i, + }, + '/en/guides/getting-started/': { + title: /Getting Started/i, + description: /Tenantial walkthrough/i, + }, + '/en/guides/first-project-checklist/': { + title: /Evaluation Checklist/i, + description: /conservative checklist for early Tenantial evaluation/i, + }, + '/en/platform/evidence-review/': { + title: /Evidence Review/i, + description: /Tenantial evidence review workflows/i, + }, +} as const; for (const route of renderedRoutes) { test(`renders intentional route ${route}`, async ({ page }) => { await page.goto(route); await expect(page.locator('body')).toContainText('Tenantial'); - await expectNoForbiddenPublicText(page); + await expectNoForbiddenPublicClaims(page); await expectNoHorizontalOverflow(page); }); } -for (const route of redirectRoutes) { - test(`redirects ${route} intentionally`, async ({ page }) => { +test('homepage first viewport explains core Tenantial capabilities', async ({ + page, +}) => { + await page.goto('/'); + + const heading = page.getByRole('heading', { + name: /Evidenzbasierte Governance für Microsoft-Tenant-Konfiguration/i, + }); + await expect(heading).toBeVisible(); + + const box = await heading.boundingBox(); + expect(box?.y ?? Number.POSITIVE_INFINITY).toBeLessThan(420); + await expect( + page.getByRole('link', { name: /Walkthrough anfragen/i }).first() + ).toHaveAttribute('href', '/contact'); + await expect( + page.getByRole('link', { name: /Plattform ansehen/i }).first() + ).toHaveAttribute('href', '/platform'); + await expectCoreCapabilitiesVisible(page); +}); + +test('/platform explains the public product model without internal runtime terms', async ({ + page, +}) => { + await page.goto('/platform'); + + await expect( + page.getByRole('heading', { name: /Plattform-Review-Modell/i }) + ).toBeVisible(); + await expectCoreCapabilitiesVisible(page); + await expect(page.locator('body')).toContainText(/statische Demo-Previews/i); + await expect(page.locator('body')).toContainText( + /authentifiziert keine Besucher, liest keinen Microsoft Tenant, führt keine Operationen aus und speichert keine Tenant-Exporte/i + ); + + const text = await page.locator('body').innerText(); + expect(text).not.toMatch(/\b(Laravel|Filament|Livewire)\b/i); + expect(text).not.toMatch(/\bGraph runtime\b/i); +}); + +for (const route of renderedRoutes) { + test(`public links on ${route} resolve intentionally`, async ({ + page, + request, + }) => { await page.goto(route); - await expect(page).toHaveURL(/\/platform\/?$/); - await expect(page.getByRole('heading', { name: /Platform review model/ })) - .toBeVisible(); + await expectNoPlaceholderLinks(page); + await expectPublicLinksAreIntentional(page, request, route); + }); +} + +test('built HTML emits no placeholder href values', async () => { + const htmlFiles = await globby('dist/**/*.html', { + absolute: true, + cwd: process.cwd(), + }); + + expect(htmlFiles.length).toBeGreaterThan(0); + + for (const file of htmlFiles) { + const html = await readFile(file, 'utf8'); + expect(html, file).not.toMatch(/href=(["'])#\1/); + expect(html, file).not.toMatch(/href=\{["']#["']\}/); + } +}); + +for (const route of [ + '/trust', + '/pricing', + '/legal', + '/privacy', + '/terms', + '/imprint', + ...docsRoutes, +] as const) { + test(`claim-sensitive route ${route} stays conservative`, async ({ + page, + }) => { + await page.goto(route); + + await expectNoForbiddenPublicClaims(page); + await expectNoHorizontalOverflow(page); + }); +} + +for (const [route, expected] of Object.entries(routeMetadata)) { + test(`metadata is route-specific for ${route}`, async ({ page }) => { + await page.goto(route); + + await expectMetadataForRoute(page, route, expected); + }); +} + +for (const route of redirectRoutes) { + test(`redirects ${route} intentionally`, async ({ page, request }) => { + const response = await request.get(route, { maxRedirects: 0 }); + const expected = redirectRouteExpectations[route]; + + if (response.status() >= 300 && response.status() < 400) { + expect(response.status()).toBe(expected.status); + expect(response.headers().location).toBe(expected.target); + } else { + expect(response.status()).toBe(200); + const html = await response.text(); + + expect(html).toContain('name="robots" content="noindex"'); + expect(html).toContain( + `rel="canonical" href="${canonicalSiteUrl}${expected.target}"` + ); + expect(html).toContain(`href="${expected.target}"`); + } + + await page.goto(route); + + await expect(page).toHaveURL(new RegExp(`${expected.target}/?$`)); + await expect( + page.getByRole('heading', { + name: /Plattform-Review-Modell|Platform review model/, + }) + ).toBeVisible(); }); } test('robots and sitemap are available', async ({ page }) => { await page.goto('/robots.txt'); - await expect(page.locator('body')).toContainText('Sitemap:'); + await expect(page.locator('body')).toContainText( + `Sitemap: ${canonicalSiteUrl}/sitemap-index.xml` + ); await page.goto('/sitemap-index.xml'); await expect(page.locator('body')).toContainText('sitemap-0.xml'); - - await page.goto('/sitemap-0.xml'); - const sitemap = await page.locator('body').innerText(); - - for (const route of redirectRoutes) { - expect(sitemap).not.toContain(`https://tenantial.com${route}/`); - } +}); + +test('sitemap exposes canonical routes and excludes redirect aliases', async ({ + page, +}) => { + const sitemapUrls = await readSitemapUrls(page); + + expectSitemapIncludesRoutes(sitemapUrls, renderedRoutes); + expectSitemapExcludesRoutes(sitemapUrls, redirectRoutes); }); diff --git a/apps/website/tests/smoke/smoke-helpers.ts b/apps/website/tests/smoke/smoke-helpers.ts index 0220b342..e1bda7b5 100644 --- a/apps/website/tests/smoke/smoke-helpers.ts +++ b/apps/website/tests/smoke/smoke-helpers.ts @@ -1,4 +1,4 @@ -import { expect, type Page } from '@playwright/test'; +import { expect, type APIRequestContext, type Page } from '@playwright/test'; export const renderedRoutes = [ '/', @@ -11,39 +11,142 @@ export const renderedRoutes = [ '/terms', '/imprint', '/welcome-to-docs/', + '/guides/intro/', + '/guides/getting-started/', + '/guides/first-project-checklist/', + '/platform/evidence-review/', + '/en/', + '/en/platform', + '/en/pricing', + '/en/contact', + '/en/trust', + '/en/legal', + '/en/privacy', + '/en/terms', + '/en/imprint', + '/en/welcome-to-docs/', + '/en/guides/intro/', + '/en/guides/getting-started/', + '/en/guides/first-project-checklist/', + '/en/platform/evidence-review/', ] as const; -export const redirectRoutes = [ - '/product', - '/products', - '/services', - '/blog', - '/insights', +export const docsRoutes = [ + '/welcome-to-docs/', + '/guides/intro/', + '/guides/getting-started/', + '/guides/first-project-checklist/', + '/platform/evidence-review/', + '/en/welcome-to-docs/', + '/en/guides/intro/', + '/en/guides/getting-started/', + '/en/guides/first-project-checklist/', + '/en/platform/evidence-review/', ] as const; -const forbiddenPublicText = [ - 'ScrewFast', - 'construction', - 'hardware', - 'template', - 'open-source', - 'TenantAtlas', - 'TenantPilot', - 'TenantCTRL', - 'trusted by', - 'SOC 2', - 'ISO 27001', - '99.9%', - 'Microsoft endorsed', - 'guaranteed recovery', - 'guaranteed compliance', +export const redirectRouteExpectations = { + '/product': { status: 301, target: '/platform' }, + '/products': { status: 301, target: '/platform' }, + '/services': { status: 301, target: '/platform' }, + '/blog': { status: 302, target: '/platform' }, + '/insights': { status: 302, target: '/platform' }, + '/en/product': { status: 301, target: '/en/platform' }, + '/en/products': { status: 301, target: '/en/platform' }, + '/en/services': { status: 301, target: '/en/platform' }, + '/en/blog': { status: 302, target: '/en/platform' }, + '/en/insights': { status: 302, target: '/en/platform' }, +} as const; + +export const redirectRoutes = Object.keys(redirectRouteExpectations) as Array< + keyof typeof redirectRouteExpectations +>; + +export const staticRoutes = [ + '/robots.txt', + '/sitemap-index.xml', + '/manifest.json', + '/favicon.ico', +] as const; + +export const canonicalSiteUrl = 'https://tenantial.com'; + +const forbiddenPublicPatterns = [ + { label: 'ScrewFast residue', pattern: /ScrewFast/i }, + { label: 'construction residue', pattern: /\bconstruction\b/i }, + { label: 'hardware residue', pattern: /\bhardware\b/i }, + { label: 'template residue', pattern: /\btemplate\b/i }, + { label: 'open-source residue', pattern: /open-source/i }, + { label: 'TenantAtlas residue', pattern: /TenantAtlas/i }, + { label: 'TenantPilot residue', pattern: /TenantPilot/i }, + { label: 'TenantCTRL residue', pattern: /TenantCTRL/i }, + { label: 'fake social proof', pattern: /trusted by/i }, + { label: 'SOC 2 claim', pattern: /SOC\s*2/i }, + { label: 'ISO 27001 claim', pattern: /ISO\s*27001/i }, + { label: 'uptime claim', pattern: /99\.9%|guaranteed uptime/i }, + { label: 'Microsoft endorsement', pattern: /Microsoft endorsed/i }, + { label: 'Microsoft certification claim', pattern: /Microsoft certified/i }, + { label: 'recovery guarantee', pattern: /guaranteed recovery/i }, + { label: 'compliance guarantee', pattern: /guaranteed compliance/i }, + { label: 'fake checkout CTA', pattern: /\b(buy now|checkout)\b/i }, + { + label: 'fake trial CTA', + pattern: /\b(start free trial|create account)\b/i, + }, + { label: 'fake app access CTA', pattern: /\b(sign in|log in|login)\b/i }, ]; +const metadataSelectors = [ + 'title', + 'meta[name="description"]', + 'meta[property="og:title"]', + 'meta[property="og:description"]', + 'meta[property="og:url"]', + 'meta[property="og:image"]', + 'meta[property="twitter:url"]', + 'meta[name="twitter:title"]', + 'meta[name="twitter:description"]', + 'meta[name="twitter:image"]', +].join(','); + +function normalizePath(path: string): string { + if (path === '') { + return '/'; + } + + return path === '/' || path.includes('.') || path.endsWith('/') + ? path + : `${path}/`; +} + +function normalizeRouteForSitemap(route: string): string { + return route === '/' ? '/' : normalizePath(route); +} + +function routeToCanonicalPath(route: string): string { + return route === '' ? '/' : route; +} + export async function expectNoForbiddenPublicText(page: Page): Promise { const text = await page.locator('body').innerText(); - for (const term of forbiddenPublicText) { - expect(text.toLowerCase()).not.toContain(term.toLowerCase()); + for (const { label, pattern } of forbiddenPublicPatterns) { + expect(text, label).not.toMatch(pattern); + } +} + +export async function expectNoForbiddenPublicClaims(page: Page): Promise { + await expectNoForbiddenPublicText(page); + + const metadataText = await page + .locator(metadataSelectors) + .evaluateAll(nodes => + nodes + .map(node => node.textContent || node.getAttribute('content') || '') + .join('\n') + ); + + for (const { label, pattern } of forbiddenPublicPatterns) { + expect(metadataText, `${label} in metadata`).not.toMatch(pattern); } } @@ -55,3 +158,164 @@ export async function expectNoHorizontalOverflow(page: Page): Promise { expect(metrics.scrollWidth).toBeLessThanOrEqual(metrics.clientWidth + 1); } + +export async function expectCoreCapabilitiesVisible(page: Page): Promise { + const text = (await page.locator('body').innerText()).toLowerCase(); + + for (const terms of [ + ['backup'], + ['restore'], + ['drift detection', 'drift'], + ['findings'], + ['evidence'], + ['auditability'], + ['exceptions'], + ['reviews', 'review'], + ]) { + expect( + terms.some(term => text.includes(term)), + `missing capability term: ${terms.join(' or ')}` + ).toBe(true); + } +} + +export async function expectNoPlaceholderLinks(page: Page): Promise { + const placeholders = await page + .locator('a[href="#"], a[href=""], area[href="#"], area[href=""]') + .evaluateAll(nodes => + nodes.map(node => ({ + text: node.textContent?.trim() || '', + href: node.getAttribute('href'), + })) + ); + + expect(placeholders).toEqual([]); +} + +export async function expectPublicLinksAreIntentional( + page: Page, + request: APIRequestContext, + sourceRoute: string +): Promise { + const allowedRoutes = new Set( + [ + ...renderedRoutes, + ...redirectRoutes, + ...staticRoutes, + '/sitemap-0.xml', + ].map(route => normalizePath(route)) + ); + + const hrefs = await page + .locator('a[href], area[href]') + .evaluateAll(nodes => + nodes + .map(node => node.getAttribute('href')?.trim()) + .filter((href): href is string => Boolean(href)) + ); + + for (const href of hrefs) { + expect(href, `placeholder link on ${sourceRoute}`).not.toBe('#'); + + if ( + href.startsWith('mailto:') || + href.startsWith('tel:') || + href.startsWith('javascript:') + ) { + continue; + } + + const url = new URL(href, canonicalSiteUrl); + + if (url.origin !== canonicalSiteUrl) { + expect(['http:', 'https:']).toContain(url.protocol); + continue; + } + + const normalizedPath = normalizePath(url.pathname); + expect( + allowedRoutes.has(normalizedPath), + `unexpected public link ${href} on ${sourceRoute}` + ).toBe(true); + + if (url.hash && normalizePath(sourceRoute) === normalizedPath) { + const targetId = url.hash.slice(1); + await expect( + page.locator(`#${targetId}`), + `missing anchor target ${url.hash} on ${sourceRoute}` + ).toHaveCount(1); + } + + if (!url.hash || normalizePath(sourceRoute) !== normalizedPath) { + const response = await request.get(url.pathname); + expect( + response.status(), + `unexpected status for linked route ${href} on ${sourceRoute}` + ).toBeLessThan(400); + } + } +} + +export async function expectMetadataForRoute( + page: Page, + route: string, + expected: { title: RegExp; description: RegExp } +): Promise { + await expect(page).toHaveTitle(expected.title); + await expect(page.locator('meta[name="description"]')).toHaveAttribute( + 'content', + expected.description + ); + + const canonicalUrl = `${canonicalSiteUrl}${routeToCanonicalPath(route)}`; + + await expect(page.locator('link[rel="canonical"]')).toHaveAttribute( + 'href', + canonicalUrl + ); + await expect(page.locator('meta[property="og:url"]').last()).toHaveAttribute( + 'content', + canonicalUrl + ); + await expect( + page + .locator('meta[property="twitter:url"], meta[name="twitter:url"]') + .last() + ).toHaveAttribute('content', canonicalUrl); + await expect( + page.locator('meta[property="og:image"]').last() + ).toHaveAttribute('content', /\/_astro\/social\.[^/]+\.png$/); + await expect( + page.locator('meta[name="twitter:image"]').last() + ).toHaveAttribute('content', /\/_astro\/social\.[^/]+\.png$/); +} + +export function extractSitemapUrls(xml: string): string[] { + return Array.from(xml.matchAll(/([^<]+)<\/loc>/g)).map(([, url]) => url); +} + +export async function readSitemapUrls(page: Page): Promise { + await page.goto('/sitemap-0.xml'); + + return extractSitemapUrls(await page.content()); +} + +export function expectSitemapIncludesRoutes( + urls: string[], + routes: readonly string[] +): void { + for (const route of routes) { + const url = `${canonicalSiteUrl}${normalizeRouteForSitemap(route)}`; + expect(urls, `sitemap missing ${url}`).toContain(url); + } +} + +export function expectSitemapExcludesRoutes( + urls: string[], + routes: readonly string[] +): void { + for (const route of routes) { + const url = `${canonicalSiteUrl}${normalizeRouteForSitemap(route)}`; + expect(urls, `sitemap should exclude ${url}`).not.toContain(url); + } +} diff --git a/specs/403-public-website-launch-readiness/contracts/public-site-contract.md b/specs/403-public-website-launch-readiness/contracts/public-site-contract.md new file mode 100644 index 00000000..6deeba5a --- /dev/null +++ b/specs/403-public-website-launch-readiness/contracts/public-site-contract.md @@ -0,0 +1,164 @@ +# Public Site Contract: Spec 403 Launch Readiness + +This feature has no REST, GraphQL, Laravel, Filament, Livewire, Microsoft Graph, database, queue, job, policy, RBAC, or product runtime API contract. + +The contract is the public static website behavior that reviewers and smoke tests must verify. + +## Rendered Canonical Routes + +The following German default routes must render intentionally and expose Tenantial-specific page content and metadata: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +The following English route mirrors must render intentionally under `/en/...`: + +- `/en/` +- `/en/platform` +- `/en/pricing` +- `/en/contact` +- `/en/trust` +- `/en/legal` +- `/en/privacy` +- `/en/terms` +- `/en/imprint` +- `/en/welcome-to-docs/` +- `/en/guides/intro/` +- `/en/guides/getting-started/` +- `/en/guides/first-project-checklist/` +- `/en/platform/evidence-review/` + +## Redirect-Only Aliases + +The following aliases must redirect intentionally to `/platform` and must not appear as canonical sitemap entries: + +- `/product` +- `/products` +- `/services` +- `/blog` +- `/insights` + +The English aliases must redirect intentionally to `/en/platform` and must not appear as canonical sitemap entries: + +- `/en/product` +- `/en/products` +- `/en/services` +- `/en/blog` +- `/en/insights` + +Current source behavior may use permanent or temporary redirects, but launch review must document which aliases are retained and why. + +Current planning baseline: + +- `/product`, `/products`, and `/services` use 301 redirects. +- `/blog` and `/insights` use 302 redirects. + +## Static Generated Routes + +The following static outputs must be available after build: + +- `/robots.txt` +- `/sitemap-index.xml` +- generated sitemap child files such as `/sitemap-0.xml` +- `/manifest.json` +- `/favicon.ico` + +`robots.txt` must point to `/sitemap-index.xml` for the configured public site URL. + +## Metadata Contract + +Each rendered canonical route must provide: + +- Tenantial-specific page title +- Tenantial-specific page description +- route-appropriate canonical, Open Graph, and Twitter URL values +- German default and English alternate `hreflang` links, plus `x-default` pointing at German +- locale-appropriate Open Graph locale values +- existing social image URL and MIME/type metadata +- no ScrewFast, construction/hardware, template, TenantAtlas, TenantPilot, or TenantCTRL residue in public metadata +- no unsupported proof claims in title, description, Open Graph, Twitter, schema, or docs metadata + +## CTA Contract + +Every public CTA, navigation item, footer link, docs link, and visible form-like control must satisfy: + +- resolves to an intentional route, anchor, static asset, or legitimate external URL +- no public `href="#"` placeholders +- demo/contact paths route to `/contact` or an intentional contact section +- localized public links remain in the current locale where a localized route exists +- no login, account creation, billing, checkout, backend submission, guaranteed provisioning, or app access implication unless implemented +- pricing CTAs remain contact/demo-oriented +- static form controls must not imply backend submission, guaranteed response, account creation, provisioning, or subscription workflow +- footer email/newsletter controls must be removed or reframed if no newsletter workflow exists + +## Trust And Pricing Claim Contract + +Public copy and metadata must avoid unsupported: + +- SOC 2, ISO, or other certification claims +- Microsoft endorsement claims +- compliance guarantees +- recovery guarantees +- uptime guarantees +- fake customer logos +- fake testimonials +- fake "trusted by" claims +- self-serve billing, checkout, subscription, or entitlement claims +- production legal details left as explicit replacement placeholders + +## Product Preview Contract + +Static product previews must: + +- be framed as illustrative, static, or demo content +- avoid implying live tenant data +- avoid internal Laravel/Filament implementation details +- avoid turning visual labels into product runtime taxonomy +- communicate meaning through text or labels, not color alone + +## Browser And Accessibility Contract + +Validated public routes must: + +- avoid body-level horizontal overflow on representative desktop and mobile viewports +- keep primary navigation, CTAs, footer links, and visible controls keyboard reachable +- show visible focus states for interactive elements +- remain understandable with reduced motion +- keep primary content and links usable if JavaScript fails where reasonably possible for a static marketing site + +## Launch Review Risk Register + +Implementation should explicitly resolve or document these current source risks: + +- Starlight child docs routes are public and indexed but not fully covered by current smoke route lists. +- Main-layout `og:url` and `twitter:url` are root-oriented rather than route-specific. +- Social image metadata may reference a missing or type-mismatched asset. +- `/imprint` contains placeholder production details. +- Contact form copy may imply a backend workflow that does not exist. +- Footer `Subscribe` wording may imply a newsletter workflow that does not exist. +- Source placeholder social links with `#` must not be emitted publicly. + +## Scope Contract + +Implementation and validation must not touch: + +- `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` +- Laravel providers, migrations, policies, jobs, queues, database, Filament resources, Livewire components, Blade views, tenant/workspace/RBAC code, Microsoft Graph code, or AuditLog behavior + +Scope proof command: + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform +``` diff --git a/specs/403-public-website-launch-readiness/data-model.md b/specs/403-public-website-launch-readiness/data-model.md new file mode 100644 index 00000000..1f4a9609 --- /dev/null +++ b/specs/403-public-website-launch-readiness/data-model.md @@ -0,0 +1,114 @@ +# Data Model: `apps/website` Public Website Launch Readiness + +This feature introduces no database tables, product runtime models, persisted entities, enums, status families, provider contracts, or shared product taxonomy. + +The records below are planning and validation concepts for static website artifacts only. + +## Public Route + +Represents an intentional public URL exposed by the static website. + +**Fields** + +- `path`: public URL path, such as `/`, `/platform`, or `/welcome-to-docs/` +- `routeKind`: `rendered`, `redirect`, `static`, or `docs` +- `canonicalTarget`: canonical page path when applicable +- `sitemapPolicy`: included or excluded from generated sitemap +- `navigationPolicy`: primary nav, footer nav, docs nav, hidden, or redirect-only +- `metadataRequired`: whether route-specific title and description must be reviewed +- `claimReviewRequired`: whether visible copy and metadata require proof-safety review + +**Validation Rules** + +- Rendered canonical routes must return successful HTML. +- Redirect-only aliases must resolve intentionally and be excluded from sitemap output. +- Static routes such as `/robots.txt` and `/sitemap-index.xml` must be available after build. +- Public route copy and metadata must not contain forbidden residue terms listed in Spec 403. + +## CTA Link + +Represents a public call-to-action, navigation item, footer link, docs link, or visible interactive route target. + +**Fields** + +- `label`: visible CTA or link text +- `sourceSurface`: page or component where the CTA appears +- `href`: route, anchor, or external URL target +- `targetType`: rendered route, anchor, static asset, external proof link, or redirect alias +- `supportedWorkflow`: contact/demo, product explanation, pricing review, trust/legal review, docs review, or navigation +- `claimRisk`: whether the CTA could imply login, billing, checkout, provisioning, account creation, backend submission, or unsupported workflow + +**Validation Rules** + +- Public CTAs must resolve to intentional routes or anchors. +- Public CTAs must not use `href="#"` placeholders. +- Demo/contact CTAs must lead to `/contact` or an intentional contact section. +- CTAs must not imply login, account creation, billing, checkout, backend submission, or automated onboarding unless implemented. + +## Public Claim + +Represents visible copy or metadata that could create a trust, compliance, product, pricing, or proof claim. + +**Fields** + +- `surface`: route, docs page, component, metadata source, or generated HTML surface +- `claimText`: reviewed copy or metadata phrase +- `claimCategory`: product positioning, trust, pricing, legal, preview framing, social proof, or deployment +- `proofStatus`: supplied proof, conservative placeholder, unsupported, or remove +- `forbiddenResidueCheck`: whether the phrase contains forbidden public residue terms + +**Validation Rules** + +- No unsupported SOC 2, ISO, Microsoft endorsement, uptime, recovery, compliance, or customer adoption claims. +- No fake customer logos, fake testimonials, or fake "trusted by" claims. +- Pricing copy must avoid self-serve subscription, billing, checkout, and entitlement claims unless workflow exists. +- Metadata must stay Tenantial-specific and route-appropriate. + +## Static Product Preview + +Represents product-like static/demo content shown on public pages. + +**Fields** + +- `surface`: route/component where preview appears +- `previewName`: human-readable preview name +- `framingCopy`: text that identifies the preview as static, illustrative, or demo +- `capabilityShown`: backup, restore, drift detection, findings, evidence, auditability, exceptions, reviews, or related product positioning +- `dataProvenance`: static/demo only +- `colorMeaningSupport`: text/icon/label support so meaning does not rely on color alone + +**Validation Rules** + +- Preview content must not imply live tenant data. +- Preview status-like values must not become shared product taxonomy. +- Preview meaning must not rely on color alone. +- Preview wording must avoid internal Laravel/Filament implementation details. + +## Launch Readiness Note + +Represents the implementation handoff artifact required by FR-034. + +**Fields** + +- `routeExposure`: canonical routes, docs routes, static routes, redirect aliases, and hidden routes +- `ctaAssumptions`: supported public CTA paths and intentionally absent workflows +- `trustPricingPosture`: conservative claim posture and unsupported claims avoided +- `sitemapBehavior`: sitemap inclusion/exclusion and robots entrypoint +- `deploymentExpectations`: build, smoke, static output, and deploy notes +- `platformScopeCheck`: recorded confirmation that `apps/platform` remains untouched + +**Validation Rules** + +- Must be stored inside the feature's website-local implementation or release handoff path. +- Must not introduce product runtime truth. +- Must document the exact validation commands reviewers should run. + +## Relationships + +- A `Public Route` may contain many `CTA Link` and `Public Claim` review points. +- A `Static Product Preview` belongs to one public route or docs route. +- The `Launch Readiness Note` summarizes all reviewed public routes, CTAs, claims, sitemap behavior, and deployment expectations. + +## State Transitions + +No product state transitions are introduced. Review progress may be tracked in tasks or checklists during implementation, but it must not become a product runtime status family. diff --git a/specs/403-public-website-launch-readiness/launch-readiness.md b/specs/403-public-website-launch-readiness/launch-readiness.md new file mode 100644 index 00000000..9b5910ed --- /dev/null +++ b/specs/403-public-website-launch-readiness/launch-readiness.md @@ -0,0 +1,127 @@ +# Launch Readiness Handoff: Spec 403 + +## Scope + +Runtime changes are limited to `apps/website`. Planning and handoff artifacts live in `specs/403-public-website-launch-readiness`. + +`apps/platform` remains out of scope and was checked with: + +```bash +git status --short -- apps/platform +``` + +The command printed no output. + +## Route Exposure + +Canonical rendered German default routes: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +Canonical rendered English routes: + +- `/en/` +- `/en/platform` +- `/en/pricing` +- `/en/contact` +- `/en/trust` +- `/en/legal` +- `/en/privacy` +- `/en/terms` +- `/en/imprint` +- `/en/welcome-to-docs/` +- `/en/guides/intro/` +- `/en/guides/getting-started/` +- `/en/guides/first-project-checklist/` +- `/en/platform/evidence-review/` + +Redirect-only aliases: + +- `/product` -> `/platform` +- `/products` -> `/platform` +- `/services` -> `/platform` +- `/blog` -> `/platform` +- `/insights` -> `/platform` +- `/en/product` -> `/en/platform` +- `/en/products` -> `/en/platform` +- `/en/services` -> `/en/platform` +- `/en/blog` -> `/en/platform` +- `/en/insights` -> `/en/platform` + +Static Astro output renders redirect aliases as noindex redirect HTML pages. Smoke coverage verifies the noindex marker, canonical target, and visible redirect link. + +Top navigation exposes `/welcome-to-docs/` as `Docs`, matching the public docs route instead of keeping documentation discoverable only through footer or contact-page links. + +German remains the unprefixed default locale. English is available as an additional locale under `/en/...`; the language picker switches between the current German route and its English counterpart where available. + +## CTA Assumptions + +Supported public CTA paths are contact/demo and product-evaluation paths only. + +- Homepage CTAs route to `/contact` and `/platform`. +- Platform CTAs route to `/contact` and `/trust`. +- Pricing CTAs route to `/contact`. +- Docs links route to intentional localized docs pages. +- Footer contact CTA routes to `/contact`. + +The public website does not claim login, account creation, self-service payment, automated provisioning, live tenant connection, or backend form submission. + +## Trust And Pricing Posture + +Public copy remains conservative: + +- no customer-logo proof +- no testimonials or third-party endorsement claims +- no external assurance or certification claims +- no uptime, recovery, or compliance guarantees +- no fixed package access or instant payment claims +- no live tenant data in static previews + +Static product previews are framed as static/demo/illustrative content and do not imply live Microsoft tenant access from the public website. + +## Sitemap And Robots + +`robots.txt` points to: + +```text +https://tenantial.com/sitemap-index.xml +``` + +Generated sitemap coverage includes canonical rendered German and English routes and excludes redirect-only aliases. Route-specific metadata checks cover title, description, canonical URL, alternate language links, Open Graph URL, Open Graph locale, Twitter URL, and generated PNG social image metadata. + +## Deployment Expectations + +Reviewer validation commands: + +```bash +corepack pnpm build:website +WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +git diff --check +git status --short -- apps/platform +! rg "href=\"#\"|href=\\{\"#\"\\}|href=\\{'#'\\}" apps/website/src +``` + +The build produces static output in `apps/website/dist`. Deploy the website artifact only; no Laravel, Filament, database, queue, Sail, Microsoft Graph, or `apps/platform` deployment step is required for this feature. + +## Review Evidence + +- Website build passed with existing Astro hints only. +- Public routes smoke suite passed: 230 passed. +- Interaction smoke suite passed: 10 passed, 4 intentionally skipped. +- Integrated browser review opened `http://127.0.0.1:4321/` with German default navigation and the language menu showing `Deutsch` and `English`. +- `git diff --check` passed. +- Source placeholder href scan passed. +- `apps/platform` scope check passed. diff --git a/specs/403-public-website-launch-readiness/plan.md b/specs/403-public-website-launch-readiness/plan.md new file mode 100644 index 00000000..05183472 --- /dev/null +++ b/specs/403-public-website-launch-readiness/plan.md @@ -0,0 +1,220 @@ +# Implementation Plan: `apps/website` Public Website Launch Readiness + +**Branch**: `feat/403-public-website-launch-readiness` | **Date**: 2026-05-21 | **Spec**: `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md` +**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md` + +## Summary + +Make the existing Spec 402 ScrewFast-derived Tenantial public website launch-ready without rebuilding the foundation or touching `apps/platform`. The implementation will stay inside `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` and focus on German-default plus English-prefixed public copy, CTA targets, route exposure, metadata, sitemap/robots behavior, conservative trust/pricing wording, static preview framing, and browser/static validation. + +## Technical Context + +**Language/Version**: TypeScript 6.0.3, Astro 6.3.3, Node.js >=20.0.0, pnpm 10.33.0 +**Primary Dependencies**: Astro, `@astrojs/starlight`, `@astrojs/sitemap`, `@astrojs/mdx`, Tailwind CSS v4, `@tailwindcss/vite`, Preline 4, Lenis, GSAP, Sharp, Playwright +**Storage**: N/A - static website content and generated build output only; no database or product persistence +**Testing**: Astro check/build plus Playwright smoke tests under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` +**Validation Lanes**: website build, public smoke, manual browser review, whitespace check, `apps/platform` scope check +**Target Platform**: Static Astro public website deployed from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`, with public site URL `https://tenantial.com` +**Project Type**: Web - standalone Astro public website inside monorepo +**Performance Goals**: No body-level horizontal overflow on validated desktop/mobile routes; static pages remain usable when JavaScript fails where reasonably possible +**Constraints**: Runtime/source changes are scoped to `apps/website`; Spec Kit artifacts may live under `specs/403-public-website-launch-readiness`; preserve Spec 402 ScrewFast-derived substrate; no backend contact/demo workflow; no authentication, billing, Microsoft Graph, Laravel, Filament, Livewire, database, queue, job, provider, policy, RBAC, or `apps/platform` changes +**Scale/Scope**: Public launch-readiness pass for canonical German default routes, English `/en/...` route mirrors, redirect aliases, docs routes, static metadata assets, navigation, footer CTAs, pricing/trust/legal/contact content, and Playwright smoke coverage + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: no admin/operator-facing product surface change; public website launch-readiness workflow only +- **Native vs custom classification summary**: ScrewFast-derived Astro website; no Filament/Blade/admin surface +- **Shared-family relevance**: none for Laravel/Filament shared interaction families +- **State layers in scope**: localized static page content, public route metadata, navigation/footer link data, Starlight docs configuration, Playwright smoke expectations +- **Audience modes in scope**: public visitor, buyer/reviewer, site owner/reviewer; no authenticated operator/MSP/support-platform modes +- **Decision/diagnostic/raw hierarchy plan**: public copy must be decision-first for visitors; diagnostics are limited to launch-readiness notes and tests, not visible runtime UI +- **Raw/support gating plan**: N/A - no raw/support product evidence surface +- **One-primary-action / duplicate-truth control**: each public page should keep a clear demo/contact path and avoid competing fake login, billing, checkout, or account CTAs +- **Handling modes by drift class or surface**: public route/copy/CTA drift is review-mandatory inside this feature; platform/admin drift is a hard stop +- **Repository-signal treatment**: `apps/platform` changes are a hard stop; generated website build artifacts are reviewed only if they become tracked changes +- **Special surface test profiles**: N/A - not a Filament surface +- **Required tests or manual smoke**: public smoke plus manual browser review across light/dark/mobile/desktop and keyboard reachability +- **Exception path and spread control**: none +- **Active feature PR close-out entry**: Smoke Coverage + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: no +- **Systems touched**: `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` public website only +- **Shared abstractions reused**: existing Spec 402 ScrewFast-derived Astro structure, page components, Starlight docs, sitemap integration, and Playwright smoke helper patterns +- **New abstraction introduced? why?**: none +- **Why the existing abstraction was sufficient or insufficient**: The website already has route, layout, navigation, metadata, docs, and smoke-test structure. Launch readiness can be implemented by tightening those local artifacts rather than adding runtime abstractions. +- **Bounded deviation / spread control**: none + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no +- **Central contract reused**: N/A +- **Delegated UX behaviors**: N/A +- **Surface-owned behavior kept local**: N/A +- **Queued DB-notification policy**: N/A +- **Terminal notification path**: N/A +- **Exception path**: none + +## Provider Boundary & Portability Fit + +- **Shared provider/platform boundary touched?**: no +- **Provider-owned seams**: N/A +- **Platform-core seams**: N/A +- **Neutral platform terms / contracts preserved**: Public product language may refer to Microsoft tenant configuration as positioning only; no product runtime seam changes. +- **Retained provider-specific semantics and why**: Microsoft tenant wording remains because Tenantial's public positioning is currently Microsoft tenant governance. +- **Bounded extraction or follow-up path**: none + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +- Inventory-first: PASS - no inventory, snapshot, or backup runtime behavior is changed. +- Read/write separation: PASS - no product write/change behavior is introduced. +- Graph contract path: PASS - no Microsoft Graph calls or contract registry changes. +- Deterministic capabilities: PASS - no capability resolver or capability derivation changes. +- RBAC-UX: PASS - no `/admin`, `/system`, tenant context, workspace context, capability, policy, or authorization behavior changes. +- Workspace isolation: PASS - no workspace data or routes. +- RBAC-UX destructive-like actions: PASS - no destructive actions. +- RBAC-UX global search: PASS - no Filament resources or global search changes. +- Tenant isolation: PASS - no tenant data, tenant routes, or cross-tenant views. +- Run observability: PASS - no long-running, remote, queued, scheduled, or security-relevant DB action. +- OperationRun start UX: PASS - no OperationRun behavior. +- Ops-UX 3-surface feedback: PASS - no OperationRun notifications. +- Ops-UX lifecycle: PASS - no OperationRun status/outcome transitions. +- Ops-UX summary counts: PASS - no summary counts. +- Ops-UX guards: PASS - no Ops-UX guard changes. +- Ops-UX system runs: PASS - no system runs. +- Automation: PASS - no queued/scheduled operations. +- Data minimization: PASS - public copy and static preview content only; no secrets or tokens. +- Test governance (TEST-GOV-001): PASS - Browser/static website classification, explicit website smoke lane, no hidden Laravel/Filament/provider/database setup. +- Proportionality (PROP-001): PASS - local website hardening, no new product runtime structure. +- No premature abstraction (ABSTR-001): PASS - no new factories, registries, resolvers, strategies, interfaces, or frameworks. +- Persisted truth (PERSIST-001): PASS - no persisted product truth. +- Behavioral state (STATE-001): PASS - no status/state/reason family. +- UI semantics (UI-SEM-001): PASS - static presentation remains local and does not become product taxonomy. +- Shared pattern first (XCUT-001): PASS - no shared operator interaction family touched. +- Provider boundary (PROV-001): PASS - public Microsoft wording only; no shared provider/platform seam changes. +- V1 explicitness / few layers (V1-EXP-001, LAYER-001): PASS - direct website-local edits only. +- Spec discipline / bloat check (SPEC-DISC-001, BLOAT-001): PASS - no new enum, DTO, presenter, persisted entity, interface, registry, resolver, taxonomy, or cross-domain framework. +- Badge semantics (BADGE-001): PASS - no product badge semantics; preview labels must not become shared status truth. +- Filament-native UI (UI-FIL-001): PASS - no Filament UI. +- UI/UX surface taxonomy: PASS - no operator-facing surface. +- Decision-first operating model: PASS - public website copy is visitor-decision oriented; no operator surface. +- Audience-aware disclosure: PASS - no raw/support product detail surface. +- UI/UX inspect model: PASS - no list/detail operator surface. +- UI/UX action hierarchy: PASS - no Filament actions. +- UI/UX scope, truth, and naming: PASS - no admin scope labels; public copy must avoid implementation-first wording. +- UI/UX placeholder ban: PASS - no Filament action groups. +- UI naming: PASS - no operator-facing action labels, run titles, notifications, or audit prose. +- Operator surfaces: PASS - no `/admin` surface. +- Filament UI Action Surface Contract: PASS - no Filament Resource/RelationManager/Page. +- Filament UI UX-001: PASS - no Filament screen. +- Action-surface discipline: PASS - no operator action surface. +- UI review workflow: PASS - plan carries N/A decisions forward and keeps `apps/platform` as hard stop. + +**Initial Gate Result**: PASS - no constitution violations or unresolved clarifications. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Browser/static website +- **Affected validation lanes**: website build, public smoke, manual browser review, whitespace/scope checks +- **Why this lane mix is the narrowest sufficient proof**: The feature changes public static pages, route/metadata exposure, CTAs, and browser rendering. Laravel/Pest/Filament lanes would not prove the changed behavior. +- **Narrowest proving command(s)**: `cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm build:website`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && git diff --check`; `cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform` +- **Fixture / helper / factory / seed / context cost risks**: none - no database, provider, workspace, membership, session, queue, Sail, Laravel, Filament, or Livewire setup +- **Expensive defaults or shared helper growth introduced?**: no +- **Heavy-family additions, promotions, or visibility changes**: Browser review remains explicit in website smoke/manual review; no heavy-governance lane +- **Surface-class relief / special coverage rule**: N/A - public website +- **Closing validation and reviewer handoff**: Reviewers should rely on website build, Playwright smoke, manual light/dark/mobile/desktop checks, no horizontal overflow, no dead CTAs/placeholders, sitemap/robots inspection, and `apps/platform` untouched status. +- **Budget / baseline / trend follow-up**: none expected +- **Review-stop questions**: lane fit, browser breadth, hidden platform cost, CTA honesty, unsupported claim posture, route exposure +- **Escalation path**: document-in-feature +- **Active feature PR close-out entry**: Smoke Coverage +- **Why no dedicated follow-up spec is needed**: The browser/static review cost is local to this launch-readiness pass unless repeated website release gates become a recurring process problem. + +## Project Structure + +### Documentation (this feature) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/ +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── contracts/ +│ └── public-site-contract.md +└── tasks.md +``` + +### Source Code (repository root) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/ +├── astro.config.mjs +├── package.json +├── playwright.config.ts +├── process-html.mjs +├── public/ +├── src/ +│ ├── components/ +│ ├── content/ +│ ├── data_files/ +│ ├── images/ +│ ├── layouts/ +│ ├── pages/ +│ └── utils/ +└── tests/ + └── smoke/ +``` + +**Structure Decision**: Use the existing `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` Astro app from Spec 402. Do not create new base source folders and do not touch `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform`. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| None | N/A | N/A | + +## Proportionality Review + +- **Current operator problem**: Public launch review lacks a single bounded plan for route exposure, copy clarity, CTA honesty, metadata, claim safety, and browser checks after the foundation rebuild. +- **Existing structure is insufficient because**: Spec 402 established the Astro/ScrewFast substrate but did not complete a release-readiness pass. +- **Narrowest correct implementation**: Website-local copy, route, metadata, CTA, smoke, and launch-note work inside `apps/website`. +- **Ownership cost created**: Maintenance of website-local public copy, smoke expectations, and launch-readiness note. +- **Alternative intentionally rejected**: Rebuilding the foundation, adding backend contact/account workflows, or introducing product runtime abstractions. +- **Release truth**: Current-release public website readiness. + +## Phase 0 Research + +Research output is captured in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/research.md`. + +**Resolved clarifications**: + +- The feature uses the existing Astro 6 website and does not need a new framework decision. +- Public routes and redirect behavior are defined by `apps/website/src/pages`, `apps/website/astro.config.mjs`, and current Playwright smoke helpers. +- Starlight child docs routes are publicly emitted and must be included in launch review, not only the docs root. +- Current launch risks to resolve include route-specific social metadata, social image metadata consistency, `/imprint` placeholder details, static contact form wording, footer subscription wording, source `href="#"` placeholders, and the German-default/English-prefixed language route model. +- German is the default locale at unprefixed public routes; English is available under `/en/...`. +- There are no API endpoints or data entities to contract; route, CTA, metadata, and launch-check contracts are sufficient. +- Validation stays in website build and Playwright smoke lanes. + +## Phase 1 Design + +Design output is captured in: + +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/data-model.md` +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/contracts/public-site-contract.md` +- `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` + +No REST, GraphQL, database, Laravel, Filament, Livewire, Microsoft Graph, queue, job, policy, RBAC, or product runtime contracts are introduced. + +## Post-Design Constitution Check + +**Post-Design Gate Result**: PASS + +- The Phase 1 design remains website-local. +- All clarification markers are resolved. +- No product persistence, abstractions, state families, provider/platform seams, OperationRun behavior, RBAC behavior, Filament behavior, or Graph calls are introduced. +- Browser/static validation remains explicit and bounded to `apps/website`. +- `apps/platform` remains out of scope and must be verified by `git status --short -- apps/platform`. diff --git a/specs/403-public-website-launch-readiness/quickstart.md b/specs/403-public-website-launch-readiness/quickstart.md new file mode 100644 index 00000000..b638270f --- /dev/null +++ b/specs/403-public-website-launch-readiness/quickstart.md @@ -0,0 +1,119 @@ +# Quickstart: Spec 403 Public Website Launch Readiness + +## Scope + +Work only in: + +```bash +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website +/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness +``` + +Do not touch: + +```bash +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform +``` + +## Install Dependencies If Needed + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm install +``` + +## Run The Website Locally + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm dev:website +``` + +Open: + +```text +http://127.0.0.1:4321 +``` + +## Build Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && corepack pnpm build:website +``` + +## Smoke Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +``` + +## Static Git Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git diff --check +cd /Users/ahmeddarrazi/Documents/projects/wt-website && git status --short -- apps/platform +``` + +The `apps/platform` command must print no output. + +## Manual Browser Review + +Review these routes in desktop and mobile widths, light and dark modes: + +- `/` +- `/platform` +- `/pricing` +- `/contact` +- `/trust` +- `/legal` +- `/privacy` +- `/terms` +- `/imprint` +- `/welcome-to-docs/` +- `/guides/intro/` +- `/guides/getting-started/` +- `/guides/first-project-checklist/` +- `/platform/evidence-review/` + +Check: + +- homepage explains Tenantial within the first viewport +- `/platform` explains the public product model, not the internal Laravel/Filament app +- all CTAs resolve to intentional routes or anchors +- no `href="#"` placeholders are visible or emitted +- contact form and footer copy do not imply backend submission or newsletter subscription workflows that do not exist +- `/imprint` contains no production replacement placeholder +- pricing remains contact/demo oriented +- trust and legal pages avoid unsupported certification, endorsement, uptime, recovery, compliance, or customer adoption claims +- static product previews are framed as illustrative/static/demo content +- keyboard users can reach navigation, CTAs, footer links, and visible controls +- visible focus states are present +- no body-level horizontal overflow occurs +- reduced-motion mode leaves content understandable + +## Sitemap And Robots Review + +After build, inspect: + +```text +/robots.txt +/sitemap-index.xml +/sitemap-0.xml +``` + +Confirm: + +- `robots.txt` points to `/sitemap-index.xml` +- canonical rendered routes are included +- redirect-only aliases are excluded +- metadata is Tenantial-specific and route-appropriate +- Open Graph/Twitter URLs and social image metadata are route-appropriate and point to existing assets + +## Launch Readiness Handoff + +Implementation must add or update a launch-readiness note documenting: + +- route exposure +- CTA assumptions +- trust/pricing claim posture +- sitemap behavior +- deployment expectations +- `apps/platform` untouched confirmation diff --git a/specs/403-public-website-launch-readiness/research.md b/specs/403-public-website-launch-readiness/research.md new file mode 100644 index 00000000..69a3d42c --- /dev/null +++ b/specs/403-public-website-launch-readiness/research.md @@ -0,0 +1,72 @@ +# Phase 0 Research: `apps/website` Public Website Launch Readiness + +## Decision: Keep the Spec 402 Astro/ScrewFast-derived foundation + +**Rationale**: The active website is a standalone Astro app at `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` with Starlight docs, sitemap generation, Tailwind v4, Preline, Lenis, and Playwright smoke tests already in place. Spec 403 is a launch-readiness pass, not another rebuild. + +**Alternatives considered**: + +- Rebuild the website foundation again: rejected by scope. +- Introduce a new design system or app shell: rejected because Spec 402 already selected the substrate. +- Move launch checks into the Laravel platform: rejected because `apps/platform` is out of scope. + +## Decision: Treat route, CTA, metadata, and sitemap behavior as the public contract + +**Rationale**: This feature has no API endpoints or product runtime entities. Public launch behavior is expressed through static pages, redirect aliases, generated metadata, navigation/footer links, docs exposure, `robots.txt`, and sitemap output. + +**Alternatives considered**: + +- Generate OpenAPI or GraphQL contracts: rejected because the public website exposes no API for this feature. +- Model route readiness as product runtime state: rejected because it would create unnecessary taxonomy/persistence. + +## Decision: Use conservative public copy and proof-safe claim checks + +**Rationale**: The spec assumes no verified customer logos, testimonials, certifications, Microsoft endorsements, uptime guarantees, compliance guarantees, recovery guarantees, self-serve billing, or account provisioning workflow. Copy and metadata must therefore stay contact/demo-oriented and avoid unsupported proof claims. + +**Alternatives considered**: + +- Use aspirational enterprise proof language: rejected because it creates public trust risk. +- Keep placeholder social proof from the substrate: rejected unless hidden, removed, or rewritten as neutral content. + +## Decision: Keep static product previews illustrative + +**Rationale**: Product-like preview content can help visitors understand Tenantial, but it must not imply live Microsoft tenant data, a running Graph integration on the website, or shared product status taxonomy. + +**Alternatives considered**: + +- Present previews as live tenant output: rejected because the public website has no authenticated tenant runtime. +- Remove all previews: rejected because static/demo previews are useful and allowed when clearly framed. + +## Decision: Validate with website build, Playwright smoke, manual browser review, and scope checks + +**Rationale**: The current package scripts support `corepack pnpm build:website` and `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`. Playwright is already configured for desktop and mobile projects and starts the Astro preview server. These lanes prove the public static website without pulling in Laravel runtime cost. + +**Alternatives considered**: + +- Run Laravel/Pest/Filament tests: rejected because this feature does not touch `apps/platform`. +- Rely only on static text review: rejected because launch readiness requires browser/mobile/light/dark/overflow/CTA validation. + +## Decision: Use a website-local launch-readiness note as the implementation handoff artifact + +**Rationale**: FR-034 requires a launch-readiness note documenting route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, and deployment expectations. This is a website release artifact, not product persistence. + +**Alternatives considered**: + +- Store launch readiness in database tables: rejected as out of scope and unnecessary. +- Skip the note and rely on PR comments: rejected because reviewers need a durable handoff artifact. + +## Current Source Observations + +- Rendered smoke routes currently include `/`, `/platform`, `/pricing`, `/contact`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and `/welcome-to-docs/`. +- Public Starlight docs routes also include `/guides/intro/`, `/guides/getting-started/`, `/guides/first-project-checklist/`, and `/platform/evidence-review/`; current smoke coverage only lists the docs root. +- Redirect smoke routes currently include `/product`, `/products`, `/services`, `/blog`, and `/insights`, all resolving to `/platform`. +- `/product`, `/products`, and `/services` currently use 301 redirects; `/blog` and `/insights` currently use 302 redirects. The launch note should confirm this split is intentional. +- `astro.config.mjs` filters redirect-only paths out of the generated sitemap. +- `robots.txt` points to the generated sitemap index through `import.meta.env.SITE`. +- `apps/website/src/utils/navigation.ts` still contains `socialLinks` with `#` placeholder targets; implementation should remove, hide, or replace public placeholders if they are emitted. +- Existing smoke helpers already check forbidden public terms and horizontal overflow; Spec 403 may need broader CTA, metadata, docs, sitemap, and keyboard coverage. +- Main-layout canonical URLs are page-specific, but current social metadata uses the site root for `og:url` and `twitter:url` on non-doc pages; launch implementation should make route metadata consistent. +- Current social image metadata has launch-review risk: main-layout image type is hardcoded as PNG while generated social output may be WebP, and Starlight docs metadata references `https://tenantial.com/social.webp` without an obvious matching public file. +- `/imprint` contains placeholder production details that must be replaced or made explicitly conservative before launch. +- The contact page includes a static form and `Prepare Email` submit button without a backend workflow; copy/behavior must not imply a guaranteed submission workflow. +- The footer includes an email field and `Subscribe` link to `/contact`; this should be reframed or removed to avoid implying a newsletter workflow. diff --git a/specs/403-public-website-launch-readiness/spec.md b/specs/403-public-website-launch-readiness/spec.md new file mode 100644 index 00000000..ae18bea4 --- /dev/null +++ b/specs/403-public-website-launch-readiness/spec.md @@ -0,0 +1,421 @@ +# Feature Specification: `apps/website` Public Website Launch Readiness + +**Feature Branch**: `feat/403-public-website-launch-readiness` +**Created**: 2026-05-20 +**Status**: Draft +**Input**: User description: "Make the current Tenantial public website release-ready after the Spec 402 ScrewFast-derived rebuild, scoped strictly to `apps/website`." + +## Spec Candidate Check + +- **Problem**: The public website now has the correct foundation, but it still needs a focused launch-readiness pass for message clarity, CTA integrity, route exposure, SEO metadata, trust/pricing claim safety, and browser review. +- **Today's failure**: A technically passing static build could still publish unclear positioning, unsupported claims, dead or misleading CTAs, route residue, or metadata that is not ready for public release. +- **User-visible improvement**: Visitors can quickly understand Tenantial in German by default or English under `/en`, follow honest contact/demo paths, review conservative trust and pricing language, and browse only intentional public routes. +- **Smallest enterprise-capable version**: Keep the Spec 402 ScrewFast-derived foundation, change only website-local copy, metadata, route exposure, CTA behavior, public assets, smoke checks, and launch-readiness notes. +- **Explicit non-goals**: No rebuild, no new design system, no backend contact workflow, no authentication, no billing, no Microsoft Graph calls, no Laravel/Filament/Livewire/admin behavior, and no `apps/platform` changes. +- **Permanent complexity imported**: Website-local launch copy, route/metadata hygiene, static preview framing, browser/static validation expectations, and a launch-readiness note. No new product runtime abstractions or persistence. +- **Why now**: Spec 402 intentionally solved the foundation; launch readiness is the next release gate before public exposure. +- **Why not local**: This is local to `apps/website`; the spec explicitly rejects broader platform/runtime changes. +- **Approval class**: Core Enterprise +- **Red flags triggered**: None. This is not a new foundation, framework, status taxonomy, persistence layer, or abstraction. +- **Score**: Nutzen 2 | Dringlichkeit 2 | Scope 2 | Komplexitaet 2 | Produktnaehe 2 | Wiederverwendung 1 | **Gesamt: 11/12** +- **Decision**: approve + +## Spec Scope Fields + +- **Scope**: N/A - public website only, strictly `apps/website` +- **Primary Routes**: `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, `/contact`, docs routes, `/en/*` localized route mirrors, redirect aliases, `/robots.txt`, `/sitemap-index.xml` +- **Data Ownership**: No workspace-owned or tenant-owned product records impacted; no persistence changes. +- **RBAC**: N/A - no authenticated/admin/platform runtime behavior, no tenant/workspace membership behavior, no authorization changes. + +For canonical-view specs: + +- **Default filter behavior when tenant-context is active**: N/A - no canonical admin view or tenant context. +- **Explicit entitlement checks preventing cross-tenant leakage**: N/A - no tenant data or authenticated runtime. + +## Cross-Cutting / Shared Pattern Reuse + +- **Cross-cutting feature?**: no +- **Interaction class(es)**: N/A - public website navigation and CTAs only; no shared operator interaction family. +- **Systems touched**: `apps/website` static/public routes, metadata, navigation, footer, docs/legal/trust/pricing/contact surfaces, smoke checks. +- **Existing pattern(s) to extend**: Spec 402 ScrewFast-derived website structure. +- **Shared contract / presenter / builder / renderer to reuse**: N/A - no Laravel/Filament shared operator contract. +- **Why the existing shared path is sufficient or insufficient**: The website-local ScrewFast-derived structure is sufficient; this spec only hardens launch readiness. +- **Allowed deviation and why**: none +- **Consistency impact**: Public copy, CTAs, metadata, route exposure, sitemap, and browser review must stay aligned across the website. +- **Review focus**: Verify no `apps/platform` dependency or admin/runtime shared pattern is introduced. + +## OperationRun UX Impact + +- **Touches OperationRun start/completion/link UX?**: no +- **Shared OperationRun UX contract/layer reused**: N/A +- **Delegated start/completion UX behaviors**: N/A +- **Local surface-owned behavior that remains**: N/A +- **Queued DB-notification policy**: N/A +- **Terminal notification path**: N/A +- **Exception required?**: none + +## Provider Boundary / Platform Core Check + +- **Shared provider/platform boundary touched?**: no +- **Boundary classification**: N/A +- **Seams affected**: N/A +- **Neutral platform terms preserved or introduced**: Public copy may mention Microsoft tenant configuration as product positioning only. +- **Provider-specific semantics retained and why**: Bounded public positioning for Microsoft tenant configuration; no runtime contracts, models, Graph calls, or platform-core taxonomy changes. +- **Why this does not deepen provider coupling accidentally**: The feature changes only static public website artifacts and does not alter product runtime boundaries. +- **Follow-up path**: none + +## UI / Surface Guardrail Impact + +`N/A - no admin/operator-facing product surface change. This spec changes public marketing website surfaces only.` + +| Surface / Change | Operator-facing surface change? | Native vs Custom | Shared-Family Relevance | State Layers Touched | Exception Needed? | Low-Impact / `N/A` Note | +|---|---|---|---|---|---|---| +| Public website launch-readiness pass | no | ScrewFast-derived Astro website | none | static pages, route metadata, navigation, footer | no | Public website only; no Laravel/Filament/Livewire surface | + +## Decision-First Surface Role + +N/A - no admin/operator-facing product surface is added or changed. + +## Audience-Aware Disclosure + +N/A - no authenticated detail/status surface is added or changed. Static product previews must remain clearly illustrative/static/demo content. + +## UI/UX Surface Classification + +N/A - no Filament/operator list, detail, queue, audit, config, or report surface is added or changed. + +## Operator Surface Contract + +N/A - no admin/operator-facing product page is added or materially refactored. + +## Proportionality Review + +- **New source of truth?**: no +- **New persisted entity/table/artifact?**: no product runtime persistence. A website-local launch-readiness note may be added as a release handoff artifact. +- **New abstraction?**: no +- **New enum/state/reason family?**: no +- **New cross-domain UI framework/taxonomy?**: no +- **Current operator problem**: Public launch review currently lacks a bounded, reviewable checklist for messaging, CTAs, claims, metadata, routes, and responsive behavior after the website foundation rebuild. +- **Existing structure is insufficient because**: Spec 402 established the substrate; it did not exhaustively certify public release quality or claim posture. +- **Narrowest correct implementation**: Website-local edits plus static/browser validation, no platform/runtime changes. +- **Ownership cost**: Maintaining website copy, metadata, smoke coverage, and the launch-readiness note. +- **Alternative intentionally rejected**: Rebuilding the website foundation again or introducing backend workflows for contact/demo/account behavior. +- **Release truth**: Current-release launch readiness for the public website. + +### Compatibility posture + +This feature assumes a pre-production environment for the public website. It introduces no product runtime compatibility, migration, legacy alias, or data-shape requirement. + +## Testing / Lane / Runtime Impact + +- **Test purpose / classification**: Browser/static website +- **Validation lane(s)**: website build, public smoke, browser review, whitespace/scope checks +- **Why this classification and these lanes are sufficient**: The feature changes static public website behavior, metadata, route exposure, CTAs, and responsive rendering rather than Laravel runtime behavior. +- **New or expanded test families**: May update website smoke checks if route/CTA/sitemap validation gaps are found. +- **Fixture / helper cost impact**: none expected; no provider, workspace, membership, database, session, Filament, Livewire, or Laravel setup. +- **Heavy-family visibility / justification**: Browser review is explicit and limited to public website pages. +- **Special surface test profile**: N/A - public website static/browser validation. +- **Standard-native relief or required special coverage**: N/A - no Filament surface. +- **Reviewer handoff**: Confirm lane fit, no hidden platform runtime cost, and exact validation commands listed in this spec. +- **Budget / baseline / trend impact**: none expected. +- **Escalation needed**: document-in-feature +- **Active feature PR close-out entry**: Smoke Coverage +- **Planned validation commands**: + - `corepack pnpm build:website` + - `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + - `git diff --check` + - `git status --short -- apps/platform` + +## Status + +Draft + +## Scope + +Runtime/source implementation for this spec is strictly local to `apps/website`. + +Spec Kit artifacts for planning, tasks, analysis, and launch handoff may live under `specs/403-public-website-launch-readiness/`. + +It builds on Spec 402, where `apps/website` was replaced with a Tenantial-branded public website derived from the pinned ScrewFast foundation. + +This spec does not rebuild the website foundation again. It does not replace the ScrewFast substrate. It focuses on launch-readiness of public copy, navigation, CTA flows, metadata, route exposure, trust wording, and browser review. + +## Important Scope Clarification + +In this feature, the word "platform" refers only to the public website route `/platform` inside `apps/website`. + +It does not refer to the Laravel/Filament application in `apps/platform`. + +`apps/platform` is completely out of scope: + +- do not modify it +- do not import from it +- do not run platform-specific migrations, tests, providers, resources, policies, Livewire, Filament, Blade, jobs, queues, database, tenant/workspace, RBAC, or Microsoft Graph code +- only verify through `git diff` or `git status` that it remains untouched + +Planning artifacts and the launch-readiness handoff note are allowed under `specs/403-public-website-launch-readiness/`; they must not introduce platform runtime behavior. + +## Goal + +Make the current Tenantial public website release-ready after the Spec 402 ScrewFast-derived rebuild. + +The website should clearly communicate Tenantial's public product positioning, expose only intentional routes, avoid unsupported public claims, provide coherent CTAs, and pass manual/browser/static launch checks. + +## Non-Goals + +This spec does not: + +- recreate `apps/website` +- replace the ScrewFast-derived foundation +- redesign the website from scratch +- remove unused vendored ScrewFast substrate files unless they are publicly emitted or create release risk +- add backend contact/demo workflow +- add authentication, login, customer portal, billing, subscription, or app access behavior +- introduce Microsoft Graph calls +- introduce platform/admin runtime behavior +- touch `apps/platform` +- change Filament, Livewire, Laravel, Blade, providers, policies, migrations, jobs, queues, database, tenant isolation, workspace behavior, RBAC, or AuditLog behavior + +## Problem + +Spec 402 established the correct website substrate and public route model. + +However, a foundation replacement is not the same as launch readiness. Before release, the public website needs a focused pass over: + +1. message clarity +2. CTA consistency +3. SEO metadata +4. route exposure +5. trust and pricing safety +6. docs/legal/contact completeness +7. mobile and keyboard usability +8. public claim discipline +9. deployment/reviewer handoff + +Without this pass, the website may technically build and smoke-test while still being unclear, under-polished, incorrectly localized, or risky from a public-claims perspective. + +## User Scenarios & Testing + +### User Story 1 - Understand Tenantial Quickly (Priority: P1) + +A first-time visitor opens the homepage and understands within one minute that Tenantial is an evidence-first governance product for Microsoft tenant configuration, with emphasis on backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews. + +**Why this priority**: The homepage is the main conversion and positioning surface. If the message is unclear, the website is not launch-ready even if the build passes. + +**Independent Test**: Open `/` in desktop and mobile widths and verify that the hero, section sequence, CTAs, and FAQ communicate the product category and core value without requiring platform knowledge. + +**Acceptance Scenarios**: + +1. **Given** a visitor opens `/`, **When** the hero loads, **Then** Tenantial's product category and primary value proposition are visible without scrolling. +2. **Given** a visitor scans the homepage, **When** they move through sections, **Then** they can identify at least three core capabilities. +3. **Given** a visitor reaches the final CTA, **When** they decide what to do next, **Then** the CTA path is clear and does not imply unsupported login, billing, or backend workflow behavior. + +--- + +### User Story 2 - Evaluate The Public Product Route (Priority: P1) + +A buyer opens `/platform` and understands Tenantial's public product model without confusing it with the internal Laravel/Filament app. + +**Why this priority**: The `/platform` page is the primary product explanation route. It must explain the product clearly while staying within public website scope. + +**Independent Test**: Open `/platform` and verify that the page explains the public product model, not internal implementation details. + +**Acceptance Scenarios**: + +1. **Given** a visitor opens `/platform`, **When** the page loads, **Then** the page explains backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews. +2. **Given** the page uses product-like previews, **When** they are reviewed, **Then** they are clearly static/demo previews and do not imply live tenant data. +3. **Given** the page references Microsoft tenants, **When** copy is reviewed, **Then** the language remains public positioning and does not imply a runtime Microsoft Graph integration on the website. + +--- + +### User Story 3 - Follow A Safe CTA Path (Priority: P2) + +A visitor who wants to evaluate Tenantial can follow a clear contact/demo path without encountering fake forms, dead links, fake account access, or unsupported self-serve purchase claims. + +**Why this priority**: CTA integrity is essential for trust. Public CTAs must match what the website can actually support. + +**Independent Test**: Click primary and secondary CTAs from homepage, `/platform`, `/pricing`, `/trust`, docs, and footer. Verify that each CTA leads to an intentional route or anchor. + +**Acceptance Scenarios**: + +1. **Given** a visitor clicks a demo/contact CTA, **When** the link resolves, **Then** it leads to `/contact` or an intentional page section. +2. **Given** a visitor sees pricing, **When** they review package language, **Then** the page avoids unsupported billing, checkout, plan entitlement, or self-serve subscription claims. +3. **Given** no backend form workflow exists, **When** a contact surface renders, **Then** it does not imply a guaranteed workflow, automated provisioning, or account creation. + +--- + +### User Story 4 - Trust The Claims (Priority: P2) + +A security-conscious buyer reads trust, legal, pricing, and product pages and sees conservative, proof-safe wording. + +**Why this priority**: Tenantial is an enterprise governance product. Unsupported claims about compliance, certifications, Microsoft endorsement, uptime, recovery guarantees, or customer adoption would be risky. + +**Independent Test**: Review visible copy and metadata on `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and docs routes. + +**Acceptance Scenarios**: + +1. **Given** no verified certifications are supplied, **When** trust pages render, **Then** they do not claim SOC 2, ISO, Microsoft endorsement, guaranteed compliance, guaranteed uptime, or guaranteed recovery. +2. **Given** no customer proof is supplied, **When** social-proof areas render, **Then** they do not show fake customer logos, fake testimonials, or "trusted by" claims. +3. **Given** static product previews are shown, **When** reviewed, **Then** they are framed as illustrative/static/demo content rather than live tenant output. + +--- + +### User Story 5 - Review Launch Metadata And Route Exposure (Priority: P3) + +A site owner can review the public website output and confirm routes, metadata, sitemap, robots, and redirects are intentional. + +**Why this priority**: Launch readiness includes not only visual pages, but also search engine exposure, route hygiene, and public metadata. + +**Independent Test**: Run static route and metadata checks against the built website output. + +**Acceptance Scenarios**: + +1. **Given** the website builds, **When** sitemap output is inspected, **Then** only canonical renderable routes are included. +2. **Given** redirect aliases exist, **When** sitemap output is inspected, **Then** redirect-only routes are excluded. +3. **Given** `robots.txt` is opened, **When** reviewed, **Then** it points to the intended sitemap index. +4. **Given** public pages are opened, **When** metadata is reviewed, **Then** titles and descriptions are Tenantial-specific and route-appropriate. + +### Edge Cases + +- If a route is retained from ScrewFast but not ready for Tenantial content, it must be hidden from navigation, redirected intentionally, or given restrained placeholder content. +- If docs pages are exposed, they must not imply product behavior that does not exist yet. +- If pricing is not final, pricing must remain conservative and contact/demo-oriented. +- If trust/legal wording is not legally reviewed, pages must use restrained placeholder language. +- If a CTA cannot be backed by a real workflow, it must link to `/contact` or be removed. +- If product previews include status-like values, they must not become shared product taxonomy or imply runtime truth. +- If visual assets are retained from the vendored ScrewFast source but unused, they are not release blockers unless they are imported, emitted, linked, or visible. +- If localized or editorial routes are not ready, they must not appear in primary navigation. +- If localization is enabled, German must remain the unprefixed default and English must use `/en/...` route prefixes. +- If reduced-motion is enabled, content must remain understandable without animation. +- If JavaScript fails, primary content and links must remain usable where reasonably possible for a static marketing site. + +## Assumptions + +- Spec 402 is already implemented and validated. +- `apps/website` is now ScrewFast-derived and Tenantial-branded. +- `apps/platform` is untouched and remains out of scope. +- No verified customer logos, testimonials, certifications, Microsoft endorsements, uptime guarantees, compliance guarantees, or recovery guarantees have been supplied. +- No self-serve billing or account provisioning workflow is available from the public website. +- The public domain is `tenantial.com`. +- The generated sitemap entrypoint is `/sitemap-index.xml`. +- German is the default public website locale at unprefixed routes. +- English is an additional public locale under `/en/...`. + +## Requirements + +### Scope And Architecture + +- **FR-001**: Runtime/source implementation MUST remain strictly scoped to `apps/website`; Spec Kit artifacts and launch handoff notes MAY live under `specs/403-public-website-launch-readiness/`. +- **FR-002**: The implementation MUST NOT touch `apps/platform`. +- **FR-003**: The implementation MUST NOT replace or rebuild the ScrewFast-derived foundation again. +- **FR-004**: Changes SHOULD focus on copy, metadata, route exposure, CTA behavior, public assets, and launch documentation. +- **FR-005**: Unused vendored ScrewFast substrate files MAY remain if they are not publicly emitted or referenced. + +### Public Positioning + +- **FR-006**: The homepage MUST clearly position Tenantial as evidence-first governance for Microsoft tenant configuration. +- **FR-007**: The homepage MUST communicate backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews either directly or through linked product sections. +- **FR-008**: The `/platform` route MUST explain the public product model without referencing internal Laravel/Filament implementation details. +- **FR-009**: Public copy MUST avoid implementation-first labels where user-facing product language is clearer. + +### CTA And Navigation + +- **FR-010**: All public CTAs MUST resolve to intentional routes or anchors. +- **FR-011**: Public CTAs MUST NOT imply login, account creation, billing, checkout, backend submission, or automated onboarding unless such workflow exists. +- **FR-012**: Primary navigation MUST expose only intentional, release-ready public routes. +- **FR-013**: Footer navigation MUST expose legal, trust, contact, and core product routes intentionally. +- **FR-014**: Redirect aliases MUST remain intentional. Aliases that render HTML MUST be noindex; aliases implemented as pure redirects MUST be documented by redirect status/target and excluded from sitemap output. +- **FR-014a**: Language switching MUST keep German as the unprefixed default locale and English as prefixed `/en/...` routes. +- **FR-014b**: Navigation, footer links, CTAs, metadata, and docs links MUST resolve to the current locale where a localized route exists. + +### Trust, Pricing, And Claims + +- **FR-015**: Trust copy MUST avoid unsupported SOC 2, ISO, Microsoft endorsement, uptime, recovery, compliance, or customer adoption claims. +- **FR-016**: Pricing copy MUST avoid unsupported plan entitlement, self-serve subscription, billing, checkout, or purchase claims. +- **FR-017**: Static product previews MUST be framed as illustrative/static/demo content and MUST NOT imply live tenant data. +- **FR-018**: Public pages MUST NOT publish fake customer logos, fake testimonials, or fake "trusted by" claims. + +### Metadata And SEO + +- **FR-019**: Each canonical public route MUST have Tenantial-specific title and description metadata. +- **FR-020**: The generated sitemap MUST include only intentional canonical public routes. +- **FR-021**: Redirect-only aliases MUST be excluded from sitemap output. +- **FR-022**: `robots.txt` MUST point to `/sitemap-index.xml`. +- **FR-023**: Public metadata MUST NOT contain ScrewFast, construction/hardware, template, TenantAtlas, TenantPilot, or TenantCTRL residue. +- **FR-023a**: Public metadata MUST expose correct locale signals for German default pages, English `/en/...` pages, and alternate language URLs. + +### Accessibility And Responsive Review + +- **FR-024**: Public pages MUST remain usable on representative mobile and desktop viewport sizes. +- **FR-025**: Body-level horizontal overflow MUST NOT occur on validated public routes. +- **FR-026**: Keyboard users MUST be able to reach primary navigation, CTAs, footer links, and visible form controls. +- **FR-027**: Focus-visible states MUST be visible for interactive elements. +- **FR-028**: Reduced-motion preferences MUST be respected for retained animations. +- **FR-029**: Status or preview meaning MUST NOT rely on color alone. + +### Validation + +- **FR-030**: Build validation MUST pass with `corepack pnpm build:website`. +- **FR-031**: Public smoke validation MUST pass with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke`. +- **FR-032**: Whitespace/conflict validation MUST pass with `git diff --check`. +- **FR-033**: Scope validation MUST confirm `git status --short -- apps/platform` is empty. +- **FR-034**: A launch-readiness note MUST document route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, and deployment expectations. + +## Constitution Alignment + +- This feature introduces no Microsoft Graph calls. +- This feature introduces no product write/change behavior. +- This feature introduces no queued or scheduled work. +- This feature introduces no OperationRun behavior. +- This feature introduces no AuditLog behavior. +- This feature introduces no tenant/workspace isolation behavior. +- This feature introduces no RBAC behavior. +- This feature introduces no Laravel/Filament/Livewire/admin behavior. +- This feature introduces no database or persisted product truth. +- This feature introduces no shared product status taxonomy. +- This feature introduces no provider/platform seam. +- This feature is limited to public website artifacts and public website validation. +- Livewire v4.0+ compliance: N/A for implementation because no Livewire code is in scope; the platform remains on Livewire v4 per application info. +- Provider registration location: N/A for implementation because no Laravel providers are added or changed; Laravel 11+/12 provider registration remains outside scope. +- Globally searchable resources: N/A because no Filament resources are added or changed. +- Destructive actions: none. +- Asset strategy: website-local Astro/ScrewFast assets only; no Filament assets are registered, so `filament:assets` is not part of this feature's deploy path. +- Testing plan: website build, public smoke validation, browser review, `git diff --check`, and `apps/platform` untouched scope check. + +## Success Criteria + +- **SC-001**: 100% of canonical public routes render successfully during smoke validation. +- **SC-002**: 100% of redirect-only aliases redirect intentionally and are excluded from sitemap output. +- **SC-003**: 0 public `href="#"` placeholders remain in source or emitted HTML. +- **SC-004**: 0 forbidden public residue terms appear in visible copy or metadata across validated public routes. +- **SC-005**: 100% of public CTAs resolve to intentional routes or anchors. +- **SC-006**: 100% of trust, pricing, and preview claims are conservative or backed by supplied proof. +- **SC-007**: 100% of validated mobile and desktop review sizes show no body-level horizontal overflow. +- **SC-008**: Primary navigation, CTAs, footer links, and visible controls are reachable by keyboard. +- **SC-009**: `corepack pnpm build:website` passes. +- **SC-010**: `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` passes. +- **SC-011**: `git diff --check` passes. +- **SC-012**: `git status --short -- apps/platform` returns empty. + +## Reviewer Notes + +Reviewers should focus on launch quality, not on replacing the website foundation again. + +The main review questions are: + +1. Does the homepage explain Tenantial clearly? +2. Does `/platform` explain the public product model without touching or implying `apps/platform` implementation details? +3. Are CTAs honest and usable? +4. Are trust and pricing claims conservative? +5. Are sitemap, robots, metadata, and route exposure intentional? +6. Is the website still ScrewFast-derived rather than a new custom design system? +7. Is `apps/platform` untouched? + +## Suggested Validation Commands + +```bash +corepack pnpm build:website +WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +git diff --check +git status --short -- apps/platform +``` diff --git a/specs/403-public-website-launch-readiness/tasks.md b/specs/403-public-website-launch-readiness/tasks.md new file mode 100644 index 00000000..76a514b8 --- /dev/null +++ b/specs/403-public-website-launch-readiness/tasks.md @@ -0,0 +1,255 @@ +# Tasks: `apps/website` Public Website Launch Readiness + +**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/` +**Prerequisites**: [plan.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/plan.md), [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/spec.md), [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/research.md), [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/data-model.md), [public-site-contract.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/contracts/public-site-contract.md), [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md) + +**Tests**: Browser/static website validation is required by Spec 403. Use Playwright smoke tests and static source/build checks only; do not add Pest, Laravel, Filament, Livewire, database, Sail, Microsoft Graph, queue, job, policy, RBAC, or `apps/platform` test coverage. + +## Phase 1: Setup + +**Purpose**: Establish the website-only scope and current launch-readiness baseline before editing. + +- [X] T001 Inspect current git scope and confirm `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` is untouched with `git status --short -- apps/platform` from `/Users/ahmeddarrazi/Documents/projects/wt-website` +- [X] T002 [P] Review current public route sources in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages` +- [X] T003 [P] Review current navigation and footer link data in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/utils/navigation.ts` +- [X] T004 [P] Review current metadata components in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/starlight/Head.astro` +- [X] T005 [P] Review current Playwright smoke coverage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +--- + +## Phase 2: Foundational + +**Purpose**: Add shared launch-readiness validation scaffolding required before user-story work. + +**Critical**: Complete this phase before story-specific implementation. + +- [X] T006 Update canonical rendered route and docs route lists in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` to include `/`, `/platform`, `/pricing`, `/contact`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, `/welcome-to-docs/`, `/guides/intro/`, `/guides/getting-started/`, `/guides/first-project-checklist/`, and `/platform/evidence-review/` +- [X] T007 Update redirect alias expectations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` for `/product`, `/products`, `/services`, `/blog`, and `/insights` +- [X] T008 Add shared forbidden metadata and public claim assertion helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T009 Add shared public link and anchor validation helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T010 Add shared sitemap parsing helpers in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T011 Confirm no foundational task touches `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/platform` by running `git status --short -- apps/platform` from `/Users/ahmeddarrazi/Documents/projects/wt-website` + +**Checkpoint**: Public route, link, metadata, and sitemap smoke helpers are ready for user-story implementation. + +--- + +## Phase 3: User Story 1 - Understand Tenantial Quickly (Priority: P1) MVP + +**Goal**: A first-time visitor understands from `/` that Tenantial is evidence-first governance for Microsoft tenant configuration, including backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews. + +**Independent Test**: Open `/` in desktop and mobile widths and verify that the hero, section sequence, CTAs, and FAQ communicate the product category and core value without requiring platform knowledge. + +### Tests For User Story 1 + +- [X] T012 [P] [US1] Add homepage smoke assertions for first-viewport positioning and core capability terms in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T013 [P] [US1] Add homepage mobile overflow and heading visibility assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +### Implementation For User Story 1 + +- [X] T014 [US1] Refine homepage hero copy and primary CTA labels in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T015 [US1] Refine homepage capability section copy for backup, restore, drift detection, findings, evidence, auditability, exceptions, and reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T016 [P] [US1] Refine homepage FAQ copy to remove implementation-first wording in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/faqs.json` +- [X] T017 [US1] Verify homepage metadata title and description are Tenantial-specific in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T018 [US1] Run fresh-build US1 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 1 is independently shippable as the MVP. + +--- + +## Phase 4: User Story 2 - Evaluate The Public Product Route (Priority: P1) + +**Goal**: A buyer can open `/platform` and understand Tenantial's public product model without confusing it with the internal Laravel/Filament app. + +**Independent Test**: Open `/platform` and verify that the page explains backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews, and that previews are static/demo content. + +### Tests For User Story 2 + +- [X] T019 [P] [US2] Add `/platform` product model smoke assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T020 [US2] Add static/demo preview framing assertions for `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 2 + +- [X] T021 [US2] Refine `/platform` product model copy for backup, restore, drift detection, findings, evidence, audit trail, exceptions, and reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T022 [US2] Add or tighten static/demo preview framing text in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T023 [US2] Remove internal Laravel, Filament, Livewire, provider, Graph runtime, or admin implementation wording from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T024 [P] [US2] Review static product preview component copy for color-independent meaning in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/blocks/ReviewComponent.astro` +- [X] T025 [US2] Verify `/platform` metadata title and description are public-product focused in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T026 [US2] Run fresh-build US2 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 2 is independently shippable once `/platform` communicates the public product model safely. + +--- + +## Phase 5: User Story 3 - Follow A Safe CTA Path (Priority: P2) + +**Goal**: A visitor can follow clear demo/contact paths without fake forms, dead links, fake account access, unsupported billing, or unsupported self-serve purchase claims. + +**Independent Test**: Click primary and secondary CTAs from homepage, `/platform`, `/pricing`, `/trust`, docs, and footer; each CTA resolves to an intentional route or anchor. + +### Tests For User Story 3 + +- [X] T027 [P] [US3] Add public CTA crawl assertions for rendered routes in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T028 [US3] Add emitted `href="#"` placeholder assertions for rendered HTML in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T029 [P] [US3] Add keyboard reachability smoke assertions for primary navigation, CTAs, footer links, and visible form controls in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` + +### Implementation For User Story 3 + +- [X] T030 [US3] Remove or replace placeholder social link values in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/utils/navigation.ts` +- [X] T031 [US3] Reframe footer email and `Subscribe` CTA so it does not imply a newsletter workflow in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/FooterSection.astro` +- [X] T032 [US3] Reframe static contact form copy and controls so they do not imply backend submission or guaranteed provisioning in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/contact.astro` +- [X] T033 [P] [US3] Verify homepage CTA targets route only to `/contact` or `/platform` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T034 [P] [US3] Verify platform CTA targets route only to intentional public routes or anchors in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T035 [P] [US3] Verify pricing CTAs remain contact/demo-oriented in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json` +- [X] T036 [P] [US3] Verify trust and docs CTA targets are intentional in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs` +- [X] T037 [US3] Run fresh-build US3 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 3 is independently shippable once CTAs are honest and resolvable. + +--- + +## Phase 6: User Story 4 - Trust The Claims (Priority: P2) + +**Goal**: A security-conscious buyer sees conservative, proof-safe wording across trust, legal, pricing, product, and docs pages. + +**Independent Test**: Review visible copy and metadata on `/`, `/platform`, `/pricing`, `/trust`, `/legal`, `/privacy`, `/terms`, `/imprint`, and docs routes; no unsupported proof claims or fake social proof appear. + +### Tests For User Story 4 + +- [X] T038 [P] [US4] Expand forbidden public claim assertions for visible copy and metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T039 [P] [US4] Add trust, pricing, legal, and docs claim-safety smoke assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 4 + +- [X] T040 [US4] Rewrite trust page copy to avoid unsupported certification, endorsement, uptime, recovery, compliance, and customer adoption claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro` +- [X] T041 [P] [US4] Rewrite pricing page and pricing data copy to avoid self-serve subscription, billing, checkout, and entitlement claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json` +- [X] T042 [P] [US4] Replace production placeholder wording on `/imprint` with conservative placeholder-safe public language in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/imprint.astro` +- [X] T043 [P] [US4] Review legal, privacy, and terms pages for unsupported claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/legal.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/privacy.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/terms.astro` +- [X] T044 [P] [US4] Review docs copy for unsupported runtime, compliance, recovery, or live-tenant claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs` +- [X] T045 [P] [US4] Remove or neutralize any fake testimonial, customer logo, or social proof imports in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/testimonials` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/landing/ClientsSection.astro` +- [X] T046 [US4] Run fresh-build US4 validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` before `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 4 is independently shippable once public claims are conservative or backed by supplied proof. + +--- + +## Phase 7: User Story 5 - Review Launch Metadata And Route Exposure (Priority: P3) + +**Goal**: A site owner can confirm route exposure, metadata, sitemap, robots, and redirects are intentional after build. + +**Independent Test**: Run static route and metadata checks against the built website output. + +### Tests For User Story 5 + +- [X] T047 [US5] Add route-specific title, description, canonical, Open Graph, and Twitter metadata assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T048 [US5] Add sitemap allowlist assertions for all intentional canonical routes and exclusion assertions for redirect-only aliases in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T049 [US5] Add exact `/sitemap-index.xml` robots assertion in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` +- [X] T050 [US5] Add redirect status and noindex-if-rendered smoke assertions for `/product`, `/products`, `/services`, `/blog`, and `/insights` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/public-routes.spec.ts` + +### Implementation For User Story 5 + +- [X] T051 [US5] Fix route-specific social URL metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` +- [X] T052 [US5] Fix social image URL and MIME/type metadata consistency in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/Meta.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/starlight/Head.astro` +- [X] T053 [P] [US5] Confirm sitemap redirect filtering still covers all redirect aliases in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/astro.config.mjs` +- [X] T054 [P] [US5] Confirm `robots.txt` points to `/sitemap-index.xml` for `https://tenantial.com` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/robots.txt.ts` +- [X] T055 [P] [US5] Confirm redirect-only pages use documented redirect status/target behavior or noindex metadata if they render HTML in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/products/index.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/services.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/blog/index.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/insights/index.astro` +- [X] T056 [US5] Run build validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` +- [X] T057 [US5] Run public smoke validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` + +**Checkpoint**: User Story 5 is independently shippable once route exposure and metadata output are intentional. + +--- + +## Final Phase: Polish And Cross-Cutting Concerns + +**Purpose**: Complete launch handoff, final browser review, and scope validation. + +- [X] T058 [P] Create launch-readiness note documenting route exposure, CTA assumptions, trust/pricing claim posture, sitemap behavior, deployment expectations, and `apps/platform` untouched confirmation in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/launch-readiness.md` +- [X] T059 [P] Perform manual desktop browser review for light and dark modes on public routes from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` +- [X] T060 [P] Perform manual mobile browser review for light and dark modes on public routes from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness/quickstart.md` +- [X] T061 [P] Verify keyboard focus reachability and visible focus states across primary navigation, CTAs, footer links, and visible controls using `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` +- [X] T062 [P] Verify reduced-motion readability and color-independent preview meaning on rendered pages in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages` +- [X] T063 Run final build validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `corepack pnpm build:website` +- [X] T064 Run final public smoke validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` +- [X] T065 Run final whitespace validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `git diff --check` +- [X] T066 Run final scope validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `git status --short -- apps/platform` +- [X] T067 Run source-wide placeholder validation from `/Users/ahmeddarrazi/Documents/projects/wt-website` with `! rg "href=\"#\"|href=\\{\"#\"\\}|href=\\{'#'\\}" apps/website/src` +- [X] T068 Add no-JavaScript primary content and link usability smoke coverage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts` +- [X] T069 Review homepage and platform copy for trust/pricing/preview claim safety in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/platform.astro` +- [X] T070 Add German-default and English-prefixed localization helpers, copy, pages, docs, redirects, and route metadata in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src` +- [X] T071 Add localized language picker behavior to primary navigation and verify `Docs` remains visible in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/Navbar.astro` +- [X] T072 Expand Playwright smoke coverage for German default routes, English `/en/...` routes, localized redirects, metadata, sitemap, and language switching in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` +- [X] T073 Update Spec 403 artifacts to document German as the default locale and English as the additional `/en/...` locale in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/403-public-website-launch-readiness` + +--- + +## Dependencies + +**Phase Dependencies** + +Setup before Foundational. Foundational before any user story. User Stories 1 and 2 are both P1 and can proceed after Foundational. User Stories 3 and 4 can proceed after the relevant P1 page and CTA surfaces exist. User Story 5 should run after route, copy, CTA, and trust/pricing decisions settle. + +**User Story Dependencies** + +- **US1**: Depends on Phase 2 only. +- **US2**: Depends on Phase 2 only. +- **US3**: Depends on Phase 2 and benefits from US1/US2 CTA copy decisions. +- **US4**: Depends on Phase 2 and can run parallel with US3 after P1 copy direction is stable. +- **US5**: Depends on Phase 2 and final route/metadata decisions from US1-US4. + +**MVP Scope** + +Complete Phase 1, Phase 2, and Phase 3 (US1) for the smallest independently reviewable increment. + +--- + +## Parallel Execution Examples + +**After Phase 2** + +```text +US1 independent files: +- T016 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/faqs.json +- T013 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/interaction.spec.ts + +US2 independent file: +- T024 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/ui/blocks/ReviewComponent.astro +``` + +**CTA And Claim Safety** + +```text +US3 CTA work: +- T031 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/navbar&footer/FooterSection.astro +- T032 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/contact.astro +- T035 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/pricing.astro and /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/data_files/pricing.json + +US4 claim work: +- T040 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/trust.astro +- T042 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/imprint.astro +- T044 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/docs +``` + +**Metadata And Route Exposure** + +```text +US5 output work: +- T053 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/astro.config.mjs +- T054 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/robots.txt.ts +- T055 in /Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/product.astro and related redirect page files +``` + +--- + +## Implementation Strategy + +1. Finish the setup and foundational smoke-helper tasks first. +2. Ship the MVP with US1 homepage positioning and smoke assertions. +3. Add US2 `/platform` product clarity before broad CTA review so link labels and product terms are stable. +4. Add US3 CTA integrity and US4 claim safety in parallel where files do not overlap. +5. Finish with US5 metadata, route exposure, sitemap, robots, and final build/smoke validation. +6. Complete the launch-readiness note and final `apps/platform` untouched proof before review. + +## Test Governance Notes + +The declared test purpose is Browser/static website. The affected validation lanes are website build, Playwright public smoke, manual browser review, whitespace validation, and `apps/platform` scope validation. No Pest, Laravel, Filament, Livewire, database, provider, workspace, membership, session, queue, Sail, or heavy-governance setup is introduced.