Repository navigation
UI Consistency: 2 issues found
Reviewed the panel-animation changes in apps/web/src (shared primitives, panel shells, settings UI, presence hook). Two concrete findings, both posted inline:
apps/web/src/components/ui/sidebar.tsx— the newanimationDurationMsprop only reaches the mobile sheet; the desktop gap/container/rail transitions readvar(--panel-animation-duration), a variable the primitive never defines (it exists only becauseAppSidebarLayoutsets it onSidebarProvider). With the declared defaults under any other provider the variable is missing,transition-durationis invalid at computed-value time and resolves to0s, so the desktop collapse loses theduration-200it had while the prop silently does nothing. Suggested fix: haveSidebarwrite the variable from its own prop.apps/web/src/components/preview/PreviewPanelShell.tsx— the frozen inner track is set to the fullwidth, but the host isborder-boxwithborder-l, so its content box iswidth - 1px. With animations enabled the content is laid out a pixel wider than the visible area and the newoverflow-cliphides that pixel (right edge of the tab bar / scrollbar lane), making layout differ by 1px from the non-animated path. Suggested fix:calc(${width}px - 1px).
No new findings on panelAnimations.ts, sheet.tsx, WorkspacePageHeader.tsx, SettingsPanels.tsx/PanelAnimationsPreview.tsx, ChatView.tsx, or _chat.pull-requests.tsx.
Details
Note
Your check run agent prompt is: .macroscope/check-run-agents/ui-consistency.md
More information about how Check Run Agents work can be found in our Help Center.
Verified in this revision (previously flagged, now resolved):
- The global
index.cssrules for the entering panel width and the header padding transition are gone; both are now owner-local Tailwind (starting:w-0!inPreviewPanelShell,[[data-panel-animations=true]_&]:motion-safe:*inWorkspacePageHeader). Preflight is active (@import "tailwindcss"), and the variant/attribute scope matches thedata-panel-animationshook onSidebarProvider, which wraps the whole route tree from__root.tsx. transition-[width]is now suppressed during resize drags via the wrapped pointer handlers;ResizableWidthHandlershas exactly the four handlers, so nothing is dropped fromRightPanelResizeHandle.overflow-hiddenwas moved off the host onto an inner wrapper, so the resize handle's 4px overhang and its 1px centered indicator are no longer clipped.- Collapsing no longer reflows panel content (fixed inner track + clip), so the terminal surface does not re-fit toward
cols = 1during close. usePanelPresencenow has focused coverage inapps/web/src/panelAnimations.test.tsx(retention for the configured duration, immediate drop when not animated, cancelled unmount on reopen, scope isolation).- Control-cluster placement: for the inline path
panelLayoutControlsrenders in the route container (absolute, same titlebar inset) rather than inside the panel, and the pull-requests route mirrors that, so the cluster stays mounted at one anchor across the transition. The sheet path now hands the cluster back to the header as soon asopenflips false, which addresses the earlier gap.
Not flagged (judged aesthetic or below the reporting bar): the [transition-duration:var(...)] arbitrary property instead of the codebase's duration-* idiom; the settings slider following the existing .settings-slider convention; the raw <button> in PanelAnimationsPreview (preview/image target); left-anchored content during the collapse; keeping the maximize control clickable during the retention window.
🔍 Agent activity — 34 tool calls
💭 I'm checking the current PR head to review the changes—panel resizing logic, animation helpers, sheet and sidebar components, ChatView, and the workspace header, noting that CSS was moved into Tailwind classes at the owner level as previously requested, along with new tests. The resize animation flicker issue during drag now has a
resizingstate fix, and tests are present. I still need to verify whether the sheet-mode controls duplication/vanishing issue was resolved by examining the actual files.
1. browse_code — apps/web/src/panelAnimations.ts
2. browse_code — apps/web/src/components/preview/PreviewPanelShell.tsx
💭 I'm checking whether Tailwind v4's
starting:variant combined withw-0!would actually override the inline width style, since !important in a stylesheet typically beats a plain inline declaration. Then I'm tracing whether@starting-stylereapplies correctly on remount when panels close and reopen, and confirming--panel-animation-durationis properly scoped within SidebarProvider so PreviewPanelShell can access it across both the chat and pull-requests routes. I also realize WorkspacePageHeader's data-attribute selector depends on being nested inside SidebarProvider — if it's rendered outside that ancestor, the header padding would just snap without animating rather than error, which is a minor inconsistency. And if--panel-animation-durationis ever undefined, the transition-duration falls back to its initial value of 0s, meaning the animation silently fails to run rather than breaking anything — a safe but invisible degradation I should verify against where PreviewPanelShell actually renders. I want to check whether_chat.pull-requests.tsxsits inside AppSidebarLayout so the CSS variable is actually in scope, then look at whetherresizeHandlerspassed toRightPanelResizeHandlemight include additional event handlers likeonDoubleClickoronKeyDownthat get dropped when spread manually — I need to checkuseResizableWidth's return type against what the handle component expects. Tracing throughusePanelPresence, I'm confirming the visibility logic works correctly across the animated/non-animated cases whenopentoggles, including the initial mount case and the timeout-based cleanup when animated is true — this all looks sound so far, and now I'm checking howPreviewPanelShellconsumes theopenprop alongside this presence logic. I'm realizing this creates a real inconsistency: sinceanimatedis passed down through RightPanelTabs to PreviewPanelShell, a maximized panel would hang visually for the full presence duration before disappearing abruptly, rather than animating smoothly. I need to check whether ChatView actually tracks the maximized state to handle this case differently. Next I'm looking atPanelAnimationsPreview, which uses raw buttons and an effect with requestAnimationFrame to toggle panels open/closed based on theenabledflag — it seems acceptable as an interactive preview graphic. Then insheet.tsx, I notice the transition styles and duration are applied inline viatransitionDurationMs, which overrides all transitions on the popup element, not just the enter/exit animation — a minor concern but likely harmless. Theanimated=falsepath correctly disables transitions withtransition-none!while still applying the style object, which is fine since it's overridden anyway. CheckingWorkspacePageHeader, the padding-left transition now usesease-outwith a variable duration instead of the originalduration-200 ease-linear, but this seems intentional to keep timing ownership consistent with the panel animation system. Verifying that the arbitrary attribute selector for panel-animations emits valid escaped CSS, then confirming that sidebar.tsx's gap and fixed divs properly inherit the transition-duration variable since they're nested inside SidebarProvider. Now checking whether other Sidebar instances rely on the default 200ms duration and animated=true settings. I'm concerned other call sites of Sidebar elsewhere in the app might silently lose their animation if they're not wrapped in a SidebarProvider that defines the panel-animation-duration variable—need to grep for other usages to confirm. The mobile sheet path looks fine with its default duration prop, so I'm moving on to check the ChatView sheet-mode controls flagged in a prior review pass. Now I'm tracing whether the current diff'srightPanelControlsInPanellogic causes duplicate controls when the sheet closes—since it flips to false the momentrightPanelOpenbecomes false while the sheet is still visually present and fading out, the header could render its own copy of the controls before the sheet's copy disappears. I need to check whether the sheet's internal rendering of its controls is actually gated on the sameopenstate to see if this creates a visible duplication during the closing transition. Actually that's the desirable behavior — control returning to the header as the sheet leaves — so I shouldn't re-flag this; the earlier fix seems reasonable as is. Now I want to check the resize handle types, other Sidebar call sites, the animation duration variable's availability on the pull-requests route, and the maximized-close behavior, so I'll grep for those.
3. browse_code — apps/web/src/components/RightPanelResizeHandle.tsx
4. browse_code — apps/web/src/hooks/useResizableWidth.ts
💭 The handlers interface has exactly four methods, so no props got dropped. I'm verifying the resizing state gets properly reset—checking that pointer capture is confirmed before setting resizing true, and that cancel/lostpointercapture events cover cases where pointerup never fires. Now let me check RightPanelResizeHandle.
5. browse_code — **/RightPanelResizeHandle.tsx
6. git_tools — panel-animation-duration
💭 I realize
--panel-animation-durationis scoped only to AppSidebarLayout's provider, so any Sidebar rendered under a different SidebarProvider with default animation enabled will reference an unset variable, breaking the transition entirely instead of falling back to the previous duration. I need to check other SidebarProvider/Sidebar usages, including whether PreviewPanelShell in the pull-requests route sits under AppSidebarLayout.
7. git_tools — <SidebarProvider|<Sidebar[ \n>]
8. browse_code — apps/web/src/components/preview/RightPanelResizeHandle.tsx
💭 Good, the overflow issue is resolved since the handle sits outside the clipped inner wrapper now. Next I want to locate where Sidebar is used, so I'll search for "SidebarProvider" instead since my earlier grep pattern didn't work.
9. git_tools — SidebarProvider
10. git_tools — Sidebar$|Sidebar |Sidebar>
💭 I'm checking that there's only one real call site for SidebarProvider and Sidebar, and noting that tests don't set the CSS variable for animation duration. Since the primitive relies on the CSS variable rather than the prop for non-mobile transitions, the missing variable could leave the duration undefined in that inline case. The fix could be setting the CSS variable directly from the prop on the root element, or providing a fallback in the transition-duration utility itself. I also notice PreviewPanelShell has the same pattern — it relies on the ancestor-set variable rather than defining its own duration, which means its behavior is contingent on always being rendered inside a SidebarProvider-like layout. I want to check whether that assumption holds across all its actual usages, like the pull-requests route. Checking browser support for the starting-style feature used for the width transition — it should work fine in Chrome, Safari, and Firefox's recent versions, with the transition properly disabled during resize so entry animations behave correctly. I should verify the Tailwind version in this repo, since the
starting:variant only exists from v4.1 onward — if it's on v4.0, that class would silently fail to generate and the animation just wouldn't trigger. I also want to check ifstarting:is used elsewhere already, and confirm the reduced-motion handling isn't redundant with the existing settings hook. I'm now tracing howusePanelPresencebehaves when the viewport crosses the sheet breakpoint mid-open — the inline panel's open/animated flags both flip false, causing an immediate unmount while the sheet variant takes over, which seems correct. Now I want to look closer at the maximized-close delay issue.
11. browse_code — apps/web/src/components/RightPanelTabs.tsx
12. git_tools — tailwindcss
💭 I need to confirm whether the
startingvariant for @starting-style support was actually introduced in Tailwind v4.0, since that affects whether it's available given this lockfile version.
13. git_tools — starting:
14. browse_code — apps/web/src/components/ChatView.tsx
💭 I realize when closing a maximized inline panel,
rightPanelMaximizedstays true becausecanMaximizeRightPanelis still true during the close transition, which disables the animated width shrink — so the panel stays fully maximized for the whole duration and then vanishes abruptly instead of smoothly transitioning. But I need to check whether closing actually clearsmaximizedRightPanelThreadKeyfirst. If it does in the same tick, then the maximize condition would flip to false immediately,animatedwould become true, and the panel would shrink via width transition as expected. I should search for whereclosePreviewPanelinteracts withmaximizedRightPanelThreadKey.
15. git_tools — maximizedRightPanelThreadKey|setMaximizedRightPanelThreadKey
💭 I'm checking whether toggleRightPanel resets the maximized state when closing via toggleVisibility instead of closePreviewPanel, so I need to look at that function's implementation. Even if closePreviewPanel does clear maximized on close, the transition from a flex-1 layout to width:0px likely won't animate at all — CSS transitions can't interpolate from
auto-resolved flex widths to a fixed pixel value in most browsers, so the panel would just snap shut instantly rather than animate, with content unmounting afterward. Now let me look at the toggle path itself.
16. browse_code — apps/web/src/components/ChatView.tsx
💭 I'm thinking through a closing-behavior edge case: when the last surface closes and the store nulls out the active surface, the presence hook keeps the previous surface mounted for its exit animation—so a terminal surface could stay rendered briefly even after its underlying terminal has already been torn down by the cleanup logic. Now I'm looking for genuinely new UI-consistency issues in this revision. One real finding: the
Sidebarprimitive accepts ananimationDurationMsprop but its desktop width transitions rely on a CSS variable set by an ancestor it never defines itself, so that prop only actually applies to the mobile sheet variant—any consumer lacking that ancestor variable silently loses the transition duration. The fix would be to haveSidebarset the variable itself or supply a fallback value, and I want to double check the sidebar source to confirm the variable truly isn't set anywhere internally. I'm now checking whetherPreviewPanelShellhas the same issue since it takes ananimatedprop without a duration prop, so I need to verify it's actually rendered under the layout that provides that CSS variable for the pull-requests route. I'm also confirming the reduced-motion handling in the animations preview component looks correct since it uses a motion-reduce class to disable transitions regardless of the raw setting value, and separately checking whether the settings panel's range input should instead be using an existing shared Slider primitive rather than a raw input element. I want to check if I'll look into SettingsPanels for boilerplate duplication. Then I'm checking WorkspacePageHeader's use of the panel-animations data attribute combined with motion-safe styling, confirming it's scoped under the same provider as before. The settings row with the disabled slider and switch placement looks fine functionally, just slightly unusual ordering aesthetically, and I'm moving on to look at PanelAnimationsPreview next. Now I'm checking PanelAnimationsPreview's accessibility—the replay button with its aria-label toggles panel widths correctly even when animations are disabled, using transition-none as expected. Then I move to sheet.tsx, where SheetPopup now always merges a transition style object with any passed-in style prop, and I want to verify this doesn't double-apply or accidentally drop a caller's custom style since previously the style prop might have just been spread through props directly. I also need to check how the animated=false case overrides the starting/ending data attributes with transition-none. Now I'm checking whether SheetBackdrop properly merges its className overrides using a cn-style utility rather than clobbering existing classes, and considering whether transition-none on the popup could conflict with Base UI's expectation that the CSS transition duration determines when the close animation completes. I'm also looking at whether the inline transitionDuration style might conflict with Tailwind's duration utility classes already applied to the popup. I want to trace how animationDurationMs flows from RightPanelSheet into SheetPopup and compare it against ChatView's panelAnimationDurationMs, verifying usePanelPresence uses a matching duration for its JS-based unmount timing. Next I should look directly at sheet.tsx, sidebar.tsx, and the chat layout file to confirm these pieces align.
17. browse_code — apps/web/src/components/ui/sheet.tsx
18. browse_code — apps/web/src/components/ui/sidebar.tsx
💭 I've confirmed that the desktop Sidebar transitions rely on a CSS variable set by an ancestor, while the primitive's own duration prop only affects the mobile sheet. This means with default settings, if the SidebarProvider doesn't set that variable, the transition duration becomes invalid and effectively falls back to zero. The only production call site currently sets this variable so it works, but the underlying contract is broken since the prop is inert on desktop. The cleanest fix would be to have Sidebar write the variable itself from the prop, applying it as an inline style with a fallback default, though I need to check where to place it since the wrapper element with the relevant data-slot currently has no style attribute, and SidebarRail also reads from this same variable. I lean toward the style-based fix as primary, since it makes the declared prop authoritative rather than silently ignored. This looks like a genuine ownership violation — the Sidebar owns its own transition duration, so it should set its own variable from the prop it declares rather than depending on a fragile ancestor-provided value. The same pattern shows up in PreviewPanelShell, except it has no duration prop of its own and just reads the ancestor variable that ChatView passes down. I need to trace whether PreviewPanelShell always renders under a provider that supplies that variable — if it doesn't, the width transition would silently disappear while the JS mount timing still assumes it's there, causing a visible snap instead of an animation. I should check the route tree, starting with
_chat.tsxand where AppSidebarLayout gets used.
19. git_tools — AppSidebarLayout
20. browse_code — apps/web/src/components/AppSidebarLayout.tsx
💭 Since AppSidebarLayout wraps the whole route tree, the variable is available everywhere, so the availability concern isn't really an issue in production. The remaining worry is more subtle: the
animationDurationMsprop is only honored on mobile, so a reviewer could flag that the desktop path silently depends on an ancestor variable the primitive itself doesn't set, which is a real ownership question worth weighing as a genuine violation. Before settling there, I want to check for stronger bugs — like whether thestarting:w-0!class in PreviewPanelShell, which applies@starting-stylewith!important, causes any issue since it stays present the whole time the panel is open rather than just at mount, though@starting-styleshould only kick in on first style resolution or when the element re-enters the DOM. Then I trace through ChatView's right panel presence logic and theusePanelPresencehook, checking timing issues around opening/closing surfaces, theretainedRefupdate timing, and whetherpresentstate transitions correctly when switching surfaces mid-animation — so far nothing seems clearly broken. I also flag a minor accessibility nit in PreviewPanelShell: during close,pointer-events-noneprevents clicks but doesn't block keyboard focus or hide content from screen readers, leaving it tabbable for the ~200-400ms animation window — a real but short-lived issue I'd likely skip. I also note the host's border stays visible through the close animation since the container width collapses but the border classes remain applied. With border-box, width 0 with a 1px border likely still renders as a 1px sliver since content can't go negative — minor visual quirk. Moving on, I'm checking the titlebar's panel layout controls region in ChatView, and then looking at how the pull-requests chat route wires up panel presence for the right panel, checking how the environment ID gets derived from the rendered pull request state. The environment fallback logic there seems unchanged from before — only the source variable was swapped, so that's not a new issue. During the close animation, the retained surface still resolves correctly since panelEnvironmentId reads from the retained value, so that's fine. Now I want to trace through the pull-requests route more thoroughly, checking whether the PreviewPanelShell's animated/open props stay consistent, and whether places using rightPanelPresent versus isOpen actually mean the same thing where it matters. I'm noticing the header-side panel controls only render when rightPanelPresent is false, which means they stay hidden throughout the entire close animation and only reappear once the panel is fully gone — not immediately as the close starts. But wait, ChatView's inline case still usesinlineRightPanelPresentfor the header, meaning it likely has the exact same gap during close, since the panel copy is also clipped inside the collapsing panel there. I need to check whether PreviewPanelShell's overflow-clip wrapper actually hides the content as the host collapses to zero width, since the inner content keeps its fixed width while the outer clips it. The tab bar's layout controls sit at the right end of the panel, so they're clipped early during the close animation while the header copy hasn't appeared yet, meaning the toggle-to-reopen controls vanish from the UI for most of the animation before popping into the header. Checking the open direction too: the header copy disappears the instant the panel starts mounting, but the panel's own controls stay clipped throughout the whole expand-from-zero animation, so the same gap exists on open as well. This looks like a genuine visual regression where the panel layout controls disappear for the full duration of every open/close transition, and I recall a prior review flagged the analogous sheet-variant issue around line 7198. During close, controls vanish for the entire duration before popping into the header, which is arguably worse since the header copy is withheld even while the panel copy is already clipped from view. The same fix logic used for the sheet case should apply here: render the header copy during close so something is visible while the panel version is clipping out. Actually since this is likely transient during the animation, maybe the toolbar appearing at the end is intended rather than a bug. Let me check what ChatHeader does with therightPanelOpenprop passed at line 7223 to see if it handles padding or spacing during the transition. (B) PreviewPanelShell has a similar split: its CSS duration comes from the ancestor var while its JS unmount timing comes from a separately threaded prop in ChatView. If those two ever diverge, content could unmount out of sync with the animation. This looks like the same root issue as (A) — the shared duration variable is really owned by AppSidebarLayout but used by two unrelated components, so the fix should likely have PreviewPanelShell own its own duration source too. Actually, sharing a runtime-derived var from a common ancestor is a legitimate pattern for cross-component values, so (A) is weaker than I thought — the real problem isn't the var itself but that Sidebar declares an unused prop for desktop, and AppSidebarLayout passes both the prop and the var redundantly, obscuring that the prop is dead weight. I'm now checking whether to recommend either wiring the prop into the var on desktop or removing it and documenting the ancestor var requirement instead. Turning to PanelAnimationsPreview, I'm tracing how its effect toggles panelsOpen via rAF based on the enabled flag, and confirming it correctly respects prefers-reduced-motion through the motion-reduce utility class. But I notice it duplicates duration/easing logic with an inline transitionDuration instead of using the app's shared animation duration variable, which is inconsistent with how the rest of the app handles pending state. Now I want to compare the settings panel's slider markup against the sibling contrast slider to see if there's a shared pattern (like a Slider primitive in components/ui) that a third copy should follow, plus check whether the range input's aria-label vs proper label association is consistent across these sliders.
21. browse_code — apps/web/src/components/ui/*.tsx
22. git_tools — settings-slider
💭 The slider matches the existing settings-slider pattern, and the added disabled cursor/opacity utility classes will take precedence over the components layer styling since Tailwind's utilities layer wins that specificity battle. Hover-triggered thumb scaling would still technically apply on disabled sliders since :hover works on disabled inputs, but that's too minor to flag. The row layout ordering differs slightly from the other slider rows, and the PanelAnimationsPreview button is a legitimate preview target so I'll skip both of those too. Checking
usePanelAnimationSettings, it relies onuseMediaQueryfor reduced motion and correctly disables the panel-animations flag under that condition, which matches the oldmotion-reduce:transition-nonebehavior for the header — so that's equivalent. But I'm now noticing that previously the header and sidebar transitions ran unconditionally at duration-200, and I need to verify whether the new implementation preserves that always-on behavior outside of reduced-motion cases. I want to check whetherSidebarProviderforwards arbitrary props likedata-panel-animationsdown to its root div, and I should look atsidebar.test.tsxfor any assertions tied toduration-200that could break. There's also a subtlety withSidebarRail'stransition-allclass lacking an explicit duration — I need to check what Tailwind's default duration resolves to when no duration utility is specified. I'm reasoning through Tailwind v4's utility ordering to confirm that the arbitrary-propertytransition-durationdeclaration takes precedence over the one baked intotransition-[width], since arbitrary properties get emitted later in the utilities layer and thus win in the cascade. I'm trying to recall whether arbitrary property ordering changed between Tailwind v3 and v4, and whether that affects precedence when combined with named transition utilities. Since I'm not fully certain, I should check the codebase itself for existing examples of similar patterns to see how they're actually used in practice.
23. git_tools — transition-duration:|duration-(|duration-[
24. browse_code — apps/web/src/components/ui/sidebar.test.tsx
💭 I noticed a global rule setting transition-duration to 0 with importance, which might already be part of a reduced-motion block elsewhere in index.css—checking the surrounding lines to see if it overlaps with the motion-reduce utility I'm adding. I'm now trying to pin down whether Tailwind v4 sorts arbitrary properties before or after standard utilities like transition-[width], since that ordering determines whether the explicit duration override actually takes effect or gets clobbered by the default 150ms transition duration, which would desync the CSS animation from the JS unmount timing. I'm not fully certain how v4 orders arbitrary property utilities versus named ones like
duration-*, and I notice the existing codebase already usesduration-[120ms]for durations rather than arbitrary property syntax, while the new code introduces[transition-duration:var(...)]in several places—an inconsistency worth flagging given my uncertainty about sort-order safety. I could suggest usingduration-(--panel-animation-duration)so the duration flows through the CSS variable directly rather than being overwritten by the transition utility, but since this seems like an aesthetic preference rather than a functional bug, I should probably not flag it as a hard failure. Let me refocus on identifying the strongest genuine functional issues instead. Actually, I'm noticing that the new inner wrapper structure always adds two extra divs around children, regardless of theanimatedflag, which changes the DOM/flex hierarchy for every panel mode. Previously children were direct flex items of the host container, but now they're nested inside a non-flex wrapper div before reaching the inner flex container, which could break CSS selectors or layout assumptions that depended on the old parent-child structure. Children still getflex-1behavior against the inner wrapper, so layout stays equivalent. But I'm now worried about a 1px mismatch: the host appliesborder-l: 1pxwith border-box sizing, while the inner div gets an explicit pixel width during animation, potentially making the inner content 1px wider than the host's content box and causing a subtle clipping issue while animating. So the content ends up 1px wider than the visible box, clipping the rightmost column when animations are on — subtle but real, and it would shift any right-aligned control slightly out of view. A cleaner fix would be to size the inner element off the host's content width rather than a fixed pixel width, so it accounts for the border automatically. When maximized, animation is disabled so there's no width constraint or clipping needed, which makes sense. I'm also checking that pointer events behave correctly during the opening animation and that content doesn't get reflowed or misclicked while the panel is partially expanded, plus thinking about whether focus could get stranded inside retained content when the panel closes. Now I'm digging into environment routing for the pull-requests panel — verifying that the environment ID stays consistent with the retained surface during the close animation, and considering whether the shared scope key for panel presence could cause any cross-environment mixups when switching between PRs. There might be a subtle issue with how the retained surface's environment is tracked once the panel closes, so I want to trace that further.
25. browse_code — apps/web/src/routes/_chat.pull-requests.tsx
26. browse_code — apps/web/src/routes/_chat.pull-requests.tsx
💭 I'm noting the placement logic for openPanelControls in the PR route—when the panel is closed the toggle strip sits inside the header, but once opened it moves to the route container so it keeps a fixed top-right anchor rather than shifting position. I'm checking where that conditional render actually happens in the code. Now I want to verify whether the route-container copy is truly a sibling of the panel or nested inside it, since that determines whether it stays visible and anchored during the open animation. I confirm it's a sibling positioned absolutely within the route container, so both header and route-container versions share the same fixed anchor and there's no visible jump. Next I want to trace the equivalent logic in ChatView for panelLayoutControls—checking whether it's rendered similarly as a sibling or passed down as layoutControls into RightPanelTabs, so I need to look at that section of the code more closely.
27. browse_code — apps/web/src/components/ChatView.tsx
28. browse_code — apps/web/src/components/ChatView.tsx
💭 I'm tracing how the panel layout controls stay anchored in the same absolute position whether inline presence is true or false, so toggling between the route container and header copy doesn't cause any visible jump. During a sheet close, both the header copy and the sheet's own layout controls render simultaneously at the same anchor point for the exit duration—one fading out on top of the other—which seems like intentional overlap from an earlier fix rather than a bug worth flagging. I'm also noticing that when inline presence is true during close, the maximize control lingers until presence flips false, meaning a user could theoretically click maximize on a panel that's already collapsing, which seems like a minor edge case worth noting but not urgent. Now I'm ranking the issues I want to report, starting with the fact that the Sidebar primitive's
animationDurationMsprop does nothing on the desktop path since desktop transitions rely entirely on a CSS variable the primitive itself never defines, making the duration silently dependent on an ancestor component. I'm also noting a second candidate: since PreviewPanelShell's inline mode always renders a left border, the inner content width calculation is consistently off by one pixel, clipping content whenever the panel is animated. I'm weighing whether to fix this by adjusting the inner element's width calculation or by deriving the width directly from the host's content box instead. I want to include this as a concrete finding since the check defaults to failure and I favor high-signal results — the box-sizing math confirms the inner ends up 1px wider than intended. I also double-check thatmin-w-0on the inner flex container doesn't override the explicit width, so my reasoning holds and the wrapper being a block element doesn't change the conclusion. The overflow-clip is only applied when animated, meaning without animation the inner block just fills the wrapper naturally with no clipping, but toggling animation on shifts things by that 1px which gets silently clipped rather than causing visible overflow. I'll skip the duration-idiom point since it's more of an aesthetic/uncertain call, and instead go back to strengthening the first finding, noting thatPreviewPanelShelltakes ananimatedprop but no duration prop. Actually I realize the desktop sidebar case works today becauseAppSidebarLayoutsets the CSS var onSidebarProvider, andSidebaris a descendant of it — so there's no live bug there, just a fragile hidden dependency where the duration prop silently does nothing unless an ancestor happens to supply the var. That's really a dead-prop/contract issue in the shared primitive rather than something broken by the current change, so I should frame it that way rather than as a live regression. The preview duplicates the animation treatment with an inline transition duration rather than the shared variable, but since it reflects the same source value that's fine. I'm also checking whether the preview's cn import path matches conventions elsewhere in components/settings, and confirming the disabled slider state and reset button don't introduce inconsistencies — these all seem like minor, non-issues. Confirmed default param usage is fine. Now deciding where to post the inline finding about the inert animationDurationMs on desktop — since inline comments must land on actual diff lines, I'll target the props section around line 188-189 in sidebar.tsx rather than the transition line itself, explaining the fix should set the CSS variable on the root element. For finding B, I'm checking whether line 131 falls within the diff range (it does, since lines 128-136 were added). I'm verifying the border logic: when isInline is true and not maximized, the wrapper gets a 1px left border with border-box sizing, while the inner content still spans the full width, causing a 1px clip on the right edge. This is a small but real bug worth flagging, since it produces a concrete layout shift. The fix should derive the inner width from the wrapper's actual content box rather than reusing the same fixed value, though hardcoding a border-width subtraction feels fragile — I'm weighing whether the fixed width belongs on the wrapper instead, since the wrapper currently uses a fluid width that would reflow when the host collapses. I'll go with conditionally applying the width style only during animation, subtracting the 1px border, since w-full on the wrapper otherwise inherits the host's content box correctly at both 0 and full states. So actually during collapse the content translates rightward and gets clipped at the right edge, meaning the rightmost content disappears first — but for a panel sliding out to the right, it's really the left content that should disappear first to feel natural. So the inner content should probably be right-anchored instead, keeping it pinned to the right edge while the left edge does the clipping. This way the right-hand controls stay visible longest and the content appears to wipe away from the left, matching the expected slide-out-right motion. Though I'm second-guessing whether this counts as a real regression versus just an animation-polish nitpick tied to the same underlying code as the 1px issue. I'll settle on flagging the 1px overflow as a factual finding with a suggested calc fix, since Tailwind's border-box preflight combined with the newly added overflow-clip makes off-by-one clipping worth noting, while skipping the more subjective anchoring debate since that leans into aesthetic preference territory.
29. browse_code — apps/web/src/index.css
30. browse_code — apps/web/src/index.css
💭 Preflight confirms border-box is applied, so that finding holds. I'm double-checking the SheetPopup animation overrides for translate and opacity across sides, and everything appears to be covered correctly, including the top-side translate-y case. For sheet.tsx, the SheetBackdrop's inline transitionStyle correctly wins over the popup's duration-200 class since it targets transition-duration directly. I want to verify whether RightPanelSheet's now-required
animatedandanimationDurationMsprops could break other call sites, so I should grep for additional usages of RightPanelSheet to make sure the diff didn't miss updating one. I'm also tracing through usePanelPresence to confirm how the sheet mounts and unmounts based on the open state and keepMounted default. There's a subtle race here: since Base UI's own exit transition starts on the same render tick as the JS unmount timer, and both durations are set equal, unmounting could clip the very last frame of the fade — a minor edge case worth noting but not a real bug. Let me grep for RightPanelSheet usages to confirm nothing else depends on the old optional prop signature.
31. git_tools — RightPanelSheet
32. browse_code — apps/web/src/components/RightPanelSheet.tsx
💭 Odd — the git_grep results show the old code at those line numbers, contradicting the newer code I saw earlier in the file view. It's possible git_grep is defaulting to a different revision than the one I'm actually viewing, since the repo URL I passed included a specific branch path. The line numbers don't cleanly map to either the merge-base or the new version of ChatView.tsx though — the diff's old side starts around line 7580, while this grep hit 6663. That mismatch suggests the grep might be running against yet another stale index or revision I'm not accounting for. Since the earlier grep for panel-animation-duration correctly returned the new head content, the grep tool itself seems reliable — something else must explain this discrepancy. I should just directly view lines 6690-6710 in ChatView.tsx at head to confirm what's actually there rather than reasoning about it further.
33. browse_code — apps/web/src/components/ChatView.tsx
💭 The grep output looks stale, probably from a different revision, so I'll disregard it. I'm noticing that RightPanelSheet uses keepMounted on SheetPopup, and previously ChatView only rendered it conditionally when shouldUseRightPanelSheet and rightPanelOpen were both true along with an active thread reference. That means before, the sheet was hardcoded to
openand mounted/unmounted entirely on toggle, so Base UI never got a chance to play an exit animation—it just vanished. Now withopen={rightPanelOpen}and keepMounted, the component stays in the tree and Base UI can properly animate the enter and exit transitions. When panelAnimationsActive is false, animated is set to false which disables the transition entirely via override styles, giving the instant open/close behavior by default, though I want to check whether this same animated=false logic accidentally also disables the mobile sidebar's sheet animation, which previously always animated regardless. This mobile sidebar drawer would now pop open instantly instead of sliding, since it inherits the same panelAnimationsActive flag—that's the documented "off by default" intent, so it's consistent even if it feels like a UX change. I'm also considering whether the SheetPopup's animated=true default properly isolates other sheets from this change, which looks fine, and now I'm drafting two review comments: one about animationDurationMs not being honored on desktop due to hidden dependency on an ancestor CSS variable, and one about a potential 1px overflow issue in PreviewPanelShell caused by the border width. For the overflow fix, I'm weighing whether to hardcode a calc() adjustment subtracting the border width from the panel width, since the border-l is always applied in the affected case, or whether there's a cleaner CSS approach that avoids hardcoding the border size. Now I'm verifying the claim in comment #1 that the desktop code paths reference a CSS variable Sidebar never actually sets, while the duration prop only ever reaches the mobile sheet — checking specific line numbers to confirm this is accurate. Since the default animation settings imply a 200ms animated sidebar out of the box, but nothing sets the variable outside AppSidebarLayout, the transition silently becomes invalid and collapses to zero duration — breaking the primitive's contract for any other consumer. The fix is to set the CSS variable from the prop directly on the sidebar root wrapper, leaving AppSidebarLayout untouched, and I'm now drafting that diff while double-checking how props are spread on the container to avoid conflicts. I'll propose the style-based fix as the primary suggestion, mentioning the fallback as secondary. For attaching the review comment, I'm deciding between line 189 (the prop declaration) versus the changed lines in the diff—since the finding is really about the prop contract, I'll anchor it there and cross-reference the other affected lines (243, 293, 306, 618). Now I need to make sure the review comment includes all required fields like path, body, line, start_line, and side. Given GitHub's constraints on start_line needing to precede line, I'll use safe multi-line ranges within the diff hunks: lines 188-189 for the sidebar and 129-131 for the PreviewPanelShell, both of which fall entirely within added lines. With two moderate findings identified, I'm leaning toward marking the overall check as a failure, per the guidance that clear consistency or regression risks should fail the check. Reconsidering finding #1's severity, I think it genuinely qualifies as a UI consistency violation — the shared primitive exposes an animation-duration prop that only wires through to one of its two rendering paths, while the other path silently depends on an ancestor-provided variable. That's a real ownership violation worth flagging. Now I'm drafting the first review comment, noting that theanimationDurationMsprop only reaches the mobile sheet while the desktop transitions remain disconnected from it. Continuing that comment, I'm explaining that with default settings inside any provider other thanAppSidebarLayout, the CSS variable the desktop path expects would be undefined, causing the transition duration to silently collapse to zero and break the animation the prop claims to control — then suggesting the primitive should own its own duration variable, followed by a diff. For the second comment, I'm now examining a box-sizing issue where the host element uses border-box with a left border, meaning its content width is one pixel narrower than expected. I'm tracing through the width mismatch: the frozen inner track uses the full width while the host's content box is a pixel smaller, causing a one-pixel layout discrepancy that gets masked by overflow-clip along the tab bar's edge. I'm working out whether an inline suggestion fits on line 131, checking the exact indentation (ten spaces) from the surrounding lines before drafting a fix that subtracts one pixel from the animated width calculation. Since suggestions apply across a whole commented range, I'm deciding to span lines 130-131 together rather than mismatching start/end lines, and planning a separate unified diff (not inline) for the other comment covering lines 188-189.
34. modify_pr — Two ownership/geometry issues in the new panel-animation plumbing.
Agent Credits: 274 credits
Token usage: 40 uncached input · 1,516,204 cache-read input · 128,441 cache-write input · 41,653 output
Agent Credits may also include non-token charges from external tools such as web research.