Skip to content
Merged
104 changes: 104 additions & 0 deletions apps/website/e2e/home-architecture.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { test, expect, type Page } from '@playwright/test';
import { CARDS, diagramHrefs } from '../src/lib/architecture-diagram';

const DIAGRAM = '[data-diagram="enterprise-architecture"]';

/**
* Measures every rendered text run and chip in the diagram against the card
* that owns it, in viewBox units (getBBox reports user space, so the numbers
* compare directly with lib/architecture-diagram.ts). The unit spec proves
* the boxes are on the grid; this proves the type set inside them fits.
*/
async function overflowReport(page: Page) {
return page.evaluate((sel) => {
const issues: string[] = [];
for (const a of document.querySelectorAll<SVGElement>(
`${sel} [data-card]`
)) {
const g = a.querySelector<SVGGElement>('[data-card-rect]');
if (!g) continue;
const cx = +g.dataset['x']!;
const cy = +g.dataset['y']!;
const cw = +g.dataset['w']!;
const ch = +g.dataset['h']!;
for (const t of a.querySelectorAll<SVGTextElement>('text')) {
const b = t.getBBox();
if (
b.x < cx + 6 ||
b.x + b.width > cx + cw - 6 ||
b.y < cy + 4 ||
b.y + b.height > cy + ch - 4
) {
issues.push(
`${a.dataset['card']}: "${t.textContent?.slice(
0,
40
)}" ${Math.round(b.x)}..${Math.round(b.x + b.width)} vs ${cx}..${
cx + cw
}`
);
}
}
for (const r of a.querySelectorAll<SVGRectElement>('[data-chip] rect')) {
const b = r.getBBox();
if (b.x + b.width > cx + cw - 6 || b.y + b.height > cy + ch - 8) {
issues.push(`${a.dataset['card']}: chip past the card edge`);
}
}
}
const images = [
...document.querySelectorAll<SVGImageElement>(`${sel} image`),
].map((i) => i.getBBox().width);
return { issues, images };
}, DIAGRAM);
}

test.describe('homepage architecture', () => {
test('replaces the scope table, links every card to its docs page, and keeps every text run inside its card', async ({
page,
}) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto('/');
await expect(page.locator('#architecture-heading')).toHaveText(
'The UI layer between your users and your agents.'
);
await expect(page.locator('#why-heading')).toHaveCount(0);
const cards = page.locator(`${DIAGRAM} [data-card]`);
await expect(cards).toHaveCount(CARDS.length);
for (const c of CARDS) {
const card = page.locator(`${DIAGRAM} [data-card="${c.id}"]`);
const link =
(await card.evaluate((el) => el.tagName.toLowerCase())) === 'a'
? card
: card.locator('a.arch-title-link');
await expect(link).toHaveAttribute('href', c.href);
}
const hrefs = await page
.locator(`${DIAGRAM} a[href]`)
.evaluateAll((els) => els.map((e) => e.getAttribute('href')));
for (const href of diagramHrefs()) expect(hrefs).toContain(href);

// Fonts must be loaded before measuring, or a fallback face lies about widths.
await page.evaluate(() => document.fonts.ready);
const report = await overflowReport(page);
expect(report.issues, report.issues.join('\n')).toEqual([]);
expect(report.images.length).toBeGreaterThanOrEqual(15);
for (const w of report.images) expect(w).toBeGreaterThan(0);
});

test('stacks the same cards on a phone instead of scrolling the drawing sideways', async ({
page,
}) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto('/');
const stack = page.locator(`${DIAGRAM} [data-arch-stack]`);
await stack.scrollIntoViewIfNeeded();
await expect(stack).toBeVisible();
await expect(page.locator(`${DIAGRAM} .tp-diagram-figure`)).toBeHidden();
await expect(stack.locator('a.arch-stack-card')).toHaveCount(CARDS.length);
const wide = await page.evaluate(
() => document.documentElement.scrollWidth > window.innerWidth
);
expect(wide, 'no horizontal page scroll on a phone').toBe(false);
});
});
2 changes: 1 addition & 1 deletion apps/website/e2e/website.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ test('landing page renders the spine in order (live-stage spec §3)', async ({ p
const ids = [
'hero-heading',
'proof-heading',
'why-heading',
'architecture-heading',
'stage-heading',
'final-cta-heading',
'pilot-heading',
Expand Down
1 change: 1 addition & 0 deletions apps/website/public/logos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ These were downloaded from `https://cdn.simpleicons.org/<slug>/111827` for a com

- `runtimes/crewai.svg` from slug `crewai`.
- `runtimes/pydantic.svg` from slug `pydantic`.
- `langchain.svg` from slug `langchain`, used for the LangSmith card on the homepage architecture diagram (Simple Icons has no LangSmith slug; LangSmith is a LangChain product in the same mark family).
- `surface/angular.svg` from slug `angular`.
- `surface/reactivex.svg` from slug `reactivex`.
- `surface/vercel.svg` from slug `vercel`.
Expand Down
1 change: 1 addition & 0 deletions apps/website/public/logos/langchain.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/website/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Hero } from '../components/landing/Hero';
import { Reliability } from '../components/landing/Reliability';
import { ScopeTable } from '../components/landing/ScopeTable';
import { EnterpriseArchitecture } from '../components/landing/EnterpriseArchitecture';
import { Stage } from '../components/landing/Stage';
import { TeamsBlock } from '../components/landing/TeamsBlock';
import { HomeFAQ } from '../components/landing/HomeFAQ';
Expand Down Expand Up @@ -34,7 +34,7 @@ export default function HomePage() {
<>
<Hero />
<Reliability />
<ScopeTable />
<EnterpriseArchitecture />

{/* The four capability beats (stream, persist, approve, render): stills
by default, the pinned live act on wide, motion-tolerant viewports
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// @vitest-environment jsdom
import React from 'react';
import { render } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import {
EnterpriseArchitecture,
ARCHITECTURE_HEADLINE,
} from './EnterpriseArchitecture';
import {
CARDS,
COLUMNS,
MODEL_STRIP,
diagramHrefs,
} from '../../lib/architecture-diagram';

describe('EnterpriseArchitecture', () => {
it('renders the section with its heading id, every column label, card, and link', () => {
render(<EnterpriseArchitecture />);
expect(document.querySelector('section#architecture')).not.toBeNull();
expect(document.querySelector('#architecture-heading')?.textContent).toBe(
ARCHITECTURE_HEADLINE
);
expect(document.querySelectorAll('[data-column]')).toHaveLength(
COLUMNS.length
);
expect(document.querySelectorAll('[data-card]')).toHaveLength(CARDS.length);
const hrefs = new Set(
[...document.querySelectorAll('a[href]')].map((a) =>
a.getAttribute('href')
)
);
for (const href of diagramHrefs()) expect(hrefs.has(href), href).toBe(true);
});

it('highlights Threadplane and the first-class LangGraph lane, and nests no anchors', () => {
render(<EnterpriseArchitecture />);
expect(
document.querySelector('[data-card="threadplane"] .arch-card--tp')
).not.toBeNull();
expect(
document.querySelector('[data-card="langgraph-sdk"] .arch-card--tp')
).not.toBeNull();
expect(
document.querySelector('[data-card="ag-ui"] .arch-card--tp')
).toBeNull();
expect(document.querySelector('a a')).toBeNull();
// The Threadplane card's title is the link; its capabilities are links of their own.
expect(
document.querySelector('[data-card="threadplane"] a.arch-title-link')
).not.toBeNull();
expect(
document.querySelectorAll('[data-card="threadplane"] a.arch-cap')
).toHaveLength(5);
});

it('draws every card rect at the data module coordinates and the model strip chips', () => {
render(<EnterpriseArchitecture />);
for (const c of CARDS) {
const rect = document.querySelector(
`[data-card="${c.id}"] rect.arch-card`
);
expect(rect?.getAttribute('x')).toBe(String(c.x));
expect(rect?.getAttribute('y')).toBe(String(c.y));
expect(rect?.getAttribute('width')).toBe(String(c.width));
expect(rect?.getAttribute('height')).toBe(String(c.height));
}
expect(
document.querySelectorAll('[data-model-strip] [data-chip]')
).toHaveLength(MODEL_STRIP.chips.length);
});

it('shows the alignment grid only when asked', () => {
render(<EnterpriseArchitecture />);
expect(document.querySelector('[data-alignment-grid]')).toBeNull();
document.body.innerHTML = '';
render(<EnterpriseArchitecture grid />);
expect(document.querySelector('[data-alignment-grid]')).not.toBeNull();
});
});
Loading
Loading