|
| 1 | +# LevelCode — Chat typography and layout — scope & plan |
| 2 | + |
| 3 | +The goal, in one sentence: make the chat read like a **document** rather than a log — a constrained |
| 4 | +measure, real vertical rhythm, and a type scale with actual hierarchy — so a long answer is as |
| 5 | +comfortable to read in LevelCode as it is in the Claude Code console. |
| 6 | + |
| 7 | +Reference behavior: the Claude Code console transcript (side-by-side screenshots, 2026-08-15). |
| 8 | +`CALM-TRANSCRIPT.md` already took the *structure* from the same reference — voice and grouped |
| 9 | +activity. This doc takes the *visual layer*, which that one deliberately left alone. |
| 10 | + |
| 11 | +This became urgent the moment the chat could open as an editor tab (#70). In a 380px sidebar the |
| 12 | +line length is bounded by the container, so nothing looks badly wrong. At 900px it is unbounded, |
| 13 | +and the same CSS produces ~130-character lines — roughly twice the readable measure. |
| 14 | + |
| 15 | +--- |
| 16 | + |
| 17 | +## 1. Verified facts about our own code (read 2026-08-15, not recalled) |
| 18 | + |
| 19 | +All from `extensions/levelcode-ai/media/chat.html`: |
| 20 | + |
| 21 | +| | current | note | |
| 22 | +| --- | --- | --- | |
| 23 | +| Prose column width | **unconstrained** | 12 `max-width` rules exist; every one is a card, dialog or the empty state. **None applies to the message column.** | |
| 24 | +| `#log` | `padding: 12px; gap: 12px` | the entire page margin | |
| 25 | +| `body` | `font-family: var(--vscode-font-family)`<br>`font-size: var(--vscode-font-size)` | the workbench **UI** font — sized for chrome, not for reading | |
| 26 | +| `.msg` | `line-height: 1.5` | | |
| 27 | +| Paragraph | `margin: 0 0 8px` | | |
| 28 | +| Headings | `1.3em / 1.18em / 1.07em`, `margin: 12px 0 6px` | h2→h3 differ by **0.11em**; at 13px that is 1.4px | |
| 29 | +| `.msg.user .body` | `background: var(--field-bg)`, `1px` border, `radius 10px`, `padding: 9px 11px` | | |
| 30 | +| `.msg.assistant .body` | `padding: 1px 2px` | effectively none | |
| 31 | +| `.msg .role` | `11px`, `opacity .55`, uppercase, `letter-spacing .05em` | a label above every turn | |
| 32 | +| Inline code | `background: var(--vscode-textCodeBlock-background…)`, `padding: 1px 5px`, `.92em` | **no `color` is set** — the red/orange in dark themes comes from the theme or webview defaults, not from us | |
| 33 | +| `pre` | `padding: 9px 11px`, `radius 8px`, `margin: 8px 0` | | |
| 34 | + |
| 35 | +Two observations worth stating plainly, because they explain almost everything: |
| 36 | + |
| 37 | +1. **Nothing constrains the measure.** Every other property is defensible; this one is simply absent. |
| 38 | +2. **The chat inherits the workbench UI font size.** That is a reasonable default for a sidebar |
| 39 | + widget and the wrong one for sustained reading. UI type is tuned for scanning labels at density; |
| 40 | + prose wants a larger size and looser leading. |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +## 2. Decisions (with the reasoning, so they can be re-litigated) |
| 45 | + |
| 46 | +### D1 — Constrain the measure. This is the single biggest lever. |
| 47 | + |
| 48 | +`max-width` on the prose column, centred, with the container still full-bleed so cards, code blocks |
| 49 | +and the composer keep their current behaviour. |
| 50 | + |
| 51 | +**Target 680px** — measured, not estimated. An earlier draft of this section said "~72ch, which lands |
| 52 | +near 640–700px". Both halves were wrong, and the correction is worth keeping because the mistake is |
| 53 | +easy to repeat: |
| 54 | + |
| 55 | +| measured in the shipped font at 13px | | |
| 56 | +| --- | --- | |
| 57 | +| `ch` (the width of `0`) | **8.13px** | |
| 58 | +| real average prose character | **5.86px** | |
| 59 | + |
| 60 | +`ch` is **39% wider than actual text**, so a `72ch` cap yields ~100 characters, not 72 — and 72 real |
| 61 | +characters would be a 422px column, narrower than the sidebar. The print-typography range of 45–75 |
| 62 | +characters does not transfer to a technical chat: it assumes prose without identifiers, file paths or |
| 63 | +code, and a 422px column would wrap every code block constantly. |
| 64 | + |
| 65 | +680px is chosen against the measurement: **~116 characters at 13px**, down from 154 at editor width, |
| 66 | +while staying wide enough that a fenced block is still readable. When T2 raises the prose size the |
| 67 | +same cap tightens to ~108 characters, which is the right direction. |
| 68 | + |
| 69 | +The elegant part: **in the sidebar this is a no-op.** The container is already narrower than the cap, |
| 70 | +so nothing moves for existing users. It only takes effect in the editor tab, which is exactly the |
| 71 | +surface that needs it. |
| 72 | + |
| 73 | +### D2 — Prose diverges from the workbench UI font size. Chrome does not. |
| 74 | + |
| 75 | +This is the real trade-off in the whole document, so it gets stated rather than smuggled in. |
| 76 | + |
| 77 | +`--vscode-font-size` (typically 13px) is the size of menu labels and tree rows. Reading three |
| 78 | +paragraphs of explanation at that size, at `line-height: 1.5`, is why the panel feels cramped next to |
| 79 | +the reference. |
| 80 | + |
| 81 | +**Decision:** message bodies get their own size (~14px) and leading (~1.65), expressed relative to a |
| 82 | +single custom property. Everything else — the composer, buttons, session cards, the status row, |
| 83 | +approval chips — keeps inheriting the workbench size, so the panel still belongs to the editor. |
| 84 | + |
| 85 | +**The cost, honestly:** the chat will no longer match workbench chrome exactly. That is a real |
| 86 | +inconsistency, and it is the deliberate price of the panel being a place you *read* rather than a |
| 87 | +place you *operate*. D7 gives it an escape hatch. |
| 88 | + |
| 89 | +### D3 — Vertical rhythm scales with the type, not with pixels. |
| 90 | + |
| 91 | +Spacing is currently absolute (`8px`, `12px`), so raising the font size makes the page *tighter* |
| 92 | +rather than proportionally airier. Every prose gap moves to `em`, anchored to the prose size: |
| 93 | +paragraph `0.85em`, block gap `1.15em`, `#log` padding to ~`20px 24px` at editor width. |
| 94 | + |
| 95 | +### D4 — Widen the heading scale so hierarchy survives. |
| 96 | + |
| 97 | +`1.3 / 1.18 / 1.07` compresses three levels into a quarter of an em. Move to roughly |
| 98 | +`1.45 / 1.25 / 1.1`, with more space *above* a heading than below it — the standard trick that makes |
| 99 | +a heading belong to the section it introduces rather than float between two. |
| 100 | + |
| 101 | +### D5 — Code surfaces get room, and stay theme-driven. |
| 102 | + |
| 103 | +`pre` padding `9px 11px` → ~`12px 14px`, with the block's vertical margin tied to D3's rhythm. |
| 104 | +Inline code keeps its neutral background; we do **not** start setting `color` (see §1 — we never |
| 105 | +did, and hard-coding it would fight every theme). |
| 106 | + |
| 107 | +Deliberately **not** in scope: a header row on code blocks (language label, copy button). That is a |
| 108 | +component, not typography, and it belongs in its own slice. |
| 109 | + |
| 110 | +### D6 — Soften the turn label; keep the user bubble. |
| 111 | + |
| 112 | +The uppercase `LEVELCODE AI` label above every assistant turn adds a line of chrome to every message. |
| 113 | +The reference distinguishes speakers by *treatment* — a tinted bubble for you, unadorned prose for |
| 114 | +the assistant — rather than by labelling both. Keep the user bubble; make the assistant label quieter |
| 115 | +or drop it where the previous turn already establishes who is speaking (`.msg.cont` already exists |
| 116 | +for exactly this case). |
| 117 | + |
| 118 | +### D7 — It stays hackable: two settings, no hard-coded values. |
| 119 | + |
| 120 | +`levelcode.ai.chat.proseWidth` (px, `0` = unconstrained) and `levelcode.ai.chat.fontSize` |
| 121 | +(`0` = follow the workbench). Both flow through CSS custom properties set on the container, so the |
| 122 | +defaults are a starting point rather than a verdict — consistent with the editor's whole posture, and |
| 123 | +the honest answer to anyone who preferred the old density. |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## 3. Slices |
| 128 | + |
| 129 | +Each ships independently and is visible on its own. |
| 130 | + |
| 131 | +**T1 — measure + rhythm** *(S)*. D1 and D3. The largest perceptual change for the least code, and the |
| 132 | +one that fixes the editor tab. Ships: a wrapper max-width, `em`-based prose spacing, wider `#log` |
| 133 | +padding at editor width. **Exit:** a long answer in the editor tab holds ~72 characters per line, and |
| 134 | +the sidebar renders byte-identically to today. |
| 135 | + |
| 136 | +**T2 — the reading type scale** *(S)*. D2 and D4. Ships: the prose size/leading custom properties and |
| 137 | +the widened heading scale. **Exit:** h1/h2/h3 are distinguishable at a glance in a screenshot with no |
| 138 | +selection, and every non-prose control still matches workbench chrome. |
| 139 | + |
| 140 | +**T3 — code surfaces** *(S)*. D5. Ships: `pre` padding and rhythm. |
| 141 | + |
| 142 | +**T4 — speaker treatment** *(S)*. D6. Ships: the quieter label, verified against `.msg.cont`. |
| 143 | + |
| 144 | +**T5 — the escape hatch** *(S)*. D7. Ships: the two settings and their plumbing. |
| 145 | + |
| 146 | +Sequencing: T1 first and alone — it may turn out to be most of the perceived fix, and shipping it |
| 147 | +by itself is the cheapest way to find out before spending effort on T2–T4. |
| 148 | + |
| 149 | +--- |
| 150 | + |
| 151 | +## 4. Risks, honestly |
| 152 | + |
| 153 | +- **Divergence from workbench chrome (D2).** The panel will read as slightly its own thing. Mitigated |
| 154 | + by scoping the change to message bodies only, and by T5. |
| 155 | +- **Sidebar users who liked the density.** At 380px the measure is a no-op, but the type-size change |
| 156 | + is not. T5 is the answer, and T1 shipping alone gives us a read on whether T2 is even wanted. |
| 157 | +- **Theme variance.** Inline-code colour already comes from the theme rather than from us (§1), so |
| 158 | + any judgement about "busy" colour must be checked across the light, dark and high-contrast themes |
| 159 | + the `webviewCss` suite already reasons about — not just the default. |
| 160 | +- **No test currently guards the measure.** `webviewCss.test.js` pins hidden-attribute defeats and the |
| 161 | + session-card overflow; it should gain a guard that the prose column is bounded, or T1 will regress |
| 162 | + silently the first time someone refactors the log container. |
| 163 | +- **Screenshots are not measurements.** Everything here is derived from our own CSS plus a |
| 164 | + side-by-side comparison. The specific numbers (72ch, 14px, 1.65) are considered starting points to |
| 165 | + be tuned against the real thing at real widths, not values copied from the reference — we cannot |
| 166 | + read the reference's stylesheet. |
0 commit comments