From df0b77f488fa8d598438d867c4209ddf1a17835f Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Sun, 13 Sep 2026 13:22:40 -0300 Subject: [PATCH 1/5] feat(contracts): add chat width client setting Adds a persisted chatWidth client setting with comfortable (default), wide, and full, so existing users keep the current layout. --- packages/contracts/src/settings.test.ts | 17 +++++++++++++++++ packages/contracts/src/settings.ts | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/packages/contracts/src/settings.test.ts b/packages/contracts/src/settings.test.ts index 16b9418ab7ccb..1331d2dcb30df 100644 --- a/packages/contracts/src/settings.test.ts +++ b/packages/contracts/src/settings.test.ts @@ -301,6 +301,23 @@ describe("ClientSettings diff colors", () => { }); }); +describe("ClientSettings chat width", () => { + it("keeps the comfortable width for existing settings without a saved width", () => { + expect(decodeClientSettings({}).chatWidth).toBe("comfortable"); + }); + + it.each(["comfortable", "wide", "full"])("round-trips the %s width", (chatWidth) => { + const settings = decodeClientSettings({ chatWidth }); + expect(encodeClientSettings(settings).chatWidth).toBe(chatWidth); + expect(decodeClientSettingsPatch({ chatWidth }).chatWidth).toBe(chatWidth); + }); + + it("rejects unsupported widths", () => { + expect(() => decodeClientSettings({ chatWidth: "huge" })).toThrow(); + expect(() => decodeClientSettingsPatch({ chatWidth: "huge" })).toThrow(); + }); +}); + describe("ClientSettings load balancing", () => { it("requires opt-in when settings are new or omit load balancing", () => { expect(decodeClientSettings({}).loadBalancingEnabled).toBe(false); diff --git a/packages/contracts/src/settings.ts b/packages/contracts/src/settings.ts index a8cce44875499..e8dc37bfc839f 100644 --- a/packages/contracts/src/settings.ts +++ b/packages/contracts/src/settings.ts @@ -291,6 +291,10 @@ export const LoadBalancingWeights = Schema.Record( export const DiffColorScheme = Schema.Literals(["red-green", "blue-orange"]); +/** Maximum width of the chat timeline and composer on wide screens. */ +export const ChatWidth = Schema.Literals(["comfortable", "wide", "full"]); +export type ChatWidth = typeof ChatWidth.Type; + export const ClientSettingsSchema = Schema.Struct({ notificationMode: NotificationMode.pipe( Schema.withDecodingDefault(Effect.succeed("off" as const)), @@ -299,6 +303,7 @@ export const ClientSettingsSchema = Schema.Struct({ diffColorScheme: DiffColorScheme.pipe( Schema.withDecodingDefault(Effect.succeed("red-green" as const)), ), + chatWidth: ChatWidth.pipe(Schema.withDecodingDefault(Effect.succeed("comfortable" as const))), loadBalancingEnabled: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))), loadBalancingWeights: LoadBalancingWeights.pipe(Schema.withDecodingDefault(Effect.succeed({}))), appearanceContrast: AppearanceContrast.pipe( @@ -1566,6 +1571,7 @@ export const ClientSettingsPatch = Schema.Struct({ notificationMode: Schema.optionalKey(NotificationMode), inAppNotificationsEnabled: Schema.optionalKey(Schema.Boolean), diffColorScheme: Schema.optionalKey(DiffColorScheme), + chatWidth: Schema.optionalKey(ChatWidth), loadBalancingEnabled: Schema.optionalKey(Schema.Boolean), loadBalancingWeights: Schema.optionalKey(LoadBalancingWeights), appearanceContrast: Schema.optionalKey(AppearanceContrast), From 0e2b553114e138cce85dc8ec69c5cba71ea3e7b6 Mon Sep 17 00:00:00 2001 From: Otavio Salvador Date: Sun, 13 Sep 2026 13:23:44 -0300 Subject: [PATCH 2/5] feat(web): size the chat column from the chat width setting The timeline and composer containers read --chat-max-width instead of a fixed max-w-3xl. The root sets data-chat-width from the client setting and CSS maps it to 48rem, 72rem, or 100%. --- apps/web/src/components/ChatView.tsx | 2 +- apps/web/src/components/chat/ChatComposer.tsx | 2 +- apps/web/src/components/chat/ComposerSurface.tsx | 2 +- apps/web/src/components/chat/MessagesTimeline.tsx | 9 ++++++--- apps/web/src/index.css | 13 +++++++++++++ apps/web/src/routes/__root.tsx | 5 +++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index f5c3825cee333..037b5d28af01e 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -10053,7 +10053,7 @@ export default function ChatView(props: ChatViewProps) { >
{isDraftHeroState ? (
diff --git a/apps/web/src/components/chat/ChatComposer.tsx b/apps/web/src/components/chat/ChatComposer.tsx index fafe24e439734..d168c6db4e5fa 100644 --- a/apps/web/src/components/chat/ChatComposer.tsx +++ b/apps/web/src/components/chat/ChatComposer.tsx @@ -6165,7 +6165,7 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) if (isInsideRestingComposerControlScope(event.target)) return; composerMentionDragHandlers.onDrop(event); }} - className="mx-auto w-full min-w-0 max-w-3xl" + className="mx-auto w-full min-w-0 max-w-(--chat-max-width)" data-chat-composer-form="true" > {composerControlsInStrip && restingControlsHost diff --git a/apps/web/src/components/chat/ComposerSurface.tsx b/apps/web/src/components/chat/ComposerSurface.tsx index bcb994085eb0d..83e94895eddca 100644 --- a/apps/web/src/components/chat/ComposerSurface.tsx +++ b/apps/web/src/components/chat/ComposerSurface.tsx @@ -13,7 +13,7 @@ function Shell({ data-slot="composer-shell" data-with-context={contextStrip || undefined} className={cn( - "@container/composer-surface group/composer-surface relative isolate mx-auto w-full max-w-3xl", + "@container/composer-surface group/composer-surface relative isolate mx-auto w-full max-w-(--chat-max-width)", "[--chat-composer-drawer-inset:1.375rem] [--chat-composer-glass-surface:var(--card)] [--chat-composer-outline:rgb(0_0_0/8%)]", "dark:[--chat-composer-glass-surface:var(--surface-raised)] dark:[--chat-composer-highlight:rgb(255_255_255/3%)] dark:[--chat-composer-outline:color-mix(in_srgb,var(--color-white)_5%,transparent)]", "[html[data-theme-id]_&]:[--chat-composer-glass-surface:var(--app-theme-surface-raised)] [html[data-theme-id]_&]:[--chat-composer-outline:var(--app-theme-toolbar-border)]", diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index 755657e794ab4..4144e5a7466e1 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -346,7 +346,7 @@ function TimelineLoadEarlierHeader({ }) { return (
-
+