Skip to content

Commit a38004b

Browse files
authored
feat(website): link hero workflow capabilities to docs (#1082)
1 parent 5fcba51 commit a38004b

6 files changed

Lines changed: 48 additions & 31 deletions

File tree

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
HERO_H1,
88
HERO_SECONDARY_HREF,
99
HERO_SUBHEAD,
10+
HERO_SUBHEAD_SEGMENTS,
1011
} from '../../lib/positioning';
1112

1213
const trackMock = vi.hoisted(() => vi.fn());
@@ -57,10 +58,13 @@ describe('Hero', () => {
5758
expect(screen.getByRole('heading', { level: 1 }).textContent).toBe(HERO_H1);
5859
expect(screen.getByText(HERO_EYEBROW)).toBeTruthy();
5960
expect(document.querySelector('.hero-subhead')?.textContent).toBe(HERO_SUBHEAD);
60-
expect(document.querySelector('.hero-subhead .marker-highlight')?.textContent).toBe(
61-
'Your backend stays where it is.',
62-
);
63-
expect(document.querySelectorAll('.hero-subhead .marker-highlight')).toHaveLength(1);
61+
expect(document.querySelectorAll('.hero-subhead a.marker-highlight')).toHaveLength(5);
62+
for (const segment of HERO_SUBHEAD_SEGMENTS.filter((s) => s.href)) {
63+
const link = screen.getByRole('link', { name: segment.text, exact: true });
64+
expect(link.getAttribute('href')).toBe(segment.href);
65+
expect(link.getAttribute('target')).toBe('_blank');
66+
expect(link.getAttribute('rel')).toBe('noopener noreferrer');
67+
}
6468
expect(document.querySelector('.hero-chip-row')).toBeNull();
6569
expect(screen.queryByText(/six months/)).toBeNull();
6670
expect(screen.queryByRole('link', { name: /Talk to our engineers/ })).toBeNull();

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ export function Hero() {
3939
))}
4040
</h1>
4141
<p className="hero-subhead">
42-
{HERO_SUBHEAD_SEGMENTS.map((segment) =>
43-
segment.highlight ? (
44-
<span className="marker-highlight" key={segment.text}>{segment.text}</span>
42+
{HERO_SUBHEAD_SEGMENTS.map((segment, index) =>
43+
segment.href ? (
44+
<a className="marker-highlight" href={segment.href} target="_blank" rel="noopener noreferrer" key={index}>{segment.text}</a>
4545
) : (
46-
<React.Fragment key={segment.text}>{segment.text}</React.Fragment>
46+
<React.Fragment key={index}>{segment.text}</React.Fragment>
4747
),
4848
)}
4949
</p>

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ describe('positioning: hero copy', () => {
5050
expect(HERO_EYEBROW).toBe('Angular · LangGraph & AG-UI');
5151
expect(HERO_H1).toBe('The open-source thread-plane for agents.');
5252
expect(HERO_SUBHEAD).toBe(
53-
'Chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI. Your backend stays where it is.',
53+
'Make agent work persistent, durable, visible, reviewable, and resumable.',
5454
);
5555
expect(HOME_TITLE).toBe('Threadplane — The open-source thread-plane for agents');
5656
expect(HOME_DESCRIPTION).toBe(
@@ -64,15 +64,15 @@ describe('positioning: hero copy', () => {
6464
expect(HERO_H1_LINES.join(' ')).toBe(HERO_H1);
6565
});
6666

67-
it('subhead segments join back to HERO_SUBHEAD, with exactly one highlight', () => {
68-
// The segments exist only so Hero.tsx can marker-highlight one phrase.
69-
// If they ever stop reassembling the source-of-truth string, the rendered
70-
// subhead silently diverges from the copy every other surface quotes.
67+
it('subhead segments preserve the copy and link each capability to its docs', () => {
7168
expect(HERO_SUBHEAD_SEGMENTS.map((s) => s.text).join('')).toBe(HERO_SUBHEAD);
72-
expect(HERO_SUBHEAD_SEGMENTS.filter((s) => s.highlight)).toHaveLength(1);
73-
expect(HERO_SUBHEAD_SEGMENTS.find((s) => s.highlight)?.text).toBe(
74-
'Your backend stays where it is.',
75-
);
69+
expect(HERO_SUBHEAD_SEGMENTS.filter((s) => s.href)).toEqual([
70+
{ text: 'persistent', href: '/docs/langgraph/guides/persistence' },
71+
{ text: 'durable', href: '/docs/langgraph/guides/persistence#choosing-a-checkpointer' },
72+
{ text: 'visible', href: '/docs/chat/components/chat-tool-calls' },
73+
{ text: 'reviewable', href: '/docs/langgraph/guides/interrupts' },
74+
{ text: 'resumable', href: '/docs/langgraph/guides/persistence#checkpoint-recovery' },
75+
]);
7676
});
7777

7878
it('pins the hero action labels and the secondary destination', () => {

apps/website/src/lib/positioning.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,30 @@ export const HERO_H1 = 'The open-source thread-plane for agents.';
1212
*/
1313
export const HERO_H1_LINES: readonly string[] = ['The open-source', 'thread-plane', 'for agents.'];
1414
export const HERO_SUBHEAD =
15-
'Chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI. Your backend stays where it is.';
15+
'Make agent work persistent, durable, visible, reviewable, and resumable.';
1616

1717
/**
18-
* The subhead, split so Hero.tsx can marker-highlight the boundary claim.
18+
* The subhead, split so each workflow capability links to its documentation.
1919
* HERO_SUBHEAD stays the single source of truth: positioning.spec.ts asserts
2020
* these segments join back to it character for character, so the two cannot
21-
* drift. Exactly one segment is highlighted — a second one in a sentence this
22-
* short reads as decoration and cancels the emphasis.
21+
* drift. Linked terms retain the hero's ink underline treatment.
2322
*/
2423
export interface HeroSubheadSegment {
2524
text: string;
26-
highlight?: boolean;
25+
href?: string;
2726
}
2827
export const HERO_SUBHEAD_SEGMENTS: readonly HeroSubheadSegment[] = [
29-
{ text: 'Chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI. ' },
30-
{ text: 'Your backend stays where it is.', highlight: true },
28+
{ text: 'Make agent work ' },
29+
{ text: 'persistent', href: '/docs/langgraph/guides/persistence' },
30+
{ text: ', ' },
31+
{ text: 'durable', href: '/docs/langgraph/guides/persistence#choosing-a-checkpointer' },
32+
{ text: ', ' },
33+
{ text: 'visible', href: '/docs/chat/components/chat-tool-calls' },
34+
{ text: ', ' },
35+
{ text: 'reviewable', href: '/docs/langgraph/guides/interrupts' },
36+
{ text: ', and ' },
37+
{ text: 'resumable', href: '/docs/langgraph/guides/persistence#checkpoint-recovery' },
38+
{ text: '.' },
3139
];
3240

3341
export const HERO_PRIMARY_LABEL = 'Install Threadplane';

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('site positioning copy', () => {
2222
expect(LONG_SUBHEAD).toContain('open-source thread-plane for agents');
2323
expect(LONG_SUBHEAD).toContain('LangGraph and AG-UI');
2424
expect(HERO_SUBHEAD).toBe(
25-
'Chat, durable threads, persistence, human approvals, and generative UI for Angular, on LangGraph and AG-UI. Your backend stays where it is.',
25+
'Make agent work persistent, durable, visible, reviewable, and resumable.',
2626
);
2727
expect(POSITIONING_PROOF_POINTS.map((p) => p.label)).toEqual([
2828
'LangGraph + AG-UI',

apps/website/src/styles/landing.css

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,21 @@
8484
}
8585
.hero-stack .hero-subhead {
8686
max-width: 40em;
87+
word-spacing: 0.16em;
8788
}
88-
/* The centered hero wraps the highlighted boundary claim mid-phrase — measured
89-
* at 768px it orphaned a lone "Your" in its own pill at the end of line 1, and
90-
* at 1440px it split into two staggered boxes. inline-block makes the phrase
91-
* wrap as one unit, so it drops whole onto its own line instead. max-width is
92-
* the safety valve: below ~360px the phrase is wider than the measure, and
93-
* without it a shrink-to-fit box would overflow the page rather than wrap. */
89+
/* Keep each documentation link and its ink underline together when wrapping. */
9490
.hero-stack .hero-subhead .marker-highlight {
9591
display: inline-block;
9692
max-width: 100%;
93+
text-decoration: none;
94+
word-spacing: normal;
95+
}
96+
.hero-stack .hero-subhead a.marker-highlight:hover {
97+
box-shadow: inset 0 -5px 0 var(--color-ink);
98+
}
99+
.hero-stack .hero-subhead a.marker-highlight:focus-visible {
100+
outline: 2px solid var(--color-ink);
101+
outline-offset: 5px;
97102
}
98103
.hero-stack .hero-cta-row {
99104
justify-content: center;

0 commit comments

Comments
 (0)