From 1e6b9aaca514d2d425bcb144c52d87ba24dd2e66 Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Wed, 22 Jul 2026 07:57:14 -0700 Subject: [PATCH] Fix UI font size scaling --- studio/frontend/package-lock.json | 1 + studio/frontend/package.json | 1 + .../src/components/shutdown-dialog.tsx | 4 +- .../stores/appearance-custom-store.ts | 7 +- .../sections/charts/eval-loss-chart-card.tsx | 9 +-- .../sections/charts/grad-norm-chart-card.tsx | 7 +- .../charts/learning-rate-chart-card.tsx | 5 +- .../charts/training-loss-chart-card.tsx | 9 +-- .../features/studio/sections/charts/utils.ts | 1 + .../studio/sections/training-section.tsx | 4 +- studio/frontend/src/index.css | 5 ++ studio/frontend/vite.config.ts | 72 ++++++++++++++++++- 12 files changed, 104 insertions(+), 21 deletions(-) diff --git a/studio/frontend/package-lock.json b/studio/frontend/package-lock.json index 1d5c09ba72..b1b861840c 100644 --- a/studio/frontend/package-lock.json +++ b/studio/frontend/package-lock.json @@ -85,6 +85,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "postcss": "^8.5.15", "typescript": "~5.9.3", "typescript-eslint": "^8.55.0", "vite": "^8.0.16" diff --git a/studio/frontend/package.json b/studio/frontend/package.json index fc6911c4be..9846384133 100644 --- a/studio/frontend/package.json +++ b/studio/frontend/package.json @@ -104,6 +104,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "postcss": "^8.5.15", "typescript": "~5.9.3", "typescript-eslint": "^8.55.0", "vite": "^8.0.16" diff --git a/studio/frontend/src/components/shutdown-dialog.tsx b/studio/frontend/src/components/shutdown-dialog.tsx index e3e6ca9920..8f746c9414 100644 --- a/studio/frontend/src/components/shutdown-dialog.tsx +++ b/studio/frontend/src/components/shutdown-dialog.tsx @@ -52,8 +52,8 @@ export function ShutdownDialog({ onAfterShutdown?.(); document.body.innerHTML = `
-

Unsloth Studio has stopped.

-

You can now close this tab.

+

Unsloth Studio has stopped.

+

You can now close this tab.

`; }; diff --git a/studio/frontend/src/features/settings/stores/appearance-custom-store.ts b/studio/frontend/src/features/settings/stores/appearance-custom-store.ts index f3618ddca5..9345a91f98 100644 --- a/studio/frontend/src/features/settings/stores/appearance-custom-store.ts +++ b/studio/frontend/src/features/settings/stores/appearance-custom-store.ts @@ -510,11 +510,14 @@ export function applyCustomizationToDocument( setVar("--custom-chat-font", null); } + // Scale typography without changing the root rem or layout dimensions. if (c.uiFontSize !== null && c.uiFontSize !== UI_FONT_SIZE_RANGE.default) { - style.fontSize = `${c.uiFontSize}px`; + setVar("--ui-font-scale", String(c.uiFontSize / UI_FONT_SIZE_RANGE.default)); } else { - style.removeProperty("font-size"); + setVar("--ui-font-scale", null); } + // Clear the root size used by older builds. + style.removeProperty("font-size"); if (c.codeFontSize !== null) { el.setAttribute("data-code-font-size", ""); diff --git a/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx index b5eeac34f3..20b2eeeeab 100644 --- a/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx +++ b/studio/frontend/src/features/studio/sections/charts/eval-loss-chart-card.tsx @@ -17,6 +17,7 @@ import type { ReactElement } from "react"; import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; import { CHART_CONTAINER_CLASS, + CHART_FONT_SIZE, DEFAULT_CHART_MARGIN, DEFAULT_Y_AXIS_WIDTH, formatAxisMetric, @@ -70,7 +71,7 @@ export function EvalLossChartCard({ tickLine={false} axisLine={false} tickMargin={8} - fontSize={10} + fontSize={CHART_FONT_SIZE} tickFormatter={(value) => formatStepTick(Number(value))} interval="preserveStartEnd" /> @@ -81,7 +82,7 @@ export function EvalLossChartCard({ axisLine={false} tickMargin={8} tickCount={5} - fontSize={10} + fontSize={CHART_FONT_SIZE} width={DEFAULT_Y_AXIS_WIDTH} tickFormatter={(value) => formatAxisMetric(Number(value))} /> @@ -132,7 +133,7 @@ export function EvalLossChartCard({ tickLine={false} axisLine={false} tickMargin={8} - fontSize={10} + fontSize={CHART_FONT_SIZE} interval="preserveStartEnd" /> formatStepTick(Number(value))} interval="preserveStartEnd" /> @@ -89,7 +90,7 @@ export function GradNormChartCard({ axisLine={false} tickMargin={8} tickCount={5} - fontSize={10} + fontSize={CHART_FONT_SIZE} width={DEFAULT_Y_AXIS_WIDTH} tickFormatter={(value) => { const num = Number(value); diff --git a/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx index 1a7495b493..badc0f22dc 100644 --- a/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx +++ b/studio/frontend/src/features/studio/sections/charts/learning-rate-chart-card.tsx @@ -16,6 +16,7 @@ import { CartesianGrid, Line, LineChart, XAxis, YAxis } from "recharts"; import type { ScaleMode } from "./types"; import { CHART_CONTAINER_CLASS, + CHART_FONT_SIZE, CHART_SYNC_ID, DEFAULT_CHART_MARGIN, DEFAULT_Y_AXIS_WIDTH, @@ -76,7 +77,7 @@ export function LearningRateChartCard({ tickLine={false} axisLine={false} tickMargin={8} - fontSize={10} + fontSize={CHART_FONT_SIZE} tickFormatter={(value) => formatStepTick(Number(value))} interval="preserveStartEnd" /> @@ -87,7 +88,7 @@ export function LearningRateChartCard({ axisLine={false} tickMargin={8} tickCount={5} - fontSize={10} + fontSize={CHART_FONT_SIZE} width={DEFAULT_Y_AXIS_WIDTH} tickFormatter={(value) => { const num = Number(value); diff --git a/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx b/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx index 16113f3219..5acd90deba 100644 --- a/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx +++ b/studio/frontend/src/features/studio/sections/charts/training-loss-chart-card.tsx @@ -22,8 +22,9 @@ import { } from "recharts"; import type { ScaleMode } from "./types"; import { - CHART_SYNC_ID, CHART_CONTAINER_CLASS, + CHART_FONT_SIZE, + CHART_SYNC_ID, DEFAULT_CHART_MARGIN, DEFAULT_Y_AXIS_WIDTH, formatAxisMetric, @@ -96,7 +97,7 @@ export function TrainingLossChartCard({ tickLine={false} axisLine={false} tickMargin={8} - fontSize={10} + fontSize={CHART_FONT_SIZE} tickFormatter={(value) => formatStepTick(Number(value))} interval="preserveStartEnd" /> @@ -107,7 +108,7 @@ export function TrainingLossChartCard({ axisLine={false} tickMargin={8} tickCount={5} - fontSize={10} + fontSize={CHART_FONT_SIZE} width={DEFAULT_Y_AXIS_WIDTH} tickFormatter={(value) => { const num = Number(value); @@ -152,7 +153,7 @@ export function TrainingLossChartCard({ value: formatMetric(avgRaw), }), position: "insideTopRight", - fontSize: 10, + fontSize: CHART_FONT_SIZE, fill: "#3b82f6", }} /> diff --git a/studio/frontend/src/features/studio/sections/charts/utils.ts b/studio/frontend/src/features/studio/sections/charts/utils.ts index 4a4a1f3b48..22f1169b41 100644 --- a/studio/frontend/src/features/studio/sections/charts/utils.ts +++ b/studio/frontend/src/features/studio/sections/charts/utils.ts @@ -9,6 +9,7 @@ export const DEFAULT_VISIBLE_POINTS = 160; export const CHART_CONTAINER_CLASS = "h-[220px] w-full"; export const DEFAULT_CHART_MARGIN = { top: 4, right: 8, bottom: 0, left: 4 }; export const DEFAULT_Y_AXIS_WIDTH = 45; +export const CHART_FONT_SIZE = "calc(10px * var(--ui-font-scale, 1))"; const TRAILING_ZEROES_RE = /\.?0+$/; const NEGATIVE_ZERO_RE = /^-0$/; diff --git a/studio/frontend/src/features/studio/sections/training-section.tsx b/studio/frontend/src/features/studio/sections/training-section.tsx index 5650b9c145..d33e6bf218 100644 --- a/studio/frontend/src/features/studio/sections/training-section.tsx +++ b/studio/frontend/src/features/studio/sections/training-section.tsx @@ -140,13 +140,13 @@ export function TrainingSection() { tickLine={false} axisLine={false} tickMargin={8} - fontSize={10} + fontSize="calc(10px * var(--ui-font-scale, 1))" /> { + if (!shouldScaleFontSize(declaration.value)) { + return; + } + declaration.value = `calc(${declaration.value} * var(${UI_FONT_SCALE_VAR}, 1))`; + }, + }, +}; + +// Scale named, arbitrary, and vendor font sizes in Tailwind's generated CSS. +function uiFontScalingPlugin(): VitePlugin { + return { + name: "unsloth-ui-font-scaling", + // Run after Tailwind and before Vite converts CSS into a module. + enforce: "pre", + async transform(code, id) { + if (!CSS_MODULE_ID_PATTERN.test(id)) { + return null; + } + const result = await postcss([scaleAbsoluteFontSizes]).process(code, { + from: id, + map: false, + }); + if (result.css === code) { + return null; + } + return { code: result.css, map: null }; + }, + }; +} // https://vite.dev/config/ export default defineConfig({ - plugins: [react(), tailwindcss()], + plugins: [react(), tailwindcss(), uiFontScalingPlugin()], optimizeDeps: { include: ["@dagrejs/dagre", "@dagrejs/graphlib"], },