| author | |
| committer | |
| log | a76413594e772f476b98ba6384d0efe9906e75ff |
| tree | f79844f24acc6c72de129628ba1d02d81c30f3e4 |
| parent | 1100d5a6f7e00ca41aa795ad601f78c670f5e5ab |
- 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 test3 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); |
| 3207 | 3207 | ||
| 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]); |
| 3219 | 3217 | ||
| 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 | }); |
| 78 | 78 | ||
| 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; |
| 11 | const ASSISTANT_MESSAGE_HORIZONTAL_PADDING_PX = 8; | 11 | const ASSISTANT_MESSAGE_HORIZONTAL_PADDING_PX = 8; |
| 12 | const USER_MONO_AVG_CHAR_WIDTH_PX = 8.4; | 12 | const USER_MONO_AVG_CHAR_WIDTH_PX = 8.4; |
| 13 | const ASSISTANT_AVG_CHAR_WIDTH_PX = 7.2; | 13 | const ASSISTANT_AVG_CHAR_WIDTH_PX = 7.2; |
| 14 | const MIN_USER_CHARS_PER_LINE = 16; | 14 | const MIN_USER_CHARS_PER_LINE = 4; |
| 15 | const MIN_ASSISTANT_CHARS_PER_LINE = 20; | 15 | const MIN_ASSISTANT_CHARS_PER_LINE = 20; |
| 16 | 16 | ||
| 17 | interface TimelineMessageHeightInput { | 17 | interface TimelineMessageHeightInput { |