Tale components
The block kinds a tale is composed from, each rendered by the same code that draws a live tale at /t/:id, beside the JSON that produces it. Copy a block, drop it into a tale's blocksarray. The field caps are the schema's; the reference lists them in full.
One envelope setting, four registers. Same grid and blocks; only paper, ink, accent and display face change. Brand overrides the accent when the owner has set a theme. Default is letter.
The executive summary. Must be the first block of a real tale. text 1–600.
The double-charge in checkout is fixed, deployed and verified. One race between the payment webhook and the order writer caused it; the fix serialises them.
{
"kind": "lead",
"text": "The double-charge in checkout is fixed, deployed and verified. One race between the payment webhook and the order writer caused it; the fix serialises them."
}Section title: always an <h2>, no level field. Write it as the claim. text 1–200.
The webhook and the order writer raced
{
"kind": "heading",
"text": "The webhook and the order writer raced"
}Body copy in a strict markdown subset: **bold**, `code`, [links](url), flat lists. No raw HTML. markdown 1–8000.
Under load, the payment webhook and the order writer both inserted a charge row. Neither held the idempotency key, so both writes succeeded. The fix is small:
- take the idempotency lock before the write
- release it after the order commits
See withLock in the webhook handler.
{
"kind": "prose",
"markdown": "Under load, the payment webhook and the order writer both inserted a charge row. Neither held the **idempotency key**, so both writes succeeded. The fix is small:\n\n- take the idempotency lock before the write\n- release it after the order commits\n\nSee `withLock` in [the webhook handler](https://example.com/webhook)."
}An aside with a tone: info | warning | success | note. Optional title ≤120, body 1–1000. No two in a row.
[
{
"kind": "callout",
"tone": "info",
"title": "Context",
"body": "This tale covers the checkout double-charge only; the refund run is tracked separately."
},
{
"kind": "callout",
"tone": "warning",
"title": "Action needed on your side",
"body": "Rotate the webhook secret before Friday: the old one was logged in plain text."
},
{
"kind": "callout",
"tone": "success",
"title": "Verified in production",
"body": "Zero duplicate charges across 40k checkouts since the deploy."
},
{
"kind": "callout",
"tone": "note",
"body": "A callout with no title is fine; the tone still sets the accent."
}
]The headline numbers: at most one block per tale. tiles 1–6 of { label, value, delta?, tone?: good|flat|bad }.
{
"kind": "stat_tiles",
"title": "Since the fix",
"tiles": [
{
"label": "duplicate charges",
"value": "0",
"delta": "was 41/day",
"tone": "good"
},
{
"label": "p95 checkout",
"value": "312 ms",
"tone": "flat"
},
{
"label": "refunds outstanding",
"value": "3",
"delta": "+3 to process",
"tone": "bad"
},
{
"label": "orders verified",
"value": "40,118"
}
]
}One series over ordered points. title required, unit? y-suffix, points 2–120, annotations? ≤4, endLabel?. No two in a row.
{
"kind": "line_chart",
"title": "Duplicate charges per day",
"subtitle": "Two-week window around the deploy",
"unit": "/day",
"points": [
{
"label": "Mon",
"value": 38
},
{
"label": "Tue",
"value": 41
},
{
"label": "Wed",
"value": 12
},
{
"label": "Thu",
"value": 0
},
{
"label": "Fri",
"value": 0
}
],
"annotations": [
{
"index": 3,
"text": "fix deployed"
}
],
"endLabel": "0/day"
}columns 1–8 (≤60 each), rows 1–50 (each exactly columns.length cells, ≤200). Optional title.
| Order | Charged | Refunded |
|---|---|---|
| #48211 | twice | yes |
| #48217 | twice | yes |
| #48219 | twice | pending |
{
"kind": "table",
"title": "Affected orders",
"columns": [
"Order",
"Charged",
"Refunded"
],
"rows": [
[
"#48211",
"twice",
"yes"
],
[
"#48217",
"twice",
"yes"
],
[
"#48219",
"twice",
"pending"
]
]
}Syntax-highlighted (Shiki). language from the enum, filename?, caption?, highlightLines? (1-indexed). code 1–8000.
const key = idempotencyKey(event);
await withLock(key, async () => {
await writeOrder(event);
});{
"kind": "code",
"language": "ts",
"filename": "src/payments/webhook.ts",
"code": "const key = idempotencyKey(event);\nawait withLock(key, async () => {\n await writeOrder(event);\n});",
"caption": "The write now runs under the idempotency lock.",
"highlightLines": [
2,
3,
4
]
}Unified-diff hunks for one file. filePath, hunks 1–20 of { oldStart, oldLines, newStart, newLines, lines }; each line prefixed ' ', '+' or '-'.
@@ -12,2 +12,3 @@ const key = idempotencyKey(event); -await writeOrder(event); +await withLock(key, () => + writeOrder(event));
{
"kind": "diff",
"filePath": "src/payments/webhook.ts",
"hunks": [
{
"oldStart": 12,
"oldLines": 2,
"newStart": 12,
"newLines": 3,
"lines": [
" const key = idempotencyKey(event);",
"-await writeOrder(event);",
"+await withLock(key, () =>",
"+ writeOrder(event));"
]
}
]
}Ordered moments. items 2–20 of { at (free label), title, body? }.
- 09:41First duplicate charge reportedSupport flagged order #48211 charged twice.
- 11:05Race identified in the webhook handler
- 14:20Fix deployed and verifiedZero duplicates since.
{
"kind": "timeline",
"items": [
{
"at": "09:41",
"title": "First duplicate charge reported",
"body": "Support flagged order #48211 charged twice."
},
{
"at": "11:05",
"title": "Race identified in the webhook handler"
},
{
"at": "14:20",
"title": "Fix deployed and verified",
"body": "Zero duplicates since."
}
]
}Two labelled panels; each carries exactly one of markdown (≤2000) or code (≤4000, language?). label 1–60.
await writeOrder(event);The write runs under the idempotency lock, so a retried webhook is a no-op.
{
"kind": "before_after",
"before": {
"label": "Before",
"code": "await writeOrder(event);",
"language": "ts"
},
"after": {
"label": "After",
"markdown": "The write runs under the idempotency lock, so a retried webhook is a no-op."
}
}A pulled quote. text 1–500, attribution? ≤120.
A retried webhook must be indistinguishable from the first delivery.
Payment provider integration guide
{
"kind": "quote",
"text": "A retried webhook must be indistinguishable from the first delivery.",
"attribution": "Payment provider integration guide"
}Action items. items 1–12 of { text, owner?, due? }.
- Rotate the webhook secret
- Enable idempotency alerts in the dashboard
- Confirm the three pending refunds cleared
{
"kind": "checklist",
"items": [
{
"text": "Rotate the webhook secret",
"owner": "your platform team",
"due": "Fri"
},
{
"text": "Enable idempotency alerts in the dashboard",
"owner": "us"
},
{
"text": "Confirm the three pending refunds cleared"
}
]
}The credibility layer. items 1–30 of { kind: web|pdf|doc|repo|dataset, name, url?, snippet?, note? }. snippet is the exact passage relied on.
{
"kind": "evidence",
"title": "What we consulted",
"items": [
{
"kind": "web",
"name": "Stripe webhook best practices",
"url": "https://docs.stripe.com/webhooks",
"snippet": "Webhook endpoints might occasionally receive the same event more than once."
},
{
"kind": "pdf",
"name": "PCI-DSS v4.0 §10",
"note": "logging requirements for the secret rotation"
},
{
"kind": "doc",
"name": "Internal incident runbook",
"note": "double-charge triage steps followed"
},
{
"kind": "repo",
"name": "payments-service",
"url": "https://github.com/acme/payments-service",
"note": "handler history reviewed back to the introduction of the race"
},
{
"kind": "dataset",
"name": "checkout-events-2026-07",
"note": "40,118 orders replayed to verify zero duplicates"
}
]
}Who calls whom, in order; drawn by the renderer as a deterministic SVG, never pixels you supply. actors 2–6 {id, label}; messages 1–30 {from, to, label, note?, reply?}; from/to must name declared actor ids; reply draws a dashed return arrow.
{
"kind": "sequence",
"title": "One charge per checkout, even on retry",
"actors": [
{
"id": "browser",
"label": "Browser"
},
{
"id": "checkout",
"label": "Checkout service"
},
{
"id": "gateway",
"label": "Payment gateway"
}
],
"messages": [
{
"from": "browser",
"to": "checkout",
"label": "Place order"
},
{
"from": "checkout",
"to": "gateway",
"label": "Capture payment",
"note": "carries the idempotency key"
},
{
"from": "gateway",
"to": "checkout",
"label": "Charge created",
"reply": true
},
{
"from": "checkout",
"to": "checkout",
"label": "Write the order",
"note": "under the lock"
},
{
"from": "checkout",
"to": "gateway",
"label": "Retry capture",
"note": "same key, after a timeout"
},
{
"from": "gateway",
"to": "checkout",
"label": "Already captured, no-op",
"reply": true
},
{
"from": "checkout",
"to": "browser",
"label": "Order confirmed",
"reply": true
}
]
}A process as a vertical flow. steps 2–12 {label, detail?, tone?: good|flat|bad, branches?}; tone tints the step edge; up to 3 branches {label, outcome} render as labelled offshoots under their step.
- Webhook receivedSignature checked against the endpoint secret
- Idempotency key looked up
- seen beforeacknowledged, no write
- Lock taken on the order row
- not acquired in 5 sretry queued
- Order written, charge recorded once
{
"kind": "flow",
"title": "What happens to a webhook delivery now",
"steps": [
{
"label": "Webhook received",
"detail": "Signature checked against the endpoint secret"
},
{
"label": "Idempotency key looked up",
"branches": [
{
"label": "seen before",
"outcome": "acknowledged, no write"
}
]
},
{
"label": "Lock taken on the order row",
"tone": "flat",
"branches": [
{
"label": "not acquired in 5 s",
"outcome": "retry queued"
}
]
},
{
"label": "Order written, charge recorded once",
"tone": "good"
}
]
}A proposed UI as a greyscale wireframe: unmistakably a proposal, never a screenshot (use image for what exists). frame browser|mobile|plain; regions 1–8 {role: navbar|sidebar|toolbar|content|footer, elements 1–12 {el, label?, emphasis?}}. Accent appears only on emphasised elements.
{
"kind": "mock",
"title": "Proposed refunds view for your dashboard",
"frame": "browser",
"regions": [
{
"role": "navbar",
"elements": [
{
"el": "heading",
"label": "Orders"
},
{
"el": "badge",
"label": "3 pending"
},
{
"el": "button",
"label": "Export"
}
]
},
{
"role": "sidebar",
"elements": [
{
"el": "list"
},
{
"el": "text"
}
]
},
{
"role": "content",
"elements": [
{
"el": "heading",
"label": "Refunds outstanding"
},
{
"el": "input",
"label": "Search orders"
},
{
"el": "chart",
"label": "Duplicate charges per day"
},
{
"el": "card",
"label": "#48219 awaiting your provider"
},
{
"el": "button",
"label": "Process all three",
"emphasis": true
}
]
},
{
"role": "footer",
"elements": [
{
"el": "text"
}
]
}
]
}A hairline break between sections. No fields.
{
"kind": "divider"
}