From f8aedd1289dc81c6eea485592a8aa5d8ba7d37ad Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Fri, 25 Sep 2026 18:04:25 +0300 Subject: [PATCH 1/2] feat(method): lay out the stack as a ruled cell strip with roles Co-Authored-By: Claude Opus 5.5 --- app/llms-full.txt/route.test.ts | 6 ++++-- app/llms-full.txt/route.ts | 4 ++-- components/site/Method.tsx | 38 +++++++++++++++++++++------------ lib/copy.test.ts | 5 ++++- lib/copy.ts | 25 ++++++++++++++++++---- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/app/llms-full.txt/route.test.ts b/app/llms-full.txt/route.test.ts index 5a8beed..186c9d3 100644 --- a/app/llms-full.txt/route.test.ts +++ b/app/llms-full.txt/route.test.ts @@ -147,8 +147,10 @@ describe("homepage mirror", () => { expect(body).toContain(`${figure.label}: ${figure.value}`); }); - it("mirrors the stack chips in order", () => { - expect(lines).toContain(`Stack: ${STACK.join(" · ")}`); + it("mirrors the stack strip in order", () => { + expect(lines).toContain( + `Stack: ${STACK.map((s) => `${s.name} (${s.role})`).join(" · ")}`, + ); }); // The header prose says "the three legal documents" in words, the way diff --git a/app/llms-full.txt/route.ts b/app/llms-full.txt/route.ts index 35e77d7..7a448a2 100644 --- a/app/llms-full.txt/route.ts +++ b/app/llms-full.txt/route.ts @@ -11,7 +11,7 @@ import { HERO_LEAD, METHOD_STEPS, SITE_DESCRIPTION, - STACK, + stackLine, statusBlock, } from "@/lib/copy"; import { FAQ_ITEMS } from "@/lib/faq"; @@ -215,7 +215,7 @@ ${compareTable} ### How it works -Stack: ${STACK.join(" · ")} +Stack: ${stackLine()} ${methodSteps} diff --git a/components/site/Method.tsx b/components/site/Method.tsx index 5aebaa6..444658e 100644 --- a/components/site/Method.tsx +++ b/components/site/Method.tsx @@ -1,4 +1,3 @@ -import { Fragment } from "react"; import { METHOD_STEPS, STACK } from "@/lib/copy"; import { Frame } from "@/components/ui/Frame"; import { MethodRow } from "@/components/ui/MethodRow"; @@ -26,20 +25,31 @@ export function Method() {
stack -
- {/* The `·` is a separator between chips, not part of one, so it is - aria-hidden and rendered only between items. */} - {STACK.map((name, i) => ( - - {i > 0 && ( - - · - - )} - {name} - + {/* Hairlines come from the list's top/left edge plus each cell's + right/bottom edge, so the grid stays single-ruled at any column + count. In one row of six, --text-h3 overflows a cell ("BLAKE3"), + so names size off the frame's container width instead. The role + label is `.meta` spelled out at 12px — `.meta` pins 11px outside + any cascade layer, so a size utility beside it can't win. */} +
    + {STACK.map(({ name, role }) => ( +
  • + + {name} + + + + {role} + +
  • ))} -
+
); diff --git a/lib/copy.test.ts b/lib/copy.test.ts index 4999cee..73f17ae 100644 --- a/lib/copy.test.ts +++ b/lib/copy.test.ts @@ -54,7 +54,10 @@ const ALL_STRINGS: readonly [string, string][] = [ [`METHOD_STEPS.${s.n}.word`, s.word], [`METHOD_STEPS.${s.n}.body`, s.body], ]), - ...STACK.map((s, i): [string, string] => [`STACK[${i}]`, s]), + ...STACK.flatMap((s): [string, string][] => [ + [`STACK.${s.name}.name`, s.name], + [`STACK.${s.name}.role`, s.role], + ]), ["DEMO_CAPTIONS.terminal", DEMO_CAPTIONS.terminal], ["DEMO_CAPTIONS.fleet", DEMO_CAPTIONS.fleet], ...CONTACT_HEADLINE.map((s, i): [string, string] => [ diff --git a/lib/copy.ts b/lib/copy.ts index 270bec7..8951cf6 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -149,10 +149,27 @@ export const METHOD_STEPS: readonly MethodStep[] = [ }, ]; -/** The stack chips under the method steps, rendered with an aria-hidden `·` - * between each. Order is the render order. This is the section's only stack - * summary — a figure strip beside it used to restate QUIC, iroh and USDC. */ -export const STACK = ["Rust", "BLAKE3", "QUIC", "iroh", "USDC", "EVM"] as const; +export type StackItem = { + name: string; + /** What the piece does in the protocol — the small label under the name. */ + role: string; +}; + +/** The stack strip under the method steps: one ruled cell per item, name over + * role. Order is the render order. This is the section's only stack summary — + * a figure strip beside it used to restate QUIC, iroh and USDC. */ +export const STACK: readonly StackItem[] = [ + { name: "Rust", role: "node & client" }, + { name: "BLAKE3", role: "verification" }, + { name: "QUIC", role: "transport" }, + { name: "iroh", role: "connectivity" }, + { name: "USDC", role: "payment" }, + { name: "EVM", role: "settlement" }, +]; + +/** The stack as one line of text, for the machine mirror. */ +export const stackLine = (): string => + STACK.map(({ name, role }) => `${name} (${role})`).join(" · "); /** The target price, as the page and both machine surfaces quote it. */ export const TARGET_RATE = "$0.01/GB"; From 79dbbfed140e053b304bea9266b7400978696205 Mon Sep 17 00:00:00 2001 From: Yigit Gurbulak Date: Fri, 25 Sep 2026 18:16:02 +0300 Subject: [PATCH 2/2] test(method): cover the stack strip and tighten its comments Co-Authored-By: Claude Opus 5.5 --- components/site/Method.test.ts | 39 ++++++++++++++++++++++++++++++++++ components/site/Method.tsx | 21 ++++++++++++------ lib/copy.test.ts | 14 ++++++++++++ lib/copy.ts | 6 +++--- 4 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 components/site/Method.test.ts diff --git a/components/site/Method.test.ts b/components/site/Method.test.ts new file mode 100644 index 0000000..2b78277 --- /dev/null +++ b/components/site/Method.test.ts @@ -0,0 +1,39 @@ +import { describe, expect, it } from "vitest"; +import { Method } from "./Method"; +import { STACK } from "@/lib/copy"; +import { + asElement, + attrs, + childElements, + findAll, + findOne, + textOf, +} from "@/test-utils/react-tree"; + +const section = asElement(Method()); +const list = findOne(section, "ul"); + +describe("Method stack strip", () => { + // The page and the llms-full.txt mirror both read STACK; this is what keeps + // the rendered side from dropping a role or reordering without a failure. + it("renders one cell per STACK item, name over role, in order", () => { + const cells = childElements(list); + expect(cells.map((c) => c.type)).toEqual(STACK.map(() => "li")); + cells.forEach((cell, i) => { + const [name, role] = childElements(cell); + expect(textOf(name)).toBe(STACK[i].name); + expect(textOf(role)).toBe(STACK[i].role); + }); + }); + + it("hides the square marker and names the list by its eyebrow", () => { + for (const cell of childElements(list)) { + const [, role] = childElements(cell); + const [marker] = childElements(role); + expect(attrs(marker)["aria-hidden"]).toBe(true); + } + const id = attrs(list)["aria-labelledby"]; + const eyebrow = findAll(section, "span").find((s) => attrs(s).id === id); + expect(eyebrow && textOf(eyebrow)).toBe("stack"); + }); +}); diff --git a/components/site/Method.tsx b/components/site/Method.tsx index 444658e..fac2748 100644 --- a/components/site/Method.tsx +++ b/components/site/Method.tsx @@ -24,20 +24,27 @@ export function Method() {
- stack + + stack + {/* Hairlines come from the list's top/left edge plus each cell's right/bottom edge, so the grid stays single-ruled at any column - count. In one row of six, --text-h3 overflows a cell ("BLAKE3"), - so names size off the frame's container width instead. The role - label is `.meta` spelled out at 12px — `.meta` pins 11px outside - any cascade layer, so a size utility beside it can't win. */} -
    + count — and closed while STACK.length divides evenly by it. In one + row of six the longest name (currently "BLAKE3") overflows + --text-h3, so names size off the frame's container width instead. + `.meta` and `.hug` sit outside any cascade layer, so a utility + beside either can't override it; the role label therefore spells + `.meta` out at 12px rather than adding a size to it. */} +
      {STACK.map(({ name, role }) => (
    • - + {name} diff --git a/lib/copy.test.ts b/lib/copy.test.ts index 73f17ae..651a264 100644 --- a/lib/copy.test.ts +++ b/lib/copy.test.ts @@ -80,6 +80,20 @@ describe("copy strings", () => { ); }); +describe("STACK", () => { + // components/site/Method.tsx lays the strip out 2 / 3 / 6 across, and its + // hairlines only close when the count divides evenly by the column count. + it("has exactly six items", () => { + expect(STACK).toHaveLength(6); + }); + + // `name` is the React key in Method.tsx and the label in ALL_STRINGS above. + it("names each item once", () => { + const names = STACK.map((s) => s.name); + expect(new Set(names).size).toBe(names.length); + }); +}); + describe("COMPARE_ROWS", () => { // Hardcoded, not `COMPARE_ROWS.length`: the section header says "seven // axes" in prose (components/site/Compare.tsx), so the count is a published diff --git a/lib/copy.ts b/lib/copy.ts index 8951cf6..f23bf8f 100644 --- a/lib/copy.ts +++ b/lib/copy.ts @@ -155,9 +155,9 @@ export type StackItem = { role: string; }; -/** The stack strip under the method steps: one ruled cell per item, name over - * role. Order is the render order. This is the section's only stack summary — - * a figure strip beside it used to restate QUIC, iroh and USDC. */ +/** The stack under the method steps, in render order. Rendered by `Method`; + * serialised by `stackLine()`. This is the section's only stack summary: put + * per-piece detail in `role`, not in a separate strip beside it. */ export const STACK: readonly StackItem[] = [ { name: "Rust", role: "node & client" }, { name: "BLAKE3", role: "verification" },