refactor(ui): re-tier font scale — heading 15 / body 13 / caption 11 - #616
Merged
Conversation
…546) Body 13 becomes the root (html font-size) and the chrome tier: ui now aliases base, landing the app on the VS Code/macOS shape (body == chrome, hierarchy by weight/color). The old 15px is demoted to an honest --font-size-heading tier consumed only by title-class sites (settings field labels, modal/card/section titles, nav-button glyph), which keep their exact pixels. Inline code compensates 0.86em -> 0.92em so it stays above caption size at the denser body. Contract re-pinned to the new four-token vocabulary.
7 tasks
Astro-Han
added a commit
that referenced
this pull request
Jul 7, 2026
…ase B) (#620) * fix(chat-message): remove stale PR#212 caret hacks, clear pre-code border leak, square blockquote/table chrome (#546) Four prose rendering defects at the 13px base, each locked by a new PROSE-POLISH-13PX-0 contract: - Drop the :nth-last-child(2) margin/inline hacks: they assumed the bubble ended with an inline timestamp child (PR #212), which moved to the turn footer long ago, so they instead zeroed/inlined the second-to-last markdown block (paragraph glued under a heading, table or code block). The streaming caret keeps its inline ride via a .maka-bubble-streaming-scoped rule. - Reset border on .maka-code-block pre code: the inline-code pill border painted a rounded outline around every wrapped line box inside pre. - Zero trailing/leading margins inside blockquote (8px/20px padding asymmetry). - Table border-collapse: separate + border-spacing: 0 so the outer hairline and border-radius actually render (collapse voids both). * refactor(chat-message): re-pin prose heading ladder to integer px at the 13px base (#546) The h1-h4 em values were derived for the 15px era (22/19/16/14) and landed on fractional pixels after the #616 re-tier: 19.07/16.47/13.87/ 12.13, with h3 only 0.87px above body and h4 below body. Re-pin to integer targets 19/16/14/13; h4 sits at 1em and reads as a heading via weight + secondary color (GitHub's h4 convention). Extend the LongFormArticle story with h3/h4/hr samples so the lower ladder is visually reviewable (hr written as *** — the storybook baseline token scan reads a quoted triple-dash as a custom property). * fix(chat-message): shrink-wrap prose tables to content width (#546) A short three-column table stretched to the full 72ch measure reads as voids between columns. Adopt GitHub's markdown-table shape: display: block + width: max-content lets narrow tables hug their cells, capped at the prose measure with overflow-x: auto so over-wide tables scroll horizontally. th gets white-space: nowrap so column-squeezed tables raise their min-content width and hit the scroller instead of pulverizing short CJK headers into vertical stacks; body cells still wrap. Adds a WideTable story pinning the scroll branch. * refactor(chat-message): frameless prose tables with reinforced header rule (#546) Picked from a four-way style comparison (card+tinted header / GitHub grid+zebra / frameless / brand-tint header): drop the outer border, radius and th fill — the 3% th tint was invisible in practice and the card frame boxed the table in for no informational gain. The header now reads via semibold + a foreground-alpha rule visibly stronger than the hairline row separators. Shrink-wrap + overflow-x scroll behavior stays. Contract re-pinned to the frameless shape. * refactor(chat-message): key heading and table-row rhythm to the paragraph gap (#546) Measured against the 12px paragraph gap, headings sat at 16px above / 8px below — too close to a plain paragraph break to separate sections, and tighter below than the body rhythm itself. Re-key to 20px above / 12px below (space-5 / space-3): sections separate clearly and the heading-to-body gap now equals the paragraph rhythm. Frameless table rows measured 36.5px for 13px content (inter-row whitespace 2.5x the paragraph line gap); drop cell vertical padding 8 -> 6 so rows land at ~32px. * fix(chat-message): slim the inline-code pill so hard-break lines breathe (#546) The Geist Mono font box is already 15.5px at the 0.92em pill size; with 1px vertical padding + 1px border the pill's ink box hit 19.5px — the entire line box. Consecutive hard-break lines carrying pills touched (measured 1.5px from pill edge to the next line's glyphs, 0 between two pills). Drop the vertical padding and the 3%-alpha border (invisible in both themes): the pill ink box returns to the font box and inter-line air recovers to ~4-5.5px, matching plain text lines. * fix(chat-message): scope frameless-table last-row reset to tbody (#546 Phase B) The GFM thead row is its parent's :last-child too, so the unscoped `.maka-prose tr:last-child th` (0,2,2) out-specified `.maka-prose th` (0,1,1) and erased the reinforced header rule — the frameless table's header/body split never painted (th computed border-bottom-width was 0). Found independently by two external reviewers; verified by computed-style probe before and after. Contract test now bans unscoped `.maka-prose tr:last-child` and pins the tbody-scoped reset. WideTable story comment updated for the frameless style (no rounded border anymore). * fix(chat-message): drop the streaming caret once the truncation badge shows (#546 Phase B) With the truncated badge appended after the Markdown, the trailing <p> is no longer :last-child: the caret-inline rule stops matching and the caret wraps onto its own line under the badge — blinking after "已截断" while promising text that will never render (output beyond the single-turn cap is dropped, chat-view.tsx). Suppress the ::after via :has(> [role="status"]); verified by computed-style probe (content none with badge, ▎ without). * test(desktop): reword pre-code border guard for the now-borderless pill The inline-code pill no longer carries a border (dropped in the pill slimming pass), so the guard's rationale read as if a live leak still existed. Keep the reset pinned as a regression guard and say so. * test(desktop): close prose contract gaps flagged in review; correct border-spacing comment - Negative assertion: ban p:last-child inlining on .maka-prose / .maka-bubble-assistant (the caret test only asserted the streaming variant exists — the regression it guards against was untestable). - Assert blockquote > :first-child margin-top: 0 to match the test's 'both ends' claim. - The table comment claimed border-collapse AND border-spacing are inherited; border-spacing is not — its initial value being 0 is what keeps the anonymous inner table seamless. Say so. * fix(chat-message): revert caret suppression under the truncation badge The rule assumed the badge means output froze. It doesn't: applyAssistantDelta sets truncated for the per-delta cap too (a single over-large delta), the flag is sticky in session-events, and later deltas keep appending — so the rule hid a live streaming indicator. CSS can't distinguish per-delta from total-cap truncation without new TSX plumbing, which isn't worth a caret's resting position. Documented the accepted caret-after-badge behavior instead. Flagged by the codex merge-gate review. * test(desktop): pin border-collapse: separate, the declaration that actually works The prose-table contract locked only the no-op border-spacing: 0 (intent documentation — not inherited, initial value already 0) while the declaration that actually reaches the anonymous inner table box, border-collapse: separate, was unpinned. Flagged in the pi merge-gate review.
3 tasks
Astro-Han
added a commit
that referenced
this pull request
Jul 12, 2026
* fix(ui): pin Markdown heading ladder to a 0.15em step (#739) The #546 Phase B ladder chased integer px targets at the 13px body base (19/16/14/13), but h3 (1.0769em, +8% over body) was too close to body copy — long answers relied on weight alone to distinguish H3 sections (#739). Re-pin to a uniform 0.15em step (1.45/1.30/1.15/1) so each tier clears the one below by +15% (CDP-verified as the minimum distinguishable gap) and hierarchy reads by size, not weight alone. The em values are design ratios, not px/13 rounding, so a future body-base change does not require re-pinning the ladder the way the #546/#616 integer-px chase did. h4 stays at 1em (GitHub's 1em h4 convention). MARKDOWN-PROSE-HEADING-HIERARCHY-0 locks the step (>=0.15em between tiers + h4=1em), not specific em values — a future patch may widen the step but must not narrow it back to the #546 shape. CDP-verified: 18.85/16.9/14.95/13px. The table-width part of #739 is not re-changed: #626 already moved prose tables to width:max-content + max-width:100% + overflow-wrap:break-word, CDP-verified (short/path/CJK/4-col/40-char hash/inline-code all fit 680px and a 520px narrow column), and the fit invariants are already covered by TABLE-A11Y-SEMANTICS-0 + PROSE-POLISH-13PX-0. Refs #739 * fix(ui): render bare markdown elements so prose.css reaches the DOM (#739) Streamdown's default components tag every markdown element with Tailwind utilities (h1 "text-3xl", h3 "text-xl", th/td "px-4 py-2 text-sm", thead "bg-muted/80", blockquote "border-l-4", ul "list-disc", ...) in the `utilities` layer, overriding prose.css's `components`-layer rules. The heading ladder and table padding from the previous commit never reached the rendered DOM — CDP-verified h3 was 16.25px (text-xl) not 14.95px, and th/td padding was 8px/16px (px-4 py-2) not 6px/12px. The issue's "16px cell padding" was the real render all along; my earlier CDP probe used hand-built HTML that bypassed Streamdown's utilities. Render bare semantic elements via a bareElement helper (h1-h6, p, ul, ol, li, blockquote, hr, strong, thead, tbody, tr, th, td) so prose.css owns all markdown typography. For `code`, distinguish inline (drop Streamdown's bg-muted/px-1.5/py-0.5/text-sm so prose.css's inline-code pill applies) from block (keep `language-*` for rehype-highlight). Elements without a prose.css rule fall back to the browser semantic default. Add MARKDOWN-PROSE-RENDER-OWNER-0 locking the bare overrides. Fix the heading-step contract's centi-em rounding (toFixed(4) rejects a 0.1451em gap, not rounded to 15) and correct the "each tier +15%" comment to "fixed 0.15em step" (the relative gap widens per tier: +15% over body for h3, +13% over h3 for h2, +12% over h2 for h1). CDP-verified on the real MarkdownBody render: h1-h4 = 18.85/16.9/14.95/ 13px (uniform 0.15em step), th/td padding 6px/12px, thead frameless, inline-code = prose.css pill (0.92em, 0/6px padding, foreground-5 bg), block code retains language-* for hljs. Addresses codex review of PR #744 (P2 utility-layer override, P3 contract rounding + comment) at the root — the markdown-body components seam — rather than patching prose.css values that never reached the DOM. Refs #739 * fix(ui): preserve HAST className and functional logic in bare markdown overrides (#739) Codex re-review of PR #744 found the previous bare-override-all was too broad and dropped the wrong thing: - bareElement destructured `className` off, but the className react-markdown forwards to a components override is the HAST node's *semantic* class (remark-gfm's `contains-task-list`/`task-list-item`, rehype-highlight's `language-*`), NOT Streamdown's utilities — the utilities are merged inside Streamdown's default component via `r(utility, t)`, which an override replaces entirely. Dropping className lost GFM task-list classes, so prose.css's `.maka-prose ul.contains-task-list` rules stopped matching. bareElement now preserves className and drops only `node` (the AST node that would otherwise leak as `node="[object Object]"`). - Overriding p/ol/section broke Streamdown's functional logic: default p unwraps a lone image (a bare p produced invalid `<p><div>…</div></p>`), and ol/section clean streaming footnotes. h5/h6 have no prose.css rule, so stripping them lost Streamdown's heading styling (Tailwind preflight resets h1-h6 to inherit). The override set is now scoped to elements whose ONLY Streamdown default is the utility merge: h1-h4, ul, li, thead, tbody, tr, th, td. p/ol/section/h5/h6 are left on Streamdown's default renderer. - Daily Review renders <Markdown> outside the chat bubble (no .maka-prose ancestor), so stripping utilities left its heading/table/blockquote with neither Streamdown utilities nor prose.css. Its section-body div now carries .maka-prose itself — heading/table/blockquote get a prose-css owner, while the section-body's own unlayered CSS still wins for p/ul/ol (components layer loses to unlayered), so Daily Review list/paragraph styling is unchanged. - The inline-code inline/block split was redundant: react-markdown forwards the HAST className (inline code has none, block code has `language-*`), so the pre-existing `<code {...rest} className={className}>` already left inline code to prose.css and block code to hljs. Reverted to that and dropped the misleading comment. MARKDOWN-PROSE-RENDER-OWNER-0 now locks both the bare-override set and the NOT-override set (p/ol/section/h5/h6), plus the Daily Review .maka-prose owner, so a future patch cannot silently drop either side. CDP-verified on the real MarkdownBody render: chat h1-h4 = 18.85/16.9/ 14.95/13px, h5/h6 keep Streamdown's semibold styling, task-list retains contains-task-list/task-list-item (prose.css task-list rules match), lone images stay outside <p>, th/td padding 6px/12px; Daily Review h2 = 16.9px (prose.css heading) while p keeps section-body color/size (unlayered wins). Addresses codex re-review of PR #744 (P2 HAST className + Daily Review owner; P3 h5/h6, p image unwrap, ol/section footnote cleanup). Refs #739 * fix(ui): override blockquote + lock HAST className preservation (#739 P3) Codex round-3 PASS with two P3: - blockquote was the one utility-only element left on Streamdown's default renderer; its `text-muted-foreground italic` utilities sit in the utilities layer and override prose.css's `--foreground-secondary` blockquote color. Added `blockquote: bareElement('blockquote')` for consistency with the h1-h4/ul/li/thead/tbody/tr/th/td set (all utility-only, no functional logic). - The RENDER-OWNER text-shape contract only checked `tag: bareElement('tag')` strings, so changing bareElement to drop `className` again (the round-2 regression) would still pass. Added a render assertion in markdown-body.test.ts that renders a GFM task-list and asserts the output carries `contains-task-list` / `task-list-item` — the HAST classes prose.css's `.maka-prose ul.contains-task-list` rules match on. This locks the HAST-className preservation that the text contract cannot. BARE_OVERRIDDEN now includes blockquote. All tests green: ui 104, desktop contract 18, desktop suite 2318, typecheck clean. Refs #739
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Re-tier the font-size tokens: body drops 15 → 13 and becomes both the root (
html) and the chrome tier (--font-size-uinow aliases--font-size-base); the old 15px is demoted to a new--font-size-headingtier consumed only by title-class sites, which keep their exact pixels. Caption stays 11. Inline code compensates 0.86em → 0.92em so it stays above caption size at the denser body.Why
15px chat body reads oversized for a coding-agent working surface. Peer measurement puts editor-embedded agent chat at 12–13px (VS Code
chat.fontSizedefault 13, source-verified; Zed user messages 12), and the resulting shape — body == chrome == 13, hierarchy carried by weight/color — is exactly the VS Code (workbench 13 == chat 13) and macOS (13pt body == 13pt controls) precedent. A chat-scoped override was considered and rejected: it would create a second parallel scale, the dual-scale maintenance risk design systems warn about. Full analysis in the #546 thread.Refs #546
Scope
Changed:
maka-tokens.css(token block re-pin + modal title → heading),styles.css(text-* line-height comment),chat-message.css(inline-code 0.92em), 6 title-class sites re-pointed to--font-size-heading(settings field labels, settings hint comment, OS-permission heading, skill card h3, plan titles ×2, browser nav-button glyph),typography-converge-contract.test.tsre-pinned to the four-token vocabulary.Not included: markdown body redesign (next PR per the re-ordered #546 plan), tool rendering, any spacing/layout changes, user-facing font-size setting.
Verification
npm run -w @maka/desktop test— 2178 pass, 0 fail (contract re-pinned: heading 15 / base 13 / ui aliases base / caption 11).turn-narrativescenario captured light + dark at 1280 and visually reviewed — chat body/tool rows/composer coherent at 13px, no layout breakage. Full-suite capture intentionally skipped in favor of the core chat scenario.--leading-normal: 1.5→ 19.5px, WCAG 1.4.12 headroom).User-facing impact
Chat prose, composer input, onboarding textarea, and settings hint text render at 13px (previously 15px). All title-tier text, chrome, and captions are pixel-unchanged.
Reviewer notes
The blast radius is deliberately four body-text populations; every other
--font-size-baseconsumer was audited and either re-pointed toheading(7 title sites, zero visual change) or confirmed as body.text-smandtext-basenow resolve to the same 13px — acceptable by design (body == chrome), flagged here so it doesn't read as an accident.