From 2190322711ca91d00277668f392c67ebc5e7530f Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Mon, 18 May 2026 16:27:24 +0200 Subject: [PATCH] feat: rebuild Tenantial homepage visuals --- Agents.md | 1 + apps/website/astro.config.mjs | 2 +- apps/website/public/favicon.svg | 14 +- .../public/images/hero-product-visual.svg | 70 -- .../tenantial-logo-transparent-clean.png | Bin 0 -> 65826 bytes .../public/images/tenantial-wave-mesh.svg | 46 + .../components/content/DashboardPreview.astro | 1092 +++++++++++++++++ .../src/components/content/Headline.astro | 2 +- .../components/content/HeroDashboard.astro | 467 ------- .../components/content/TenantialLogo.astro | 35 + .../src/components/layout/Footer.astro | 10 +- .../src/components/layout/Navbar.astro | 64 +- .../src/components/layout/PageShell.astro | 13 +- .../src/components/primitives/Button.astro | 6 +- .../components/sections/FeaturePillars.astro | 70 ++ .../src/components/sections/PageHero.astro | 85 +- .../src/components/sections/TrustBar.astro | 30 + apps/website/src/content/pages/changelog.ts | 4 +- apps/website/src/content/pages/contact.ts | 10 +- apps/website/src/content/pages/home.ts | 189 +-- apps/website/src/content/pages/imprint.ts | 8 +- .../website/src/content/pages/integrations.ts | 6 +- apps/website/src/content/pages/legal.ts | 4 +- apps/website/src/content/pages/privacy.ts | 8 +- apps/website/src/content/pages/product.ts | 6 +- .../src/content/pages/security-trust.ts | 4 +- apps/website/src/content/pages/solutions.ts | 6 +- apps/website/src/content/pages/terms.ts | 8 +- apps/website/src/content/pages/trust.ts | 4 +- apps/website/src/lib/site.ts | 87 +- apps/website/src/pages/index.astro | 75 +- apps/website/src/pages/product.astro | 2 +- apps/website/src/styles/global.css | 213 ++-- apps/website/src/styles/tokens.css | 179 +-- .../tests/smoke/changelog-core-ia.spec.ts | 17 +- .../website/tests/smoke/contact-legal.spec.ts | 8 +- apps/website/tests/smoke/home-product.spec.ts | 182 +-- apps/website/tests/smoke/smoke-helpers.ts | 73 +- .../visual-foundation-guardrails.spec.ts | 8 +- .../checklists/requirements.md | 36 + .../contracts/public-homepage.yaml | 110 ++ .../data-model.md | 216 ++++ .../plan.md | 201 +++ .../quickstart.md | 73 ++ .../research.md | 94 ++ .../spec.md | 260 ++++ .../tasks.md | 257 ++++ 47 files changed, 3182 insertions(+), 1173 deletions(-) delete mode 100644 apps/website/public/images/hero-product-visual.svg create mode 100644 apps/website/public/images/tenantial-logo-transparent-clean.png create mode 100644 apps/website/public/images/tenantial-wave-mesh.svg create mode 100644 apps/website/src/components/content/DashboardPreview.astro delete mode 100644 apps/website/src/components/content/HeroDashboard.astro create mode 100644 apps/website/src/components/content/TenantialLogo.astro create mode 100644 apps/website/src/components/sections/FeaturePillars.astro create mode 100644 apps/website/src/components/sections/TrustBar.astro create mode 100644 specs/400-tenantial-homepage-visual-rebuild/checklists/requirements.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml create mode 100644 specs/400-tenantial-homepage-visual-rebuild/data-model.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/plan.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/quickstart.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/research.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/spec.md create mode 100644 specs/400-tenantial-homepage-visual-rebuild/tasks.md diff --git a/Agents.md b/Agents.md index e092c426..50b8b846 100644 --- a/Agents.md +++ b/Agents.md @@ -942,6 +942,7 @@ ## Active Technologies - PHP 8.4 (Laravel 12) + Filament v5 + Livewire v4 - PostgreSQL (Sail) - 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) ## 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 3eae393b..1a650fb6 100644 --- a/apps/website/astro.config.mjs +++ b/apps/website/astro.config.mjs @@ -4,7 +4,7 @@ import tailwindcss from '@tailwindcss/vite'; import icon from 'astro-icon'; import { defineConfig } from 'astro/config'; -const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example'; +const publicSiteUrl = process.env.PUBLIC_SITE_URL ?? 'https://tenantial.example'; export default defineConfig({ integrations: [icon()], diff --git a/apps/website/public/favicon.svg b/apps/website/public/favicon.svg index 8895399e..2a4f6385 100644 --- a/apps/website/public/favicon.svg +++ b/apps/website/public/favicon.svg @@ -1,11 +1,9 @@ - - + + + - + diff --git a/apps/website/public/images/hero-product-visual.svg b/apps/website/public/images/hero-product-visual.svg deleted file mode 100644 index 6ac04ffa..00000000 --- a/apps/website/public/images/hero-product-visual.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - TenantAtlas - - - - WORKSPACE - - Change history - Restore preview - Review queue - Evidence - Assignments - - Current tenant - Northwind Services - Inventory linked to reviewable history - - Recent tenant changes - - Policies - - Drift - - Assignments - - CHANGE RECORD - - Windows Compliance Baseline - Version diff prepared for review - - Ready - - Conditional Access MFA - Assignment drift surfaced before rollout - - Needs review - - BitLocker policy - Restore candidate linked to prior snapshot - - Snapshot linked - - RESTORE PREVIEW - Scope validated - Assignments included - Confirmation required - - - - - REVIEW QUEUE - Conditional Access drift - Restore plan awaiting approval - Evidence attached to change record - - Change history, restore preview, and review queue stay connected on one screen. - diff --git a/apps/website/public/images/tenantial-logo-transparent-clean.png b/apps/website/public/images/tenantial-logo-transparent-clean.png new file mode 100644 index 0000000000000000000000000000000000000000..e24524c6c44df41308e360518d57295801d1ee99 GIT binary patch literal 65826 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 literal 0 HcmV?d00001 diff --git a/apps/website/public/images/tenantial-wave-mesh.svg b/apps/website/public/images/tenantial-wave-mesh.svg new file mode 100644 index 00000000..0a7dbc81 --- /dev/null +++ b/apps/website/public/images/tenantial-wave-mesh.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/apps/website/src/components/content/DashboardPreview.astro b/apps/website/src/components/content/DashboardPreview.astro new file mode 100644 index 00000000..f1b0cbd2 --- /dev/null +++ b/apps/website/src/components/content/DashboardPreview.astro @@ -0,0 +1,1092 @@ +--- +import TenantialLogo from '@/components/content/TenantialLogo.astro'; +import { Icon } from 'astro-icon/components'; + +const navigationItems = [ + { icon: 'lucide:layout-dashboard', label: 'Overview' }, + { icon: 'lucide:circle-alert', label: 'Findings' }, + { icon: 'lucide:file-check-2', label: 'Evidence' }, + { icon: 'lucide:git-branch', label: 'Drift' }, + { icon: 'lucide:archive-restore', label: 'Backups' }, + { icon: 'lucide:clipboard-check', label: 'Reviews' }, + { icon: 'lucide:history', label: 'Audit Trail' }, + { icon: 'lucide:building-2', label: 'Tenants' }, + { icon: 'lucide:settings', label: 'Settings' }, +]; + +const metrics = [ + { + label: 'Overall posture', + value: '92%', + status: 'Healthy', + meta: 'Demo baseline', + tone: 'healthy', + }, + { + label: 'Findings', + value: '14', + status: 'Review required', + meta: 'Total', + tone: 'warning', + }, + { + label: 'Drift detected', + value: '7', + status: 'Critical', + meta: 'Resources', + tone: 'critical', + }, + { + label: 'Evidence items', + value: '1,248', + status: 'Review-ready', + meta: 'Collected', + tone: 'evidence', + }, + { + label: 'Backup status', + value: '98%', + status: 'Healthy', + meta: 'Successful', + tone: 'healthy', + }, +]; + +const findings = [ + ['Critical', 'Guest user admin roles', 'Privileged Access', 'Sample signal'], + ['High', 'CA policy not enforced', 'Identity', 'Sample signal'], + ['Medium', 'SharePoint public sharing', 'Data Protection', 'Sample signal'], + ['Low', 'Legacy auth enabled', 'Security Baseline', 'Sample signal'], + ['Low', 'Mailbox auditing off', 'Audit & Compliance', 'Sample signal'], +]; + +const timeline = [ + ['Policy "Require MFA for admins" changed', 'Critical sample drift', 'Sample step 1', 'critical'], + ['New guest user added to Global Admins', 'High sample drift', 'Sample step 2', 'warning'], + ['Conditional Access policy updated', 'Medium sample drift', 'Sample step 3', 'warning'], + ['SharePoint public sharing enabled', 'Low sample drift', 'Sample step 4', 'info'], + ['MFA registration policy updated', 'No sample drift', 'Sample step 5', 'healthy'], +]; + +const evidenceItems = [ + { icon: 'lucide:shield-check', meta: 'Updated recently - demo value', title: 'Conditional Access policy' }, + { icon: 'lucide:user-check', meta: 'Sample admin change', title: 'Role assignment change' }, + { icon: 'lucide:share-2', meta: 'Updated recently - demo value', title: 'SharePoint sharing setting' }, + { icon: 'lucide:mail', meta: 'Updated recently - demo value', title: 'Mailbox audit configuration' }, + { icon: 'lucide:square-plus', meta: 'Sample new item', title: 'App registration added' }, +]; +--- + +
+
+
+ + +
+
+ Sample tenant +
+ Static demo preview + Demo values +
+ +
+ +
+
+
+

Governance overview

+

Static posture and activity across Microsoft tenant governance work.

+
+ Sample week +
+ +
+
+

Overall posture

+ + 92% + Healthy +
+ +
+

Findings

+ 14 +

Total

+ Review required + Critical 2 + High 5 + Medium 4 + Low 3 +
+ +
+

Drift detected

+ 7 +

Resources

+ + Critical +
+ +
+

Evidence items

+ 1,248 +

Collected

+ ▲ 18% vs sample week + Review-ready +
+ +
+

Backup status

+ 98% +

Successful

+ + Healthy +
+
+ +
+
+
+

Recent findings

+ Sample set +
+
+ { + findings.map(([severity, title, area, time]) => ( +
+ +
+

{title}

+

{area}

+
+ {severity} + {time} +
+ )) + } +
+
+ +
+
+

Drift timeline

+ Sample week +
+
    + { + timeline.map(([title, description, time, tone]) => ( +
  1. + +
    +

    {title}

    + {description} +
    + +
  2. + )) + } +
+
+ +
+
+
+

Backups and restores

+ Sample values +
+
+
+

Microsoft 365

+ Recent sample success +
+ 98% + +
+
+
+

Azure AD

+ Recent sample success +
+ 97% + +
+
+ +
+
+

Governance reviews

+ Sample reviews +
+

Quarterly access review Due in 5 days

+

Privileged roles review In progress

+
+
+
+ +
+
+

Evidence spotlight

+ Sample evidence +
+
+ { + evidenceItems.map((item) => ( +
+ +

{item.title}

+ {item.meta} +
+ )) + } +
+
+
+
+
+
+
+ + diff --git a/apps/website/src/components/content/Headline.astro b/apps/website/src/components/content/Headline.astro index 31ae6915..ca92fbbe 100644 --- a/apps/website/src/components/content/Headline.astro +++ b/apps/website/src/components/content/Headline.astro @@ -17,6 +17,6 @@ const sizeClasses = { }; --- -.accent]:text-[var(--color-primary)]', sizeClasses[size], className]}> +.accent]:text-[var(--color-primary)]', sizeClasses[size], className]}> diff --git a/apps/website/src/components/content/HeroDashboard.astro b/apps/website/src/components/content/HeroDashboard.astro deleted file mode 100644 index ce4c319e..00000000 --- a/apps/website/src/components/content/HeroDashboard.astro +++ /dev/null @@ -1,467 +0,0 @@ ---- ---- - - - - diff --git a/apps/website/src/components/content/TenantialLogo.astro b/apps/website/src/components/content/TenantialLogo.astro new file mode 100644 index 00000000..bb4b5ca1 --- /dev/null +++ b/apps/website/src/components/content/TenantialLogo.astro @@ -0,0 +1,35 @@ +--- +interface Props { + class?: string; + imageClass?: string; + invert?: boolean; + label?: string; + markClass?: string; +} + +const { + class: className = '', + imageClass, + invert = true, + label = 'Tenantial', + markClass = 'h-9 w-9', +} = Astro.props; + +const resolvedImageClass = imageClass ?? markClass; +--- + + + {label} + diff --git a/apps/website/src/components/layout/Footer.astro b/apps/website/src/components/layout/Footer.astro index 2ca0f1cc..54e33689 100644 --- a/apps/website/src/components/layout/Footer.astro +++ b/apps/website/src/components/layout/Footer.astro @@ -17,10 +17,10 @@ const footerNavigationGroups = await getFooterNavigationGroups();
-

+

{footerLead.eyebrow}

-

+

{footerLead.title}

@@ -33,7 +33,7 @@ const footerNavigationGroups = await getFooterNavigationGroups(); { footerNavigationGroups.map((group) => (

-

+

{group.title}

    @@ -52,9 +52,9 @@ const footerNavigationGroups = await getFooterNavigationGroups();
-

© {currentYear} {siteMetadata.siteName}. Core public route foundation.

+

© {currentYear} {siteMetadata.siteName}. Evidence-first governance for Microsoft tenants.

- Built as a static Astro track with no platform auth, session, or API coupling. + Static public website with sample preview values only.

diff --git a/apps/website/src/components/layout/Navbar.astro b/apps/website/src/components/layout/Navbar.astro index 5f707a06..a4c08eab 100644 --- a/apps/website/src/components/layout/Navbar.astro +++ b/apps/website/src/components/layout/Navbar.astro @@ -1,5 +1,6 @@ --- -import SecondaryCTA from '@/components/content/SecondaryCTA.astro'; +import PrimaryCTA from '@/components/content/PrimaryCTA.astro'; +import TenantialLogo from '@/components/content/TenantialLogo.astro'; import Container from '@/components/primitives/Container.astro'; import { getHeaderCta, getPrimaryNavigation, isActiveNavigationPath, siteMetadata } from '@/lib/site'; @@ -12,39 +13,31 @@ const headerCta = getHeaderCta(currentPath); const primaryNavigation = await getPrimaryNavigation(); --- -
- +
+ diff --git a/apps/website/src/components/layout/PageShell.astro b/apps/website/src/components/layout/PageShell.astro index 661e1077..82ef0138 100644 --- a/apps/website/src/components/layout/PageShell.astro +++ b/apps/website/src/components/layout/PageShell.astro @@ -37,10 +37,15 @@ const pageDefinition = getPageDefinition(currentPath); data-surface-group={pageDefinition.surfaceGroup} data-journey-stage={pageDefinition.journeyStage} > -
-
+
+ { + pageDefinition.pageRole === 'home' && ( + <> + + + + ) + }
diff --git a/apps/website/src/components/primitives/Button.astro b/apps/website/src/components/primitives/Button.astro index 229b2952..6876abba 100644 --- a/apps/website/src/components/primitives/Button.astro +++ b/apps/website/src/components/primitives/Button.astro @@ -34,10 +34,10 @@ const sizeClasses = { const variantClasses = { primary: - 'border-transparent bg-[var(--color-ink-900)] text-white shadow-[0_2px_8px_rgba(0,0,0,0.12)] hover:bg-[var(--color-ink-800)] hover:shadow-[0_4px_16px_rgba(0,0,0,0.18)] active:scale-[0.98]', + 'border-transparent bg-[var(--color-primary)] text-[var(--color-primary-foreground)] shadow-[0_2px_14px_rgba(111,229,191,0.2)] hover:bg-[var(--color-mint-300)] hover:shadow-[0_4px_20px_rgba(111,229,191,0.26)] active:scale-[0.98]', secondary: - 'border-[color:var(--color-border)] bg-white text-[var(--color-secondary-foreground)] hover:border-[var(--color-border-strong)] hover:bg-[var(--surface-muted)] active:scale-[0.98]', - ghost: 'border-transparent bg-transparent text-[var(--color-ink-800)] hover:bg-white/70', + 'border-[color:var(--color-border)] bg-[var(--color-secondary)] text-[var(--color-secondary-foreground)] hover:border-[var(--color-border-strong)] hover:bg-[var(--surface-muted-strong)] active:scale-[0.98]', + ghost: 'border-transparent bg-transparent text-[var(--color-muted-foreground)] hover:bg-[var(--surface-muted)] hover:text-[var(--color-foreground)]', }; const classes = [baseClass, sizeClasses[size], variantClasses[variant], className]; diff --git a/apps/website/src/components/sections/FeaturePillars.astro b/apps/website/src/components/sections/FeaturePillars.astro new file mode 100644 index 00000000..773fd80a --- /dev/null +++ b/apps/website/src/components/sections/FeaturePillars.astro @@ -0,0 +1,70 @@ +--- +import { Icon } from 'astro-icon/components'; +import Container from '@/components/primitives/Container.astro'; +import Section from '@/components/primitives/Section.astro'; +import SectionHeader from '@/components/primitives/SectionHeader.astro'; +import type { FeatureItemContent } from '@/types/site'; + +interface Props { + items: FeatureItemContent[]; +} + +const { items } = Astro.props; + +const lucideMap: Record = { + archive: 'lucide:archive', + refresh: 'lucide:refresh-cw', + 'git-branch': 'lucide:git-branch', + 'file-check': 'lucide:file-check', + clipboard: 'lucide:clipboard-list', + shield: 'lucide:shield-check', +}; +--- + +
+ +
+ + +
+ { + items.map((item) => { + const iconName = item.icon ? lucideMap[item.icon] : undefined; + + return ( +
+
+ {iconName && ( + + )} +
+

+ {item.title} +

+

+ {item.description} +

+ {item.meta && ( +

+ {item.meta} +

+ )} +
+
+
+ ); + }) + } +
+
+
+
diff --git a/apps/website/src/components/sections/PageHero.astro b/apps/website/src/components/sections/PageHero.astro index ebe229fa..f9fc7683 100644 --- a/apps/website/src/components/sections/PageHero.astro +++ b/apps/website/src/components/sections/PageHero.astro @@ -3,12 +3,13 @@ import Badge from '@/components/primitives/Badge.astro'; import Card from '@/components/primitives/Card.astro'; import Cluster from '@/components/primitives/Cluster.astro'; import Container from '@/components/primitives/Container.astro'; +import DashboardPreview from '@/components/content/DashboardPreview.astro'; import Headline from '@/components/content/Headline.astro'; -import HeroDashboard from '@/components/content/HeroDashboard.astro'; import Lead from '@/components/content/Lead.astro'; import Metric from '@/components/content/Metric.astro'; import PrimaryCTA from '@/components/content/PrimaryCTA.astro'; import SecondaryCTA from '@/components/content/SecondaryCTA.astro'; +import { Icon } from 'astro-icon/components'; import type { HeroContent, MetricItem } from '@/types/site'; interface Props { @@ -23,11 +24,13 @@ const isHomepageHero = Astro.url.pathname === '/'; const heroHeadlineSize = isHomepageHero ? 'page' : 'display'; const heroLeadSize = isHomepageHero ? 'body' : 'lead'; const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline'; +const trustSignalIcons = ['lucide:shield-check', 'lucide:lock', 'lucide:check-circle']; +const audienceLabels = ['MSP operators', 'Endpoint teams', 'Security reviewers', 'Audit owners', 'Cloud operations']; ---
- + {isHomepageHero ? ( -
-
-
-
+
+
+
+
{hero.eyebrow} @@ -54,7 +57,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline'; {hero.titleHtml ? : hero.title} @@ -64,7 +67,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline'; data-hero-supporting-copy data-hero-segment="supporting-copy" > - + {hero.description}
@@ -72,7 +75,7 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
{(hero.primaryCta || hero.secondaryCta) && (
- + {hero.secondaryCta && ( @@ -81,9 +84,37 @@ const heroPrimaryAnchor = hero.primaryAnchor ?? 'headline';
)}
+ {hero.trustSubclaims && hero.trustSubclaims.length > 0 && ( +
+
    + {hero.trustSubclaims.map((claim, index) => ( +
  • +
  • + ))} +
+
+

+ Built for operator-led teams +

+
    + {audienceLabels.map((label) => ( +
  • + {label} +
  • + ))} +
+
+
+ )}
-
- {hero.visualFocus && ( -
-

- {hero.visualFocus.eyebrow} -

-

- {hero.visualFocus.title} -

-
    - {hero.visualFocus.points.map((point) => ( -
  • - {point} -
  • - ))} -
-
- )} - -
+
- {hero.trustSubclaims && hero.trustSubclaims.length > 0 && ( -
-
    - {hero.trustSubclaims.map((claim) => ( -
  • - {claim} -
  • - ))} -
-
- )}
) : ( diff --git a/apps/website/src/components/sections/TrustBar.astro b/apps/website/src/components/sections/TrustBar.astro new file mode 100644 index 00000000..875ba8fb --- /dev/null +++ b/apps/website/src/components/sections/TrustBar.astro @@ -0,0 +1,30 @@ +--- +import Container from '@/components/primitives/Container.astro'; +import Section from '@/components/primitives/Section.astro'; +import type { TrustPrincipleContent } from '@/types/site'; + +interface Props { + statements: TrustPrincipleContent[]; +} + +const { statements } = Astro.props; +--- + +
+ +
+ { + statements.map((statement) => ( +
+

+ {statement.title} +

+

+ {statement.description} +

+
+ )) + } +
+
+
diff --git a/apps/website/src/content/pages/changelog.ts b/apps/website/src/content/pages/changelog.ts index de8d8b39..524e59c0 100644 --- a/apps/website/src/content/pages/changelog.ts +++ b/apps/website/src/content/pages/changelog.ts @@ -1,9 +1,9 @@ import type { HeroContent, PageSeo } from '@/types/site'; export const changelogSeo: PageSeo = { - title: 'TenantAtlas | Changelog', + title: 'Tenantial | Changelog', description: - 'TenantAtlas uses a dedicated changelog to show dated public progress without pretending a broader editorial or resources program is already live.', + 'Tenantial uses a dedicated changelog to show dated public progress without pretending a broader editorial or resources program is already live.', path: '/changelog', }; diff --git a/apps/website/src/content/pages/contact.ts b/apps/website/src/content/pages/contact.ts index 884ca85b..ac787c72 100644 --- a/apps/website/src/content/pages/contact.ts +++ b/apps/website/src/content/pages/contact.ts @@ -1,9 +1,9 @@ import type { HeroContent, LegalSection, PageSeo } from '@/types/site'; export const contactSeo: PageSeo = { - title: 'TenantAtlas | Contact', + title: 'Tenantial | Contact', description: - 'TenantAtlas uses one clear contact path for serious product, trust, and rollout conversations instead of splitting first contact across vague demo flows.', + 'Tenantial uses one clear contact path for serious product, trust, and rollout conversations instead of splitting first contact across vague demo flows.', path: '/contact', }; @@ -13,8 +13,8 @@ export const contactHero: HeroContent = { description: 'The contact path should help serious buyers explain who they are, what governance questions they are trying to solve, and what kind of follow-up would actually be useful.', primaryCta: { - href: 'mailto:hello@tenantatlas.example?subject=TenantAtlas%20working%20session', - label: 'Email the TenantAtlas team', + href: 'mailto:hello@tenantial.example?subject=Tenantial%20working%20session', + label: 'Email the Tenantial team', variant: 'primary', }, secondaryCta: { @@ -50,7 +50,7 @@ export const contactPrompts = [ export const contactPreview = { message: - 'We operate Microsoft tenant governance across multiple environments and want to understand how TenantAtlas approaches version history, safer restore flows, drift visibility, and review evidence.', + 'We operate Microsoft tenant governance across multiple environments and want to understand how Tenantial approaches version history, safer restore flows, drift visibility, and review evidence.', topic: 'Environment and operating model summary', }; diff --git a/apps/website/src/content/pages/home.ts b/apps/website/src/content/pages/home.ts index ce9b4a0b..fa5fe4de 100644 --- a/apps/website/src/content/pages/home.ts +++ b/apps/website/src/content/pages/home.ts @@ -1,171 +1,112 @@ import type { - CapabilityClusterContent, CtaLink, + FeatureItemContent, HeroContent, - IntegrationEntry, - OutcomeSectionContent, PageSeo, - ProgressTeaserContent, - TrustSignalGroupContent, + TrustPrincipleContent, } from '@/types/site'; export const homeSeo: PageSeo = { - title: 'TenantAtlas | Governance of record for Microsoft tenant operations', + title: 'Tenantial - Evidence-first governance for Microsoft tenants', description: - 'TenantAtlas helps teams understand Microsoft tenant change history, restore posture, trust boundaries, and the next evaluation step without a bloated public sitemap.', + 'Tenantial helps Microsoft tenant teams govern backup, restore, drift detection, snapshot-backed audit context, verifiable evidence, and structured governance reviews from one evidence-first surface.', + ogTitle: 'Evidence-first governance for Microsoft tenants.', + ogDescription: + 'Evidence-oriented restore discipline, drift detection, and governance review workflows for Microsoft tenants without unsupported proof claims.', path: '/', }; export const homeHero: HeroContent = { - eyebrow: 'Microsoft tenant governance', - title: 'TenantAtlas gives Microsoft tenant teams one operating record for change history, drift review, and restore planning.', + eyebrow: 'Governance that earns trust', + title: 'Evidence-first governance for Microsoft tenants.', description: - 'Security, endpoint, and platform teams use TenantAtlas to see what changed, preview restores, and move reviews forward without stitching governance across exports and memory.', + 'Backup and restore with confidence. Detect drift before it becomes audit work. Preserve snapshot history and review context for governance conversations that need evidence.', primaryCta: { href: '/contact', - label: 'Request a working session', + label: 'Book a demo', }, secondaryCta: { href: '/product', - label: 'See the product model', + label: 'Explore the platform', variant: 'secondary', }, - productVisual: { - src: '/images/hero-product-visual.svg', - alt: 'TenantAtlas screen showing change history, restore preview, and a review queue for Microsoft tenant policies', - }, trustSubclaims: [ - 'Tenant-scoped boundaries', - 'Reviewable change history', - 'Preview before restore', + 'Built for Microsoft 365 and Azure AD', + 'Snapshot-first history with review context.', + 'Designed for audit-ready workflows.', ], }; -export const homeOutcome: OutcomeSectionContent = { - title: 'Calmer operations start with visible governance.', - description: - 'TenantAtlas replaces fragmented spreadsheets and manual audit trails with one connected record of tenant change, restore safety, and review context.', - audienceBias: 'MSP and enterprise operations teams', - outcomes: [ - { - title: 'Understand what changed and why it matters.', - description: - 'Immutable version history gives every tenant configuration a traceable timeline, so drift and unexpected changes surface before they become incidents.', - }, - { - title: 'Restore with confidence, not guesswork.', - description: - 'Preview-first restore flows validate scope and impact before execution, turning risky rollbacks into reviewable operations.', - }, - { - title: 'Reduce the operational risk of governance gaps.', - description: - 'Connected findings, evidence, and review workflows keep audit context in one place instead of scattered across tools and memory.', - }, - ], -}; - -export const homeCapabilities: CapabilityClusterContent[] = [ +export const homeTrustBar: TrustPrincipleContent[] = [ { - title: 'Backup & Version History', - description: 'Immutable snapshots of tenant configuration state with full version lineage.', - capabilities: ['Automated backup', 'Immutable snapshots', 'Version comparison', 'Change timeline'], - href: '/product', - meta: 'Core safety net', + title: 'Microsoft tenant focused', + description: 'Built around the governance surfaces Microsoft tenant teams already need to inspect.', }, { - title: 'Restore & Recovery', - description: 'Preview-first restore with selective scope and explicit confirmation.', - capabilities: ['Dry-run preview', 'Selective restore', 'Rollback safety', 'Conflict detection'], - href: '/product', - meta: 'Safer change operations', + title: 'Evidence-oriented workflows', + description: 'Backup, drift, restore, and review context stay connected to the decision trail.', }, { - title: 'Inventory & Drift Visibility', - description: 'Connected view of what exists, what drifted, and what needs attention.', - capabilities: ['Policy inventory', 'Drift detection', 'Assignment visibility', 'Cross-tenant comparison'], - href: '/product', - meta: 'Operational clarity', + title: 'Designed for audit review', + description: 'Claims stay grounded in reviewable evidence rather than unverified certification language.', }, { - title: 'Governance & Evidence', - description: 'Audit-ready evidence, findings, and review workflows for tenant operations.', - capabilities: ['Audit trails', 'Evidence linkage', 'Exception tracking', 'Review workflows'], - href: '/product', - meta: 'Accountability and review', + title: 'Operator-led governance', + description: 'High-risk actions stay framed by preview, confirmation, and explicit accountability.', }, ]; -export const homeTrustSignals: TrustSignalGroupContent = { - title: 'Trust is a first-read concern, not a footnote.', - description: - 'Tenant isolation, access boundaries, and operating discipline are product rules, not marketing language. Every public claim routes back to one bounded trust surface.', - supportRoute: '/trust', - signals: [ - { - title: 'Tenant isolation is a product boundary.', - description: - 'Tenant-scoped data, access, and workflow boundaries are enforced as deliberate product rules.', - }, - { - title: 'Access stays bounded and purpose-specific.', - description: - 'Microsoft tenant-facing access follows least-privilege scoping tied to the governance operations that require it.', - }, - { - title: 'Restore operations require preview and confirmation.', - description: - 'High-risk changes go through validation, selective scope, and explicit confirmation instead of one-click execution.', - }, - ], -}; - -export const homeProgressTeaser: ProgressTeaserContent = { - title: 'Product movement you can verify.', - description: - 'Real progress shows up as dated changelog entries, not vague promises. Check the changelog for the latest updates.', - entries: [], - cta: { - href: '/changelog', - label: 'Read the full changelog', +export const homeFeaturePillars: FeatureItemContent[] = [ + { + title: 'Backup', + description: 'Preserve full Microsoft tenant configuration snapshots so recovery starts from known evidence.', + icon: 'archive', + meta: 'Snapshot record', }, -}; + { + title: 'Restore', + description: 'Preview scope, assignments, and conflicts before an operator commits to a restore path.', + icon: 'refresh', + meta: 'Dry-run first', + }, + { + title: 'Drift Detection', + description: 'Surface meaningful configuration change so review work starts before drift becomes incident work.', + icon: 'git-branch', + meta: 'Review required', + }, + { + title: 'Evidence', + description: 'Keep screenshots, snapshots, findings, and review notes attached to the governance decision.', + icon: 'file-check', + meta: 'Decision context', + }, + { + title: 'Audit Trail', + description: 'Show who changed, reviewed, backed up, or restored tenant configuration with clear timestamps.', + icon: 'clipboard', + meta: 'Traceable activity', + }, + { + title: 'Governance Reviews', + description: 'Move structured tenant reviews from memory and exports into a repeatable operating rhythm.', + icon: 'shield', + meta: 'Review cadence', + }, +]; export const homeCtaSection = { eyebrow: 'Next step', - title: 'Move from first read into a working session.', + title: 'Build tenant governance on evidence, not assumptions.', description: - 'Once you understand the product model, the trust posture, and the progress record, the next step is a conversation about your governance reality.', + 'Use a focused walkthrough to map Tenantial against your Microsoft tenant backup, restore, drift, evidence, and review workflows.', primary: { href: '/contact', - label: 'Request a working session', + label: 'Book a demo', } as CtaLink, secondary: { href: '/product', - label: 'See the product model', + label: 'Explore the platform', variant: 'secondary', } as CtaLink, }; - -export const homeEcosystem: IntegrationEntry[] = [ - { - category: 'Microsoft', - name: 'Microsoft Graph', - summary: 'Graph-backed inventory and restore without implying the public website depends on live tenant access.', - }, - { - category: 'Identity', - name: 'Entra ID', - summary: 'Identity context where change control, tenant access, and review posture intersect.', - }, - { - category: 'Endpoint', - name: 'Intune', - summary: 'Configuration state, backup, restore posture, and drift visibility stay central to the product story.', - }, - { - category: 'Governance', - name: 'Review workflows', - summary: 'Exceptions, evidence, and reviews stay connected to operational reality.', - }, -]; diff --git a/apps/website/src/content/pages/imprint.ts b/apps/website/src/content/pages/imprint.ts index a7352e41..2ff3bad8 100644 --- a/apps/website/src/content/pages/imprint.ts +++ b/apps/website/src/content/pages/imprint.ts @@ -1,15 +1,15 @@ import type { HeroContent, LegalSection, PageSeo } from '@/types/site'; export const imprintSeo: PageSeo = { - title: 'TenantAtlas | Imprint', + title: 'Tenantial | Imprint', description: - 'TenantAtlas uses the Imprint route as the canonical public legal notice and publisher baseline for the website.', + 'Tenantial uses the Imprint route as the canonical public legal notice and publisher baseline for the website.', path: '/imprint', }; export const imprintHero: HeroContent = { eyebrow: 'Canonical legal notice', - title: 'Imprint and public legal notice baseline for the TenantAtlas website.', + title: 'Imprint and public legal notice baseline for the Tenantial website.', description: 'This route is the canonical public notice surface for publisher identity and jurisdiction-specific disclosure details. During controlled evaluation, it also makes clear which launch-ready fields still need to be finalized before broader publication.', primaryCta: { @@ -32,7 +32,7 @@ export const imprintSections: LegalSection[] = [ { title: 'Current publication status', body: [ - 'TenantAtlas is still tightening its launch-ready publisher and jurisdiction details for the broader public website.', + 'Tenantial is still tightening its launch-ready publisher and jurisdiction details for the broader public website.', 'Until those fields are finalized, this route makes the intended legal-notice location explicit so the public IA stays honest about where the canonical notice belongs.', ], bullets: [ diff --git a/apps/website/src/content/pages/integrations.ts b/apps/website/src/content/pages/integrations.ts index b204afa8..9bb0eaf8 100644 --- a/apps/website/src/content/pages/integrations.ts +++ b/apps/website/src/content/pages/integrations.ts @@ -6,9 +6,9 @@ import type { } from '@/types/site'; export const integrationsSeo: PageSeo = { - title: 'TenantAtlas | Integrations', + title: 'Tenantial | Integrations', description: - 'TenantAtlas keeps ecosystem-fit detail available as a supporting page without pretending integrations belong in the primary navigation.', + 'Tenantial keeps ecosystem-fit detail available as a supporting page without pretending integrations belong in the primary navigation.', path: '/integrations', }; @@ -16,7 +16,7 @@ export const integrationsHero: HeroContent = { eyebrow: 'Retained supporting page', title: 'Keep ecosystem fit detail visible without pretending it is the first thing every buyer needs.', description: - 'This page shows the real systems TenantAtlas is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.', + 'This page shows the real systems Tenantial is built around, the workflows it expects to support, and the deliberate boundaries where speculative integration language would create more noise than trust.', primaryCta: { href: '/contact', label: 'Plan the working session', diff --git a/apps/website/src/content/pages/legal.ts b/apps/website/src/content/pages/legal.ts index 768175f1..5f493e7d 100644 --- a/apps/website/src/content/pages/legal.ts +++ b/apps/website/src/content/pages/legal.ts @@ -1,9 +1,9 @@ import type { HeroContent, LegalSection, PageSeo } from '@/types/site'; export const legalSeo: PageSeo = { - title: 'TenantAtlas | Legal', + title: 'Tenantial | Legal', description: - 'The TenantAtlas legal baseline keeps privacy, terms, imprint, and trust routing accessible without promoting the legal hub into the primary navigation.', + 'The Tenantial legal baseline keeps privacy, terms, imprint, and trust routing accessible without promoting the legal hub into the primary navigation.', path: '/legal', }; diff --git a/apps/website/src/content/pages/privacy.ts b/apps/website/src/content/pages/privacy.ts index 93940ae1..ff72e38d 100644 --- a/apps/website/src/content/pages/privacy.ts +++ b/apps/website/src/content/pages/privacy.ts @@ -1,15 +1,15 @@ import type { HeroContent, LegalSection, PageSeo } from '@/types/site'; export const privacySeo: PageSeo = { - title: 'TenantAtlas | Privacy', + title: 'Tenantial | Privacy', description: - 'Public-site privacy overview for TenantAtlas inquiries, including how contact details and evaluation context are handled on the public website.', + 'Public-site privacy overview for Tenantial inquiries, including how contact details and evaluation context are handled on the public website.', path: '/privacy', }; export const privacyHero: HeroContent = { eyebrow: 'Privacy', - title: 'Public-site privacy overview for TenantAtlas inquiries.', + title: 'Public-site privacy overview for Tenantial inquiries.', description: 'This page explains the privacy expectations for the public website and the contact path, rather than promising a full product-tenant data processing agreement from a static marketing surface.', primaryCta: { @@ -32,7 +32,7 @@ export const privacySections: LegalSection[] = [ { title: 'Scope', body: [ - 'This privacy overview applies to the public TenantAtlas website and to information a visitor intentionally shares through the public contact path.', + 'This privacy overview applies to the public Tenantial website and to information a visitor intentionally shares through the public contact path.', 'It does not describe tenant data processing inside the product itself, which belongs in product-specific legal and contractual materials.', ], }, diff --git a/apps/website/src/content/pages/product.ts b/apps/website/src/content/pages/product.ts index 6d9d040d..8f8e4a14 100644 --- a/apps/website/src/content/pages/product.ts +++ b/apps/website/src/content/pages/product.ts @@ -7,9 +7,9 @@ import type { } from '@/types/site'; export const productSeo: PageSeo = { - title: 'TenantAtlas | Product', + title: 'Tenantial | Product', description: - 'TenantAtlas explains backup, restore, version history, drift, findings, evidence, and reviews as one operating model rather than a loose feature list.', + 'Tenantial explains backup, restore, version history, drift, findings, evidence, and reviews as one operating model rather than a loose feature list.', path: '/product', }; @@ -17,7 +17,7 @@ export const productHero: HeroContent = { eyebrow: 'Product model', title: 'Explain the product as one operating model before asking a buyer to trust the route map around it.', description: - 'TenantAtlas treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.', + 'Tenantial treats Microsoft tenant governance as one connected system: observe the current state, preserve immutable history, detect meaningful change, and support reviews or restores with the context operators actually need.', primaryCta: { href: '/trust', label: 'Review the trust posture', diff --git a/apps/website/src/content/pages/security-trust.ts b/apps/website/src/content/pages/security-trust.ts index a570383b..cd937e30 100644 --- a/apps/website/src/content/pages/security-trust.ts +++ b/apps/website/src/content/pages/security-trust.ts @@ -6,9 +6,9 @@ import type { } from '@/types/site'; export const securityTrustSeo: PageSeo = { - title: 'TenantAtlas | Security & Trust', + title: 'Tenantial | Security & Trust', description: - 'TenantAtlas frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.', + 'Tenantial frames trust through substantiated product posture, safer restore discipline, and operational clarity rather than inflated guarantees.', path: '/security-trust', }; diff --git a/apps/website/src/content/pages/solutions.ts b/apps/website/src/content/pages/solutions.ts index 5e42c82d..2ab5d8ed 100644 --- a/apps/website/src/content/pages/solutions.ts +++ b/apps/website/src/content/pages/solutions.ts @@ -6,9 +6,9 @@ import type { } from '@/types/site'; export const solutionsSeo: PageSeo = { - title: 'TenantAtlas | Solutions', + title: 'Tenantial | Solutions', description: - 'TenantAtlas keeps MSP and enterprise outcome framing available as a supporting page without requiring it for the first public read.', + 'Tenantial keeps MSP and enterprise outcome framing available as a supporting page without requiring it for the first public read.', path: '/solutions', }; @@ -79,7 +79,7 @@ export const solutionsSignals: FeatureItemContent[] = [ eyebrow: 'Where it lands', title: 'The product belongs in the operating layer, not just the reporting layer.', description: - 'Visitors should understand that TenantAtlas helps teams make safer decisions about configuration state rather than merely summarize activity afterward.', + 'Visitors should understand that Tenantial helps teams make safer decisions about configuration state rather than merely summarize activity afterward.', }, { eyebrow: 'How it reads', diff --git a/apps/website/src/content/pages/terms.ts b/apps/website/src/content/pages/terms.ts index 54ce9bfc..69fc0131 100644 --- a/apps/website/src/content/pages/terms.ts +++ b/apps/website/src/content/pages/terms.ts @@ -1,15 +1,15 @@ import type { HeroContent, LegalSection, PageSeo } from '@/types/site'; export const termsSeo: PageSeo = { - title: 'TenantAtlas | Terms', + title: 'Tenantial | Terms', description: - 'Website terms for the public TenantAtlas surface, covering informational use of the site and the limits of public product statements.', + 'Website terms for the public Tenantial surface, covering informational use of the site and the limits of public product statements.', path: '/terms', }; export const termsHero: HeroContent = { eyebrow: 'Website terms', - title: 'Website terms for the public TenantAtlas surface.', + title: 'Website terms for the public Tenantial surface.', description: 'These terms describe the public website itself: informational use of the content, basic conduct expectations, and the fact that a public product site is not the same thing as a signed service agreement.', primaryCta: { @@ -32,7 +32,7 @@ export const termsSections: LegalSection[] = [ { title: 'Informational website use', body: [ - 'The public TenantAtlas website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.', + 'The public Tenantial website is provided to explain the product category, trust posture, integrations direction, and contact path for evaluation conversations.', 'Nothing on the public site should be interpreted as a guarantee of product availability, certification, or commercial commitment unless it is later confirmed in signed agreements.', ], }, diff --git a/apps/website/src/content/pages/trust.ts b/apps/website/src/content/pages/trust.ts index c1748a4e..a5a795fd 100644 --- a/apps/website/src/content/pages/trust.ts +++ b/apps/website/src/content/pages/trust.ts @@ -6,9 +6,9 @@ import type { } from '@/types/site'; export const trustSeo: PageSeo = { - title: 'TenantAtlas | Trust', + title: 'Tenantial | Trust', description: - 'TenantAtlas uses the Trust surface to explain tenant isolation, access boundaries, operating discipline, and the limits of public claims in one explicit place.', + 'Tenantial uses the Trust surface to explain tenant isolation, access boundaries, operating discipline, and the limits of public claims in one explicit place.', path: '/trust', }; diff --git a/apps/website/src/lib/site.ts b/apps/website/src/lib/site.ts index 6aab015d..35ec67fb 100644 --- a/apps/website/src/lib/site.ts +++ b/apps/website/src/lib/site.ts @@ -16,11 +16,11 @@ import type { } from '@/types/site'; export const siteMetadata: SiteMetadata = { - siteName: 'TenantAtlas', - siteTagline: 'Governance of record for Microsoft tenant operations.', + siteName: 'Tenantial', + siteTagline: 'Evidence-first governance for Microsoft tenants.', siteDescription: - 'TenantAtlas helps MSP and enterprise teams keep Microsoft tenant change history observable, reviewable, and safer to operate.', - siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantatlas.example', + 'Tenantial helps MSP and enterprise teams govern Microsoft tenant backup, restore, drift, evidence, audit trails, and structured reviews from one evidence-first surface.', + siteUrl: import.meta.env.PUBLIC_SITE_URL ?? 'https://tenantial.example', }; export const visualFoundationContract: VisualFoundationContract = { @@ -68,17 +68,17 @@ interface FooterGroupSeed { export const contactCta: CtaLink = { href: '/contact', - label: 'Request a working session', - helper: 'Bring your governance questions, rollout concerns, or evaluation goals.', + label: 'Book a demo', + helper: 'Bring tenant governance questions, restore concerns, or audit-readiness goals.', variant: 'primary', }; const footerLeadByFamily: Record = { landing: { - eyebrow: 'Keep the next move obvious', - title: 'Product, trust, progress, and contact should stay connected.', + eyebrow: 'Next step', + title: 'Build tenant governance on evidence, not assumptions.', description: - 'Landing pages should move visitors from orientation into product understanding, trust review, changelog proof, or the contact path without fake maturity signals.', + 'Use a focused demo conversation to map Tenantial against your Microsoft tenant backup, restore, drift, evidence, and review needs.', intent: 'conversion', primaryCta: contactCta, }, @@ -90,7 +90,7 @@ const footerLeadByFamily: Record = { intent: 'guidance', primaryCta: { href: '/contact', - label: 'Discuss trust requirements', + label: 'Book a demo', helper: 'Bring current review, legal, or rollout questions into one working conversation.', variant: 'primary', }, @@ -103,7 +103,7 @@ const footerLeadByFamily: Record = { intent: 'legal', primaryCta: { href: '/contact', - label: 'Continue the evaluation path', + label: 'Book a demo', helper: 'Move from the reading surface back into a product or trust conversation.', variant: 'primary', }, @@ -113,19 +113,19 @@ const footerLeadByFamily: Record = { const headerCtaByFamily: Record = { landing: { href: '/contact', - label: 'Request a working session', - helper: 'Bring your governance questions, rollout concerns, or evaluation goals.', + label: 'Book a demo', + helper: 'Discuss the tenant governance surface with the Tenantial team.', variant: 'secondary', }, trust: { href: '/contact', - label: 'Discuss trust requirements', + label: 'Book a demo', helper: 'Route trust, legal, or rollout questions into one conversation.', variant: 'secondary', }, content: { href: '/contact', - label: 'Start the contact path', + label: 'Book a demo', helper: 'Turn the reading path into a concrete next step.', variant: 'secondary', }, @@ -157,38 +157,55 @@ export async function getSurfaceAvailability(): Promise { } const primaryNavigationSeeds: CollectionGatedNavigationItem[] = [ - { href: '/product', label: 'Product', description: 'See how the product connects backup, restore, drift, and evidence.' }, - { href: '/trust', label: 'Trust', description: 'Review the operating posture and bounded public claims.' }, - { href: '/changelog', label: 'Changelog', description: 'Inspect dated product progress instead of placeholder content.' }, - { href: '/resources', label: 'Resources', description: 'Optional deeper content when substantive material exists.', collection: 'resources' }, - { href: '/contact', label: 'Contact', description: 'Move into a working session with one clear next step.' }, + { href: '/product', label: 'Platform', description: 'Explore the evidence-first governance surface.' }, + { href: '/solutions', label: 'Solutions', description: 'Review MSP and enterprise governance fit.' }, + { href: '/changelog', label: 'Resources', description: 'Use the changelog as the current public resource baseline.' }, + { href: '/contact', label: 'Pricing', description: 'Pricing is handled through a scoped demo conversation.' }, + { href: '/contact', label: 'Company', description: 'Contact is the current company and team introduction path.' }, ]; const footerNavigationGroupSeeds: FooterGroupSeed[] = [ { - title: 'Product', - items: [ - { href: '/product', label: 'Product' }, - { href: '/changelog', label: 'Changelog' }, - ], + title: 'Platform', + items: [{ href: '/product', label: 'Explore the platform' }], }, { - title: 'Trust & Legal', - items: [ - { href: '/trust', label: 'Trust' }, - { href: '/privacy', label: 'Privacy' }, - { href: '/imprint', label: 'Imprint' }, - { href: '/terms', label: 'Terms' }, - ], + title: 'Solutions', + items: [{ href: '/solutions', label: 'Solutions' }], + }, + { + title: 'Resources', + items: [{ href: '/changelog', label: 'Changelog' }], + }, + { + title: 'Pricing', + items: [{ href: '/contact', label: 'Book a demo' }], + }, + { + title: 'Company', + items: [{ href: '/contact', label: 'Contact' }], }, { title: 'Contact', items: [{ href: '/contact', label: 'Contact' }], }, { - title: 'Content', - collection: 'resources', - items: [{ href: '/resources', label: 'Resources', collection: 'resources' }], + title: 'Legal', + items: [ + { href: '/legal', label: 'Legal' }, + { href: '/imprint', label: 'Imprint' }, + ], + }, + { + title: 'Privacy', + items: [{ href: '/privacy', label: 'Privacy' }], + }, + { + title: 'Security', + items: [ + { href: '/trust', label: 'Trust' }, + { href: '/terms', label: 'Terms' }, + ], }, ]; diff --git a/apps/website/src/pages/index.astro b/apps/website/src/pages/index.astro index 28d816ab..42e5cbd6 100644 --- a/apps/website/src/pages/index.astro +++ b/apps/website/src/pages/index.astro @@ -1,87 +1,24 @@ --- import PageShell from '@/components/layout/PageShell.astro'; -import CapabilityGrid from '@/components/sections/CapabilityGrid.astro'; import CTASection from '@/components/sections/CTASection.astro'; -import LogoStrip from '@/components/sections/LogoStrip.astro'; -import OutcomeSection from '@/components/sections/OutcomeSection.astro'; +import FeaturePillars from '@/components/sections/FeaturePillars.astro'; import PageHero from '@/components/sections/PageHero.astro'; -import ProgressTeaser from '@/components/sections/ProgressTeaser.astro'; -import TrustGrid from '@/components/sections/TrustGrid.astro'; -import PrimaryCTA from '@/components/content/PrimaryCTA.astro'; -import Container from '@/components/primitives/Container.astro'; -import Section from '@/components/primitives/Section.astro'; -import { getRecentChangelogEntries } from '@/lib/changelog'; +import TrustBar from '@/components/sections/TrustBar.astro'; import { - homeCapabilities, homeCtaSection, - homeEcosystem, + homeFeaturePillars, homeHero, - homeOutcome, - homeProgressTeaser, homeSeo, - homeTrustSignals, + homeTrustBar, } from '@/content/pages/home'; - -const recentChangelog = await getRecentChangelogEntries(3); -const progressContent = { - ...homeProgressTeaser, - entries: recentChangelog.length > 0 ? recentChangelog : homeProgressTeaser.entries, -}; --- - + - - - - -
- -
- -
- -
-
-
-
- - {progressContent.entries.length > 0 && ( - - )} - {progressContent.entries.length === 0 && ( -
- -
-

- Follow product progress on the changelog. -

-
-
-
- )} +
{ +test('core IA publishes Tenantial homepage navigation without dead template routes', async ({ page }) => { await visitPage(page, '/'); const header = page.getByRole('banner'); const footer = page.getByRole('contentinfo'); - await expect(header.getByRole('link', { name: 'Resources' })).toHaveCount(0); - await expect(header.getByRole('link', { name: 'Solutions' })).toHaveCount(0); - await expect(header.getByRole('link', { name: 'Integrations' })).toHaveCount(0); + await expect(header.getByRole('link', { name: 'Platform', exact: true })).toHaveAttribute('href', '/product'); + await expect(header.getByRole('link', { name: 'Solutions', exact: true })).toHaveAttribute('href', '/solutions'); + await expect(header.getByRole('link', { name: 'Resources', exact: true })).toHaveAttribute('href', '/changelog'); + await expect(header.getByRole('link', { name: 'Pricing', exact: true })).toHaveAttribute('href', '/contact'); + await expect(header.getByRole('link', { name: 'Company', exact: true })).toHaveAttribute('href', '/contact'); + await expect(header.getByRole('link', { name: 'Book a demo', exact: true })).toHaveAttribute('href', '/contact'); + await expect(header.locator('[data-nav-state="deferred"]').filter({ hasText: 'Sign in' }).first()).toBeVisible(); await expect(header.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0); - await expect(footer.getByRole('link', { name: 'Resources' })).toHaveCount(0); + for (const group of ['Platform', 'Solutions', 'Resources', 'Pricing', 'Company', 'Contact', 'Legal', 'Privacy', 'Security']) { + await expect(footer.getByText(group, { exact: true }).first()).toBeVisible(); + } + await expect(footer.getByRole('link', { name: 'Articles' })).toHaveCount(0); await expect(footer.getByRole('link', { name: 'Security & Trust' })).toHaveCount(0); }); diff --git a/apps/website/tests/smoke/contact-legal.spec.ts b/apps/website/tests/smoke/contact-legal.spec.ts index 0d2b067d..85486968 100644 --- a/apps/website/tests/smoke/contact-legal.spec.ts +++ b/apps/website/tests/smoke/contact-legal.spec.ts @@ -26,7 +26,7 @@ test('contact page qualifies the conversation and keeps legal links reachable', name: 'Structure the first conversation before anyone shares sensitive context.', }), ).toBeVisible(); - await expect(page.getByRole('main').getByRole('link', { name: 'Email the TenantAtlas team' }).first()).toBeVisible(); + await expect(page.getByRole('main').getByRole('link', { name: 'Email the Tenantial team' }).first()).toBeVisible(); await expect(page.getByRole('main').getByRole('link', { name: 'Review the trust posture' }).first()).toBeVisible(); await expect(page.getByRole('main').getByRole('link', { name: 'Privacy' }).first()).toBeVisible(); await expect(page.getByRole('main').getByRole('link', { name: 'Terms' }).first()).toBeVisible(); @@ -77,9 +77,9 @@ test.describe('mobile navigation', () => { await visitPage(page, '/'); await openMobileNavigation(page); await expect(page.locator('[data-mobile-nav]').first()).toBeVisible(); - await expect(page.getByRole('banner').getByRole('link', { name: /Contact/ }).first()).toBeVisible(); - await expect(page.getByRole('banner').getByRole('link', { name: 'Trust' }).first()).toBeVisible(); - await expect(page.getByRole('banner').getByRole('link', { name: 'Changelog' }).first()).toBeVisible(); + await expect(page.getByRole('banner').getByRole('link', { name: 'Book a demo' }).first()).toBeVisible(); + await expect(page.getByRole('banner').getByRole('link', { name: 'Platform' }).first()).toBeVisible(); + await expect(page.getByRole('banner').getByRole('link', { name: 'Resources' }).first()).toBeVisible(); await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Privacy' })).toBeVisible(); await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Terms' })).toBeVisible(); await expect(page.getByRole('contentinfo').getByRole('link', { name: 'Imprint' })).toBeVisible(); diff --git a/apps/website/tests/smoke/home-product.spec.ts b/apps/website/tests/smoke/home-product.spec.ts index c5ac98f1..81332762 100644 --- a/apps/website/tests/smoke/home-product.spec.ts +++ b/apps/website/tests/smoke/home-product.spec.ts @@ -8,32 +8,59 @@ import { expectHomepageHeroOrder, expectHomepageHeroRouteTargets, expectHomepageHeroStructure, - expectHomepageHeroTrustSignals, expectHomepageHeroVisibleOnMobile, expectHomepageSectionOrder, expectMobileReadability, expectNavigationVsCtaDifferentiation, + expectNoBodyHorizontalOverflow, expectOnwardRouteReachable, expectPageFamily, - expectProductNearVisual, expectPrimaryNavigation, expectShell, visitPage, } from './smoke-helpers'; -test('home uses the landing foundation to explain the product category with one clear action hierarchy', async ({ - page, -}) => { +const forbiddenHomepageTerms = [ + 'AstroDeck', + 'Open Source', + 'MIT Licensed', + 'TenantCTRL', + 'TenantPilot', + 'TenantAtlas', +] as const; + +async function expectForbiddenHomepageResidueAbsent(page: import('@playwright/test').Page): Promise { + const body = page.locator('body'); + const metadata = await page.locator('head').evaluate((head) => { + const title = document.title; + const meta = Array.from(head.querySelectorAll('meta')) + .map((element) => element.getAttribute('content') ?? '') + .join(' '); + const canonical = head.querySelector('link[rel="canonical"]')?.getAttribute('href') ?? ''; + + return `${title} ${meta} ${canonical}`; + }); + + for (const term of forbiddenHomepageTerms) { + await expect(body).not.toContainText(new RegExp(term, 'i')); + expect(metadata, `Homepage metadata should not contain ${term}`).not.toMatch(new RegExp(term, 'i')); + } +} + +test('home first read positions Tenantial with one clear action hierarchy', async ({ page }) => { await visitPage(page, '/'); - await expectShell(page, /TenantAtlas/); + await expectShell(page, 'Evidence-first governance for Microsoft tenants.'); await expectPageFamily(page, 'landing'); await expectDisclosureLayer(page, '1'); await expectDisclosureLayer(page, '2'); await expectPrimaryNavigation(page); await expectNavigationVsCtaDifferentiation(page); await expectFooterLinks(page); - await expectCtaHierarchy(page, 'Request a working session', 'See the product model'); - await expect(page.getByRole('main').getByRole('link', { name: 'Request a working session' }).first()).toBeVisible(); + await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform'); + await expect(page.getByRole('main').getByRole('link', { name: 'Book a demo' }).first()).toBeVisible(); + await expect(page.getByRole('main').getByRole('link', { name: 'Explore the platform' }).first()).toBeVisible(); + await expect(page.getByRole('heading', { level: 1 })).toHaveCount(1); + await expectForbiddenHomepageResidueAbsent(page); const skipLink = page.getByRole('link', { name: 'Skip to content' }); @@ -41,102 +68,95 @@ test('home uses the landing foundation to explain the product category with one await expect(skipLink).toBeFocused(); }); -test('homepage hero explains the product with a product-near visual and outcome framing', async ({ - page, -}) => { - await visitPage(page, '/'); - await expectProductNearVisual(page); - await expect(page.locator('[data-section="outcome"]')).toBeVisible(); - await expect( - page.getByRole('heading', { name: /calmer operations|governance pain|operational risk/i }).first(), - ).toBeVisible(); - await expectCtaHierarchy(page, 'Request a working session', 'See the product model'); -}); - -test('homepage maintains required section order: outcome before capability before trust before progress before cta', async ({ - page, -}) => { - await visitPage(page, '/'); - await expectHomepageSectionOrder(page, ['outcome', 'capability', 'trust', 'progress', 'cta']); -}); - -test('homepage shows grouped capability clusters instead of a feature wall', async ({ - page, -}) => { - await visitPage(page, '/'); - await expect(page.locator('[data-section="capability"]')).toBeVisible(); - await expect( - page.getByRole('heading', { name: /product model|what TenantAtlas covers/i }), - ).toBeVisible(); -}); - -test('homepage shows explicit trust signals before the final CTA', async ({ - page, -}) => { - await visitPage(page, '/'); - await expect(page.locator('[data-section="trust"]')).toBeVisible(); - await expectOnwardRouteReachable(page, ['/trust']); -}); - -test('homepage hero makes the product category, text core, and one CTA pair explicit on first read', async ({ - page, -}) => { +test('homepage hero explains Tenantial, Microsoft tenant context, and required CTA routes', async ({ page }) => { await visitPage(page, '/'); await expectHomepageHeroStructure(page); - await expect(page.locator('[data-homepage-hero="true"] [data-hero-eyebrow]')).toContainText(/microsoft tenant governance/i); + await expect(page.locator('[data-homepage-hero="true"] [data-hero-eyebrow]')).toContainText( + /governance that earns trust/i, + ); await expect( page.locator('[data-homepage-hero="true"] [data-hero-heading]').getByRole('heading', { level: 1, - name: /one operating record for change history, drift review, and restore planning/i, + name: 'Evidence-first governance for Microsoft tenants.', }), ).toBeVisible(); await expect(page.locator('[data-homepage-hero="true"] [data-hero-supporting-copy]')).toContainText( - /security, endpoint, and platform teams use TenantAtlas to see what changed, preview restores, and move reviews forward/i, + /backup and restore with confidence\. detect drift before it becomes audit work\. preserve snapshot history/i, ); - await expectHomepageHeroCtaPair(page, /working session/i, /product model/i); + await expectHomepageHeroCtaPair(page, 'Book a demo', 'Explore the platform'); + await expectHomepageHeroRouteTargets(page, ['/contact', '/product']); }); -test('homepage hero keeps product-near proof and bounded trust cues inside the hero itself', async ({ +test('homepage uses neutral trust statements and the six required feature pillars', async ({ page }) => { + await visitPage(page, '/'); + await expect(page.locator('[data-section="trustbar"]')).toBeVisible(); + await expect(page.locator('[data-section="trustbar"]')).toContainText(/Microsoft tenant focused/i); + await expect(page.locator('[data-section="trustbar"]')).toContainText(/Evidence-oriented workflows/i); + await expect(page.locator('[data-section="trustbar"]')).toContainText(/Designed for audit review/i); + await expect(page.locator('[data-section="trustbar"]')).toContainText(/Operator-led governance/i); + + const pillars = page.locator('[data-section="feature-pillars"]'); + await expect(pillars).toBeVisible(); + + for (const capability of ['Backup', 'Restore', 'Drift Detection', 'Evidence', 'Audit Trail', 'Governance Reviews']) { + await expect(pillars.getByRole('heading', { name: capability, exact: true })).toBeVisible(); + } + + await expect(page.locator('body')).not.toContainText(/SOC 2|ISO\s?\d*|99\.9%|trusted by|customer logos/i); +}); + +test('homepage dashboard preview is static, responsive, and explains status without color alone', async ({ page, }) => { await visitPage(page, '/'); - await expectProductNearVisual(page, /change history, restore preview, and a review queue/i); - await expectHomepageHeroTrustSignals(page); - await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText( - /tenant-scoped boundaries/i, - ); - await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText( - /reviewable change history/i, - ); - await expect(page.locator('[data-homepage-hero="true"] [data-hero-trust-signals]')).toContainText( - /preview before restore/i, - ); + + const preview = page.locator('[data-dashboard-preview]').first(); + + await expect(preview).toBeVisible(); + await expect(preview).toContainText('Static demo preview'); + await expect(preview).toContainText('Demo values'); + await expect(preview).toContainText('92%'); + await expect(preview).toContainText('14'); + await expect(preview).toContainText('7'); + await expect(preview).toContainText('1,248'); + await expect(preview).toContainText('98%'); + + for (const panel of [ + 'Recent findings', + 'Drift timeline', + 'Backups and restores', + 'Governance reviews', + 'Evidence spotlight', + ]) { + await expect(preview.getByText(panel, { exact: true })).toBeVisible(); + } + + for (const status of ['Healthy', 'Review required', 'Critical', 'Review-ready']) { + await expect(preview.getByText(status, { exact: true }).first()).toBeVisible(); + } + + await expect(preview).not.toContainText(/real[- ]?time|streaming|live tenant feed|May 19|Updated 2m ago/i); }); -test('homepage shows dated progress signals before the final CTA', async ({ - page, -}) => { +test('homepage keeps the final CTA and launch-readiness sections in order', async ({ page }) => { await visitPage(page, '/'); - await expect(page.locator('[data-section="progress"]')).toBeVisible(); - await expectOnwardRouteReachable(page, ['/changelog']); -}); - -test('homepage routes into Product, Trust, Changelog, and Contact', async ({ - page, -}) => { - await visitPage(page, '/'); - await expectOnwardRouteReachable(page, ['/product', '/trust', '/changelog', '/contact']); + await expectHomepageSectionOrder(page, ['hero', 'trustbar', 'feature-pillars', 'cta']); + await expect(page.locator('[data-section="cta"]')).toContainText( + 'Build tenant governance on evidence, not assumptions.', + ); + await expectOnwardRouteReachable(page, ['/product', '/contact']); }); test.describe('homepage mobile', () => { test.use({ viewport: { width: 390, height: 844 } }); - test('homepage remains readable on narrow screens', async ({ page }) => { + test('homepage remains readable on narrow screens without body overflow', async ({ page }) => { await visitPage(page, '/'); await expectMobileReadability(page); - await expect(page.locator('[data-section="outcome"]')).toBeVisible(); - await expect(page.locator('[data-section="capability"]')).toBeVisible(); - await expect(page.locator('[data-section="trust"]')).toBeVisible(); + await expectNoBodyHorizontalOverflow(page); + await expect(page.locator('[data-section="trustbar"]')).toBeVisible(); + await expect(page.locator('[data-section="feature-pillars"]')).toBeVisible(); + await expect(page.locator('[data-dashboard-preview]').first()).toBeVisible(); }); test('homepage hero preserves meaning order and hero route intent on narrow screens', async ({ page }) => { @@ -146,8 +166,8 @@ test.describe('homepage mobile', () => { 'headline', 'supporting-copy', 'cta-pair', - 'product-near-visual', 'trust-subclaims', + 'product-near-visual', ]); await expectHomepageHeroVisibleOnMobile(page); await expectHomepageHeroRouteTargets(page, ['/contact', '/product']); diff --git a/apps/website/tests/smoke/smoke-helpers.ts b/apps/website/tests/smoke/smoke-helpers.ts index b9d96808..818d9b61 100644 --- a/apps/website/tests/smoke/smoke-helpers.ts +++ b/apps/website/tests/smoke/smoke-helpers.ts @@ -3,17 +3,32 @@ import { expect, type Page } from '@playwright/test'; export const coreRoutePaths = ['/', '/product', '/trust', '/changelog', '/contact', '/privacy', '/imprint'] as const; export const secondaryRoutePaths = ['/legal', '/terms', '/solutions', '/integrations'] as const; -export const primaryNavigationLabels = ['Product', 'Trust', 'Changelog', 'Contact'] as const; -export const hiddenPrimaryNavigationLabels = [ - 'Solutions', - 'Integrations', - 'Security & Trust', - 'Resources', - 'Articles', -] as const; +export const primaryNavigationLabels = ['Platform', 'Solutions', 'Resources', 'Pricing', 'Company'] as const; +export const hiddenPrimaryNavigationLabels = ['Product', 'Security & Trust', 'Articles'] as const; -export const footerLabels = ['Product', 'Changelog', 'Trust', 'Privacy', 'Imprint', 'Terms', 'Contact'] as const; -export const hiddenFooterLabels = ['Resources', 'Articles', 'Security & Trust', 'Contact / Demo'] as const; +export const footerGroupLabels = [ + 'Platform', + 'Solutions', + 'Resources', + 'Pricing', + 'Company', + 'Contact', + 'Legal', + 'Privacy', + 'Security', +] as const; +export const footerLabels = [ + 'Explore the platform', + 'Solutions', + 'Changelog', + 'Book a demo', + 'Contact', + 'Legal', + 'Imprint', + 'Privacy', + 'Trust', +] as const; +export const hiddenFooterLabels = ['Articles', 'Security & Trust', 'Contact / Demo'] as const; export async function visitPage(page: Page, path: string): Promise { await page.goto(path); @@ -39,26 +54,47 @@ export async function expectPageFamily(page: Page, family: 'content' | 'landing' export async function expectPrimaryNavigation(page: Page): Promise { const header = page.getByRole('banner'); + const expectedRoutes: Record<(typeof primaryNavigationLabels)[number], string> = { + Platform: '/product', + Solutions: '/solutions', + Resources: '/changelog', + Pricing: '/contact', + Company: '/contact', + }; for (const label of primaryNavigationLabels) { const link = header.getByRole('link', { name: label, exact: true }).first(); await expect(link).toBeVisible(); await expect(link).toHaveAttribute('data-nav-link'); + await expect(link).toHaveAttribute('href', expectedRoutes[label]); } for (const label of hiddenPrimaryNavigationLabels) { await expect(header.getByRole('link', { name: label, exact: true })).toHaveCount(0); } + + await expect(header.getByText('Sign in', { exact: true }).first()).toBeVisible(); + await expect(header.locator('[data-nav-state="deferred"]').filter({ hasText: 'Sign in' }).first()).toBeVisible(); + await expect(header.getByRole('link', { name: 'Book a demo', exact: true }).first()).toHaveAttribute( + 'href', + '/contact', + ); } export async function expectFooterLinks(page: Page): Promise { + const footer = page.getByRole('contentinfo'); + + for (const label of footerGroupLabels) { + await expect(footer.getByText(label, { exact: true }).first()).toBeVisible(); + } + for (const label of footerLabels) { - await expect(page.getByRole('contentinfo').getByRole('link', { name: label, exact: true })).toBeVisible(); + await expect(footer.getByRole('link', { name: label, exact: true }).first()).toBeVisible(); } for (const label of hiddenFooterLabels) { - await expect(page.getByRole('contentinfo').getByRole('link', { name: label, exact: true })).toHaveCount(0); + await expect(footer.getByRole('link', { name: label, exact: true })).toHaveCount(0); } } @@ -265,7 +301,7 @@ export async function expectNavigationVsCtaDifferentiation(page: Page): Promise< const header = page.getByRole('banner'); await expect(header.locator('[data-nav-link]').first()).toBeVisible(); - await expect(header.locator('[data-cta-weight="secondary"]').first()).toBeVisible(); + await expect(header.locator('[data-header-cta]').first()).toBeVisible(); } export async function expectHomepageSectionOrder(page: Page, sections: string[]): Promise { @@ -313,6 +349,17 @@ export async function expectMobileReadability(page: Page): Promise { await expect(page.getByRole('contentinfo')).toBeVisible(); } +export async function expectNoBodyHorizontalOverflow(page: Page): Promise { + const overflow = await page.evaluate(() => { + const documentElement = document.documentElement; + const body = document.body; + + return Math.max(documentElement.scrollWidth, body.scrollWidth) - documentElement.clientWidth; + }); + + expect(overflow, 'Page should not create body-level horizontal overflow').toBeLessThanOrEqual(1); +} + export async function expectOnwardRouteReachable(page: Page, routes: string[]): Promise { const main = page.getByRole('main'); diff --git a/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts b/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts index f569ba36..85bbc21b 100644 --- a/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts +++ b/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts @@ -13,13 +13,15 @@ test('representative pages route CTA, badge, surface, and input semantics throug page, }) => { await visitPage(page, '/'); - await expectShell(page, /control surface/i); + await expectShell(page, 'Evidence-first governance for Microsoft tenants.'); await expectPageFamily(page, 'landing'); await expectPrimaryNavigation(page); await expectNavigationVsCtaDifferentiation(page); - await expectCtaHierarchy(page, 'Request a working session', /product model/i); - await expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Request a working session' }).first()).toBeVisible(); + await expectCtaHierarchy(page, 'Book a demo', 'Explore the platform'); + await expect(page.locator('[data-interaction="button"]').filter({ hasText: 'Book a demo' }).first()).toBeVisible(); await expect(page.locator('[data-badge-tone]').first()).toBeVisible(); + await expect(page.locator('[data-shell-surface="header"]').first()).toHaveAttribute('data-visual-tone', 'dark'); + await expect(page.locator('[data-section="feature-pillars"] [data-surface="tenantial-pillar"]').first()).toBeVisible(); await visitPage(page, '/trust'); await expectShell(page, /trust posture|trust/i); diff --git a/specs/400-tenantial-homepage-visual-rebuild/checklists/requirements.md b/specs/400-tenantial-homepage-visual-rebuild/checklists/requirements.md new file mode 100644 index 00000000..0fcec2e8 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/checklists/requirements.md @@ -0,0 +1,36 @@ +# Specification Quality Checklist: Tenantial Homepage Visual Rebuild + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-05-17 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Validation iteration 1: Passed. The spec contains no clarification markers or template placeholders, keeps functional requirements focused on public homepage outcomes, and confines repo-specific validation language to the required testing impact section. +- Planning review correction: Approval class is exactly one class (`Core Enterprise`) per SPEC-GATE-001. +- Items marked incomplete require spec updates before `/speckit.clarify` or `/speckit.plan`. diff --git a/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml b/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml new file mode 100644 index 00000000..d5fdf1cb --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml @@ -0,0 +1,110 @@ +openapi: 3.1.0 +info: + title: Tenantial Public Homepage Route Contract + version: "400.0.0" + summary: Static route expectations for the Tenantial homepage rebuild. + description: > + This contract documents public static website route behavior for Spec 400. + It does not introduce a backend API, authentication flow, database contract, + Microsoft Graph integration, or platform runtime dependency. +servers: + - url: http://127.0.0.1:4321 + description: Local Astro website development server +paths: + /: + get: + summary: Render the Tenantial public homepage + operationId: renderTenantialHomepage + tags: + - public-website + responses: + "200": + description: Static HTML homepage for Tenantial + content: + text/html: + schema: + type: string + examples: + homepage: + summary: Required first-read content + value: "Tenantial - Evidence-first governance for Microsoft tenantsEvidence-first governance for Microsoft tenants." + x-route-kind: static-html + x-required-visible-content: + brand: Tenantial + headline: Evidence-first governance for Microsoft tenants. + primaryCta: Book a demo + secondaryCta: Explore the platform + requiredCapabilities: + - Backup + - Restore + - Drift Detection + - Evidence + - Audit Trail + - Governance Reviews + dashboardMetrics: + overallPosture: "92%" + findings: "14" + driftDetected: "7" + evidenceItems: "1,248" + backupStatus: "98%" + x-required-sections: + - Header + - Hero + - DashboardPreview + - TrustBar + - FeaturePillars + - CTASection + - Footer + x-forbidden-visible-or-metadata-content: + - AstroDeck + - Open Source + - MIT Licensed + - TenantCTRL + - TenantPilot + - TenantAtlas + x-trust-claim-boundary: + allowed: + - Microsoft tenant focused + - Evidence-oriented workflows + - Designed for audit review workflows + - Operator-led governance + forbidden-unless-verified: + - Real customer logos + - SOC 2 certification + - ISO certification + - 99.9% uptime + - Trusted by named real companies + x-accessibility-contract: + primaryHeadings: 1 + focusVisible: true + keyboardReachableNavigation: true + nonColorOnlyStatus: true + mobileBodyHorizontalOverflow: false + /contact: + get: + summary: Intentional destination for Book a demo + operationId: renderContactDestination + tags: + - public-website + responses: + "200": + description: Existing public contact route used as the demo destination + content: + text/html: + schema: + type: string + x-navigation-role: primary-homepage-cta-target + /product: + get: + summary: Intentional destination for Explore the platform + operationId: renderProductDestination + tags: + - public-website + responses: + "200": + description: Existing public product route used as the platform exploration destination + content: + text/html: + schema: + type: string + x-navigation-role: secondary-homepage-cta-target diff --git a/specs/400-tenantial-homepage-visual-rebuild/data-model.md b/specs/400-tenantial-homepage-visual-rebuild/data-model.md new file mode 100644 index 00000000..b298bae4 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/data-model.md @@ -0,0 +1,216 @@ +# Data Model: Tenantial Homepage Visual Rebuild + +This feature introduces no persisted database model, no runtime API schema, and no platform data contract. The models below describe static public website content that supports implementation and test planning. + +## Entity: Homepage Message + +**Purpose**: Defines the first-read Tenantial brand, product category, promise, and CTA hierarchy for `/`. + +**Fields**: + +- `brandName`: Must be `Tenantial`. +- `eyebrow`: Must communicate "GOVERNANCE THAT EARNS TRUST" or equivalent. +- `headline`: Must be "Evidence-first governance for Microsoft tenants." +- `supportingCopy`: Must mention backup, restore, drift detection, snapshot-backed audit context, evidence, and structured reviews. +- `primaryCta`: CTA with label `Book a demo` and an intentional destination. +- `secondaryCta`: CTA with label `Explore the platform` and an intentional destination. +- `trustBullets`: Up to three short trust cues. + +**Relationships**: + +- Owns one primary CTA and one secondary CTA. +- Appears before Dashboard Preview and supporting homepage sections. + +**Validation Rules**: + +- Exactly one primary page heading must be present. +- Public visible copy must not include old/template brand names. +- CTA hierarchy must remain primary `Book a demo`, secondary `Explore the platform`. + +**State Transitions**: None. + +## Entity: Header Navigation Item + +**Purpose**: Defines one visible public header label and its behavior. + +**Fields**: + +- `label`: One of Platform, Solutions, Resources, Pricing, Company, Sign in, Book a demo. +- `destination`: Existing route, known external URL, or no-link/de-emphasized text for unavailable sign-in. +- `priority`: Primary CTA, secondary utility, or navigation. +- `behavior`: Link, CTA, or inert/de-emphasized label. + +**Relationships**: + +- Header owns multiple navigation items. +- CTA items point visitors toward Contact or Product routes. + +**Validation Rules**: + +- Must not point to dead template routes. +- `Book a demo` must remain the primary visible action. +- `Sign in` must not imply implemented auth unless a real sign-in destination exists. + +**State Transitions**: None. + +## Entity: Static Dashboard Preview + +**Purpose**: Communicates product-near governance posture using static demo values. + +**Fields**: + +- `overallPosture`: Static demo value `92%`. +- `findings`: Static demo value `14`. +- `driftDetected`: Static demo value `7`. +- `evidenceItems`: Static demo value `1,248`. +- `backupStatus`: Static demo value `98%`. +- `panels`: Recent findings, drift timeline, backups and restores, reviews, evidence spotlight. +- `statusLabels`: Text labels that explain status meaning without relying on color only. + +**Relationships**: + +- Contains Dashboard Metrics and Dashboard Panels. +- Appears with or immediately after the Hero. + +**Validation Rules**: + +- Must not depend on backend/API/auth/tenant/platform data. +- Must not claim to be live or realtime. +- Must remain readable on desktop and usable on mobile without body-level horizontal overflow. +- Status meaning must be available through text, not only color. + +**State Transitions**: None. + +## Entity: Dashboard Metric + +**Purpose**: A single static value in the Dashboard Preview. + +**Fields**: + +- `label`: Human-readable metric name. +- `value`: Static demo value. +- `tone`: Healthy, warning, critical, evidence, or neutral. +- `description`: Short text explaining the metric. + +**Relationships**: + +- Belongs to Static Dashboard Preview. + +**Validation Rules**: + +- Tone must not be the only status signal. +- Value must be static and clearly demo-like. + +**State Transitions**: None. + +## Entity: Trust Statement + +**Purpose**: Provides credibility without fake proof. + +**Fields**: + +- `title`: Short credibility statement. +- `description`: One-sentence explanation. +- `claimType`: Product focus, workflow discipline, audit review workflow design, or operator governance. + +**Relationships**: + +- Appears inside Trust Bar. + +**Validation Rules**: + +- Must not use real customer logos unless verified. +- Must not claim SOC 2, ISO, 99.9% uptime, or named customer trust unless verified. +- Must not overstate security/compliance posture. + +**State Transitions**: None. + +## Entity: Feature Pillar + +**Purpose**: Describes one required Tenantial homepage capability. + +**Fields**: + +- `title`: Backup, Restore, Drift Detection, Evidence, Audit Trail, or Governance Reviews. +- `description`: Concise capability explanation. +- `iconLabel`: Meaningful accessible label or decorative status. +- `proofBoundary`: Copy note ensuring unsupported claims are avoided. + +**Relationships**: + +- Feature Pillars section contains exactly the required six capability pillars unless implementation explicitly keeps four to six cards while preserving all required capabilities. + +**Validation Rules**: + +- Required capability titles must be visible. +- Copy must be short and product-specific. +- Unsupported security/compliance promises are forbidden. + +**State Transitions**: None. + +## Entity: CTA Section + +**Purpose**: Repeats the homepage's primary next step near the bottom of the page. + +**Fields**: + +- `headline`: "Build tenant governance on evidence, not assumptions." or equivalent. +- `primaryCta`: `Book a demo`. +- `secondaryCta`: `Explore the platform`. + +**Relationships**: + +- Mirrors Homepage Message CTA hierarchy. + +**Validation Rules**: + +- CTA labels and destinations must match actual behavior. +- CTA text must not clip on mobile. + +**State Transitions**: None. + +## Entity: Footer Link Group + +**Purpose**: Provides public footer navigation without template residue. + +**Fields**: + +- `groupTitle`: Platform, Solutions, Resources, Pricing, Company, Contact, Legal, Privacy, or Security. +- `items`: Intentional links or route aliases to existing public pages. +- `brandName`: Tenantial. + +**Relationships**: + +- Footer owns multiple Footer Link Groups. + +**Validation Rules**: + +- Footer must not contain AstroDeck/template content or old public brand names. +- Legal/privacy/security links must point to existing routes where available. +- No fake social proof. + +**State Transitions**: None. + +## Entity: SEO Metadata + +**Purpose**: Defines public search/social metadata for the homepage. + +**Fields**: + +- `title`: `Tenantial - Evidence-first governance for Microsoft tenants`. +- `description`: Tenantial-specific summary of backup, restore, drift detection, audit trails, evidence, and structured reviews. +- `ogTitle`: Evidence-first governance for Microsoft tenants. +- `ogDescription`: Evidence-oriented governance positioning without unsupported certification, uptime, customer, or security guarantees. +- `canonicalPath`: `/`. + +**Relationships**: + +- Applies to Homepage Message and public route `/`. + +**Validation Rules**: + +- Must not include AstroDeck/template titles. +- Must not include old public brand names. +- Must not include unverified customer, certification, or uptime claims. + +**State Transitions**: None. diff --git a/specs/400-tenantial-homepage-visual-rebuild/plan.md b/specs/400-tenantial-homepage-visual-rebuild/plan.md new file mode 100644 index 00000000..987cab78 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/plan.md @@ -0,0 +1,201 @@ +# Implementation Plan: Tenantial Homepage Visual Rebuild + +**Branch**: `400-tenantial-homepage-visual-rebuild` | **Date**: 2026-05-17 | **Spec**: [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md) +**Input**: Feature specification from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md` + +**Status**: Implementation complete in this branch. This plan now records the bounded Spec 400 implementation slice and its validation path. + +## Summary + +Rebuild the public `/` homepage in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` so Tenantial is presented as a premium dark enterprise SaaS product for evidence-first Microsoft tenant governance. The implementation approach is a static Astro website slice: update homepage content, public shell/navigation/footer, website-local design tokens, a static product-near dashboard preview, SEO metadata, narrow public-page brand consistency, and Playwright smoke expectations without adding platform runtime, backend data, auth, database, Filament, Livewire, or Microsoft Graph coupling. + +## Technical Context + +**Language/Version**: TypeScript 5.9, Astro 6 static components, HTML, CSS +**Primary Dependencies**: 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 +**Storage**: N/A - static public website content only; no database or persisted runtime data +**Testing**: Playwright smoke tests under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` plus Astro static build +**Validation Lanes**: browser +**Target Platform**: Static public website generated by Astro and served by the website deployment container +**Project Type**: Web frontend static site in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` +**Performance Goals**: Static homepage renders without heavy third-party scripts, body-level horizontal overflow, clipped CTA text, or live-data dependencies; desktop and mobile first-read content remains usable and readable +**Constraints**: No platform/auth/API/database/Graph coupling; no fake customer proof, certifications, or uptime claims; Tailwind v4 conventions only; use existing workspace script names and `WEBSITE_PORT` behavior; dark page must maintain accessible focus, contrast, and non-color-only status meaning; dark visual direction must use the Spec 400 Obsidian/Ivory/Mint baseline: near-black page background, ivory primary text, muted warm gray secondary text, mint primary accent, amber warning, coral critical, violet evidence/review accents, and WCAG-readable contrast for text and controls +**Scale/Scope**: One public route (`/`) plus shared public shell elements used by that route: header, footer, SEO metadata, website-local styles/tokens, static homepage sections, homepage smoke tests, and minimal brand/SEO cleanup on existing public pages reachable from the global shell + +## UI / Surface Guardrail Plan + +- **Guardrail scope**: No operator-facing surface change. +- **Native vs custom classification summary**: N/A for Filament/admin surfaces. Public website components may be customized within `apps/website`. +- **Shared-family relevance**: None for operator-facing shared families. +- **State layers in scope**: Public website page and shell state only; no admin shell, detail state, URL-query state, tenant state, or operation state. +- **Audience modes in scope**: Public visitor, MSP buyer, enterprise IT buyer, security/compliance reviewer. +- **Decision/diagnostic/raw hierarchy plan**: Public marketing first-read content only; no operator diagnostics or support/raw evidence surfaces. +- **Raw/support gating plan**: N/A. +- **One-primary-action / duplicate-truth control**: Homepage keeps "Book a demo" as the primary CTA and "Explore the platform" as secondary; repeated CTA sections preserve this hierarchy. +- **Handling modes by drift class or surface**: N/A. +- **Repository-signal treatment**: Report-only for website smoke review; no guardrail exception. +- **Special surface test profiles**: N/A. +- **Required tests or manual smoke**: Homepage Playwright smoke coverage, metadata/template-residue assertions, mobile overflow check, desktop/mobile screenshot review. +- **Exception path and spread control**: None. +- **Active feature PR close-out entry**: Smoke Coverage. + +## Shared Pattern & System Fit + +- **Cross-cutting feature marker**: No for operator-facing systems; yes only in the public website sense that header/footer/site metadata may be updated for Tenantial. +- **Systems touched**: Public website app under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website`; homepage page, content, layout navigation/footer, styles, static visual component, existing public-page brand copy, and smoke tests. +- **Shared abstractions reused**: Existing website shell, layout, primitive/content/section components where they fit; existing Playwright smoke helper style. +- **New abstraction introduced? why?**: No runtime or domain abstraction. A page-local static dashboard preview component is acceptable as presentation, not shared product truth. +- **Why the existing abstraction was sufficient or insufficient**: Existing Astro/Tailwind/Playwright website structure is sufficient. Existing copy, light palette, TenantAtlas brand, and product visual direction are insufficient for the Tenantial dark enterprise homepage. +- **Bounded deviation / spread control**: Homepage-specific static preview and dark visual treatment stay in `apps/website`; no platform or admin design system changes. Existing internal workspace names such as `@tenantatlas/website` remain unchanged. + +## 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**: Existing runtime contracts remain unchanged. +- **Retained provider-specific semantics and why**: Marketing language may say "Microsoft tenants" because the public product positioning targets that environment. It must not change provider contracts, identifiers, compare semantics, governed-subject taxonomy, or runtime naming. +- **Bounded extraction or follow-up path**: None. + +## Constitution Check + +### Pre-Design Gate + +PASS. No unresolved gate failures. + +- **Spec Candidate Gate**: Passed in spec with exactly one approval class (`Core Enterprise`), approval score 11/12, and decision `approve`. +- **Inventory / snapshots / Graph / deterministic capabilities**: N/A. No platform inventory, snapshots, Graph calls, or capability resolver behavior. +- **Read/write separation**: PASS. Public static website change only; no write/change operation. +- **RBAC / workspace / tenant isolation**: N/A. Public homepage introduces no authorization, tenant route, workspace context, global search, or destructive action. +- **OperationRun / Ops-UX**: N/A. No queued, scheduled, remote, long-running, or operation-link behavior. +- **Data minimization**: PASS. Static demo preview data only; no secrets, tenant data, or live payloads. +- **Test governance**: PASS. Browser lane is explicit and scoped to homepage route/build behavior. +- **Proportionality / bloat**: PASS. No persisted entity, enum/status family, resolver, registry, interface, DTO layer, or cross-domain UI framework. +- **Shared pattern first**: PASS. Reuse website shell/primitives/test helpers where practical; no operator-facing shared path touched. +- **Provider boundary**: PASS. Microsoft tenant wording remains marketing positioning and does not enter platform-core contracts. +- **Filament / Livewire**: N/A. No Filament v5, Livewire v4, provider registration, global search, destructive action, or Filament asset behavior is touched. + +### Post-Design Gate + +PASS. Phase 0 and Phase 1 artifacts keep the feature static, bounded, and website-local. + +- `research.md` resolves technical choices without adding framework or runtime coupling. +- `data-model.md` models static content only and explicitly avoids persisted data. +- `contracts/public-homepage.yaml` documents public route expectations and does not define a backend API. +- `quickstart.md` uses existing website build/test commands and does not introduce Sail, Laravel, Filament, database, or platform setup. +- No `NEEDS CLARIFICATION` markers remain. + +## Test Governance Check + +- **Test purpose / classification by changed surface**: Browser for public homepage route, content, responsive behavior, metadata, and visual smoke. +- **Affected validation lanes**: browser. +- **Why this lane mix is the narrowest sufficient proof**: The behavior is user-visible static website rendering. Browser smoke tests and build catch route, content, responsive, and metadata regressions without database or platform setup. +- **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` +- **Fixture / helper / factory / seed / context cost risks**: None. No DB, session, workspace, tenant, provider, member, or seed setup. +- **Expensive defaults or shared helper growth introduced?**: No. Any smoke helper additions must remain website-only. +- **Heavy-family additions, promotions, or visibility changes**: Browser coverage is deliberate and homepage-scoped; no heavy-governance family. +- **Surface-class relief / special coverage rule**: N/A. +- **Closing validation and reviewer handoff**: Reviewers should verify no platform setup appears in website tests, no unverified trust claims appear, no old brand/template residue remains on the homepage or globally reachable public shell, and mobile body overflow is absent. +- **Budget / baseline / trend follow-up**: None expected. +- **Review-stop questions**: Stop if smoke tests require platform state, if the dashboard preview depends on live data, if unsupported trust claims appear, if hidden horizontal overflow remains, or if navigation points to stale template routes. +- **Escalation path**: document-in-feature. +- **Active feature PR close-out entry**: Smoke Coverage. +- **Why no dedicated follow-up spec is needed**: This is a contained homepage browser smoke update. A separate follow-up is only needed if the visual system expands into a cross-page public website framework. + +## Project Structure + +### Documentation (this feature) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/ +├── plan.md +├── research.md +├── data-model.md +├── quickstart.md +├── contracts/ +│ └── public-homepage.yaml +└── tasks.md # Created later by /speckit.tasks +``` + +### Source Code (repository root) + +```text +/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/ +├── src/ +│ ├── pages/ +│ │ └── index.astro +│ ├── content/pages/ +│ │ └── home.ts +│ ├── lib/ +│ │ ├── site.ts +│ │ └── seo.ts +│ ├── components/ +│ │ ├── layout/ +│ │ │ ├── Navbar.astro +│ │ │ ├── PageShell.astro +│ │ │ └── Footer.astro +│ │ ├── content/ +│ │ │ └── DashboardPreview.astro # expected new or renamed static preview +│ │ └── sections/ +│ │ ├── FeaturePillars.astro # expected new or adapted homepage section +│ │ ├── TrustBar.astro # expected new or adapted homepage section +│ │ └── CTASection.astro +│ ├── styles/ +│ │ ├── tokens.css +│ │ └── global.css +│ └── types/ +│ └── site.ts + ├── public/ +│ └── favicon.svg +└── tests/smoke/ + ├── changelog-core-ia.spec.ts + ├── home-product.spec.ts + ├── smoke-helpers.ts + └── visual-foundation-guardrails.spec.ts +``` + +**Structure Decision**: Use the existing Astro website app and existing smoke-test family. The implementation should prefer adapting existing content/layout/primitives before adding new components. Any new dashboard or section component must remain website-local presentation and must not become a shared platform/admin abstraction. + +## Complexity Tracking + +| Violation | Why Needed | Simpler Alternative Rejected Because | +|-----------|------------|-------------------------------------| +| None | N/A | N/A | + +## Proportionality Review + +- **Current operator problem**: Prospective buyers and stakeholders cannot currently understand Tenantial's Microsoft tenant governance value from the public homepage. +- **Existing structure is insufficient because**: Current homepage content, brand naming, light visual direction, and product visual do not match the premium dark Tenantial direction or required evidence-first story. +- **Narrowest correct implementation**: One static homepage slice plus the public shell/styling/smoke checks needed for that page. +- **Ownership cost created**: Website-local homepage content, static preview data, navigation mapping, public SEO metadata, and smoke assertions must be maintained. +- **Alternative intentionally rejected**: Full multi-page website rebuild or platform-coupled product data preview. +- **Release truth**: Current public website direction only. + +## Phase 0: Research Output + +Completed in [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/research.md). All planning unknowns are resolved. + +## Phase 1: Design And Contracts Output + +Completed artifacts: + +- [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md) +- [contracts/public-homepage.yaml](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml) +- [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md) + +Agent context update command: + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website +.specify/scripts/bash/update-agent-context.sh codex +``` diff --git a/specs/400-tenantial-homepage-visual-rebuild/quickstart.md b/specs/400-tenantial-homepage-visual-rebuild/quickstart.md new file mode 100644 index 00000000..ce10d57d --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/quickstart.md @@ -0,0 +1,73 @@ +# Quickstart: Tenantial Homepage Visual Rebuild + +## Prerequisites + +- Work from `/Users/ahmeddarrazi/Documents/projects/wt-website`. +- Use Node.js 20+ with Corepack and pnpm, matching the repo root `packageManager`. +- This feature does not require Sail, Laravel, Filament, a database, Microsoft Graph credentials, or platform auth. + +## Install Dependencies + +```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 `http://127.0.0.1:4321/`. + +## Build Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website +corepack pnpm build:website +``` + +Expected result: Astro builds the static website without requiring platform services. + +## Browser Smoke Validation + +```bash +cd /Users/ahmeddarrazi/Documents/projects/wt-website +WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke +``` + +Expected proof: + +- `/` renders the Tenantial homepage. +- The headline "Evidence-first governance for Microsoft tenants." is visible. +- `Book a demo` and `Explore the platform` are visible with the correct hierarchy. +- Static dashboard preview content is visible. +- Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews are visible. +- Old/template public copy is absent from visible homepage content and metadata. +- Existing public pages reachable from the global shell use Tenantial visible brand copy and SEO metadata. +- Mobile viewport has no body-level horizontal overflow. + +## Manual Review + +Use desktop and mobile browser review after implementation. + +Review points: + +- Tenantial is the only public brand visible on the homepage. +- No AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, or TenantAtlas homepage residue remains. +- No unverified customer logos, certification claims, uptime claims, or fake social proof appear. +- Dashboard preview labels use static/sample/demo wording and do not show future-dated timestamps. +- Header labels and CTA destinations are intentional. +- Dashboard preview is static and does not imply live/realtime data. +- Text does not clip, overlap, or create mobile body overflow. +- Focus states are visible and mobile navigation is keyboard usable. + +## Out Of Scope For This Feature + +- Do not add demo-booking backend behavior. +- Do not add sign-in/auth behavior. +- Do not add platform API calls. +- Do not add Laravel, Filament, Livewire, Microsoft Graph, database, queue, or OperationRun behavior. +- Do not rename root packages or existing workspace scripts. diff --git a/specs/400-tenantial-homepage-visual-rebuild/research.md b/specs/400-tenantial-homepage-visual-rebuild/research.md new file mode 100644 index 00000000..02156e31 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/research.md @@ -0,0 +1,94 @@ +# Phase 0 Research: Tenantial Homepage Visual Rebuild + +## Decision: Keep the implementation inside the existing Astro website app + +**Decision**: Use `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` as the only runtime surface for this feature. + +**Rationale**: The feature is a public static homepage rebuild. The repo already has a standalone Astro website app with static output, a public route structure, shared layout components, SEO helpers, Tailwind v4 styling, and Playwright smoke tests. This matches the feature without platform runtime setup. + +**Alternatives considered**: + +- Add platform/Laravel/Filament integration: rejected because the spec forbids platform coupling and this is public marketing content. +- Create a separate website app: rejected because an Astro website app already exists. +- Use a CMS/content collection strategy now: rejected because the spec explicitly excludes CMS and full content architecture. + +## Decision: Update website-local brand metadata and public brand copy from TenantAtlas to Tenantial + +**Decision**: Treat Tenantial as the public brand for the homepage and update homepage-visible copy, SEO metadata, header/footer labels, smoke expectations, and narrow brand/SEO copy on existing public pages reachable from the global shell accordingly. + +**Rationale**: The current website content and `siteMetadata` still identified the public brand as TenantAtlas. The spec requires Tenantial to be the only visible public brand on the homepage and requires old/template brand residue to be absent. Because the updated header/footer route visitors to existing public pages, those pages also need minimal brand consistency cleanup so the website does not expose mixed public-brand SEO/copy. + +**Alternatives considered**: + +- Leave shared metadata as TenantAtlas and override only the homepage: rejected because header/footer/OG metadata would still leak the old public brand on the homepage. +- Rename root packages and internal workspace names: rejected because the spec requires internal package names and workspace scripts to remain unchanged. +- Redesign secondary public pages: rejected because Spec 400 is a homepage implementation slice; secondary pages receive only narrow brand/SEO consistency cleanup. + +## Decision: Use Tailwind CSS v4 CSS-first tokens and existing global styles + +**Decision**: Implement the premium dark visual direction by adapting `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`, using Tailwind v4-compatible CSS-first `@theme` tokens and existing utility patterns. + +**Rationale**: The app already imports Tailwind with `@import "tailwindcss"` and defines tokens with `@theme`. This matches project and Tailwind v4 conventions and avoids deprecated Tailwind v3 utilities. + +**Alternatives considered**: + +- Add `tailwind.config.js`: rejected because Tailwind v4 in this project is CSS-first. +- Add a one-off inline style block for all homepage visuals: rejected because homepage, header, footer, and CTA surfaces need consistent website-local tokens. +- Add a heavy visual library: rejected because the spec requires no heavy third-party scripts for static visuals. + +## Decision: Build the dashboard preview as static semantic HTML/CSS, not a raster screenshot or live data + +**Decision**: Implement a static product-near dashboard preview with fixed demo values and semantic labels. + +**Rationale**: The spec requires the preview to be sharp, responsive, and clearly static. It must not imply live product data or depend on backend/API/auth/tenant data. HTML/CSS keeps the preview accessible, inspectable, responsive, and testable. + +**Alternatives considered**: + +- Raster screenshot: rejected because the spec asks for HTML/CSS/SVG and because text/status assertions would be weaker. +- Live dashboard data: rejected because the spec forbids backend/API/platform coupling. +- Interactive preview controls: rejected because they could imply functional product behavior that is not implemented. + +## Decision: Map homepage navigation to existing intentional routes and de-emphasize sign-in + +**Decision**: Use existing routes for visible CTAs where possible: `Book a demo` -> `/contact`, `Explore the platform` -> `/product`, `Platform` -> `/product`, and `Solutions` -> `/solutions`. `Resources`, `Pricing`, `Company`, and `Sign in` currently have no implemented same-name routes; they must be intentionally handled through existing routes, disabled/de-emphasized treatment, or omitted from functional link behavior instead of silently pointing at dead routes. `Sign in` must be text-only/de-emphasized or point only to a known valid platform URL if one is confirmed during implementation. + +**Rationale**: The current website has `/product`, `/solutions`, `/contact`, `/trust`, `/changelog`, `/integrations`, and legal routes, but no `/pricing`, `/company`, `/resources`, or known sign-in route. `/resources` is currently gated off, and existing tests expect it to stay hidden until substantive material exists. The spec requires intentional links and forbids silent dead template routes or implying implemented auth. + +**Alternatives considered**: + +- Create secondary pages now: rejected because the spec excludes a full multi-page website build. +- Link to missing future routes: rejected because the spec requires intentional, non-dead routes. +- Hide all unavailable labels: rejected because the spec expects the desktop header to carry the named navigation labels. + +## Decision: Use Playwright smoke coverage plus static build as validation + +**Decision**: Validate with `corepack pnpm build:website` and the existing website Playwright smoke lane. + +**Rationale**: This change is user-visible static website behavior. Browser smoke tests can verify route rendering, brand/copy, CTA hierarchy, forbidden copy, responsive overflow, metadata, and accessibility-relevant structure without database or platform setup. + +**Alternatives considered**: + +- Laravel/Pest tests: rejected because no Laravel/platform behavior is changed. +- Unit tests for every presentation component: rejected because this would over-test thin static presentation and add maintenance without proving user value. +- Visual regression infrastructure: deferred because this spec only requires browser screenshot review, not a permanent screenshot regression system. + +## Decision: No backend API contract is introduced + +**Decision**: The contract artifact documents the public static route expectations instead of defining a backend API. + +**Rationale**: The homepage has user actions such as opening `/` and following links, but no data submission, backend mutation, authentication, or API response. A route contract gives reviewers a concrete acceptance surface while preserving the spec's no-backend boundary. + +**Alternatives considered**: + +- Generate REST endpoints for demo booking or sign-in: rejected because those flows are out of scope. +- Skip contracts entirely: rejected because the `/speckit.plan` workflow requires a contracts artifact. + +## Decision: No unresolved clarifications remain + +**Decision**: All open implementation choices have reasonable defaults from the spec. + +**Rationale**: The feature has defaults for brand, CTA destinations, logo availability, customer proof, static preview content, and secondary pages. None require user clarification before planning. + +**Alternatives considered**: + +- Ask whether `/pricing`, `/company`, or `/resources` pages should be built: rejected because the spec's non-goals and assumptions exclude secondary pages for this slice. diff --git a/specs/400-tenantial-homepage-visual-rebuild/spec.md b/specs/400-tenantial-homepage-visual-rebuild/spec.md new file mode 100644 index 00000000..fcc5e214 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/spec.md @@ -0,0 +1,260 @@ +# Feature Specification: Tenantial Homepage Visual Rebuild + +**Feature Branch**: `400-tenantial-homepage-visual-rebuild` +**Created**: 2026-05-17 +**Status**: Implemented (ready to merge) +**Input**: User description: "Rebuild the public website homepage for Tenantial to match a premium dark enterprise SaaS mockup direction, positioning Tenantial as evidence-first governance for Microsoft tenants." + +## Spec Candidate Check *(mandatory - SPEC-GATE-001)* + +- **Problem**: The public homepage still carries generic SaaS/template signals and does not clearly position Tenantial as a serious Microsoft tenant governance product. +- **Today's failure**: Visitors can leave without understanding that Tenantial focuses on backup, restore, drift detection, evidence, audit trails, and structured governance reviews for Microsoft tenants. +- **User-visible improvement**: The homepage immediately communicates Tenantial's evidence-first governance value, uses a distinct premium dark identity, and shows a product-near static dashboard preview without implying live data. +- **Smallest enterprise-capable version**: Rebuild only the public homepage and the shared public shell needed by that page: header, hero, static dashboard preview, trust bar, feature pillars, call to action, footer, SEO metadata, homepage smoke expectations, and minimal public-page brand consistency where globally reachable pages would otherwise mix Tenantial and TenantAtlas. +- **Explicit non-goals**: No platform UI, no Filament/admin changes, no authentication flow, no CMS, no backend data, no demo-booking backend, no database changes, no Microsoft Graph integration, and no full multi-page website build. +- **Permanent complexity imported**: Public homepage brand direction, static marketing content, a static product-preview concept, homepage smoke coverage, and brand/SEO cleanup expectations. +- **Why now**: The project needs one credible Tenantial homepage direction before expanding into platform, pricing, trust, resources, or company pages. +- **Why not local**: Page-local copy changes alone would leave brand hierarchy, navigation intent, trust boundaries, product-preview language, and template cleanup inconsistent. +- **Approval class**: Core Enterprise +- **Red flags triggered**: None. The work creates no persisted truth, no domain state, no runtime abstraction, and no cross-domain UI framework. +- **Score**: Nutzen: 2 | Dringlichkeit: 2 | Scope: 2 | Komplexitaet: 2 | Produktnaehe: 2 | Wiederverwendung: 1 | **Gesamt: 11/12** +- **Decision**: approve + +## Spec Scope Fields *(mandatory)* + +- **Scope**: Public website homepage. This is outside workspace, tenant, and canonical admin scopes. +- **Primary Routes**: `/` +- **Data Ownership**: Static public website content and static marketing preview data only. No workspace-owned, tenant-owned, platform runtime, or persisted application data is introduced. +- **RBAC**: None. The homepage is public and introduces no authorization behavior. + +## Relationship To Existing Website Specs + +- Spec 223 remains the historical AstroDeck reset/rebuild frame. +- Spec 214 remains useful background for website visual discipline. +- Spec 215 remains useful for public information architecture principles. +- Spec 217 and Spec 218 remain background for homepage structure and hero discipline. +- Spec 400 is the concrete homepage implementation slice for the Tenantial mockup direction. +- Spec 400 owns the homepage implementation direction where the Tenantial mockup direction is more specific than the older AstroDeck-era homepage specs. +- Spec 400 permits website-local homepage presentation components for this slice only. It does not create a reusable public-site framework and does not broaden the AstroDeck reset into platform/admin code. +- No `specs/227-*` artifact is present in the current checkout. No phantom Spec 227 is created here; Spec 400 replaces the previously discussed homepage/visual-foundation path for the homepage only. + +## Cross-Cutting / Shared Pattern Reuse *(mandatory when the feature touches notifications, status messaging, action links, header actions, dashboard signals/cards, alerts, navigation entry points, evidence/report viewers, or any other existing shared operator interaction family; otherwise write `N/A - no shared interaction family touched`)* + +N/A - no shared operator interaction family touched. This feature changes a public marketing homepage, not admin notifications, operator status messaging, operation links, dashboard signals, alerts, report viewers, or other shared operator interaction contracts. + +## OperationRun UX Impact *(mandatory when the feature creates, queues, deduplicates, resumes, blocks, completes, or deep-links to an `OperationRun`; otherwise write `N/A - no OperationRun start or link semantics touched`)* + +N/A - no OperationRun start, completion, deduplication, resume, block, or link semantics touched. + +## Provider Boundary / Platform Core Check *(mandatory when the feature changes shared provider/platform seams, identity scope, governed-subject taxonomy, compare strategy selection, provider connection descriptors, or operator vocabulary that may leak provider-specific semantics into platform-core truth; otherwise write `N/A - no shared provider/platform boundary touched`)* + +N/A - no shared provider/platform boundary touched. The homepage may describe Microsoft tenant governance at a marketing level, but it does not change platform-core contracts, provider seams, identity scope, compare strategy, governed-subject taxonomy, or runtime vocabulary. + +## UI / Surface Guardrail Impact *(mandatory when operator-facing surfaces are changed; otherwise write `N/A`)* + +N/A - no operator-facing surface change. This feature changes a public marketing surface only and does not affect Filament/admin pages, tenant-scoped operator workflows, shared operator components, action surfaces, or governance decision surfaces. + +## Proportionality Review *(mandatory when structural complexity is introduced)* + +- **New source of truth?**: Yes, limited to public homepage brand/content direction. No product runtime or tenant-governance truth is introduced. +- **New persisted entity/table/artifact?**: No. +- **New abstraction?**: No runtime abstraction. Any page components are website-local presentation pieces for this homepage slice. +- **New enum/state/reason family?**: No. +- **New cross-domain UI framework/taxonomy?**: No. +- **Current operator problem**: Prospective buyers and stakeholders cannot yet infer Tenantial's Microsoft tenant governance value from the public homepage. +- **Existing structure is insufficient because**: The current website direction still contains template residue and does not provide a focused brand, trust, and evidence story for Tenantial. +- **Narrowest correct implementation**: One homepage slice with static content, static preview data, clear CTA hierarchy, trust boundaries, accessibility/responsive expectations, SEO cleanup, and smoke coverage. +- **Ownership cost**: Homepage copy, static preview content, public navigation labels, and brand/SEO expectations must be maintained as later website pages are added. +- **Alternative intentionally rejected**: Rebuilding the full website now. That would expand scope before the homepage direction is proven. +- **Release truth**: Current public website direction, not platform runtime truth or future product data truth. + +### Compatibility posture + +This feature assumes a pre-production environment. + +Backward compatibility for old public template pages, old TenantAtlas/TenantPilot public copy, deleted demo routes, and template homepage content is out of scope unless explicitly required by a later spec. Public pages reachable from the updated header/footer may receive narrow brand/SEO copy cleanup so the website does not expose a mixed Tenantial/TenantAtlas public brand. + +Internal package names, workspace scripts, and platform runtime contracts must not be renamed by this feature. In particular, the internal workspace package name `@tenantatlas/website` remains unchanged. + +## Testing / Lane / Runtime Impact *(mandatory for runtime behavior changes)* + +- **Test purpose / classification**: Browser. +- **Validation lane(s)**: browser. +- **Why this classification and these lanes are sufficient**: The feature changes a public static website route, visual hierarchy, responsive behavior, copy, and SEO metadata. Browser smoke checks and screenshot review are the narrowest honest proof. +- **New or expanded test families**: Homepage website smoke coverage only. +- **Fixture / helper cost impact**: None. No database, workspace, membership, provider, session, seed, or platform setup should be required. +- **Heavy-family visibility / justification**: Browser coverage is intentional and scoped to the homepage shell, visual behavior, and template-residue checks. +- **Special surface test profile**: N/A. +- **Standard-native relief or required special coverage**: Homepage smoke checks plus desktop/mobile visual review. +- **Reviewer handoff**: Reviewers must confirm that browser coverage remains website-scoped, no hidden platform setup is introduced, and the proof checks visible content, metadata cleanup, and mobile overflow. +- **Budget / baseline / trend impact**: None expected beyond a small homepage smoke suite. +- **Escalation needed**: document-in-feature. +- **Active feature PR close-out entry**: Smoke Coverage. +- **Planned validation commands**: repository website build command; website browser smoke test command; `git diff --check`; desktop and mobile browser review when screenshots are intentionally captured. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Understand Tenantial Immediately (Priority: P1) + +A first-time visitor opens the homepage and understands that Tenantial is an evidence-first governance platform for Microsoft tenants, not a generic SaaS template. + +**Why this priority**: This is the minimum viable homepage outcome. If the first viewport does not establish the brand, category, and core promise, the rebuild fails. + +**Independent Test**: Can be tested by opening `/` and verifying that the first viewport shows the Tenantial brand, the evidence-first governance headline, Microsoft tenant context, and primary/secondary CTAs. + +**Acceptance Scenarios**: + +1. **Given** a first-time visitor, **When** they open `/`, **Then** they see Tenantial as the public brand and the headline "Evidence-first governance for Microsoft tenants." +2. **Given** a visitor scanning the hero, **When** they read the supporting copy, **Then** they can identify backup, restore, drift detection, snapshot-backed audit context, evidence, and structured reviews as the product focus. +3. **Given** a visitor looking for the next step, **When** they inspect the hero actions, **Then** "Book a demo" is the primary CTA and "Explore the platform" is secondary. + +--- + +### User Story 2 - Evaluate Trust Without False Proof (Priority: P2) + +An IT, MSP, security, or compliance stakeholder reviews the homepage and sees credible trust positioning without fake customer logos, unsupported certifications, or exaggerated availability/security claims. + +**Why this priority**: Tenantial operates in a governance and evidence domain; credibility depends on restrained claims and clear proof boundaries. + +**Independent Test**: Can be tested by reviewing the trust bar, feature pillars, static preview, and footer for allowed claims and absence of unverified social proof. + +**Acceptance Scenarios**: + +1. **Given** no verified customer logos are available, **When** the trust bar renders, **Then** it uses neutral credibility statements rather than named real customer logos. +2. **Given** no verified security certifications are provided, **When** the homepage renders, **Then** it does not claim SOC 2, ISO certification, uptime guarantees, or "trusted by" proof. +3. **Given** a stakeholder reviewing capabilities, **When** they reach the feature pillars, **Then** Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews are all present with concise descriptions. + +--- + +### User Story 3 - Inspect A Product-Near Preview (Priority: P3) + +A visitor sees a product-near dashboard preview that communicates posture, findings, drift, evidence, backup status, reviews, and recent activity while clearly remaining static marketing content. + +**Why this priority**: The mockup direction depends on a strong preview surface, but false live-data implications would undermine trust. + +**Independent Test**: Can be tested by checking that the dashboard preview is visible, readable, responsive, and framed as static demo content. + +**Acceptance Scenarios**: + +1. **Given** the homepage has loaded, **When** a visitor views the preview, **Then** they see static demo values for overall posture, findings, drift detected, evidence items, and backup status. +2. **Given** the preview contains status information, **When** colors are removed or unavailable, **Then** labels or text still communicate the status meaning. +3. **Given** a visitor is on mobile, **When** they reach the preview, **Then** the page remains usable without body-level horizontal overflow. + +--- + +### User Story 4 - Verify Public Launch Readiness (Priority: P4) + +A website owner reviews the homepage before launch and can confirm that old template residue, wrong brand names, broken CTAs, and unsupported promises are absent. + +**Why this priority**: The rebuild should establish a clean baseline before later public pages reuse the direction. + +**Independent Test**: Can be tested through homepage smoke checks, metadata review, link review, and desktop/mobile visual review. + +**Acceptance Scenarios**: + +1. **Given** the homepage is ready for review, **When** the visible copy and metadata are checked, **Then** AstroDeck/template positioning and old public brand names are absent. +2. **Given** a reviewer inspects navigation, **When** they follow homepage CTAs and nav links, **Then** links are intentional and do not silently point to dead template routes. +3. **Given** a reviewer checks desktop and mobile, **When** the page is visually reviewed, **Then** text does not clip, CTAs remain visible, and the header adapts to the viewport. + +### Edge Cases + +- If dedicated destination pages do not yet exist, homepage links must still be intentional and must not point to stale template routes. +- If no real logo asset is available, the homepage may use a simple Tenantial mark that does not imply a third-party endorsement. +- If no real customer proof is available, the trust bar must use neutral product-focus statements instead of fake logos or named companies. +- If a visitor uses a narrow mobile viewport, the hero, CTA text, header, and dashboard preview must not create body-level horizontal overflow. +- If motion reduction is preferred by the visitor, decorative movement must not be required to understand the page. +- If status colors are not distinguishable, status labels and text must still communicate meaning. +- If platform authentication is not implemented or no sign-in destination is known, homepage sign-in treatment must not imply a completed login flow. + +## Requirements *(mandatory)* + +**Constitution alignment (required):** This feature introduces no Microsoft Graph calls, no write/change behavior, no queued or scheduled work, no OperationRun, no AuditLog changes, no tenant isolation changes, and no platform runtime behavior. + +**Constitution alignment (PROP-001 / ABSTR-001 / PERSIST-001 / STATE-001 / BLOAT-001):** This feature introduces no persisted product truth, no domain abstraction, no enum/status/reason family, and no cross-domain UI framework. The only new source of truth is the public homepage brand/content direction, bounded to the website. + +**Constitution alignment (XCUT-001):** This feature does not touch shared operator interaction families. Public website navigation and marketing preview elements must remain website-local. + +**Constitution alignment (DECIDE-AUD-001 / OPSURF-001):** This feature does not change admin detail/status surfaces, audience modes, support/raw evidence disclosure, or operator next-action surfaces. + +**Constitution alignment (PROV-001):** This feature does not change provider/platform seams. Microsoft tenant language is marketing positioning only and must not create runtime provider coupling. + +**Constitution alignment (TEST-GOV-001):** Browser coverage is intentional, homepage-scoped, and must not introduce database, workspace, provider, membership, session, or platform defaults. + +**Constitution alignment (OPS-UX / OPS-UX-START-001):** N/A - no OperationRun behavior is created, queued, completed, deduplicated, resumed, blocked, or linked. + +**Constitution alignment (RBAC-UX):** N/A - no authorization plane, capability, membership, global search, mutation, or destructive action behavior changes. + +**Constitution alignment (BADGE-001):** N/A - no admin status badge semantics change. Public marketing status labels in the static preview must be descriptive and not become a shared status taxonomy. + +**Constitution alignment (UI-FIL-001):** N/A - no Filament, Blade admin, Livewire, Resource, RelationManager, or Page changes. + +**Constitution alignment (UI-NAMING-001 / DECIDE-001 / UI surface rules):** N/A for operator-facing naming and action-surface contracts. Public homepage copy must still use calm, precise, user-facing language and avoid implementation-first labels. + +### Functional Requirements + +- **FR-001**: `/` MUST render a Tenantial-specific public homepage. +- **FR-002**: The homepage MUST present Tenantial as an evidence-first governance platform for Microsoft tenants. +- **FR-003**: The homepage MUST use a premium dark enterprise visual direction with high-contrast text and restrained accent colors. +- **FR-004**: The homepage MUST show the headline "Evidence-first governance for Microsoft tenants." +- **FR-005**: The homepage MUST include supporting copy that communicates backup, restore, drift detection, snapshot-backed audit context, verifiable evidence, and structured governance reviews. +- **FR-006**: "Book a demo" MUST be visible as the primary CTA. +- **FR-007**: "Explore the platform" MUST be visible as the secondary CTA. +- **FR-008**: CTA and navigation destinations MUST be intentional and MUST NOT silently point to dead template routes. +- **FR-009**: If no implemented sign-in destination is known, "Sign in" MUST NOT be presented as a prominent functional promise. +- **FR-010**: The desktop header MUST include Tenantial branding and navigation labels for Platform, Solutions, Resources, Pricing, Company, Sign in, and Book a demo. +- **FR-011**: The homepage MUST include a static product-near dashboard preview. +- **FR-012**: The dashboard preview MUST be clearly static marketing/demo content and MUST NOT depend on backend, API, authentication, tenant, or platform data. +- **FR-013**: The dashboard preview MUST show static demo values for overall posture, findings, drift detected, evidence items, and backup status. +- **FR-014**: The dashboard preview MUST include supporting areas for recent findings, drift timeline, backups and restores, reviews, and evidence spotlight. +- **FR-015**: The homepage MUST include a trust bar using neutral credibility statements such as Microsoft tenant focus, evidence-oriented workflows, audit review workflow design, and operator-led governance. +- **FR-016**: The trust bar MUST NOT use real customer logos, certification claims, uptime claims, "trusted by" statements, or security/compliance promises unless independently verified. +- **FR-017**: The feature pillars MUST include Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews. +- **FR-018**: Feature pillar copy MUST remain concise, product-specific, and free of unsupported compliance or security promises. +- **FR-019**: The homepage MUST include a final CTA section with "Build tenant governance on evidence, not assumptions." or equivalent wording with the same meaning. +- **FR-020**: The homepage footer MUST include Tenantial branding and footer link groups for Platform, Solutions, Resources, Pricing, Company, Contact, Legal, Privacy, and Security. +- **FR-021**: Visible homepage copy MUST NOT contain AstroDeck, template demo positioning, Open Source, MIT Licensed, TenantCTRL, TenantPilot, or TenantAtlas as public brand names. +- **FR-022**: Homepage SEO metadata MUST be Tenantial-specific and MUST describe evidence-first governance for Microsoft tenants. +- **FR-023**: Homepage metadata MUST NOT contain AstroDeck/template titles, old public brand names, fake customer proof, or unverified certification claims. +- **FR-024**: The homepage MUST have exactly one primary page heading. +- **FR-025**: Keyboard users MUST be able to reach and identify interactive homepage controls, including navigation and CTAs. +- **FR-026**: Status information in the dashboard preview MUST NOT rely on color alone. +- **FR-027**: The homepage MUST provide sufficient contrast on the dark background for body copy, labels, links, and CTAs. +- **FR-028**: The homepage MUST remain usable on mobile, tablet, desktop, and wide desktop viewports. +- **FR-029**: The mobile homepage MUST NOT create body-level horizontal overflow. +- **FR-030**: The header MUST collapse or simplify on mobile without hiding essential navigation and CTA access from keyboard users. +- **FR-031**: Decorative icons or visual flourishes MUST NOT be required to understand the page content. +- **FR-032**: Decorative motion, if present, MUST be subtle and respect reduced-motion preferences. +- **FR-033**: The homepage MUST NOT introduce platform, auth, database, Microsoft Graph, Filament, Livewire, or runtime API coupling. +- **FR-034**: Existing root workspace script names and website port conventions MUST remain unchanged. + +### Key Entities + +- **Homepage Message**: The public brand, category, headline, supporting promise, and CTA hierarchy for the `/` route. +- **Static Dashboard Preview**: A product-near visual using fixed demo values to communicate governance posture, findings, drift, evidence, backup status, reviews, and recent activity. +- **Trust Statement**: A neutral credibility statement that supports confidence without implying verified customer proof, certifications, or availability guarantees. +- **Feature Pillar**: A concise capability card describing one of Tenantial's required homepage capabilities: Backup, Restore, Drift Detection, Evidence, Audit Trail, or Governance Reviews. +- **Footer Link Group**: A public website navigation group that points visitors toward product, company, contact, legal, privacy, and security information without template residue. + +### Assumptions + +- The public brand for this homepage is Tenantial. +- No verified customer logos, security certifications, or uptime claims are available for this slice. +- The "Book a demo" destination defaults to `/contact` unless a valid existing destination is confirmed before implementation. +- The "Explore the platform" destination defaults to `/product` until a dedicated platform route exists. +- Header route defaults: Platform -> `/product`, Solutions -> `/solutions`, Book a demo -> `/contact`, and Explore the platform -> `/product`. Resources, Pricing, Company, and Sign in must not link to missing same-name routes; they may be de-emphasized, mapped to an existing intentional route, or rendered non-prominently until real routes exist. +- No secondary public pages are built by this spec. +- The dashboard preview uses static demo content, not screenshots, live product data, or platform API data. +- Existing website font choices may be reused if they support the premium enterprise direction. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: In a stakeholder review, at least 8 of 10 reviewers can identify Tenantial as a Microsoft tenant governance platform within 5 seconds of viewing the first homepage viewport. +- **SC-002**: 100% of required homepage sections render on desktop and mobile review: header, hero, static dashboard preview, trust bar, feature pillars, CTA section, and footer. +- **SC-003**: Review of visible copy and homepage metadata finds 0 occurrences of AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, TenantAtlas, or other old/template public brand positioning. +- **SC-004**: Desktop and mobile review confirms 0 body-level horizontal overflow issues at narrow mobile, tablet, desktop, and wide desktop widths. +- **SC-005**: At least 90% of reviewers can identify Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews from the homepage without needing external documentation. +- **SC-006**: Trust review finds 0 unverified customer, certification, uptime, or security/compliance claims. +- **SC-007**: Accessibility review confirms the page has one primary heading, keyboard-visible controls, readable contrast on dark backgrounds, and non-color-only status meaning. +- **SC-008**: Homepage smoke validation confirms the route renders, required brand/capability copy is visible, required CTAs are visible, static preview content is present, and mobile overflow is absent. diff --git a/specs/400-tenantial-homepage-visual-rebuild/tasks.md b/specs/400-tenantial-homepage-visual-rebuild/tasks.md new file mode 100644 index 00000000..12228451 --- /dev/null +++ b/specs/400-tenantial-homepage-visual-rebuild/tasks.md @@ -0,0 +1,257 @@ +# Tasks: Tenantial Homepage Visual Rebuild + +**Input**: Design documents from `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/` +**Prerequisites**: [plan.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/plan.md), [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md), [research.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/research.md), [data-model.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md), [contracts/public-homepage.yaml](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml), [quickstart.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md) + +**Tests**: Required. Spec 400 changes public website runtime behavior and requires browser smoke coverage. Test tasks use Playwright only; no Pest/Laravel/Filament tests are needed because no platform behavior changes. + +**Organization**: Tasks are grouped by user story so each story can be implemented and verified as an independent increment after shared setup/foundation. + +## Phase 1: Setup (Shared Infrastructure) + +**Purpose**: Confirm the existing website structure, route inventory, test files, and styling baseline before editing. + +- [X] T001 Review Spec 400 planning inputs in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/plan.md`, `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/data-model.md`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/contracts/public-homepage.yaml` +- [X] T002 Verify current homepage assembly and content sources in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts` +- [X] T003 [P] Verify current homepage smoke and IA assertions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/changelog-core-ia.spec.ts` +- [X] T004 [P] Verify Tailwind v4 CSS-first styling constraints in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/package.json` + +--- + +## Phase 2: Foundational (Blocking Prerequisites) + +**Purpose**: Establish shared Tenantial brand, dark shell, route intent, and smoke-test helpers used by all user stories. + +**Critical**: No user story implementation should begin until this phase is complete. + +- [X] T005 Update public site metadata, primary navigation seeds, footer group seeds, and route intent for Tenantial in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/lib/site.ts` +- [X] T006 Update homepage smoke helper constants and shared assertions for Tenantial labels, footer labels, forbidden old brands, metadata checks, and route targets in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` +- [X] T007 Update global Tenantial homepage SEO defaults and old-brand cleanup assumptions in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T008 [P] Replace the light TenantAtlas color foundation with website-local dark Tenantial design tokens using Tailwind v4 CSS-first `@theme` in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/tokens.css` +- [X] T009 [P] Update body background, shell surfaces, focus-visible styling, reduced-motion behavior, and horizontal-overflow safeguards in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css` +- [X] T010 Update shared public shell styling and Tenantial brand presentation in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/PageShell.astro`, `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Navbar.astro`, and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Footer.astro` + +**Checkpoint**: Shared Tenantial brand shell, route intent, dark tokens, and test helpers are ready. + +--- + +## Phase 3: User Story 1 - Understand Tenantial Immediately (Priority: P1) - MVP + +**Goal**: A first-time visitor opens `/` and immediately understands Tenantial as an evidence-first governance platform for Microsoft tenants with clear primary and secondary CTAs. + +**Independent Test**: Open `/` and verify Tenantial branding, the headline "Evidence-first governance for Microsoft tenants.", Microsoft tenant context, `Book a demo`, `Explore the platform`, and exactly one primary page heading. + +### Tests for User Story 1 + +- [X] T011 [US1] Update homepage hero smoke assertions for Tenantial brand, headline, supporting copy, CTA hierarchy, one H1, and `/contact` plus `/product` route targets in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` + +### Implementation for User Story 1 + +- [X] T012 [US1] Replace homepage hero copy, trust bullets, CTA labels, CTA destinations, and SEO metadata with Tenantial first-read content in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T013 [US1] Adapt the homepage hero layout, heading scale, eyebrow treatment, CTA pair, and first-viewport composition in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/PageHero.astro` +- [X] T014 [US1] Update homepage section ordering so the hero and first-read CTA story appear before supporting sections in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T015 [US1] Implement desktop and mobile header behavior for Platform -> `/product`, Solutions -> `/solutions`, Book a demo -> `/contact`, and explicit non-dead handling for Resources, Pricing, Company, and Sign in in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Navbar.astro` +- [X] T016 [US1] Run the User Story 1 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` and confirm it fails before implementation and passes after implementation + +**Checkpoint**: User Story 1 is independently testable as the homepage MVP. + +--- + +## Phase 4: User Story 2 - Evaluate Trust Without False Proof (Priority: P2) + +**Goal**: An IT, MSP, security, or compliance stakeholder sees credible trust positioning, neutral claims, and the required capability pillars without fake proof. + +**Independent Test**: Review `/` and confirm neutral trust statements, no unverified customer/certification/uptime claims, and visible Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews pillars. + +### Tests for User Story 2 + +- [X] T017 [US2] Add homepage smoke assertions for TrustBar statements, required FeaturePillars, and absence of SOC 2, ISO, uptime, trusted-by, and real-customer-logo claims in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` + +### Implementation for User Story 2 + +- [X] T018 [P] [US2] Create the static trust bar section with neutral credibility statements in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/TrustBar.astro` +- [X] T019 [P] [US2] Create the six required feature pillar cards with accessible icon treatment in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/FeaturePillars.astro` +- [X] T020 [US2] Add TrustBar and FeaturePillars content data for Backup, Restore, Drift Detection, Evidence, Audit Trail, and Governance Reviews in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T021 [US2] Wire TrustBar and FeaturePillars into the homepage after the hero section using a stable placeholder position that does not depend on the final DashboardPreview implementation in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T022 [US2] Remove or replace old trust, logo-strip, and capability copy that conflicts with Tenantial's no-fake-proof boundary in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/pages/index.astro` +- [X] T023 [US2] Run the User Story 2 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` and confirm trust/capability assertions pass + +**Checkpoint**: User Story 2 is independently testable after shared shell and homepage assembly are available. + +--- + +## Phase 5: User Story 3 - Inspect A Product-Near Preview (Priority: P3) + +**Goal**: A visitor sees a static product-near dashboard preview with posture, findings, drift, evidence, backup status, reviews, and recent activity that remains responsive and clearly non-live. + +**Independent Test**: Open `/`, inspect the preview, and verify static demo values `92%`, `14`, `7`, `1,248`, `98%`, supporting panels, text-based status meaning, no live/realtime claim, and no body-level horizontal overflow on mobile. + +### Tests for User Story 3 + +- [X] T024 [US3] Add homepage smoke assertions for static dashboard metrics, supporting panels, non-live wording, and status text labels in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` +- [X] T025 [P] [US3] Add a reusable mobile body-overflow assertion for homepage smoke tests in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/smoke-helpers.ts` + +### Implementation for User Story 3 + +- [X] T026 [US3] Create the static HTML/CSS dashboard preview with metrics, recent findings, drift timeline, backups and restores, reviews, and evidence spotlight in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/content/DashboardPreview.astro` +- [X] T027 [US3] Replace the old hero visual usage with DashboardPreview in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/PageHero.astro` +- [X] T028 [US3] Add dashboard-specific responsive layout, status label, and overflow containment styling in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles/global.css` +- [X] T029 [US3] Remove stale hero screenshot references from homepage content so the static dashboard preview is the only homepage product visual in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T030 [US3] Run the User Story 3 smoke subset from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` with desktop and 390px mobile viewport coverage + +**Checkpoint**: User Story 3 is independently testable as a static, responsive, non-live product preview. + +--- + +## Phase 6: User Story 4 - Verify Public Launch Readiness (Priority: P4) + +**Goal**: A website owner can verify that old template residue, wrong brand names, unsupported promises, broken CTAs, and mobile layout issues are absent before launch. + +**Independent Test**: Run the homepage smoke suite and manually review desktop/mobile `/` for forbidden copy, metadata cleanup, intentional links, footer completeness, no clipping, and no body overflow. + +### Tests for User Story 4 + +- [X] T031 [US4] Add forbidden visible-copy and metadata assertions for AstroDeck, Open Source, MIT Licensed, TenantCTRL, TenantPilot, and TenantAtlas in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/home-product.spec.ts` +- [X] T032 [P] [US4] Update core IA route and hidden-label assertions for Tenantial header/footer expectations in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/changelog-core-ia.spec.ts` +- [X] T033 [P] [US4] Update visual foundation guardrail smoke expectations for Tenantial brand, CTA text, and dark surface semantics in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke/visual-foundation-guardrails.spec.ts` + +### Implementation for User Story 4 + +- [X] T034 [US4] Update final CTA copy and homepage CTA section wiring to "Build tenant governance on evidence, not assumptions." with Book a demo and Explore the platform in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/sections/CTASection.astro` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T035 [US4] Update footer copy, footer group labels, copyright text, and old-template cleanup in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components/layout/Footer.astro` +- [X] T036 [P] [US4] Update the public Tenantial favicon or simple local mark in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/public/favicon.svg` +- [X] T037 [US4] Remove the unused old product-visual asset in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/public/images/hero-product-visual.svg` and replace old visual references in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/content/pages/home.ts` +- [X] T038 [US4] Run a forbidden-copy scrub across `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src` and update homepage-visible and globally reachable public brand residue found in the affected source files + +**Checkpoint**: User Story 4 is independently testable as launch-readiness cleanup and smoke validation. + +--- + +## Phase 7: Polish & Cross-Cutting Concerns + +**Purpose**: Validate the complete feature, record test governance, and ensure no scope drift into platform/admin behavior. + +- [X] T039 Run `corepack pnpm build:website` from `/Users/ahmeddarrazi/Documents/projects/wt-website/package.json` and fix any build failures in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` +- [X] T040 Run `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` from `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/package.json` and fix any smoke failures in `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/tests/smoke` +- [X] T041 Use browser review for mobile, tablet, desktop, and wide desktop `/` viewports, including keyboard focus order for header navigation, mobile menu, and CTAs, against `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/quickstart.md` +- [X] T042 [P] Verify no Laravel, Filament, Livewire, Microsoft Graph, database, auth, OperationRun, or platform coupling was introduced under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website` +- [X] T043 [P] Verify Tailwind v4 compliance by checking no deprecated v3 utilities or `tailwind.config.js` assumptions were introduced under `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/styles` and `/Users/ahmeddarrazi/Documents/projects/wt-website/apps/website/src/components` +- [X] T044 Record final Smoke Coverage close-out notes and any residual browser-review caveats in `/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/tasks.md` + +## Smoke Coverage Close-Out + +- Build: `corepack pnpm build:website` passed on 2026-05-18. Astro emitted non-failing content-loader warnings for missing `src/content/articles/` and `src/content/resources/` directories. +- Browser smoke: `WEBSITE_PORT=4321 corepack pnpm --filter @tenantatlas/website test:smoke` passed with 19/19 tests on 2026-05-18. +- Diff hygiene: `git diff --check` passed on 2026-05-18. +- Responsive/browser coverage: homepage smoke includes 390px mobile overflow checks, CTA hierarchy, metadata residue checks, static dashboard preview checks, header/mobile navigation, TrustBar, FeaturePillars, and footer reachability. +- Screenshots: no screenshot artifacts are committed for this branch. Earlier local-only screenshot paths were removed from this close-out so review remains repo-reproducible; use the quickstart manual review steps when fresh screenshots are needed. +- Scope/coupling: no Laravel, Filament, Livewire, database, auth, OperationRun, queue, or platform runtime behavior was introduced. Remaining `Microsoft Graph` and `database` search hits are pre-existing supporting-page/integration content, not new homepage coupling. +- Tailwind: no deprecated v3 utility patterns or `tailwind.config.js` assumptions were introduced in the checked website styles/components. +- Brand cleanup: existing public pages reachable from header/footer use Tenantial public brand copy while internal workspace names such as `@tenantatlas/website` remain unchanged. +- Spec 227: no `specs/227-*` artifact exists in the current checkout; no phantom spec was created. + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Phase 1 Setup**: No dependencies. +- **Phase 2 Foundational**: Depends on Phase 1 and blocks all user stories. +- **Phase 3 US1**: Depends on Phase 2 and is the MVP. +- **Phase 4 US2**: Depends on Phase 2; component creation and homepage wiring can proceed independently of US3 by using a stable post-hero section position. +- **Phase 5 US3**: Depends on Phase 2; dashboard component creation can begin after foundation, but final hero integration should account for US1 edits to `PageHero.astro`. +- **Phase 6 US4**: Depends on US1, US2, and US3 because it verifies final launch readiness across copy, metadata, navigation, footer, and visual behavior. +- **Phase 7 Polish**: Depends on all desired user stories being complete. + +### User Story Dependencies + +- **US1 (P1)**: Independent MVP after foundation. +- **US2 (P2)**: Independently testable after foundation; integrates with homepage assembly. +- **US3 (P3)**: Independently testable after foundation; integrates with hero/dashboard visual. +- **US4 (P4)**: Final readiness story; depends on the implemented homepage surface from US1-US3. + +### Within Each User Story + +- Write or update Playwright assertions first and confirm they fail before implementing the story. +- Update content/data before page wiring when both are needed. +- Create new components before importing them into `index.astro` or `PageHero.astro`. +- Run the story-specific smoke subset before proceeding to the next story. + +--- + +## Parallel Execution Examples + +### User Story 1 + +```text +Sequential because US1 edits shared homepage and hero files: +T011 -> T012 -> T013 -> T014 -> T015 -> T016 +``` + +### User Story 2 + +```text +Parallel component work after T017: +Task T018: Create TrustBar.astro +Task T019: Create FeaturePillars.astro + +Then: +T020 -> T021 -> T022 -> T023 +``` + +### User Story 3 + +```text +Parallel test helper and component work: +Task T025: Add mobile overflow helper +Task T026: Create DashboardPreview.astro + +Then: +T027 -> T028 -> T029 -> T030 +``` + +### User Story 4 + +```text +Parallel readiness assertions after US1-US3: +Task T032: Update changelog-core-ia.spec.ts +Task T033: Update visual-foundation-guardrails.spec.ts +Task T036: Update favicon.svg + +Then: +T031 -> T034 -> T035 -> T037 -> T038 +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1 Setup. +2. Complete Phase 2 Foundational tasks. +3. Complete Phase 3 User Story 1. +4. Stop and validate `/` as a Tenantial first-read homepage MVP. +5. Continue only after the hero, brand, headline, CTA hierarchy, and basic navigation behavior pass. + +### Incremental Delivery + +1. **US1**: Tenantial first-read homepage and CTA hierarchy. +2. **US2**: Trust bar and feature pillars without false proof. +3. **US3**: Static product-near dashboard preview. +4. **US4**: Launch-readiness cleanup, metadata, footer, forbidden-copy checks, and mobile review. + +### Parallel Team Strategy + +1. Complete Setup and Foundation together. +2. Split US2 TrustBar and FeaturePillars work while US3 DashboardPreview work proceeds in a separate file. +3. Serialize edits to shared files: `index.astro`, `home.ts`, `PageHero.astro`, `global.css`, and smoke specs. +4. Reserve US4 for final cleanup after all homepage sections exist. + +## Notes + +- `[P]` tasks are safe to start in parallel because they touch different files or do not depend on incomplete same-file edits. +- Story labels map to the four user stories in [spec.md](/Users/ahmeddarrazi/Documents/projects/wt-website/specs/400-tenantial-homepage-visual-rebuild/spec.md). +- Do not introduce platform, auth, database, Microsoft Graph, Laravel, Filament, Livewire, queue, or OperationRun behavior. +- Do not rename root packages or existing workspace scripts from `@tenantatlas/website` or the current pnpm script names.