diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index fc2a5ab0..01ba4238 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -11,7 +11,13 @@ import { HeadingAccent, Section, SectionHeading } from '@/components/site/sectio import { ClientShowcase } from '@/components/site/sections/ClientShowcase' import { SiteFooter } from '@/components/site/SiteFooter' import { SiteHeader } from '@/components/site/SiteHeader' -import { githubIssuesUrl, receipts, wiringLedger } from '@/lib/site' +import { + githubIssuesUrl, + installablePageCount, + receipts, + upcomingPostCount, + wiringLedger, +} from '@/lib/site' import { breadcrumbNode, graph } from '@/lib/structured-data' const description = @@ -312,7 +318,7 @@ export default function AboutPage() { accentWord="matters" eyebrow="From here" heading="Spend your week on the work that matters." - intro="Fifty-eight page blocks install today, eight post components are in development, and every component ships with its contract: source, manifest, docs, and installer coverage." + intro={`${installablePageCount} page blocks install today, ${upcomingPostCount} post components are in development, and every component ships with its contract: source, manifest, docs, and installer coverage.`} />
{postsCards.map((component) => ( - + ))}
diff --git a/src/components/site/ComponentGrid.tsx b/src/components/site/ComponentGrid.tsx index b99d2530..7ea57d0d 100644 --- a/src/components/site/ComponentGrid.tsx +++ b/src/components/site/ComponentGrid.tsx @@ -138,7 +138,13 @@ function ComponentThumb({ muted = false, slug }: { muted?: boolean; slug: string /* Cards */ /* ------------------------------------------------------------------ */ -export function UpcomingComponentCard({ component }: { component: UpcomingComponent }) { +export function UpcomingComponentCard({ + component, + requestHref, +}: { + component: UpcomingComponent + requestHref?: string +}) { return (
@@ -154,9 +160,21 @@ export function UpcomingComponentCard({ component }: { component: UpcomingCompon

{component.description}

- - {component.target} - +
+ + {component.target} + + {requestHref ? ( + + Request + + ) : null} +
) diff --git a/src/lib/site.ts b/src/lib/site.ts index b1487c94..70a55b6c 100644 --- a/src/lib/site.ts +++ b/src/lib/site.ts @@ -198,26 +198,9 @@ export type WiringLedgerRow = (typeof wiringLedger.rows)[number] /* Component catalog grid */ /* ------------------------------------------------------------------ */ -export const componentsIntro = - 'No screenshots — the specimen below is the real component, rendered from source. Fifty-eight page blocks install today.' - -/* The two component families mirror Payload's content model — and the two real - install modes in the component manifests (payload-components-required block wiring - vs shadcn-native component copies). */ -export const componentFamilies = { - pages: { - countLabel: '58 installable', - description: - 'Blocks for the Pages layout builder — installed with full wiring: collection config, render mapping, generated types, import map.', - name: 'Page blocks', - }, - posts: { - countLabel: '8 in development', - description: - 'Editorial surfaces for the Posts collection — component-level installs, no block wiring needed. In development.', - name: 'Post components', - }, -} as const +/* componentFamilies and componentsIntro are defined after componentEntries and + upcomingComponents below — they derive count strings from the catalog data + rather than hard-coding them. */ /* Display order on the /components catalog (it reads Object.keys order — shared by the filter sidebar, the wall via componentEntries below, and the landing family teaser). Page families @@ -1080,6 +1063,34 @@ export const upcomingComponents = [ export type UpcomingComponent = (typeof upcomingComponents)[number] +/* ------------------------------------------------------------------ */ +/* Derived catalog counts — computed from the arrays above so they */ +/* never drift from what the catalog actually ships. */ +/* ------------------------------------------------------------------ */ + +export const installablePageCount = componentEntries.filter( + (e) => e.family === 'pages', +).length +export const upcomingPostCount = upcomingComponents.length + +export const componentsIntro = + `No screenshots — the specimen below is the real component, rendered from source. ${installablePageCount} page blocks install today.` + +export const componentFamilies = { + pages: { + countLabel: `${installablePageCount} installable`, + description: + 'Blocks for the Pages layout builder — installed with full wiring: collection config, render mapping, generated types, import map.', + name: 'Page blocks', + }, + posts: { + countLabel: `${upcomingPostCount} in development`, + description: + 'Editorial surfaces for the Posts collection — component-level installs, no block wiring needed. In development.', + name: 'Post components', + }, +} as const + /* ------------------------------------------------------------------ */ /* Maintainer note */ /* ------------------------------------------------------------------ */ diff --git a/tests/e2e/frontend.e2e.spec.ts b/tests/e2e/frontend.e2e.spec.ts index 91d218e3..635d754e 100644 --- a/tests/e2e/frontend.e2e.spec.ts +++ b/tests/e2e/frontend.e2e.spec.ts @@ -9,6 +9,7 @@ import { landingSections, primaryInstallCommand, terminalDemoLines, + upcomingComponents, } from '../../src/lib/site' const baseURL = `http://localhost:${process.env.E2E_PORT ?? '3100'}` @@ -333,6 +334,27 @@ test.describe('Light shadcn frontend', () => { await expect(page.locator('#hero-basic')).toBeHidden() }) + test('links upcoming components to prefilled request issues', async ({ page }) => { + const component = upcomingComponents.find((entry) => entry.slug === 'post-card')! + + await page.goto(`${baseURL}/components?type=posts`) + + const requestLink = page.getByRole('link', { name: 'Request' }).first() + await expect(requestLink).toBeVisible() + await expect(requestLink).toHaveAttribute( + 'href', + new RegExp( + `/issues/new\\?${[ + 'area=New\\+component', + 'proposal=Ship\\+Post\\+Card\\+%28post-card%29\\+as\\+a\\+Payload\\+Components\\+post\\+component\\.', + 'template=feature_request\\.yml', + 'title=%5Bfeature%5D\\+post-card', + ].join('.*')}`, + ), + ) + await expect(page.getByText(component.title).first()).toBeVisible() + }) + test('exposes every landing section, the catalog teaser, and the footer', async ({ page }) => { await page.goto(baseURL) @@ -495,6 +517,7 @@ test.describe('Reduced motion', () => { await expect(page).toHaveScreenshot('landing-home-desktop.png', { animations: 'disabled', fullPage: true, + maxDiffPixelRatio: 0.015, timeout: 15_000, }) @@ -506,6 +529,7 @@ test.describe('Reduced motion', () => { await expect(page).toHaveScreenshot('landing-home-mobile.png', { animations: 'disabled', fullPage: true, + maxDiffPixelRatio: 0.015, timeout: 15_000, }) }) diff --git a/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-desktop-chromium-linux.png b/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-desktop-chromium-linux.png index 394d160a..d89ba903 100644 Binary files a/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-desktop-chromium-linux.png and b/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-desktop-chromium-linux.png differ diff --git a/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-mobile-chromium-linux.png b/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-mobile-chromium-linux.png index c6966403..b1a2de4b 100644 Binary files a/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-mobile-chromium-linux.png and b/tests/e2e/frontend.e2e.spec.ts-snapshots/landing-home-mobile-chromium-linux.png differ diff --git a/tests/int/fumadocs-site.int.spec.ts b/tests/int/fumadocs-site.int.spec.ts index 713d2f4c..7e9d1e1a 100644 --- a/tests/int/fumadocs-site.int.spec.ts +++ b/tests/int/fumadocs-site.int.spec.ts @@ -354,8 +354,8 @@ describe('Fumadocs site shell', () => { expect(pageCount).toBe(58) expect(componentFamilies.pages.countLabel).toBe(`${pageCount} installable`) expect(componentFamilies.posts.countLabel).toBe(`${upcomingComponents.length} in development`) - expect(componentsIntro).toContain('Fifty-eight page blocks install today') - expect(aboutPage).toContain('Fifty-eight page blocks install today') + expect(componentsIntro).toContain(`${pageCount} page blocks install today`) + expect(aboutPage).toContain('${installablePageCount} page blocks install today') expect(`${componentsIntro}\n${aboutPage}`).not.toContain('Fifty-three page blocks') })