Skip to content

Commit f9c8f12

Browse files
committed
feat(chat): your turn sits right, the assistant's stays left — T6
docs/CHAT-TYPOGRAPHY.md D8, taken from the reference: in the Claude Code console your messages are right-aligned bubbles and the replies are left-aligned prose. This is the half T4 was missing, and it closes a risk T4 opened rather than adding polish. T4 dropped the labels and left the BUBBLE carrying the speaker distinction alone — but the bubble is `--field-bg` on a `--border` outline, near-invisible in some themes. On a low-contrast theme the transcript could read as one undifferentiated voice, which is exactly the failure D6 set out to avoid. Side is unmissable in every theme, at every contrast, and costs no chrome. Three decisions, each with a way to get it wrong: - `align-items`, NOT `text-align`. The bubble is what moves; the prose inside it stays left. `text-align: right` looks identical on a one-line message and is unreadable on a three-line one — the suite fails on it. - The bubble HUGS its content, so "Yes" is a 47px bubble and a pasted stack trace is a wide one. The shape of a turn now carries what the label used to spell out. - Capped at 85%, not 100%. At 100% a long question fills the measure, reads as a full-width block again, and the side cue disappears when the transcript is densest. Measured in headless Chrome at a 680px column, against develop: before after short bubble ("Yes") 680px 46.9px, flush right long user turn 680px 578px — exactly the 85% cap assistant 680px left 680px left (unchanged) text-align in bubble start start Tint stays as the secondary cue rather than being dropped: side alone would fail on any surface that reflows the log to one column. Guards, each bypass-verified by reverting the fix: - alignment dropped; text-align:right substituted for align-items (the trap) - the assistant pushed right too, which erases the asymmetry that IS the cue - the bubble no longer hugging; the cap removed; the cap raised to 100% - row direction, which would put the checkpoint control beside the bubble T3 (code surfaces) is now the only slice of the plan still outstanding. 27 tests in webviewCss, 32 suites green.
1 parent c13ec4d commit f9c8f12

3 files changed

Lines changed: 105 additions & 1 deletion

File tree

docs/CHAT-TYPOGRAPHY.md

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,39 @@ the contribution schema only drive the settings *editor*; a hand-edited `setting
163163
transcript — a panel with nothing left on screen to open settings with, whose only exit is finding the
164164
JSON file again. `webviewCss.test.js` pins the clamp to the schema so the two cannot drift.
165165

166+
### D8 — Your turn sits right. The assistant's stays left.
167+
168+
Added after T4 shipped, from the reference: in the Claude Code console your messages are right-aligned
169+
bubbles and the replies are left-aligned prose.
170+
171+
This is the half T4 was missing, and it closes a risk T4 opened rather than merely adding polish. T4
172+
removed the labels and left the **bubble** carrying the speaker distinction alone — but the bubble is
173+
`--field-bg` on a `--border` outline, which is near-invisible in some themes. On a low-contrast theme a
174+
transcript could read as one undifferentiated voice, which is precisely the failure D6 was trying to
175+
avoid. **Side is unmissable in every theme, at every contrast, and costs no chrome at all.**
176+
177+
Three things this decides, each with a way to get it wrong:
178+
179+
- **`align-items`, not `text-align`.** The *bubble* is what moves; the prose inside it stays
180+
left-aligned. `text-align: right` looks identical on a one-line message and is unreadable on a
181+
three-line one. The suite fails on it.
182+
- **The bubble hugs its content** (`width: fit-content`), so "Yes" is a 47px bubble and a pasted stack
183+
trace is a wide one. The shape of a turn now carries information the label used to spell out.
184+
- **Capped at 85%, not 100%.** At 100% a long question fills the measure, reads as a full-width block
185+
again, and the side cue disappears exactly when the transcript is densest.
186+
187+
Measured in headless Chrome at a 680px column, against `develop`:
188+
189+
| | before | after |
190+
| --- | --- | --- |
191+
| short bubble ("Yes") | 680px | **46.9px**, flush right |
192+
| long user turn | 680px | **578px** — exactly the 85% cap, flush right |
193+
| assistant | 680px, left | 680px, left (unchanged) |
194+
| `text-align` inside the bubble | `start` | `start` |
195+
196+
Tint stays as the secondary cue rather than being removed: side alone would fail on any surface that
197+
reflows the log to a single column.
198+
166199
---
167200

168201
## 3. Slices
@@ -185,7 +218,13 @@ selection, and every non-prose control still matches workbench chrome.
185218
accessibility tree, and a turn start buys back part of the height it was occupying. **Exit:** the
186219
transcript reads as prose with the bubble as the only visual speaker cue, a continuation is still
187220
visibly tighter than a new turn, and `.msg.cont` still emits no label — verified by computed styles
188-
against `develop`, not by eye.
221+
against `develop`, not by eye. *(T6 then made side the primary cue, so "the bubble alone" describes
222+
T4 as it shipped, not the current state.)*
223+
224+
**T6 — side** *(S)*. D8. **Shipped.** Your turn moves right and hugs its content; the assistant stays
225+
left and full-measure. **Exit:** a short turn renders as a short right-flush bubble, a long one caps
226+
below the column, the assistant is untouched, and the prose inside the bubble is still left-aligned —
227+
all four measured, not eyeballed.
189228

190229
**T5 — the escape hatch** *(S)*. D7. **Folded into T2 and shipped with it.** Sequencing it last was a
191230
mistake: T2 is the one slice that changes what every existing user sees, and shipping a divisive
@@ -195,6 +234,11 @@ property reaches the webview from settings, the second is a line — so splittin
195234
Sequencing: T1 first and alone — it may turn out to be most of the perceived fix, and shipping it
196235
by itself is the cheapest way to find out before spending effort on T2–T4.
197236

237+
That held up: T1–T2, T4 and T6 have shipped in that order, each visible on its own. **T3 is the only
238+
slice of this plan still outstanding.** D8/T6 was not in the original decomposition — it came from
239+
looking at the reference again after T4, which is the argument for shipping slices small enough to
240+
look at.
241+
198242
---
199243

200244
## 4. Risks, honestly

extensions/levelcode-ai/media/chat.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,24 @@
215215
.msg .body > div > :last-child, .msg .body > p:last-child, .msg .body > ul:last-child, .msg .body > ol:last-child { margin-bottom: 0; }
216216
.msg.user .body { background: var(--field-bg); border: 1px solid var(--border); border-radius: 10px; padding: 9px 11px; }
217217
.msg.assistant .body { padding: 1px 2px; }
218+
/* T6 (docs/CHAT-TYPOGRAPHY.md D8) — your turn sits RIGHT, the assistant's stays LEFT.
219+
This is what T4 was missing. T4 dropped the labels and left the bubble carrying the speaker
220+
distinction alone — but the bubble is `--field-bg`, which is near-invisible in some themes, so on
221+
a low-contrast theme the transcript could read as one undifferentiated voice. Side is unmissable
222+
in every theme, at every contrast, and costs no chrome at all.
223+
224+
The bubble HUGS its content rather than filling the column, so "Yes" is a short bubble and a
225+
pasted stack trace is a wide one — the shape of a turn now carries information the label used to
226+
spell out. Capped at 85%: at 100% a long question fills the column, reads as a full-width block
227+
again, and the side cue disappears exactly when the transcript is densest.
228+
229+
`align-items`, NOT `text-align`: the bubble is the thing being placed, so the prose inside it
230+
stays left-aligned — right-aligned body text is unreadable past one line. `.ckwrap` rides along
231+
to the right edge, which is correct: the checkpoint control belongs to the turn it restores.
232+
`width: fit-content` is what `align-items: flex-end` already implies; it is stated so that
233+
changing the alignment later cannot silently stretch the bubble back to full width. */
234+
.msg.user { display: flex; flex-direction: column; align-items: flex-end; }
235+
.msg.user .body { width: fit-content; max-width: 85%; }
218236
.err { color: var(--vscode-errorForeground); }
219237
/* free-tier cap reached → a compelling upgrade CTA instead of raw 402 error text */
220238
.upgradecard { border: 1px solid color-mix(in srgb, var(--accent) 45%, var(--border)); border-radius: 12px; margin: 8px 2px; padding: 13px 15px 14px; background: color-mix(in srgb, var(--accent) 9%, var(--field-bg)); }

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,48 @@ test('TRANSCRIPT: dropping the label does not collapse the gap between speakers'
437437
'the bubble needs its border: --field-bg alone is near-invisible in some themes');
438438
});
439439

440+
test('TRANSCRIPT: your turn sits right, the assistant stays left', () => {
441+
// docs/CHAT-TYPOGRAPHY.md D8/T6. T4 removed the labels and left the bubble carrying the speaker
442+
// distinction alone — but the bubble is `--field-bg`, near-invisible in some themes, so on a
443+
// low-contrast theme the transcript could read as one undifferentiated voice. Side is unmissable in
444+
// every theme, at every contrast, and costs no chrome.
445+
assert.match(css, /\.msg\.user \{[^}]*display:\s*flex/, 'the user turn is no longer a flex container');
446+
assert.match(css, /\.msg\.user \{[^}]*flex-direction:\s*column/,
447+
'a row direction would put the checkpoint control beside the bubble instead of under it');
448+
assert.match(css, /\.msg\.user \{[^}]*align-items:\s*flex-end/, 'the user turn is no longer pushed right');
449+
450+
// THE TRAP. `text-align: right` looks like the same change on a one-line message and is completely
451+
// different on a three-line one: it right-aligns the PROSE, which is unreadable past one line.
452+
// The bubble is the thing being placed; the words inside it stay left.
453+
const userRules = [...css.matchAll(/\.msg\.user[^{}\n]*\{([^}]*)\}/g)].map((m) => m[1]).join(' ');
454+
assert.ok(!/text-align\s*:\s*right/.test(userRules),
455+
'right-align the BUBBLE (align-items), never the text — multi-line prose becomes unreadable');
456+
457+
// The asymmetry IS the cue. Give the assistant the same treatment and both sides move together,
458+
// which restores exactly the undifferentiated column T4 was at risk of.
459+
assert.ok(!/\.msg\.assistant[^{}\n]*\{[^}]*align-items:\s*flex-end/.test(css),
460+
'the assistant must stay left — if both sides sit right there is no side cue at all');
461+
});
462+
463+
test('TRANSCRIPT: the user bubble hugs its content, and is capped short of the column', () => {
464+
const rule = /\.msg\.user \.body \{([^}]*width:\s*fit-content[^}]*)\}/.exec(css);
465+
assert.ok(rule, 'the user bubble no longer hugs its content — "Yes" would be a full-width block');
466+
467+
// Capped BELOW the column: at 100% a long question fills the measure, reads as a full-width block
468+
// again, and the side cue disappears exactly when the transcript is densest.
469+
const cap = /max-width:\s*(\d+)%/.exec(rule[1]);
470+
assert.ok(cap, 'the bubble needs a percentage cap, or a long turn spans the whole column');
471+
const pct = Number(cap[1]);
472+
assert.ok(pct >= 70 && pct <= 90,
473+
'the cap is ' + pct + '%; below ~70% a normal question wraps far too early, above ~90% the '
474+
+ 'asymmetry stops being visible');
475+
476+
// Measured in headless Chrome at a 680px column: "Yes" renders 46.9px wide and the long turn
477+
// lands on 578px, exactly the cap. Both flush to the column's right edge; the assistant stays 680.
478+
assert.match(css, /\.msg\.user \.body \{[^}]*background:\s*var\(--field-bg\)/,
479+
'tint is the secondary cue and still earns its place — side alone would drop on a wrapped log');
480+
});
481+
440482
test('TRANSCRIPT: the heading scale has steps you can actually see', () => {
441483
// The old 1.3/1.18/1.07 put 0.11em between h2 and h3 — 1.4px at 13px, i.e. three levels of
442484
// hierarchy that were indistinguishable without selecting the text.

0 commit comments

Comments
 (0)