From e2a16aeb26462ee55a0ce38ffa6dad52668bcff0 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 29 Aug 2026 18:14:36 -0700 Subject: [PATCH 1/2] =?UTF-8?q?docs(plans):=20the=20docs=20polish=20arc=20?= =?UTF-8?q?(project=203)=20=E2=80=94=20three=20PRs=20against=20the=20findi?= =?UTF-8?q?ngs=20audit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plans/2026-08-30-docs-polish-arc.md | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-30-docs-polish-arc.md diff --git a/docs/superpowers/plans/2026-08-30-docs-polish-arc.md b/docs/superpowers/plans/2026-08-30-docs-polish-arc.md new file mode 100644 index 000000000..c7f273350 --- /dev/null +++ b/docs/superpowers/plans/2026-08-30-docs-polish-arc.md @@ -0,0 +1,56 @@ +# Docs Polish Arc Implementation Plan (Project 3) + +> **For agentic workers:** This plan is executed by the controller directly with +> live browser measurement — the implement-measure-adjust loop each fix needs. +> Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Fix every defect in `docs/superpowers/audits/2026-08-29-docs-visual-review-findings.md` — the review that started this whole arc. Unlike Projects 1–2, these PRs **deliberately change what readers see**; each change is bounded by a measured before-state from the audit and a measured after-assertion here. + +**Prerequisites (all merged):** #845 (tokens), #848–#858 (substrate). Every fix below is now a stylesheet edit. + +**Verification stance:** the migration's "signature-identical to prod" gate inverts — for each defect the audit's measured *broken* value must flip to the specified *fixed* value, and surfaces a PR does not target must stay signature-identical to prod at 1280 (blast-radius check). + +--- + +## PR 1 — structural: overflow truth, sticky rails, one nav height + +Findings §1, §2, §5, §6(partial). + +- [ ] **`--nav-h`** custom property on `:root` in `chrome.css`: `58px`, `81px` at `min-width: 768px` (measured nav heights). Consumers: docs shell `padding-top` (was hardcoded 80px → 22px mobile dead space), sidebar/TOC sticky `top` + `max-height`, mobile overlay `top` (`calc(var(--nav-h) - 1px)`, was 57px), and `html { scroll-padding-top: calc(var(--nav-h) + 16px) }` — which fixes every deep link landing under the nav (§5) without touching a heading. +- [ ] **Revive sticky** (§1): delete `overflow-x: hidden` from `body` (it makes `` a scroll container; sticky descendants pin against it and never stick) and the `overflow-x-hidden` utility from the docs shell div. `html`'s guard **stays** — verified it propagates to the viewport without breaking sticky. Sidebar additionally needs `align-self: flex-start` + `max-height: calc(100vh - var(--nav-h))` so its own `overflow-y: auto` finally engages (it was 10,030px tall). +- [ ] **Fix the real overflow sources the guards were masking** (§2), then prove no page scrolls horizontally at 320/375/768 *with the guard gone*: + - `` body: `min-width: 0` on `.mdx-step-body` (flex item refused to shrink; +512px on quickstarts at 320). + - landing `.why-row__body` / hero demo box / whitepaper card: responsive max-widths. + - blog index cards (`width: 300px` fixed) → fluid with a 300px cap. + - about page GitHub URL → `overflow-wrap: anywhere`. + - anything else the 320px probe finds — the probe is the authority, not this list. +- [ ] **Assertions:** at `scrollY=3000` on a docs page, sidebar and TOC `getBoundingClientRect().top === nav-h`; hash-jump to a mid-page heading lands its top ≥ nav bottom; docs shell top padding equals actual nav height at 375 and 1280; `documentElement.scrollWidth === innerWidth` on all 15 probe pages at 320 AND 375; non-targeted pages signature-identical to prod at 1280. + +## PR 2 — details: breadcrumb, tables, rails, mdx + +Findings §3, §4, §6(rail), §9. + +- [ ] **Breadcrumb** (§3): typography moves to the `
    ` (13px), `align-items: center`, separators become `li + li::before` (or equivalent) so they can never diverge from link size again; wrapped-row gap. Assertion: separator and link share font-size and vertical center on every crumb. +- [ ] **Tables** (§4): `min-width` on tables inside `.docs-table-scroll` so the scroller scrolls (at 375 the props table rendered `agent` as three lines in a 49px column, rows 227px tall); edge-fade affordance; `tabindex="0"` + `role="region"` + aria-label on the wrapper (keyboard reachability); same wrapper treatment for `ApiDocRenderer`/`ApiRefTable` tables. Inline `code` chips get `word-break: normal` so `@threadplane/langgraph` stops splitting into two pills. +- [ ] **One horizontal rail** (§6): breadcrumb/header wrapper, article, API block, and prev/next all share one padding scale (the 8px misalignment at <640px). +- [ ] **`` connector**: `:last-child` kills the dangling line below the final step. +- [ ] **Dead CSS** (§9): delete the `.shiki` and `[data-rehype-pretty-code-title]` rules (zero matching elements site-wide; documented in the audit). +- [ ] **Assertions:** table wrapper `scrollWidth > clientWidth` at 375 with single-line `agent` cell; breadcrumb metrics; rail lefts equal at 375; docs pages signature vs prod shows deltas ONLY on the targeted elements. + +## PR 3 — a11y + interaction + +Findings §6(anchors/targets), §7. + +- [ ] Shared `:focus-visible` ring (`var(--shadow-focus)`) on interactive docs chrome. +- [ ] **DocsSearch**: `role="dialog"` + `aria-modal`, focus trap + restore, `listbox`/`option` semantics, selected item scrolled into view, and a **mobile entry point** (the ⌘K trigger lives in the desktop-only sidebar). +- [ ] **Tabs/TabGroup**: `role="tablist"`/`tab`/`tabpanel`, arrow keys, horizontal scroll on narrow screens. +- [ ] **PageActions**: menu keyboard nav + item hover/focus styles. +- [ ] Heading anchors get a mobile-visible affordance (currently `display: none` below 768px — no way to deep-link from a phone). +- [ ] Touch targets ≥44px (PageActions 32px, code-copy 28px) via hit-area padding, not visual growth. +- [ ] `prefers-reduced-motion` guards `scroll-behavior: smooth`. +- [ ] **Assertions:** axe-style manual checks per control; targets measured ≥44; keyboard walkthrough of search + tabs + menu. + +## Deferred, explicitly + +- **Font unification onto next/font** (findings §10): a real sitewide visual change; needs its own decision. +- **Toast width on small screens**; **`AnnouncementToast` focus management** — candidates for a later pass. From db12b6af8d1a73b0973e684867ebbb35a3143c1b Mon Sep 17 00:00:00 2001 From: Brian Love Date: Sat, 29 Aug 2026 18:28:46 -0700 Subject: [PATCH 2/2] fix(website): revive sticky rails, clear anchors of the nav, and make 320px honest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The structural third of the docs polish arc (findings §1, §2, §5, §6-partial). - body gets overflow-x: CLIP, not hidden. `hidden` made a scroll container, so every sticky rail pinned against a thing that never scrolls - the docs sidebar and TOC have been dead since they were written. `clip` clips identically, creates no scroll container (sticky works), and keeps the mobile layout viewport pinned (without any body guard, min-content overflow forced phones to zoom out: measured vw 421 on a 320 device). - One nav height: --nav-h (58px / 81px at md) drives the docs shell padding (was a hardcoded 80px = 22px dead space on phones), both sticky rails, the mobile overlay offset, and html's scroll-padding-top - which stops every deep link landing under the fixed nav. - The sidebar gets align-self:flex-start + max-height so its own scrollbar finally engages (it was stretched to the article's 10,030px). - The overflow the old guards were hiding is fixed at the source: Steps' flex item, why-row's flex item and wp-cover-wrap's grid item (min-width:0), the wp cover's height-derived aspect-ratio width, five auto-fit grids with unclamped px minimums, five stacked 1fr breakpoints with auto minimums, and the about page's unbreakable repo URL. Measured: rails pin at var(--nav-h) at scrollY 3000; #anchor jumps land 16px clear of the nav; zero elements exceed a true pinned 320px viewport on /, /pilot-to-prod, /solutions, /about, /blog, /pricing, and the docs pages; and / and /pricing are hash-identical to production at 1280 (desktop untouched). Co-Authored-By: Claude Opus 5 --- .../docs/[library]/[section]/[slug]/page.tsx | 2 +- apps/website/src/app/global.css | 12 ++++- apps/website/src/styles/chrome.css | 17 +++++- apps/website/src/styles/docs.css | 16 ++++-- apps/website/src/styles/landing.css | 52 +++++++++++++++---- apps/website/src/styles/pages.css | 7 ++- apps/website/src/styles/ui.css | 7 +++ 7 files changed, 94 insertions(+), 19 deletions(-) diff --git a/apps/website/src/app/docs/[library]/[section]/[slug]/page.tsx b/apps/website/src/app/docs/[library]/[section]/[slug]/page.tsx index 02271cc93..c4ba04c91 100644 --- a/apps/website/src/app/docs/[library]/[section]/[slug]/page.tsx +++ b/apps/website/src/app/docs/[library]/[section]/[slug]/page.tsx @@ -85,7 +85,7 @@ export default async function DocsPage({ params }: DocsRouteProps) { ]); return ( -
    +
    diff --git a/apps/website/src/app/global.css b/apps/website/src/app/global.css index 455f1f8fa..208b05d53 100644 --- a/apps/website/src/app/global.css +++ b/apps/website/src/app/global.css @@ -21,6 +21,8 @@ html { overflow-x: hidden; + /* Anchor jumps clear the fixed nav (--nav-h defined in styles/chrome.css). */ + scroll-padding-top: calc(var(--nav-h) + 16px); } body { @@ -29,7 +31,15 @@ body { font-family: var(--font-inter); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - overflow-x: hidden; + /* overflow-x: CLIP, never hidden. `hidden` makes a scroll container + * and every position:sticky descendant pins against it — which never + * scrolls — so the docs rails were dead site-wide. `clip` clips identically + * but creates no scroll container, so sticky works AND the mobile layout + * viewport stays pinned to the device width (without any body guard, + * min-content overflow forces phones to zoom out — measured vw 421 on a + * 320 device). Real overflow sources are still fixed at the source; this + * is the belt, not the fix. */ + overflow-x: clip; } /* Code blocks scroll internally; never expand the layout viewport. */ diff --git a/apps/website/src/styles/chrome.css b/apps/website/src/styles/chrome.css index b92cbe396..afaa16276 100644 --- a/apps/website/src/styles/chrome.css +++ b/apps/website/src/styles/chrome.css @@ -14,6 +14,21 @@ * 'var(--font-garamond,"EB Garamond",Georgia,serif)') are intentional. */ +/* One nav height. The fixed nav measures 58px (px-6 py-4) and 81px at the md + * breakpoint (md:px-8 md:py-5, 1px border included). Everything that offsets + * against the nav — the docs shell's top padding, both sticky rails, the + * mobile overlay, and html's scroll-padding for anchor jumps — reads this one + * variable instead of hardcoding its own guess (the old hardcoded 80px left + * 22px of dead space on phones; anchors landed 81px under the nav). */ +:root { + --nav-h: 58px; +} +@media (min-width: 768px) { + :root { + --nav-h: 81px; + } +} + /* Footer */ .footer-root { background: var(--color-surface); @@ -132,7 +147,7 @@ margin: -12px; } .nav-mobile-overlay { - top: 57px; + top: calc(var(--nav-h) - 1px); z-index: 9999; background: var(--color-surface); border-top: 1px solid var(--color-border); diff --git a/apps/website/src/styles/docs.css b/apps/website/src/styles/docs.css index 413b6868a..4bad69ac6 100644 --- a/apps/website/src/styles/docs.css +++ b/apps/website/src/styles/docs.css @@ -457,6 +457,10 @@ } .mdx-step-content { flex: 1; + /* flex:1 leaves min-width:auto; the item refuses to shrink below its + * content's min-content width and blew the quickstart pages 512px past a + * 320px viewport (findings §2). */ + min-width: 0; padding-bottom: 8px; } .mdx-step-title { @@ -701,9 +705,13 @@ .docs-sidebar { border-right: 1px solid var(--color-border); background: var(--color-surface); - min-height: calc(100vh - 5rem); position: sticky; - top: 5rem; + top: var(--nav-h); + /* Without align-self the flex row stretches the aside to the article's full + * height (measured 10,030px), so its overflow-y:auto never engaged. */ + align-self: flex-start; + min-height: calc(100vh - var(--nav-h)); + max-height: calc(100vh - var(--nav-h)); } .docs-sidebar-lib-trigger { background: var(--color-surface); @@ -890,9 +898,9 @@ /* DocsTOC */ .docs-toc { position: sticky; - top: 5rem; + top: var(--nav-h); align-self: flex-start; - max-height: calc(100vh - 6rem); + max-height: calc(100vh - var(--nav-h) - 1rem); overflow-y: auto; } .docs-toc-label { diff --git a/apps/website/src/styles/landing.css b/apps/website/src/styles/landing.css index ac3360c28..65c007af5 100644 --- a/apps/website/src/styles/landing.css +++ b/apps/website/src/styles/landing.css @@ -90,7 +90,9 @@ @keyframes blink { to { visibility: hidden; } } @media (max-width: 900px) { .hero-grid { - grid-template-columns: 1fr !important; + /* minmax(0,…): bare 1fr keeps an auto minimum, so one wide child pushed + * the stacked column past the viewport (findings §2). */ + grid-template-columns: minmax(0, 1fr) !important; gap: 40px !important; } } @@ -190,7 +192,7 @@ } .feature-block-card-row { display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr)); gap: 8px; margin-bottom: 24px; } @@ -219,7 +221,9 @@ } @media (max-width: 900px) { .feature-block-grid { - grid-template-columns: 1fr !important; + /* minmax(0,…): bare 1fr keeps an auto minimum, so one wide child pushed + * the stacked column past the viewport (findings §2). */ + grid-template-columns: minmax(0, 1fr) !important; gap: 32px !important; } .feature-block-grid > div { @@ -277,6 +281,9 @@ } .why-row__body { flex: 1; + /* min-width:auto refused to shrink past the nowrap primitive chip and + * pushed the row 52px past a 320px viewport (findings §2). */ + min-width: 0; display: flex; gap: 16px; align-items: baseline; @@ -421,8 +428,27 @@ .wp-cover-wrap { display: flex; justify-content: center; + /* Grid items have min-width:auto too — without this the wrap sat at its + * content's 392px inside a 280px track. */ + min-width: 0; +} +/* The frame is shrink-to-fit, so the cover's `100%` was indefinite and its + * aspect-ratio kept deriving a fixed ~392px width from content height. A + * definite width (capped by the frame's own inline maxWidth) lets the cover + * track the viewport. Scoped to the stacked breakpoint: at desktop the grid + * track is wider than the card, and an unconditional 100% would grow it. */ +@media (max-width: 640px) { + .wp-cover-wrap > [data-ui="browser-frame"] { + width: 100%; + } } .wp-cover { + /* aspect-ratio alone derives width from the content's height, which makes + * the cover a fixed ~392px regardless of container — the audit's 392px + * card forcing phones to clip. Pin the width; aspect-ratio then drives + * height, and content taller than the ratio still stretches it (min-height + * auto). */ + width: min(100%, 392px); aspect-ratio: 8.5 / 11; background: linear-gradient(135deg, #fafbfc 0%, #eaf3ff 100%); padding: 48px 36px; @@ -462,7 +488,9 @@ } @media (max-width: 900px) { .wp-grid { - grid-template-columns: 1fr !important; + /* minmax(0,…): bare 1fr keeps an auto minimum, so one wide child pushed + * the stacked column past the viewport (findings §2). */ + grid-template-columns: minmax(0, 1fr) !important; gap: 40px !important; } } @@ -569,7 +597,9 @@ } @media (max-width: 900px) { .pilot-block-grid { - grid-template-columns: 1fr !important; + /* minmax(0,…): bare 1fr keeps an auto minimum, so one wide child pushed + * the stacked column past the viewport (findings §2). */ + grid-template-columns: minmax(0, 1fr) !important; gap: 40px !important; } } @@ -662,7 +692,7 @@ } .ecosystem-row-tiles { display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 10px; } [data-ui="ecosystem-tile"]:hover { @@ -672,7 +702,9 @@ } @media (max-width: 760px) { .ecosystem-row { - grid-template-columns: 1fr !important; + /* minmax(0,…): bare 1fr keeps an auto minimum, so one wide child pushed + * the stacked column past the viewport (findings §2). */ + grid-template-columns: minmax(0, 1fr) !important; gap: 8px !important; } .ecosystem-row > div:first-child { @@ -712,7 +744,7 @@ } .promises-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(200px, 100%), 1fr)); gap: 16px; max-width: 1100px; margin: 0 auto; @@ -752,7 +784,7 @@ } .recent-articles-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 16px; } .recent-articles-footer { @@ -829,7 +861,7 @@ /* BackendsGrid — components/landing/ag-ui/BackendsGrid.tsx */ .backends-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr)); gap: 10px; padding: 16px; background: var(--color-surface-tinted); diff --git a/apps/website/src/styles/pages.css b/apps/website/src/styles/pages.css index d988f24c7..c61d1053d 100644 --- a/apps/website/src/styles/pages.css +++ b/apps/website/src/styles/pages.css @@ -364,7 +364,7 @@ * not this migration. */ .docs-shell-page { background: var(--color-canvas); - padding-top: 80px; + padding-top: var(--nav-h); } .docs-shell-body { background: var(--color-surface); @@ -410,6 +410,9 @@ margin-top: 32px; } .about-link { + /* The full repo URL is one unbreakable token; without this it pushed the + * page 9px past a 320px viewport (findings §2). */ + overflow-wrap: anywhere; color: var(--color-accent); } @@ -1139,7 +1142,7 @@ } .blog-index-grid { display: grid; - grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr)); gap: 16px; } diff --git a/apps/website/src/styles/ui.css b/apps/website/src/styles/ui.css index 334b08d9d..b12630fd8 100644 --- a/apps/website/src/styles/ui.css +++ b/apps/website/src/styles/ui.css @@ -314,6 +314,13 @@ } [data-ui="browser-frame-url"] { flex: 1; + /* An unbreakable mono URL is the frame's min-content; without these three + * it propagated a ~390px floor up to the page and forced phones to zoom out + * (findings §2). Real browser chrome ellipsizes — so do we. */ + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; text-align: center; font-family: "JetBrains Mono", monospace; font-size: 11px;