diff --git a/BUILD-NOTES.md b/BUILD-NOTES.md
index 0b517cd..b36da68 100644
--- a/BUILD-NOTES.md
+++ b/BUILD-NOTES.md
@@ -757,3 +757,53 @@ when the production privacy salt correctly rotates. This one test now fixes
 tomorrow/rotation coverage and all production analytics code remain unchanged.
 The unchanged-base failure and corrected fourteen-test pass are recorded in the
 review; no runtime clock, customer data, analytics behavior or financial change.
+
+## Accepted technical templates — Astra integration
+
+Adopts the parent's accepted Batch04 compositions: Changelog / Release Ledger, Migration guide /
+Conversion Workbench, and Runbook / Operator's Bench. gpt-6-astra is the sole integrator. The
+immutable acceptance bundle is
+`aa-template-revamp-1788686289312/batch-04-technical/bundle.json` (SHA-256
+`bd219cc78d9f71c984ed4c49a1744a8c55d25d0213c5a934ecf4e85227b7c886`).
+Integration evidence is published under
+`https://anacreon.ai/downloads/aa-template-revamp-1788686289312/integrated-batch04/`.
+
+Changelog and Migration guide are exact accepted files, including CSS and reader-facing titles.
+Runbook has precisely one authorized copy correction: “Only once the check below has been clean
+for five minutes.” becomes “Only once the verification check has been clean for five minutes.”
+The complete Verify block stays between the four-step ordered list and the list starting at5.
+Every other byte is unchanged. No sample commands are executed; no production instructions,
+credentials, customer records or financial behavior are modified by these inert examples.
+
+Canonical SHA-256:
+- Changelog: `77ff080efe080fc40813e626c7e8d217bd9d5e31d25b7fdeec8e539ef9bda124`
+- Migration guide: `f903fe929606e0538cd66a5ca3f316055a5b17edd808f3b6d2c66bc0431d8606`
+- Runbook: `64f9d4322e821044456be0a4ebd798339a9eb52d1ef16982759eba6995c965c7`
+  (accepted `bc6270a80eb633b43f5e48afb65800563f76b3c6e19364cde3906d50547633cc`
+  plus the one sentence above).
+
+All262 original text nodes,147 numeric occurrences and49 keyed record groups are compared with
+only that explicit expected sentence change. The11 original pre blocks and every command/output
+descendant remain exact; no SVG or meter exists in this batch. The existing Changelog patch-count
+mismatch and Migration guide documentation link are preserved, not “corrected” by inventing facts.
+Historical preview comments remain as acceptance provenance, not an indication that canonical files
+are unapproved. There is no document-height cap.
+
+The unchanged thumbnail generator uses1280px for Changelog and Migration guide, showing the version
+slab/breaking actions and actual code conversion pairs. Runbook uses1920px so its whole precondition
+rail, Stop panel and opening command/output records enter the crop; the explicit trade-off is smaller
+text and wider gutters at gallery scale. All output remains1000×625. Descriptions match these actual
+compositions. Other template sources, PNGs and catalog fields remain untouched.
+
+Native sections name locally scrolling code, with no explicit role/tabindex or lint suppression.
+Chromium keyboard tests exercise native region focus, ArrowRight panning, Enter disclosures, and
+footer reachability inside the real app sandbox. Cross-engine auto-focus and screen-reader
+navigation are not certified. Unit tests protect the Runbook order/copy and original pre descendants,
+including negative controls; e2e coverage runs through the existing seven viewport projects.
+
+Lint has zero errors.40warnings comprise30 unchanged outside this batch and10 in the accepted
+Runbook CSS, replacing7 legacy Runbook warnings: seven descending-specificity diagnostics plus
+three new important-style diagnostics. The three local note-margin overrides preserve the selected
+base/tablet/mobile caution spacing, including the existing inline Verify note margin. They are
+inventoried rather than suppressed or “fixed” by changing approved CSS. No global configuration,
+runtime, schema, billing, dependencies, sandbox or launch settings change.
diff --git a/public/assets/template-thumbs/changelog.png b/public/assets/template-thumbs/changelog.png
index 56cf303..72e016a 100644
Binary files a/public/assets/template-thumbs/changelog.png and b/public/assets/template-thumbs/changelog.png differ
diff --git a/public/assets/template-thumbs/migration-guide.png b/public/assets/template-thumbs/migration-guide.png
index 7cf83bb..0e9d44f 100644
Binary files a/public/assets/template-thumbs/migration-guide.png and b/public/assets/template-thumbs/migration-guide.png differ
diff --git a/public/assets/template-thumbs/runbook.png b/public/assets/template-thumbs/runbook.png
index aff8e64..44c5177 100644
Binary files a/public/assets/template-thumbs/runbook.png and b/public/assets/template-thumbs/runbook.png differ
diff --git a/templates/changelog.html b/templates/changelog.html
index d75479d..cda86a8 100644
--- a/templates/changelog.html
+++ b/templates/changelog.html
@@ -1,380 +1,14 @@
 <!doctype html>
 <html lang="en">
   <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Kestrel Platform — Changelog</title>
-    <!--
-      TEMPLATE  changelog · category: releases
-      VOICE     mono-led — monospace for headings, version numbers, tags and refs;
-                system sans for prose only.
-      SCHEME    high contrast: near-black ink on white, one saturated accent (signal blue),
-                used three times at most. Type colours carry a text label, never colour alone.
-
-      REWRITING THIS ARTIFACT
-      · Replace the copy, keep the structure. Touch :root only to re-theme.
-      · Delete, do not empty. No breaking changes this release? Remove the whole
-        `.breaking` section — never leave a heading with nothing under it.
-      · Two releases in full plus a collapsed list of older ones is the intended density.
-        Past ~12 entries in one release, summarise: the changelog is a reading document.
-    -->
-    <style>
-      :root {
-        --ink: #101114;
-        --muted: #5a5d66;
-        --faint: #6b6f7a;
-        --line: #e4e5e9;
-        --rule: #cfd1d8;
-        --page: #f4f4f6;
-        --card: #ffffff;
-        --accent: #1b3fd8;
-        --add: #146c43;
-        --break: #b42318;
-        --break-bg: #fdf1f0;
-        --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
-        --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-      }
-      *,
-      *::before,
-      *::after { box-sizing: border-box; }
-      html { -webkit-text-size-adjust: 100%; }
-      body {
-        margin: 0;
-        padding: 1.6rem 1rem 2.5rem;
-        background: var(--page);
-        color: var(--ink);
-        font-family: var(--sans);
-        font-size: 1rem;
-        line-height: 1.5;
-        -webkit-font-smoothing: antialiased;
-      }
-      h1,
-      h2,
-      h3 {
-        margin: 0;
-        line-height: 1.2;
-      }
-      p { margin: 0; }
-      ul,
-      ol {
-        margin: 0;
-        padding: 0;
-        list-style: none;
-      }
-      a {
-        color: var(--accent);
-        text-underline-offset: 2px;
-      }
-      code {
-        font-family: var(--mono);
-        font-size: 0.875em;
-        overflow-wrap: anywhere;
-        background: #f0f1f4;
-        border-radius: 3px;
-        padding: 0.1em 0.34em;
-      }
-      .page {
-        max-width: 58rem;
-        margin-inline: auto;
-        background: var(--card);
-        border: 1px solid var(--line);
-        border-radius: 4px;
-        box-shadow: 0 1px 2px rgb(16 17 20 / 0.05), 0 12px 32px -16px rgb(16 17 20 / 0.28);
-      }
-
-      /* HEAD ------------------------------------------------------------- */
-      .head {
-        padding: 1.75rem 1.25rem 1.5rem;
-        border-bottom: 2px solid var(--ink);
-      }
-      .eyebrow {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        letter-spacing: 0.18em;
-        text-transform: uppercase;
-        color: var(--accent);
-        font-weight: 700;
-      }
-      h1 {
-        margin-top: 0.65rem;
-        font-family: var(--mono);
-        font-size: clamp(1.75rem, 5.4vw, 2.5rem);
-        font-weight: 700;
-        letter-spacing: -0.03em;
-      }
-      .lede {
-        margin-top: 0.75rem;
-        max-width: 40rem;
-        color: var(--muted);
-        font-size: 1.0625rem;
-      }
-      .meta {
-        display: flex;
-        flex-wrap: wrap;
-        gap: 0.5rem 2.25rem;
-        margin: 1.5rem 0 0;
-      }
-      .meta div { min-width: 6.5rem; }
-      .meta dt {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        letter-spacing: 0.14em;
-        text-transform: uppercase;
-        color: var(--faint);
-      }
-      .meta dd {
-        margin: 0.2rem 0 0;
-        font-family: var(--mono);
-        font-size: 0.9375rem;
-        font-weight: 600;
-      }
-
-      /* BREAKING --------------------------------------------------------- */
-      .breaking {
-        padding: 1.25rem;
-        background: var(--break-bg);
-        border-bottom: 1px solid var(--line);
-      }
-      .breaking h2 {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: baseline;
-        gap: 0.6rem;
-        font-family: var(--mono);
-        font-size: 1rem;
-        letter-spacing: -0.01em;
-      }
-      .flag {
-        font-size: 0.6875rem;
-        letter-spacing: 0.12em;
-        text-transform: uppercase;
-        color: #ffffff;
-        background: var(--break);
-        padding: 0.2rem 0.45rem;
-        border-radius: 3px;
-      }
-      .breaking ol {
-        margin-top: 1rem;
-        display: grid;
-        gap: 0.85rem;
-      }
-      .breaking li {
-        padding-left: 1.4rem;
-        position: relative;
-        font-size: 0.9375rem;
-        color: #3d2b28;
-      }
-      .breaking li::before {
-        content: "→";
-        position: absolute;
-        left: 0;
-        top: 0;
-        font-family: var(--mono);
-        color: var(--break);
-        font-weight: 700;
-      }
-      .breaking b { color: var(--ink); }
-
-      /* RELEASE ---------------------------------------------------------- */
-      .rel {
-        display: grid;
-        gap: 1rem;
-        padding: 1.5rem 1.25rem;
-        border-bottom: 1px solid var(--line);
-      }
-      .rel__rail {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: baseline;
-        gap: 0.5rem 0.9rem;
-      }
-      .ver {
-        font-family: var(--mono);
-        font-size: 1.5rem;
-        font-weight: 700;
-        letter-spacing: -0.03em;
-      }
-      .rel--past .ver {
-        font-size: 1.25rem;
-        color: var(--muted);
-      }
-      .stamp {
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        color: var(--faint);
-      }
-      .chip {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        letter-spacing: 0.1em;
-        text-transform: uppercase;
-        font-weight: 700;
-        border: 1px solid var(--rule);
-        border-radius: 3px;
-        padding: 0.15rem 0.4rem;
-        color: var(--muted);
-      }
-      .chip--now {
-        border-color: var(--accent);
-        color: var(--accent);
-      }
-      .summary {
-        font-size: 1rem;
-        color: var(--muted);
-        max-width: 42rem;
-      }
-      .entries {
-        display: grid;
-        gap: 0.9rem;
-        margin-top: 1rem;
-      }
-      .entry {
-        font-size: 0.9375rem;
-        display: grid;
-        gap: 0.35rem 1rem;
-        padding-top: 0.9rem;
-        border-top: 1px solid var(--line);
-      }
-      .entry:first-child {
-        padding-top: 0;
-        border-top: 0;
-      }
-      .tag {
-        align-self: start;
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.1em;
-        text-transform: uppercase;
-        display: inline-flex;
-        align-items: center;
-        gap: 0.4rem;
-        color: var(--muted);
-      }
-      .tag::before {
-        content: "";
-        width: 0.5rem;
-        height: 0.5rem;
-        background: currentColor;
-        border-radius: 1px;
-        flex: none;
-      }
-      .tag--add { color: var(--add); }
-      .tag--fix { color: var(--accent); }
-      .tag--rm { color: var(--break); }
-      .entry h3 {
-        font-size: 1.0625rem;
-        font-weight: 650;
-        letter-spacing: -0.01em;
-      }
-      .entry p { color: var(--muted); }
-      .ref {
-        font-family: var(--mono);
-        font-size: 0.75rem;
-        color: var(--faint);
-        overflow-wrap: anywhere;
-      }
-
-      /* OLDER ------------------------------------------------------------ */
-      .older {
-        padding: 1rem 1.25rem;
-        border-bottom: 1px solid var(--line);
-      }
-      .older > summary {
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        font-weight: 700;
-        letter-spacing: 0.02em;
-        cursor: pointer;
-        padding: 0.6rem 0;
-        min-height: 44px;
-        display: flex;
-        align-items: center;
-        gap: 0.5rem;
-        color: var(--accent);
-      }
-      .older > summary::marker { content: ""; }
-      .older > summary::before {
-        content: "+";
-        font-weight: 700;
-        border: 1px solid var(--rule);
-        border-radius: 3px;
-        width: 1.35rem;
-        height: 1.35rem;
-        display: inline-flex;
-        align-items: center;
-        justify-content: center;
-        flex: none;
-      }
-      .older[open] > summary::before { content: "–"; }
-      .older:focus-within > summary::before {
-        outline: 2px solid var(--accent);
-        outline-offset: 2px;
-      }
-      .archive {
-        margin-top: 0.75rem;
-        display: grid;
-        gap: 0.6rem;
-      }
-      .archive li {
-        display: grid;
-        grid-template-columns: 4.25rem 1fr;
-        gap: 0.15rem 0.9rem;
-        padding-top: 0.6rem;
-        border-top: 1px solid var(--line);
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-      .archive b {
-        font-family: var(--mono);
-        font-weight: 700;
-        color: var(--ink);
-        font-size: 0.875rem;
-      }
-
-      /* FOOT ------------------------------------------------------------- */
-      .foot {
-        padding: 1.25rem;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        color: var(--faint);
-        display: grid;
-        gap: 0.4rem;
-      }
-
-      /* 34rem+ ----------------------------------------------------------- */
-      @media (min-width: 34rem) {
-        .entry { grid-template-columns: 7rem 1fr; }
-        .tag {
-          grid-row: span 3;
-          padding-top: 0.28rem;
-        }
-      }
-      @media (min-width: 48rem) {
-        body { padding: 3.5rem 2rem 5rem; }
-        .head { padding: 3rem 2.5rem 2rem; }
-        .breaking,
-        .older,
-        .foot { padding: 1.75rem 2.5rem; }
-        .rel {
-          grid-template-columns: 11rem 1fr;
-          gap: 1.5rem 2rem;
-          padding: 2.25rem 2.5rem;
-        }
-        .rel__rail { display: block; }
-        .stamp,
-        .chip {
-          display: block;
-          width: fit-content;
-          margin-top: 0.5rem;
-        }
-        .foot { grid-template-columns: 1fr auto; }
-      }
-    </style>
+    <!-- Preview-only redesign. All sample text and command/output bytes are retained from the frozen source. Never execute sample commands. System fonts and native disclosures only. Delete whole empty sections for thin documents. No document-height cap. --><style>:root{--ink:#151a24;--muted:#535e70;--line:#cbd2dc;--paper:#fafbfc;--accent:#2444b8;--break:#9e291f;--break-bg:#fff4f1;--sans:"Helvetica Neue",Helvetica,Arial,sans-serif;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace}*{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{margin:0;background:var(--paper);color:var(--ink);font:17px/1.6 var(--sans)}h1,h2,h3,p{margin:0}h1,h2,h3{line-height:1.25}main{max-width:1184px;margin:auto;padding:38px 32px 48px;overflow-wrap:anywhere}h1{font:700 clamp(28px,3.6vw,38px)/1.15 var(--mono);letter-spacing:-.06em;margin:10px 0 16px}.eyebrow{font:700 12px/1.5 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--accent)}.head{display:grid;grid-template-columns:1fr 1fr;column-gap:44px;padding-bottom:26px}.head .eyebrow{grid-column:1}.head h1{grid-column:1}.lede{grid-area:1/2/3;align-self:end;color:var(--muted);max-width:58ch}.release-intro{display:grid;grid-template-columns:270px minmax(0,1fr);gap:32px;align-items:start;border-top:3px solid var(--ink);padding:30px 0}.meta{margin:0;padding:24px;background:#e7edff;display:grid;gap:22px;min-width:0}.meta dt{font:600 12px/1.5 var(--mono);text-transform:uppercase;letter-spacing:.06em;color:#354975}.meta dd{margin:3px 0 0;font:600 16px/1.5 var(--mono)}.meta>div:first-child dd{font-size:58px;line-height:1.1;letter-spacing:-.07em;color:var(--accent)}.breaking h2{font:650 21px/1.4 var(--sans)}.flag{display:table;font:700 12px/1.5 var(--mono);background:var(--break);color:#fff;padding:4px 8px;margin-bottom:12px;text-transform:uppercase}.flag::before{content:'! ';}.breaking ol{margin:18px 0 0;padding:0;list-style-position:inside}.breaking li{padding:18px 20px;background:var(--break-bg);border:1px solid #d8b9b4;margin-top:10px}.breaking li::marker{font:700 14px var(--mono);color:var(--break)}code{font: .9em/1.5 var(--mono);background:#eef0f4;padding:.08em .16em;overflow-wrap:anywhere}.rel{display:grid;grid-template-columns:220px minmax(0,1fr);gap:40px;padding:30px 0;border-top:2px solid var(--ink)}.rel__rail{min-width:0}.ver{font:700 38px/1.2 var(--mono);letter-spacing:-.06em}.stamp{font:14px/1.6 var(--mono);margin-top:12px;color:var(--muted)}.chip{display:table;padding:3px 8px;border:1px solid var(--muted);font:600 12px/1.5 var(--mono);margin-top:12px}.chip--now{color:var(--accent);border-color:var(--accent)}.summary{max-width:70ch;color:var(--muted);margin-bottom:22px}.entries{margin:0;padding:0;list-style-type:none}.entry{display:grid;grid-template-columns:112px minmax(0,1fr);gap:24px;border-top:1px solid var(--line);padding:20px 0}.tag{font:700 12px/1.5 var(--mono);text-transform:uppercase;padding-top:3px;display:flex;gap:8px;align-items:baseline}.tag::before{content:'↔';font-size:16px}.tag--add::before{content:'+'}.tag--fix::before{content:'✓'}.tag--rm::before{content:'−'}.tag--add{color:#166238}.tag--fix{color:var(--accent)}.tag--rm{color:var(--break)}.entry-copy{min-width:0}.entry h3{font-size:20px;letter-spacing:-.02em;margin-bottom:8px}.entry p{color:var(--muted)}.entry .ref{font:16px/1.6 var(--mono);margin-top:10px;color:var(--muted)}.older{border-top:2px solid var(--ink);border-bottom:1px solid var(--line);padding:14px 0}.older>summary{font:600 16px/1.6 var(--mono);padding:12px 0;cursor:pointer;color:var(--accent)}.archive{padding:0;margin:8px 0 12px;list-style:none}.archive li{display:grid;grid-template-columns:100px minmax(0,1fr);gap:20px;padding:15px 0;border-top:1px solid var(--line)}.archive b{font-family:var(--mono)}.foot{display:flex;flex-wrap:wrap;justify-content:space-between;gap:12px 32px;padding-top:24px;color:var(--muted);font-size:16px}:focus-visible{outline:3px solid var(--accent);outline-offset:4px}@media(max-width:900px){main{padding:28px 24px 40px}.head{display:block}.lede{margin-top:16px}.release-intro{grid-template-columns:210px minmax(0,1fr);gap:24px}.meta{padding:20px}.meta>div:first-child dd{font-size:48px}.rel{grid-template-columns:145px minmax(0,1fr);gap:24px}.entry{grid-template-columns:90px minmax(0,1fr);gap:14px}.ver{font-size:30px}}@media(max-width:600px){main{padding:24px 16px 36px}.release-intro{display:block;padding:22px 0}.meta{grid-template-columns:1fr 1fr;gap:18px 12px;margin-bottom:24px}.meta>div:first-child dd{font-size:40px}.breaking li{padding:16px}.rel{display:block;padding:24px 0}.rel__rail{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px 18px;margin-bottom:18px}.stamp,.chip{margin:0}.entry{display:block;padding:20px 0}.tag{margin-bottom:9px}.entry h3{font-size:19px}.archive li{grid-template-columns:72px minmax(0,1fr);gap:12px}.foot{display:block}.foot p+p{margin-top:12px}}
+</style>
   </head>
   <body>
     <main class="page">
-      <!-- HEAD ---------------------------------------------------------- -->
       <header class="head">
         <p class="eyebrow">Changelog</p>
         <h1>Kestrel Platform</h1>
@@ -382,15 +16,14 @@
           Every user-visible change to the Kestrel API, CLI and dashboard. Dates are the day a
           release reached the stable channel.
         </p>
-        <dl class="meta">
+        </header>
+
+      <div class="release-intro"><dl class="meta">
           <div><dt>Latest</dt><dd>4.3.0</dd></div>
           <div><dt>Released</dt><dd>4 Sep 2026</dd></div>
           <div><dt>Channel</dt><dd>stable</dd></div>
           <div><dt>API version</dt><dd>2026-06-01</dd></div>
         </dl>
-      </header>
-
-      <!-- BREAKING ------------------------------------------------------ -->
       <section class="breaking" aria-labelledby="breaking-h">
         <h2 id="breaking-h">
           <span class="flag">Breaking</span> Two changes need action before you upgrade
@@ -409,8 +42,7 @@
         </ol>
       </section>
 
-      <!-- CURRENT RELEASE ----------------------------------------------- -->
-      <section class="rel" aria-labelledby="v430">
+      </div><section class="rel" aria-labelledby="v430">
         <div class="rel__rail">
           <h2 class="ver" id="v430">4.3.0</h2>
           <p class="stamp">4 Sep 2026</p>
@@ -424,55 +56,54 @@
           <ul class="entries">
             <li class="entry">
               <span class="tag tag--add">Added</span>
-              <h3>Backfill for scheduled runs</h3>
+              <div class="entry-copy"><h3>Backfill for scheduled runs</h3>
               <p>
                 <code>kestrel schedule backfill &lt;id&gt; --from 2026-08-01</code> replays a
                 schedule over a past window, at most 50 runs at a time, with the same concurrency
                 limit as live execution.
               </p>
               <p class="ref">#2841 · docs/schedules#backfill</p>
-            </li>
+            </div></li>
             <li class="entry">
               <span class="tag tag--add">Added</span>
-              <h3>Run log streaming over SSE</h3>
+              <div class="entry-copy"><h3>Run log streaming over SSE</h3>
               <p>
                 <code>GET /v1/runs/:id/logs?stream=true</code> emits log lines as they are written.
                 Logs are retained for 30 days on Team plans, 7 on Free.
               </p>
               <p class="ref">#2803</p>
-            </li>
+            </div></li>
             <li class="entry">
               <span class="tag">Changed</span>
-              <h3>Run logs keep the last 200k lines, not 10k</h3>
+              <div class="entry-copy"><h3>Run logs keep the last 200k lines, not 10k</h3>
               <p>
                 Long-running jobs were silently losing their tail, which is precisely the part
                 anyone reads. Older lines are now dropped from the head instead.
               </p>
               <p class="ref">#2790 · reported by 14 workspaces</p>
-            </li>
+            </div></li>
             <li class="entry">
               <span class="tag tag--fix">Fixed</span>
-              <h3>Retries no longer double-count against the rate limit</h3>
+              <div class="entry-copy"><h3>Retries no longer double-count against the rate limit</h3>
               <p>
                 An automatic retry consumed two units of quota instead of one. Affected workspaces
                 have had the difference credited back to the current billing period.
               </p>
               <p class="ref">#2822 · severity: high</p>
-            </li>
+            </div></li>
             <li class="entry">
               <span class="tag tag--rm">Removed</span>
-              <h3>The legacy <code>/v0</code> endpoints are gone</h3>
+              <div class="entry-copy"><h3>The legacy <code>/v0</code> endpoints are gone</h3>
               <p>
                 Deprecated since March, at 0.02% of traffic for the last eight weeks. Every caller
                 was contacted directly in August.
               </p>
               <p class="ref">#2755 · announced 12 Mar 2026</p>
-            </li>
+            </div></li>
           </ul>
         </div>
       </section>
 
-      <!-- PREVIOUS RELEASE ---------------------------------------------- -->
       <section class="rel rel--past" aria-labelledby="v422">
         <div class="rel__rail">
           <h2 class="ver" id="v422">4.2.2</h2>
@@ -484,24 +115,23 @@
           <ul class="entries">
             <li class="entry">
               <span class="tag tag--fix">Fixed</span>
-              <h3>Webhook signatures verified against the wrong secret after rotation</h3>
+              <div class="entry-copy"><h3>Webhook signatures verified against the wrong secret after rotation</h3>
               <p>
                 For roughly six hours after a secret was rotated, deliveries were signed with the
                 retired key. Rotation now writes both keys and accepts either for 24 hours.
               </p>
               <p class="ref">#2731 · postmortem published</p>
-            </li>
+            </div></li>
             <li class="entry">
               <span class="tag">Changed</span>
-              <h3>Timeouts are reported in seconds everywhere</h3>
+              <div class="entry-copy"><h3>Timeouts are reported in seconds everywhere</h3>
               <p>The API returned milliseconds, the CLI printed seconds, and the dashboard mixed both.</p>
               <p class="ref">#2724</p>
-            </li>
+            </div></li>
           </ul>
         </div>
       </section>
 
-      <!-- ARCHIVE ------------------------------------------------------- -->
       <details class="older">
         <summary>Six earlier releases, 4.0.0 – 4.2.1</summary>
         <ul class="archive">
diff --git a/templates/manifest.ts b/templates/manifest.ts
index 2bec6a3..a80b797 100644
--- a/templates/manifest.ts
+++ b/templates/manifest.ts
@@ -169,7 +169,7 @@ export const starterTemplateManifest = [
         "slug": "changelog",
         "category": "releases",
         "name": "Changelog",
-        "description": "Release notes as a reading document: breaking changes hoisted above the fold, versions set in the margin, entries tagged Added / Changed / Fixed / Removed, and older releases collapsed behind one line. Rehash it for any product's release history.",
+        "description": "A release ledger: the latest version and date beside two complete breaking-action records, followed by version, change type and release notes, with a native archive.",
         "type": "html",
         "content_file": "changelog.html",
         "thumbnail": "/assets/template-thumbs/changelog.png",
@@ -189,7 +189,7 @@ export const starterTemplateManifest = [
         "slug": "migration-guide",
         "category": "releases",
         "name": "Migration guide",
-        "description": "A version-to-version upgrade guide built around before/after code pairs: a dated deadline callout, an effort strip, every breaking change shown as old code beside new code, a three-command upgrade path with real terminal output, and a collapsed troubleshooting list.",
+        "description": "A conversion workbench: version and deadline context above paired before/after code, followed by three execution steps with exact commands and output, troubleshooting and support dates.",
         "type": "html",
         "content_file": "migration-guide.html",
         "thumbnail": "/assets/template-thumbs/migration-guide.png",
@@ -220,10 +220,11 @@ export const starterTemplateManifest = [
         "slug": "runbook",
         "category": "plans",
         "name": "Runbook",
-        "description": "A procedure somebody follows at 3am: preconditions as runnable checks with a stop rule, five numbered steps each with the command and its real output, a verification gate placed between the two steps that matter, a rollback with its safe window stated.",
+        "description": "An operator’s bench: static preconditions beside the procedure, labelled command/output records and a complete verification check between steps four and five, followed by rollback and escalation.",
         "type": "html",
         "content_file": "runbook.html",
         "thumbnail": "/assets/template-thumbs/runbook.png",
-        "slots": []
+        "slots": [],
+        "thumbnail_viewport": 1920
       }
     ] as const;
diff --git a/templates/migration-guide.html b/templates/migration-guide.html
index 3bf6cbb..b2d1d8a 100644
--- a/templates/migration-guide.html
+++ b/templates/migration-guide.html
@@ -1,417 +1,26 @@
 <!doctype html>
 <html lang="en">
   <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Halyard SDK v3 → v4 migration guide</title>
-    <!--
-      TEMPLATE  migration-guide · category: releases
-      VOICE     mono-led — monospace for headings, labels, code and every version number;
-                system sans for prose only. Same family as `changelog`, different shape.
-      SCHEME    high contrast: near-black ink on white, one saturated accent (signal blue).
-                Before/after panels use red and green, always with the words "Before" and
-                "After" and a − / + sign, so colour is never the only cue.
+    <!-- Preview-only redesign. All sample text and command/output bytes are retained from the frozen source. Never execute sample commands. System fonts and native disclosures only. Delete whole empty sections for thin documents. No document-height cap. --><style>:root{--ink:#171b29;--muted:#505a6b;--line:#c9d0dc;--accent:#2942ba;--page:#fff;--before:#973321;--after:#185b42;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;--sans:system-ui,-apple-system,"Segoe UI",sans-serif;--grotesque:"Helvetica Neue",Helvetica,Arial,sans-serif}*{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{margin:0;color:var(--ink);background:var(--page);font:17px/1.6 var(--sans)}main{max-width:1248px;margin:auto;padding:36px 32px 42px;overflow-wrap:anywhere}h1,h2,h3,p{margin:0}h1,h2,h3{font-family:var(--grotesque);line-height:1.25;letter-spacing:-.025em}.head{display:grid;grid-template-columns:1.2fr 1fr;gap:48px;padding-bottom:30px}h1{font-size:clamp(30px,3.4vw,42px);margin-top:10px}.eyebrow{color:var(--accent);font:700 12px/1.5 var(--sans);letter-spacing:.13em;text-transform:uppercase}.jump{font:650 38px/1.2 var(--grotesque);letter-spacing:-.04em;margin-top:18px}.jump s{color:var(--muted);text-decoration-thickness:1px}.jump span{color:var(--accent);margin:0 12px}.lede{color:var(--muted);margin-top:18px;max-width:66ch}.context{align-self:center;min-width:0}.deadline{background:#eef0fd;border:1px solid #b9c3ec;padding:20px 22px}.deadline b{color:var(--accent);font:700 13px/1.5 var(--sans);text-transform:uppercase;letter-spacing:.07em}.deadline p{margin-top:8px}.facts{margin:20px 0 0;display:grid;grid-template-columns:1fr 1fr;gap:15px 24px}.facts dt{font-size:13px;font-weight:600;color:var(--muted)}.facts dd{font-size:16px;font-weight:600;margin:3px 0 0}section{padding:28px 0;border-top:2px solid var(--ink)}h2{font-size:25px}.change{display:grid;grid-template-columns:238px minmax(0,1fr);gap:28px;margin-top:28px;padding:24px 0;border-top:1px solid var(--line)}.change-caption h3{font-size:21px}.change-caption p{margin-top:12px;color:var(--muted)}code{font: .9em/1.6 var(--mono);overflow-wrap:anywhere}.pair{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:14px;align-items:start}.panel{min-width:0;border:1px solid var(--line)}.panel__label{padding:11px 16px;font:650 14px/1.5 var(--sans);border-bottom:1px solid var(--line);background:#f2f3f6}.panel--before{border-color:#d6bbb4}.panel--before .panel__label{background:#fcf2ef;color:var(--before);border-color:#d6bbb4}.panel--after{border-color:#abc9bd}.panel--after .panel__label{background:#eaf5ef;color:var(--after);border-color:#abc9bd}.panel--before .panel__label::before{content:'− ';}.panel--after .panel__label::before{content:'+ ';}pre{margin:0;padding:18px 16px;white-space:pre;overflow:auto;font:14px/1.65 var(--mono);tab-size:2}pre code{font:inherit;overflow-wrap:normal}.cm{color:#596475}.renames{margin:0;padding:0;list-style:none}.renames li{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:18px;border-top:1px solid var(--line);padding:16px;font:14px/1.65 var(--mono)}.renames li:first-child{border:0}.renames s{color:var(--before)}.renames b{color:var(--after)}.renames s::before{content:'− ';}.renames b::before{content:'+ ';}.steps{list-style-position:inside;margin:20px 0 0;padding:0}.steps>li{padding:24px 0;border-top:1px solid var(--line);display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:30px;counter-increment:step}.steps{counter-reset:step}.steps h3{font-size:21px}.steps h3::before{content:counter(step) ' / ';font:600 15px var(--mono);color:var(--accent)}.step-copy p{color:var(--muted);margin-top:12px}.cmd{display:block;min-width:0;align-self:start;white-space:pre;overflow:auto;padding:18px 20px;background:#182137;color:#f2f4ff;font:14px/1.65 var(--mono);tab-size:2}.cmd .p{color:#b4c5ff}.cmd .o{color:#c0cadc;display:block;border-top:1px solid #65738c;padding-top:12px;margin-top:12px}.trouble{margin-top:20px}.trouble details{border-top:1px solid var(--line)}.trouble details:last-child{border-bottom:1px solid var(--line)}summary{cursor:pointer;font:600 16px/1.5 var(--mono);padding:16px 0;color:var(--before)}.trouble p{margin:4px 0 20px;color:var(--muted)}.dates{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:28px;list-style:none;margin:24px 0 0;padding:0}.dates li{border:1px solid var(--line);padding:18px}.dates b{display:block;font:650 19px/1.5 var(--grotesque);margin-bottom:9px}.dates li[data-now]{background:#eef0fd;border-color:#b9c3ec}.dates span{color:var(--muted)}footer{border-top:1px solid var(--line);padding-top:24px;font-size:16px;color:var(--muted)}a{color:var(--accent);text-underline-offset:3px;display:inline-block;min-height:44px;padding:8px 0}:focus-visible{outline:3px solid var(--accent);outline-offset:3px}@media(max-width:1040px){main{padding:28px 24px 40px}.head{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:28px}.change{display:block}.change-caption{margin-bottom:20px}.change-caption p{max-width:74ch}.steps>li{gap:24px}.facts{gap:12px}}@media(max-width:720px){main{padding:24px 16px 36px}.head{display:block}.context{margin-top:24px}.jump{font-size:34px}.pair{display:block}.panel+.panel{margin-top:16px}.change{margin-top:22px;padding:22px 0}.steps>li{display:block}.cmd{margin-top:18px}.dates{display:block}.dates li+li{margin-top:14px}.renames li{display:block}.renames b{display:block;margin-top:8px}h2{font-size:24px}.deadline{padding:18px}.panel__label{padding:12px 14px}pre{padding:16px 14px}}
 
-      REWRITING THIS ARTIFACT
-      · Replace the copy, keep the structure. Touch :root only to re-theme.
-      · Delete, do not empty. No codemod? Remove that step. No troubleshooting yet?
-        Remove the whole section — never leave a heading with nothing under it.
-      · Keep code lines under ~44 characters so 390px never needs a horizontal drag.
-      · Three before/after pairs and three steps is the intended density.
-    -->
-    <style>
-      :root {
-        --ink: #101114;
-        --muted: #55585f;
-        --faint: #6b6f7a;
-        --line: #e4e5e9;
-        --page: #ffffff;
-        --wash: #f5f5f7;
-        --accent: #1b3fd8;
-        --before: #b42318;
-        --before-bg: #fdf2f1;
-        --after: #146c43;
-        --after-bg: #eff7f2;
-        --mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
-        --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-      }
-      *,
-      *::before,
-      *::after { box-sizing: border-box; }
-      html { -webkit-text-size-adjust: 100%; }
-      body {
-        margin: 0;
-        background: var(--page);
-        color: var(--ink);
-        font-family: var(--sans);
-        font-size: 1rem;
-        line-height: 1.6;
-        -webkit-font-smoothing: antialiased;
-      }
-      h1,
-      h2,
-      h3 {
-        margin: 0;
-        line-height: 1.2;
-        font-family: var(--mono);
-        letter-spacing: -0.02em;
-      }
-      p { margin: 0; }
-      ul,
-      ol {
-        margin: 0;
-        padding: 0;
-        list-style: none;
-      }
-      a { color: var(--accent); }
-      code {
-        font-family: var(--mono);
-        font-size: 0.875em;
-        overflow-wrap: break-word;
-      }
-      .wrap {
-        max-width: 56rem;
-        margin-inline: auto;
-        padding-inline: 1.25rem;
-      }
-      section {
-        padding-block: 0.56rem;
-        border-top: 1px solid var(--line);
-      }
-      .h2 {
-        font-size: 1.25rem;
-        font-weight: 700;
-      }
-      .lede {
-        margin-top: 0.75rem;
-        max-width: 46ch;
-        color: var(--muted);
-      }
-
-      /* HEAD ------------------------------------------------------------- */
-      .head { padding-block: 1.3rem 0.92rem; }
-      .eyebrow {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.16em;
-        text-transform: uppercase;
-        color: var(--accent);
-      }
-      h1 {
-        margin-top: 0.75rem;
-        font-size: clamp(1.625rem, 5.2vw, 2.375rem);
-        font-weight: 700;
-      }
-      .jump {
-        display: inline-flex;
-        align-items: baseline;
-        gap: 0.5rem;
-        margin-top: 0.9rem;
-        font-family: var(--mono);
-        font-size: 0.9375rem;
-        font-weight: 700;
-      }
-      .jump s {
-        color: var(--faint);
-        text-decoration-thickness: 1px;
-      }
-      .jump span { color: var(--accent); }
-      .deadline {
-        display: flex;
-        flex-wrap: wrap;
-        align-items: baseline;
-        gap: 0.35rem 0.75rem;
-        margin-top: 1rem;
-        padding: 0.65rem 0.9rem;
-        background: var(--ink);
-        color: #ffffff;
-        border-radius: 4px;
-      }
-      .deadline b {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        letter-spacing: 0.12em;
-        text-transform: uppercase;
-        background: #ffd166;
-        color: var(--ink);
-        padding: 0.2rem 0.45rem;
-        border-radius: 3px;
-      }
-      .deadline p {
-        font-size: 0.9375rem;
-        color: #e7e8ec;
-      }
-      .facts {
-        display: grid;
-        grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
-        gap: 0.7rem 1.5rem;
-        margin-top: 1rem;
-      }
-      .facts dt {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        letter-spacing: 0.14em;
-        text-transform: uppercase;
-        color: var(--faint);
-      }
-      .facts dd {
-        margin: 0.2rem 0 0;
-        font-family: var(--mono);
-        font-size: 0.9375rem;
-        font-weight: 600;
-      }
-
-      /* BEFORE / AFTER --------------------------------------------------- */
-      .change { margin-top: 0.72rem; }
-      .change h3 {
-        font-size: 1rem;
-        font-weight: 700;
-      }
-      .change > p {
-        margin-top: 0.4rem;
-        max-width: 60ch;
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-      .pair {
-        display: grid;
-        gap: 0.5rem;
-        margin-top: 0.65rem;
-      }
-      .panel {
-        border: 1px solid var(--line);
-        border-radius: 4px;
-        overflow: hidden;
-        background: var(--wash);
-      }
-      .panel__label {
-        display: flex;
-        align-items: center;
-        gap: 0.4rem;
-        padding: 0.4rem 0.7rem;
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.1em;
-        text-transform: uppercase;
-        border-bottom: 1px solid var(--line);
-      }
-      .panel--before .panel__label {
-        background: var(--before-bg);
-        color: var(--before);
-      }
-      .panel--after .panel__label {
-        background: var(--after-bg);
-        color: var(--after);
-      }
-      .panel__label::before {
-        font-size: 0.875rem;
-        line-height: 1;
-      }
-      .panel--before .panel__label::before { content: "−"; }
-      .panel--after .panel__label::before { content: "+"; }
-      pre {
-        margin: 0;
-        padding: 0.5rem 0.7rem;
-        /* wrap rather than clip: a hidden 10px scroll with no affordance reads as broken */
-        white-space: pre-wrap;
-        overflow-wrap: break-word;
-        background: var(--page);
-        font-size: 0.8125rem;
-        line-height: 1.45;
-        tab-size: 2;
-      }
-      /* 1em, NOT 1rem: rem is root-relative and would render this code at 16px
-         inside a pre declared at 13px */
-      pre code { font-size: 1em; }
-      .cm { color: var(--faint); }
-      .panel__label--diff {
-        background: var(--wash);
-        color: var(--muted);
-      }
-      .renames {
-        display: grid;
-        background: var(--page);
-      }
-      .renames li {
-        display: grid;
-        gap: 0.15rem 0.75rem;
-        padding: 0.5rem 0.7rem;
-        border-top: 1px solid var(--line);
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        line-height: 1.5;
-        overflow-wrap: break-word;
-      }
-      .renames li:first-child { border-top: 0; }
-      .renames s {
-        color: var(--before);
-        text-decoration-thickness: 1px;
-      }
-      .renames b {
-        color: var(--after);
-        font-weight: 600;
-      }
-      .renames s::before,
-      .renames b::before {
-        display: inline-block;
-        width: 1ch;
-        color: var(--faint);
-      }
-      .renames s::before { content: "−"; }
-      .renames b::before { content: "+"; }
-
-      /* STEPS ------------------------------------------------------------ */
-      .steps {
-        counter-reset: s;
-        display: grid;
-        gap: 1.05rem;
-        margin-top: 1.05rem;
-      }
-      .steps > li {
-        counter-increment: s;
-        display: grid;
-        grid-template-columns: 1.75rem 1fr;
-        gap: 0.4rem 0.85rem;
-        align-content: start;
-      }
-      .steps > li > * { grid-column: 2; }
-      .steps > li::before {
-        content: counter(s);
-        grid-area: 1 / 1;
-        width: 1.75rem;
-        height: 1.75rem;
-        border-radius: 4px;
-        background: var(--ink);
-        color: #ffffff;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        font-weight: 700;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-      .steps h3 {
-        font-size: 1rem;
-        font-weight: 700;
-        align-self: center;
-      }
-      .steps p {
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-      .cmd {
-        display: block;
-        white-space: pre-wrap;
-        overflow-wrap: break-word;
-        padding: 0.7rem 0.8rem;
-        border-radius: 4px;
-        background: var(--ink);
-        color: #e9eaee;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        line-height: 1.5;
-      }
-      .cmd .p {
-        color: #8aa4ff;
-        user-select: none;
-      }
-      .cmd .o { color: #a9adba; }
-
-      /* TROUBLE ---------------------------------------------------------- */
-      .trouble {
-        margin-top: 0.85rem;
-        border-top: 1px solid var(--line);
-      }
-      .trouble details { border-bottom: 1px solid var(--line); }
-      .trouble summary {
-        display: flex;
-        align-items: baseline;
-        gap: 0.6rem;
-        padding: 0.6rem 0;
-        min-height: 44px;
-        box-sizing: border-box;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        font-weight: 700;
-        color: var(--before);
-        cursor: pointer;
-        list-style: none;
-        overflow-wrap: break-word;
-      }
-      .trouble summary::-webkit-details-marker { display: none; }
-      .trouble summary::before {
-        content: "+";
-        flex: none;
-        width: 1em;
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-      .trouble details[open] summary::before { content: "\2212"; }
-      .trouble summary:focus-visible {
-        outline: 2px solid var(--accent);
-        outline-offset: 2px;
-      }
-      .trouble p {
-        margin: 0 0 0.75rem 1.6rem;
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-
-      /* TIMELINE --------------------------------------------------------- */
-      .dates {
-        display: grid;
-        grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
-        gap: 0.5rem 1.5rem;
-        margin-top: 0.75rem;
-      }
-      .dates li {
-        padding-top: 0.6rem;
-        border-top: 2px solid var(--line);
-        font-size: 0.875rem;
-        color: var(--muted);
-      }
-      .dates b {
-        display: block;
-        margin-bottom: 0.15rem;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        color: var(--ink);
-      }
-      .dates li[data-now] { border-top-color: var(--accent); }
-      .dates li[data-now] b { color: var(--accent); }
-      .foot {
-        padding-block: 0.75rem 1rem;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        color: var(--faint);
-        border-top: 1px solid var(--line);
-      }
-
-      @media (min-width: 44rem) {
-        .wrap { padding-inline: 2rem; }
-        section { padding-block: 1.9rem; }
-        .head { padding-block: 3rem 2.25rem; }
-        .pair {
-          grid-template-columns: 1fr 1fr;
-          align-items: start;
-        }
-        .steps { gap: 1.5rem; }
-      }
-    </style>
+/* Native named scroll regions; only genuinely overflowing regions enter Chromium tab order. */
+section.code-region{margin:0;padding:0;border:0;min-width:0;overflow:auto}section.code-region>pre,section.code-region>.cmd,section.code-region>code{width:max-content;min-width:100%;max-width:none;overflow:visible;overflow-wrap:normal}
+.steps>li>.code-region{align-self:start}section.code-region>.cmd{margin:0}@media(max-width:720px){.steps>li>.code-region{margin-top:18px}}
+</style>
   </head>
   <body>
-    <!-- HEAD ------------------------------------------------------------ -->
-    <header class="head wrap">
-      <p class="eyebrow">Migration guide</p>
+    <main class="document"><header class="head wrap">
+      <div class="identity"><p class="eyebrow">Migration guide</p>
       <h1>Halyard SDK v3 → v4</h1>
       <p class="jump"><s>3.8.2</s> <span>→</span> 4.1.0</p>
       <p class="lede">
         Three breaking changes, one codemod, about twenty minutes. No coordinated deploy: v3 and
         v4 clients can talk to the API at the same time.
       </p>
-      <div class="deadline">
+      </div><div class="context"><div class="deadline">
         <b>Deadline</b>
         <p>v3 stops receiving security fixes on <strong>1 December 2026</strong> — 88 days away.</p>
       </div>
@@ -421,62 +30,61 @@
         <div><dt>Breaking</dt><dd>3 changes</dd></div>
         <div><dt>Rollback</dt><dd>Safe, any time</dd></div>
       </dl>
-    </header>
+    </div></header>
 
-    <!-- WHAT CHANGED ---------------------------------------------------- -->
     <section class="wrap">
       <h2 class="h2">What breaks, and what to write instead</h2>
 
       <article class="change">
-        <h3>1 · The client takes an options object</h3>
+        <div class="change-caption"><h3 id="change-1">1 · The client takes an options object</h3>
         <p>
           A bare API-key string is no longer accepted, and the export is now named.
         </p>
-        <div class="pair">
+        </div><div class="pair">
           <div class="panel panel--before">
-            <p class="panel__label">Before · v3</p>
-            <pre><code>import Halyard from "halyard";
+            <p class="panel__label" id="change-1-label-0">Before · v3</p>
+            <section class="code-region" aria-labelledby="change-1 change-1-label-0"><pre><code>import Halyard from "halyard";
 const hal = new Halyard(
   process.env.HALYARD_API_KEY
 );</code></pre>
-          </div>
+          </section></div>
           <div class="panel panel--after">
-            <p class="panel__label">After · v4</p>
-            <pre><code>import { Halyard } from "halyard";
+            <p class="panel__label" id="change-1-label-1">After · v4</p>
+            <section class="code-region" aria-labelledby="change-1 change-1-label-1"><pre><code>import { Halyard } from "halyard";
 const hal = new Halyard({
   token: process.env.HALYARD_TOKEN,
 });</code></pre>
-          </div>
+          </section></div>
         </div>
       </article>
 
       <article class="change">
-        <h3>2 · List calls return a page, not an array</h3>
+        <div class="change-caption"><h3 id="change-2">2 · List calls return a page, not an array</h3>
         <p>
           Every <code>list*</code> method returns <code>{ data, cursor }</code>. Iterate the client
           when you want all of it — it pages for you.
         </p>
-        <div class="pair">
+        </div><div class="pair">
           <div class="panel panel--before">
-            <p class="panel__label">Before · v3</p>
-            <pre><code>const runs = await hal.listRuns();
+            <p class="panel__label" id="change-2-label-0">Before · v3</p>
+            <section class="code-region" aria-labelledby="change-2 change-2-label-0"><pre><code>const runs = await hal.listRuns();
 runs.map(r =&gt; r.id);
 <span class="cm">// silently capped at 100</span></code></pre>
-          </div>
+          </section></div>
           <div class="panel panel--after">
-            <p class="panel__label">After · v4</p>
-            <pre><code>const page = await hal.listRuns();
+            <p class="panel__label" id="change-2-label-1">After · v4</p>
+            <section class="code-region" aria-labelledby="change-2 change-2-label-1"><pre><code>const page = await hal.listRuns();
 page.data.map(r =&gt; r.id);
 for await (const r of hal.runs) {}</code></pre>
-          </div>
+          </section></div>
         </div>
       </article>
 
       <article class="change">
-        <h3>3 · Three renames, no other behaviour change</h3>
+        <div class="change-caption"><h3 id="change-3">3 · Three renames, no other behaviour change</h3>
         <p>The codemod does all three; they are listed so you can grep for them yourself.</p>
-        <div class="panel">
-          <p class="panel__label panel__label--diff">− v3 &nbsp;/&nbsp; + v4</p>
+        </div><div class="panel">
+          <p class="panel__label panel__label--diff" id="change-3-label-0">− v3  /  + v4</p>
           <ul class="renames">
             <li><s>err.code === 429</s><b>err.type === "rate_limited"</b></li>
             <li><s>hal.close()</s><b>await hal.dispose()</b></li>
@@ -486,40 +94,38 @@ for await (const r of hal.runs) {}</code></pre>
       </article>
     </section>
 
-    <!-- STEPS ----------------------------------------------------------- -->
     <section class="wrap">
       <h2 class="h2">Do it in three steps</h2>
       <ol class="steps">
         <li>
-          <h3>Upgrade the dependency</h3>
-          <code class="cmd"><span class="p">$</span> npm install halyard@^4.1.0</code>
-        </li>
+          <div class="step-copy"><h3 id="upgrade-1">Upgrade the dependency</h3>
+          </div><section class="code-region" aria-labelledby="upgrade-1"><code class="cmd"><span class="p">$</span> npm install halyard@^4.1.0</code>
+        </section></li>
         <li>
-          <h3>Run the codemod</h3>
+          <div class="step-copy"><h3 id="upgrade-2">Run the codemod</h3>
           <p>
             It rewrites constructors, renames and <code>err.code</code> comparisons. Where it is not
             sure it leaves a <code>TODO(halyard)</code> — <code>grep -rn "TODO(halyard)" src/</code>
             to find them. Pagination is the one only you can decide.
           </p>
-          <code class="cmd"><span class="p">$</span> npx halyard-codemod v3-to-v4 src/
+          </div><section class="code-region" aria-labelledby="upgrade-2"><code class="cmd"><span class="p">$</span> npx halyard-codemod v3-to-v4 src/
 <span class="o">  42 files scanned
   17 files changed
    3 need a human · see TODO(halyard)</span></code>
-        </li>
+        </section></li>
         <li>
-          <h3>Verify before you deploy</h3>
+          <div class="step-copy"><h3 id="upgrade-3">Verify before you deploy</h3>
           <p>
             Strict mode turns every remaining v3 shape into a thrown error instead of a warning, so
             the test run tells you the truth.
           </p>
-          <code class="cmd"><span class="p">$</span> HALYARD_STRICT=1 npm test
+          </div><section class="code-region" aria-labelledby="upgrade-3"><code class="cmd"><span class="p">$</span> HALYARD_STRICT=1 npm test
 <span class="o">  ✓ 184 passing
   ✓ 0 deprecation warnings</span></code>
-        </li>
+        </section></li>
       </ol>
     </section>
 
-    <!-- TROUBLESHOOTING -------------------------------------------------- -->
     <section class="wrap">
       <h2 class="h2">If it goes wrong</h2>
       <p class="lede">
@@ -544,7 +150,6 @@ for await (const r of hal.runs) {}</code></pre>
       </div>
     </section>
 
-    <!-- TIMELINE --------------------------------------------------------- -->
     <section class="wrap">
       <h2 class="h2">The dates that matter</h2>
       <ul class="dates">
@@ -560,5 +165,5 @@ for await (const r of hal.runs) {}</code></pre>
         <a href="https://halyard.dev/docs/v4">halyard.dev/docs/v4</a>
       </p>
     </footer>
-  </body>
+  </main></body>
 </html>
diff --git a/templates/runbook.html b/templates/runbook.html
index 41b93cc..02dd7e3 100644
--- a/templates/runbook.html
+++ b/templates/runbook.html
@@ -1,271 +1,25 @@
 <!doctype html>
 <html lang="en">
   <head>
-    <meta charset="utf-8" />
-    <meta name="viewport" content="width=device-width, initial-scale=1" />
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
     <title>Runbook — rotate the production database credentials</title>
-    <!--
-      TEMPLATE  runbook · category: plans
-      VOICE     humanist sans for prose and headings (the plans family voice); monospace
-                reserved for commands, output, identifiers and step labels.
-      SCHEME    soft sage paper, forest accent, near-black terminal blocks. Amber is the
-                stop colour and always ships with the word STOP or ROLLBACK beside it.
+    <!-- Preview-only redesign. All sample text and command/output bytes are retained from the frozen source. Never execute sample commands. System fonts and native disclosures only. Delete whole empty sections for thin documents. No document-height cap. --><style>:root{--ink:#1b2a23;--muted:#4d5d53;--line:#bfccc0;--page:#f2f6f0;--accent:#20583e;--stop:#7a3c0e;--stop-bg:#fbedd7;--mono:ui-monospace,"SF Mono",Menlo,Consolas,"Liberation Mono",monospace;--sans:system-ui,-apple-system,"Segoe UI",sans-serif}*{box-sizing:border-box}html{-webkit-text-size-adjust:100%}body{margin:0;background:var(--page);color:var(--ink);font:17px/1.6 var(--sans)}main{max-width:1224px;margin:auto;padding:36px 32px 44px;overflow-wrap:anywhere}h1,h2,h3,p{margin:0}h1,h2,h3{line-height:1.25;letter-spacing:-.025em}.head{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:64px;padding-bottom:28px;border-bottom:3px solid var(--ink)}.eyebrow{font:700 12px/1.5 var(--sans);letter-spacing:.15em;color:var(--accent);text-transform:uppercase}h1{font-size:clamp(28px,3.3vw,40px);max-width:25ch;margin-top:10px}.lede{margin-top:18px;color:var(--muted);max-width:66ch}.lede b{color:var(--ink)}.facts{margin:0;align-self:center;display:grid;grid-template-columns:1fr 1fr;gap:20px 26px;padding:22px;border:1px solid var(--line);background:#fff}.facts dt{font-size:13px;color:var(--muted)}.facts dd{font-size:17px;font-weight:650;margin:4px 0 0}.operator-bench{display:grid;grid-template-columns:320px minmax(0,1fr);gap:44px;padding-top:28px;align-items:start}.preconditions{padding:20px;background:#e5ede2}.preconditions h2,.procedure>h2{font-size:24px}.sub{color:var(--muted);margin-top:10px}.pre{padding:0 0 0 18px;margin:20px 0 0;list-style-type:disc}.pre li{padding:16px 0;border-top:1px solid var(--line)}.pre li::marker{color:var(--accent)}.pre b{font-size:16px;font-weight:650}.pre code{display:block;font:14px/1.65 var(--mono);background:#fff;color:var(--ink);border:1px solid #c2cec1;padding:12px;margin-top:10px;white-space:pre;overflow:auto}.stop{display:block;background:var(--stop-bg);border:1px solid #c7a982;padding:18px;margin-top:14px;color:#513615}.stop b{display:block;font-size:16px;margin-bottom:8px;color:var(--stop)}.steps{margin:22px 0 0;padding:0;list-style-position:inside}.steps>li{position:relative;display:block;padding:0 0 24px;margin-top:24px;border-bottom:1px solid var(--line)}.steps h3{font-size:22px;padding-left:46px;min-height:32px;display:flex;align-items:center}.n{position:absolute;left:0;top:0;font:650 26px/1 var(--mono);color:var(--accent)}.body{margin-top:12px}.steps p{color:var(--muted)}.body p b{color:var(--ink)}code{font:.9em/1.6 var(--mono);overflow-wrap:anywhere}pre{margin:16px 0 0;background:#fff;border:1px solid #b9c7bc;white-space:pre;overflow:auto;font:14px/1.65 var(--mono);color:#3b5143;padding:16px 18px;tab-size:2}pre>b{display:block;font-weight:500;color:#153624}pre>b+b{margin-top:8px}pre>span{display:block;color:#506057;border-top:1px solid #b9c7bc;margin-top:14px;padding-top:12px}pre::before{content:attr(data-command-label);display:block;font:650 12px/1.6 var(--sans);text-transform:uppercase;letter-spacing:.06em;color:#506057;margin-bottom:8px}pre>span::before{content:'Output';display:block;font:650 12px/1.6 var(--sans);text-transform:uppercase;letter-spacing:.06em;color:#506057;margin-bottom:8px}.note{padding:18px;background:#fff8ec;border:1px solid #c7a982;margin-top:16px!important;color:#513615}.verify{padding:24px;background:#223d2e;color:#fff;margin:26px 0;border:2px solid #223d2e}.verify h2{font-size:23px}.verify .sub{color:#e0ecdf}.verify pre{border:0}.verify .note{color:#513615}.final-step{margin-top:0}.final-step .n{color:var(--stop)}.final-step>li{border:2px solid #c7a982;padding:22px;background:#fcf7ed}.final-step .n{top:22px;left:20px}.final-step h3{padding-left:44px}.recovery-bench{display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);gap:36px;border-top:2px solid var(--ink);padding:28px 0;margin-top:28px}.recovery h2,.escalation h2{font-size:25px}.rollback{margin-top:18px}.rollback h3{font-size:20px;color:var(--stop)}.rollback p{margin-top:12px;color:var(--muted)}.esc{list-style:disc;padding:0 0 0 20px;margin:20px 0 0}.esc li{padding:16px 0;border-top:1px solid var(--line)}.esc span{display:block;color:var(--muted);margin-top:8px}.foot{border-top:1px solid var(--line);padding-top:22px;color:var(--muted);font-size:16px}:focus-visible{outline:3px solid #9a560d;outline-offset:3px}@media(max-width:1040px){main{padding:28px 24px 40px}.head{gap:28px}.operator-bench{display:block}.preconditions{margin-bottom:28px}.pre{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0 34px;padding-left:20px}.pre li{padding-right:6px}.preconditions .stop{max-width:none}.procedure{max-width:none}.body{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.1fr);gap:22px}.body pre{margin:0}.body .note{grid-column:1/-1;margin-top:0!important}.recovery-bench{gap:28px}}@media(max-width:680px){main{padding:24px 16px 36px}.head{display:block}.facts{margin-top:22px;padding:18px;gap:14px 24px}.operator-bench{padding-top:22px}.preconditions{padding:18px}.pre{display:block}.pre li{padding-right:0}.body{display:block}.body pre{margin-top:16px}.body .note{margin-top:16px!important}.steps h3{font-size:21px;padding-left:42px}.verify{padding:18px}.verify h2{font-size:22px}.final-step>li{padding:18px}.final-step .n{top:18px;left:16px}.final-step h3{padding-left:40px}.recovery-bench{display:block}.escalation{margin-top:28px}.foot{font-size:16px}}
 
-      REWRITING THIS ARTIFACT
-      · Replace the copy, keep the structure. Touch :root only to re-theme.
-      · A command block is <pre><b>$ cmd</b><span>output</span></pre>. Keep lines at or under
-        35 characters INCLUDING the `$ ` prefix — that is what fits a 390px screen at
-        13px mono. Blocks wrap rather than scroll, so nothing is ever hidden.
-      · Delete, do not empty. Nothing to roll back? Remove the section — never leave a
-        heading with nothing under it.
-      · Five steps is the intended density. Past nine, split the runbook in two.
-    -->
-    <style>
-      :root {
-        --ink: #17211a;
-        --muted: #4f5a53;
-        --faint: #66726a;
-        --line: #dce3d8;
-        --rule: #c4cfbe;
-        --page: #f2f5ef;
-        --card: #ffffff;
-        --accent: #1f6b45;
-        --accent-soft: #d6e7dc;
-        --stop: #9a4a10;
-        --stop-soft: #f8e9d8;
-        --term: #141c17;
-        --term-dim: #93a89a;
-        --term-cmd: #a8e0bf;
-        --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
-        --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
-      }
-      * { box-sizing: border-box; }
-      html { -webkit-text-size-adjust: 100%; }
-      body {
-        margin: 0;
-        background: var(--page);
-        color: var(--ink);
-        font-family: var(--sans);
-        font-size: 16px;
-        line-height: 1.5;
-      }
-      h1, h2, h3 { margin: 0; line-height: 1.22; letter-spacing: -0.012em; }
-      p { margin: 0; }
-      ul, ol { margin: 0; padding: 0; list-style: none; }
-      code { font-family: var(--mono); font-size: 0.875em; }
-      .wrap { max-width: 50rem; margin-inline: auto; padding-inline: 1.1rem; }
-      section { padding-block: 0.42rem; border-top: 1px solid var(--line); }
-      .h2 { font-size: 1.0625rem; font-weight: 700; }
-      .sub { margin-top: 0.25rem; font-size: 0.9375rem; color: var(--muted); }
-
-      /* HEAD -------------------------------------------------------------- */
-      .head { padding-block: 1.15rem 0.75rem; }
-      .eyebrow {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.13em;
-        text-transform: uppercase;
-        color: var(--accent);
-      }
-      .head h1 {
-        margin-top: 0.4rem;
-        font-size: clamp(1.6rem, 5.6vw, 2.25rem);
-        font-weight: 700;
-      }
-      .head .lede {
-        margin-top: 0.65rem;
-        max-width: 48ch;
-        font-size: 1rem;
-        color: var(--muted);
-      }
-      .head .lede b { color: var(--ink); }
-      .facts {
-        display: grid;
-        grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
-        gap: 0.5rem 1.25rem;
-        margin-top: 0.9rem;
-        padding-top: 0.72rem;
-        border-top: 1px solid var(--rule);
-      }
-      .facts dt {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.11em;
-        text-transform: uppercase;
-        color: var(--faint);
-      }
-      .facts dd { margin: 0.15rem 0 0; font-size: 0.9375rem; font-weight: 600; }
-
-      /* PRECONDITIONS ----------------------------------------------------- */
-      .pre { display: grid; gap: 0.26rem; margin-top: 0.55rem; }
-      .pre li {
-        display: grid;
-        grid-template-columns: 1.15rem 1fr;
-        gap: 0.1rem 0.55rem;
-        font-size: 0.9375rem;
-      }
-      .pre li::before {
-        content: "";
-        width: 0.95rem;
-        height: 0.95rem;
-        margin-top: 0.32rem;
-        border: 1.5px solid var(--accent);
-        border-radius: 3px;
-      }
-      .pre > li > b { font-weight: 600; }
-      .pre code {
-        display: block;
-        grid-column: 2;
-        margin-top: 0.25rem;
-        padding: 0.28rem 0.5rem;
-        white-space: pre-wrap;
-        overflow-wrap: break-word;
-        background: var(--term);
-        border-radius: 4px;
-        color: var(--term-cmd);
-        font-size: 0.8125rem;
-        line-height: 1.45;
-        text-indent: -2ch;
-        padding-left: calc(0.5rem + 2ch);
-      }
-      .pre code::before { content: "$ "; color: #5f7a68; }
-      .stop {
-        display: grid;
-        gap: 0.2rem;
-        margin-top: 0.9rem;
-        padding: 0.52rem 0.8rem;
-        background: var(--stop-soft);
-        border: 1px solid #dfae7a;
-        border-radius: 5px;
-        font-size: 0.9375rem;
-        color: #5f2f08;
-      }
-      .stop b {
-        font-family: var(--mono);
-        font-size: 0.6875rem;
-        font-weight: 700;
-        letter-spacing: 0.11em;
-        text-transform: uppercase;
-        color: var(--stop);
-      }
-
-      /* STEPS ------------------------------------------------------------- */
-      .steps { margin-top: 0.9rem; }
-      .steps > li {
-        display: grid;
-        grid-template-columns: 1.7rem 1fr;
-        gap: 0.15rem 0.7rem;
-        padding-bottom: 0.5rem;
-      }
-      .steps > li + li { padding-top: 0.5rem; border-top: 1px solid var(--line); }
-      .n {
-        grid-row: span 2;
-        width: 1.7rem;
-        height: 1.7rem;
-        display: grid;
-        place-items: center;
-        border-radius: 50%;
-        background: var(--accent);
-        color: #fff;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        font-weight: 700;
-      }
-      .steps h3 { font-size: 1rem; font-weight: 700; align-self: center; }
-      .steps .body { grid-column: 2; display: grid; gap: 0.4rem; margin-top: 0.3rem;
-        font-size: 0.9375rem; }
-      .steps p { color: var(--muted); }
-      .steps p b { color: var(--ink); }
-
-      pre {
-        margin: 0;
-        padding: 0.6rem 0.75rem;
-        /* wrap rather than clip; continuation lines hang under the command */
-        white-space: pre-wrap;
-        overflow-wrap: break-word;
-        background: var(--term);
-        border-radius: 5px;
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        line-height: 1.4;
-        color: var(--term-dim);
-        tab-size: 2;
-      }
-      pre b,
-      pre span,
-      pre .hl { display: block; padding-left: 2ch; text-indent: -2ch; }
-      pre b { font-weight: 400; color: var(--term-cmd); }
-      pre b::before { content: "$ "; color: #5f7a68; }
-      pre .hl { display: block; color: #e8b98a; }
-
-      .note {
-        padding-left: 0.7rem;
-        border-left: 2px solid var(--rule);
-        font-size: 0.875rem;
-        color: var(--muted);
-      }
-      .note b { color: var(--ink); }
-
-      /* ROLLBACK ---------------------------------------------------------- */
-      .rollback {
-        margin-top: 0.6rem;
-        padding: 0.7rem 0.9rem 0.78rem;
-        background: var(--card);
-        border: 1px solid #dfae7a;
-        border-radius: 6px;
-      }
-      .rollback h3 {
-        font-size: 0.9375rem;
-        font-weight: 700;
-        color: var(--stop);
-      }
-      .rollback p { margin-top: 0.35rem; font-size: 0.9375rem; color: var(--muted); }
-      .rollback pre { margin-top: 0.6rem; }
-
-      /* ESCALATE ---------------------------------------------------------- */
-      .esc { display: grid; gap: 0.34rem; margin-top: 0.55rem; }
-      .esc li {
-        display: grid;
-        gap: 0.1rem;
-        padding-top: 0.5rem;
-        border-top: 1px solid var(--line);
-        font-size: 0.9375rem;
-        color: var(--muted);
-      }
-      .esc b { color: var(--ink); }
-
-      .foot {
-        padding-block: 0.5rem 0.65rem;
-        border-top: 1px solid var(--rule);
-        font-family: var(--mono);
-        font-size: 0.8125rem;
-        color: var(--faint);
-      }
-
-      @media (min-width: 44rem) {
-        .wrap { padding-inline: 2rem; }
-        section { padding-block: 1.5rem; }
-        .head { padding-block: 2.4rem 1.6rem; }
-        .steps > li { grid-template-columns: 2rem 1fr; gap: 0.2rem 1rem; }
-        .steps > li + li { padding-top: 1.15rem; }
-        .n { width: 2rem; height: 2rem; font-size: 0.875rem; }
-        .steps h3 { font-size: 1.0625rem; }
-        pre { padding: 0.8rem 0.9rem; }
-      }
-    </style>
+/* Native named scroll regions; only genuinely overflowing regions enter Chromium tab order. */
+section.code-region{margin:0;padding:0;border:0;min-width:0;overflow:auto}section.code-region>pre,section.code-region>.cmd,section.code-region>code{width:max-content;min-width:100%;max-width:none;overflow:visible;overflow-wrap:normal}
+.pre li>.code-region{margin-top:10px}.pre .code-region>code{margin:0}.body>.code-region{margin-top:16px}.code-region>pre{margin:0}.verify>.code-region,.rollback>.code-region{margin-top:16px}@media(min-width:681px) and (max-width:1040px){.body>.code-region{margin:0}}
+</style>
   </head>
   <body>
-    <!-- HEAD -------------------------------------------------------------- -->
-    <header class="head wrap">
-      <p class="eyebrow">Runbook</p>
+    <main class="document"><header class="head wrap">
+      <div class="identity"><p class="eyebrow">Runbook</p>
       <h1>Rotate the production database credentials</h1>
       <p class="lede">
         Quarterly rotation of <code>api_rw</code> on <code>prod-pg-1</code>. Zero downtime —
         <b>but only if step 4 finishes before you touch step 5.</b>
       </p>
-      <dl class="facts">
+      </div><dl class="facts">
         <div><dt>When</dt><dd>Quarterly</dd></div>
         <div><dt>Takes</dt><dd>~15 min</dd></div>
         <div><dt>Downtime</dt><dd>None</dd></div>
@@ -273,28 +27,27 @@
       </dl>
     </header>
 
-    <!-- PRECONDITIONS ----------------------------------------------------- -->
-    <section class="wrap">
-      <h2 class="h2">Before you start</h2>
+    <div class="operator-bench"><section class="preconditions">
+      <h2 class="h2" id="operation-section-0">Before you start</h2>
       <p class="sub">All four. They take a minute together.</p>
       <ul class="pre">
         <li>
-          <b>You are on the VPN and reach the primary.</b>
-          <code>psql -h prod-pg-1 -c '\conninfo'</code>
-        </li>
+          <b id="precondition-1">You are on the VPN and reach the primary.</b>
+          <section class="code-region" aria-labelledby="precondition-1"><code>psql -h prod-pg-1 -c '\conninfo'</code>
+        </section></li>
         <li>
-          <b>The deploy freeze is off.</b>
-          <code>./bin/freeze status   # =&gt; open</code>
-        </li>
+          <b id="precondition-2">The deploy freeze is off.</b>
+          <section class="code-region" aria-labelledby="precondition-2"><code>./bin/freeze status   # =&gt; open</code>
+        </section></li>
         <li>
-          <b>No migration is running.</b>
-          <code>./bin/migrations running   # =&gt; 0</code>
-        </li>
+          <b id="precondition-3">No migration is running.</b>
+          <section class="code-region" aria-labelledby="precondition-3"><code>./bin/migrations running   # =&gt; 0</code>
+        </section></li>
         <li>
-          <b>You can write to the secret store.</b>
-          <code>vault kv get -field=version \
+          <b id="precondition-4">You can write to the secret store.</b>
+          <section class="code-region" aria-labelledby="precondition-4"><code>vault kv get -field=version \
   secret/prod/db</code>
-        </li>
+        </section></li>
       </ul>
       <p class="stop">
         <b>Stop if any is false</b>
@@ -302,115 +55,111 @@
       </p>
     </section>
 
-    <!-- PROCEDURE --------------------------------------------------------- -->
-    <section class="wrap">
+    <section class="procedure">
       <h2 class="h2">The procedure</h2>
       <p class="sub">
         Two roles exist at once for a few minutes. That overlap is the design — nothing
         restarts, nothing has to be timed.
       </p>
       <ol class="steps">
-        <li>
-          <span class="n">1</span>
-          <h3>Announce it</h3>
+        <li data-step="1">
+          <span class="n" aria-hidden="true">1</span>
+          <h3 id="command-1">Announce it</h3>
           <div class="body">
             <p>A rotation nobody expected costs more than the one you announce.</p>
-            <pre><b>./bin/announce "rotating api_rw,
+            <section class="code-region" aria-labelledby="command-1"><pre data-command-label="Command" data-output-label="Output"><b>./bin/announce "rotating api_rw,
   ~15 min, no downtime expected"</b><span>posted to #eng-oncall</span></pre>
-          </div>
+          </section></div>
         </li>
 
-        <li>
-          <span class="n">2</span>
-          <h3>Generate the new secret</h3>
+        <li data-step="2">
+          <span class="n" aria-hidden="true">2</span>
+          <h3 id="command-2">Generate the new secret</h3>
           <div class="body">
             <p>Written to a staging slot the application does not read yet.</p>
-            <pre><b>export NEW=$(openssl rand -hex 24)</b><b>vault kv put secret/prod/db/next \
+            <section class="code-region" aria-labelledby="command-2"><pre data-command-label="Command" data-output-label="Output"><b>export NEW=$(openssl rand -hex 24)</b><b>vault kv put secret/prod/db/next \
   password="$NEW"</b><span>Key       Value
 version   8</span></pre>
-          </div>
+          </section></div>
         </li>
 
-        <li>
-          <span class="n">3</span>
-          <h3>Create the successor role</h3>
+        <li data-step="3">
+          <span class="n" aria-hidden="true">3</span>
+          <h3 id="command-3">Create the successor role</h3>
           <div class="body">
             <p>
               It inherits from the <code>api_rw</code> group role, so you never re-grant table
               by table.
             </p>
-            <pre><b>psql -h prod-pg-1 \
+            <section class="code-region" aria-labelledby="command-3"><pre data-command-label="Command" data-output-label="Output"><b>psql -h prod-pg-1 \
   -f sql/rotate.sql \
   -v pw="$NEW" -v role=api_rw_q3</b><span>CREATE ROLE
 GRANT</span></pre>
-          </div>
+          </section></div>
         </li>
 
-        <li>
-          <span class="n">4</span>
-          <h3>Point the application at it</h3>
+        <li data-step="4">
+          <span class="n" aria-hidden="true">4</span>
+          <h3 id="command-4">Point the application at it</h3>
           <div class="body">
             <p>
               <b>This is the step that matters.</b> The sidecar reloads on its own, no restart.
               Until it returns, the old role is still serving traffic.
             </p>
-            <pre><b>vault kv put secret/prod/db \
+            <section class="code-region" aria-labelledby="command-4"><pre data-command-label="Command" data-output-label="Output"><b>vault kv put secret/prod/db \
   username=api_rw_q3 \
   password="$NEW"</b><b>./bin/wait-for-rollout \
   --key secret/prod/db \
   --timeout 120s</b><span>all 14 pods on version 9 (47s)</span></pre>
-          </div>
+          </section></div>
         </li>
 
-        <li>
-          <span class="n">5</span>
-          <h3>Retire the old role</h3>
-          <div class="body">
-            <p>Only once the check below has been clean for five minutes.</p>
-            <pre><b>psql -h prod-pg-1 \
-  -c "drop role api_rw_q2"</b><span>DROP ROLE</span></pre>
-            <p class="note">
-              <b>If it says objects depend on it:</b> something still owns tables as that role.
-              Stop and escalate — never <code>REASSIGN OWNED</code> on production alone.
-            </p>
-          </div>
-        </li>
-      </ol>
-    </section>
-
-    <!-- VERIFY ------------------------------------------------------------ -->
-    <section class="wrap">
-      <h2 class="h2">Verify — between steps 4 and 5</h2>
+        </ol>
+    <section class="verify">
+      <h2 class="h2" id="operation-section-1">Verify — between steps 4 and 5</h2>
       <p class="sub">
         Zero on the old role for five straight minutes. That is the bar.
       </p>
-      <pre><b>psql -h prod-pg-1 -c "select usename,
+      <section class="code-region" aria-labelledby="operation-section-1"><pre data-command-label="Command" data-output-label="Output"><b>psql -h prod-pg-1 -c "select usename,
   count(*) from pg_stat_activity
   where datname='app' group by 1"</b><span> api_rw_q3 | 42
  api_rw_q2 |  0</span></pre>
-      <p class="note" style="margin-top: 0.6rem">
+      </section><p class="note" style="margin-top: 0.6rem">
         <b>If the old role still has connections:</b> a pod missed the reload. Restart it.
       </p>
     </section>
 
-    <!-- ROLLBACK ---------------------------------------------------------- -->
-    <section class="wrap">
-      <h2 class="h2">If it goes wrong</h2>
+    <ol class="steps final-step" start="5"><li data-step="5">
+          <span class="n" aria-hidden="true">5</span>
+          <h3 id="command-6">Retire the old role</h3>
+          <div class="body">
+            <p>Only once the verification check has been clean for five minutes.</p>
+            <section class="code-region" aria-labelledby="command-6"><pre data-command-label="Command" data-output-label="Output"><b>psql -h prod-pg-1 \
+  -c "drop role api_rw_q2"</b><span>DROP ROLE</span></pre>
+            </section><p class="note">
+              <b>If it says objects depend on it:</b> something still owns tables as that role.
+              Stop and escalate — never <code>REASSIGN OWNED</code> on production alone.
+            </p>
+          </div>
+        </li>
+      </ol></section>
+
+    </div><div class="recovery-bench"><section class="recovery">
+      <h2 class="h2" id="operation-section-2">If it goes wrong</h2>
       <div class="rollback">
-        <h3>Rollback — safe at any point before step 5</h3>
+        <h3 id="command-7">Rollback — safe at any point before step 5</h3>
         <p>
           The old secret version and the old role both still exist, so this is a sixty-second
           undo — not a deploy.
         </p>
-        <pre><b>vault kv rollback -version=8 \
+        <section class="code-region" aria-labelledby="command-7"><pre data-command-label="Command" data-output-label="Output"><b>vault kv rollback -version=8 \
   secret/prod/db</b><b>./bin/wait-for-rollout \
   --key secret/prod/db</b><span class="hl">all 14 pods back on version 8 (39s)</span></pre>
-      </div>
+      </section></div>
     </section>
 
-    <!-- ESCALATE ---------------------------------------------------------- -->
-    <section class="wrap">
-      <h2 class="h2">When to escalate</h2>
+    <section class="escalation">
+      <h2 class="h2" id="operation-section-3">When to escalate</h2>
       <ul class="esc">
         <li>
           <b>Connections have not drained after 10 minutes.</b>
@@ -423,10 +172,10 @@ GRANT</span></pre>
       </ul>
     </section>
 
-    <footer class="foot wrap">
+    </div><footer class="foot wrap">
       <p>
         rotate-db-credentials · owner data-platform · reviewed 28 Aug 2026 · last run 12 Jun 2026
       </p>
     </footer>
-  </body>
+  </main></body>
 </html>
diff --git a/tests/e2e/template-technical-semantics.spec.ts b/tests/e2e/template-technical-semantics.spec.ts
new file mode 100644
index 0000000..9c84192
--- /dev/null
+++ b/tests/e2e/template-technical-semantics.spec.ts
@@ -0,0 +1,95 @@
+import { expect, test } from '@playwright/test';
+
+const cloudOrigin = process.env.E2E_CLOUD_BASE_URL;
+const sandboxOrigin = process.env.E2E_CLOUD_SANDBOX_ORIGIN;
+
+for (const [slug, regionCount, summaryCount] of [
+  ['changelog', 0, 1],
+  ['migration-guide', 7, 2],
+  ['runbook', 11, 0],
+] as const) {
+  test(`${slug}: native reading order, disclosures and long code survive the real sandbox`, async ({
+    page,
+  }) => {
+    await page.goto(`${cloudOrigin}/templates/${slug}`);
+    await expect(page.locator('iframe')).toHaveAttribute('sandbox', 'allow-scripts');
+    await expect(page.locator('iframe')).toHaveAttribute(
+      'src',
+      `${sandboxOrigin}/templates/${slug}/frame`
+    );
+    const frame = page.frameLocator('iframe');
+    await expect(frame.locator('h1')).toHaveCount(1);
+    await expect(frame.locator('[role], [tabindex]')).toHaveCount(0);
+    await expect(frame.locator('section.code-region')).toHaveCount(regionCount);
+    for (const region of await frame.locator('section.code-region').all()) {
+      const name = await region.evaluate((element) =>
+        (element.getAttribute('aria-labelledby') ?? '')
+          .split(/\s+/)
+          .map((id) => document.getElementById(id)?.textContent?.trim())
+      );
+      expect(name.every((label) => Boolean(label))).toBe(true);
+      // Generated Before/After signs and step counters also contribute to the accessible name.
+      // Require every referenced label's complete text, in order, without discarding those signs.
+      const pattern = name
+        .map((label) => (label ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
+        .join('.*');
+      await expect(region).toHaveAccessibleName(new RegExp(`^.*${pattern}.*$`));
+    }
+    if (slug === 'runbook') {
+      await expect(frame.locator('ol.steps').first().locator(':scope > li')).toHaveCount(4);
+      await expect(frame.locator('ol.final-step')).toHaveAttribute('start', '5');
+      expect(
+        await frame.locator('.verify').evaluate((element) => ({
+          previous: element.previousElementSibling?.tagName,
+          previousItems: element.previousElementSibling?.children.length,
+          next: element.nextElementSibling?.getAttribute('start'),
+        }))
+      ).toEqual({ previous: 'OL', previousItems: 4, next: '5' });
+      await expect(frame.locator('[data-step="5"] p').first()).toHaveText(
+        'Only once the verification check has been clean for five minutes.'
+      );
+      expect(await frame.locator('.procedure').ariaSnapshot()).toContain('listitem');
+    }
+    await expect(frame.locator('summary')).toHaveCount(summaryCount);
+    for (const summary of await frame.locator('summary').all()) {
+      await summary.focus();
+      await page.keyboard.press('Enter');
+      await expect(summary.locator('xpath=..')).toHaveAttribute('open', '');
+    }
+    if (regionCount) {
+      const region = frame.locator('section.code-region').first();
+      // Inert DOM-only stress: lengthen a sample line, never evaluate or execute its content.
+      await region.evaluate((element) => {
+        const code = element.querySelector('code, pre');
+        if (!code) throw new Error('Missing code sample');
+        code.append(document.createTextNode(' LONG_SAMPLE_SEGMENT'.repeat(40)));
+      });
+      await frame.locator('body').click({ position: { x: 5, y: 5 } });
+      await page.keyboard.press('Control+Home');
+      let reached = false;
+      for (let attempt = 0; attempt < 24; attempt += 1) {
+        await page.keyboard.press('Tab');
+        if (await region.evaluate((element) => document.activeElement === element)) {
+          reached = true;
+          break;
+        }
+      }
+      expect(reached).toBe(true);
+      expect(await region.evaluate((element) => element.matches(':focus-visible'))).toBe(true);
+      await page.keyboard.press('ArrowRight');
+      await expect.poll(() => region.evaluate((element) => element.scrollLeft)).toBeGreaterThan(0);
+      expect(
+        await region.evaluate((element) =>
+          [...element.querySelectorAll('pre, code')].every(
+            (code) => Number.parseFloat(getComputedStyle(code).fontSize) >= 14
+          )
+        )
+      ).toBe(true);
+    }
+    expect(
+      await frame.locator('html').evaluate((element) => element.scrollWidth <= innerWidth + 1)
+    ).toBe(true);
+    await frame.locator('footer').scrollIntoViewIfNeeded();
+    await expect(frame.locator('footer')).toBeInViewport();
+  });
+}
diff --git a/tests/unit/template-technical-semantics.test.ts b/tests/unit/template-technical-semantics.test.ts
new file mode 100644
index 0000000..e6be07a
--- /dev/null
+++ b/tests/unit/template-technical-semantics.test.ts
@@ -0,0 +1,49 @@
+import { createHash } from 'node:crypto';
+import { readFileSync } from 'node:fs';
+import { describe, expect, it } from 'vitest';
+
+const source = (slug: string) =>
+  readFileSync(new URL(`../../templates/${slug}.html`, import.meta.url), 'utf8');
+
+const expectRunbookOrder = (html: string) => {
+  expect(html).toContain('Only once the verification check has been clean for five minutes.');
+  expect(html).not.toContain('Only once the check below has been clean for five minutes.');
+  expect(html).toMatch(
+    /<ol class="steps">[\s\S]*?data-step="4"[\s\S]*?<\/ol>\s*<section class="verify">[\s\S]*?<ol class="steps final-step" start="5"><li data-step="5">/
+  );
+  expect(html.match(/data-step="[1-5]"/g)).toEqual(
+    [1, 2, 3, 4, 5].map((step) => `data-step="${step}"`)
+  );
+};
+
+describe('technical template native structure and inert sample content', () => {
+  it('keeps the five-minute check explicitly between steps four and five', () => {
+    const html = source('runbook');
+    expectRunbookOrder(html);
+    // Red controls exercise the same assertions without touching a file or running a command.
+    expect(() =>
+      expectRunbookOrder(html.replace('the verification check', 'the check below'))
+    ).toThrow();
+    expect(() => expectRunbookOrder(html.replace('start="5"', 'start="1"'))).toThrow();
+  });
+
+  for (const [slug, regions, preCount, digest] of [
+    ['changelog', 0, 0, '4f53cda18c2baa0c0354bb5f9a3ecbe5ed12ab4d8e11ba873c2f11161202b945'],
+    ['migration-guide', 7, 4, '9cc5c6c6da16ccdf1536321d467aa984620f23349fce4d254b864cb818d7126a'],
+    ['runbook', 11, 7, '39e4d3307250b0bcffaa58064f7c7382215a24c527c9bdd691411b8a018dd7ec'],
+  ] as const) {
+    it(`${slug} preserves named native regions and complete sample pre descendants`, () => {
+      const html = source(slug);
+      expect(html).not.toMatch(/<[a-z][^>]*\s(?:role|tabindex)=|<script\b|<form\b|biome-ignore/);
+      expect(
+        html.match(/<section class="code-region" aria-labelledby="[^"]+">/g) ?? []
+      ).toHaveLength(regions);
+      const blocks = [...html.matchAll(/<pre\b[^>]*>([\s\S]*?)<\/pre>/g)].map(
+        (match) => match[1] ?? ''
+      );
+      expect(blocks).toHaveLength(preCount);
+      // Order can follow the approved reading flow; descendants and sample code bytes cannot drift.
+      expect(createHash('sha256').update(JSON.stringify(blocks.sort())).digest('hex')).toBe(digest);
+    });
+  }
+});
