Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e00219c
docs: spec for customer-facing concept graphics
blove Sep 2, 2026
2b3e54c
docs: implementation plan for customer concept graphics
blove Sep 2, 2026
f422138
feat(website): compact diagram scale + mono meta variant
blove Sep 2, 2026
c64b6cf
fix(website): compact scale — no scroll chrome, 420px cap, cascade notes
blove Sep 2, 2026
9ebd9f3
refactor(website): generic DiagramSection; StackDiagramSection wraps it
blove Sep 2, 2026
5f01811
feat(website): Stream + Render compact concept diagrams
blove Sep 2, 2026
1137d10
fix(website): RenderConcept bottom margin — viewHeight 240
blove Sep 2, 2026
1b5d1ba
fix(website): concept cards — payoff accent, uniform 240 height, hone…
blove Sep 2, 2026
2abb6ef
feat(website): Approve + Ship compact concept diagrams
blove Sep 2, 2026
a7532c9
fix(website): concept-card family — payoff accents, neutral pills, lo…
blove Sep 2, 2026
d1a05b2
feat(website): homepage How-it-works concept grid
blove Sep 2, 2026
be4c8ff
fix(website): honest Ship card sentence — durability is the backend's…
blove Sep 2, 2026
6febe1f
fix(website): concept-grid copy — distinct Render sentence, tighter S…
blove Sep 2, 2026
3701b01
feat(website): render transform graphic on /render
blove Sep 2, 2026
d836f3c
fix(website): RenderTransform at kit-standard 640; concrete headline
blove Sep 2, 2026
7aa84f9
feat(website): pilot journey graphic on /pilot-to-prod
blove Sep 2, 2026
4030932
fix(website): PilotJourney — symmetric margins, handoff gate, page-fa…
blove Sep 2, 2026
062298a
fix(website): communication-audit fixes for concept graphics
blove Sep 2, 2026
4e33661
docs(website): diagram-kit tone semantics + compact pill rhythm
blove Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/website/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LogoRibbon } from '../components/landing/LogoRibbon';
import { YesWall } from '../components/landing/YesWall';
import { FeatureBlock } from '../components/landing/FeatureBlock';
import { StackDiagramSection } from '../components/landing/StackDiagramSection';
import { HomeConceptGrid } from '../components/landing/HomeConceptGrid';
import { DemoShowcase } from '../components/landing/DemoShowcase';
import { MediumSwitcher } from '../components/landing/MediumSwitcher';
import { SECTION_MEDIA } from '../lib/section-media';
Expand Down Expand Up @@ -46,6 +47,8 @@ export default async function HomePage() {
caption="The chat surface never imports a runtime SDK — only the contract."
/>

<HomeConceptGrid />

{/* Interactive demo showcase */}
<Section surface="canvas">
<Container>
Expand Down
11 changes: 11 additions & 0 deletions apps/website/src/app/pilot-to-prod/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { BrowserFrame } from '../../components/ui/BrowserFrame';
import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock';
import { Promises } from '../../components/landing/Promises';
import { FinalCTA } from '../../components/landing/FinalCTA';
import { DiagramSection } from '../../components/landing/DiagramSection';
import { PilotJourney } from '../../components/docs/diagrams';
import { createPageMetadata } from '../../lib/site-metadata';

export const metadata = createPageMetadata({
Expand Down Expand Up @@ -45,6 +47,15 @@ export default function PilotToProdPage() {
</Container>
</Section>

<DiagramSection
id="pilot-journey"
eyebrow="The engagement"
headline="Three phases, each with a gate you can point at"
body="No open-ended consulting arc: each phase ends with a concrete deliverable — a roadmap, a working agent, an on-call runbook — not a status update."
>
<PilotJourney />
</DiagramSection>

{/* Discover */}
<FeatureBlock
id="discover"
Expand Down
11 changes: 11 additions & 0 deletions apps/website/src/app/render/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { FeatureBlock } from '../../components/landing/FeatureBlock';
import { WhitePaperBlock } from '../../components/landing/WhitePaperBlock';
import { FinalCTA } from '../../components/landing/FinalCTA';
import { MediumSwitcher } from '../../components/landing/MediumSwitcher';
import { DiagramSection } from '../../components/landing/DiagramSection';
import { RenderTransform } from '../../components/docs/diagrams';
import { RenderCodeShowcase } from '../../components/landing/render/RenderCodeShowcase';
import { createPageMetadata } from '../../lib/site-metadata';
import { SECTION_MEDIA } from '../../lib/section-media';
Expand Down Expand Up @@ -51,6 +53,15 @@ export default async function RenderPage() {
</Container>
</Section>

<DiagramSection
id="render-transform"
eyebrow="How it works"
headline="Schema on the wire, your design system on screen"
body="The agent emits a spec. Your registry, state, and handlers resolve it — and your own Angular components render it."
>
<RenderTransform />
</DiagramSection>

<FeatureBlock
id="schemas"
eyebrow="Schemas"
Expand Down
63 changes: 63 additions & 0 deletions apps/website/src/components/docs/diagrams/ApproveConcept.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
import { DiagramFrame } from './DiagramFrame';
import { DiagramNode } from './DiagramNode';
import { DiagramEdge } from './DiagramEdge';
import { DiagramPill } from './DiagramPill';

const SLUG = 'concept-approve';

/**
* Homepage concept card: nothing irreversible without a human — the agent
* proposes an action, an `interrupt` pauses for a human decision, and
* `resume` sends the agent on with the decision (interrupts.mdx lifecycle:
* Agent Plans → Interrupt Fires → UI Shows Dialog → User Decides → Agent
* Resumes).
*
* Kept runtime-neutral per the design spec's CLAIMS table: both the LangGraph
* and AG-UI interrupts guides use the same "agent pauses, hands control to a
* human, resumes with the decision" language, so the card names neither
* runtime and never claims durability (that's LangGraph-checkpoint-specific;
* the AG-UI path differs).
*
* Geometry (viewHeight 240, centered): row 1 is a 52px-tall pair of nodes
* broken by the `interrupt` pill (Agent → interrupt → Human, human accented
* as the payoff — the buyer's control point). Human's right edge (x=214,
* w=90 → 304) aligns with the outcome node's right edge (x=16, w=288 → 304).
* A loop drops from the human node's bottom-mid (x=259), jogs 6px above the
* `resume` pill's top face before dropping into it (avoids butting into the
* pill along its border), and breaks around the pill into a 44px outcome
* node. Content height 52 + 48 (12 segment + 24 pill + 8 segment + 4
* arrow-fill) + 44 = 144; top/bottom margins split the remaining 96 evenly
* (48 each), so the stack sits dead-center in the 240 frame.
*/
export function ApproveConcept() {
return (
<DiagramFrame
slug={SLUG}
viewWidth={320}
viewHeight={240}
scale="compact"
label="Approval concept: the agent plans an action, an interrupt pauses for a human decision, and the agent resumes with the decision."
>
<DiagramNode x={16} y={48} w={90} h={52} title="Agent" align="middle" titleStyle="sans" tone="dim" />
<DiagramEdge d="M106 74 H118" slug={SLUG} />
<DiagramPill cx={155} cy={74} w={74} label="interrupt" tone="neutral" />
<DiagramEdge d="M192 74 H210" slug={SLUG} arrow />
<DiagramNode x={214} y={48} w={90} h={52} title="Human" align="middle" titleStyle="sans" tone="accent" />

<DiagramEdge d="M259 100 V106 H160 V112" slug={SLUG} />
<DiagramPill cx={160} cy={124} w={56} label="resume" tone="neutral" />
<DiagramEdge d="M160 136 V144" slug={SLUG} arrow />

<DiagramNode
x={16}
y={148}
w={288}
h={44}
title="Resumes with the decision"
align="middle"
titleStyle="sans"
/>
</DiagramFrame>
);
}
14 changes: 12 additions & 2 deletions apps/website/src/components/docs/diagrams/DiagramFrame.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,18 @@ interface DiagramFrameProps {
/** Accessible one-sentence description of what the diagram shows. */
label: string;
caption?: string;
/** Marketing pages render the same SVG larger. */
scale?: 'docs' | 'marketing';
/**
* Marketing pages render the same SVG larger; compact cards render it small with a bigger type
* ramp. Compact compositions author at a ~320 viewBox with the compact type ramp (eyebrow 10 /
* mono title 13.5 / sans title 12 / meta 11 / pill 10.5, viewBox units) — see DiagramNode's note
* for the shared baseline offsets; rendered width is capped at 420px regardless of card size.
* Compact rhythm: 18px inter-node gaps, edges stop 4px short of the node they arrive at
* (arrowhead fills the rest), 16px outer margins, and a uniform viewHeight of 240 across the
* homepage concept-card set — so every card in that set is the same height regardless of
* viewWidth or node count. Second rhythm, for node↔pill gaps: 12px (segment lengths flex to
* fit; pills abut their segments).
*/
scale?: 'docs' | 'marketing' | 'compact';
children: ReactNode;
}

Expand Down
12 changes: 11 additions & 1 deletion apps/website/src/components/docs/diagrams/DiagramNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@ interface DiagramNodeProps {
title: string;
eyebrow?: string;
meta?: string;
/** 'accent' has two conventions: concept/marketing compositions accent the single
* PAYOFF node (what the customer gets); StackDiagram uses it as a subject
* highlight ("the node this page is about"). 'dim' marks inputs/externals. */
tone?: 'neutral' | 'accent' | 'dim';
/** 'middle' centers text horizontally (title-only summary nodes). */
align?: 'start' | 'middle';
/** 'sans' for prose-y titles (backend lists); default mono for package names. */
titleStyle?: 'mono' | 'sans';
/** 'mono' for code-shaped meta lines (JSON fragments, API names); default Inter. */
metaStyle?: 'sans' | 'mono';
}

const PAD = 16;
Expand All @@ -21,6 +26,10 @@ const PAD = 16;
* Minimum heights: `h >= 64` with eyebrow+meta, `h >= 52` with meta only,
* `h >= 52` with eyebrow and no meta (eyebrow at y+20, title at y+38),
* any `h` for title-only (vertically centered).
*
* Compact-scale compositions author at a ~320 viewBox with the compact type
* ramp (eyebrow 10 / mono title 13.5 / sans title 12 / meta 11 / pill 10.5,
* viewBox units); the same baseline offsets above apply unchanged.
*/
export function DiagramNode({
x,
Expand All @@ -33,6 +42,7 @@ export function DiagramNode({
tone = 'neutral',
align = 'start',
titleStyle = 'mono',
metaStyle = 'sans',
}: DiagramNodeProps) {
const tx = align === 'middle' ? x + w / 2 : x + PAD;
const anchor = align === 'middle' ? 'middle' : undefined;
Expand All @@ -41,7 +51,7 @@ export function DiagramNode({
const titleY = eyebrow ? y + 38 : meta ? y + 26 : y + h / 2 + 4;
const metaY = eyebrow ? y + 54 : y + 42;
return (
<g className="tp-diagram-node" data-tone={tone} data-title={titleStyle}>
<g className="tp-diagram-node" data-tone={tone} data-title={titleStyle} data-meta={metaStyle}>
<rect x={x} y={y} width={w} height={h} rx="10" />
{eyebrow ? (
<text className="tp-diagram-eyebrow" x={tx} y={y + 20} textAnchor={anchor}>
Expand Down
10 changes: 8 additions & 2 deletions apps/website/src/components/docs/diagrams/DiagramPill.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ interface DiagramPillProps {
cy: number;
w: number;
label: string;
/** 'accent' (default) for a payoff-adjacent pill; 'neutral' for an event
* label that should not compete with the card's one accented node. The
* default preserves the older docs diagrams, but NEW compositions should
* pass 'neutral' — pills label events/gates, and an accent pill competes
* with the payoff node. */
tone?: 'accent' | 'neutral';
}

const PILL_H = 24;

export function DiagramPill({ cx, cy, w, label }: DiagramPillProps) {
export function DiagramPill({ cx, cy, w, label, tone = 'accent' }: DiagramPillProps) {
return (
<g className="tp-diagram-pill">
<g className="tp-diagram-pill" data-tone={tone}>
<rect x={cx - w / 2} y={cy - PILL_H / 2} width={w} height={PILL_H} rx={PILL_H / 2} />
<text x={cx} y={cy + 3.5} textAnchor="middle">
{label}
Expand Down
106 changes: 106 additions & 0 deletions apps/website/src/components/docs/diagrams/PilotJourney.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT
import { DiagramFrame } from './DiagramFrame';
import { DiagramNode } from './DiagramNode';
import { DiagramEdge } from './DiagramEdge';
import { DiagramPill } from './DiagramPill';

const SLUG = 'pilot-journey';

/**
* /pilot-to-prod marketing graphic: the three FeatureBlock phases of the
* engagement (`id="discover"`, `id="build"`, `id="harden"` on the page) laid
* out as a horizontal journey. Eyebrows and titles are lifted verbatim from
* the page's own phase framing ("Week 1–2 · Discover", "Week 3–5 · Build",
* "Week 6–7 · Harden"); each node's meta abbreviates that phase's actual
* `rows` claims (stack audit + roadmap; real data + weekly demos) or its
* own body copy (Harden's is literally "on-call runbook") rather than
* inventing new copy.
*
* Gate pills mark every transition the page's copy actually names an
* artifact for: Discover's own row api is literally "roadmap" (carried into
* Build), Build's headline is literally "Ship a working agent on your real
* data" (the thing Harden then hardens), and the "Roadmap draft" panel in
* the Discover block ends on a row that is literally "W8 · Train your team ·
* handoff" (the page's own week-8 close, not a fourth phase — Harden is
* still the last FeatureBlock). All three pills
* stay neutral so they read as hand-offs, not payoffs; the terminal one
* after Harden has no outbound arrow since there is no fourth node. The
* Harden node carries the sole accent: it is what the customer is left
* holding at the end of the engagement (a working agent, a trained team, an
* on-call runbook), matching the outcomes section below it on the page ("A
* working agent. A trained team. A runbook.").
*
* Tiled at the kit-standard 640 viewWidth (16px outer margins) against
* measured glyph widths (JetBrains Mono / Inter, actual `getBBox()` reads via
* a live render of this exact composition — not a character-count estimate):
* node "Discover" meta "stack audit · roadmap" text 107.1 → w=132 (slack 8.9)
* node "Build" meta "real data · weekly demos" text 123.4 → w=146 (slack 6.6)
* node "Harden" meta "on-call runbook" text 78.5 → w=101 (slack 6.5)
* pill "roadmap" text 42.0 → w=52 (pad 5.0/side)
* pill "working agent" text 78.0 → w=94 (pad 8.0/side)
* pill "handoff" text 42.0 → w=52 (pad 5.0/side)
* (meta is the widest line in every node; eyebrow and title both measure
* narrower at 132/146/101.)
* Inter-node rhythm mirrors RenderTransform: a 5px segment into each pill, a
* 7px arrow-bearing segment out, a 2px arrowhead stop-short. The Build→Harden
* gap (a real named artifact, "working agent") gets the same pill treatment
* rather than a bare arrow, since the page names it too. The terminal
* Harden→handoff gap is a bare 5px segment into the pill with no exit
* segment or arrowhead — it is the end of the line, not a hand-off to a
* fourth node. Margins land at 16px left / 14px right (was 16/36 — the
* 20px right-margin surplus, plus shortening Harden's meta from
* "observability · runbook" to the page's own "on-call runbook", made room
* for the terminal pill without touching the earlier two nodes/pills).
*/
export function PilotJourney() {
return (
<DiagramFrame
slug={SLUG}
viewWidth={640}
viewHeight={96}
scale="marketing"
label="The Pilot-to-Prod journey: Discover produces a roadmap, Build ships a working agent on your real data, Harden delivers an on-call runbook, and the engagement ends in handoff."
>
<DiagramNode
x={16}
y={16}
w={132}
h={64}
eyebrow="Week 1–2"
title="Discover"
titleStyle="sans"
meta="stack audit · roadmap"
tone="dim"
/>
<DiagramEdge d="M148 48 H153" slug={SLUG} />
<DiagramPill cx={179} cy={48} w={52} label="roadmap" tone="neutral" />
<DiagramEdge d="M205 48 H212" slug={SLUG} arrow />
<DiagramNode
x={214}
y={16}
w={146}
h={64}
eyebrow="Week 3–5"
title="Build"
titleStyle="sans"
meta="real data · weekly demos"
/>
<DiagramEdge d="M360 48 H365" slug={SLUG} />
<DiagramPill cx={412} cy={48} w={94} label="working agent" tone="neutral" />
<DiagramEdge d="M459 48 H466" slug={SLUG} arrow />
<DiagramNode
x={468}
y={16}
w={101}
h={64}
eyebrow="Week 6–7"
title="Harden"
titleStyle="sans"
meta="on-call runbook"
tone="accent"
/>
<DiagramEdge d="M569 48 H574" slug={SLUG} />
<DiagramPill cx={600} cy={48} w={52} label="handoff" tone="neutral" />
</DiagramFrame>
);
}
56 changes: 56 additions & 0 deletions apps/website/src/components/docs/diagrams/RenderConcept.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
import { DiagramFrame } from './DiagramFrame';
import { DiagramNode } from './DiagramNode';
import { DiagramEdge } from './DiagramEdge';

const SLUG = 'concept-render';

/**
* Homepage concept card: a UI spec — abbreviated from the `@threadplane/render`
* intro's own example (`type: 'Text'`, `props: { … }`) — resolves through
* `defineAngularRegistry()` into your own Angular components.
*/
export function RenderConcept() {
return (
<DiagramFrame
slug={SLUG}
viewWidth={320}
viewHeight={240}
scale="compact"
label="Generative UI concept: a UI spec resolves through your Angular registry into your own components."
>
<DiagramNode
x={16}
y={16}
w={288}
h={64}
eyebrow="Spec"
title="type: 'Text'"
meta="props: { … }"
metaStyle="mono"
tone="dim"
/>
<DiagramEdge d="M160 80 V94" slug={SLUG} arrow />
<DiagramNode
x={16}
y={98}
w={288}
h={64}
eyebrow="Render"
title="defineAngularRegistry()"
meta="@threadplane/render"
/>
<DiagramEdge d="M160 162 V176" slug={SLUG} arrow />
<DiagramNode
x={16}
y={180}
w={288}
h={44}
title="Your own Angular components"
align="middle"
titleStyle="sans"
tone="accent"
/>
</DiagramFrame>
);
}
Loading
Loading