diff --git a/apps/web/src/components/chat/ChatCanvas.tsx b/apps/web/src/components/chat/ChatCanvas.tsx index 3606f6db9700b9aa76588798479fdb3d00f9bf67..4df7ad66deb7741700979eb0baab3547ddfe7372 100644 --- a/apps/web/src/components/chat/ChatCanvas.tsx +++ b/apps/web/src/components/chat/ChatCanvas.tsx @@ -41,7 +41,7 @@ export function ChatCanvas({ const [measurements, setMeasurements] = useState({ width: 0, height: 0, - padding: 20, + padding: 48, maxChatWidth: 768, minChatWidth: 640, composerHeight: 0, @@ -139,7 +139,7 @@ export function ChatCanvas({
{children}
diff --git a/apps/web/src/components/chat/chatCanvasLayout.test.ts b/apps/web/src/components/chat/chatCanvasLayout.test.ts index ea99128a73d0e5480ad8fac96dbe9d59cb0f557b..2d6f77af1b4839d75b4c0afcaa65ba04e13a4880 100644 --- a/apps/web/src/components/chat/chatCanvasLayout.test.ts +++ b/apps/web/src/components/chat/chatCanvasLayout.test.ts @@ -51,7 +51,13 @@ describe("chat canvas layout", () => { it("centers chat in the whole container without a preview", () => { expect(resolve(1344, null).chat).toEqual({ left: 288, width: 768, insetStart: 0, insetEnd: 0 }); - expect(resolve(390, null).chat).toEqual({ left: 20, width: 350, insetStart: 0, insetEnd: 0 }); + expect( + resolveChatCanvasLayout({ + container: { width: 390, height: 900 }, + preview: null, + padding: 12, + }).chat, + ).toEqual({ left: 12, width: 366, insetStart: 0, insetEnd: 0 }); }); it("does not move chat when a bottom-right preview fits in its margin", () => { const result = resolve(1600); @@ -66,21 +72,21 @@ describe("chat canvas layout", () => { }); it("shrinks chat modestly after using the left margin", () => { const result = resolve(1200, { ...preview, width: 480 }); - expect(result.chat.width).toBe(676); - expect(result.chat.left).toBe(20); + expect(result.chat.width).toBe(648); + expect(result.chat.left).toBe(48); expectClear(result); }); it("stops leftward dragging before the player can push chat to the right", () => { const centered = resolve(1344, null).chat; - for (const x of [1100, 800, 672, 600, 400, 12, -100]) { + for (const x of [1100, 800, 700, 600, 400, 12, -100]) { const result = resolve(1344, { ...preview, position: { x, y: 500 } }); - expect(result.frame!.x).toBeGreaterThanOrEqual(672); + expect(result.frame!.x).toBeGreaterThanOrEqual(700); expect(result.chat.left).toBeLessThanOrEqual(centered.left); expect(result.chat.insetStart).toBe(0); expect(result.chat.width).toBeGreaterThanOrEqual(640); expectClear(result); } - expect(resolve(1344, { ...preview, position: { x: 12, y: 500 } }).frame!.x).toBe(672); + expect(resolve(1344, { ...preview, position: { x: 12, y: 500 } }).frame!.x).toBe(700); }); it("keeps a dragged player above the composer when no readable lane fits beside it", () => { const result = resolve(1000, { ...preview, position: { x: 12, y: 700 } }); @@ -171,8 +177,8 @@ describe("chat canvas layout", () => { expect(shifted.overlapsDetailsCard).toBe(false); expectClear(shifted); - const narrowed = layout(560); - expect(narrowed.frame!.width).toBe(560); + const narrowed = layout(532); + expect(narrowed.frame!.width).toBe(532); expect(narrowed.chat.width).toBe(656); expect(narrowed.overlapsDetailsCard).toBe(false); expectClear(narrowed); @@ -182,7 +188,7 @@ describe("chat canvas layout", () => { expect(grown.overlapsDetailsCard).toBe(true); expect(grown.overlapsChat).toBe(false); expectClear(grown); - expect(layout(560)).toEqual(narrowed); + expect(layout(532)).toEqual(narrowed); }); it.each(["west", "north"] as const)( "folds the card before moving a %s resize beside it", @@ -214,7 +220,7 @@ describe("chat canvas layout", () => { const density = (result: ReturnType) => { const card = resolveThreadDetailsCardLayout({ container, - lane: { padding: 20, minChatWidth: 640 }, + lane: { padding: 48, minChatWidth: 640 }, frame: result.frame, overlapsDetailsCard: result.overlapsDetailsCard, }); @@ -274,16 +280,30 @@ describe("workspace card beside chat", () => { expect(withCard(1147).chat).toEqual({ left: 87, width: 736, insetStart: 0, insetEnd: 237 }); }); it("narrows chat only after it reaches the left padding", () => { - expect(withCard(1080).chat).toMatchObject({ left: 20, width: 736 }); - expect(withCard(1000).chat).toEqual({ left: 20, width: 656, insetStart: 0, insetEnd: 304 }); - expect(withCard(984).chat).toMatchObject({ left: 20, width: 640 }); + expect(withCard(1108).chat).toMatchObject({ left: 48, width: 736 }); + expect(withCard(1028).chat).toEqual({ left: 48, width: 656, insetStart: 0, insetEnd: 276 }); + expect(withCard(1012).chat).toMatchObject({ left: 48, width: 640 }); + }); + it("reserves the marker gutter across widths where the details card fits", () => { + for (let width = 1012; width <= 1440; width++) { + const container = { width, height: 900 }; + const card = resolveThreadDetailsCardLayout({ + container, + lane: { padding: 48, minChatWidth: 640 }, + frame: null, + })!; + const chat = withCard(width).chat; + expect(chat.left).toBeGreaterThanOrEqual(48); + expect(chat.width).toBeGreaterThanOrEqual(640); + expect(card.x - chat.left - chat.width).toBeGreaterThanOrEqual(32); + } }); it("keeps a full-width chat clear of the card", () => { expect(withCard(1147, null, 10_000).chat).toEqual({ - left: 20, - width: 803, + left: 48, + width: 775, insetStart: 0, - insetEnd: 304, + insetEnd: 276, }); }); it("keeps centered chat in place when a new preview opens below the card", () => { diff --git a/apps/web/src/components/chat/chatCanvasLayout.ts b/apps/web/src/components/chat/chatCanvasLayout.ts index 714b9e286402b2bca684671fe37127489552d01b..01c5d527ccb7c75dc4cb0f4fd89b14b8562f5625 100644 --- a/apps/web/src/components/chat/chatCanvasLayout.ts +++ b/apps/web/src/components/chat/chatCanvasLayout.ts @@ -27,7 +27,7 @@ export const DETAILS_CARD_CLEARANCE = 32; export function resolveChatCanvasLayout({ container, preview, - padding = 20, + padding = 48, maxChatWidth = 768, minChatWidth = 640, composerHeight = 0, diff --git a/apps/web/src/components/chat/threadDetailsCardLayout.test.ts b/apps/web/src/components/chat/threadDetailsCardLayout.test.ts index e7d961bc811c56132d6a01bbf8f087e4144a9d68..923dbc4e521c6ea5beff478fc12f8f4bbd77a3d8 100644 --- a/apps/web/src/components/chat/threadDetailsCardLayout.test.ts +++ b/apps/web/src/components/chat/threadDetailsCardLayout.test.ts @@ -4,7 +4,7 @@ import { resolveThreadDetailsCardLayout, } from "./threadDetailsCardLayout"; -const lane = { padding: 20, minChatWidth: 640 }; +const lane = { padding: 48, minChatWidth: 640 }; const resolve = (width: number, height: number, previewY: number | null = null) => resolveThreadDetailsCardLayout({ container: { width, height }, @@ -33,8 +33,8 @@ describe("workspace card", () => { ).toEqual({ x: 1308, y: 60, width: 280, height: 828 }); }); it("hides when a readable chat lane cannot fit beside it", () => { - expect(resolve(984, 900)).toMatchObject({ x: 692 }); - expect(resolve(983, 900)).toBeNull(); + expect(resolve(1012, 900)).toMatchObject({ x: 720 }); + expect(resolve(1011, 900)).toBeNull(); }); it("keeps the card at the top right while the preview is freely dragged vertically", () => { for (const y of [12, 170, 250, 400, 648]) { diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 73e56caaf4e6e870173611e450ed65e98df597f4..6ecd5608e39d8bdadcb9cada6664ea3ba057131e 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -2344,18 +2344,16 @@ code { @media (min-width: 640px) { .chat-composer-lane { padding-inline-start: calc( - env(safe-area-inset-left) + 1.25rem + var(--chat-lane-inset-start, 0px) - ); - padding-inline-end: calc( - env(safe-area-inset-right) + 1.25rem + var(--chat-lane-inset-end, 0px) + env(safe-area-inset-left) + 3rem + var(--chat-lane-inset-start, 0px) ); + padding-inline-end: calc(env(safe-area-inset-right) + 3rem + var(--chat-lane-inset-end, 0px)); } [data-chat-canvas] .messages-timeline-scroll { padding-inline-start: calc( - 1.25rem + var(--chat-lane-inset-start, 0px) - var(--chat-timeline-gutter, 0px) + 3rem + var(--chat-lane-inset-start, 0px) - var(--chat-timeline-gutter, 0px) ); padding-inline-end: calc( - 1.25rem + var(--chat-lane-inset-end, 0px) - var(--chat-timeline-gutter, 0px) + 3rem + var(--chat-lane-inset-end, 0px) - var(--chat-timeline-gutter, 0px) ); } }