PR: #588: feat(app): text lens reads in a centered reading column [Story-2343] by @ankitmaheshwari-com
View PR →

Story-2343: the Text lens reads in a reading column

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.

The idea in one line

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.

The token — and why it is NOT the existing one

TokenValueCaps
--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 */

The diff

Sections rail and focus mode

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.

Evidence

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.

Honest seams (verification doctrine)

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.

Files changed

5 file(s), +59 / -7 lines

Path Role + -
design/v1/colors_and_type.cssStyles+6-0
src/app/globals.cssApp route+8-0
src/components/documents/viewer/document-insights-dossier.tsxComponent+1-1
src/components/documents/viewer/document-machine-copy.stories.tsxComponent+37-3
src/components/documents/viewer/document-machine-copy.tsxComponent+7-3

Observability

Instrumentation-exempt per pramaan-docs ADR-124: presentational layout/CSS only — no new handler, route, job, or product-state transition.