From 100e0e7b9aae944906a99ae790d3d953f2545125 Mon Sep 17 00:00:00 2001 From: Sergii Demianchuk Date: Sat, 15 Aug 2026 21:03:53 -0400 Subject: [PATCH] =?UTF-8?q?feat(chat):=20prose=20code=20blocks=20get=20roo?= =?UTF-8?q?m=20=E2=80=94=20T3,=20and=20the=20plan=20is=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit docs/CHAT-TYPOGRAPHY.md D5, the last slice. `pre` padding 9px 11px -> 12px 14px, with the block's vertical margin joining D3's em rhythm so raising the prose size opens it too. THE PART D5 DID NOT ANTICIPATE: `pre` is a GLOBAL selector in this stylesheet, 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 the decision is actually about. Only the logo has no padding override of its own, so widening bare `pre` would have quietly moved it. Scoped to `.msg .body pre`, which is the same distinction T2 had to make between `.msg` and `.msg .body` — the same mistake waiting in a new place. Gated to reading width, like D3. Measured against develop: sidebar (420px) editor (1200px) prose pre padding 9px 11px unchanged 12px 14px prose pre margin 8px unchanged 14px (1em at prose size) ASCII logo / terminal / inline unchanged unchanged Inline code is deliberately untouched. D5 says keep it theme-driven — no `color`, ever — and its tight 1px 5px padding is correct rather than an oversight: vertical padding on an inline box does not grow the line box, so a roomier span overlaps the line above it. "Code surfaces get room" is a statement about blocks, not spans, and both halves are now guarded. Guards, each bypass-verified by reverting the fix: - the rule removed; bare `pre` widened (which moves the ASCII logo) - the margin reverting to an absolute, leaving D3's rhythm - inline code starting to set a colour; inline padding grown - the rule moved outside the width gate, so the sidebar changes too One test message corrected while bypassing: an assertion claimed to prove the rule was inside the gate when it only catches a second ungated copy declared earlier. Membership in the gate block is what actually proves it; the message now says so. 32 tests in webviewCss, 33 suites green. --- docs/CHAT-TYPOGRAPHY.md | 31 +++++++++-- extensions/levelcode-ai/media/chat.html | 11 ++++ .../levelcode-ai/test/webviewCss.test.js | 52 +++++++++++++++++++ 3 files changed, 90 insertions(+), 4 deletions(-) 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.