From 2a8554af276071158b0994e8af75d69bb36f4adb Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 17 Sep 2026 13:43:26 +0300 Subject: [PATCH] =?UTF-8?q?Typography=20round=204=20(owner):=20the=20snap?= =?UTF-8?q?=20to=20the=20ladder=20=E2=80=94=20prose=2016.5=E2=86=9216,=20x?= =?UTF-8?q?xs=20rung=20merged=20into=20xs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The system: the ladder is the truth, everything snaps to it. - Prose snaps to the ladder's 16px rung (--lh-prose-size 16.5→16; the half-pixel was a reskin-era hand-tune with no visible payoff and a system cost). lh-prose, lh-input, and the session titles all read the one variable — one reading size, now a ladder citizen. @theme inline inlines literals without emitting runtime vars, so the variable can't var()-link to --text-lg: documented as the one deliberate duplication (change one, change both). - The 10px rung merges into 11px: one pixel apart was a distinction with no visible difference, and 10px sits at the mono legibility floor — quiet labels (Sessions, Background agents, dock hints, model-picker footers) get their quiet from color + tracking, not a notch of size. All 13 text-xxs uses → text-xs; --text-xxs token removed. - Derived constant follows: TEXT_CAP_SCALE 16.5/12.5 → 16/12.5 (1.32 → 1.28; test pin 232.32px → 225.28px) — the plan-graph text cap now tracks the snapped prose size. Live: title 16px, Sessions/Background-agents labels 11px, composer 16px with the 20px lh-input line box. Gates: typecheck, lint, 469/469. Lane note: components/graph-context-marker.tsx (graph-stream file) had a mechanical text-xxs → text-xs swap — required: the token removal otherwise leaves a dead class in their file. --- app/globals.css | 27 ++++++++++++++++++++------- components/activity-dock.tsx | 2 +- components/answered-card.tsx | 4 ++-- components/graph-context-marker.tsx | 2 +- components/model-picker.tsx | 8 ++++---- components/plan-block.tsx | 7 ++++--- components/question-dock.tsx | 8 ++++---- components/sidebar.tsx | 2 +- tests/render-plan-block.test.tsx | 5 +++-- 9 files changed, 40 insertions(+), 25 deletions(-) diff --git a/app/globals.css b/app/globals.css index abffdab..7ad08e1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -40,7 +40,12 @@ /* Emphasis coloring in teaching prose — ready for the settings UI. */ --lh-ink-strong: #c96b4a; --lh-ink-italic: #6fa9d6; - --lh-prose-size: 16.5px; + /* THE READING SIZE = the ladder's 16px rung (--text-lg). Can't be + var()-linked: @theme inline inlines literals into utilities without + emitting runtime vars — so this is the one documented duplication: + change one, change both. (owner, typography round 4: snapped from + the reskin's hand-tuned 16.5 to the ladder's 16.) */ + --lh-prose-size: 16px; --lh-prose-leading: 1.5; } @@ -67,7 +72,12 @@ --ring: #8d8d8d; --lh-ink-strong: #c96b4a; --lh-ink-italic: #6fa9d6; - --lh-prose-size: 16.5px; + /* THE READING SIZE = the ladder's 16px rung (--text-lg). Can't be + var()-linked: @theme inline inlines literals into utilities without + emitting runtime vars — so this is the one documented duplication: + change one, change both. (owner, typography round 4: snapped from + the reskin's hand-tuned 16.5 to the ladder's 16.) */ + --lh-prose-size: 16px; --lh-prose-leading: 1.5; } @@ -93,9 +103,12 @@ --font-mono: var(--font-jetbrains), ui-monospace, SFMono-Regular, monospace; --font-sans: var(--font-sans3), ui-sans-serif, system-ui, sans-serif; /* The modular type ladder — each size carries its own line-height. - Component sizes must pull from this ladder. */ - --text-xxs: 10px; - --text-xxs--line-height: 1.4; + Component sizes must pull from this ladder. (owner, typography + round 4: the 10px rung is merged into 11 — one pixel apart was a + distinction with no visible difference, and 10px sits at the mono + legibility floor; quiet labels get their quiet from color + + tracking, not a notch of size. Reading prose = the 16px rung via + --lh-prose-size.) */ --text-xs: 11px; --text-xs--line-height: 1.4; --text-sm: 12.5px; @@ -166,9 +179,9 @@ body { /* lh-input (owner, typography round 2): lh-prose's 1.5 READING leading makes typing carets tower over the glyphs — the caret fills the line - box (24.75px at 16.5px text, ~2.1× the glyph height). Inputs are not + box (24px at 16px text, ~2× the glyph height). Inputs are not paragraphs: a tighter 1.25 leading brings the caret to the font-metric - floor (~21px, as close to the glyphs as Chromium will draw it) while + floor (~20px, as close to the glyphs as Chromium will draw it) while wrapped lines in growing textareas still read fine. Rendered prose keeps 1.5 — the two share family and size, they differ only in leading. */ .lh-input { diff --git a/components/activity-dock.tsx b/components/activity-dock.tsx index 46b8237..b89df03 100644 --- a/components/activity-dock.tsx +++ b/components/activity-dock.tsx @@ -274,7 +274,7 @@ export function ActivitySection({ feed: feedProp }: { feed?: ActivityFeed }) { // alone (the cost span is population, not decoration). return (
-
+
Background agents {blobs.length > 0 && ( diff --git a/components/answered-card.tsx b/components/answered-card.tsx index 112097b..5bc7dd5 100644 --- a/components/answered-card.tsx +++ b/components/answered-card.tsx @@ -72,7 +72,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) { )} > {/* Owner round 8: a touch smaller than the text (size-4→size-3.5, - ≈14px vs the 16.5px row) while centered. */} + ≈14px vs the 16px row) while centered. */} {(key || sel) && } )} @@ -101,7 +101,7 @@ export function AnsweredCard({ answer }: { answer: QuestionAnswerData }) { {answer.note && (
- + Your thinking {answer.note} diff --git a/components/graph-context-marker.tsx b/components/graph-context-marker.tsx index 7d3b833..5213be2 100644 --- a/components/graph-context-marker.tsx +++ b/components/graph-context-marker.tsx @@ -91,7 +91,7 @@ export function GraphContextList({ data }: { data: GraphContextData }): ReactNod
  • {i + 1}. {p.level && ( - + {p.level} )} diff --git a/components/model-picker.tsx b/components/model-picker.tsx index da592e4..841b198 100644 --- a/components/model-picker.tsx +++ b/components/model-picker.tsx @@ -190,7 +190,7 @@ export function ModelControl({ className="flex items-center gap-1.5 rounded-full border border-border/60 bg-card/70 px-2.5 py-1 transition-colors hover:border-border hover:bg-muted" > {effectiveId ? prettyModel(effectiveId) : "—"} - ▾ + ▾ {open === "model" && ( @@ -267,7 +267,7 @@ export function ModelControl({
  • ))} -
    +
    Applies to the next turn · background agents stay config-pinned
    @@ -299,7 +299,7 @@ export function ModelControl({ ))} {error &&
    {error}
    } -
    +
    Levels this model actually supports
    diff --git a/components/plan-block.tsx b/components/plan-block.tsx index 2e6f977..ed19bcc 100644 --- a/components/plan-block.tsx +++ b/components/plan-block.tsx @@ -192,8 +192,9 @@ export const GLOW_BLUR = 5; export const GLOW_REACH = GLOW_MARGIN + 3 * GLOW_BLUR; /** Node titles render at text-xs (12.5px) in svg units. The in-chat block * scales up to fill the column but NEVER past the chat prose size - * (--lh-prose-size, 16.5px) — the width cap that makes that hold. */ -export const TEXT_CAP_SCALE = 16.5 / 12.5; + * (--lh-prose-size, 16px — the ladder's reading rung) — the width cap + * that makes that hold. */ +export const TEXT_CAP_SCALE = 16 / 12.5; // ─── The owner's arrowhead (plan-graph round 5) ────────────────────────────── @@ -808,7 +809,7 @@ export function PlanPopup({ messages, onClose }: { messages: readonly PopupMessa />
    - Drag to pan · scroll to zoom + Drag to pan · scroll to zoom
    - Sessions + Sessions { assert.ok(!/data-testid="plan-block-full"[^>]*border/.test(html), "no wrapping chrome"); // Fills the column UP TO the chat-font cap: the svg is w-full + // mx-auto with maxWidth = extent × TEXT_CAP_SCALE, so node text never - // renders past the 16.5px prose size (owner, plan-graph round 1). + // renders past the 16px prose size (owner, plan-graph round 1; the + // snap 16.5→16 landed in typography round 4). assert.match(html, /]*class="block h-auto w-full mx-auto"/); - assert.match(html, /max-width:232\.32px/); // (160+16) × 1.32 + assert.match(html, /max-width:225\.28px/); // (160+16) × 1.28 // Two stacked nodes: 2*NODE_H + GAP_Y = 136 extent + GRAPH_PAD on // every side (strokes/glow never clip at the edges). assert.match(html, /viewBox="-8 -8 176 152"/);