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)
);
}
}