Skip to content

fix(web): keep server update banners flush with the composer - #8000

Open
t3dotgg wants to merge 1 commit into
mainfrom
t3code/fix-server-update-banner-overlap
Open

fix(web): keep server update banners flush with the composer#8000
t3dotgg wants to merge 1 commit into
mainfrom
t3code/fix-server-update-banner-overlap

fix(web): keep server update banners flush with the composer

ceeec6b
Select commit
Loading
Failed to load commit list.
MacroscopeApp / Macroscope - UI Consistency failed Aug 23, 2026 in 3m 0s

UI Consistency: 1 issue found

apps/web/src/components/chat/ComposerBannerStack.tsx:202before:mask-none cancels the shared composer drawer attachment-seam mask at a single call site.

The shared rule :is(.chat-composer-drawer-surface, .chat-composer-top-drawer)::before (apps/web/src/index.css:887) draws the banner's glass fill, 1px outline (border-radius: 16px 16px 0 0) and shadow, then masks away the bottom --chat-composer-attachment-overlap (calc(1rem + 1px)) — precisely the strip the composer is pulled into by .chat-composer-drawer-slot { margin-bottom: calc(-1 * var(--chat-composer-attachment-overlap)) }, while the call site still reserves that space with pb-[calc(var(--chat-composer-attachment-overlap)_+_0.375rem)]. Removing the mask makes the pseudo-element paint in that strip: a second translucent glass fill plus backdrop-filter beneath the composer's own shell, a stray 1px bottom border line, and square bottom corners outside the composer's 22px top radius. The change also affects every attached front banner, not just the server-update banner, diverging from the other attached drawers (ThreadSyncStatusPill, ComposerCommandMenu, ComposerStashMenu).

Suggested fix: revert the call-site class and, if a real gap exists for the server-update banner, address it in the shared drawer contract (overlap variable or the .chat-composer-drawer-slot + .chat-composer-drawer-slot::before seam rule); the matching assertion at ComposerBannerStack.test.tsx:56 should be dropped with it.

An inline review comment with a suggested fix was posted.

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.

Files examined: apps/web/src/components/chat/ComposerBannerStack.tsx, apps/web/src/components/chat/ComposerBannerStack.test.tsx, apps/web/src/index.css (composer drawer surface/seam rules, lines 789–960 and 1100–1170), apps/web/src/components/ChatView.tsx (banner stack / composer layout at 6720–6790, banner item construction at 2110–2200), plus the other drawer-surface consumers (ThreadSyncStatusPill.tsx, ComposerCommandMenu.tsx, ComposerStashMenu.tsx).

Secondary note included in the inline comment: Tailwind's mask-none overrides only mask-image, while the source rule declares both -webkit-mask-image and mask-image — a pairing this stylesheet applies consistently for every mask — so the emitted production CSS should be inspected before relying on the utility to neutralize the mask uniformly across engines. This was reported as supporting risk, not as the primary finding.


🔍 Agent activity — 13 tool calls

💭 I'm checking the diff in ComposerBannerStack.tsx, which adds a before:mask-none class to override a mask style on a pseudo-element.

1. browse_code — apps/web/src/components/chat/ComposerBannerStack.tsx

2. browse_code — apps/web/src/components/chat/ComposerBannerStack.test.tsx

💭 I want to understand what those chat-composer-drawer CSS classes do and how the before-pseudo-element mask relates to the server update banner overlap fix, so I'm going to grep index.css for the mask-none change.

3. git_tools — chat-composer-drawer-surface|chat-composer-drawer-attached|chat-composer-attachment-overlap|chat-composer-banner-stack-cap

4. browse_code — apps/web/src/index.css

💭 I'm realizing the ::before mask intentionally leaves the bottom edge transparent so the drawer blends seamlessly into the composer below, matching the "open attachment seam" comment. The PR's before:mask-none override cancels that mask for the attached banner case, so that bottom region now behaves differently instead of staying transparent. Since the drawer slot has a negative bottom margin equal to the overlap, without the mask, the banner's glass surface—with its border, backdrop blur, and rounded top corners—will now paint over the composer's top edge instead of letting it show through, creating a visible double-surface or border artifact where they overlap. I'm also second-guessing whether Tailwind's mask-none even fully suppresses masking across browsers, since it sets the unprefixed mask-image but may leave -webkit-mask-image untouched, meaning Safari or older Chromium/Electron builds could still apply the original mask despite the utility being added. The real question is layering: whether the shared drawer-surface mask rule lives in @layer components versus Tailwind utilities in @layer utilities, since that determines whether before:mask-none actually overrides it, and I want to confirm this override breaks the shared contract used by other components like the sync status pill and composer menus, creating inconsistent rendering across them.

5. git_tools — ^@layer|^}

💭 I'm tracing through the CSS file's brace structure to figure out where the components layer actually closes, checking the line numbers against what the file view is showing me. The key insight is that Tailwind v4's mask-none utility only outputs mask-image: none, not the -webkit-mask-image property, so since it's in the utilities layer which comes after components, it overrides mask-image but leaves the webkit-prefixed mask property from the component layer still active. I'm uncertain whether Tailwind actually emits both prefixed properties here without checking the source directly, so I should set that argument aside. The more solid, unambiguous point to make is about design ownership of the mask on that chat composer element. That padding calculation still assumes the overlap gets masked away, which is now broken. I'm also realizing this attached banner is just one of four consumers sharing the same drawer-surface contract, so disabling the mask only here creates inconsistency — a seam would appear above the composer whenever a banner shows but not with the other attached drawers. There's also a sibling rule handling stacked slots that reinforces this is meant to be handled at the contract level, not with a one-off override, so I want to check the git history to see if there's a real bug this change was meant to fix.

6. git_tools — ComposerBannerStack|mask-none

7. git_tools — apps/web/src/components/chat/ComposerBannerStack.tsx

💭 I'm trying to figure out why the commit removed the mask—it seems the author noticed a visual gap between the banner and composer, and the mask combined with negative margin was meant to make the banner's surface end exactly flush at the composer top rather than overlapping or leaving space. Let me check ChatComposer.tsx, though the search flagged it as binary-ish, possibly minified. Regardless of exact paint order, the mask enforces the shared seam contract — disabling it reintroduces the composer's own bottom border (1px, part of its rounded-top glass shell) becoming visible right where the mask would normally hide it, creating a stray horizontal line and doubled backdrop-filter darkening across the overlap region. That's likely the visual bug the design is flagging. I'm also thinking about whether stacked drawer slots rely on the same masked-bottom mechanism to stack seamlessly, and whether Tailwind v4's mask-none utility actually emits both -webkit-mask-image and mask-image: none, or just one — I should check the Tailwind version and any generated CSS in the repo to confirm.

8. git_tools — mask-image|mask-[|webkit-mask

9. git_tools — "tailwindcss"

💭 I'm noticing the codebase pairs -webkit-mask-image with mask-image for browser compatibility, and I want to verify that Tailwind v4's mask-none utility only outputs mask-image: none without a webkit-prefixed version — checking the docs confirms this is the case. But there's added uncertainty since the Vite plugin uses Lightning CSS with browserslist targets, which could inject -webkit-mask-image automatically depending on the project's configured targets. Since I can't verify the actual build output, I'll treat this as a secondary risk rather than the primary finding, and focus my conclusion on the ownership/regression angle instead. I should also check the ChatView usage and the other masked overlap section in index.css to firm up my understanding before finalizing.

10. browse_code — apps/web/src/index.css

11. browse_code — apps/web/src/components/ChatView.tsx

💭 I'm tracing the stacking context math: the banner stack and composer shell both end up with z-index effectively 0, so it comes down to DOM order — the composer shell, appearing later, paints on top of the banner despite the drawer's negative margin pulling it up visually. Since the composer shell's ::before is only inset to its own rounded rect, the banner's unmasked square bottom corners would peek out past the composer's 22px rounded corners, and because the composer glass is semi-transparent, the overlapping banner surface underneath doubles up — darkening the top strip of the composer and leaving a stray 1px line at the seam. That's exactly the visible artifact I'm chasing, and I suspect the original fix intent was just to close a gap without accounting for this overlap. I'm now wondering if there's a real edge case: the ThreadSyncStatusPill can sit between the banner stack and the composer, and since it's also a drawer-slot with its own surface and negative margin, the banner's masked bottom might be overlapping the pill instead of the composer in that scenario — which would change how the mask needs to behave. I suspect the actual regression happens when whatever renders below (composer or pill) uses translucent glass, causing the unmasked surface underneath to double-paint and expose the rounded corners/border. I want to check whether the update banner passes a special className in ChatView's systemComposerBannerItems, so I'm going to look at that section around lines 2120-2200.

12. browse_code — apps/web/src/components/ChatView.tsx

💭 I have enough to work with now — this change actually affects every composer banner, not just the server-update one, which is a scope concern worth flagging. I'll post a single focused inline comment on ComposerBannerStack.tsx line 202 suggesting removal of before:mask-none, rather than splitting it across multiple comments. I'll also note that the corresponding test assertion at line 56 in ComposerBannerStack.test.tsx would need updating, and mention that if there's a genuine visual gap, the proper fix belongs in the shared drawer overlap logic rather than disabling the mask at this one call site. Then I'll submit this as a review comment via github_pr_review with state "comment."

13. modify_pr — One finding: the new before:mask-none cancels the shared composer drawer attachment-seam mask at this call site, which reintroduces the surface/border overlap over the composer instead of removing it. Details inline.


Agent Credits: 81 credits