),
@@ -1585,7 +1596,7 @@ const UserMessageBody = memo(function UserMessageBody(props: {
}
return (
-
+
)}
diff --git a/apps/web/src/components/settings/SettingsPanels.tsx b/apps/web/src/components/settings/SettingsPanels.tsx
index fa7c7299667..c6c7d1c3028 100644
--- a/apps/web/src/components/settings/SettingsPanels.tsx
+++ b/apps/web/src/components/settings/SettingsPanels.tsx
@@ -69,6 +69,7 @@ import { Button } from "../ui/button";
import { DraftInput } from "../ui/draft-input";
import { Select, SelectItem, SelectPopup, SelectTrigger, SelectValue } from "../ui/select";
import { Switch } from "../ui/switch";
+import { Toggle, ToggleGroup } from "../ui/toggle-group";
import { stackedThreadToast, toastManager } from "../ui/toast";
import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { AddProviderInstanceDialog } from "./AddProviderInstanceDialog";
@@ -114,6 +115,18 @@ const THEME_OPTIONS = [
},
] as const;
+const TRANSCRIPT_TEXT_SIZE_OPTIONS = [
+ { value: "small", label: "Small" },
+ { value: "medium", label: "Medium" },
+ { value: "large", label: "Large" },
+] as const;
+
+const TRANSCRIPT_WIDTH_OPTIONS = [
+ { value: "narrow", label: "Narrow" },
+ { value: "medium", label: "Medium" },
+ { value: "wide", label: "Wide" },
+] as const;
+
const TIMESTAMP_FORMAT_LABELS = {
locale: "System default",
"12-hour": "12-hour",
@@ -404,6 +417,12 @@ export function useSettingsRestore(onRestored?: () => void) {
...(settings.timestampFormat !== DEFAULT_UNIFIED_SETTINGS.timestampFormat
? ["Time format"]
: []),
+ ...(settings.transcriptTextSize !== DEFAULT_UNIFIED_SETTINGS.transcriptTextSize
+ ? ["Transcript text size"]
+ : []),
+ ...(settings.transcriptWidth !== DEFAULT_UNIFIED_SETTINGS.transcriptWidth
+ ? ["Transcript width"]
+ : []),
...(settings.sidebarThreadPreviewCount !== DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount
? ["Visible threads"]
: []),
@@ -463,6 +482,8 @@ export function useSettingsRestore(onRestored?: () => void) {
settings.sidebarProjectGroupingMode,
settings.sidebarThreadPreviewCount,
settings.timestampFormat,
+ settings.transcriptTextSize,
+ settings.transcriptWidth,
settings.wordWrap,
theme,
],
@@ -481,6 +502,8 @@ export function useSettingsRestore(onRestored?: () => void) {
setTheme("system");
updateSettings({
timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,
+ transcriptTextSize: DEFAULT_UNIFIED_SETTINGS.transcriptTextSize,
+ transcriptWidth: DEFAULT_UNIFIED_SETTINGS.transcriptWidth,
wordWrap: DEFAULT_UNIFIED_SETTINGS.wordWrap,
diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,
glassOpacity: DEFAULT_UNIFIED_SETTINGS.glassOpacity,
@@ -588,6 +611,78 @@ export function GeneralSettingsPanel() {
}
/>
+
+ updateSettings({
+ transcriptTextSize: DEFAULT_UNIFIED_SETTINGS.transcriptTextSize,
+ })
+ }
+ />
+ ) : null
+ }
+ control={
+ {
+ const nextValue = value[0];
+ if (nextValue === "small" || nextValue === "medium" || nextValue === "large") {
+ updateSettings({ transcriptTextSize: nextValue });
+ }
+ }}
+ aria-label="Transcript text size"
+ >
+ {TRANSCRIPT_TEXT_SIZE_OPTIONS.map((option) => (
+
+ {option.label}
+
+ ))}
+
+ }
+ />
+
+
+ updateSettings({ transcriptWidth: DEFAULT_UNIFIED_SETTINGS.transcriptWidth })
+ }
+ />
+ ) : null
+ }
+ control={
+ {
+ const nextValue = value[0];
+ if (nextValue === "narrow" || nextValue === "medium" || nextValue === "wide") {
+ updateSettings({ transcriptWidth: nextValue });
+ }
+ }}
+ aria-label="Transcript width"
+ >
+ {TRANSCRIPT_WIDTH_OPTIONS.map((option) => (
+
+ {option.label}
+
+ ))}
+
+ }
+ />
+
select#reasoning-effort) select {
}
/* Chat markdown rendering */
+[data-transcript-width="narrow"] {
+ --chat-content-max-width: 40rem;
+}
+
+[data-transcript-width="medium"] {
+ --chat-content-max-width: 48rem;
+}
+
+[data-transcript-width="wide"] {
+ --chat-content-max-width: 64rem;
+}
+
+[data-transcript-text-size="small"] {
+ --transcript-text-size: 0.8125rem;
+ --transcript-compact-text-size: 0.6875rem;
+}
+
+[data-transcript-text-size="medium"] {
+ --transcript-text-size: 0.875rem;
+ --transcript-compact-text-size: 0.75rem;
+}
+
+[data-transcript-text-size="large"] {
+ --transcript-text-size: 1rem;
+ --transcript-compact-text-size: 0.875rem;
+}
+
.chat-markdown {
min-width: 0;
overflow-wrap: anywhere;
word-break: break-word;
+ font-size: var(--transcript-text-size, 0.875rem);
}
.chat-markdown > :first-child {
@@ -1125,21 +1153,21 @@ label:has(> select#reasoning-effort) select {
}
.chat-markdown h1 {
- font-size: 1.25rem;
+ font-size: 1.4286em;
}
.chat-markdown h2 {
- font-size: 1.125rem;
+ font-size: 1.2857em;
}
.chat-markdown h3 {
- font-size: 1rem;
+ font-size: 1.1429em;
}
.chat-markdown h4,
.chat-markdown h5,
.chat-markdown h6 {
- font-size: 0.875rem;
+ font-size: 1em;
}
.chat-markdown h6 {
@@ -1222,7 +1250,7 @@ label:has(> select#reasoning-effort) select {
border-top: 1px solid var(--border);
padding-top: 0.75rem;
color: var(--muted-foreground);
- font-size: 0.75rem;
+ font-size: var(--transcript-compact-text-size, 0.75rem);
}
.chat-markdown section[data-footnotes] ol {
@@ -1252,7 +1280,7 @@ label:has(> select#reasoning-effort) select {
background: var(--muted);
padding: 0.1rem 0.35rem;
color: var(--foreground);
- font-size: 0.75rem;
+ font-size: var(--transcript-compact-text-size, 0.75rem);
}
.chat-markdown a.chat-markdown-file-link {
@@ -1283,7 +1311,7 @@ label:has(> select#reasoning-effort) select {
border: none;
background: transparent;
padding: 0;
- font-size: 0.75rem;
+ font-size: var(--transcript-compact-text-size, 0.75rem);
}
.chat-markdown pre {
@@ -1390,7 +1418,7 @@ label:has(> select#reasoning-effort) select {
border-collapse: collapse;
overflow-wrap: normal;
word-break: normal;
- font-size: 0.75rem;
+ font-size: var(--transcript-compact-text-size, 0.75rem);
}
.chat-markdown th,
diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts
index e8eaf723e17..30c8156e6b7 100644
--- a/packages/contracts/src/settings.test.ts
+++ b/packages/contracts/src/settings.test.ts
@@ -49,6 +49,29 @@ describe("ClientSettings glass opacity", () => {
});
});
+describe("ClientSettings transcript presentation", () => {
+ it("defaults to the existing medium transcript presentation", () => {
+ const settings = decodeClientSettings({});
+ expect(settings.transcriptTextSize).toBe("medium");
+ expect(settings.transcriptWidth).toBe("medium");
+ });
+
+ it.each(["small", "medium", "large"] as const)("accepts transcript text size: %s", (value) => {
+ expect(decodeClientSettings({ transcriptTextSize: value }).transcriptTextSize).toBe(value);
+ expect(decodeClientSettingsPatch({ transcriptTextSize: value }).transcriptTextSize).toBe(value);
+ });
+
+ it.each(["narrow", "medium", "wide"] as const)("accepts transcript width: %s", (value) => {
+ expect(decodeClientSettings({ transcriptWidth: value }).transcriptWidth).toBe(value);
+ expect(decodeClientSettingsPatch({ transcriptWidth: value }).transcriptWidth).toBe(value);
+ });
+
+ it("rejects unsupported transcript presentation values", () => {
+ expect(() => decodeClientSettings({ transcriptTextSize: "extra-large" })).toThrow();
+ expect(() => decodeClientSettingsPatch({ transcriptWidth: "full" })).toThrow();
+ });
+});
+
describe("ClientSettings sidebar v2", () => {
it("defaults the beta off with a three-day auto-settle threshold", () => {
const settings = decodeClientSettings({});
diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts
index 06f7de3db67..889aee618c7 100644
--- a/packages/contracts/src/settings.ts
+++ b/packages/contracts/src/settings.ts
@@ -59,6 +59,14 @@ export const GlassOpacity = Schema.Int.check(
export type GlassOpacity = typeof GlassOpacity.Type;
export const DEFAULT_GLASS_OPACITY: GlassOpacity = 80;
+export const TranscriptTextSize = Schema.Literals(["small", "medium", "large"]);
+export type TranscriptTextSize = typeof TranscriptTextSize.Type;
+export const DEFAULT_TRANSCRIPT_TEXT_SIZE: TranscriptTextSize = "medium";
+
+export const TranscriptWidth = Schema.Literals(["narrow", "medium", "wide"]);
+export type TranscriptWidth = typeof TranscriptWidth.Type;
+export const DEFAULT_TRANSCRIPT_WIDTH: TranscriptWidth = "medium";
+
export const ClientSettingsSchema = Schema.Struct({
autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
@@ -118,6 +126,12 @@ export const ClientSettingsSchema = Schema.Struct({
timestampFormat: TimestampFormat.pipe(
Schema.withDecodingDefault(Effect.succeed(DEFAULT_TIMESTAMP_FORMAT)),
),
+ transcriptTextSize: TranscriptTextSize.pipe(
+ Schema.withDecodingDefault(Effect.succeed(DEFAULT_TRANSCRIPT_TEXT_SIZE)),
+ ),
+ transcriptWidth: TranscriptWidth.pipe(
+ Schema.withDecodingDefault(Effect.succeed(DEFAULT_TRANSCRIPT_WIDTH)),
+ ),
wordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
});
export type ClientSettings = typeof ClientSettingsSchema.Type;
@@ -605,6 +619,8 @@ export const ClientSettingsPatch = Schema.Struct({
sidebarThreadPreviewCount: Schema.optionalKey(SidebarThreadPreviewCount),
sidebarV2Enabled: Schema.optionalKey(Schema.Boolean),
timestampFormat: Schema.optionalKey(TimestampFormat),
+ transcriptTextSize: Schema.optionalKey(TranscriptTextSize),
+ transcriptWidth: Schema.optionalKey(TranscriptWidth),
wordWrap: Schema.optionalKey(Schema.Boolean),
});
export type ClientSettingsPatch = typeof ClientSettingsPatch.Type;