diff --git a/docs/CHAT-TYPOGRAPHY.md b/docs/CHAT-TYPOGRAPHY.md index e55027e..aea80b8 100644 --- a/docs/CHAT-TYPOGRAPHY.md +++ b/docs/CHAT-TYPOGRAPHY.md @@ -122,10 +122,30 @@ a heading belong to the section it introduces rather than float between two. ### D5 — Code surfaces get room, and stay theme-driven. -`pre` padding `9px 11px` → ~`12px 14px`, with the block's vertical margin tied to D3's rhythm. +`pre` padding `9px 11px` → `12px 14px`, with the block's vertical margin tied to D3's rhythm. Inline code keeps its neutral background; we do **not** start setting `color` (see §1 — we never did, and hard-coding it would fight every theme). +**Shipped (T3), with one thing the decision did not anticipate:** `pre` is a **global** selector in +this stylesheet and draws four different things — the empty state's ASCII logo, the MCP approval +card's command block, the terminal output pane, and the prose code block this decision is about. Only +the logo has no padding override of its own, so widening bare `pre` would have quietly moved it. The +rule is scoped to `.msg .body pre`, which is the same distinction T2 had to make between `.msg` and +`.msg .body`. + +Gated to reading width, like D3: a 380px sidebar is deliberately dense, and six more pixels a side is +content width it does not have. Measured, against `develop`: + +| | sidebar (420px) | editor (1200px) | +| --- | --- | --- | +| prose `pre` padding | `9px 11px` — **unchanged** | **`12px 14px`** | +| prose `pre` margin | `8px` — **unchanged** | **`14px`** (1em at the shipped prose size) | +| ASCII logo, terminal pane, inline code | unchanged | **unchanged** | + +Inline code's `padding: 1px 5px` is deliberately left alone. Vertical padding on an inline box does +not grow the line box, so a roomier inline span overlaps the line above it — "code surfaces get room" +is a statement about blocks, not spans. + Deliberately **not** in scope: a header row on code blocks (language label, copy button). That is a component, not typography, and it belongs in its own slice. @@ -262,7 +282,10 @@ comparing computed styles against `develop` at 520px, not by eye. the widened heading scale. **Exit:** h1/h2/h3 are distinguishable at a glance in a screenshot with no selection, and every non-prose control still matches workbench chrome. -**T3 — code surfaces** *(S)*. D5. Ships: `pre` padding and rhythm. +**T3 — code surfaces** *(S)*. D5. **Shipped.** `pre` padding and rhythm, scoped to `.msg .body pre` +and gated to reading width. **Exit:** a fenced block has room at editor width, the sidebar renders +identically to before, and the three other things that use `
` are untouched — measured, not
+eyeballed.
 
 **T4 — speaker treatment** *(S)*. D6. **Shipped.** The label leaves the screen and stays in the
 accessibility tree, and a turn start buys back part of the height it was occupying. **Exit:** the
@@ -289,8 +312,8 @@ property reaches the webview from settings, the second is a line — so splittin
 Sequencing: T1 first and alone — it may turn out to be most of the perceived fix, and shipping it
 by itself is the cheapest way to find out before spending effort on T2–T4.
 
-That held up: T1–T2, T4, T6 and T7 have shipped in that order, each visible on its own. **T3 is the
-only slice of this plan still outstanding.** Neither D8/T6 nor D9/T7 was in the original decomposition
+That held up: T1–T2, T4, T6, T7 and T3 shipped in that order, each visible on its own. **Every slice
+of this plan has now shipped.** Neither D8/T6 nor D9/T7 was in the original decomposition
 — both came from looking at the reference again after shipping, which is the argument for slices small
 enough to look at. D9 in particular was invisible from inside the plan: T1's own wording said the
 composer "keeps its current behaviour", and it took a side-by-side screenshot to notice that was the
diff --git a/extensions/levelcode-ai/media/chat.html b/extensions/levelcode-ai/media/chat.html
index 2d87bea..a6188eb 100644
--- a/extensions/levelcode-ai/media/chat.html
+++ b/extensions/levelcode-ai/media/chat.html
@@ -209,6 +209,17 @@
     .msg .body h1, .msg .body h2, .msg .body h3,
     .msg .body h4, .msg .body h5, .msg .body h6 { margin: 1.6em 0 .55em; }
     .msg .body > :first-child { margin-top: 0; }
+    /* T3 (docs/CHAT-TYPOGRAPHY.md D5) — prose code blocks get room, and their vertical margin joins the
+       same em rhythm as a paragraph so raising the prose size opens them with it.
+
+       SCOPED TO `.msg .body pre`, NOT bare `pre`. That selector also draws the empty state's ASCII
+       logo, the MCP approval card's command block and the terminal output pane — chrome with its own
+       density, and only the logo has no padding override of its own, so a global change would quietly
+       move it. This is the T2 mistake (`.msg` vs `.msg .body`) waiting in a new place.
+
+       Gated to reading width for the same reason D3 is: a 380px sidebar is deliberately dense, and
+       spending 6 more pixels a side on padding there costs content width the panel does not have. */
+    .msg .body pre { padding: 12px 14px; margin: 1em 0; }
   }
   .msg .body h4, .msg .body h5, .msg .body h6 { font-size: 1em; }
   .msg .body blockquote { margin: 0 0 8px; padding: 2px 0 2px 12px; border-left: 3px solid var(--border); color: var(--muted); }
diff --git a/extensions/levelcode-ai/test/webviewCss.test.js b/extensions/levelcode-ai/test/webviewCss.test.js
index 4f74cc1..ab1afc4 100644
--- a/extensions/levelcode-ai/test/webviewCss.test.js
+++ b/extensions/levelcode-ai/test/webviewCss.test.js
@@ -578,6 +578,58 @@ test('TRANSCRIPT: the user bubble hugs its content, and is capped short of the c
 		'tint is the secondary cue and still earns its place — side alone would drop on a wrapped log');
 });
 
+test('CODE: prose blocks get room, and nothing else that uses 
 moves', () => {
+	// docs/CHAT-TYPOGRAPHY.md D5/T3. `pre` is a GLOBAL selector in this file, and it draws four different
+	// things: the empty state's ASCII logo, the MCP approval card's command block, the terminal output
+	// pane, and the prose code block D5 is actually about. Only the logo has no padding override of its
+	// own — so widening bare `pre` would quietly move it, which is the T2 mistake (`.msg` vs `.msg .body`)
+	// waiting in a new place.
+	const gates = [...cssBlocks.matchAll(/@media \(min-width: 760px\)/g)].map((m) => m.index);
+	const at = gates.find((i) => blockAt(cssBlocks, i).includes('#log {'));
+	assert.ok(at !== undefined, 'the rhythm gate is gone');
+	const block = blockAt(cssBlocks, at);
+
+	assert.match(block, /\.msg \.body pre \{[^}]*padding:/,
+		'the code-block padding must be scoped to .msg .body pre');
+	assert.ok(!/^\s*pre \{[^}]*padding:\s*12px/m.test(css),
+		'bare `pre` was widened — that moves the ASCII logo and the approval cards too');
+	assert.match(css, /^\s*pre \{[^}]*padding:\s*9px 11px/m,
+		'the base `pre` padding changed; the logo and cards inherit it and have no override');
+
+	// Gated to reading width for the same reason D3 is: a 380px sidebar is deliberately dense, and
+	// 6 more pixels a side is content width it does not have. Measured: at 420px the computed padding
+	// and margins are identical to develop; at 1200px they are 12px 14px and 14px.
+	// Membership in `block` above is what proves it is gated; this catches the narrower case of a SECOND
+	// ungated copy declared earlier in the file, which would apply at every width and win nothing visible
+	// in review.
+	assert.ok(!/\.msg \.body pre \{[^}]*padding:\s*12px/.test(css.slice(0, at)),
+		'a second, ungated copy of the code-block padding is declared before the width gate');
+
+	// The margin joins D3's rhythm rather than staying an absolute, so raising the prose size opens the
+	// spacing around a block with it.
+	assert.match(block, /\.msg \.body pre \{[^}]*margin:\s*[\d.]+em/,
+		'the block margin must be em-based, or it stops matching the paragraph spacing beside it');
+});
+
+test('CODE: inline code stays theme-driven — we still never set its colour', () => {
+	// D5, and §1 before it: no `color` has ever been set here. The red/orange in dark themes comes from
+	// the theme, so hard-coding one would fight every theme rather than fixing anything. It is the kind
+	// of line that gets added while "tidying up the code style" and is invisible until someone switches
+	// to a light theme.
+	const rule = /:not\(pre\) > code \{([^}]*)\}/.exec(css);
+	assert.ok(rule, 'the inline-code rule is gone');
+	assert.ok(!/(^|;)\s*color\s*:/.test(rule[1]),
+		'inline code now sets a colour — D5 keeps this theme-driven: ' + rule[1].trim());
+	assert.match(rule[1], /background:\s*var\(--vscode-textCodeBlock-background/,
+		'inline code must keep the theme background it has always had');
+
+	// Its padding stays tight ON PURPOSE. Vertical padding on an inline box does not grow the line box,
+	// so a roomier inline code span overlaps the line above it — "code surfaces get room" applies to
+	// blocks, not to spans.
+	assert.match(rule[1], /padding:\s*1px 5px/,
+		'inline padding grew; on an inline box that overlaps the neighbouring line rather than adding room');
+});
+
 test('TRANSCRIPT: the heading scale has steps you can actually see', () => {
 	// The old 1.3/1.18/1.07 put 0.11em between h2 and h3 — 1.4px at 13px, i.e. three levels of
 	// hierarchy that were indistinguishable without selecting the text.