Make chat timeline height estimates width-aware with attachment-aware tests#167
Make chat timeline height estimates width-aware with attachment-aware tests#167juliusmarminge merged 11 commits intomainfrom
Conversation
- extract `estimateTimelineMessageHeight` into `timelineHeight.ts` - account for timeline width and explicit newlines when estimating wrapped lines - re-measure virtualized rows on width changes to fix attachment/message height sizing - add Vitest coverage for assistant/user wrapping and attachment row height rules
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: ResizeObserver never activates for initially empty threads
- Replaced useRef with a state-backed callback ref (useState + setTimelineRootNode as the ref callback) so the useLayoutEffect re-runs when the timeline div mounts after transitioning from the empty-thread state.
Or push these changes by commenting:
@cursor push fd13dceb67
Preview (fd13dceb67)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -3187,12 +3187,11 @@
onImageExpand,
markdownCwd,
}: MessagesTimelineProps) {
- const timelineRootRef = useRef<HTMLDivElement | null>(null);
+ const [timelineRootNode, setTimelineRootNode] = useState<HTMLDivElement | null>(null);
const [timelineWidthPx, setTimelineWidthPx] = useState<number | null>(null);
useLayoutEffect(() => {
- const timelineRoot = timelineRootRef.current;
- if (!timelineRoot) return;
+ if (!timelineRootNode) return;
const updateWidth = (nextWidth: number) => {
setTimelineWidthPx((previousWidth) => {
@@ -3203,7 +3202,7 @@
});
};
- updateWidth(timelineRoot.getBoundingClientRect().width);
+ updateWidth(timelineRootNode.getBoundingClientRect().width);
if (typeof ResizeObserver === "undefined") return;
const observer = new ResizeObserver((entries) => {
@@ -3211,11 +3210,11 @@
if (!entry) return;
updateWidth(entry.contentRect.width);
});
- observer.observe(timelineRoot);
+ observer.observe(timelineRootNode);
return () => {
observer.disconnect();
};
- }, []);
+ }, [timelineRootNode]);
const rows = useMemo<TimelineRow[]>(() => {
const nextRows: TimelineRow[] = [];
@@ -3645,7 +3644,7 @@
}
return (
- <div ref={timelineRootRef} className="mx-auto w-full min-w-0 max-w-3xl overflow-x-hidden">
+ <div ref={setTimelineRootNode} className="mx-auto w-full min-w-0 max-w-3xl overflow-x-hidden">
{virtualizedRowCount > 0 && (
<div className="relative" style={{ height: `${rowVirtualizer.getTotalSize()}px` }}>
{virtualRows.map((virtualRow: VirtualItem) => {- add browser E2E tests validating timeline height estimator against rendered DOM - configure Playwright test runner and scripts in `apps/web` - run browser tests in CI with Playwright browser caching and install step
- Replace Playwright e2e timeline-height test setup with Vitest browser config - Add `ChatView.browser.tsx` parity tests for text wrapping and attachment height estimation - Add row data attributes in `ChatView` to support in-browser measurement targeting
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Inconsistent width measurement APIs cause initial value mismatch
- Replaced
getBoundingClientRect().width(border-box) withclientWidth - paddingLeft - paddingRight(content-box) for the initial measurement, making it consistent with the ResizeObserver'scontentRect.width.
- Replaced
Or push these changes by commenting:
@cursor push e1d5bbad14
Preview (e1d5bbad14)
diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx
--- a/apps/web/src/components/ChatView.tsx
+++ b/apps/web/src/components/ChatView.tsx
@@ -3203,7 +3203,12 @@
});
};
- updateWidth(timelineRoot.getBoundingClientRect().width);
+ const cs = getComputedStyle(timelineRoot);
+ updateWidth(
+ timelineRoot.clientWidth -
+ parseFloat(cs.paddingLeft) -
+ parseFloat(cs.paddingRight),
+ );
if (typeof ResizeObserver === "undefined") return;
const observer = new ResizeObserver((entries) => {- Replace mocked router hooks with a memory router + RouterProvider - Render ChatView through a test route to exercise attachment height layout in realistic routing context
- Run ChatView browser tests through the real app router/store stack - Add MSW worker setup and mocked WS/attachment responses for full-app rendering - Extract shared `getRouter` setup and add a timeline root data hook for reliable measurement
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Document title no longer set after refactor
- Fixed the meta format from
{ name: "title", content: ... }to{ title: APP_DISPLAY_NAME }and added the missing<HeadContent />component to both render paths in the root route so TanStack Router's head management actually takes effect.
- Fixed the meta format from
Or push these changes by commenting:
@cursor push 7f77794bdd
Preview (7f77794bdd)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -1,5 +1,6 @@
import { ThreadId } from "@t3tools/contracts";
import {
+ HeadContent,
Outlet,
createRootRouteWithContext,
type ErrorComponentProps,
@@ -27,26 +28,30 @@
component: RootRouteView,
errorComponent: RootRouteErrorView,
head: () => ({
- meta: [{ name: "title", content: APP_DISPLAY_NAME }],
+ meta: [{ title: APP_DISPLAY_NAME }],
}),
});
function RootRouteView() {
if (!readNativeApi()) {
return (
- <div className="flex h-screen flex-col bg-background text-foreground">
- <div className="flex flex-1 items-center justify-center">
- <p className="text-sm text-muted-foreground">
- Connecting to {APP_DISPLAY_NAME} server...
- </p>
+ <>
+ <HeadContent />
+ <div className="flex h-screen flex-col bg-background text-foreground">
+ <div className="flex flex-1 items-center justify-center">
+ <p className="text-sm text-muted-foreground">
+ Connecting to {APP_DISPLAY_NAME} server...
+ </p>
+ </div>
</div>
- </div>
+ </>
);
}
return (
<ToastProvider>
<AnchoredToastProvider>
+ <HeadContent />
<EventRouter />
<DesktopProjectBootstrap />
<Outlet />- replace manual `createRoot` mounting with `render`/`unmount` from vitest-browser-react - add `vitest-browser-react` to web devDependencies and lockfile
- Replace manual timeout polling loops with `vi.waitFor` in test helpers - Improve reliability of locating and measuring virtualized user rows in attachment-height tests
- 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
- Add viewport matrix coverage for long text and attachment row-height parity - Reuse a mount/measure test harness to validate resize behavior in one session - Ensure production CSS and viewport sizing are applied before measurements
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Condition inversion changes system message height estimation
- Changed
message.role !== "user"back tomessage.role === "assistant"so system messages use user sizing rules (matching the original behavior) instead of being incorrectly routed to assistant sizing.
- Changed
Or push these changes by commenting:
@cursor push 7fbf52b7af
Preview (7fbf52b7af)
diff --git a/apps/web/src/components/timelineHeight.ts b/apps/web/src/components/timelineHeight.ts
--- a/apps/web/src/components/timelineHeight.ts
+++ b/apps/web/src/components/timelineHeight.ts
@@ -67,7 +67,7 @@
message: TimelineMessageHeightInput,
layout: TimelineHeightEstimateLayout = { timelineWidthPx: null },
): number {
- if (message.role !== "user") {
+ if (message.role === "assistant") {
const charsPerLine = estimateCharsPerLineForAssistant(layout.timelineWidthPx);
const estimatedLines = estimateWrappedLineCount(message.text, charsPerLine);
return ASSISTANT_BASE_HEIGHT_PX + estimatedLines * LINE_HEIGHT_PX;- Route `system` messages through assistant height estimation - Keep user attachment height logic scoped to user messages - Add regression test for system-message sizing behavior

Summary
timelineHeight.tsand reuse it fromChatViewResizeObserverTesting
apps/web/src/components/timelineHeight.test.tsbun lintbun typecheckNote
Medium Risk
Changes
ChatViewvirtualization sizing logic and introduces width-driven remeasurement, which can affect scrolling behavior and performance across viewports. Adds new Playwright/MSW browser-test infrastructure and CI steps that may introduce flakiness if timings or browser deps differ in CI.Overview
Chat timeline virtualization now estimates row heights using measured width. The inline
estimateTimelineMessageHeightlogic is extracted totimelineHeight.ts, updated to account for wrapping based ontimelineWidthPx(and attachment row sizing), and wired intoMessagesTimelinevia aResizeObserver+rowVirtualizer.measure()on width changes.Adds automated coverage and CI for real browser layout parity. Introduces unit tests for
estimateTimelineMessageHeight, plus a Vitest+Playwright browser test (ChatView.browser.tsx) that boots the full app with MSW-mocked WS/HTTP and asserts measured DOM heights stay within tolerance across multiple viewport sizes and with attachments; CI now installs/caches Playwright Chromium and runsapps/webtest:browser. Also factors router creation intogetRouter, adds MSW worker config/asset, and updates.gitignorefor Playwright artifacts/screenshots.Written by Cursor Bugbot for commit 93412ac. This will update automatically on new commits. Configure here.
Note
Make
apps/web/src/components/ChatView.MessagesTimelineheight estimation width-aware usingtimelineHeight.estimateTimelineMessageHeightand add Playwright-driven browser tests to validate attachment and text wrapping behaviorIntroduce a width-aware
timelineHeight.estimateTimelineMessageHeightand wire it intoChatView.MessagesTimeline; add browser tests with Playwright/Vitest and MSW; update CI to install/cache Playwright and run browser tests; refactor router setup into a factory.📍Where to Start
Start with the estimator in timelineHeight.ts, then review its integration in ChatView.tsx and validations in ChatView.browser.tsx.
Macroscope summarized 93412ac.