diff --git a/apps/desktop/src/settings/DesktopClientSettings.test.ts b/apps/desktop/src/settings/DesktopClientSettings.test.ts
index 23a75eb3f79d..d361873d19cc 100644
--- a/apps/desktop/src/settings/DesktopClientSettings.test.ts
+++ b/apps/desktop/src/settings/DesktopClientSettings.test.ts
@@ -25,6 +25,7 @@ const clientSettings: ClientSettings = {
fontFamilySans: "",
fontFamilyTerminal: "",
fontSizeCode: 13,
+ fontSizeToolOutput: 11,
fontSizeInterface: 16,
fontSizePrompt: 14,
fontSizeTerminal: 12,
diff --git a/apps/web/src/appearanceFonts.test.ts b/apps/web/src/appearanceFonts.test.ts
index c82c908207a3..3fbc579e4bc1 100644
--- a/apps/web/src/appearanceFonts.test.ts
+++ b/apps/web/src/appearanceFonts.test.ts
@@ -5,6 +5,7 @@ import {
clampCodeFontSize,
clampInterfaceFontSize,
clampPromptFontSize,
+ clampToolOutputFontSize,
DEFAULT_CODE_FONT_STACK,
DEFAULT_SANS_FONT_STACK,
appearanceFontStack,
@@ -13,6 +14,7 @@ import {
resolveDefaultFamilyLabel,
resolveTerminalFontPreference,
resolveTerminalFontSizePreference,
+ applyAppearanceFontVariables,
} from "./appearanceFonts";
describe("installed font discovery", () => {
@@ -155,11 +157,41 @@ describe("font size clamping", () => {
expect(clampInterfaceFontSize(96)).toBe(20);
expect(clampPromptFontSize(40)).toBe(20);
expect(clampCodeFontSize(1)).toBe(10);
+ expect(clampToolOutputFontSize(1)).toBe(10);
+ expect(clampToolOutputFontSize(99)).toBe(18);
});
it("rounds fractional values and falls back for unusable input", () => {
expect(clampCodeFontSize(13.4)).toBe(13);
expect(clampInterfaceFontSize(Number.NaN)).toBe(16);
expect(clampPromptFontSize(Number.POSITIVE_INFINITY)).toBe(14);
+ expect(clampToolOutputFontSize(Number.NaN)).toBe(11);
+ });
+});
+
+describe("appearance font variables", () => {
+ it("emits an independently clamped absolute tool-output size", () => {
+ const setProperty = vi.fn();
+ const root = {
+ style: {
+ fontSize: "",
+ removeProperty: vi.fn(),
+ setProperty,
+ },
+ } as unknown as HTMLElement;
+
+ applyAppearanceFontVariables(root, {
+ sans: "",
+ code: "",
+ composer: "",
+ sizeInterface: 16,
+ sizePrompt: 14,
+ sizeCode: 14,
+ sizeToolOutput: 8,
+ smoothing: true,
+ });
+
+ expect(setProperty).toHaveBeenCalledWith("--font-size-code", "14px");
+ expect(setProperty).toHaveBeenCalledWith("--font-size-tool-output", "10px");
});
});
diff --git a/apps/web/src/appearanceFonts.ts b/apps/web/src/appearanceFonts.ts
index 9eda4e3df97a..f21b8491e36a 100644
--- a/apps/web/src/appearanceFonts.ts
+++ b/apps/web/src/appearanceFonts.ts
@@ -9,12 +9,15 @@ import {
DEFAULT_CODE_FONT_SIZE,
DEFAULT_INTERFACE_FONT_SIZE,
DEFAULT_PROMPT_FONT_SIZE,
+ DEFAULT_TOOL_OUTPUT_FONT_SIZE,
MAX_CODE_FONT_SIZE,
MAX_INTERFACE_FONT_SIZE,
MAX_PROMPT_FONT_SIZE,
+ MAX_TOOL_OUTPUT_FONT_SIZE,
MIN_CODE_FONT_SIZE,
MIN_INTERFACE_FONT_SIZE,
MIN_PROMPT_FONT_SIZE,
+ MIN_TOOL_OUTPUT_FONT_SIZE,
} from "@t3tools/contracts";
export const DEFAULT_SANS_FONT_STACK =
@@ -84,6 +87,7 @@ export interface AppearanceFontPreferences {
readonly sizeInterface: number;
readonly sizePrompt: number;
readonly sizeCode: number;
+ readonly sizeToolOutput: number;
/** Grayscale `antialiased` rendering; false keeps the heavier platform default. */
readonly smoothing: boolean;
}
@@ -119,6 +123,10 @@ export function applyAppearanceFontVariables(
root.style.setProperty("--font-size-prompt", `${clampPromptFontSize(preferences.sizePrompt)}px`);
const code = clampCodeFontSize(preferences.sizeCode);
root.style.setProperty("--font-size-code", `${code}px`);
+ root.style.setProperty(
+ "--font-size-tool-output",
+ `${clampToolOutputFontSize(preferences.sizeToolOutput)}px`,
+ );
// The @pierre/diffs surfaces read their own hook for code text.
root.style.setProperty("--diffs-font-size", `${code}px`);
@@ -155,6 +163,15 @@ export function clampCodeFontSize(value: number): number {
return clampFontSize(value, MIN_CODE_FONT_SIZE, MAX_CODE_FONT_SIZE, DEFAULT_CODE_FONT_SIZE);
}
+export function clampToolOutputFontSize(value: number): number {
+ return clampFontSize(
+ value,
+ MIN_TOOL_OUTPUT_FONT_SIZE,
+ MAX_TOOL_OUTPUT_FONT_SIZE,
+ DEFAULT_TOOL_OUTPUT_FONT_SIZE,
+ );
+}
+
const FONT_PROBE_TEXT = "mmmmmmmmMMWli1O0@# fjord";
let fontProbeContext: CanvasRenderingContext2D | null | undefined;
diff --git a/apps/web/src/components/chat/MessagesTimeline.test.tsx b/apps/web/src/components/chat/MessagesTimeline.test.tsx
index a9e95c2b0f22..a8501e68166d 100644
--- a/apps/web/src/components/chat/MessagesTimeline.test.tsx
+++ b/apps/web/src/components/chat/MessagesTimeline.test.tsx
@@ -626,6 +626,24 @@ describe("MessagesTimeline", () => {
expect(copyControlMarkup).not.toContain("Copy output");
});
+ it("sizes every expanded body block with the tool-output variable", () => {
+ const markup = renderToStaticMarkup(
+
+
{block.isError ? "Error output\n" : null}
{block.text}
{block.truncated ? "\n\nOutput truncated" : null}
@@ -2317,14 +2317,14 @@ export const WorkEntryExpandedBody = memo(function WorkEntryExpandedBody(props:
) : block.kind === "empty" ? (
(No output)
) : (
{block.text}
diff --git a/apps/web/src/components/settings/SettingsPanels.logic.test.ts b/apps/web/src/components/settings/SettingsPanels.logic.test.ts
index ec4ad4ff5875..a757b340923d 100644
--- a/apps/web/src/components/settings/SettingsPanels.logic.test.ts
+++ b/apps/web/src/components/settings/SettingsPanels.logic.test.ts
@@ -27,8 +27,9 @@ describe("typography settings restore", () => {
...DEFAULT_UNIFIED_SETTINGS,
fontSizeInterface: 18,
fontFamilyCode: "Fira Code",
+ fontSizeToolOutput: 12,
}),
- ).toEqual(["Interface font", "Code font"]);
+ ).toEqual(["Interface font", "Code font", "Tool output"]);
});
});
diff --git a/apps/web/src/components/settings/SettingsPanels.logic.ts b/apps/web/src/components/settings/SettingsPanels.logic.ts
index 39f4f3cdafd6..b3e9df6a7a8c 100644
--- a/apps/web/src/components/settings/SettingsPanels.logic.ts
+++ b/apps/web/src/components/settings/SettingsPanels.logic.ts
@@ -83,6 +83,7 @@ type TypographySettings = Pick<
| "fontSizeInterface"
| "fontSizePrompt"
| "fontSizeCode"
+ | "fontSizeToolOutput"
| "fontSizeTerminal"
>;
@@ -101,6 +102,9 @@ export function getChangedTypographySettingLabels(settings: TypographySettings):
settings.fontSizeCode !== DEFAULT_UNIFIED_SETTINGS.fontSizeCode
? ["Code font"]
: []),
+ ...(settings.fontSizeToolOutput !== DEFAULT_UNIFIED_SETTINGS.fontSizeToolOutput
+ ? ["Tool output"]
+ : []),
...(settings.fontFamilyTerminal !== DEFAULT_UNIFIED_SETTINGS.fontFamilyTerminal ||
settings.fontSizeTerminal !== DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal
? ["Terminal font"]
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index 6f90e6a02553..08ab356ba01d 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -24,12 +24,14 @@ import {
MAX_GLASS_OPACITY,
MAX_INTERFACE_FONT_SIZE,
MAX_PROMPT_FONT_SIZE,
+ MAX_TOOL_OUTPUT_FONT_SIZE,
MAX_SIDEBAR_AUTO_SETTLE_AFTER_DAYS,
MAX_TERMINAL_FONT_SIZE,
MIN_CODE_FONT_SIZE,
MIN_GLASS_OPACITY,
MIN_INTERFACE_FONT_SIZE,
MIN_PROMPT_FONT_SIZE,
+ MIN_TOOL_OUTPUT_FONT_SIZE,
MIN_SIDEBAR_AUTO_SETTLE_AFTER_DAYS,
MIN_TERMINAL_FONT_SIZE,
} from "@t3tools/contracts/settings";
@@ -544,6 +546,7 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.fontFamilySans,
settings.fontFamilyTerminal,
settings.fontSizeCode,
+ settings.fontSizeToolOutput,
settings.fontSizeInterface,
settings.fontSizePrompt,
settings.fontSizeTerminal,
@@ -654,6 +657,7 @@ export function useSettingsRestore(onRestored?: () => void) {
fontSizeInterface: DEFAULT_UNIFIED_SETTINGS.fontSizeInterface,
fontSizePrompt: DEFAULT_UNIFIED_SETTINGS.fontSizePrompt,
fontSizeCode: DEFAULT_UNIFIED_SETTINGS.fontSizeCode,
+ fontSizeToolOutput: DEFAULT_UNIFIED_SETTINGS.fontSizeToolOutput,
fontSizeTerminal: DEFAULT_UNIFIED_SETTINGS.fontSizeTerminal,
});
onRestored?.();
@@ -1243,6 +1247,38 @@ function TerminalFontRow() {
);
}
+function ToolOutputFontRow() {
+ const settings = usePrimarySettings();
+ const updateSettings = useUpdatePrimarySettings();
+ return (
+
+ updateSettings({
+ fontSizeToolOutput: DEFAULT_UNIFIED_SETTINGS.fontSizeToolOutput,
+ })
+ }
+ />
+ ) : null
+ }
+ control={
+ updateSettings({ fontSizeToolOutput })}
+ />
+ }
+ />
+ );
+}
+
function FontSmoothingRow() {
const settings = usePrimarySettings();
const updateSettings = useUpdatePrimarySettings();
@@ -1304,6 +1340,7 @@ function FontSettingsGroup() {
+
>
@@ -1341,6 +1378,7 @@ function SimpleFontRows() {
>
}
/>
+
>
);
}
@@ -1399,6 +1437,44 @@ function TypographySection() {
);
}
+function FontSizeSelect({
+ label,
+ min,
+ max,
+ value,
+ onChange,
+}: {
+ label: string;
+ min: number;
+ max: number;
+ value: number;
+ onChange: (value: number) => void;
+}) {
+ return (
+
+ );
+}
+
function FontFamilySettingsRow({
id,
title,
@@ -1455,29 +1531,13 @@ function FontFamilySettingsRow({
const control = (
{familyControl}
-
+
);
return (
diff --git a/apps/web/src/components/settings/settingsSearch.ts b/apps/web/src/components/settings/settingsSearch.ts
index e3aef6705665..2c8bbfd08230 100644
--- a/apps/web/src/components/settings/settingsSearch.ts
+++ b/apps/web/src/components/settings/settingsSearch.ts
@@ -83,6 +83,11 @@ export const SETTINGS_SEARCH_ITEMS = [
title: "Code font",
to: "/settings/appearance",
},
+ {
+ id: "tool-output-font",
+ title: "Tool output",
+ to: "/settings/appearance",
+ },
{
id: "terminal-font",
title: "Terminal font",
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
index bc8f8507b814..99be179d81bd 100644
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -168,6 +168,7 @@ function FontAppearanceSync() {
const fontSizeInterface = useClientSettings((settings) => settings.fontSizeInterface);
const fontSizePrompt = useClientSettings((settings) => settings.fontSizePrompt);
const fontSizeCode = useClientSettings((settings) => settings.fontSizeCode);
+ const fontSizeToolOutput = useClientSettings((settings) => settings.fontSizeToolOutput);
const fontSmoothing = useClientSettings((settings) => settings.fontSmoothing);
useEffect(() => {
@@ -178,6 +179,7 @@ function FontAppearanceSync() {
sizeInterface: fontSizeInterface,
sizePrompt: fontSizePrompt,
sizeCode: fontSizeCode,
+ sizeToolOutput: fontSizeToolOutput,
smoothing: fontSmoothing,
});
for (const family of new Set([fontFamilySans, fontFamilyCode, fontFamilyComposer])) {
@@ -195,6 +197,7 @@ function FontAppearanceSync() {
fontFamilyComposer,
fontFamilySans,
fontSizeCode,
+ fontSizeToolOutput,
fontSizeInterface,
fontSizePrompt,
fontSmoothing,
diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts
index 570157292b54..f578f7ef7834 100644
--- a/packages/contracts/src/settings.test.ts
+++ b/packages/contracts/src/settings.test.ts
@@ -49,6 +49,22 @@ describe("ClientSettings glass opacity", () => {
});
});
+describe("ClientSettings tool output font size", () => {
+ it("defaults to 11px", () => {
+ expect(decodeClientSettings({}).fontSizeToolOutput).toBe(11);
+ });
+
+ it.each([9, 19, 11.5])("rejects a size outside the supported integer range: %s", (value) => {
+ expect(() => decodeClientSettings({ fontSizeToolOutput: value })).toThrow();
+ expect(() => decodeClientSettingsPatch({ fontSizeToolOutput: value })).toThrow();
+ });
+
+ it.each([10, 11, 18])("accepts a size within the supported range: %s", (value) => {
+ expect(decodeClientSettings({ fontSizeToolOutput: value }).fontSizeToolOutput).toBe(value);
+ expect(decodeClientSettingsPatch({ fontSizeToolOutput: value }).fontSizeToolOutput).toBe(value);
+ });
+});
+
describe("ClientSettings environment identification", () => {
it("defaults to artwork and accepts each presentation mode", () => {
expect(decodeClientSettings({}).environmentIdentificationMode).toBe("artwork");
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
index 9306e430b6e1..8a60280a13f8 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
@@ -92,6 +92,14 @@ export const CodeFontSize = Schema.Int.check(
export type CodeFontSize = typeof CodeFontSize.Type;
export const DEFAULT_CODE_FONT_SIZE: CodeFontSize = 13;
+export const MIN_TOOL_OUTPUT_FONT_SIZE = 10;
+export const MAX_TOOL_OUTPUT_FONT_SIZE = 18;
+export const ToolOutputFontSize = Schema.Int.check(
+ Schema.isBetween({ minimum: MIN_TOOL_OUTPUT_FONT_SIZE, maximum: MAX_TOOL_OUTPUT_FONT_SIZE }),
+);
+export type ToolOutputFontSize = typeof ToolOutputFontSize.Type;
+export const DEFAULT_TOOL_OUTPUT_FONT_SIZE: ToolOutputFontSize = 11;
+
export const MIN_TERMINAL_FONT_SIZE = 8;
export const MAX_TERMINAL_FONT_SIZE = 20;
export const TerminalFontSize = Schema.Int.check(
@@ -136,6 +144,9 @@ export const ClientSettingsSchema = Schema.Struct({
fontSizeCode: CodeFontSize.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_CODE_FONT_SIZE)),
),
+ fontSizeToolOutput: ToolOutputFontSize.pipe(
+ Schema.withDecodingDefault(Effect.succeed(DEFAULT_TOOL_OUTPUT_FONT_SIZE)),
+ ),
fontSizeTerminal: TerminalFontSize.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_TERMINAL_FONT_SIZE)),
),
@@ -778,6 +789,7 @@ export const ClientSettingsPatch = Schema.Struct({
fontSizeInterface: Schema.optionalKey(InterfaceFontSize),
fontSizePrompt: Schema.optionalKey(PromptFontSize),
fontSizeCode: Schema.optionalKey(CodeFontSize),
+ fontSizeToolOutput: Schema.optionalKey(ToolOutputFontSize),
fontSizeTerminal: Schema.optionalKey(TerminalFontSize),
fontFamilyCode: Schema.optionalKey(FontFamilyPreference),
fontFamilyComposer: Schema.optionalKey(FontFamilyPreference),