Skip to content

Commit 318252b

Browse files
bloveclaude
andauthored
feat(website): homepage punch list — hero merge, FAQ copy, FeatureBlock rows everywhere (#897)
Hero's two noun rows merge into one linked capability row; the first FAQ answer loses its litany; all five non-home pages migrate to FeatureBlock rows (33 rows, claim→API grammar); the legacy bullets+cards path is deleted and rows becomes required. Net -197 lines. Squashed rebuild of the original commit series to shed an unsigned api-docs bot commit that tripped required_signatures. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 02379f0 commit 318252b

14 files changed

Lines changed: 625 additions & 316 deletions

File tree

apps/website/src/app/ag-ui/page.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,10 @@ export default async function AgUiPage() {
5959
eyebrow="Runtime choice"
6060
headline="Pick a backend. Keep the UI."
6161
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."
62-
bullets={[
63-
'Stream from Python, .NET, or TypeScript backends — same chat primitives',
64-
'Swap runtimes without rewriting the UI layer',
65-
'Protocol-first: tool calls, state deltas, citations all standardized',
66-
'Future runtimes that ship AG-UI work day-one',
67-
]}
68-
supportingCards={[
69-
{ title: 'LangGraph', description: 'Python or TS via AG-UI.' },
70-
{ title: 'Mastra', description: 'TypeScript-native.' },
71-
{ title: 'CrewAI / AG2', description: 'Multi-agent crews.' },
62+
rows={[
63+
{ claim: 'Stream from Python, .NET, or TypeScript', api: 'AG-UI protocol' },
64+
{ claim: 'Tool calls, state deltas, citations — standardized', api: 'protocol events' },
65+
{ claim: 'New AG-UI runtimes work day one', api: 'no adapter needed' },
7266
]}
7367
cta={{ label: 'Browse the AG-UI protocol', href: 'https://github.com/ag-ui-protocol/ag-ui' }}
7468
visual={<BackendsGrid />}
@@ -79,16 +73,10 @@ export default async function AgUiPage() {
7973
eyebrow="Same primitives"
8074
headline="Drop-in for everything @threadplane/chat ships."
8175
body="provideAgent registers an AG-UI client and exposes the same Agent contract that @threadplane/langgraph provides. Chat rendering, status, tool calls, generative UI, and citations use the same Angular primitives; durable checkpointed threads and history depend on the backend protocol, so use @threadplane/langgraph when you need the native LangGraph thread API."
82-
bullets={[
83-
'provideAgent + injectAgent — same names across adapters',
84-
'Shared Agent contract: messages() / status() / reload()',
85-
'Same A2UI surface, themes, and citations rendering',
86-
'MockAgentTransport works the same way for tests',
87-
]}
88-
supportingCards={[
89-
{ title: 'provideAgent', description: 'AG-UI wiring.' },
90-
{ title: 'injectAgent()', description: 'No-args helper.' },
91-
{ title: '@threadplane/chat', description: 'Same components.' },
76+
rows={[
77+
{ claim: 'Same names across adapters', api: 'provideAgent + injectAgent' },
78+
{ claim: 'Same components, themes, citations', api: '@threadplane/chat' },
79+
{ claim: 'Same deterministic testing', api: 'MockAgentTransport' },
9280
]}
9381
cta={{ label: 'API reference', href: '/docs/langgraph/api/inject-agent' }}
9482
visualLeft

apps/website/src/app/chat/page.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,10 @@ export default async function ChatPage() {
5151
eyebrow="Compositions"
5252
headline="Opinionated shells, swappable parts."
5353
body="chat-timeline is a drop-in conversation surface that handles streaming, tool calls, interrupts, branching, and time-travel. chat-debug ships devtools alongside — tool-call inspector, message replay, thread history."
54-
bullets={[
55-
'chat-timeline — drop-in production surface',
56-
'chat-debug — devtools alongside, ship-ready',
57-
'Sidenav + history search palette',
58-
'Themable via CSS vars or component overrides',
59-
]}
60-
supportingCards={[
61-
{ title: 'chat-timeline', description: 'The conversation surface.' },
62-
{ title: 'chat-debug', description: 'Tool-call devtools.' },
63-
{ title: 'sidenav', description: 'Thread navigation.' },
54+
rows={[
55+
{ claim: 'A drop-in production conversation surface', api: 'chat-timeline' },
56+
{ claim: 'Devtools beside it, ship-ready', api: 'chat-debug' },
57+
{ claim: 'Thread navigation and history search', api: 'sidenav + palette' },
6458
]}
6559
cta={{ label: 'See @threadplane/chat docs', href: '/docs/chat/getting-started/introduction' }}
6660
visual={
@@ -90,16 +84,10 @@ export default async function ChatPage() {
9084
eyebrow="Headless"
9185
headline="Or skip the shell — use the primitives."
9286
body="If you have a design system, use the headless primitives directly. They're the same building blocks the compositions are made of — bring your own DOM, keep our state machine."
93-
bullets={[
94-
'Primitives stay unstyled',
95-
'Bring your own design tokens',
96-
'Compose with the streaming agent contract',
97-
'No two-way coupling to the chat shell',
98-
]}
99-
supportingCards={[
100-
{ title: 'message primitives', description: 'Streaming-aware atoms.' },
101-
{ title: 'tool primitives', description: 'Tool-call lifecycle.' },
102-
{ title: 'interrupt primitive', description: 'Approval gate.' },
87+
rows={[
88+
{ claim: 'Unstyled primitives, your design tokens', api: 'message + tool primitives' },
89+
{ claim: 'The approval gate as a component', api: 'interrupt primitive' },
90+
{ claim: 'Composes against the streaming contract', api: 'Agent contract' },
10391
]}
10492
cta={{ label: 'Headless API', href: '/docs/chat/api/provide-chat' }}
10593
visualLeft

apps/website/src/app/langgraph/page.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,10 @@ export default async function LangGraphPage() {
5252
eyebrow="Providers"
5353
headline="Drop it into app.config.ts. Done."
5454
body="provideAgent wires LangGraph into Angular's DI container. From any component, injectAgent() returns a signal-based handle for messages, status, errors, and interrupts."
55-
bullets={[
56-
'provideAgent — wire it once in app.config.ts',
57-
'injectAgent() returns a typed signal-based handle',
58-
'OnPush tested',
59-
'Test transports for deterministic specs',
60-
]}
61-
supportingCards={[
62-
{ title: 'provideAgent', description: 'LangGraph wiring.' },
63-
{ title: 'injectAgent()', description: 'No-args helper.' },
64-
{ title: 'MockAgentTransport', description: 'Deterministic tests.' },
55+
rows={[
56+
{ claim: 'Wire it once in app.config.ts', api: 'provideAgent' },
57+
{ claim: 'A typed, signal-based handle, no args', api: 'injectAgent()' },
58+
{ claim: 'Deterministic tests without a backend', api: 'MockAgentTransport' },
6559
]}
6660
cta={{ label: 'API reference', href: '/docs/langgraph/api/inject-agent' }}
6761
visual={
@@ -94,16 +88,10 @@ export class ChatComponent {
9488
eyebrow="Signals"
9589
headline="Reactive without RxJS gymnastics."
9690
body="Every agent surface is exposed as a signal — message stream, tool progress, interrupts, errors, status. Compose with the rest of your Angular reactivity story. No subscriptions to leak."
97-
bullets={[
98-
'messages() / status() / error() / reload()',
99-
'interrupt() for human-in-the-loop gates',
100-
'Branch / history / time-travel built in',
101-
'Computed signals integrate cleanly',
102-
]}
103-
supportingCards={[
104-
{ title: 'messages()', description: 'Streaming message list.' },
105-
{ title: 'interrupt()', description: 'Approval-gate signal.' },
106-
{ title: 'reload()', description: 'Recover from errors.' },
91+
rows={[
92+
{ claim: 'messages(), status(), error() — live signals', api: 'signal-native handle' },
93+
{ claim: 'Human-in-the-loop gates', api: 'interrupt()' },
94+
{ claim: 'Branch, history, time-travel built in', api: 'checkpoints' },
10795
]}
10896
cta={{ label: 'Read the streaming guide', href: '/docs/langgraph/guides/streaming' }}
10997
visualLeft

apps/website/src/app/pilot-to-prod/page.tsx

Lines changed: 12 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,16 +52,10 @@ export default function PilotToProdPage() {
5252
eyebrow="Week 1–2 · Discover"
5353
headline="Map your stack. Pick the work that earns its keep."
5454
body="We don't start with the model. We start with the workflow — the meeting where someone says 'this would be a great use of AI' and the friction that's stopping it from shipping."
55-
bullets={[
56-
'Audit existing Angular surfaces + agent-eligible workflows',
57-
'Identify the 1–2 highest-leverage agents to build first',
58-
'Lock down auth, data residency, observability constraints',
59-
'Decide LangGraph vs AG-UI adapter strategy',
60-
]}
61-
supportingCards={[
62-
{ title: 'Workshops', description: 'On-site or remote with your team.' },
63-
{ title: 'Stack audit', description: 'Existing Angular + backend review.' },
64-
{ title: 'Roadmap', description: 'Concrete scope for build phase.' },
55+
rows={[
56+
{ claim: 'Audit your surfaces and agent-eligible workflows', api: 'stack audit' },
57+
{ claim: 'Pick the one or two agents that earn their keep', api: 'roadmap' },
58+
{ claim: 'Auth, residency, observability locked early', api: 'workshops' },
6559
]}
6660
cta={{ label: 'See sample roadmap', href: '#whitepaper-block' }}
6761
visual={
@@ -93,16 +87,10 @@ export default function PilotToProdPage() {
9387
eyebrow="Week 3–5 · Build"
9488
headline="Ship a working agent on your real data."
9589
body="Working code, not slideware. We integrate against your real backend, your real auth, and your real Angular app — paired with your engineers, not behind a curtain."
96-
bullets={[
97-
'Real LangGraph or AG-UI backend (yours or ours, your call)',
98-
'Streaming chat surface using @threadplane/chat compositions',
99-
'Generative UI for the workflows that benefit from it',
100-
'Daily syncs · weekly demo to stakeholders',
101-
]}
102-
supportingCards={[
103-
{ title: 'Pair programming', description: 'Your engineers drive.' },
104-
{ title: 'Open repo', description: 'You own the source from day one.' },
105-
{ title: 'Weekly demo', description: 'Stakeholder transparency throughout.' },
90+
rows={[
91+
{ claim: 'A working agent on your real data', api: 'your repo, your engineers' },
92+
{ claim: 'Streaming surface from the chat compositions', api: '@threadplane/chat' },
93+
{ claim: 'Weekly demos to stakeholders', api: 'open progress' },
10694
]}
10795
cta={{ label: 'See @threadplane/chat', href: '/chat' }}
10896
visualLeft
@@ -125,17 +113,10 @@ export default function PilotToProdPage() {
125113
eyebrow="Week 6–7 · Harden"
126114
headline="Production-ready, not demo-ready."
127115
body="Observability, error boundaries, fallback strategies, deploy paths, on-call runbook. The stuff that makes the difference between a demo and an app you can leave running on a Friday afternoon."
128-
bullets={[
129-
'Observability — tracing, metrics, error budgets',
130-
'Error/fallback strategy across every agent surface',
131-
'CI/CD integration with your existing pipeline',
132-
'Load + chaos testing patterns',
133-
'On-call runbook handed to your team',
134-
]}
135-
supportingCards={[
136-
{ title: 'Tracing', description: 'OpenTelemetry hooks.' },
137-
{ title: 'Fallback API', description: 'Per-component readiness.' },
138-
{ title: 'Runbook', description: 'Yours forever.' },
116+
rows={[
117+
{ claim: 'Tracing, metrics, error budgets', api: 'OpenTelemetry hooks' },
118+
{ claim: 'Fallbacks across every agent surface', api: 'readiness + fallback' },
119+
{ claim: 'Load tested, on-call ready', api: 'runbook, yours' },
139120
]}
140121
cta={{ label: 'Production patterns', href: '/docs/langgraph/guides/deployment' }}
141122
visual={

apps/website/src/app/render/page.tsx

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,10 @@ export default async function RenderPage() {
4949
eyebrow="Schemas"
5050
headline="One spec. Your components."
5151
body="The agent emits structured UI as JSON. @threadplane/render maps each spec node to one of your Angular components — so the design system stays yours, and the agent gets to assemble it."
52-
bullets={[
53-
'Vercel json-render adapter',
54-
'Google A2UI protocol',
55-
'Component registry — declare once, use everywhere',
56-
'Server schema, client validation',
57-
]}
58-
supportingCards={[
59-
{ title: 'json-render', description: 'Vercel adapter.' },
60-
{ title: 'A2UI v1', description: 'Google A2UI protocol.' },
61-
{ title: 'registry', description: 'Spec → component.' },
52+
rows={[
53+
{ claim: 'One spec, rendered by components you own', api: 'component registry' },
54+
{ claim: 'Both protocols spoken', api: 'json-render + A2UI' },
55+
{ claim: 'Schema on the server, validation in the client', api: 'validated specs' },
6256
]}
6357
cta={{ label: 'See @threadplane/render docs', href: '/docs/render/getting-started/introduction' }}
6458
visual={
@@ -93,16 +87,10 @@ export default async function RenderPage() {
9387
eyebrow="Fallbacks"
9488
headline="Readiness gate + per-component fallback."
9589
body="When the agent emits a spec your registry doesn't know how to render, @threadplane/render falls back gracefully — and surfaces it to your observability layer. No mystery white screens."
96-
bullets={[
97-
'Per-component fallback API',
98-
'Readiness gate holds renders until safe',
99-
'Telemetry hook for render events',
100-
'Streaming partial renders supported',
101-
]}
102-
supportingCards={[
103-
{ title: 'fallback views', description: 'Per-component recovery.' },
104-
{ title: 'readiness gate', description: 'Hold until safe.' },
105-
{ title: 'render events', description: 'Telemetry surface.' },
90+
rows={[
91+
{ claim: 'Unknown components degrade, not crash', api: 'fallback API' },
92+
{ claim: 'Renders hold until the surface is real', api: 'readiness gate' },
93+
{ claim: 'Partial renders while streaming', api: 'streaming specs' },
10694
]}
10795
cta={{ label: 'Fallback patterns', href: '/docs/render/guides/registry' }}
10896
visualLeft

apps/website/src/components/landing/FeatureBlock.spec.tsx

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,21 +24,7 @@ describe('FeatureBlock', () => {
2424
expect(screen.getByText('Your design system, not a chat widget')).toBeTruthy();
2525
expect(screen.getByText('@threadplane/render')).toBeTruthy();
2626
expect(container.querySelector('.feature-block-card-row')).toBeNull();
27-
expect(container.querySelector('.feature-block-bullets')).toBeNull();
27+
expect(container.querySelectorAll('.feature-block-row')).toHaveLength(2);
2828
expect(container.querySelector('.feature-block-rail')).toBeTruthy();
2929
});
30-
31-
it('bullets variant (the five non-home pages) still renders bullets and cards', () => {
32-
const { container } = render(
33-
<FeatureBlock
34-
{...base}
35-
bullets={['First bullet', 'Second bullet']}
36-
supportingCards={[{ title: 'chat-timeline', description: 'Drop-in surface.' }]}
37-
/>,
38-
);
39-
expect(screen.getByText('First bullet')).toBeTruthy();
40-
expect(screen.getByText('chat-timeline')).toBeTruthy();
41-
expect(container.querySelector('.feature-block-rows')).toBeNull();
42-
expect(container.querySelector('.feature-block-rail')).toBeNull();
43-
});
4430
});

apps/website/src/components/landing/FeatureBlock.tsx

Lines changed: 13 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import Link from 'next/link';
33
import { Container } from '../ui/Container';
44
import { Section } from '../ui/Section';
55
import { Eyebrow } from '../ui/Eyebrow';
6-
import { Card } from '../ui/Card';
76

87
export interface FeatureRow {
98
claim: string;
@@ -15,14 +14,10 @@ export interface FeatureBlockProps {
1514
headline: string;
1615
body: ReactNode;
1716
/**
18-
* Rows structure (homepage, spec 2026-08-31): claim left, mono API right,
19-
* in the Yes wall's row grammar. Renders the rail eyebrow and NO cards.
20-
* Mutually exclusive with bullets/supportingCards.
17+
* Rows structure: claim left, mono API right, in the Yes wall's row
18+
* grammar. Renders the rail eyebrow.
2119
*/
22-
rows?: FeatureRow[];
23-
/** Legacy structure — the five non-home consumers. */
24-
bullets?: string[];
25-
supportingCards?: { title: string; description: string }[];
20+
rows: FeatureRow[];
2621
cta: { label: string; href: string };
2722
visual: ReactNode;
2823
/** If true, visual on the left; text on the right. Used to alternate sections. */
@@ -38,8 +33,6 @@ export function FeatureBlock({
3833
headline,
3934
body,
4035
rows,
41-
bullets,
42-
supportingCards,
4336
cta,
4437
visual,
4538
visualLeft = false,
@@ -53,55 +46,24 @@ export function FeatureBlock({
5346
<div className="feature-block-grid" data-visual-left={visualLeft || undefined}>
5447
{/* Text column */}
5548
<div className="feature-block-text">
56-
{rows ? (
57-
<div className="feature-block-rail">
58-
<Eyebrow tone="accent" className="feature-block-eyebrow">{eyebrow}</Eyebrow>
59-
<span className="feature-block-rail-line" aria-hidden="true" />
60-
</div>
61-
) : (
49+
<div className="feature-block-rail">
6250
<Eyebrow tone="accent" className="feature-block-eyebrow">{eyebrow}</Eyebrow>
63-
)}
51+
<span className="feature-block-rail-line" aria-hidden="true" />
52+
</div>
6453
<h2 id={headingId} className="feature-block-heading">
6554
{headline}
6655
</h2>
6756
<p className="feature-block-body">
6857
{body}
6958
</p>
70-
{rows ? (
71-
<div className="feature-block-rows">
72-
{rows.map((row) => (
73-
<div className="feature-block-row" key={row.claim}>
74-
<span className="feature-block-row-claim">{row.claim}</span>
75-
<span className="feature-block-row-api">{row.api}</span>
76-
</div>
77-
))}
78-
</div>
79-
) : (
80-
<>
81-
<ul className="feature-block-bullets">
82-
{(bullets ?? []).map((b) => (
83-
<li key={b} className="feature-block-bullet">
84-
<span aria-hidden="true" className="feature-block-bullet-check">
85-
86-
</span>
87-
<span>{b}</span>
88-
</li>
89-
))}
90-
</ul>
91-
<div className="feature-block-card-row">
92-
{(supportingCards ?? []).map((sc) => (
93-
<Card key={sc.title} padding="md" surface="tinted">
94-
<div className="feature-block-card-title">
95-
{sc.title}
96-
</div>
97-
<div className="feature-block-card-desc">
98-
{sc.description}
99-
</div>
100-
</Card>
101-
))}
59+
<div className="feature-block-rows">
60+
{rows.map((row) => (
61+
<div className="feature-block-row" key={row.claim}>
62+
<span className="feature-block-row-claim">{row.claim}</span>
63+
<span className="feature-block-row-api">{row.api}</span>
10264
</div>
103-
</>
104-
)}
65+
))}
66+
</div>
10567

10668
<Link href={cta.href} className="feature-block-cta">
10769
{cta.label}

0 commit comments

Comments
 (0)