feat(web): Appearance settings tab with font-size slider + live preview#2174
feat(web): Appearance settings tab with font-size slider + live preview#2174akarabach wants to merge 16 commits intopingdotgg:mainfrom
Conversation
Move Theme and Time format settings from the General tab into a new dedicated Appearance tab, improving settings discoverability.
Adds a new "Chat font size" appearance setting that proportionally scales every text tier in the chat timeline — message bodies, work log rows, metadata, code blocks, and inline markdown — via a single CSS variable on the timeline wrapper. The control is a numeric stepper (12–24 px, default 14) and participates in Restore defaults.
Pull the presentational outer shells for user and assistant chat bubbles into a shared module (plus a `ChatFontSizeScope` helper that sets the `--chat-font-size` CSS var and the `data-timeline-root` attribute). The live timeline now renders through these shells, and the appearance settings preview will too — so both stay pixel-identical as the styling evolves. No behavior change for the live chat; this is a lift-and-reuse.
Replaces the +/- stepper with a continuous slider (base-ui) and swaps
the two-bubble preview for a compact chat mock-up that exercises every
`text-chat-*` tier plus `ChatMarkdown`:
- work-log section header (text-chat-mini)
- Bash / Edit tool rows (text-chat-xs heading, text-chat-xxs preview)
- changed-file chip (font-mono text-chat-xxxs)
- user bubble body (text-chat-body) with text-chat-xs timestamp
- assistant bubble rendering real ChatMarkdown (paragraph, bulleted
list, inline code, fenced ts code block) with a text-chat-xxxs meta
line
So every scale point the live chat uses visibly responds as the slider
moves. Also moves the Chat font size row to the bottom of the Appearance
tab so simpler rows (Theme, Time format) stay above the fold.
Preview lives in its own `ChatFontSizePreview` component to keep
`AppearanceSettingsPanel.tsx` focused on wiring.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
ApprovabilityVerdict: Needs human review This PR introduces a new user-facing feature (Appearance settings tab with font-size customization and live preview), which warrants human review. Additionally, there are unresolved review comments identifying potential bugs: missing copy-button padding in code blocks, and the Restore defaults button not appearing on the new Appearance page. You can customize Macroscope's approvability policy. Learn more. |
The `chatBubbleShells` module introduced by the earlier refactor added indirection without meaningful payoff: - `UserMessageBubbleShell` / `AssistantMessageBubbleShell` are one-to-two-element wrappers over trivial Tailwind class strings; inlining them at the two call sites is cheaper than maintaining a shared module. - `ChatFontSizeScope` was only used by the settings preview. The live timeline sets `--chat-font-size` on the LegendList wrapper and `data-timeline-root` on each row, so the single-element helper never fit there. - The preview is decorative — pixel-perfect parity with the real bubble JSX isn't required, so cross-file coupling isn't earning us anything. Delete the module, restore inline bubble JSX in MessagesTimeline, and inline the wrapper + font-size scope in ChatFontSizePreview.
|
|
||
| .markdown-file-link-tooltip-scroll { | ||
| scrollbar-width: thin; | ||
| scrollbar-color: color-mix(in srgb, var(--border) 78%, transparent) transparent; |
There was a problem hiding this comment.
Codeblock copy-button padding-right accidentally removed
Medium Severity
The --chat-markdown-codeblock-copy-button-space variable and the padding-right rule on .chat-markdown .chat-markdown-codeblock pre were removed. This padding reserved space so code content wouldn't flow under the absolutely-positioned copy button. Without it, the copy button (which appears on hover) will overlap the rightmost code text in fenced code blocks.
Reviewed by Cursor Bugbot for commit 47d87c5. Configure here.
…ngs-tab # Conflicts: # apps/web/src/components/chat/MessagesTimeline.tsx # apps/web/src/localApi.test.ts # packages/contracts/src/settings.ts
Revert the identifier rename from the extraction — the component was called `SimpleWorkEntryRow` in MessagesTimeline and should keep that name in its new file too. Only the module path changes.
Today's merge from main silently dropped several CSS additions that landed on main while our branch was in flight: - `.chat-markdown-file-link` / `:hover` / `:focus-visible` / `-icon` / `-label` (from pingdotgg#1956 — the file-link UX redesign) - `.markdown-file-link-tooltip-scroll` + its `::-webkit-scrollbar*` variants (same PR) - `--chat-markdown-codeblock-copy-button-space` var, the padding-right adjustment that uses it, and the `cursor: pointer` on the copy button (from pingdotgg#1985) - `@custom-variant wco` (from pingdotgg#1969) `ChatMarkdown.tsx` still references all four file-link class names, so their absence silently un-styled every inline file link the assistant renders. Fix by rebasing our index.css additions on top of origin/main so the only delta versus main is the additive Chat-font-size tier block (`text-chat-*` utilities + `[data-timeline-root]` scaling).
Two focused additions guarding the chat font-size feature:
- `packages/contracts/src/settings.test.ts` — new file, 11 tests.
Covers `ChatFontSize` schema bounds (min 12 / max 24, rejects below,
above, non-integer, non-numeric), `ClientSettingsSchema` decoding
default behaviour for `chatFontSize`, and confirms both
`DEFAULT_CLIENT_SETTINGS` and `DEFAULT_UNIFIED_SETTINGS` carry the
default value 14.
- `apps/web/src/components/chat/MessagesTimeline.test.tsx` — one new
SSR-render test asserting that a `chatFontSize={20}` prop surfaces as
`--chat-font-size:20px` on the list wrapper, pinning the contract
between the prop and the CSS variable that drives every `text-chat-*`
tier plus `.chat-markdown` scaling.
Replace our hand-rolled slider.tsx with the exact upstream
`@shadcn` `base-mira` component via:
bunx --bun shadcn@latest add slider --overwrite
Differences vs the previous local copy are purely cosmetic and match
the current shadcn default:
- Track: `rounded-md bg-muted` with `h-1` (was `rounded-full
bg-muted-foreground/30 h-1.5`)
- Thumb: `size-3 rounded-md` (was `size-4 rounded-full`)
- Data-attr variants: `data-horizontal:` / `data-vertical:` short
form (was `data-[orientation=horizontal]:` / `-vertical:`)
Keeps us aligned with upstream so future `shadcn@latest add slider`
diffs stay empty. The `_values` fallback for the edge case of a
scalar-number `value` / `defaultValue` is preserved verbatim from
upstream — we do not locally patch shadcn components.
| function Slider({ | ||
| className, | ||
| defaultValue, | ||
| value, | ||
| min = 0, | ||
| max = 100, | ||
| ...props | ||
| }: SliderPrimitive.Root.Props) { | ||
| const _values = Array.isArray(value) | ||
| ? value | ||
| : Array.isArray(defaultValue) | ||
| ? defaultValue | ||
| : [min, max]; |
There was a problem hiding this comment.
🟢 Low ui/slider.tsx:5
When value is passed as a single number (e.g., value={50}), Array.isArray(value) returns false so the code falls through to check defaultValue or use [min, max]. This renders 2 thumbs when only 1 value exists, creating a mismatch between the UI and the actual slider state. Consider normalizing single values to an array before the length check.
- const _values = Array.isArray(value)
- ? value
- : Array.isArray(defaultValue)
- ? defaultValue
- : [min, max];
+ const _values =
+ value !== undefined
+ ? Array.isArray(value)
+ ? value
+ : [value]
+ : defaultValue !== undefined
+ ? Array.isArray(defaultValue)
+ ? defaultValue
+ : [defaultValue]
+ : [min, max];🤖 Copy this AI Prompt to have your agent fix this:
In file apps/web/src/components/ui/slider.tsx around lines 5-17:
When `value` is passed as a single number (e.g., `value={50}`), `Array.isArray(value)` returns false so the code falls through to check `defaultValue` or use `[min, max]`. This renders 2 thumbs when only 1 value exists, creating a mismatch between the UI and the actual slider state. Consider normalizing single values to an array before the length check.
Evidence trail:
apps/web/src/components/ui/slider.tsx lines 12-16 show the _values logic. Base UI documentation at https://base-ui.com/react/components/slider confirms `value` prop type is `number | number[] | undefined`. When `value={50}` is passed, `Array.isArray(50)` is false, so `_values` falls through to `[min, max]` (length 2), but `SliderPrimitive.Root` receives `value={50}` (single number) creating a UI/state mismatch.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit fbab76c. Configure here.
| [ | ||
| areProviderSettingsDirty, | ||
| isGitWritingModelDirty, | ||
| settings.chatFontSize, |
There was a problem hiding this comment.
Restore defaults button missing on Appearance settings page
Medium Severity
The showRestoreDefaults guard in settings.tsx only checks for "/settings/general", so the "Restore defaults" button never appears on the new /settings/appearance route. Theme, Time format, and Chat font size were moved to Appearance, but users on that page have no way to trigger a bulk restore. Additionally, useSettingsRestore still lists those Appearance-only settings (Theme, Time format, Chat font size) in changedSettingLabels, so the General page's "Restore defaults" dialog can mention and reset settings that aren't even visible there.
Reviewed by Cursor Bugbot for commit fbab76c. Configure here.
|
@juliusmarminge Should be straightforward to review. The only thing I'm a bit unsure about is the custom chat font size classes like |


Re-open of #2014 (closed, couldn't reopen)
Summary
Introduces a dedicated Appearance tab under settings and adds a new
Chat font size control with a slider and a live preview that mirrors
the real chat timeline.
/settings/appearanceroute (the original scope of feat(web): extract appearance settings to a separate tab #2014).chatFontSizeclient setting (12–24 px, default 14) that cascadesthrough a single
--chat-font-sizeCSS variable into everytext-chat-*tier and.chat-markdownrule.(
@base-ui/react/slider).apps/web/src/components/chat/chatBubbleShells.tsxso the livetimeline and the settings preview render identical bubbles.
ChatFontSizePreviewcomponent renders a compact mock turn:work-log header (
text-chat-mini), Bash/Edit tool rows(
text-chat-xs/text-chat-xxs), a font-mono changed-file chip(
text-chat-xxxs), a user bubble (text-chat-bodywith atext-chat-xstimestamp), and an assistant bubble rendering realChatMarkdown(paragraph, list, inline code, fenced code block) witha
text-chat-xxxsmeta line — so every tier scales visibly as theslider moves.
Test plan
bun fmt && bun lint && bun typecheck— all green.header, tool heading, tool preview, changed-file chip, user
bubble body, assistant markdown (paragraph + list + inline
code+ fencedtscode block), and meta line all scale.rendering at the same slider value.
follows, preview re-renders at default size.
Screenshots
Closes #2014 (re-opened as this PR).
Note
Medium Risk
Touches shared settings schema/persistence and applies a new CSS-variable-driven typography scale across the chat timeline, so regressions could affect rendering or settings decoding across web/desktop.
Overview
Adds a new Appearance settings section at
/settings/appearance(with sidebar nav + route generation) and moves Theme and Time format controls out of General.Introduces a persisted
chatFontSizeclient setting (12–24px, default 14) inpackages/contractsand wires it through web/desktop persistence and local API tests.Applies chat font scaling by passing
chatFontSizefromChatViewintoMessagesTimeline, setting--chat-font-sizeon the timeline wrapper, updating timeline text classes totext-chat-*, and adding[data-timeline-root]CSS rules (including markdown/code scaling). Includes a newSlidercomponent, a liveChatAppearancePreview, and refactors work-log row rendering into reusableSimpleWorkEntryRowwith updated tests.Reviewed by Cursor Bugbot for commit 24cacb3. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add Appearance settings tab with chat font-size slider and live preview
/settings/appearanceroute andAppearanceSettingsPanelwith controls for theme, time format, and chat font size (range 12–24px, default 14).ClientSettingsSchemaaschatFontSizeand applied via a--chat-font-sizeCSS variable scoped to[data-timeline-root]inindex.css, scaling all chat text tiers uniformly.ChatAppearancePreviewcomponent that renders a live preview of the chat timeline as the slider is adjusted.SimpleWorkEntryRowcomponent and replaces hard-coded text size classes throughoutMessagesTimelinewith scalabletext-chat-*utilities.Macroscope summarized 24cacb3.