Skip to content

Commit bed887e

Browse files
betterdatacoclaude
andcommitted
feat(product/how-it-works): inline state-machine + Decision Record visuals
Two of the nine image slots on /product/how-it-works now render real inline visuals instead of labeled placeholders. Wired via a small concept-slug → visual map so future slots can drop in the same way. - components/site/LoopStateMachineDiagram.tsx (new): SVG rendering of the Alpine supplier invoice loop's state machine — OPENED → PENDING_APPROVAL → APPROVED / REJECTED / ESCALATED — with the governed happy-path transition drawn in brand primary and the active guards annotated in a chip strip beneath the diagram (AI confidence ≥ 0.85, evidence: Looker + NetSuite + Slack, authority: Tier B · Approval Matrix v3.2). Carries the seeded-scenario fidelity badge. - app/product/how-it-works/page.tsx: - Slot #1 (Decision loops) now shows <LoopStateMachineDiagram /> - Slot #8 (Evidence) now shows <DecisionRecordDiagram scenario="invoice"/> (the same record that appears on /made-for/finance and elsewhere across the site — narrative continuity for the Alpine scenario) - Captions on both slots rewritten from shot-list notes ("Recommended: …") to descriptions of what's shown. The other seven slots stay as labeled placeholders with their shot recommendations until real screenshots are captured. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f1369de commit bed887e

2 files changed

Lines changed: 242 additions & 5 deletions

File tree

app/product/how-it-works/page.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
import { CONTACT_PAGE, SALES_CONTACT_URL } from "@/lib/contact-routes";
22
import { VisualSlot } from "@/components/site/VisualSlot";
3+
import { LoopStateMachineDiagram } from "@/components/site/LoopStateMachineDiagram";
4+
import { DecisionRecordDiagram } from "@/components/site/DecisionRecordDiagram";
35
import type { Metadata } from "next";
6+
import type { ReactNode } from "react";
47
import Link from "next/link";
58

9+
/**
10+
* Inline visuals available today for specific concepts. Slots without an
11+
* entry here render the labeled dashed placeholder from VisualSlot.
12+
*/
13+
const CONCEPT_VISUALS: Record<string, ReactNode> = {
14+
"decision-loops": <LoopStateMachineDiagram />,
15+
evidence: <DecisionRecordDiagram scenario="invoice" />,
16+
};
17+
618
export const metadata: Metadata = {
719
title: "How it works — the Boss Loops engine, in plain English",
820
description:
@@ -40,8 +52,8 @@ const CONCEPTS: Concept[] = [
4052
docsHref: "/docs/concepts/what-is-a-loop",
4153
docsLabel: "What is a loop",
4254
image: {
43-
label: "Diagram or screenshot — a decision loop's state machine (Alpine supplier invoice)",
44-
caption: "Recommended: the Alpine invoice loop shown as states + transitions (OPENED → PENDING_APPROVAL → APPROVED / REJECTED / ESCALATED), with the active guards annotated on the PENDING_APPROVAL → APPROVED transition."
55+
label: "Diagram — a decision loop's state machine (Alpine supplier invoice)",
56+
caption: "The Alpine supplier invoice loop. Active guards on the governed happy-path transition are annotated below the diagram — a transition that doesn't satisfy them doesn't commit."
4557
}
4658
},
4759
{
@@ -150,8 +162,8 @@ const CONCEPTS: Concept[] = [
150162
docsHref: "/docs/concepts/evidence-providers",
151163
docsLabel: "Evidence Providers",
152164
image: {
153-
label: "Screenshot — evidence row on the Alpine Decision Record (Looker semantic evidence)",
154-
caption: "Recommended: the evidence row expanded on the Alpine invoice record — value, source definition, freshness, provenance, and qualification, all frozen at the moment the decision was made."
165+
label: "The Alpine Decision Record",
166+
caption: "The Alpine supplier invoice Decision Record — evidence rows are frozen at the moment they inform the decision, with definitions inherited from the source. Same record shown across the site."
155167
}
156168
},
157169
{
@@ -246,7 +258,9 @@ export default function HowItWorksPage() {
246258
</p>
247259
))}
248260
{concept.image ? (
249-
<VisualSlot label={concept.image.label} caption={concept.image.caption} />
261+
<VisualSlot label={concept.image.label} caption={concept.image.caption}>
262+
{CONCEPT_VISUALS[concept.slug] ?? undefined}
263+
</VisualSlot>
250264
) : null}
251265
<p style={{ marginTop: 20, fontSize: "var(--text-sm)", color: "var(--color-ink-muted)" }}>
252266
Go deeper:{" "}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
import type { CSSProperties } from "react";
2+
import { FidelityBadge } from "./FidelityBadge";
3+
4+
/**
5+
* Illustrative rendering of a governed decision loop as a state machine.
6+
* Ships the Alpine invoice-approval scenario:
7+
*
8+
* OPENED → PENDING_APPROVAL → APPROVED
9+
* → REJECTED
10+
* → ESCALATED
11+
*
12+
* The transition PENDING_APPROVAL → APPROVED is highlighted with the active
13+
* guards annotated below the diagram. The APPROVED state is styled with the
14+
* brand-primary tone so the "governed happy path" reads at a glance.
15+
*
16+
* Use inside a <VisualSlot children=…> until a real product screenshot is
17+
* captured for this concept.
18+
*/
19+
20+
type Node = {
21+
x: number;
22+
y: number;
23+
label: string;
24+
variant?: "default" | "approved" | "rejected" | "escalated";
25+
};
26+
27+
const NODE_W = 148;
28+
const NODE_H = 40;
29+
30+
const NODES: Node[] = [
31+
{ x: 20, y: 110, label: "OPENED" },
32+
{ x: 236, y: 110, label: "PENDING_APPROVAL" },
33+
{ x: 500, y: 20, label: "APPROVED", variant: "approved" },
34+
{ x: 500, y: 110, label: "REJECTED", variant: "rejected" },
35+
{ x: 500, y: 200, label: "ESCALATED", variant: "escalated" },
36+
];
37+
38+
function nodeFill(variant: Node["variant"]): string {
39+
if (variant === "approved") return "var(--color-primary-light, #ecfdf5)";
40+
if (variant === "escalated") return "var(--color-surface-alt, #fef3c7)";
41+
return "var(--color-surface)";
42+
}
43+
44+
function nodeStroke(variant: Node["variant"]): string {
45+
if (variant === "approved") return "var(--color-primary)";
46+
return "var(--color-border)";
47+
}
48+
49+
function nodeTextFill(variant: Node["variant"]): string {
50+
if (variant === "approved") return "var(--color-primary-dark, #065f46)";
51+
return "var(--color-ink)";
52+
}
53+
54+
export function LoopStateMachineDiagram({ style }: { style?: CSSProperties }) {
55+
return (
56+
<div
57+
style={{
58+
position: "relative",
59+
display: "flex",
60+
flexDirection: "column",
61+
gap: 16,
62+
fontFamily: "var(--font-body)",
63+
...style,
64+
}}
65+
>
66+
<div style={{ position: "absolute", top: 0, right: 0, zIndex: 1 }}>
67+
<FidelityBadge kind="seeded" />
68+
</div>
69+
70+
<svg
71+
viewBox="0 0 660 260"
72+
role="img"
73+
aria-label="Alpine invoice loop — state machine"
74+
style={{ width: "100%", height: "auto", display: "block", marginTop: 8 }}
75+
>
76+
<defs>
77+
<marker
78+
id="arrow-default"
79+
viewBox="0 0 10 10"
80+
refX="8"
81+
refY="5"
82+
markerWidth="7"
83+
markerHeight="7"
84+
orient="auto-start-reverse"
85+
>
86+
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--color-ink-tertiary)" />
87+
</marker>
88+
<marker
89+
id="arrow-primary"
90+
viewBox="0 0 10 10"
91+
refX="8"
92+
refY="5"
93+
markerWidth="7"
94+
markerHeight="7"
95+
orient="auto-start-reverse"
96+
>
97+
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--color-primary)" />
98+
</marker>
99+
</defs>
100+
101+
{/* Transitions */}
102+
{/* OPENED → PENDING_APPROVAL */}
103+
<line
104+
x1="168"
105+
y1="130"
106+
x2="236"
107+
y2="130"
108+
stroke="var(--color-ink-tertiary)"
109+
strokeWidth="1.5"
110+
markerEnd="url(#arrow-default)"
111+
/>
112+
{/* PENDING_APPROVAL → APPROVED (governed happy path) */}
113+
<path
114+
d="M 384 122 C 440 122, 452 44, 500 40"
115+
stroke="var(--color-primary)"
116+
strokeWidth="2"
117+
fill="none"
118+
markerEnd="url(#arrow-primary)"
119+
/>
120+
{/* PENDING_APPROVAL → REJECTED */}
121+
<line
122+
x1="384"
123+
y1="130"
124+
x2="500"
125+
y2="130"
126+
stroke="var(--color-ink-tertiary)"
127+
strokeWidth="1.5"
128+
markerEnd="url(#arrow-default)"
129+
/>
130+
{/* PENDING_APPROVAL → ESCALATED */}
131+
<path
132+
d="M 384 138 C 440 138, 452 216, 500 220"
133+
stroke="var(--color-ink-tertiary)"
134+
strokeWidth="1.5"
135+
fill="none"
136+
markerEnd="url(#arrow-default)"
137+
/>
138+
139+
{/* Nodes */}
140+
{NODES.map((node) => (
141+
<g key={node.label}>
142+
<rect
143+
x={node.x}
144+
y={node.y}
145+
width={NODE_W}
146+
height={NODE_H}
147+
rx={6}
148+
ry={6}
149+
fill={nodeFill(node.variant)}
150+
stroke={nodeStroke(node.variant)}
151+
strokeWidth="1.5"
152+
/>
153+
<text
154+
x={node.x + NODE_W / 2}
155+
y={node.y + NODE_H / 2 + 4}
156+
textAnchor="middle"
157+
fontFamily="var(--font-mono)"
158+
fontSize="12"
159+
fill={nodeTextFill(node.variant)}
160+
>
161+
{node.label}
162+
</text>
163+
</g>
164+
))}
165+
</svg>
166+
167+
{/* Active guards on the governed happy-path transition */}
168+
<div
169+
style={{
170+
padding: "12px 14px",
171+
border: "1px solid var(--color-primary)",
172+
borderRadius: 8,
173+
background: "var(--color-primary-light, #ecfdf5)",
174+
}}
175+
>
176+
<p
177+
style={{
178+
fontFamily: "var(--font-mono)",
179+
fontSize: 10,
180+
textTransform: "uppercase",
181+
letterSpacing: "0.08em",
182+
color: "var(--color-primary-dark, #065f46)",
183+
margin: 0,
184+
}}
185+
>
186+
Active guards · PENDING_APPROVAL → APPROVED
187+
</p>
188+
<div
189+
style={{
190+
marginTop: 8,
191+
display: "flex",
192+
flexWrap: "wrap",
193+
gap: 8,
194+
}}
195+
>
196+
{[
197+
"AI confidence ≥ 0.85",
198+
"evidence: Looker + NetSuite + Slack",
199+
"authority: Tier B · Approval Matrix v3.2",
200+
].map((rule) => (
201+
<span
202+
key={rule}
203+
style={{
204+
display: "inline-flex",
205+
alignItems: "center",
206+
padding: "4px 10px",
207+
borderRadius: 999,
208+
border: "1px solid var(--color-primary)",
209+
background: "var(--color-surface)",
210+
fontFamily: "var(--font-mono)",
211+
fontSize: 11,
212+
color: "var(--color-ink)",
213+
whiteSpace: "nowrap",
214+
}}
215+
>
216+
{rule}
217+
</span>
218+
))}
219+
</div>
220+
</div>
221+
</div>
222+
);
223+
}

0 commit comments

Comments
 (0)