Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/website/src/app/ag-ui/page.spec.tsx
Original file line number Diff line number Diff line change
@@ -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(),
Expand All @@ -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);
Expand Down
9 changes: 5 additions & 4 deletions apps/website/src/app/ag-ui/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -34,10 +35,10 @@ export default async function AgUiPage() {
<span className="lib-hero-rail-line" aria-hidden="true" />
</div>
<h1 id="ag-ui-hero-heading" className="ag-ui-page-h1">
One adapter. Eight backends.
One adapter. Seven backends.
</h1>
<p className="ag-ui-page-hero-subtitle">
Build the Angular UI once, on the AG-UI protocol — eight runtimes speak it today, and new ones <span className="marker-highlight">work the day they ship</span>. 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 <span className="marker-highlight">work the day they ship</span>. History and checkpoint behavior stays with your backend.
</p>
<div className="ag-ui-page-hero-buttons">
<Button variant="primary" size="lg" href="/docs/ag-ui/getting-started/quickstart">Get started</Button>
Expand All @@ -48,7 +49,7 @@ export default async function AgUiPage() {
</p>
<div className="ag-ui-page-hero-pills">
<Pill variant="accent">MIT</Pill>
<Pill variant="angular">Angular 20–22</Pill>
<Pill variant="angular">{`Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS[0]}–${WEBSITE_SUPPORTED_ANGULAR_MAJORS.at(-1)}`}</Pill>
<Pill variant="neutral">AG-UI protocol</Pill>
</div>
<p className="ag-ui-page-langgraph-note">
Expand All @@ -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' },
Expand Down
3 changes: 2 additions & 1 deletion apps/website/src/app/langgraph/page.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -47,7 +48,7 @@ export default async function LangGraphPage() {
</p>
<div className="langgraph-page-hero-pills">
<Pill variant="accent">MIT</Pill>
<Pill variant="angular">Angular 20–22</Pill>
<Pill variant="angular">{`Angular ${WEBSITE_SUPPORTED_ANGULAR_MAJORS[0]}–${WEBSITE_SUPPORTED_ANGULAR_MAJORS.at(-1)}`}</Pill>
<Pill variant="neutral">LangGraph + AG-UI</Pill>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion apps/website/src/app/render/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default async function RenderPage() {
Generative UI without a second framework.
</h1>
<p className="render-page-hero-subtitle">
Server-emitted JSON specs render into Angular <span className="marker-highlight">components you already own</span>. Vercel json-render and Google A2UI both supported. Per-component fallback, readiness gate, no surprises.
Server-emitted JSON specs render into Angular <span className="marker-highlight">components you already own</span>. Vercel json-render and Google A2UI both supported. Unknown specs degrade gracefully — no surprises.
</p>
<div className="render-page-hero-buttons">
<Button variant="primary" size="lg" href="/docs/render/getting-started/introduction">Get started</Button>
Expand Down
1 change: 0 additions & 1 deletion apps/website/src/components/landing/FeatureBlock.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
8 changes: 4 additions & 4 deletions apps/website/src/components/landing/FinalCTA.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand All @@ -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 (
Expand Down
9 changes: 6 additions & 3 deletions apps/website/src/components/landing/HomeFAQ.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,12 @@ export function HomeFAQ() {
<Section surface="white" ariaLabelledBy="faq-heading">
<Container>
<div className="home-faq-intro">
<Eyebrow tone="accent" className="home-faq-eyebrow">
Questions
</Eyebrow>
<div className="home-faq-rail">
<Eyebrow tone="accent" className="home-faq-eyebrow">
Questions
</Eyebrow>
<span className="home-faq-rail-line" aria-hidden="true" />
</div>
<h2 id="faq-heading" className="home-faq-heading">
Frequently asked questions.
</h2>
Expand Down
3 changes: 3 additions & 0 deletions apps/website/src/components/landing/YesWall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
5 changes: 4 additions & 1 deletion apps/website/src/components/landing/ag-ui/BackendsGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
Expand Down
5 changes: 3 additions & 2 deletions apps/website/src/components/pricing/PricingDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TIERS, type TierSlug } from '../../../../../pricing/tiers.config';
import { Eyebrow } from '../ui/Eyebrow';

type ComparisonCells = Record<TierSlug, string>;

Expand Down Expand Up @@ -70,7 +71,7 @@ export function ArchitectureBoundary() {
<div className="pricing-boundary">
<div className="pricing-section-heading-wrap">
<div className="pricing-section-rail">
<p className="pricing-section-kicker">What you are buying</p>
<Eyebrow tone="accent" className="pricing-section-kicker">What you are buying</Eyebrow>
<span className="pricing-section-rail-line" aria-hidden="true" />
</div>
<h2 id="pricing-value-heading" className="pricing-section-heading">
Expand Down Expand Up @@ -121,7 +122,7 @@ export function PricingComparison() {
<div className="pricing-comparison">
<div className="pricing-section-heading-wrap">
<div className="pricing-section-rail">
<p className="pricing-section-kicker">Full comparison</p>
<Eyebrow tone="accent" className="pricing-section-kicker">Full comparison</Eyebrow>
<span className="pricing-section-rail-line" aria-hidden="true" />
</div>
<h2 id="pricing-comparison-heading" className="pricing-section-heading">
Expand Down
3 changes: 1 addition & 2 deletions apps/website/src/components/shared/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -112,7 +111,7 @@ export function Footer() {
<LogoMark size="md" />
</div>
<p className="text-sm mb-4 footer-tagline">
{SHORT_POSITIONING_DESCRIPTION}
The Angular UI layer for production agents.
</p>
<NewsletterForm />
{/* Social links */}
Expand Down
12 changes: 11 additions & 1 deletion apps/website/src/styles/landing.css
Original file line number Diff line number Diff line change
Expand Up @@ -727,7 +727,6 @@

/* HomeFAQ — components/landing/HomeFAQ.tsx */
.home-faq-intro {
text-align: center;
margin-bottom: 48px;
}
.home-faq-eyebrow {
Expand Down Expand Up @@ -1474,3 +1473,14 @@
height: 1px;
background: var(--color-border);
}

.home-faq-rail {
display: flex;
align-items: baseline;
gap: 14px;
}
.home-faq-rail-line {
flex: 1;
height: 1px;
background: var(--color-border);
}
76 changes: 0 additions & 76 deletions apps/website/src/styles/marketing.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,6 @@
margin: 0 auto;
padding: 32px 24px 88px;
}
.pricing-billing-fieldset {
border: 0;
padding: 0;
margin: 0 0 32px;
display: flex;
justify-content: center;
}
.pricing-billing-toggle {
display: inline-flex;
padding: 4px;
background: var(--color-surface-tinted);
border: 1px solid var(--color-border);
border-radius: 999px;
gap: 4px;
}
.pricing-billing-tab {
position: relative;
font-family: var(--font-inter);
font-size: 13px;
font-weight: 600;
padding: 8px 16px;
cursor: pointer;
transition: background 150ms ease, color 150ms ease;
background: transparent;
color: var(--color-text-secondary);
border-radius: 999px;
}
.pricing-billing-tab[data-active] {
background: var(--color-accent);
color: #fff;
}
.pricing-billing-tab:has(input:focus-visible) {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.pricing-plan-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
Expand Down Expand Up @@ -134,17 +99,8 @@
color: var(--color-text-primary);
line-height: 1;
}
.pricing-plan-price-period {
font-family: var(--font-inter);
font-size: 11px;
color: var(--color-text-muted);
}
.pricing-plan-price-qualifier,
.pricing-plan-billing-note,
.pricing-plan-additional-qualifier {
font-family: var(--font-inter);
line-height: 1.45;
}
.pricing-plan-price-qualifier {
margin: 10px 0 0;
color: var(--color-text-primary);
Expand All @@ -156,12 +112,6 @@
color: var(--color-text-muted);
font-size: 11px;
}
.pricing-plan-additional-qualifier {
margin: 7px 0 0;
color: var(--color-accent);
font-size: 12px;
font-weight: 700;
}
.pricing-plan-description {
margin: 6px 0 20px;
min-height: 88px;
Expand Down Expand Up @@ -197,10 +147,6 @@
margin-top: auto;
padding-top: 24px;
}
.pricing-plan-form,
.pricing-plan-btn {
width: 100%;
}
.pricing-section-heading-wrap {
max-width: 760px;
margin-bottom: 36px;
Expand Down Expand Up @@ -332,14 +278,6 @@
.pricing-comparison-table td[data-highlight] {
background: var(--color-surface-tinted);
}
.pricing-comparison-popular {
display: block;
margin-top: 3px;
color: var(--color-accent);
font-size: 9px;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.pricing-comparison-row-heading {
position: sticky;
left: 0;
Expand All @@ -349,13 +287,6 @@
font-weight: 600;
text-align: left;
}
.pricing-comparison-row-note {
display: block;
margin-top: 5px;
color: var(--color-text-muted);
font-size: 10px;
font-weight: 400;
}
.pricing-comparison-table tbody:last-child tr:last-child th,
.pricing-comparison-table tbody:last-child tr:last-child td {
border-bottom: 0;
Expand Down Expand Up @@ -390,16 +321,9 @@
transform: rotate(90deg);
justify-self: center;
}
.pricing-billing-tab {
padding: 8px 12px;
font-size: 12px;
}
}

@media (prefers-reduced-motion: reduce) {
.pricing-billing-tab {
transition: none;
}
}

/* LeadForm — components/pricing/LeadForm.tsx */
Expand Down
30 changes: 30 additions & 0 deletions docs/superpowers/specs/2026-09-01-final-review-fixes-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Final-review fix batch + rulings

**Date:** 2026-09-01 · **Status:** Approved ("ship"). · **Branch:** `blove/final-review-fixes`

Fixes from the all-surfaces final review:
1. **/ag-ui eight→seven** (H1, subtitle, block body — the grid lists 7). A
page-spec assertion ties `BACKENDS.length` to the copy so adding an 8th
runtime fails tests until the words update. BACKENDS gains a comment.
2. **Footer tagline de-litanied** → "The Angular UI layer for production
agents." (meta/OG consumers of SHORT_POSITIONING_DESCRIPTION untouched).
3. **HomeFAQ header → railkick** (the homepage's last centered header).
4. **/render subtitle deduped** → "Unknown specs degrade gracefully — no
surprises." (block headline keeps the terms).
5. **FinalCTA caption** → "All packages are MIT · Production support
available · Installation is inert"; stale doc comment updated to the
product-pages rule.
6. **Angular pills derived** on /langgraph + /ag-ui from
WEBSITE_SUPPORTED_ANGULAR_MAJORS.
7. **8 orphaned marketing.css classes deleted** (zero-consumer verified).
8. **FeatureBlock.spec tautological card-row assertion removed.**
9. **PricingDetails kicker unified on Eyebrow.**
10. **YesWall aside** gains a sync comment beside TOTAL_QUESTIONS.

Rulings recorded (no code change): the Promises 5-marker ledger is the
sanctioned exception to marker-≤2 (ledger rows may sweep per-row; prose
stays ≤2); Yes-wall↔block API-tail echoes are the design's deliberate
grammar (wall asks, block elaborates); the hero caption's Genkit mention is
defensible ("keep your backend" claims compatibility of intent, not an
adapter); pricing hero's comma-join vs ProofStrip's en-dash are different
registers of the same derived source — both stand.
Loading