Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions docs/CHAT-TYPOGRAPHY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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 `<pre>` 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
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions extensions/levelcode-ai/media/chat.html
Original file line number Diff line number Diff line change
Expand Up @@ -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); }
Expand Down
52 changes: 52 additions & 0 deletions extensions/levelcode-ai/test/webviewCss.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pre> 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.
Expand Down