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.