authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-03-05 00:13:45-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 10:20:34-08:00
loga76413594e772f476b98ba6384d0efe9906e75ff
treef79844f24acc6c72de129628ba1d02d81c30f3e4
parent1100d5a6f7e00ca41aa795ad601f78c670f5e5ab

Fix chat attachment height on narrow timeline resizes

- recompute timeline width from the root element on ResizeObserver updates - rerun width effect when message/working state changes - reduce minimum user chars per line for narrow layouts and add regression test

3 files changed, 14 insertions(+), 6 deletions(-)

apps/web/src/components/ChatView.tsx+3-5
...@@ -3206,16 +3206,14 @@ const MessagesTimeline = memo(function MessagesTimeline({...@@ -3206,16 +3206,14 @@ const MessagesTimeline = memo(function MessagesTimeline({
3206 updateWidth(timelineRoot.getBoundingClientRect().width);3206 updateWidth(timelineRoot.getBoundingClientRect().width);
32073207
3208 if (typeof ResizeObserver === "undefined") return;3208 if (typeof ResizeObserver === "undefined") return;
3209 const observer = new ResizeObserver((entries) => {3209 const observer = new ResizeObserver(() => {
3210 const [entry] = entries;3210 updateWidth(timelineRoot.getBoundingClientRect().width);
3211 if (!entry) return;
3212 updateWidth(entry.contentRect.width);
3213 });3211 });
3214 observer.observe(timelineRoot);3212 observer.observe(timelineRoot);
3215 return () => {3213 return () => {
3216 observer.disconnect();3214 observer.disconnect();
3217 };3215 };
3218 }, []);3216 }, [hasMessages, isWorking]);
32193217
3220 const rows = useMemo<TimelineRow[]>(() => {3218 const rows = useMemo<TimelineRow[]>(() => {
3221 const nextRows: TimelineRow[] = [];3219 const nextRows: TimelineRow[] = [];
apps/web/src/components/timelineHeight.test.ts+10
...@@ -76,6 +76,16 @@ describe("estimateTimelineMessageHeight", () => {...@@ -76,6 +76,16 @@ describe("estimateTimelineMessageHeight", () => {
76 expect(estimateTimelineMessageHeight(message, { timelineWidthPx: 768 })).toBe(118);76 expect(estimateTimelineMessageHeight(message, { timelineWidthPx: 768 })).toBe(118);
77 });77 });
7878
79 it("does not clamp user wrapping too aggressively on very narrow layouts", () => {
80 const message = {
81 role: "user" as const,
82 text: "a".repeat(20),
83 };
84
85 expect(estimateTimelineMessageHeight(message, { timelineWidthPx: 100 })).toBe(184);
86 expect(estimateTimelineMessageHeight(message, { timelineWidthPx: 320 })).toBe(118);
87 });
88
79 it("uses narrower width to increase assistant line wrapping", () => {89 it("uses narrower width to increase assistant line wrapping", () => {
80 const message = {90 const message = {
81 role: "assistant" as const,91 role: "assistant" as const,
apps/web/src/components/timelineHeight.ts+1-1
...@@ -11,7 +11,7 @@ const USER_BUBBLE_HORIZONTAL_PADDING_PX = 32;...@@ -11,7 +11,7 @@ const USER_BUBBLE_HORIZONTAL_PADDING_PX = 32;
11const ASSISTANT_MESSAGE_HORIZONTAL_PADDING_PX = 8;11const ASSISTANT_MESSAGE_HORIZONTAL_PADDING_PX = 8;
12const USER_MONO_AVG_CHAR_WIDTH_PX = 8.4;12const USER_MONO_AVG_CHAR_WIDTH_PX = 8.4;
13const ASSISTANT_AVG_CHAR_WIDTH_PX = 7.2;13const ASSISTANT_AVG_CHAR_WIDTH_PX = 7.2;
14const MIN_USER_CHARS_PER_LINE = 16;14const MIN_USER_CHARS_PER_LINE = 4;
15const MIN_ASSISTANT_CHARS_PER_LINE = 20;15const MIN_ASSISTANT_CHARS_PER_LINE = 20;
1616
17interface TimelineMessageHeightInput {17interface TimelineMessageHeightInput {