Skip to content

Commit d0376cb

Browse files
bloveclaude
andauthored
copy(website): tagline is "The open-source thread-plane for agents." (#1040)
* copy(website): tagline is "The open-source thread plane for enterprise agents." The hero H1, <title>, meta description, OG/Twitter defaults, OG image alt, About page, root and library READMEs, and the gtm docs all move from "The AI agent UI framework for Angular." to the new tagline. The site description becomes the 159-character sentence that follows it everywhere. The hero eyebrow drops "Open-source" so the hero does not say it twice. Tests that pinned the old strings pin the new ones. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * copy(website): tagline is "The open-source thread-plane for agents." on three lines Drops "enterprise" (gtm.md already lists it as reading sales-first) and hyphenates thread-plane. HERO_H1_LINES breaks the H1 where it is meant to break; a spec asserts the lines rejoin to HERO_H1, and both the hero and the OG card render from the lines. The description drops "enterprise" to match. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * test(website): repoint the two hero e2e guards at the new tagline The H1 no longer names Angular, so the guard that the hero says which framework this is moves to the eyebrow above it. The three-line H1 also leaves the demo stage short of the 25% visibility threshold that mounts the frame at the default viewport, so the desktop mount test scrolls it into view first, as the mobile sibling already does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent f9b3792 commit d0376cb

20 files changed

Lines changed: 103 additions & 35 deletions

File tree

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<p align="center">
22
<img
33
src="https://threadplane.ai/assets/hero.svg"
4-
alt="Threadplane — the AI agent UI framework for Angular"
4+
alt="Threadplane — the open-source thread-plane for agents"
55
width="100%"
66
/>
77
</p>
88

99
<p align="center">
10-
<em>The AI agent UI framework for Angular.</em>
10+
<em>The open-source thread-plane for agents.</em>
1111
</p>
1212

1313
<p align="center">
@@ -33,8 +33,8 @@
3333

3434
---
3535

36-
**Threadplane is the open-source Angular AI agent UI framework.** Chat, durable
37-
threads, human approvals, tool progress, subagents, and generative UI — built on
36+
**Threadplane is the open-source thread-plane for agents.** Chat, durable
37+
threads, persistence, human approvals, tool progress, subagents, and generative UI — built on
3838
Angular Signals and dependency injection, for Angular 20–22. Your backend stays
3939
where it is: Threadplane adapts a LangGraph or AG-UI agent into a runtime-neutral
4040
`Agent` contract that the UI consumes, and renders generated UI with the design

apps/website/e2e/home-hero.spec.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ test.describe('homepage hero', () => {
2424
await page.goto('/');
2525
const demo = page.locator('[data-hero-demo]');
2626
await expect(demo.locator('img')).toHaveAttribute('src', '/screenshots/hero-walkthrough-poster.webp');
27+
// The frame mounts on a 25%-visibility threshold, and the three-line H1
28+
// leaves the stage short of that at the default viewport. Scroll it in, as
29+
// the mobile sibling below already does, so this asserts the mount and not
30+
// the fold position.
31+
await demo.scrollIntoViewIfNeeded();
2732
await expect(demo.locator('iframe')).toHaveAttribute('src', 'https://demo.threadplane.ai/hero');
2833
});
2934

apps/website/e2e/website.spec.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ async function expectNoHorizontalOverflow(
2121
test('landing page renders hero headline', async ({ page }) => {
2222
await page.goto('/');
2323
await expect(page.locator('#hero-heading')).toBeVisible();
24-
await expect(page.locator('#hero-heading')).toHaveText('The AI agent UI framework for Angular.');
25-
const headline = await page.locator('#hero-heading').textContent();
26-
expect(headline?.toLowerCase()).toContain('angular');
24+
await expect(page.locator('#hero-heading')).toHaveText('The open-source thread-plane for agents.');
25+
// The tagline stopped naming Angular on 2026-09-06, so the guard that the
26+
// hero says which framework this is moved to the eyebrow directly above it.
27+
await expect(page.locator('.hero-eyebrow')).toContainText('Angular');
2728
});
2829

2930
test('landing page renders the dark proof band', async ({ page }) => {

apps/website/src/app/opengraph-image.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
* If either changes, re-resolve them here.
3333
*/
3434
import { ImageResponse } from 'next/og';
35-
import { HERO_H1, HERO_SUBHEAD, POSITIONING_PROOF_POINTS, PRIMARY_TAGLINE } from '../lib/positioning';
35+
import { HERO_H1_LINES, HERO_SUBHEAD, POSITIONING_PROOF_POINTS, PRIMARY_TAGLINE } from '../lib/positioning';
3636
import { loadCardFonts } from './og-font';
3737

3838
// Node runtime (not edge) so we can read the bundled Garamond TTF off disk.
@@ -159,6 +159,8 @@ export default async function OpenGraphImage() {
159159
<div
160160
style={{
161161
display: 'flex',
162+
flexDirection: 'column',
163+
alignItems: 'center',
162164
marginTop: 34,
163165
fontSize: 52,
164166
fontWeight: 600,
@@ -167,7 +169,9 @@ export default async function OpenGraphImage() {
167169
color: TOKENS.textPrimary,
168170
}}
169171
>
170-
{HERO_H1}
172+
{HERO_H1_LINES.map((line) => (
173+
<div key={line} style={{ display: 'flex' }}>{line}</div>
174+
))}
171175
</div>
172176

173177
{/* What you get. Wrapped to two lines on purpose — see the header. */}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { Button } from '../ui/Button';
88
import { trackCtaClick } from '../../lib/analytics/client';
99
import {
1010
HERO_EYEBROW,
11-
HERO_H1,
11+
HERO_H1_LINES,
1212
HERO_PRIMARY_LABEL,
1313
HERO_SECONDARY_HREF,
1414
HERO_SECONDARY_LABEL,
@@ -31,7 +31,14 @@ export function Hero() {
3131
<Container>
3232
<div className="hero-stack">
3333
<Eyebrow tone="accent" className="hero-eyebrow">{HERO_EYEBROW}</Eyebrow>
34-
<h1 id="hero-heading" className="hero-heading">{HERO_H1}</h1>
34+
<h1 id="hero-heading" className="hero-heading">
35+
{HERO_H1_LINES.map((line, i) => (
36+
<React.Fragment key={line}>
37+
{i > 0 ? ' ' : ''}
38+
<span className="hero-heading-line">{line}</span>
39+
</React.Fragment>
40+
))}
41+
</h1>
3542
<p className="hero-subhead">
3643
{HERO_SUBHEAD_SEGMENTS.map((segment) =>
3744
segment.highlight ? (

apps/website/src/lib/positioning.spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
formatAngularRange,
1111
HERO_EYEBROW,
1212
HERO_H1,
13+
HERO_H1_LINES,
1314
HERO_PRIMARY_LABEL,
1415
HERO_SECONDARY_HREF,
1516
HERO_SECONDARY_LABEL,
@@ -46,16 +47,21 @@ function parses(code: string): boolean {
4647

4748
describe('positioning: hero copy', () => {
4849
it('names the exact category in eyebrow, H1, title and description', () => {
49-
expect(HERO_EYEBROW).toBe('Open-source · Angular · LangGraph & AG-UI');
50-
expect(HERO_H1).toBe('The AI agent UI framework for Angular.');
50+
expect(HERO_EYEBROW).toBe('Angular · LangGraph & AG-UI');
51+
expect(HERO_H1).toBe('The open-source thread-plane for agents.');
5152
expect(HERO_SUBHEAD).toBe('Chat, threads, approvals, and generative UI on Signals and DI. Your backend stays where it is.');
52-
expect(HOME_TITLE).toBe('Threadplane — Angular AI Agent UI Framework');
53+
expect(HOME_TITLE).toBe('Threadplane — The open-source thread-plane for agents');
5354
expect(HOME_DESCRIPTION).toBe(
54-
'Open-source Angular AI agent UI framework for LangGraph and AG-UI: chat, durable threads, human approvals, and generative UI with Signals and DI.',
55+
'The open-source thread-plane for agents: chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI.',
5556
);
5657
expect(HOME_DESCRIPTION.length).toBeLessThanOrEqual(160);
5758
});
5859

60+
it('H1 lines join back to HERO_H1 on three lines', () => {
61+
expect(HERO_H1_LINES).toHaveLength(3);
62+
expect(HERO_H1_LINES.join(' ')).toBe(HERO_H1);
63+
});
64+
5965
it('subhead segments join back to HERO_SUBHEAD, with exactly one highlight', () => {
6066
// The segments exist only so Hero.tsx can marker-highlight one phrase.
6167
// If they ever stop reassembling the source-of-truth string, the rendered

apps/website/src/lib/positioning.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@
22
import { WEBSITE_SUPPORTED_ANGULAR_MAJORS } from '../components/pricing/angular-support.mjs';
33
import type { StageBeat } from './stage-beats';
44

5-
export const HERO_EYEBROW = 'Open-source · Angular · LangGraph & AG-UI';
6-
export const HERO_H1 = 'The AI agent UI framework for Angular.';
5+
export const HERO_EYEBROW = 'Angular · LangGraph & AG-UI';
6+
export const HERO_H1 = 'The open-source thread-plane for agents.';
7+
/**
8+
* The H1 broken where it is meant to break: three lines, one thought each.
9+
* HERO_H1 stays the single source of truth — positioning.spec.ts asserts the
10+
* lines join back to it with single spaces, so the rendered heading, the
11+
* <title> and the social card cannot drift apart.
12+
*/
13+
export const HERO_H1_LINES: readonly string[] = ['The open-source', 'thread-plane', 'for agents.'];
714
export const HERO_SUBHEAD =
815
'Chat, threads, approvals, and generative UI on Signals and DI. Your backend stays where it is.';
916

@@ -28,13 +35,13 @@ export const HERO_SECONDARY_LABEL = 'See it running in the docs →';
2835
export const HERO_SECONDARY_HREF = '/docs/chat/guides/generative-ui?mode=run';
2936

3037
/** Kept for layout.tsx default title and the OG image alt. */
31-
export const PRIMARY_TAGLINE = 'Threadplane — Angular AI Agent UI Framework';
38+
export const PRIMARY_TAGLINE = 'Threadplane — The open-source thread-plane for agents';
3239
export const HOME_TITLE = PRIMARY_TAGLINE;
3340
export const HOME_DESCRIPTION =
34-
'Open-source Angular AI agent UI framework for LangGraph and AG-UI: chat, durable threads, human approvals, and generative UI with Signals and DI.';
41+
'The open-source thread-plane for agents: chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI.';
3542
/** Longer form used by layout.tsx OG/Twitter defaults and the About page. */
3643
export const LONG_SUBHEAD =
37-
'Threadplane is the open-source Angular AI agent UI framework: signal-native chat, durable threads, human approvals, tool progress, subagents, and generative UI for LangGraph and AG-UI backends — without replacing your backend or design system.';
44+
'Threadplane is the open-source thread-plane for agents: signal-native chat, durable threads, persistence, human approvals, tool progress, subagents, and generative UI for Angular, on LangGraph and AG-UI — without replacing your backend or design system.';
3845

3946
// ── Trust line (values verified by positioning.spec.ts + angular-support-copy.spec.ts) ──
4047
export function formatAngularRange(majors: readonly number[]): string {

apps/website/src/lib/site-metadata.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ import { resolveWebsiteDir } from './website-dir';
1818

1919
describe('site positioning copy', () => {
2020
it('exports the approved primary tagline and supporting copy', () => {
21-
expect(PRIMARY_TAGLINE).toBe('Threadplane — Angular AI Agent UI Framework');
22-
expect(LONG_SUBHEAD).toContain('open-source Angular AI agent UI framework');
21+
expect(PRIMARY_TAGLINE).toBe('Threadplane — The open-source thread-plane for agents');
22+
expect(LONG_SUBHEAD).toContain('open-source thread-plane for agents');
2323
expect(LONG_SUBHEAD).toContain('LangGraph and AG-UI');
2424
expect(HERO_SUBHEAD).toBe(
2525
'Chat, threads, approvals, and generative UI on Signals and DI. Your backend stays where it is.',
@@ -65,7 +65,7 @@ describe('site positioning copy', () => {
6565
type: 'website',
6666
});
6767

68-
expect(metadata.title).toBe('Threadplane — Angular AI Agent UI Framework');
68+
expect(metadata.title).toBe('Threadplane — The open-source thread-plane for agents');
6969
expect(metadata.description).toBe(HOME_DESCRIPTION);
7070
});
7171
});
@@ -142,7 +142,7 @@ describe('createPageMetadata article fields', () => {
142142
expect(openGraph.images[0].url).toBe('/opengraph-image');
143143
expect(openGraph.images[0].width).toBe(1200);
144144
expect(openGraph.images[0].height).toBe(630);
145-
expect(openGraph.images[0].alt).toMatch(/agent UI framework for Angular/);
145+
expect(openGraph.images[0].alt).toMatch(/open-source thread-plane for agents/);
146146
});
147147

148148
it('accepts a page-specific social image', () => {

apps/website/src/lib/site-metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const DEFAULT_SOCIAL_IMAGE_META = {
2020
url: DEFAULT_SOCIAL_IMAGE,
2121
width: 1200,
2222
height: 630,
23-
alt: 'Threadplane — the AI agent UI framework for Angular. Chat, threads, approvals, and generative UI on Signals and DI, for LangGraph and AG-UI.',
23+
alt: 'Threadplane — the open-source thread-plane for agents. Chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI.',
2424
} as const;
2525
export {
2626
CODING_AGENT_PROMPT,

apps/website/src/styles/landing.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
margin-bottom: 24px;
2424
letter-spacing: -0.02em;
2525
}
26+
/* One line per thought; the space between the spans keeps textContent equal to HERO_H1. */
27+
.hero-heading-line {
28+
display: block;
29+
}
2630
.hero-subhead {
2731
font-family: var(--font-inter);
2832
font-size: var(--text-body-lg);

0 commit comments

Comments
 (0)