Skip to content

Commit 373ebff

Browse files
bloveclaude
andcommitted
refactor(website): even columns, symmetric margins, and centred arrows in the architecture diagram
Measured the geometry and fixed what it found: the adapters-to-agents gap was 48 against 64 elsewhere, the drawing sat 24px off centre (64 left, 40 right), the users arrow was 28px above the two card centres it joins, the LangSmith rows started 12px above the LangGraph rows beside them, and the People card's content floated at the top of a 392px card. Columns are now 64 apart with 48px margins on all four sides, every arrow is 64 long and lands on the centre of the card it enters, the paired cards share their row positions, and the People block is centred where its arrow leaves. The spec asserts each of those, so they cannot drift back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent e2a3a6c commit 373ebff

3 files changed

Lines changed: 89 additions & 37 deletions

File tree

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ function CardView({ card }: { card: Card }) {
287287
<Icon
288288
name={card.icon.name}
289289
x={card.x + CARD_PAD}
290-
y={card.y + 32}
290+
y={card.y + 32 + (card.contentDy ?? 0)}
291291
size={44}
292292
bg={card.icon.bg}
293293
fg={card.icon.fg}
@@ -315,7 +315,11 @@ function CardView({ card }: { card: Card }) {
315315
</text>
316316
</a>
317317
) : card.icon ? (
318-
<text className="arch-title" x={card.x + CARD_PAD} y={card.y + 116}>
318+
<text
319+
className="arch-title"
320+
x={card.x + CARD_PAD}
321+
y={card.y + 116 + (card.contentDy ?? 0)}
322+
>
319323
{card.title}
320324
</text>
321325
) : (

apps/website/src/lib/architecture-diagram.spec.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
33
import { describe, expect, it } from 'vitest';
44
import {
55
ARROWS,
6+
STRIP_CHIP_H,
67
CARDS,
78
CARD_GAP,
89
CARD_PAD,
@@ -74,19 +75,60 @@ describe('architecture diagram geometry', () => {
7475
expect(card('ag-ui-servers').y).toBe(bottom.y);
7576
});
7677

77-
it('draws every arrow from one card edge to the next card edge, on a grid row', () => {
78+
it('lands every arrow on the vertical centre of the card it enters, at one length', () => {
79+
const lengths = new Set<number>();
7880
for (const a of ARROWS) {
79-
expect(onGrid(a.y), `arrow y ${a.y}`).toBe(true);
8081
const from = CARDS.find(
8182
(c) => c.x + c.width === a.x1 && a.y > c.y && a.y < c.y + c.height
8283
);
83-
const to = CARDS.find(
84-
(c) => c.x === a.x2 && a.y > c.y && a.y < c.y + c.height
85-
);
84+
const to = CARDS.find((c) => c.x === a.x2);
8685
expect(from, `arrow at ${a.x1} leaves a card`).toBeDefined();
8786
expect(to, `arrow at ${a.x2} enters a card`).toBeDefined();
88-
expect(a.y).toBeGreaterThan(from!.y);
89-
expect(a.y).toBeLessThan(from!.y + from!.height);
87+
const enters = CARDS.filter(
88+
(c) => c.x === a.x2 && a.y > c.y && a.y < c.y + c.height
89+
)[0];
90+
expect(a.y, `arrow into ${enters?.id}`).toBe(
91+
enters!.y + enters!.height / 2
92+
);
93+
lengths.add(a.x2 - a.x1);
94+
}
95+
expect([...lengths], 'every arrow is the same length').toHaveLength(1);
96+
});
97+
98+
it('spaces the columns evenly and centres the drawing in the view', () => {
99+
const xs = [...new Set(CARDS.map((c) => c.x))].sort((a, b) => a - b);
100+
const gaps = xs.slice(1).map((x, i) => {
101+
const right = Math.max(
102+
...CARDS.filter((c) => c.x === xs[i]).map((c) => c.x + c.width)
103+
);
104+
return x - right;
105+
});
106+
expect([...new Set(gaps)], `column gaps ${gaps}`).toHaveLength(1);
107+
expect(onGrid(gaps[0])).toBe(true);
108+
const left = xs[0];
109+
const right = VIEW.width - Math.max(...CARDS.map((c) => c.x + c.width));
110+
expect(right, 'left and right margins match').toBe(left);
111+
expect(
112+
VIEW.height - (MODEL_STRIP.chipY + STRIP_CHIP_H),
113+
'bottom margin matches the sides'
114+
).toBe(left);
115+
expect(MODEL_STRIP.x, 'the strip starts at the first column').toBe(left);
116+
});
117+
118+
it('aligns the rows of side-by-side cards', () => {
119+
const firstItemY = (id: string) => {
120+
const r = card(id).rows.find((row) => row.kind === 'items');
121+
return r && r.kind === 'items' ? r.y : null;
122+
};
123+
expect(firstItemY('langsmith')).toBe(firstItemY('langgraph-sdk'));
124+
expect(firstItemY('ag-ui-servers')).toBe(firstItemY('ag-ui'));
125+
// Both rows of the stack share their tops and bottoms across the columns.
126+
for (const [a, b] of [
127+
['langgraph-sdk', 'langsmith'],
128+
['ag-ui', 'ag-ui-servers'],
129+
] as const) {
130+
expect(card(b).y).toBe(card(a).y);
131+
expect(card(b).height).toBe(card(a).height);
90132
}
91133
});
92134

apps/website/src/lib/architecture-diagram.ts

Lines changed: 34 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* of a role, never as integrations the library claims.
1515
*/
1616

17-
export const VIEW = { width: 1280, height: 624 } as const;
17+
export const VIEW = { width: 1280, height: 640 } as const;
1818
export const GRID = 8;
1919
export const MAJOR = 40;
2020
export const CARD_GAP = 40;
@@ -122,6 +122,8 @@ export interface Card {
122122
readonly bg: string;
123123
readonly fg: string;
124124
};
125+
/** Pushes an icon card's icon and title down, to centre a short card's content. */
126+
readonly contentDy?: number;
125127
/** Right-aligned tag on the card's first line. */
126128
readonly tag?: string;
127129
readonly highlight?: boolean;
@@ -145,30 +147,33 @@ export interface StripChip {
145147
}
146148

147149
export const COLUMNS: readonly ColumnLabel[] = [
148-
{ x: 64, label: 'YOUR USERS' },
149-
{ x: 304, label: 'YOUR ANGULAR APPLICATION' },
150-
{ x: 776, label: 'ADAPTERS' },
151-
{ x: 1024, label: 'YOUR AGENTS' },
150+
{ x: 48, label: 'YOUR USERS' },
151+
{ x: 288, label: 'YOUR ANGULAR APPLICATION' },
152+
{ x: 744, label: 'ADAPTERS' },
153+
{ x: 1008, label: 'YOUR AGENTS' },
152154
];
153155
export const COLUMN_LABEL_Y = 72;
154156

155157
export const CARDS: readonly Card[] = [
156158
{
157159
id: 'users',
158-
x: 64,
160+
x: 48,
159161
y: 104,
160162
width: 176,
161163
height: 392,
162164
title: 'People',
163165
href: '/docs/chat/getting-started/introduction',
164166
icon: { name: 'users', bg: '#fff3e0', fg: '#c2410c' },
165-
rows: [{ kind: 'text', y: 248, text: 'web · mobile' }],
167+
// The column's single node: its block is centred on the card, where its
168+
// outgoing arrow leaves.
169+
contentDy: 104,
170+
rows: [{ kind: 'text', y: 352, text: 'web · mobile' }],
166171
},
167172
{
168173
id: 'threadplane',
169-
x: 304,
174+
x: 288,
170175
y: 104,
171-
width: 408,
176+
width: 392,
172177
height: 392,
173178
title: 'Threadplane',
174179
href: '/docs/chat/getting-started/introduction',
@@ -202,8 +207,8 @@ export const CARDS: readonly Card[] = [
202207
},
203208
],
204209
},
205-
{ kind: 'badge', x: 540, y: 224, mark: 'google', label: 'A2UI' },
206-
{ kind: 'badge', x: 540, y: 272, mark: 'vercel', label: 'json-render' },
210+
{ kind: 'badge', x: 524, y: 224, mark: 'google', label: 'A2UI' },
211+
{ kind: 'badge', x: 524, y: 272, mark: 'vercel', label: 'json-render' },
207212
{
208213
kind: 'mono',
209214
y: 470,
@@ -213,7 +218,7 @@ export const CARDS: readonly Card[] = [
213218
},
214219
{
215220
id: 'langgraph-sdk',
216-
x: 776,
221+
x: 744,
217222
y: 104,
218223
width: 200,
219224
height: 208,
@@ -238,7 +243,7 @@ export const CARDS: readonly Card[] = [
238243
},
239244
{
240245
id: 'ag-ui',
241-
x: 776,
246+
x: 744,
242247
y: 352,
243248
width: 200,
244249
height: 144,
@@ -247,39 +252,39 @@ export const CARDS: readonly Card[] = [
247252
href: '/docs/ag-ui/getting-started/introduction',
248253
mark: 'agui',
249254
rows: [
250-
{ kind: 'items', y: 460, step: 32, items: ['events · tools · state'] },
255+
{ kind: 'items', y: 468, step: 32, items: ['events · tools · state'] },
251256
],
252257
},
253258
{
254259
id: 'langsmith',
255-
x: 1024,
260+
x: 1008,
256261
y: 104,
257-
width: 216,
262+
width: 224,
258263
height: 208,
259264
title: 'LangSmith',
260265
href: '/docs/langgraph/guides/deployment',
261266
mark: 'langchain',
262267
rows: [
263268
{
264269
kind: 'items',
265-
y: 200,
270+
y: 212,
266271
step: 32,
267272
items: ['deploy · observe', 'traces · evals', 'or self-hosted'],
268273
},
269274
],
270275
},
271276
{
272277
id: 'ag-ui-servers',
273-
x: 1024,
278+
x: 1008,
274279
y: 352,
275-
width: 216,
280+
width: 224,
276281
height: 144,
277282
title: 'AG-UI servers',
278283
href: '/docs/runtimes/getting-started/introduction',
279284
rows: [
280285
{
281286
kind: 'marks',
282-
y: 400,
287+
y: 408,
283288
marks: ['crewai', 'mastra', 'microsoft', 'bedrock', 'pydantic'],
284289
size: 30,
285290
step: 34,
@@ -294,25 +299,26 @@ export const CARDS: readonly Card[] = [
294299
},
295300
];
296301

302+
/** Each arrow lands on the vertical centre of the card it enters. */
297303
export const ARROWS: readonly Arrow[] = [
298-
{ x1: 240, x2: 304, y: 272 },
299-
{ x1: 712, x2: 776, y: 208 },
300-
{ x1: 712, x2: 776, y: 424 },
301-
{ x1: 976, x2: 1024, y: 208 },
302-
{ x1: 976, x2: 1024, y: 424 },
304+
{ x1: 224, x2: 288, y: 300 },
305+
{ x1: 680, x2: 744, y: 208 },
306+
{ x1: 680, x2: 744, y: 424 },
307+
{ x1: 944, x2: 1008, y: 208 },
308+
{ x1: 944, x2: 1008, y: 424 },
303309
];
304310
/** The two-line caption between the adapter arrows. */
305311
export const CONTRACT_CAPTION = {
306-
x: 744,
312+
x: 712,
307313
y: 306,
308314
lines: ['one Agent', 'contract'],
309315
} as const;
310316

311317
export const MODEL_STRIP = {
312318
label: 'ANY MODEL',
313-
labelY: 540,
319+
labelY: 524,
314320
chipY: 556,
315-
x: 64,
321+
x: 48,
316322
chips: [
317323
{ mark: 'openai', label: 'OpenAI' },
318324
{ mark: 'anthropic', label: 'Anthropic' },

0 commit comments

Comments
 (0)