Repository navigation
UI Consistency: 2 issues found
Two consistency issues found in the changed UI code.
apps/web/src/components/ui/sidebar.tsx(line 284) — the sidebar collapse is retimed to240ms+--motion-ease-drawer, but transitions driven by the same interaction were left atduration-200 ease-linear:SidebarGroupLabel(ui/sidebar.tsx:731) and the topbartransition-[padding-left]insets incomponents/usage/UsagePage.tsx:110,123andcomponents/settings/ProjectSettingsPanel.tsx:180,190. The same inset was updated inChatView,NoActiveThreadState,routes/settings.tsxandroutes/_chat.index.tsx, so those two pages now trail the sidebar edge. Suggested folding the transition into the sharedCOLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASSconstant.apps/web/src/components/preview/PreviewPanelShell.tsx(line 112) — the new inner content column is pinned towidthpx while its clip parent isw-fullof a host whoseborder-lsits inside the border box, so the right 1px of every inline panel surface is cropped and the content no longer reflows whenmax-w-fullclamps the host.
Both findings were posted as inline review comments.
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.
Prior-run findings verified as addressed at head 9202269: RightPanelResizeHandle is now rendered as a sibling of the overflow-hidden/[contain:layout_paint] wrapper (covered by the new PreviewPanelShell.test.ts case), and the persistent terminal drawer animates via a content-sized grid-rows-[1fr]/0fr wrapper rather than a wrapper pinned to the store height, so pointer-drag resizing is no longer clipped and the xterm element keeps its measured size while collapsed.
Checked and found no issue with:
floating-surface-motion/-resizablecomponent-layer contract vs Tailwind utility layer ordering; the:is([data-*-style])branch covers popups that carry the transition attributes themselves (menu, popover, select, tooltip) and the:has(> …)branch covers the wrapper-plus-child structure incombobox/autocomplete.[--floating-surface-duration:*]overrides and[data-instant]still win by specificity within the layer.- Dialog backdrop
transition-all→transition-opacity:dialog-backdroponly declares static background/backdrop-filter, so nothing else was being transitioned. - Right-panel retain/exit state machine:
RIGHT_PANEL_EXIT_DURATION_MS(200ms) matches theduration-200shell transitions;panelLayoutControlskeeps one absolute inset in both containers;selectSelectedRightPanelSurfacealready existed inrightPanelStore.tsand returns the selection while closed, which is what keeps the tab bar and retained content correct. - Launcher shortcut ownership:
keyboardShortcutsEnabledgates both the capture-phase key handler and thedata-surface-launcher-keysattribute read byChatView's type-to-focus guard, so a hidden-but-mounted panel no longer claims letter keys;inert/aria-hiddenprevent focus and AT exposure of the hidden panel and drawer. - No
position: fixeddescendants inside right-panel surfaces that the new[contain:layout_paint]wrapper could clip, and no overhanging absolute handles inside the terminal drawer (its resize strip istop-0, inside the aside).
🔍 Agent activity — 1 tool call
💭 I'm checking whether the previous issues with PreviewPanelShell's overflow clipping and ChatView's terminal drawer height were fixed in the current commit. The diff shows a new test for keeping the resize handle outside the panel content clip, which suggests that was addressed, but I need to verify if the overflow property is still causing problems. The resize handle issue looks resolved—it's now positioned outside the clipping wrapper as a sibling element. Now I'm examining the ChatView terminal drawer implementation to see how it's handling the grid-rows approach instead of a fixed height. I'm working through how the grid layout resolves row sizing when the wrapper uses
grid-rows-[1fr]withoverflow-hiddenduring a resize drag—specifically whether the 1fr value expands to fill the grid container or collapses to the content's max-content when the container itself is shrink-0 in a flex column. Looks like the changes addressed the earlier issues with clipping and grid layout. Now I'm reviewing the current diff to spot any new problems, particularly checking whether the transition duration constant of 200ms aligns with what's actually being used in the PreviewPanelShell and the right panel container in ChatView. The sheet implementation uses 240ms instead, and I need to verify whetherretainClosedRightPanelContentis even relevant for the sheet path. I'm also noticing thatrightPanelMaximizedis now computed differently using!shouldUseRightPanelSheetrather than the previouscanMaximizeRightPanellogic, and there's a removed call tosetMaximizedRightPanelThreadKey(null)that I should trace to understand the context. TheRightPanelTabscomponent is remounting based on visibility state with a conditional key, which might be intentional for resetting launcher state but worth confirming. Finally, I'm checking the CSS layer ordering in index.css where.floating-surface-motionis defined in the components layer, but Tailwind utilities likeduration-*would override it since utilities come after components. Now I'm looking at the selector specificity for the floating surface motion styles and whether the removed Tailwind data-starting-style utilities are causing conflicts with the new CSS approach. Thefloating-surface-motionclass handles the reduced motion case by setting scale to 1, which disables the scale animation. The tooltip'sdata-instantattribute uses a duration override that wins through specificity, but I need to verify whether that attribute actually sits on the popup element that has thefloating-surface-motionclass. Looking at the menu component, it sets a duration of 150ms, which is fine. But I'm noticing the menu popup previously had no transitions defined in its classes — they might have been in thedropdown-glassstyle from index.css, so I should check whether the new approach is accidentally applying transitions twice. I'm noticing thatfloating-surface-motionis applied to the SelectPrimitive.Popup element, and Base UI does add the transition data attributes (data-starting-style and data-ending-style) to the Popup. This means the select popup now has animation classes where it previously had none. The question is whether the Popup itself needs atransitionproperty defined for the exit animation to work, since Base UI waits for the transition on whichever element has the data-ending-style attribute. The:has(> ...)selector variant likely handles cases where the transition attribute lives on a child element rather than the Popup itself. I should verify where the data attributes actually get applied in menu.tsx and popover.tsx. There's also a potential concern about whether the:has(> :is(...))selector will work properly with Tailwind and Lightning CSS, though it should pass through fine as plain CSS in the @layer components. Thescale: 0.98andopacity: 0in the components layer should coexist fine with any Tailwind data-starting-style utilities since those would take precedence anyway. Now I need to look at the bigger behavioral changes in ChatView — specifically the rightPanelMaximized state, the removal of setMaximizedRightPanelThreadKey(null) at line 3401, the panelLayoutControls, and how RightPanelTabs visibility is being spread across the component. I need to locate where the right panel container gets rendered around lines 6680-6740 and understand its collapse animation behavior. I should also verify that theselectSelectedRightPanelSurfaceselector is properly defined in the store, since it's being imported but I want to confirm it actually exists. When the panel is closed but not maximized, RightPanelTabs renders with visibility hidden and zero width, which works fine. However, when closing a maximized panel, rightPanelMaximized stays true for 200ms during the fade-out animation, causing the chat column to collapse to zero width at the same time—so both elements are shrinking simultaneously instead of sequentially. After the 200ms delay, rightPanelMaximized flips to false and the chat column snaps back to full width instantly, which creates an abrupt appearance with no animation. The panelLayoutControls also remount during this transition, jumping from an absolute position to the header, which is a minor layout shift. The empty state has a focus-on-mount ref that calls focus when the component mounts, but since the panel stays mounted with visibility toggled, the empty state renders while hidden. Looking at the key prop, it forces a remount whenever visibility changes, which triggers the focus handler both when the panel opens and closes—stealing focus unintentionally when it should only happen on open. When the panel closes, the empty state remounts due to the key change, and focus() gets called on an element inside an inert container. Since inert elements can't receive focus, the call becomes a no-op and focus stays where it is, which is probably fine. But I need to check whether the empty state is actually rendered by RightPanelTabs itself or if it's part of the retained content during the 200ms close animation. I'm realizing the RightPanelTabs component stays mounted even when closed in inline mode, and it conditionally renders either an empty state or children based on whether activeSurfaceId is null. The key insight is that activeSurfaceId comes from a new selector that returns the selected surface's ID regardless of the panel's open state, whereas the old selector would return null when closed—so now when the panel is closed but a surface is selected, the children still render. There's a potential issue with the empty state callingfocus()on mount while the panel is inert and hidden, which wouldn't actually move focus. ThefocusOnMountbehavior is intended when the panel becomes visible, but I need to check whether toggling visibility causes a remount and whetherinertis properly scoped to inline mode only. I'm also tracking down wheredata-surface-launcher-keysis used and whether setting it to undefined when hidden prevents duplicates as intended. Now I'm examining the sidebar styling — specifically the offcanvas collapse state where the sidebar translates off-canvas with opacity changes. Thedata-collapsibleattribute is only set when collapsed, so the group selector should only apply in that state. I'm also checking whether addingscaletransforms to buttons, especially popover triggers, creates any containing block issues that could affect layout or stacking context. There's a potential conflict with transition utilities: the newtransition-[scale,box-shadow]on buttons replaces the previoustransition-shadow, and if call sites add their owntransition-colors, Tailwind's cascade means the later class in the CSS file wins rather than source order. This is the same conflict that existed before, though, andtailwind-mergewould handle it by removing the base transition in favor of the override. For the dialog backdrop, I need to check whether changing fromtransition-alltotransition-opacityloses any animations — specifically whether the backdrop-filter property (like blur) was being transitioned. Looking at the CSS, the starting and ending states only differ in opacity, sotransition-allwas effectively just transitioning opacity anyway. The dialog popup also switches fromease-in-outto a custom easing variable with motion-reduce support. For the Sheet popup, it usestransition-[opacity,translate]with motion-reduce disabling all transitions, but I'm wondering if Base UI's Sheet component relies on those transitions for its internal behavior. Looking at the Collapsible component, it also getsmotion-reduce:transition-none, which means the height jumps instantly under reduced motion since the transition is removed but the height still changes from the starting style. Now examining thefloating-surface-motionlayer... I need to verify where the.floating-surface-motionclass is actually placed in components like menu and combobox to ensure the:hasselector targets the right element with the transition attributes—if the class ends up on the wrong level, the enter/exit animations could break silently. The wrapper span holds the class while the inner Popup receives data-starting-style and data-ending-style attributes, which means the:has(> ...)selector correctly targets direct children. I'm noticing a concrete issue though: the wrapper previously hadtransition-[scale,opacity]but lacked the data-starting-style utilities that would actually define those initial values. Looking at the grep results, there's no global CSS rule for[data-starting-style]in index.css beyond the new ones, so the previous implementation probably relied on Base UI's own styling. The PR's intent is to add animation where there wasn't any before, which seems fine. The MenuPopup width-defaulting contract stays unchanged, and the base class now includes[--floating-surface-duration:150ms]without interfering with the width detection regex since that only inspects the consumer's className. I should quickly verify the autocomplete popup structure and confirm the class placement relative to the data attributes. I'm working through how the Popover popup receives thedata-starting-styleattribute and how it interacts with the Viewport's transition states. The selector.floating-surface-motion:has(> :is([data-ending-style], [data-starting-style]))targets the popup when its child (the Viewport) has elements with those transition attributes. I need to clarify whether Base UI appliesdata-starting-styledirectly to the Viewport itself, and how the opacity animation works with thedata-currentanddata-ending-stylecombination. The direct child of the popup is the Viewport, which doesn't itself receive the starting/ending style attributes — those are applied to the descendant panels inside it. Since the:has(> ...)selector only matches direct children, the popup won't trigger the fade-out rule based on its Viewport's contents. I should verify this holds for other cases like Tooltip and MenuPopup to ensure the selector doesn't accidentally match popups with direct children that have these transition attributes. The specificity works out becausedata-instantsets transition-duration to 0ms in the same layer, overriding the custom property value. Since Base UI appliesdata-instantto the popup element itself, the instant behavior should work correctly. Let me check the tooltip structure to see how the popup and positioner are organized. I'm noticing a potential conflict with the tooltip popup's styling—the animation utilities that were previously defined as Tailwind classes have moved into the components layer, but the popup's default opacity when open is already set to 1, so that should be fine. The real issue is that the floating surface motion resizable element has a transition property that includes width, height, opacity, and scale, which could interact unexpectedly with the tooltip's own transitions. Looking at the CSS cascade, the.floating-surface-motionrule sets the duration and timing, then.floating-surface-motion-resizablecomes later and overrides the property—since they're both in the components layer with equal specificity, the later rule wins, which is correct. The:is([data-ending-style], [data-starting-style])selector sets opacity to 0 and scale to 0.98 in the components layer, but Tailwind utilities likeopacity-*live in a higher layer, so there shouldn't be a conflict there. For the dropdown-glass popups, that's a utility in Tailwind that only handles background and backdrop-filter without touching opacity, so that's safe. The critical check is whether Base UI appliesdata-starting-styleto the Popup element as a direct child—it does for combobox popups, which means the:has(> :is(...))selector should work as intended. But there's a potential issue with the exit animation: the transition is on the parent span, not on the popup element itself, and Base UI observes the popup element's transition to know when the exit animation finishes. If Base UI unmounts the popup immediately because it has no transition, the exit animation on the parent would be cut off before it completes—though this is speculative since I'm not entirely sure how Base UI'suseAnimationsFinishedhook traverses the subtree. The real question is whether this counts as a concrete regression worth reporting. It's not a regression from previous behavior since there was no exit animation before, but it is an inconsistency: menus and popovers would get the exit fade while combobox and autocomplete wouldn't. That feels like a consistency issue rather than a hard violation, and without runtime evidence it's risky to speculate about Base UI's internals. I should focus on stronger, more concrete findings instead. The unlayered CSS wins over Tailwind utilities, which is fine for this single-use case. Thetranslateproperty replacement is consistent with Tailwind v4's approach. For the terminal drawer,@starting-styletriggers the entry animation when the drawer transitions from hidden to visible, but since it's now always mounted with visibility toggled, I need to check whether that animation fires correctly on state changes rather than just initial mount. I'm also verifying browser support for animatinggrid-template-rowsbetween0frand1frvalues — Chrome 107+, Firefox 116+, and Safari 16+ all support this interpolation, so that should be fine. The real question is whether1frresolves properly in an indefinite-height container when transitioning from0fr. In an auto-height grid, a single1frrow gets sized to its content (thefrunit treats the row asminmax(auto, ...)with flex resolved against the content), so the collapse animation from1frto0frworks as expected. But I need to think through what happens during drag resize when the terminal drawer's aside has its own explicit height style applied. Now I'm checking whether thevisibleprop is properly threaded throughPersistentThreadTerminalPaneldown toThreadTerminalPaneland ultimately toThreadTerminalDrawer. The panel receivesvisible={rightPanelOpen}from ChatView, and I need to verify that this visibility state is actually being passed through and respected in the right-panel terminal surface. I'm tracing through how thevisibleprop flows into ThreadTerminalDrawer — it looks like the diff addsvisible={visible}at line 1167, but I need to check what was happening before this change. The previous version of PersistentThreadTerminalPanel didn't seem to pass avisibleprop at all, so ThreadTerminalDrawer was getting it hardcoded or not receiving it in panel mode. Since the typecheck passes,visibleis probably optional with a sensible default. Whenvisible=falseduring the panel's exit animation, the drawer stays mounted but keyboard shortcuts disable, which is the intended behavior for a closed-but-retained panel. The preview surface also getsvisible={rightPanelOpen}, so during exit the webview may detach or hide immediately, leaving an empty panel for the animation. Now I'm thinking about theRightPanelEmptyStateremount behavior and what actually needs flagging here. u call focus() on a non-focusable element, focus doesn't move. I'm checking for other issues now —aria-hiddenandinertare both applied when the panel's hidden, which is redundant but harmless. The visibility styling uses width, translate, and opacity transitions together, and the inner content has a fixed width withshrink-0while the outer container constrains tomax-w-fullwithoverflow-hiddenand layout containment. Looking at the height constraints: the clip wrapper usesh-full min-h-0inside a flex column host that also hash-full min-h-0, and the content column inside getsh-fullas well. Thecontain: layout painton the clip wrapper creates a new formatting context, so I'm checking whether there are any absolutely-positioned descendants that might be affected by that containment. Actually, I'm realizing a potential issue with the width: the host iswidth: ${width}pxwithborder-l, so its content box is actuallywidth - 1pxdue to the border. But the inner content div is explicitly set towidth: ${width}px, which means it's 1px wider than the clip area and gets clipped on the right side by the overflow-hidden. So the real issue is that when the host gets constrained bymax-w-fullin a flex context, the content stays at its fixed width and clips rather than reflowing to fit the narrower space. but the inner content no longer reflows into the clamped host. The fixed inner width was added to prevent content from squashing during the collapse animation, but a better approach might be usingmin-widthor only pinning the width during the animation itself. This looks like a concrete issue: the inner content width duplicates the host width but ignores themax-w-fullclamp and the 1px border, making the content 1px wider than its clip boundary. Let me verify howmax-w-fullactually behaves on the host when the parent is the flex row. So the panel'smax-w-fullequals 100% of its container, and when the width exceeds that container, the host gets clamped to the container width while the clip wrapper becomes container minus 1. This means content gets cut off—the tab bar buttons and right padding disappear—and the content doesn't shrink anymore, whereas previously the flex children would have shrunk to fit. This happens on window resize when the viewport narrows, themaxWidthgets recomputed, and I need to check whetheruseResizableWidthis actually clamping the stored width value. The hook's clamp should pick this up automatically, so width follows the clamp. With the floor atMath.max(PREVIEW_PANEL_MIN_WIDTH, ...), on very narrow rows (less than 720px),maxWidthbecomes 360 while the row might only be 500px total, so the host width of 360 fits fine. To exceed the container, I'd need the container itself to be less than 360—which is very narrow and probably triggersshouldUseRightPanelSheeton narrow screens. Themax-w-fullclamp mainly kicks in at edge cases, though the host beingshrink-0in the flex row means if both columns can't fit, the sibling chat column shrinks instead. The 1px content clip on the right edge is a real issue—the panel sits at the far right of the app, so losing that 1px of right padding or scrollbar is nearly invisible visually, but it does mean right-anchored elements like the tab bar's padding or scrollbars get cut off slightly. I think the cleaner fix is to make sure the inner content div respects the full available width by either putting the border on the outer wrapper, subtracting the border width from the content's width calculation, or applying the panel width directly to the inner div so the host stays at full width. So the 1px overflow is a real clipping regression, even if minor. Given that this file was already flagged for clipping issues before and the check defaults to failure, it might not be worth the nitpick. Let me look at the sidebar changes instead since those could have more widespread impact. The sidebar uses fixed positioning with hidden/flex breakpoints, and when collapsed it shifts off-canvas with negative left positioning, zero opacity, and a translate transform. Since it's completely off-screen, the opacity and translate are mostly visual polish, though I should note that translate on a fixed element creates a containing block for children, and opacity below 1 establishes a new stacking context—but that's only during the collapsed state when it's hidden anyway. For the button styling, there's an active state that scales down slightly, and I'm using an arbitrary transition property that targets both scale and box-shadow. The scale property is animatable, so that works fine. With motion-reduce, it falls back to just transitioning the box-shadow. The duration gets merged properly by tailwind-merge at call sites. One thing to watch: when buttons render as menu triggers and scale on active, the scaled button might misalign with an anchored menu. The issue is that menus open on pointerdown while the trigger is scaled to 0.97, so the popup positions itself against the shrunk button rect. Since floating-ui's ResizeObserver doesn't detect transform changes, the popup stays positioned to that scaled rect even after the button returns to normal size on release — creating a sub-pixel offset that's barely noticeable but technically there. Now I'm checking the dialog backdrop CSS to see if switching from transition-all to transition-opacity makes sense. The dialog backdrop's blur is static, so using transition-all doesn't actually animate anything—transition-opacity would be equivalent. I notice the sheet backdrop has motion-reduce applied but the dialog backdrop doesn't, which is a minor inconsistency. The opacity fade itself is fine under reduced motion since the dialog keeps that transition enabled, so this seems like an intentional design choice rather than an oversight. Now I'm looking at where thatsetMaximizedRightPanelThreadKey(null)call was removed from—need to check what function that was in and understand the context around it. I'm checking whethercanMaximizeRightPanelis still referenced elsewhere in the codebase, since TypeScript's noUnusedLocals would catch it if it's orphaned. For the sheet rendering, theactiveSurfaceIdis correctly set to the selected right panel surface when the panel is open, so the active and selected states align. There's a semantic shift happening where the inline panel'sactiveSurfaceIdnow pulls fromselectedRightPanelSurfaceinstead of its previous source. I see thatselectSelectedRightPanelSurfaceis already defined in rightPanelStore, so that's not something this PR added. Now I need to check how the maximize toggle works around line 3528 wherecanMaximizeRightPanelis used, especially since the panel can now be maximized while closed. When the user closes the maximized panel, there's now a 200ms delay before the maximized state resets, which means the chat column stays collapsed during that window. The key question is whether the panel actually animates out during this time, or if the maximized state needs to toggle first for the animation to work. React 18 batches separate setTimeout callbacks as distinct tasks, so they trigger two renders—between them the panel is hidden but content retained, which is fine since both are invisible. When the user switches threads, the maximized state clears immediately. But there's a real issue: the layout controls can jump position 200ms after closing a maximized panel, since they're rendered in different branches depending on the maximized state, whereas closing a non-maximized panel moves them immediately as before. I'm noticing the panelLayoutControls uses absolute positioning in both layout states, staying at the same offset so it doesn't shift between containers. The row container is always relatively positioned, but the header's positioning changes depending on whether we're in Electron mode — it's relative in Electron but not in non-Electron, which means the absolutely positioned controls resolve against different ancestors depending on the mode. TheRightPanelMaximizeControlonly renders when the right panel is open and we're not using the sheet layout, so it disappears during the fade-out animation of a maximized panel closing. Looking at the actual rendering now, I'm seeing a potential issue with the PreviewPanelShell's inner content width — it's set to the full width while the clip wrapper is 1px narrower due to the left border, which could be clipping the right edge of all right-panel content. During the collapse animation, the host width animates down to zero while the content stays at its original width, getting progressively clipped. For the chat column,overflow-hiddenis applied instead of justoverflow-x-hidden, which adds vertical clipping. This is likely to prevent content from spilling during the maximize collapse when the width goes to zero, but I need to verify whether the terminal drawers inside the chat column have any intentional vertical overflow that this would affect. The overflow behavior change is minor—previously the chat column could scroll vertically if content overflowed, but now withoverflow: hiddenit clips instead, though this shouldn't happen in practice with the flex layout. Now I'm looking at another issue:activeThreadTerminalMountedhas self-referential logic where it readscurrentThreadIdsfrom the state updater parameter. Now I'm realizing the real problem: keeping the drawer mounted with zero height means the xterm terminal inside stays in the DOM with a 0-height container, which can cause the fit addon to compute dimensions from that zero height and resize the PTY to just 1 row—a classic bug that would reflow all the shell output. Before, when inactive threads were hidden withdisplay:none, the fit addon would bail out when it detected zero dimensions, but now with the grid-rows approach it stays mounted and tries to fit. The terminal's height stays at 300px even when clipped, so xterm dimensions don't get reset and ResizeObserver-based fit works fine. For width, same story. Now looking at theinertattribute approach — React 19 supports it as a boolean, so they can passinert={!visible ? true : undefined}to conditionally disable the element, and the React version here is likely 19 given theuse()hook in the combobox. The drawer wrapper combinespointer-events-nonewithinertwhen hidden, plusaria-hiddenfor good measure. With reduced motion enabled, the hide transition becomes instant but the element stays in the layout with zero-height rows. For the entry animation, they're using a@starting-stylerule at the top level that sets the initial state with zero-height rows, zero opacity, and a slight downward translate. Since this rule is unlayered, it takes precedence over Tailwind's translate utility, which is fine. The opacity also starts at zero in the@starting-styleblock. Now looking at another candidate approach... The pill container is conditionally rendered, so@starting-stylehandles the entry animation, but when it's removed there's no exit animation — asymmetric but acceptable. There's a potential issue though:@starting-styleblocks are at the top level of the stylesheet, while the reduced-motion variant is nested inside a media query, which should work fine. I'm noticing a conflict with thefloating-surface-motioncomponent-layer class versus Tailwind utilities — if consumers pass transition, duration, opacity, or scale utilities through className, they could clash with what's already defined. The bigger concern is whether any consumers are relying ondata-starting-styleordata-ending-styleattributes, or if they're passingtransition-noneexpecting it to override the popup's base transitions. I should search for these patterns in the codebase to see if removing the base transitions breaks anything. I'm noticing thattoast.tsxat line 742 still has the old inline transition pattern on a dropdown popup, which duplicates what the PR is trying to standardize withfloating-surface-motion. It's a consistency issue, but since the file wasn't the focus of this PR, I'll leave it alone rather than scope creep into unrelated cleanup. Now I need to verify something important about the popover implementation— specifically how Base UI'sPopoverPrimitive.Viewportinteracts with thedata-starting-styleanddata-ending-styleattributes. The Viewport's className uses descendant selectors with these attributes, and it also setsdata-instant:transition-noneon itself, which suggests Base UI applies transition-status attributes to the Viewport too. If that's the case, swapping popover content could cause the entire popup to fade out—a visible regression for multi-page popovers. I should check Base UI's Popover.Viewport implementation to understand whether it applies these attributes and how the rendered children inherit them. I'm checking Base UI's documentation for how the Viewport handles transition attributes — it looks like the Viewport itself carriesdata-transitioningrather than the starting/ending style attributes, which are applied to its children instead. That should keep things safe from unintended blinking during content swaps. The strongest issue I'm seeing so far is the PreviewPanelShell's 1px border and width mismatch, so let me dig deeper into how severe that actually is. So in non-inline modes there's no left border, so no width mismatch. But inline non-maximized mode has that border, creating a 1px discrepancy where the content box ends up narrower than the fixed inner width, causing the right edge to clip by 1px and shift the panel content. The persisted width measurement also gets thrown off since the resize handle sets the width but the visible content is actually 1px less. It's a real geometry regression from the recent changes, though I'm weighing whether it's significant enough to fail the check. The handle sits at the panel's left edge, so the width calculation is straightforward. Given that a 1px clip is a real bug even if minor, I could fix it by sizing the inner div with a calculated width that accounts for the border, or by moving the panel width to a CSS variable. The cleanest approach might be to apply the width directly to the inner content div and let the outer host animate through a different mechanism, or shift the border from the host to the clipping wrapper instead. so the host's content box is actually W-1 pixels wide. The simplest fix is probably to set the inner element's width tocalc(${width}px - 1px)to account for the border, or define the panel width as a CSS variable and use that in the calculation. Before the change, flex children fit within that W-1 space; now they're getting the full W, so content is 1px wider and gets clipped on the right. It's subtle—the right padding of elements like the diff columns and tab bar effectively shrinks by 1px—but it's a real geometric regression. The fix is straightforward enough that I think it's worth flagging as a finding. Let me verify I'm not overlooking something by checking how the ThreadTerminalDrawer interacts with the layout. The wrapper hasshrink-0with grid, and the chat column above it isflex min-h-0 flex-col overflow-hidden, so the drawer stays at its content height while the messages area shrinks to fill the remaining space. That part looks correct, but I need to trace through whether the drawer's sizing depends on being a flex item in a specific way. The grid usesgrid-rows-[1fr], which in an auto-height container behaves likefit-content— the row expands to fit its content. So the wrapper's height is determined by the drawer's content. Now I'm wondering if addingoverflow-hiddento the inner wrapper would clip the resize handle at the top edge of the drawer, especially if that handle has a negative offset overhang like the preview panel does. The handle positioning and grid row sizing are working correctly—as the aside height changes during a resize drag, the grid row expands to match, keeping the handle visible. But I'm realizing the transition applied to grid-template-rows might cause a delay in that responsiveness. The specified value stays1frin both states, so technically no transition should fire since the CSS property itself isn't changing—only the computed used size is. But browsers like Chrome actually do interpolate grid-template-rows transitions on the used values, which is why the0frto1frtrick works smoothly. So if the specified value remains1fr, the transition might not trigger at all, or it could create a mismatch between the instant height change and the animated grid row. The specified values stay interpolable (0fr to 1fr), so transitions only trigger on style changes, not content layout shifts—keeping drag smooth. When the terminal opens, the grid animates from 0fr to 1fr nicely. With the hidden state using grid-rows-[0fr], the wrapper collapses to zero height while the inner aside stays clipped at 300px, preserving xterm. Now I'm checking browser support for animating grid-template-rows—Chrome and Firefox have solid support, but Safari's handling of fr value interpolation is less clear to me. Since this is an Electron app with Chromium, the animation will work fine there, and if Safari doesn't animate it smoothly, the drawer would just snap closed instead of sliding, which is acceptable degradation. Let me search the codebase to see if anything else depends on that.hiddenclass I'm removing from the drawer wrapper. I also notice they've switched to usinginertfor hidden drawers instead ofdisplay:none, which properly prevents focus on inactive thread content. But now hidden drawers are rendered at zero height with their content clipped by overflow, so they're not painted but still take up layout space — and with multiple hidden drawers stacking up to the MAX_HIDDEN limit, that could add some overhead. There's a potential performance concern too: xterm instances in hidden threads might now render continuously since they're technically visible in the DOM, though xterm.js likely has optimizations like IntersectionObserver checks to skip rendering when elements aren't actually visible. I'll skip diving into that for now. The accessibility looks solid witharia-hiddenandinertboth applied to hidden drawers. Now I'm checking the CSS approach with@starting-styleand thedata-hiddenattribute... I'm checking whetherfocusOnMountworks correctly when the panel starts closed with no surfaces — theRightPanelEmptyStatemounts on page load while hidden inside an inert host, and callingfocus()on a node withtabIndex={0}might get blocked if inert is properly supported. The mobile sheet mode doesn't apply here since it only renders when open, but there's something else to consider about this setup. Looking at howinertgets applied: it's only set whenisInline && !visible, andRightPanelTabsreceives thevisibleprop fromChatViewasrightPanelOpen. In inline mode, inert applies correctly. The_chat.pull-requests.tsxfile also uses inline mode without an explicitvisibleprop, so it defaults to true and skips the inert — that's fine and unchanged. When the panel is closed on first load,focus()on an inert element does nothing per spec, which Chrome implements correctly. If the user opens the panel via keyboard shortcut, the remount triggers focus to move to the launcher, same as before. Now I need to think through what happens when the panel closes while focus is already inside it. I'm checking whether other places with padding-left transitions were missed—if some topbars animate at 240ms with the new easing while others stayed at 200ms linear, they'd desync during sidebar toggles, creating an inconsistency the PR introduced. Let me search for any remaining instances of those transition patterns. I'm noticing there are still several animation timing mismatches across the codebase — the topbars inProjectSettingsPanel.tsxandUsagePage.tsxare stuck at 200ms with linear easing for the sidebar toggle padding animation, whileSidebarGroupLabelhas the same timing issue, causing its margin and opacity to fall out of sync with the sidebar's new 240ms drawer easing. The sidebar width animation at 240ms with drawer easing now desynchronizes from the titlebar padding-left animation at 200ms linear on the Usage and ProjectSettings pages — they're both in scope but not in the diff, so I can only report this inconsistency in the summary rather than as inline comments. Actually, I can work around this by commenting on an adjacent changed line in the sidebar file itself, noting that the two unupdated headers and SidebarGroupLabel still use the old 200ms timing and create the visible desync. Since the PR already updated four of six such headers, flagging the two missed ones is directly aligned with the intent of the change, not unrelated cleanup. The real question is whether this is a symptom of a deeper issue—six call sites repeating the same hardcoded duration suggests this should be a shared token or variant rather than a literal value scattered across the codebase. That would be a more durable fix than manually updating each call site. The fix is to consolidate the transition styles into the sharedCOLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASSconstant so all six consumers automatically stay synchronized instead of each maintaining their own transition separately. I should anchor my comment on a changed line in the diff—NoActiveThreadState.tsxline 13 is a good target since it's a small file with a clear change. I'll point out that two call sites were missed and suggest moving the timing into the shared constant to prevent future drift. Now for the second finding about the border: the clip wrapper's content width is actually the host width minus 1px due to the border-left, while the inner div is fixed at the full width, which means 1px gets clipped on the right. The resize handle positioning remains correct since it's still outside the clip wrapper. Theoverflow-hiddenon the clip wrapper enforces this clipping behavior. But this creates a potential issue: if panel surfaces have absolutely-positioned elements that overhang their bounds—like internal split handles with negative left/right offsets, floating controls in the preview panel, or drag regions—those would now get clipped where they previously wouldn't have been. I need to search for any-left-1or-right-1positioning within nested surfaces that might be affected by this change. The RightPanelResizeHandle is the only element overhanging outside the clip boundary, which is what I want. I'm considering whether panel content like the preview's inline mini-player or resize handles within the DiffPanel might also overflow, but I don't think they do. On the 1px discrepancy, I'm fairly certain it's a real regression that I can trace back to the diff—the host element has a left border and box-border sizing, which accounts for the measurement difference. But I already reviewed this file and it was corrected properly last time. Adding a 1px complaint feels nitpicky, especially given the guidelines about reporting only concrete violations from changed lines and not failing on optional aesthetic preferences. A 1px clip is technically a geometry regression, not just aesthetics, but I'm questioning whether it's worth flagging. There's also the question of whether the panel's content being 1px wider than the visible area could trigger a horizontal scrollbar, though the overflow-hidden clip prevents that anyway. I'm thinking about a different angle on the wrapper'scontain: layout_paintproperty—the paint containment also clips fixed-position descendants. If any panel content renders fixed-positioned elements like an image lightbox or floating widget within the DiffPanel, those would be affected. Let me search through the panel components to see if any of them use fixed positioning directly. There's no fixed-position issue, so the 1px discrepancy is the main finding. Let me verify once more whether theborder-lon the host creates a compensating element in the clip wrapper — the wrapper isw-full, which means it's 100% of the host's content box width, so if the host has a 1px left border, the content box is actually width minus 1, making the clip wrapper that same reduced width while the inner content tries to fill the original width. During the collapse animation, the inner content stays fixed at full width while the host shrinks to 0. The clip wrapper is left-anchored and moves right as the host narrows (since it's the last flex item, its left edge shifts right while the right edge stays fixed at the viewport). The content inside is anchored to the clip's left edge, so it slides right with the panel and gets progressively cropped on the right side by the shrinking width — combined with the translate and opacity changes, this is the intended effect. Now I'm deciding whether to report the 1px issue — the check has already been reviewed once and the author addressed the previous findings. The stronger framing is that the inner content width duplicates the host width and ignores both the 1px border and themax-w-fullclamp that the existing test protects. This is a real geometry-ownership problem: when the row clamps the host, the surface content doesn't reflow and gets silently cropped instead. The fix could be to size the inner div from the same box as the clip (usingw-fullnormally orcalc(width - 1px)), or usemin-w-semantics. But if they set the inner tow-full, they lose the "no reflow during collapse" property, which is the whole point. A more precise approach would be to move the border from the host to the inner content div, so the host's content box equals the width directly. The correct solution is to keep the border on the host and set the inner width tocalc(${width}px - 1px)to account for the border-box. I'll suggest this in the comment, framing it as: the inner is pinned to the full width while its clip is width minus 1px because the host's border is inside the border-box, so the right 1px gets cropped—the fix is to subtract the border from the inner width or move the border onto the inner column. The anchor point is line 112 in PreviewPanelShell.tsx where the width style is being set. I'm confident about the border-box math here, so this is a real finding worth reporting as a minor issue alongside the main timing inconsistency. Now I'm verifying the animation desync more carefully—the sidebar gap animates its width over 240ms with drawer easing, while the topbar padding-left on those pages animates over 200ms with linear timing, creating a visible 40ms mismatch before the sidebar finishes expanding. I also notice SidebarGroupLabel in the sidebar component animates margin and opacity over 200ms linear when collapsing to icon mode, which now desynchronizes with the sidebar's 240ms width animation. This is another regression introduced by the PR changes. I'm deciding whether to anchor both findings in a single comment or split them across two—one on the sidebar file itself and another on the topbar component. I'm debating whether comment A is worth the overhead—the 40ms label desync is minor compared to the broader timing mismatch pattern in B. Since GitHub inline comments are file-specific, I can't easily merge them across sidebar.tsx and NoActiveThreadState.tsx. I'll post B and C as separate comments, then reference A's file location within B's body to keep the feedback cohesive. Actually, I could anchor one unified comment on sidebar.tsx at the line where the shared constant was changed, covering all the desynchronized transitions across SidebarGroupLabel, UsagePage headers, and ProjectSettingsPanel headers. That's cleaner—one comment naming the root cause and the fix needed. rrced withCOLLAPSED_SIDEBAR_TITLEBAR_INSET_CLASS, so consolidating the transition timing into that constant would prevent the six locations from getting out of sync. For the preview panel, the inner column's fixed width gets clipped by its parent's full-width border box, leaving the right edge cropped by 1px — subtracting the border width from the column keeps it from reflowing while avoiding that crop. Adjusting the style property... I'm going to post the review with those two inline comments. Let me double-check the first comment against the "don't demand unrelated cleanup" rule — the PR retimed the shared sidebar animation, and both UsagePage and ProjectSettingsPanel are direct consumers that render the same collapse class, so they're legitimately affected by this timing change. I should verify those pages are actually live routes and not deprecated. Both pages render the sidebar headers, confirming they're active workspace pages. I'll frame this as a suggestion and post it.
1. modify_pr — Reviewed the motion/visibility refactor against the shared component system. Both findings from the previous run look addressed (the resize handle now sits outside the panel content clip, and the terminal drawer animates with a content-sized grid row instead of a pinned store height). Two new consistency issues below.
Agent Credits: 451 credits