UAT #2338 caught the Text lens rendering extracted text edge-to-edge across the window — 300+ characters on a line. Nobody reads that; the eye loses its place on the return sweep. Typography has one law here, the same law the Original lens's fit-width cap already obeys: a line of prose runs 60–75 characters. The Insights lens was already right (a centered ~720px column). The Text lens was not. This Story makes them the same column — literally the same number.
One measure, one token. The 720px cap becomes
--reading-measure, exposed to Tailwind as the reading
container size, and both prose surfaces compose max-w-reading. There
is no second magic number to drift from the first.
| Token | Value | Caps |
|---|---|---|
--reading-column-max (Story-2309, unchanged) |
1000px | A rendered page of paper in the Original lens. |
--reading-measure (new) |
720px | A column of text — the Insights dossier, the Text lens. |
Two caps, deliberately. A page of paper is a picture of a line, not the line itself — the ink inside an A4 render is already broken to its own measure, so the sheet may be wider. Live text is not: its cap is the measure. Collapsing them into one number would either shrink the paper or unbind the prose.
/* design/v1/colors_and_type.css — canonical */ --reading-measure: 720px; /* globals.css @theme inline — the utility */ --container-reading: var(--reading-measure); /* → max-w-reading */
document-machine-copy.tsx — the article becomes
mx-auto w-full max-w-reading: a centered column inside whatever
space the lens gives it.
document-insights-dossier.tsx — w-[720px] →
w-full max-w-reading. Same rendered width, now derived from the
token instead of restating it. This is the "one measure, not two magic numbers"
acceptance criterion made literal.
text-[14px] leading-[1.75],
paragraphs and lists get vertical air, block gap 3 → 4, and a
heading block sits a rung above the body it opens
(text-[15px] font-semibold text-ink-strong, with space above).
Nothing to special-case, by construction. The column centers in the flex child it
lives in, so it re-centers in whatever width remains when the sections outline is
open, and again when focus mode hands the whole frame to the viewer. Section
boundaries stay inside the column, still carrying
data-section-id / data-page / data-block —
so the outline's jump targets and the note anchors (SS-06) are untouched.
Storybook: new MachineCopy → LongProse story — a
1280px frame (the meta decorator was widened on purpose: a measure story only
proves anything when the window is wider than the measure) filled with long
pleading paragraphs. The text holds the centered column; it does not stretch.
The utility is real, not just authored (LI-7 / L1 — an
arbitrary class that never generates a CSS rule silently no-ops). From the built
CSS: .max-w-reading{max-width:var(--reading-measure)} and
--reading-measure:720px.
Checks: pnpm lint 0 errors (3 pre-existing
warnings) · pnpm test 1203 passed / 186 files ·
pnpm build green · pnpm storybook:check green.
Storybook proves the measure and the rhythm on fixture prose. The live proof — a real extracted document, read at full window width with the rail open and in focus mode — belongs to the UAT pass on staging; this Story does not claim it.
The measure is fixed at 720px, not fluid per character count. If a future surface needs a different measure, it changes the token or adds a named sibling — it does not re-inline a number.
What died: the last hard-coded w-[720px] in the
viewer, and the full-window line length in the Text lens.
5 file(s), +59 / -7 lines
| Path | Role | + | - |
|---|---|---|---|
| design/v1/colors_and_type.css | Styles | +6 | -0 |
| src/app/globals.css | App route | +8 | -0 |
| src/components/documents/viewer/document-insights-dossier.tsx | Component | +1 | -1 |
| src/components/documents/viewer/document-machine-copy.stories.tsx | Component | +37 | -3 |
| src/components/documents/viewer/document-machine-copy.tsx | Component | +7 | -3 |
Instrumentation-exempt per pramaan-docs ADR-124: presentational layout/CSS only — no new handler, route, job, or product-state transition.