From 8aaf2f78ebcd7346a83dc639eff1e192bdabc4f2 Mon Sep 17 00:00:00 2001 From: Michael Han <107991372+shimmyshimmer@users.noreply.github.com> Date: Thu, 23 Jul 2026 01:26:56 -0700 Subject: [PATCH] Studio: drive UI font size through a typography scale instead of the root font size (#7359) * Studio: drive UI font size through a typography scale, not the root font size Follow up to #7355. The preference now writes --ui-font-scale (selected / 16) and a data-ui-font-size attribute on the root instead of mutating the root font size, and the applier clears any stale inline root font-size left by older builds. Because the rem base never moves, every layout-only rem-to-px conversion from #7355 is reverted to its original form: the spacing, radius and container tokens, sidebar and thread widths, grid tracks, calc margins and hub.css dimensions match pre-#7355 main again, which also restores rem-based accessibility scaling for users with a larger browser default font size. Typography scales through tokens in index.css, all exact at 16px: - The named Tailwind sizes (--text-xs through --text-4xl) multiply their defaults by the scale, so standard utilities scale - One token per design px size (--text-ui-8 ... --text-ui-34) replaces every arbitrary text-[Npx] class; leading-ui-* mirrors the exact line heights and the numeric --leading-3..10 scale as well - CSS font-size and line-height declarations multiply by the scale - Chart labels scale through a .recharts-text rule; streamdown and react-flow px text is re-based via scaled overrides; KaTeX's 1px layout trick stays fixed by design - The logo lockups keep their half-rate behavior via the scale var - The explicit Code font size remains unmultiplied Keeps the #7355 behavior fixes: color chip min width, voice select min/max widths, and the select and dropdown menus scrolling an inner viewport so their corners stay rounded. The whitespace-password and IME rename guards that merged alongside are preserved. * Studio: contract and Playwright coverage for the UI font size scale test_ui_font_scale_contract.py pins the mechanism (scale var written, root font size never mutated, tokens scaled, code font size not multiplied, the Radix select viewport owning scroll state) and guards against new raw pixel typography, with a documented allowlist for the recharts fontSize props covered by the stylesheet override and the offscreen clipboard textarea. playwright_ui_font_scale.py drives the real appearance controls: root font size fixed at 12/16/20, text and line height scale by size/16, sidebar width invariant, explicit code font size stays fixed, an overflowing dictation select scrolls its Radix viewport by keyboard and wheel, and the default restores exactly. Wired into the UI smoke workflow against the second studio boot. The thinking-compact and descender contracts move back to the rem and token forms now that layout values no longer need px pinning. --- .github/workflows/studio-ui-smoke.yml | 10 + studio/frontend/src/app/provider.tsx | 8 +- .../frontend/src/components/app-sidebar.tsx | 34 +-- .../components/assistant-ui/audio-player.tsx | 2 +- .../message-response-details-sheet.tsx | 4 +- .../assistant-ui/message-timing.tsx | 2 +- .../src/components/assistant-ui/reasoning.tsx | 2 +- .../src/components/assistant-ui/sources.tsx | 2 +- .../src/components/assistant-ui/thread.tsx | 52 ++-- .../assistant-ui/tool-ui-knowledge-base.tsx | 2 +- .../assistant-ui/tool-ui-render-html.tsx | 2 +- .../src/components/floating-monitor.tsx | 6 +- .../src/components/llama-update-banner.tsx | 8 +- .../frontend/src/components/section-card.tsx | 2 +- .../src/components/shutdown-dialog.tsx | 4 +- .../src/components/tauri/startup-screen.tsx | 2 +- .../src/components/tauri/update-banner.tsx | 16 +- .../src/components/tauri/update-screen.tsx | 4 +- .../src/components/tauri/window-titlebar.tsx | 8 +- .../frontend/src/components/ui/calendar.tsx | 4 +- studio/frontend/src/components/ui/chart.tsx | 2 +- .../src/components/ui/copyable-error-chip.tsx | 6 +- .../frontend/src/components/ui/data-table.tsx | 2 +- studio/frontend/src/components/ui/dialog.tsx | 2 +- .../src/components/ui/input-group.tsx | 4 +- studio/frontend/src/components/ui/sidebar.tsx | 8 +- .../src/components/web/update-banner.tsx | 8 +- .../frontend/src/features/auth/login-page.tsx | 2 +- .../features/chat/artifacts/artifact-card.tsx | 4 +- .../frontend/src/features/chat/chat-page.tsx | 38 +-- .../features/chat/chat-providers-dialog.tsx | 8 +- .../src/features/chat/chat-settings-sheet.tsx | 68 ++--- .../chat/components/chat-search-dialog.tsx | 6 +- .../chat/components/context-usage-bar.tsx | 4 +- .../chat/components/model-load-status.tsx | 12 +- .../components/openai-code-exec-section.tsx | 14 +- .../chat/components/project-switcher.tsx | 2 +- .../chat/hooks/use-chat-model-runtime.ts | 2 +- .../features/chat/permission-mode-select.tsx | 2 +- .../src/features/chat/projects-page.tsx | 16 +- .../prompt-storage/prompt-storage-dialog.tsx | 10 +- .../src/features/chat/thread-sidebar.tsx | 2 +- .../data-recipes/pages/data-recipes-page.tsx | 8 +- .../export/components/export-run-panel.tsx | 24 +- .../export/components/method-picker.tsx | 2 +- .../export/components/quant-picker.tsx | 10 +- .../src/features/export/export-page.tsx | 36 +-- .../features/hub/catalog/catalog-states.tsx | 32 +-- .../hub/catalog/dataset-download-section.tsx | 2 +- .../src/features/hub/catalog/dot-tag.tsx | 2 +- .../features/hub/catalog/download-card.tsx | 2 +- .../catalog/external-link-confirm-dialog.tsx | 4 +- .../hub/catalog/gguf-download-card.tsx | 10 +- .../hub/catalog/gguf-status-cards.tsx | 4 +- .../features/hub/catalog/hub-detail-view.tsx | 2 +- .../features/hub/catalog/hub-option-menu.tsx | 4 +- .../features/hub/catalog/hub-section-row.tsx | 2 +- .../hub/catalog/local-dataset-card.tsx | 2 +- .../hub/catalog/local-on-device-card.tsx | 20 +- .../src/features/hub/catalog/model-card.tsx | 6 +- .../features/hub/catalog/model-inspector.tsx | 36 +-- .../src/features/hub/catalog/model-readme.tsx | 20 +- .../hub/catalog/models-catalog-lists.tsx | 12 +- .../hub/catalog/models-catalog-rows.tsx | 30 +-- .../features/hub/catalog/models-header.tsx | 4 +- .../src/features/hub/catalog/models-table.tsx | 46 ++-- .../features/hub/catalog/models-toolbar.tsx | 10 +- .../hub/catalog/on-device-folders-dialog.tsx | 24 +- .../src/features/hub/catalog/owner-avatar.tsx | 8 +- .../hub/catalog/owner-scope-toggle.tsx | 2 +- .../features/hub/catalog/recent-searches.tsx | 6 +- .../hub/catalog/safetensors-download-card.tsx | 2 +- .../hub/catalog/sampling-settings-dialog.tsx | 10 +- .../src/features/hub/catalog/shared.tsx | 4 +- .../hub/catalog/transport-conflict-dialog.tsx | 2 +- .../features/hub/catalog/transport-toggle.tsx | 2 +- .../hub/components/hf-token-indicator.tsx | 6 +- .../features/hub/components/page-heading.tsx | 4 +- .../download-manager-panel.tsx | 8 +- .../download-progress-bar.tsx | 2 +- studio/frontend/src/features/hub/hub-page.tsx | 2 +- studio/frontend/src/features/hub/hub.css | 80 +++--- .../chat-template-editor-dialog.tsx | 4 +- .../components/model-config-page.tsx | 24 +- .../components/model-selector.tsx | 14 +- .../model-selector/folder-browser.tsx | 10 +- .../components/model-selector/pickers.tsx | 62 ++--- .../components/model-selector/pill-tabs.tsx | 2 +- .../components/native-model-chip.tsx | 2 +- .../components/native-model-drop-overlay.tsx | 4 +- .../components/steps/model-selection-step.tsx | 4 +- .../components/steps/model-type-step.tsx | 2 +- .../onboarding/components/wizard-sidebar.tsx | 8 +- .../profile-personalization-panel.tsx | 6 +- .../profile/components/user-avatar.tsx | 2 +- .../rag/components/document-preview-sheet.tsx | 2 +- .../rag/components/document-status-chip.tsx | 2 +- .../rag/components/project-sources-panel.tsx | 4 +- .../components/retrieval-settings-section.tsx | 22 +- .../recipe-studio/components/block-sheet.tsx | 4 +- .../executions/execution-sidebar.tsx | 2 +- .../components/executions/executions-view.tsx | 2 +- .../inline/inline-category-badges.tsx | 6 +- .../components/inline/inline-field.tsx | 2 +- .../components/inline/inline-llm.tsx | 2 +- .../components/inline/inline-seed.tsx | 6 +- .../components/recipe-graph-node.tsx | 4 +- .../components/recipe-studio-header.tsx | 10 +- .../runtime/execution-progress-island.tsx | 16 +- .../shared/available-references-inline.tsx | 14 +- .../models/local-recipe-model-selector.tsx | 20 +- .../recipe-studio/dialogs/preview-dialog.tsx | 2 +- .../dialogs/seed/seed-dialog.tsx | 2 +- .../tool-profile/tool-profile-dialog.tsx | 6 +- .../easy/github-crawler-easy-view.tsx | 2 +- .../recipe-studio/recipe-studio-page.tsx | 2 +- .../features/recipe-studio/utils/ui-tones.ts | 6 +- .../components/remote-code-consent-dialog.tsx | 8 +- .../settings/components/api-key-row.tsx | 4 +- .../components/api-monitor-console.tsx | 10 +- .../settings/components/create-key-form.tsx | 2 +- .../components/embedding-model-combobox.tsx | 4 +- .../settings/components/key-reveal-card.tsx | 2 +- .../settings/components/language-select.tsx | 2 +- .../components/sidebar-menu-customizer.tsx | 4 +- .../components/update-studio-instructions.tsx | 4 +- .../components/uploaded-files-dialog.tsx | 6 +- .../settings/components/usage-examples.tsx | 34 +-- .../src/features/settings/settings-dialog.tsx | 14 +- .../stores/appearance-custom-store.ts | 13 +- .../features/settings/tabs/resources-tab.tsx | 4 +- .../src/features/settings/tabs/voice-tab.tsx | 6 +- .../src/features/studio/history-card-grid.tsx | 12 +- .../studio/recent-trainings-section.tsx | 2 +- .../sections/charts/chart-settings-sheet.tsx | 2 +- .../sections/charts/eval-loss-chart-card.tsx | 8 +- .../sections/charts/grad-norm-chart-card.tsx | 4 +- .../charts/learning-rate-chart-card.tsx | 4 +- .../charts/training-loss-chart-card.tsx | 6 +- .../dataset-preview-dialog-mapping.tsx | 14 +- .../sections/dataset-preview-dialog.tsx | 20 +- .../studio/sections/dataset-section.tsx | 14 +- .../studio/sections/model-section.tsx | 16 +- .../studio/sections/params-section.tsx | 16 +- .../studio/sections/progress-section.tsx | 22 +- .../studio/sections/s3-config-form.tsx | 2 +- .../studio/sections/training-section.tsx | 4 +- .../src/features/studio/studio-page.tsx | 2 +- .../studio/training-start-overlay.tsx | 8 +- .../features/tour/components/guided-tour.tsx | 8 +- studio/frontend/src/index.css | 238 ++++++++++-------- tests/studio/playwright_ui_font_scale.py | 215 ++++++++++++++++ .../test_chat_thinking_compact_layout.py | 2 +- .../test_studio_text_descender_clipping.py | 2 +- tests/studio/test_ui_font_scale_contract.py | 147 +++++++++++ 155 files changed, 1222 insertions(+), 833 deletions(-) create mode 100644 tests/studio/playwright_ui_font_scale.py create mode 100644 tests/studio/test_ui_font_scale_contract.py diff --git a/.github/workflows/studio-ui-smoke.yml b/.github/workflows/studio-ui-smoke.yml index 0ad55ebd6d..97eb07b2d8 100644 --- a/.github/workflows/studio-ui-smoke.yml +++ b/.github/workflows/studio-ui-smoke.yml @@ -231,6 +231,15 @@ jobs: mkdir -p logs/playwright_extra python tests/studio/playwright_extra_ui.py + - name: UI font size scaling regression (Playwright) + env: + BASE_URL: http://127.0.0.1:18894 + STUDIO_PW: ${{ env.STUDIO_EXTRA_NEW_PW }} + PW_ART_DIR: logs/playwright_fontscale + run: | + mkdir -p logs/playwright_fontscale + python tests/studio/playwright_ui_font_scale.py + - name: Stop second Unsloth if: always() run: | @@ -352,6 +361,7 @@ jobs: logs/playwright logs/playwright-permissions-* logs/playwright_extra + logs/playwright_fontscale logs/playwright_modelcfg logs/playwright_ime logs/studio-permissions-*.log diff --git a/studio/frontend/src/app/provider.tsx b/studio/frontend/src/app/provider.tsx index 275c3c6623..e6c89b9cd7 100644 --- a/studio/frontend/src/app/provider.tsx +++ b/studio/frontend/src/app/provider.tsx @@ -213,7 +213,7 @@ function TauriUpdateLayer({ } return ( -
+
+
- {label} + {label} {spinner && ( )} @@ -904,7 +904,7 @@ export function AppSidebar() { ? "sidebar-row-action group-hover/project-chat-item:opacity-100 group-hover/project-chat-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto" : "sidebar-row-action group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"; const buttonClass = cn( - "sidebar-nav-btn h-[33px] cursor-pointer rounded-full pr-4 text-[0.90625rem] leading-[1.1875rem] tracking-nav font-medium", + "sidebar-nav-btn h-[33px] cursor-pointer rounded-full pr-4 text-ui-14p5 leading-ui-19 tracking-nav font-medium", // pl-3 (12px) over the content's pl-1.5 (6px) = 18px, aligning the // title with the nav items above. variant === "project" ? "pl-[39px]" : "pl-3", @@ -939,7 +939,7 @@ export function AppSidebar() { aria-label={translate("shell.dialog.renameChat.placeholder")} className={cn( // No pill or box; edit in place as plain highlighted text. - "text-foreground h-[33px] w-full border-0 bg-transparent pr-4 text-[0.90625rem] leading-[1.1875rem] font-medium tracking-nav outline-none", + "text-foreground h-[33px] w-full border-0 bg-transparent pr-4 text-ui-14p5 leading-ui-19 font-medium tracking-nav outline-none", variant === "project" ? "pl-[39px]" : "pl-3", )} /> @@ -1185,16 +1185,16 @@ export function AppSidebar() { tabIndex={chatDisabled ? -1 : undefined} > {/* Logo lockup follows the UI font size at half rate: - base + (root - 16px) / 2, written as (base - 8)px + 0.5rem. */} + base + (root scale - 1) * 8px. Exact base sizes at 16px. */} Unsloth - + unsloth - + {t("shell.beta")} @@ -1221,7 +1221,7 @@ export function AppSidebar() { hidden={isMobile} > {t("shell.navigation.search")} - + {isMacPlatform ? "⌘K" : "Ctrl+K"} @@ -1538,7 +1538,7 @@ export function AppSidebar() { className="sidebar-nav-btn h-[33px] rounded-full gap-[8.5px] pl-3 pr-2.5 font-medium group-hover/recent-item:pr-16 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-8" > - {project.name} + {project.name} {/* New chat in this project */}
@@ -1832,11 +1832,11 @@ export function AppSidebar() { />
- + {t("shell.updateAvailable")} {updateVersion && ( - + v{updateVersion} )} @@ -1873,8 +1873,8 @@ export function AppSidebar() { {/* min-w-0 so long names truncate instead of overflowing; pr on the button reserves room for the settings cog */}
- {displayTitle} - Unsloth + {displayTitle} + Unsloth
@@ -1882,7 +1882,7 @@ export function AppSidebar() { side="top" align="center" sideOffset={8} - className="app-user-menu menu-soft-surface-up ring-0 w-[256px] px-2.5 py-2.5 font-heading rounded-[20px] border-0" + className="app-user-menu menu-soft-surface-up ring-0 w-[16rem] px-2.5 py-2.5 font-heading rounded-[20px] border-0" > = ({ src }) => { onChange={handleSeek} className="h-1.5 w-full cursor-pointer accent-primary" /> -
+
{formatTime(progress)} {formatTime(duration)}
diff --git a/studio/frontend/src/components/assistant-ui/message-response-details-sheet.tsx b/studio/frontend/src/components/assistant-ui/message-response-details-sheet.tsx index 5dd4f1c91d..8b2e9e37ea 100644 --- a/studio/frontend/src/components/assistant-ui/message-response-details-sheet.tsx +++ b/studio/frontend/src/components/assistant-ui/message-response-details-sheet.tsx @@ -207,7 +207,7 @@ function DetailRow({ }) { if (value == null || value === "") return null; return ( -
+
{label} diff --git a/studio/frontend/src/components/assistant-ui/message-timing.tsx b/studio/frontend/src/components/assistant-ui/message-timing.tsx index c602a776cf..e0a111820b 100644 --- a/studio/frontend/src/components/assistant-ui/message-timing.tsx +++ b/studio/frontend/src/components/assistant-ui/message-timing.tsx @@ -86,7 +86,7 @@ export const MessageTiming: FC<{ data-slot="message-timing-trigger" aria-label="Message timing" className={cn( - "flex items-center rounded-[10px] p-1 font-mono text-chat-icon-fg text-[0.8125rem] tabular-nums transition-colors hover:bg-chat-icon-bg-hover hover:text-chat-icon-fg-hover", + "flex items-center rounded-[10px] p-1 font-mono text-chat-icon-fg text-ui-13 tabular-nums transition-colors hover:bg-chat-icon-bg-hover hover:text-chat-icon-fg-hover", className, )} > diff --git a/studio/frontend/src/components/assistant-ui/reasoning.tsx b/studio/frontend/src/components/assistant-ui/reasoning.tsx index d869cc93ef..97891e9358 100644 --- a/studio/frontend/src/components/assistant-ui/reasoning.tsx +++ b/studio/frontend/src/components/assistant-ui/reasoning.tsx @@ -163,7 +163,7 @@ function ReasoningContent({

Generated image

{overlay.metadata ? ( -

+

{overlay.metadata}

) : null} @@ -1390,7 +1390,7 @@ const ComposerAnimated: FC<{ disableQueue?: boolean; }> = ({ disabled, threadId, menuSide, disableQueue }) => { return ( -
+
writeComposerDraft(draftKey, composerText), 300); return () => clearTimeout(t); }, [composerText, draftKey]); - // Without this the restore effect above puts the sent text back when the - // runtime rebinds on the first message. - const draftKeyRef = useRef(draftKey); - useEffect(() => { - draftKeyRef.current = draftKey; - }, [draftKey]); - const clearStoredDraft = useCallback(() => { - const key = draftKeyRef.current; - if (key) { - writeComposerDraft(key, ""); - } - }, []); // react-textarea-autosize re-measures only on value change or window resize, // not on the width swap from expanding, so it keeps the taller height and // leaves a stray blank row. Nudge a resize whenever input width changes. @@ -1732,10 +1720,9 @@ const Composer: FC<{ setPendingSend(false); dismissWaitToast(); if (text.trim().length > 0 || attachments.length > 0) { - clearStoredDraft(); aui.composer().send(); } - }, [pendingSend, indexingActive, aui, clearStoredDraft, dismissWaitToast]); + }, [pendingSend, indexingActive, aui, dismissWaitToast]); // Drop any queued send + toast on unmount (e.g. thread switch). useEffect( @@ -1778,7 +1765,6 @@ const Composer: FC<{ flushResourcesSync(() => { aui.composer().setText(""); }); - clearStoredDraft(); startPromptQueue( [queuedPrompt], createPromptQueueTarget(), @@ -1812,7 +1798,6 @@ const Composer: FC<{ closeOverlay(); return; } - clearStoredDraft(); setImageToolsEnabled(true); setPendingImageEditReference({ threadId: overlay.threadId ?? referenceThreadId, @@ -1830,15 +1815,11 @@ const Composer: FC<{ ); }); closeOverlay(); - return; } - - clearStoredDraft(); }, [ aui, canQueueCurrentPrompt, - clearStoredDraft, closeOverlay, composerText, createPromptQueueTarget, @@ -1940,7 +1921,6 @@ const Composer: FC<{ flushResourcesSync(() => { aui.composer().setText(""); }); - clearStoredDraft(); startPromptQueue([queuedPrompt], createPromptQueueTarget(), true); }} onSendClick={interceptSend} @@ -3317,7 +3297,7 @@ const PromptQueueStack: FC<{ queueThreadIds: string[] }> = ({
) : ( -
+
@@ -3329,7 +3309,7 @@ const PromptQueueStack: FC<{ queueThreadIds: string[] }> = ({ type="button" variant="ghost" size="sm" - className="h-7 w-[84px] justify-center gap-1 px-0 text-sm font-normal text-muted-foreground/80 hover:text-foreground" + className="h-7 w-[5.25rem] justify-center gap-1 px-0 text-sm font-normal text-muted-foreground/80 hover:text-foreground" onClick={() => startEditing(item)} > @@ -3565,14 +3545,14 @@ const DiffusionCanvas: FC = () => { canvas.total > 0 ? `step ${canvas.step + 1}/${canvas.total}` : "denoising"; return (
-
+
Denoising block {canvas.block + 1} - {stepLabel}
-
+      
         {canvas.text}
       
@@ -3646,7 +3626,7 @@ const AssistantMessage: FC = () => { return (
@@ -3676,7 +3656,7 @@ const AssistantMessage: FC = () => { ) : ( <>
- +
@@ -3759,7 +3739,7 @@ const ForkCountBadge: FC = () => { if (count <= 0) return null; return ( @@ -4084,7 +4064,7 @@ const UserMessageAudio: FC = () => { const UserMessage: FC = () => { return ( @@ -4195,7 +4175,7 @@ const BranchPicker: FC = ({ = ({ - + / diff --git a/studio/frontend/src/components/assistant-ui/tool-ui-knowledge-base.tsx b/studio/frontend/src/components/assistant-ui/tool-ui-knowledge-base.tsx index 4fbaa227bd..ec24060072 100644 --- a/studio/frontend/src/components/assistant-ui/tool-ui-knowledge-base.tsx +++ b/studio/frontend/src/components/assistant-ui/tool-ui-knowledge-base.tsx @@ -48,7 +48,7 @@ export function CitationBadge({ - + {errorText ?? (isStaleGeneratingArtifact ? "Refresh stopped this preview" diff --git a/studio/frontend/src/components/floating-monitor.tsx b/studio/frontend/src/components/floating-monitor.tsx index fb1ead3e63..80501a518c 100644 --- a/studio/frontend/src/components/floating-monitor.tsx +++ b/studio/frontend/src/components/floating-monitor.tsx @@ -102,7 +102,7 @@ export function FloatingMonitor() { initial={{ opacity: 0, scale: 0.9 }} animate={{ opacity: 1, scale: 1 }} exit={{ opacity: 0, scale: 0.9 }} - className="settings-surface fixed bottom-4 right-4 w-64 max-w-[calc(100vw-32px)] resize overflow-hidden rounded-xl border border-border/70 p-3 shadow-border ring-0 backdrop-blur-sm pointer-events-auto cursor-default select-none" + className="settings-surface fixed bottom-4 right-4 w-64 max-w-[calc(100vw-2rem)] resize overflow-hidden rounded-xl border border-border/70 p-3 shadow-border ring-0 backdrop-blur-sm pointer-events-auto cursor-default select-none" >
@@ -139,7 +139,7 @@ export function FloatingMonitor() { className="space-y-3 overflow-hidden" >
-
+
{t("settings.resources.liveMonitor.ram")} -
+
{t("settings.resources.liveMonitor.vram")}{" "} {devices.length > 1 diff --git a/studio/frontend/src/components/llama-update-banner.tsx b/studio/frontend/src/components/llama-update-banner.tsx index 25c413ad61..5baca8ffe2 100644 --- a/studio/frontend/src/components/llama-update-banner.tsx +++ b/studio/frontend/src/components/llama-update-banner.tsx @@ -131,7 +131,7 @@ export function LlamaUpdateBanner({

-

+

{sizeLabel ? `${sizeLabel} download · ` : ""}No restart needed after update

@@ -209,7 +209,7 @@ export function LlamaUpdateBanner({
diff --git a/studio/frontend/src/components/tauri/update-screen.tsx b/studio/frontend/src/components/tauri/update-screen.tsx index 64f2e95a87..d37e6873ae 100644 --- a/studio/frontend/src/components/tauri/update-screen.tsx +++ b/studio/frontend/src/components/tauri/update-screen.tsx @@ -72,7 +72,7 @@ function LogViewer({ logs }: { logs: string[] }) { return (
{logs.map((line, i) => (
@@ -197,7 +197,7 @@ export function UpdateScreen({ readOnly value={manualReport} onFocus={(event) => event.currentTarget.select()} - className="mt-2 h-32 w-full max-w-xl resize-none rounded-lg border border-border/50 bg-muted/30 p-2 font-mono text-[0.625rem] text-muted-foreground" + className="mt-2 h-32 w-full max-w-xl resize-none rounded-lg border border-border/50 bg-muted/30 p-2 font-mono text-ui-10 text-muted-foreground" /> )} diff --git a/studio/frontend/src/components/tauri/window-titlebar.tsx b/studio/frontend/src/components/tauri/window-titlebar.tsx index 66cc2e1b41..6a0ff8741a 100644 --- a/studio/frontend/src/components/tauri/window-titlebar.tsx +++ b/studio/frontend/src/components/tauri/window-titlebar.tsx @@ -112,8 +112,8 @@ export function WindowTitlebar({ const { pinned, togglePinned } = useSidebarPin(); const sidebarWidth = showSidebarSurface ? pinned - ? "var(--studio-sidebar-expanded-width,280px)" - : "var(--studio-sidebar-collapsed-width,48px)" + ? "var(--studio-sidebar-expanded-width,17.5rem)" + : "var(--studio-sidebar-collapsed-width,3rem)" : "0px"; const contentBorderLeft = pinned ? `calc(${sidebarWidth} + 12px)` : "0px"; @@ -273,7 +273,7 @@ export function WindowTitlebar({ draggable={false} className="size-5 shrink-0 rounded-[6px] object-cover" /> - + Unsloth Studio
@@ -325,7 +325,7 @@ export function WindowTitlebar({ className="pointer-events-auto absolute top-0 h-full" style={{ left: sidebarWidth, - right: "calc(var(--studio-window-control-inset,112px) + 8px)", + right: "calc(var(--studio-window-control-inset,112px) + 0.5rem)", }} onMouseDown={handleDragMouseDown} onDoubleClick={handleDragDoubleClick} diff --git a/studio/frontend/src/components/ui/calendar.tsx b/studio/frontend/src/components/ui/calendar.tsx index 6ace64bed6..6ea80b9c88 100644 --- a/studio/frontend/src/components/ui/calendar.tsx +++ b/studio/frontend/src/components/ui/calendar.tsx @@ -93,7 +93,7 @@ function Calendar({ table: "w-full border-collapse", weekdays: cn("flex", defaultClassNames.weekdays), weekday: cn( - "text-muted-foreground rounded-(--cell-radius) flex-1 font-normal text-[0.8rem] select-none", + "text-muted-foreground rounded-(--cell-radius) flex-1 font-normal text-[calc(0.8rem*var(--ui-font-scale,1))] select-none", defaultClassNames.weekday, ), week: cn("flex w-full mt-2", defaultClassNames.week), @@ -102,7 +102,7 @@ function Calendar({ defaultClassNames.week_number_header, ), week_number: cn( - "text-[0.8rem] select-none text-muted-foreground", + "text-[calc(0.8rem*var(--ui-font-scale,1))] select-none text-muted-foreground", defaultClassNames.week_number, ), day: cn( diff --git a/studio/frontend/src/components/ui/chart.tsx b/studio/frontend/src/components/ui/chart.tsx index 25dc88d1cd..32da410bb5 100644 --- a/studio/frontend/src/components/ui/chart.tsx +++ b/studio/frontend/src/components/ui/chart.tsx @@ -246,7 +246,7 @@ function ChartTooltipContent({ return (
diff --git a/studio/frontend/src/components/ui/copyable-error-chip.tsx b/studio/frontend/src/components/ui/copyable-error-chip.tsx index 6f21b6d829..b9759f111c 100644 --- a/studio/frontend/src/components/ui/copyable-error-chip.tsx +++ b/studio/frontend/src/components/ui/copyable-error-chip.tsx @@ -53,7 +53,7 @@ export function CopyableErrorChip({
diff --git a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx index 82a236a387..531b17522d 100644 --- a/studio/frontend/src/features/chat/artifacts/artifact-card.tsx +++ b/studio/frontend/src/features/chat/artifacts/artifact-card.tsx @@ -145,12 +145,12 @@ export function ArtifactCard({ {isCode ? "HTML Code" : artifact.title} - + HTML canvas {isStreaming && !isCode ? ( - + Generating ) : null} diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx index 16176f0f5f..e46ea0ac46 100644 --- a/studio/frontend/src/features/chat/chat-page.tsx +++ b/studio/frontend/src/features/chat/chat-page.tsx @@ -576,7 +576,7 @@ function CompareShell({ {children}
-
{composer}
+
{composer}
{showModelDisclaimer && (

LLMs can make mistakes. Double-check responses. @@ -651,7 +651,7 @@ const LoraCompareContent = memo(function LoraCompareContent({ handleName="base" header={

- + Base Model
@@ -665,8 +665,8 @@ const LoraCompareContent = memo(function LoraCompareContent({ handleName="lora" borderClassName="border-t border-border/60 md:border-t-0 md:border-l" header={ -
- +
+ Fine-tuned
@@ -721,8 +721,8 @@ function GeneralCompareHeader({ side === "left" ? pinned ? "pl-12 pr-3 md:pl-2" - : "pl-12 pr-3 md:pl-[calc(8px+max(0px,var(--studio-mac-traffic-light-inset,0px)-var(--sidebar-width-icon,48px)))]" - : "pl-3 pr-[calc(48px+var(--studio-chat-header-right-inset,var(--studio-window-control-inset,0px)))]", + : "pl-12 pr-3 md:pl-[calc(0.5rem+max(0px,var(--studio-mac-traffic-light-inset,0px)-var(--sidebar-width-icon,3rem)))]" + : "pl-3 pr-[calc(3rem+var(--studio-chat-header-right-inset,var(--studio-window-control-inset,0px)))]", )} > {/* Slightly narrower than the composer max; every block shares this. */} -
+
-

+

{projectName}

@@ -1349,7 +1349,7 @@ function ProjectLanding({ type="button" onClick={() => setProjectTab("chats")} data-active={projectTab === "chats"} - className="h-10 rounded-full px-5 text-[0.875rem] font-semibold transition-colors data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=false]:text-muted-foreground data-[active=false]:hover:bg-nav-surface-hover" + className="h-10 rounded-full px-5 text-ui-14 font-semibold transition-colors data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=false]:text-muted-foreground data-[active=false]:hover:bg-nav-surface-hover" > Chats @@ -1357,7 +1357,7 @@ function ProjectLanding({ type="button" onClick={() => setProjectTab("sources")} data-active={projectTab === "sources"} - className="h-10 rounded-full px-5 text-[0.875rem] font-semibold transition-colors data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=false]:text-muted-foreground data-[active=false]:hover:bg-nav-surface-hover" + className="h-10 rounded-full px-5 text-ui-14 font-semibold transition-colors data-[active=true]:bg-muted data-[active=true]:text-foreground data-[active=false]:text-muted-foreground data-[active=false]:hover:bg-nav-surface-hover" > Sources @@ -1425,7 +1425,7 @@ function ProjectLanding({ onFocus={(event) => event.currentTarget.select()} maxLength={120} aria-label="Rename chat" - className="w-full border-0 bg-transparent text-[0.9375rem] font-semibold leading-5 text-foreground outline-none" + className="w-full border-0 bg-transparent text-ui-15 font-semibold leading-5 text-foreground outline-none" />
@@ -1450,11 +1450,11 @@ function ProjectLanding({ className="flex min-h-[58px] min-w-0 flex-1 items-center gap-4 rounded-full px-4 py-2 text-left" >
-
+
{displayTitle}
- + {preview?.date ?? formatProjectChatDate(item.createdAt)} @@ -3113,14 +3113,14 @@ export function ChatPage({ )}
@@ -3149,7 +3149,7 @@ export function ChatPage({ /> )} {incognito && view.mode === "single" && ( -
+

When off, all connections are disabled.

@@ -1616,7 +1616,7 @@ export function ChatProvidersSettings({ {provider.name} - + {provider.models.length}{" "} {provider.models.length === 1 ? "model" : "models"} @@ -1631,7 +1631,7 @@ export function ChatProvidersSettings({ ) : null}
{modelSummary} @@ -1702,7 +1702,7 @@ export function ChatProvidersDialog({ Connections diff --git a/studio/frontend/src/features/chat/chat-settings-sheet.tsx b/studio/frontend/src/features/chat/chat-settings-sheet.tsx index 99d697f619..672ab2b2d3 100644 --- a/studio/frontend/src/features/chat/chat-settings-sheet.tsx +++ b/studio/frontend/src/features/chat/chat-settings-sheet.tsx @@ -141,7 +141,7 @@ export function ParamSlider({
- + {label} {info && {info}} @@ -249,7 +249,7 @@ function CollapsibleSection({ }; const headerClasses = cn( - "flex w-full items-center justify-between text-[0.75rem] font-medium normal-case tracking-[0.04em] text-nav-fg-muted transition-colors focus-visible:outline-none focus-visible:ring-0", + "flex w-full items-center justify-between text-ui-12 font-medium normal-case tracking-[0.04em] text-nav-fg-muted transition-colors focus-visible:outline-none focus-visible:ring-0", first ? "pt-4 pb-5" : "py-5", ); @@ -695,12 +695,12 @@ export function ChatSettingsPanel({ {/* Header is outside the scroll area so the scrollbar never shifts the close button. */}
{isMobile ? ( - + Run settings ) : ( <> - + Run settings @@ -740,7 +740,7 @@ export function ChatSettingsPanel({
{modelConfig} {showSpecFallback && ( -
+

{specFallbackReason === "mla_mtp_disabled" ? "MTP is disabled by default for this model architecture because it currently runs slower than standard decoding. Choose MTP in the model picker to force it." @@ -757,7 +757,7 @@ export function ChatSettingsPanel({ {mtpUpdatable && llamaUpdateStatus?.update_available && (

-

+

Use this for longer edits. Save writes back to the active configuration only. Insert variables with {"{{ env }}"}.

@@ -1230,16 +1230,16 @@ export function ChatSettingsPanel({
-
+
Prompt variables
-

+

Define values as JSON below, then use each key in your prompt, like {"{{ env }}"}.

- + Built-in, fill in automatically
@@ -1247,7 +1247,7 @@ export function ChatSettingsPanel({ {token} @@ -1272,11 +1272,11 @@ export function ChatSettingsPanel({ aria-invalid={Boolean(systemVariablesError)} /> {systemVariablesError ? ( -

+

{systemVariablesError}

) : ( -

+

Names you don't define are left unchanged, so a stray {" {{ typo }} "}stays visible in the prompt.

@@ -1288,7 +1288,7 @@ export function ChatSettingsPanel({ onChange={(event) => setSystemPromptDraft(event.target.value)} placeholder="You are a helpful assistant..." fieldSizing="fixed" - className="min-h-[320px] max-h-[48vh] overflow-y-auto border-0 text-sm leading-6 corner-squircle focus-visible:ring-0" + className="min-h-[20rem] max-h-[48vh] overflow-y-auto border-0 text-sm leading-6 corner-squircle focus-visible:ring-0" rows={14} />
@@ -1333,7 +1333,7 @@ export function ChatSettingsPanel({ if (isMobile) { return ( - + Run settings Chat inference settings @@ -1351,7 +1351,7 @@ export function ChatSettingsPanel({ data-tour="chat-settings" className={cn( "relative z-50 shrink-0 overflow-hidden bg-panel-surface text-panel-surface-fg font-heading", - open ? "w-[272px] border-l border-sidebar-border" : "w-0", + open ? "w-[17rem] border-l border-sidebar-border" : "w-0", )} style={{ height: "calc(100% - var(--studio-custom-titlebar-height, 0px))", @@ -1426,7 +1426,7 @@ function AutoHealToolCallsToggle() { return (
- + Auto-Healing Tool Calls @@ -1450,7 +1450,7 @@ function NudgeToolCallsToggle() { return (
- + Nudge Tool Calls @@ -1475,7 +1475,7 @@ function ConfirmToolCallsToggle() {
- + Confirm tool calls @@ -1487,7 +1487,7 @@ function ConfirmToolCallsToggle() {
{permissionMode === "full" ? ( - + Overridden by Full access ) : null} @@ -1508,7 +1508,7 @@ function BypassPermissionsToggle() { return (
- + Tool permissions @@ -1517,9 +1517,9 @@ function BypassPermissionsToggle() {
{/* Full width, styled like the panel selects/preset input. */} - + {permissionMode === "full" ? ( - + Tool calls run with no confirmation and no sandbox. ) : null} diff --git a/studio/frontend/src/features/chat/components/chat-search-dialog.tsx b/studio/frontend/src/features/chat/components/chat-search-dialog.tsx index ea95040f44..7bf1b4b2aa 100644 --- a/studio/frontend/src/features/chat/components/chat-search-dialog.tsx +++ b/studio/frontend/src/features/chat/components/chat-search-dialog.tsx @@ -83,7 +83,7 @@ export function ChatSearchDialog() { @@ -143,10 +143,10 @@ export function ChatSearchDialog() { strokeWidth={2} className="size-4 shrink-0 text-muted-foreground" /> - + {item.title || "Untitled chat"} - + {formatRelative(item.createdAt)} diff --git a/studio/frontend/src/features/chat/components/context-usage-bar.tsx b/studio/frontend/src/features/chat/components/context-usage-bar.tsx index eeacef66df..9c80ce6b11 100644 --- a/studio/frontend/src/features/chat/components/context-usage-bar.tsx +++ b/studio/frontend/src/features/chat/components/context-usage-bar.tsx @@ -71,7 +71,7 @@ export const ContextUsageBar: FC<{ : `Token usage: ${formatTokenCount(used)} tokens` } className={cn( - "flex items-center gap-2 rounded-[10px] px-2.5 py-1 font-mono text-chat-icon-fg text-[0.8125rem] tabular-nums transition-colors hover:bg-chat-icon-bg-hover hover:text-chat-icon-fg-hover", + "flex items-center gap-2 rounded-[10px] px-2.5 py-1 font-mono text-chat-icon-fg text-ui-13 tabular-nums transition-colors hover:bg-chat-icon-bg-hover hover:text-chat-icon-fg-hover", className, )} > @@ -149,7 +149,7 @@ export const ContextUsageBar: FC<{
{hasKnownLimit && percent !== null && percent > 85 ? ( -
+
Close to the context limit. Generation will stop at 100%. Increase Context Length in the chat Settings panel to keep going. diff --git a/studio/frontend/src/features/chat/components/model-load-status.tsx b/studio/frontend/src/features/chat/components/model-load-status.tsx index 613b5c260b..6e22c6de4b 100644 --- a/studio/frontend/src/features/chat/components/model-load-status.tsx +++ b/studio/frontend/src/features/chat/components/model-load-status.tsx @@ -54,14 +54,14 @@ export function ModelLoadDescription({ {title ?

{title}

: null} {hasProgress ? (
-
+
{labelPrimary} {Math.round(clampProgress(progressPercent))}%
{labelSecondary ? ( -
+
{labelSecondary}
) : null} @@ -96,18 +96,18 @@ export function ModelLoadInlineStatus({ const hasProgress = typeof progressPercent === "number"; return ( -
+
{label}
{hasProgress ? (
-
+
{/* Tight inline layout: show only the primary (bytes) chunk; @@ -124,7 +124,7 @@ export function ModelLoadInlineStatus({ type="button" size="xs" variant="outline" - className="shrink-0 text-[0.6875rem]" + className="shrink-0 text-ui-11" onClick={onStop} > Stop diff --git a/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx b/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx index 04c88e4eba..4da9774192 100644 --- a/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx +++ b/studio/frontend/src/features/chat/components/openai-code-exec-section.tsx @@ -435,7 +435,7 @@ export function OpenAICodeExecSection({
@@ -459,7 +459,7 @@ export function OpenAICodeExecSection({ ACTIVE pill marks which one (no separate picker). */}
- + Containers
diff --git a/studio/frontend/src/features/chat/components/project-switcher.tsx b/studio/frontend/src/features/chat/components/project-switcher.tsx index 2a170e5a39..a37df882b4 100644 --- a/studio/frontend/src/features/chat/components/project-switcher.tsx +++ b/studio/frontend/src/features/chat/components/project-switcher.tsx @@ -57,7 +57,7 @@ export function ProjectSwitcher({ className="size-icon shrink-0 text-foreground/70" /> - + {label} diff --git a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts index 4b3f57b368..76a310ac33 100644 --- a/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts +++ b/studio/frontend/src/features/chat/hooks/use-chat-model-runtime.ts @@ -111,7 +111,7 @@ const MODEL_LOAD_TOAST_CLASSNAMES = { title: "leading-5", description: "mt-0 w-full", cancelButton: - "!h-auto !rounded-none !border-0 !bg-transparent !px-1 !text-[0.6875rem] !font-normal !text-muted-foreground hover:!bg-transparent hover:!text-destructive focus-visible:!text-destructive", + "!h-auto !rounded-none !border-0 !bg-transparent !px-1 !text-ui-11 !font-normal !text-muted-foreground hover:!bg-transparent hover:!text-destructive focus-visible:!text-destructive", } as const; const MODEL_LOADED_TOAST_CLASSNAMES = { diff --git a/studio/frontend/src/features/chat/permission-mode-select.tsx b/studio/frontend/src/features/chat/permission-mode-select.tsx index 7e0ecb0c7e..f3e1594795 100644 --- a/studio/frontend/src/features/chat/permission-mode-select.tsx +++ b/studio/frontend/src/features/chat/permission-mode-select.tsx @@ -120,7 +120,7 @@ export function PermissionModeMenuItems({ > - {option.label} + {option.label} {option.description} diff --git a/studio/frontend/src/features/chat/projects-page.tsx b/studio/frontend/src/features/chat/projects-page.tsx index 494368faec..e20e517787 100644 --- a/studio/frontend/src/features/chat/projects-page.tsx +++ b/studio/frontend/src/features/chat/projects-page.tsx @@ -367,7 +367,7 @@ export function ProjectsPage() { }} />
-

+

Projects

@@ -419,7 +419,7 @@ export function ProjectsPage() { Export All Projects - + Combined {EXPORT_FORMATS_LIST.map(({ fmt, label }) => ( @@ -430,7 +430,7 @@ export function ProjectsPage() { - + Per chat {EXPORT_FORMATS_LIST.map(({ fmt, label }) => ( @@ -445,7 +445,7 @@ export function ProjectsPage() { Export Projects + Recents - + Combined {EXPORT_FORMATS_LIST.map(({ fmt, label }) => ( @@ -456,7 +456,7 @@ export function ProjectsPage() { - + Per chat {EXPORT_FORMATS_LIST.map(({ fmt, label }) => ( @@ -482,7 +482,7 @@ export function ProjectsPage() { {!hasLoaded ? (
-
+
Name Modified @@ -526,7 +526,7 @@ export function ProjectsPage() {
{/* Column header. Name starts at the folder icon's left edge; the right-anchored columns keep Modified over its values. */} -
+
Name Modified @@ -571,7 +571,7 @@ export function ProjectsPage() { className="size-5" /> - + {project.name} diff --git a/studio/frontend/src/features/chat/prompt-storage/prompt-storage-dialog.tsx b/studio/frontend/src/features/chat/prompt-storage/prompt-storage-dialog.tsx index 4b815a7695..7295605553 100644 --- a/studio/frontend/src/features/chat/prompt-storage/prompt-storage-dialog.tsx +++ b/studio/frontend/src/features/chat/prompt-storage/prompt-storage-dialog.tsx @@ -1341,7 +1341,7 @@ function ExportModal({ {/* */}
-

+

Export as

@@ -1390,7 +1390,7 @@ function ExportModal({

ShareGPT format for Unsloth fine-tuning

- + {`{"conversations":[{"from":"human","value":"..."},{"from":"gpt","value":""}]}`}
@@ -1400,7 +1400,7 @@ function ExportModal({ {/* */}
-

+

Format

@@ -1730,7 +1730,7 @@ function PromptListCard({
{entry.name} - + {entry.items.length}
@@ -1779,7 +1779,7 @@ function PromptListCard({

))} {entry.items.length > 3 && ( -

+

+{entry.items.length - 3} more

)} diff --git a/studio/frontend/src/features/chat/thread-sidebar.tsx b/studio/frontend/src/features/chat/thread-sidebar.tsx index 4e2765bebd..bd1fb9d87c 100644 --- a/studio/frontend/src/features/chat/thread-sidebar.tsx +++ b/studio/frontend/src/features/chat/thread-sidebar.tsx @@ -266,7 +266,7 @@ export function ThreadSidebar({ > {item.isFork ? ( fork diff --git a/studio/frontend/src/features/data-recipes/pages/data-recipes-page.tsx b/studio/frontend/src/features/data-recipes/pages/data-recipes-page.tsx index 27584a646f..7716671151 100644 --- a/studio/frontend/src/features/data-recipes/pages/data-recipes-page.tsx +++ b/studio/frontend/src/features/data-recipes/pages/data-recipes-page.tsx @@ -280,7 +280,7 @@ function LearningRecipeCards({ {badge} @@ -288,7 +288,7 @@ function LearningRecipeCards({ {extraLearningBadgeCount > 0 ? ( +{extraLearningBadgeCount} @@ -296,7 +296,7 @@ function LearningRecipeCards({ {isReady ? null : ( Soon @@ -403,7 +403,7 @@ export function DataRecipesPage(): ReactElement {
-

+

Data Recipes

diff --git a/studio/frontend/src/features/export/components/export-run-panel.tsx b/studio/frontend/src/features/export/components/export-run-panel.tsx index 86c82935d6..9d938a5ee3 100644 --- a/studio/frontend/src/features/export/components/export-run-panel.tsx +++ b/studio/frontend/src/features/export/components/export-run-panel.tsx @@ -278,7 +278,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) {

onSaveDirectoryChange(e.target.value)} spellCheck={false} @@ -303,7 +303,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { Browse
-

+

{saveDirectory !== defaultSaveDirectory ? ( <>Default: {defaultSaveDirectory} ) : ( @@ -350,7 +350,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { href="https://huggingface.co/settings/tokens" target="_blank" rel="noopener noreferrer" - className="flex items-center gap-1 text-[0.6875rem] text-emerald-600 hover:text-emerald-700 transition-colors" + className="flex items-center gap-1 text-ui-11 text-emerald-600 hover:text-emerald-700 transition-colors" > Get token @@ -369,7 +369,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { onChange={(e) => onHfTokenChange(e.target.value)} /> -

+

Leave empty if already logged in via CLI.

@@ -427,7 +427,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { ) : null} {o.path} @@ -503,11 +503,11 @@ export function ExportRunPanel(props: ExportRunPanelProps) { {showProgress && (
- + {PHASE_LABELS[run.phase] ?? run.phase} {summaryMethod === "gguf" && run.quantTotal > 1 && ( - + Quant{" "} {Math.min( run.quantIndex + (isExporting ? 1 : 0), @@ -516,10 +516,10 @@ export function ExportRunPanel(props: ExportRunPanelProps) { of {run.quantTotal} )} - + {progress}% - + {formatElapsed(elapsedSeconds)}
@@ -536,7 +536,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { /> {run.stage && (

{run.stage} @@ -552,7 +552,7 @@ export function ExportRunPanel(props: ExportRunPanelProps) { -

+
{run.logLines.length === 0 ? (
diff --git a/studio/frontend/src/features/export/components/method-picker.tsx b/studio/frontend/src/features/export/components/method-picker.tsx index e240fd44ca..0ab72b0b4a 100644 --- a/studio/frontend/src/features/export/components/method-picker.tsx +++ b/studio/frontend/src/features/export/components/method-picker.tsx @@ -123,7 +123,7 @@ export function MethodPicker({ value, onChange, disabledMethods = [], disabledRe {m.badge && ( {m.badge} diff --git a/studio/frontend/src/features/export/components/quant-picker.tsx b/studio/frontend/src/features/export/components/quant-picker.tsx index 688e5fb87f..b8c8e0fc77 100644 --- a/studio/frontend/src/features/export/components/quant-picker.tsx +++ b/studio/frontend/src/features/export/components/quant-picker.tsx @@ -61,7 +61,7 @@ export function QuantPicker({ value, onChange, sizes }: QuantPickerProps) { - + — select one or more
@@ -90,10 +90,10 @@ export function QuantPicker({ value, onChange, sizes }: QuantPickerProps) { )} {q.label} {sizeLabel && ( - {sizeLabel} + {sizeLabel} )} {q.recommended && !active && ( - + rec )} @@ -103,13 +103,13 @@ export function QuantPicker({ value, onChange, sizes }: QuantPickerProps) {
{value.length > 0 && (
- + {value.length} selected diff --git a/studio/frontend/src/features/export/export-page.tsx b/studio/frontend/src/features/export/export-page.tsx index 80235846d1..fc30555253 100644 --- a/studio/frontend/src/features/export/export-page.tsx +++ b/studio/frontend/src/features/export/export-page.tsx @@ -895,7 +895,7 @@ export function ExportPage() {
-

+

Export Model

@@ -964,21 +964,21 @@ export function ExportPage() { Local Model Fine-tuned Hugging Face @@ -1289,7 +1289,7 @@ export function ExportPage() { {model?.display_name ?? id} - + {source} @@ -1300,15 +1300,15 @@ export function ExportPage() {

{isLoadingLocalModels ? ( -

+

Scanning local models...

) : localModelsError ? ( -

+

{localModelsError}

) : ( -

+

{exportableLocalModels.length > 0 ? `${exportableLocalModels.length} local/cached models found` : "No local models found. Enter path manually."} @@ -1318,7 +1318,7 @@ export function ExportPage() { )}

-

+

Direct model exports currently support GGUF only.

@@ -1327,7 +1327,7 @@ export function ExportPage() { {sourceMode === "checkpoint" && (
- + Training Info
@@ -1374,7 +1374,7 @@ export function ExportPage() { key={step} className="flex items-start gap-2 text-xs text-muted-foreground" > - + {i + 1} {step} @@ -1422,7 +1422,7 @@ export function ExportPage() {
Precision
- + — select one or more
@@ -1479,7 +1479,7 @@ export function ExportPage() { {f.label} {f.needsCalibration ? " *" : ""} - + {f.hint} @@ -1492,7 +1492,7 @@ export function ExportPage() { {selectedFormats.length > 0 && (
- + {selectedFormats.length} selected:{" "} {selectedFormats .map( @@ -1506,7 +1506,7 @@ export function ExportPage() { @@ -1515,7 +1515,7 @@ export function ExportPage() { )} {hubMultiFormat && ( -
+
Hub export supports one format at a time (each writes to the repository root). Select a single format, or export locally to produce several at once. @@ -1527,13 +1527,13 @@ export function ExportPage() { MERGED_FORMATS.find((f) => f.value === v) ?.needsCalibration, ) && ( -
+
* calibrates on data (uses a small calibration set).
)} {!hasNvidia && ( -
+
No NVIDIA GPU detected: compressed-tensors formats are hidden. 16-bit and portable FP8/INT8 (torchao) still work here and load in vLLM. diff --git a/studio/frontend/src/features/hub/catalog/catalog-states.tsx b/studio/frontend/src/features/hub/catalog/catalog-states.tsx index 693b5b40c0..513f1c21c8 100644 --- a/studio/frontend/src/features/hub/catalog/catalog-states.tsx +++ b/studio/frontend/src/features/hub/catalog/catalog-states.tsx @@ -38,20 +38,20 @@ export function NetworkErrorState({
-

+

{title}

-

+

{body}

-

{message}

+

{message}

{onSwitchDevice ? ( @@ -59,7 +59,7 @@ export function NetworkErrorState({
-

+

No matches yet

-

+

Scanned {scannedCount.toLocaleString()} results. Load another page to keep searching Hugging Face.

@@ -105,7 +105,7 @@ export function DiscoverFetchMoreState({ @@ -114,7 +114,7 @@ export function DiscoverFetchMoreState({ type="button" onClick={onFetchMore} disabled={isLoadingMore} - className="inline-flex h-8 items-center gap-1.5 rounded-full bg-transparent px-3 text-[0.75rem] font-medium text-foreground transition-colors hover:bg-foreground/[0.04] disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/[0.05]" + className="inline-flex h-8 items-center gap-1.5 rounded-full bg-transparent px-3 text-ui-12 font-medium text-foreground transition-colors hover:bg-foreground/[0.04] disabled:cursor-not-allowed disabled:opacity-50 dark:hover:bg-white/[0.05]" > {/* Only warn about hidden results when a filter is actually narrowing them. */} {hasActiveFilters && ( -

+

Some results may be hidden by your filters.

)} @@ -149,7 +149,7 @@ export function DiscoverFetchMoreFooter({ type="button" onClick={onFetchMore} disabled={isLoadingMore} - className="inline-flex h-8 items-center gap-1.5 rounded-full bg-foreground/[0.06] px-3 text-[0.75rem] font-medium text-foreground transition-colors hover:bg-foreground/[0.1] disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white/[0.06] dark:hover:bg-white/[0.1]" + className="inline-flex h-8 items-center gap-1.5 rounded-full bg-foreground/[0.06] px-3 text-ui-12 font-medium text-foreground transition-colors hover:bg-foreground/[0.1] disabled:cursor-not-allowed disabled:opacity-50 dark:bg-white/[0.06] dark:hover:bg-white/[0.1]" >
-

+

Couldn't load your library

-

+

Something went wrong reading your downloaded{" "} {isDataset ? "datasets" : "models"}. Check that the backend is running and try again. @@ -187,7 +187,7 @@ export function InventoryErrorState({

-

+

{title}

-

+

{body}

diff --git a/studio/frontend/src/features/hub/catalog/dataset-download-section.tsx b/studio/frontend/src/features/hub/catalog/dataset-download-section.tsx index ade8d2de30..81c75ca881 100644 --- a/studio/frontend/src/features/hub/catalog/dataset-download-section.tsx +++ b/studio/frontend/src/features/hub/catalog/dataset-download-section.tsx @@ -126,7 +126,7 @@ export function DatasetDownloadSection({ } >
- + {isDownloaded && } {!isDownloaded && isPartial && !downloading && ( diff --git a/studio/frontend/src/features/hub/catalog/dot-tag.tsx b/studio/frontend/src/features/hub/catalog/dot-tag.tsx index 5ae1be53d0..5c2a0c8d1a 100644 --- a/studio/frontend/src/features/hub/catalog/dot-tag.tsx +++ b/studio/frontend/src/features/hub/catalog/dot-tag.tsx @@ -36,7 +36,7 @@ export function DotTag({ return ( diff --git a/studio/frontend/src/features/hub/catalog/download-card.tsx b/studio/frontend/src/features/hub/catalog/download-card.tsx index 9bc64ced0e..8016a9406d 100644 --- a/studio/frontend/src/features/hub/catalog/download-card.tsx +++ b/studio/frontend/src/features/hub/catalog/download-card.tsx @@ -140,7 +140,7 @@ export function CardUpdateButton({ e.stopPropagation(); onClick(); }} - className="inline-flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-full bg-amber-500/[0.07] pl-2 pr-2.5 text-[0.75rem] font-medium text-amber-800/90 transition-colors duration-150 hover:bg-amber-500/[0.12] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-amber-500/25 dark:bg-amber-400/[0.08] dark:text-amber-200/85 dark:hover:bg-amber-400/[0.16]" + className="inline-flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-full bg-amber-500/[0.07] pl-2 pr-2.5 text-ui-12 font-medium text-amber-800/90 transition-colors duration-150 hover:bg-amber-500/[0.12] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-amber-500/25 dark:bg-amber-400/[0.08] dark:text-amber-200/85 dark:hover:bg-amber-400/[0.16]" > {pendingUrl && (
-

+

{hostOf(pendingUrl)}

-

+

{pendingUrl}

diff --git a/studio/frontend/src/features/hub/catalog/gguf-download-card.tsx b/studio/frontend/src/features/hub/catalog/gguf-download-card.tsx index 9345874a53..b3d5f45ded 100644 --- a/studio/frontend/src/features/hub/catalog/gguf-download-card.tsx +++ b/studio/frontend/src/features/hub/catalog/gguf-download-card.tsx @@ -128,7 +128,7 @@ const FIT_BADGE: Record = { /** Chip styling matching the on-device list's StatChip, no icon. */ const CHIP_BASE = - "inline-flex h-5 shrink-0 items-center justify-center whitespace-nowrap rounded-full border px-2 text-[0.71875rem] font-medium tabular-nums leading-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]"; + "inline-flex h-5 shrink-0 items-center justify-center whitespace-nowrap rounded-full border px-2 text-ui-11p5 font-medium tabular-nums leading-3 shadow-[inset_0_1px_0_rgba(255,255,255,0.04)]"; const CHIP_DEFAULT = "border-foreground/15 bg-muted text-foreground/85 dark:border-border/60 dark:bg-white/[0.04] dark:text-foreground/85"; const CHIP_ACTIVE = @@ -184,7 +184,7 @@ function QuantBadge({ // group's `overflow-hidden` sacrifices the trailing status tags instead. @@ -914,7 +914,7 @@ export function GgufDownloadCard({ {/* Quant label + status tags travel together as one left-aligned group so the fit-info icon never floats orphaned from its tags; only the chevron pins right, the standard select affordance. */} - + {selected ? ( ) : ( - + Select quantization )} @@ -1126,7 +1126,7 @@ export function GgufDownloadCard({ diff --git a/studio/frontend/src/features/hub/catalog/gguf-status-cards.tsx b/studio/frontend/src/features/hub/catalog/gguf-status-cards.tsx index c7f402159b..f4f4fb27d2 100644 --- a/studio/frontend/src/features/hub/catalog/gguf-status-cards.tsx +++ b/studio/frontend/src/features/hub/catalog/gguf-status-cards.tsx @@ -34,7 +34,7 @@ export function GgufDownloadStatusCard({
@@ -91,7 +91,7 @@ export function GgufDownloadingFallbackCard({
- + {progress.variant && } Downloading… diff --git a/studio/frontend/src/features/hub/catalog/hub-detail-view.tsx b/studio/frontend/src/features/hub/catalog/hub-detail-view.tsx index e7d176442a..78e1d0a420 100644 --- a/studio/frontend/src/features/hub/catalog/hub-detail-view.tsx +++ b/studio/frontend/src/features/hub/catalog/hub-detail-view.tsx @@ -69,7 +69,7 @@ export function HubDetailView({ ) : ( - + {content} )} @@ -325,11 +325,11 @@ function ModelStatusChips({ > This model may not be supported yet. {unslothSupport.reason && ( - + {unslothSupport.reason} )} - + Still downloadable to your Hugging Face cache. @@ -349,7 +349,7 @@ function ModelStatusChips({ > This device has no supported GPU or usable MLX, so only GGUF models can run here. - + Still downloadable to your Hugging Face cache. @@ -368,7 +368,7 @@ function ModelStatusChips({ className="tooltip-compact max-w-xs" > Estimated 4-bit memory load is around {vramInfo.est} GB. - + {vramDetail} @@ -502,10 +502,10 @@ export const ModelInspector = memo(function ModelInspector({
-

+

Select a {isDataset ? "dataset" : "model"}

-

+

{isDataset ? "Choose a dataset from the catalog to inspect its download state and details." : "Choose an item from the catalog to inspect its runtime fit, download state, and model card."} @@ -528,7 +528,7 @@ export const ModelInspector = memo(function ModelInspector({ model.downloadsAllTime != null ? ( <> Downloads (30 days) - + {formatCompact(model.downloadsAllTime)} all time @@ -582,11 +582,11 @@ export const ModelInspector = memo(function ModelInspector({

-

+

{model.title}

{model.hubRepoId && ( @@ -599,7 +599,7 @@ export const ModelInspector = memo(function ModelInspector({
)}
-
+
{model.owner} {model.owner.toLowerCase() === "unsloth" && ( {isDataset && ( - + Dataset )} {!isDataset && ( - + {selectionHiddenByFilters && ( -

+

Current selection is hidden by the active filters or search.

)} {metadataUnavailable && ( -

+

Couldn't load full details from Hugging Face. Some fields may be incomplete.

diff --git a/studio/frontend/src/features/hub/catalog/model-readme.tsx b/studio/frontend/src/features/hub/catalog/model-readme.tsx index 42cc55372a..889b011399 100644 --- a/studio/frontend/src/features/hub/catalog/model-readme.tsx +++ b/studio/frontend/src/features/hub/catalog/model-readme.tsx @@ -126,17 +126,17 @@ function prepareReadmeBody(markdown: string): string { } const PROSE = cn( - "max-w-none text-[0.84375rem] leading-[1.7] text-foreground/85", - "[&_h1]:text-[1.125rem] [&_h1]:font-semibold [&_h1]:tracking-tight [&_h1]:mt-2 [&_h1]:mb-3", - "[&_h2]:text-[0.96875rem] [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:mt-5 [&_h2]:mb-2", - "[&_h3]:text-[0.875rem] [&_h3]:font-semibold [&_h3]:mt-4 [&_h3]:mb-1.5", + "max-w-none text-ui-13p5 leading-[1.7] text-foreground/85", + "[&_h1]:text-ui-18 [&_h1]:font-semibold [&_h1]:tracking-tight [&_h1]:mt-2 [&_h1]:mb-3", + "[&_h2]:text-ui-15p5 [&_h2]:font-semibold [&_h2]:tracking-tight [&_h2]:mt-5 [&_h2]:mb-2", + "[&_h3]:text-ui-14 [&_h3]:font-semibold [&_h3]:mt-4 [&_h3]:mb-1.5", "[&_p]:my-2.5 [&_ul]:my-2 [&_ol]:my-2 [&_li]:my-0.5", "[&_a]:text-primary [&_a:hover]:underline", - "[&_code]:rounded-md [&_code]:bg-muted/60 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-[0.75rem] [&_code]:font-mono", - "[&_pre]:rounded-[12px] [&_pre]:border [&_pre]:border-border/60 [&_pre]:bg-muted/40 [&_pre]:p-3 [&_pre]:text-[0.75rem] [&_pre]:overflow-x-auto", + "[&_code]:rounded-md [&_code]:bg-muted/60 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-ui-12 [&_code]:font-mono", + "[&_pre]:rounded-[12px] [&_pre]:border [&_pre]:border-border/60 [&_pre]:bg-muted/40 [&_pre]:p-3 [&_pre]:text-ui-12 [&_pre]:overflow-x-auto", "[&_pre_code]:bg-transparent [&_pre_code]:p-0", "[&_blockquote]:border-l-2 [&_blockquote]:border-border/60 [&_blockquote]:pl-3 [&_blockquote]:text-muted-foreground", - "[&_table]:my-3 [&_table]:text-[0.78125rem]", + "[&_table]:my-3 [&_table]:text-ui-12p5", "[&_th]:px-2 [&_th]:py-1.5 [&_th]:text-left [&_th]:font-semibold [&_th]:border-b [&_th]:border-border/60", "[&_td]:px-2 [&_td]:py-1.5 [&_td]:border-b [&_td]:border-border/40", "[&_img]:rounded-[10px] [&_img]:my-2 [&_img]:max-w-full", @@ -300,7 +300,7 @@ function ReadmePlaceholder({ aria-busy="true" aria-live="polite" > -
+
{message ?? `Loading ${kind === "dataset" ? "dataset" : "model"} card…`}
@@ -540,7 +540,7 @@ export function ModelReadme({ ? current.error : readmeUnavailableMessage(subject); return ( -

+

{errorMessage}

); @@ -548,7 +548,7 @@ export function ModelReadme({ if (!current.body) { return ( -

+

{readmeMissingMessage(subject)}

); diff --git a/studio/frontend/src/features/hub/catalog/models-catalog-lists.tsx b/studio/frontend/src/features/hub/catalog/models-catalog-lists.tsx index 926c002a65..cbae108006 100644 --- a/studio/frontend/src/features/hub/catalog/models-catalog-lists.tsx +++ b/studio/frontend/src/features/hub/catalog/models-catalog-lists.tsx @@ -68,7 +68,7 @@ export function InventoryWarningRow({ onRetry: () => void; }) { return ( -
+
Some on-device sources couldn't be scanned. Showing available{" "} @@ -76,7 +76,7 @@ export function InventoryWarningRow({ @@ -444,7 +444,7 @@ export function DownloadedList({ <> {pinnedItems.length > 0 && ( <> -
+
{unpinnedItems.length > 0 && ( -
+
All {isDataset ? "datasets" : "models"}
)} diff --git a/studio/frontend/src/features/hub/catalog/models-catalog-rows.tsx b/studio/frontend/src/features/hub/catalog/models-catalog-rows.tsx index 156bafba70..24b3fd49ef 100644 --- a/studio/frontend/src/features/hub/catalog/models-catalog-rows.tsx +++ b/studio/frontend/src/features/hub/catalog/models-catalog-rows.tsx @@ -188,14 +188,14 @@ function CachedSizeChipLive({ ))} ) : ( - + {variantMessage} )} @@ -225,7 +225,7 @@ export function StatChip({ return ( @@ -482,7 +482,7 @@ export const DiscoverModelRow = memo(function DiscoverModelRow({
-

+

{row.repo}

-
+
{row.owner} {row.owner.toLowerCase() === "unsloth" && ( @@ -528,7 +528,7 @@ export const DiscoverModelRow = memo(function DiscoverModelRow({ /> )} - + {formatRelativeShort(row.result.updatedAt)}
@@ -666,7 +666,7 @@ export const InventoryRow = memo(function InventoryRow({ {paramLabel} )} {quantLabel && ( - + {quantLabel} )} @@ -716,7 +716,7 @@ export const InventoryRow = memo(function InventoryRow({ ) : null; const ownerLine = ( - + {subLabel} {subLabel.toLowerCase() === "unsloth" && (
- + {title} {compactMarkers}
- + {subLabel} {subLabel.toLowerCase() === "unsloth" && ( @@ -851,7 +851,7 @@ export const InventoryRow = memo(function InventoryRow({ )}
-
+
{row.kind === "cache" ? (
- + {title} {statusMarkers} @@ -915,11 +915,11 @@ export const InventoryRow = memo(function InventoryRow({ cachePath={row.cachePath} /> ) : trailing ? ( - + {trailing} ) : sourceLabel ? ( - + {sourceLabel} ) : null} diff --git a/studio/frontend/src/features/hub/catalog/models-header.tsx b/studio/frontend/src/features/hub/catalog/models-header.tsx index 1702ed6fca..f0e0950871 100644 --- a/studio/frontend/src/features/hub/catalog/models-header.tsx +++ b/studio/frontend/src/features/hub/catalog/models-header.tsx @@ -91,7 +91,7 @@ export function ModelsHeader({ {activeCheckpoint && ( -
+