From 1a19ba9d94263b78e64f56454977a0b9d285eb01 Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Mon, 9 Nov 2026 18:52:32 +0100 Subject: [PATCH 1/5] feat(docs): lead Throughline overview with distributed checkout recovery --- README.md | 4 +- content/docs/throughline/index.mdx | 4 +- scripts/throughline-interactive.test.mjs | 13 +++++- src/lib/throughline-demos.ts | 57 ++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3056e08..2da917c 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ The navigation validation tests run with `pnpm test`. They verify tool roots, de ## Interactive Throughline examples -Selected Throughline guides render the published `@0xbuff/throughline@0.0.2` React player. Sources are kept in `src/lib/throughline-demos.ts`; MDX pages use ``. The component is registered in `src/components/mdx.tsx`. Its compiler, player and namespaced CSS load in a separate module only when a demo is rendered. All other 0xBUFF tool documentation remains independent of Throughline. +Selected Throughline guides render the published `@0xbuff/throughline@0.0.2` React player. The overview leads with a multi-system checkout recovery trace; introductory pages retain smaller teaching examples. Sources are kept in `src/lib/throughline-demos.ts`; MDX pages use ``. The component is registered in `src/components/mdx.tsx`. Its compiler, player and namespaced CSS load in a separate module only when a demo is rendered. All other 0xBUFF tool documentation remains independent of Throughline. -The showcase is deliberately limited to a few pages and does not automatically transform every `thl` code fence. This preserves portable source examples and avoids unnecessary client JavaScript. `scripts/throughline-interactive.test.mjs` compiles every showcased trace with the installed package; CI also typechecks and builds both environments. +The showcase is deliberately limited to a few pages and does not automatically transform every `thl` code fence. The official `remarkThroughline({ mode: "mdx" })` integration and `ThroughlineEmbed` require a subsequent package release; v0.0.2 must not be assumed to contain them. This preserves portable source examples and avoids unnecessary client JavaScript. `scripts/throughline-interactive.test.mjs` compiles every showcased trace with the installed package; CI also typechecks and builds both environments. ## Content ownership and publication diff --git a/content/docs/throughline/index.mdx b/content/docs/throughline/index.mdx index cd0eb7e..bcb10a1 100644 --- a/content/docs/throughline/index.mdx +++ b/content/docs/throughline/index.mdx @@ -10,9 +10,9 @@ Use it to explain algorithms, data structures, recursion, and system workflows. ## See it in action - + -Open **View trace source** to see exactly what the player is visualizing. The compiler creates the frames before playback; **Play** and the timeline only select existing frames. +This checkout scenario follows a lost provider reply, an unknown payment outcome, a retry with the same idempotency key and eventual recovery. Open **View trace source** to inspect the authored steps. The compiler creates the frames before playback; **Play** and the timeline only select existing frames. ## Get started diff --git a/scripts/throughline-interactive.test.mjs b/scripts/throughline-interactive.test.mjs index bca818f..a4c3c0b 100644 --- a/scripts/throughline-interactive.test.mjs +++ b/scripts/throughline-interactive.test.mjs @@ -4,7 +4,7 @@ import { compile } from "@0xbuff/throughline"; import { THROUGHLINE_DEMOS } from "../src/lib/throughline-demos.ts"; test("interactive documentation demos compile with the published Throughline package", () => { - assert.deepEqual(Object.keys(THROUGHLINE_DEMOS).sort(), ["binary", "counter", "references"]); + assert.deepEqual(Object.keys(THROUGHLINE_DEMOS).sort(), ["binary", "checkout", "counter", "references"]); for (const [id, demo] of Object.entries(THROUGHLINE_DEMOS)) { const trace = compile(demo.source); assert.equal(trace.version, 1, id); @@ -14,6 +14,17 @@ test("interactive documentation demos compile with the published Throughline pac } }); +test("the checkout demo preserves an unknown outcome until confirmation", () => { + const trace = compile(THROUGHLINE_DEMOS.checkout.source); + const get = (index, id) => trace.frames[index]?.state.entities.find((item) => item.id === id)?.value; + assert.deepEqual(get(6, "order"), { status: "awaiting_payment" }); + assert.equal(get(6, "worker"), "outcome unknown"); + assert.equal(get(6, "status"), "processing"); + assert.equal(get(8, "provider"), "one charge: pay-order-1; retry deduplicated"); + assert.deepEqual(get(11, "order"), { status: "paid" }); + assert.equal(get(11, "status"), "confirmed"); +}); + test("the counter and binary demos show their expected final values", () => { const counter = compile(THROUGHLINE_DEMOS.counter.source); const lastCounter = counter.frames.at(-1)?.state.entities.find((item) => item.id === "counter"); diff --git a/src/lib/throughline-demos.ts b/src/lib/throughline-demos.ts index d2c751e..d09190f 100644 --- a/src/lib/throughline-demos.ts +++ b/src/lib/throughline-demos.ts @@ -14,6 +14,63 @@ export const THROUGHLINE_DEMOS = { ].join("\n"), expectedFrames: 3, }, + checkout: { + title: "Recover a distributed checkout after a timeout", + description: "Trace a payment whose reply is lost, then follow an idempotent retry through order recovery.", + source: [ + "thl flow", + 'group "Checkout" as column', + 'group "Payment" as column', + 'node api "Checkout API" in "Checkout" = "received"', + 'node order "Order record" in "Checkout" as record = {"status":"absent"}', + 'node status "Customer status" in "Checkout" = "not started"', + 'node queue "Payment queue" in "Payment" as array = []', + 'node worker "Worker" in "Payment" = "idle"', + 'node provider "Provider" in "Payment" = "no charge"', + 'link persist api -> order "persist"', + 'link enqueue api -> queue "enqueue"', + 'link deliver queue -> worker "deliver"', + 'link charge worker -> provider "charge"', + 'link report api -> status "status"', + 'step "Persist pending order"', + ' set order = {"status":"awaiting_payment"}', + ' note "The order is pending. No payment is confirmed yet."', + 'step "Enqueue payment work"', + ' set queue = ["pay-order-1"]', + ' note "A job is queued with a stable payment key."', + 'step "Report processing"', + ' set status = "processing"', + ' note "The customer is told the operation is in progress, not that it succeeded."', + 'step "Deliver the job"', + ' set queue = []', + ' set worker = "charging with pay-order-1"', + ' note "The worker begins the first payment attempt."', + 'step "Provider records one charge"', + ' set provider = "one charge: pay-order-1; reply lost"', + ' note "The provider recorded one charge, but its response was lost."', + 'step "Worker times out"', + ' set worker = "outcome unknown"', + ' note "A timeout does not establish payment failure. The order remains pending."', + 'step "Retry with the same key"', + ' set queue = ["retry pay-order-1"]', + ' set worker = "retry queued"', + ' note "The retry uses the original idempotency key."', + 'step "Provider deduplicates"', + ' set queue = []', + ' set provider = "one charge: pay-order-1; retry deduplicated"', + ' note "For this provider, the same key identifies the existing charge."', + 'step "Worker receives confirmation"', + ' set worker = "payment confirmed"', + ' note "The response arrives before local payment success is recorded."', + 'step "Persist paid order"', + ' set order = {"status":"paid"}', + ' note "The confirmed outcome is now persisted."', + 'step "Notify customer"', + ' set status = "confirmed"', + ' note "The customer sees the recovered order; no second charge was created."', + ].join("\\n"), + expectedFrames: 12, + }, references: { title: "Watch two references share an array", description: "Step through copying a reference, mutating the array, and moving one pointer.", From 001e84e47f5cc37d23d10740b44aaea7fc6ee91f Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Mon, 9 Nov 2026 18:53:11 +0100 Subject: [PATCH 2/5] fix(docs): join checkout trace source with actual newlines --- src/lib/throughline-demos.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/throughline-demos.ts b/src/lib/throughline-demos.ts index d09190f..f8710b2 100644 --- a/src/lib/throughline-demos.ts +++ b/src/lib/throughline-demos.ts @@ -68,7 +68,7 @@ export const THROUGHLINE_DEMOS = { 'step "Notify customer"', ' set status = "confirmed"', ' note "The customer sees the recovered order; no second charge was created."', - ].join("\\n"), + ].join("\n"), expectedFrames: 12, }, references: { From bfa4c1e98f51f3e7646285437e416fde5df0656e Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Wed, 11 Nov 2026 10:16:57 +0100 Subject: [PATCH 3/5] feat(docs): annotate checkout transfers separately from persistent state --- src/lib/throughline-demos.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/lib/throughline-demos.ts b/src/lib/throughline-demos.ts index f8710b2..911acca 100644 --- a/src/lib/throughline-demos.ts +++ b/src/lib/throughline-demos.ts @@ -32,40 +32,55 @@ export const THROUGHLINE_DEMOS = { 'link deliver queue -> worker "deliver"', 'link charge worker -> provider "charge"', 'link report api -> status "status"', + 'link retry worker -> queue "retry"', + 'link receipt provider -> worker "confirmation"', + 'link confirm worker -> api "confirm payment"', 'step "Persist pending order"', + ' send persist = {"status":"awaiting_payment"}', ' set order = {"status":"awaiting_payment"}', ' note "The order is pending. No payment is confirmed yet."', 'step "Enqueue payment work"', + ' send enqueue = "pay-order-1"', ' set queue = ["pay-order-1"]', ' note "A job is queued with a stable payment key."', 'step "Report processing"', + ' send report = "processing"', ' set status = "processing"', ' note "The customer is told the operation is in progress, not that it succeeded."', 'step "Deliver the job"', + ' send deliver = "pay-order-1"', ' set queue = []', ' set worker = "charging with pay-order-1"', ' note "The worker begins the first payment attempt."', 'step "Provider records one charge"', + ' send charge = "pay-order-1"', ' set provider = "one charge: pay-order-1; reply lost"', ' note "The provider recorded one charge, but its response was lost."', 'step "Worker times out"', ' set worker = "outcome unknown"', ' note "A timeout does not establish payment failure. The order remains pending."', 'step "Retry with the same key"', + ' send retry = "pay-order-1"', ' set queue = ["retry pay-order-1"]', ' set worker = "retry queued"', ' note "The retry uses the original idempotency key."', 'step "Provider deduplicates"', + ' send charge = "pay-order-1"', ' set queue = []', ' set provider = "one charge: pay-order-1; retry deduplicated"', + ' set worker = "retrying pay-order-1"', ' note "For this provider, the same key identifies the existing charge."', 'step "Worker receives confirmation"', + ' send receipt = "pay-order-1 paid"', ' set worker = "payment confirmed"', ' note "The response arrives before local payment success is recorded."', 'step "Persist paid order"', + ' send confirm = "pay-order-1 paid"', + ' send persist = {"status":"paid"}', ' set order = {"status":"paid"}', ' note "The confirmed outcome is now persisted."', 'step "Notify customer"', + ' send report = "confirmed"', ' set status = "confirmed"', ' note "The customer sees the recovered order; no second charge was created."', ].join("\n"), From 067ce5f49feffb05d517f1468746dde44b0b8f2c Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Wed, 11 Nov 2026 10:17:20 +0100 Subject: [PATCH 4/5] test(docs): assert transfer provenance and delayed persistence in checkout showcase --- scripts/throughline-interactive.test.mjs | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scripts/throughline-interactive.test.mjs b/scripts/throughline-interactive.test.mjs index a4c3c0b..942b89a 100644 --- a/scripts/throughline-interactive.test.mjs +++ b/scripts/throughline-interactive.test.mjs @@ -25,6 +25,36 @@ test("the checkout demo preserves an unknown outcome until confirmation", () => assert.equal(get(11, "status"), "confirmed"); }); +test("checkout transfers preserve real link endpoints and never imply persistence", () => { + const trace = compile(THROUGHLINE_DEMOS.checkout.source); + const links = new Map(trace.frames[0].state.relations.map((link) => [link.id, link])); + const expected = [ + ["persist"], ["enqueue"], ["report"], ["deliver"], ["charge"], [], + ["retry"], ["charge"], ["receipt"], ["confirm", "persist"], ["report"], + ]; + for (const [index, relationIds] of expected.entries()) { + const frame = trace.frames[index + 1]; + assert.deepEqual(frame.state.transfers.map(({ relation }) => relation), relationIds, `Step ${index + 1}`); + for (const transfer of frame.state.transfers) { + const link = links.get(transfer.relation); + assert.ok(link, `Unknown link ${transfer.relation}`); + assert.ok(frame.state.entities.some((entity) => entity.id === link.from)); + assert.ok(frame.state.entities.some((entity) => entity.id === link.to)); + } + } + + const state = (index, id) => + trace.frames[index].state.entities.find((entity) => entity.id === id)?.value; + assert.equal(state(4, "provider"), "no charge", "delivery is not a persisted provider charge"); + assert.deepEqual(state(9, "order"), { status: "awaiting_payment" }, "confirmation is not an order write"); + assert.equal(state(10, "status"), "processing", "recording paid does not automatically notify the customer"); + assert.equal(state(11, "status"), "confirmed"); + assert.deepEqual( + trace.frames[10].state.transfers.find(({ relation }) => relation === "persist")?.value, + { status: "paid" }, + ); +}); + test("the counter and binary demos show their expected final values", () => { const counter = compile(THROUGHLINE_DEMOS.counter.source); const lastCounter = counter.frames.at(-1)?.state.entities.find((item) => item.id === "counter"); From 1a26de437491da2a51f613063aa779d6f30f3250 Mon Sep 17 00:00:00 2001 From: "Christopher S. Aondona" Date: Wed, 11 Nov 2026 10:17:36 +0100 Subject: [PATCH 5/5] docs(throughline): distinguish transfer annotations from persisted state --- content/docs/throughline/index.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/throughline/index.mdx b/content/docs/throughline/index.mdx index bcb10a1..5e941b4 100644 --- a/content/docs/throughline/index.mdx +++ b/content/docs/throughline/index.mdx @@ -12,7 +12,7 @@ Use it to explain algorithms, data structures, recursion, and system workflows. -This checkout scenario follows a lost provider reply, an unknown payment outcome, a retry with the same idempotency key and eventual recovery. Open **View trace source** to inspect the authored steps. The compiler creates the frames before playback; **Play** and the timeline only select existing frames. +This checkout scenario follows a lost provider reply, an unknown payment outcome, a retry with the same idempotency key and eventual recovery. Directed transfers show what was **sent**; order, provider and customer values change only at explicitly authored writes. Open **View trace source** to inspect both kinds of events. The compiler creates the frames before playback; **Play** and the timeline only select existing frames. ## Get started