Skip to content

Commit 4e6593b

Browse files
authored
Merge pull request #79 from levelcodeai/feat/chat-typography-t3
feat(chat): prose code blocks get room — T3, and the plan is complete
2 parents 2f1ee0c + 100e0e7 commit 4e6593b

3 files changed

Lines changed: 90 additions & 4 deletions

File tree

docs/CHAT-TYPOGRAPHY.md

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,30 @@ a heading belong to the section it introduces rather than float between two.
122122

123123
### D5 — Code surfaces get room, and stay theme-driven.
124124

125-
`pre` padding `9px 11px`~`12px 14px`, with the block's vertical margin tied to D3's rhythm.
125+
`pre` padding `9px 11px``12px 14px`, with the block's vertical margin tied to D3's rhythm.
126126
Inline code keeps its neutral background; we do **not** start setting `color` (see §1 — we never
127127
did, and hard-coding it would fight every theme).
128128

129+
**Shipped (T3), with one thing the decision did not anticipate:** `pre` is a **global** selector in
130+
this stylesheet and draws four different things — the empty state's ASCII logo, the MCP approval
131+
card's command block, the terminal output pane, and the prose code block this decision is about. Only
132+
the logo has no padding override of its own, so widening bare `pre` would have quietly moved it. The
133+
rule is scoped to `.msg .body pre`, which is the same distinction T2 had to make between `.msg` and
134+
`.msg .body`.
135+
136+
Gated to reading width, like D3: a 380px sidebar is deliberately dense, and six more pixels a side is
137+
content width it does not have. Measured, against `develop`:
138+
139+
| | sidebar (420px) | editor (1200px) |
140+
| --- | --- | --- |
141+
| prose `pre` padding | `9px 11px`**unchanged** | **`12px 14px`** |
142+
| prose `pre` margin | `8px`**unchanged** | **`14px`** (1em at the shipped prose size) |
143+
| ASCII logo, terminal pane, inline code | unchanged | **unchanged** |
144+
145+
Inline code's `padding: 1px 5px` is deliberately left alone. Vertical padding on an inline box does
146+
not grow the line box, so a roomier inline span overlaps the line above it — "code surfaces get room"
147+
is a statement about blocks, not spans.
148+
129149
Deliberately **not** in scope: a header row on code blocks (language label, copy button). That is a
130150
component, not typography, and it belongs in its own slice.
131151

@@ -262,7 +282,10 @@ comparing computed styles against `develop` at 520px, not by eye.
262282
the widened heading scale. **Exit:** h1/h2/h3 are distinguishable at a glance in a screenshot with no
263283
selection, and every non-prose control still matches workbench chrome.
264284

265-
**T3 — code surfaces** *(S)*. D5. Ships: `pre` padding and rhythm.
285+
**T3 — code surfaces** *(S)*. D5. **Shipped.** `pre` padding and rhythm, scoped to `.msg .body pre`
286+
and gated to reading width. **Exit:** a fenced block has room at editor width, the sidebar renders
287+
identically to before, and the three other things that use `<pre>` are untouched — measured, not
288+
eyeballed.
266289

267290
**T4 — speaker treatment** *(S)*. D6. **Shipped.** The label leaves the screen and stays in the
268291
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
289312
Sequencing: T1 first and alone — it may turn out to be most of the perceived fix, and shipping it
290313
by itself is the cheapest way to find out before spending effort on T2–T4.
291314

292-
That held up: T1–T2, T4, T6 and T7 have shipped in that order, each visible on its own. **T3 is the
293-
only slice of this plan still outstanding.** Neither D8/T6 nor D9/T7 was in the original decomposition
315+
That held up: T1–T2, T4, T6, T7 and T3 shipped in that order, each visible on its own. **Every slice
316+
of this plan has now shipped.** Neither D8/T6 nor D9/T7 was in the original decomposition
294317
— both came from looking at the reference again after shipping, which is the argument for slices small
295318
enough to look at. D9 in particular was invisible from inside the plan: T1's own wording said the
296319
composer "keeps its current behaviour", and it took a side-by-side screenshot to notice that was the

extensions/levelcode-ai/media/chat.html

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,17 @@
209209
.msg .body h1, .msg .body h2, .msg .body h3,
210210
.msg .body h4, .msg .body h5, .msg .body h6 { margin: 1.6em 0 .55em; }
211211
.msg .body > :first-child { margin-top: 0; }
212+
/* T3 (docs/CHAT-TYPOGRAPHY.md D5) — prose code blocks get room, and their vertical margin joins the
213+
same em rhythm as a paragraph so raising the prose size opens them with it.
214+
215+
SCOPED TO `.msg .body pre`, NOT bare `pre`. That selector also draws the empty state's ASCII
216+
logo, the MCP approval card's command block and the terminal output pane — chrome with its own
217+
density, and only the logo has no padding override of its own, so a global change would quietly
218+
move it. This is the T2 mistake (`.msg` vs `.msg .body`) waiting in a new place.
219+
220+
Gated to reading width for the same reason D3 is: a 380px sidebar is deliberately dense, and
221+
spending 6 more pixels a side on padding there costs content width the panel does not have. */
222+
.msg .body pre { padding: 12px 14px; margin: 1em 0; }
212223
}
213224
.msg .body h4, .msg .body h5, .msg .body h6 { font-size: 1em; }
214225
.msg .body blockquote { margin: 0 0 8px; padding: 2px 0 2px 12px; border-left: 3px solid var(--border); color: var(--muted); }

extensions/levelcode-ai/test/webviewCss.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,58 @@ test('TRANSCRIPT: the user bubble hugs its content, and is capped short of the c
578578
'tint is the secondary cue and still earns its place — side alone would drop on a wrapped log');
579579
});
580580

581+
test('CODE: prose blocks get room, and nothing else that uses <pre> moves', () => {
582+
// docs/CHAT-TYPOGRAPHY.md D5/T3. `pre` is a GLOBAL selector in this file, and it draws four different
583+
// things: the empty state's ASCII logo, the MCP approval card's command block, the terminal output
584+
// pane, and the prose code block D5 is actually about. Only the logo has no padding override of its
585+
// own — so widening bare `pre` would quietly move it, which is the T2 mistake (`.msg` vs `.msg .body`)
586+
// waiting in a new place.
587+
const gates = [...cssBlocks.matchAll(/@media \(min-width: 760px\)/g)].map((m) => m.index);
588+
const at = gates.find((i) => blockAt(cssBlocks, i).includes('#log {'));
589+
assert.ok(at !== undefined, 'the rhythm gate is gone');
590+
const block = blockAt(cssBlocks, at);
591+
592+
assert.match(block, /\.msg \.body pre \{[^}]*padding:/,
593+
'the code-block padding must be scoped to .msg .body pre');
594+
assert.ok(!/^\s*pre \{[^}]*padding:\s*12px/m.test(css),
595+
'bare `pre` was widened — that moves the ASCII logo and the approval cards too');
596+
assert.match(css, /^\s*pre \{[^}]*padding:\s*9px 11px/m,
597+
'the base `pre` padding changed; the logo and cards inherit it and have no override');
598+
599+
// Gated to reading width for the same reason D3 is: a 380px sidebar is deliberately dense, and
600+
// 6 more pixels a side is content width it does not have. Measured: at 420px the computed padding
601+
// and margins are identical to develop; at 1200px they are 12px 14px and 14px.
602+
// Membership in `block` above is what proves it is gated; this catches the narrower case of a SECOND
603+
// ungated copy declared earlier in the file, which would apply at every width and win nothing visible
604+
// in review.
605+
assert.ok(!/\.msg \.body pre \{[^}]*padding:\s*12px/.test(css.slice(0, at)),
606+
'a second, ungated copy of the code-block padding is declared before the width gate');
607+
608+
// The margin joins D3's rhythm rather than staying an absolute, so raising the prose size opens the
609+
// spacing around a block with it.
610+
assert.match(block, /\.msg \.body pre \{[^}]*margin:\s*[\d.]+em/,
611+
'the block margin must be em-based, or it stops matching the paragraph spacing beside it');
612+
});
613+
614+
test('CODE: inline code stays theme-driven — we still never set its colour', () => {
615+
// D5, and §1 before it: no `color` has ever been set here. The red/orange in dark themes comes from
616+
// the theme, so hard-coding one would fight every theme rather than fixing anything. It is the kind
617+
// of line that gets added while "tidying up the code style" and is invisible until someone switches
618+
// to a light theme.
619+
const rule = /:not\(pre\) > code \{([^}]*)\}/.exec(css);
620+
assert.ok(rule, 'the inline-code rule is gone');
621+
assert.ok(!/(^|;)\s*color\s*:/.test(rule[1]),
622+
'inline code now sets a colour — D5 keeps this theme-driven: ' + rule[1].trim());
623+
assert.match(rule[1], /background:\s*var\(--vscode-textCodeBlock-background/,
624+
'inline code must keep the theme background it has always had');
625+
626+
// Its padding stays tight ON PURPOSE. Vertical padding on an inline box does not grow the line box,
627+
// so a roomier inline code span overlaps the line above it — "code surfaces get room" applies to
628+
// blocks, not to spans.
629+
assert.match(rule[1], /padding:\s*1px 5px/,
630+
'inline padding grew; on an inline box that overlaps the neighbouring line rather than adding room');
631+
});
632+
581633
test('TRANSCRIPT: the heading scale has steps you can actually see', () => {
582634
// The old 1.3/1.18/1.07 put 0.11em between h2 and h3 — 1.4px at 13px, i.e. three levels of
583635
// hierarchy that were indistinguishable without selecting the text.

0 commit comments

Comments
 (0)