diff --git a/apps/website/src/app/ag-ui/page.spec.tsx b/apps/website/src/app/ag-ui/page.spec.tsx
index 596d8091c..9f09b5a2d 100644
--- a/apps/website/src/app/ag-ui/page.spec.tsx
+++ b/apps/website/src/app/ag-ui/page.spec.tsx
@@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import AgUiPage from './page';
+import { BACKENDS } from '../../components/landing/ag-ui/BackendsGrid';
vi.mock('../../lib/analytics/client', () => ({
track: vi.fn(),
@@ -13,6 +14,8 @@ describe('AgUiPage', () => {
it('renders the package kicker, marker sweep, switcher, and dark closer', async () => {
const ui = await AgUiPage();
const { container } = render(ui);
+ // The H1/subtitle/body say "seven"; this pins the grid to that word.
+ expect(BACKENDS).toHaveLength(7);
expect(screen.getByText('@threadplane/ag-ui · protocol adapter')).toBeTruthy();
expect(container.querySelector('.marker-highlight')?.textContent).toBe('work the day they ship');
expect(screen.getAllByRole('tablist').length).toBeGreaterThanOrEqual(1);
diff --git a/apps/website/src/app/ag-ui/page.tsx b/apps/website/src/app/ag-ui/page.tsx
index b3bece472..43501bd5d 100644
--- a/apps/website/src/app/ag-ui/page.tsx
+++ b/apps/website/src/app/ag-ui/page.tsx
@@ -1,4 +1,5 @@
import Link from 'next/link';
+import { WEBSITE_SUPPORTED_ANGULAR_MAJORS } from '../../components/pricing/angular-support.mjs';
import { Container } from '../../components/ui/Container';
import { Section } from '../../components/ui/Section';
import { Eyebrow } from '../../components/ui/Eyebrow';
@@ -34,10 +35,10 @@ export default async function AgUiPage() {
- One adapter. Eight backends.
+ One adapter. Seven backends.
- Build the Angular UI once, on the AG-UI protocol — eight runtimes speak it today, and new ones work the day they ship. History and checkpoint behavior stays with your backend.
+ Build the Angular UI once, on the AG-UI protocol — seven runtimes speak it today, and new ones work the day they ship. History and checkpoint behavior stays with your backend.
@@ -48,7 +49,7 @@ export default async function AgUiPage() {
MIT
- Angular 20–22
+ {`Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS[0]}–${WEBSITE_SUPPORTED_ANGULAR_MAJORS.at(-1)}`}AG-UI protocol
@@ -66,7 +67,7 @@ export default async function AgUiPage() {
id="backends"
eyebrow="Runtime choice"
headline="Pick a backend. Keep the UI."
- body="The AG-UI protocol decouples your agent runtime from your front-end. @threadplane/ag-ui wraps any AG-UI AbstractAgent into the runtime-neutral Agent contract that @threadplane/chat consumes — so the same Angular components ship against eight different runtimes."
+ body="The AG-UI protocol decouples your agent runtime from your front-end. @threadplane/ag-ui wraps any AG-UI AbstractAgent into the runtime-neutral Agent contract that @threadplane/chat consumes — so the same Angular components ship against seven different runtimes."
rows={[
{ claim: 'Stream from Python, .NET, or TypeScript', api: 'AG-UI protocol' },
{ claim: 'Tool calls, state deltas, citations — standardized', api: 'protocol events' },
diff --git a/apps/website/src/app/langgraph/page.tsx b/apps/website/src/app/langgraph/page.tsx
index 0e91edc9f..ebd2cdf73 100644
--- a/apps/website/src/app/langgraph/page.tsx
+++ b/apps/website/src/app/langgraph/page.tsx
@@ -1,4 +1,5 @@
import { Container } from '../../components/ui/Container';
+import { WEBSITE_SUPPORTED_ANGULAR_MAJORS } from '../../components/pricing/angular-support.mjs';
import { Section } from '../../components/ui/Section';
import { Eyebrow } from '../../components/ui/Eyebrow';
import { Button } from '../../components/ui/Button';
@@ -47,7 +48,7 @@ export default async function LangGraphPage() {
MIT
- Angular 20–22
+ {`Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS[0]}–${WEBSITE_SUPPORTED_ANGULAR_MAJORS.at(-1)}`}LangGraph + AG-UI
diff --git a/apps/website/src/app/render/page.tsx b/apps/website/src/app/render/page.tsx
index b913ce6f9..6b551b41f 100644
--- a/apps/website/src/app/render/page.tsx
+++ b/apps/website/src/app/render/page.tsx
@@ -36,7 +36,7 @@ export default async function RenderPage() {
Generative UI without a second framework.
- Server-emitted JSON specs render into Angular components you already own. Vercel json-render and Google A2UI both supported. Per-component fallback, readiness gate, no surprises.
+ Server-emitted JSON specs render into Angular components you already own. Vercel json-render and Google A2UI both supported. Unknown specs degrade gracefully — no surprises.
diff --git a/apps/website/src/components/landing/FeatureBlock.spec.tsx b/apps/website/src/components/landing/FeatureBlock.spec.tsx
index 5615e69cc..aa27ae72e 100644
--- a/apps/website/src/components/landing/FeatureBlock.spec.tsx
+++ b/apps/website/src/components/landing/FeatureBlock.spec.tsx
@@ -23,7 +23,6 @@ describe('FeatureBlock', () => {
);
expect(screen.getByText('Your design system, not a chat widget')).toBeTruthy();
expect(screen.getByText('@threadplane/render')).toBeTruthy();
- expect(container.querySelector('.feature-block-card-row')).toBeNull();
expect(container.querySelectorAll('.feature-block-row')).toHaveLength(2);
expect(container.querySelector('.feature-block-rail')).toBeTruthy();
});
diff --git a/apps/website/src/components/landing/FinalCTA.tsx b/apps/website/src/components/landing/FinalCTA.tsx
index 8fb1f1d09..d81768fa4 100644
--- a/apps/website/src/components/landing/FinalCTA.tsx
+++ b/apps/website/src/components/landing/FinalCTA.tsx
@@ -15,9 +15,9 @@ interface FinalCTAProps {
/** Optional trailing caption. Defaults to licensing and telemetry line. Pass null to hide. */
caption?: string | null;
/**
- * 'dark' renders on the dark band — homepage only, pairing with the Yes
- * wall so the inverted treatment appears twice (spec: a lone dark band
- * reads as arbitrary). All other pages keep the default tinted surface.
+ * 'dark' renders on the dark band. Rule (amended 2026-08-31): dark closes
+ * PRODUCT pages — the homepage (pairing with the Yes wall) and the four
+ * library pages. Commerce pages keep the default tinted surface.
*/
variant?: 'default' | 'dark';
}
@@ -29,7 +29,7 @@ export function FinalCTA({
subtext = 'Install the framework, read the docs, and have a streaming chat in your app this afternoon.',
primary = null,
secondary = DEFAULT_SECONDARY,
- caption = 'All packages are MIT · Production support available · App telemetry off by default',
+ caption = 'All packages are MIT · Production support available · Installation is inert',
variant = 'default',
}: FinalCTAProps = {}) {
return (
diff --git a/apps/website/src/components/landing/HomeFAQ.tsx b/apps/website/src/components/landing/HomeFAQ.tsx
index 1e90bcef0..61f234d32 100644
--- a/apps/website/src/components/landing/HomeFAQ.tsx
+++ b/apps/website/src/components/landing/HomeFAQ.tsx
@@ -39,9 +39,12 @@ export function HomeFAQ() {
-
- Questions
-
+
+
+ Questions
+
+
+
Frequently asked questions.
diff --git a/apps/website/src/components/landing/YesWall.tsx b/apps/website/src/components/landing/YesWall.tsx
index a1a6a6e1e..140243ae4 100644
--- a/apps/website/src/components/landing/YesWall.tsx
+++ b/apps/website/src/components/landing/YesWall.tsx
@@ -54,6 +54,9 @@ export const YES_WALL_GROUPS: readonly YesGroup[] = [
},
];
+// NOTE: the SectionHeader aside says "Sixteen questions" in prose — update it
+// (and the spec's toBe(16)) together with any row change; the footer count
+// derives automatically.
const TOTAL_QUESTIONS = YES_WALL_GROUPS.reduce((n, g) => n + g.rows.length, 0);
export function YesWall() {
diff --git a/apps/website/src/components/landing/ag-ui/BackendsGrid.tsx b/apps/website/src/components/landing/ag-ui/BackendsGrid.tsx
index de7945549..9c62c5d68 100644
--- a/apps/website/src/components/landing/ag-ui/BackendsGrid.tsx
+++ b/apps/website/src/components/landing/ag-ui/BackendsGrid.tsx
@@ -3,7 +3,10 @@ interface Backend {
readonly note: string;
}
-const BACKENDS: readonly Backend[] = [
+// Three copy sites on /ag-ui say "seven" — the page spec ties this list's
+// length to that word. Adding a runtime here fails that test until the copy
+// updates. Honest counts only.
+export const BACKENDS: readonly Backend[] = [
{ name: 'LangGraph', note: 'Python / TS' },
{ name: 'CrewAI', note: 'Python' },
{ name: 'Mastra', note: 'TypeScript' },
diff --git a/apps/website/src/components/pricing/PricingDetails.tsx b/apps/website/src/components/pricing/PricingDetails.tsx
index ff9d3f6c0..8c58cb899 100644
--- a/apps/website/src/components/pricing/PricingDetails.tsx
+++ b/apps/website/src/components/pricing/PricingDetails.tsx
@@ -1,4 +1,5 @@
import { TIERS, type TierSlug } from '../../../../../pricing/tiers.config';
+import { Eyebrow } from '../ui/Eyebrow';
type ComparisonCells = Record;
@@ -70,7 +71,7 @@ export function ArchitectureBoundary() {
-
What you are buying
+ What you are buying
@@ -121,7 +122,7 @@ export function PricingComparison() {
-
Full comparison
+ Full comparison
diff --git a/apps/website/src/components/shared/Footer.tsx b/apps/website/src/components/shared/Footer.tsx
index 5c8708be8..9e166cc0d 100644
--- a/apps/website/src/components/shared/Footer.tsx
+++ b/apps/website/src/components/shared/Footer.tsx
@@ -3,7 +3,6 @@ import { useState } from 'react';
import Link from 'next/link';
import { analyticsEvents } from '../../lib/analytics/events';
import { track, trackCtaClick, trackExternalLinkClick } from '../../lib/analytics/client';
-import { SHORT_POSITIONING_DESCRIPTION } from '../../lib/positioning';
import { DEMOS, demoCtaSuffix } from '../../lib/demos';
import { LogoMark } from '../ui/LogoMark';
import { Button } from '../ui/Button';
@@ -112,7 +111,7 @@ export function Footer() {
- {SHORT_POSITIONING_DESCRIPTION}
+ The Angular UI layer for production agents.