Skip to content

fix(web): space composer shoulder tabs

MacroscopeApp / Macroscope - UI Consistency failed Aug 21, 2026 in 4m 18s

UI Consistency: 1 issue found

apps/web/src/index.css (L780-782) — New [data-chat-composer-form="true"]:has(.chat-composer-shoulder-tab) { padding-top: 1.75rem } places ordinary single-owner spacing in global CSS. ChatComposer owns the form and already computes whether a shoulder tab renders (tasks badge L2942-2947, stash badge L2957-2960 with stashQueue.length > 0), so the reservation belongs at the owner as a conditional Tailwind class (pt-7) rather than a global :has() rule inferring component state from a descendant class string. The literal 1.75rem also duplicates the -top-7 offset hardcoded in ComposerTasksBadge and ComposerStashBadge, giving three places that must stay in sync.

An inline review comment with the suggested fix has been 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/index.css (composer/glass-shell section, lines 757-1200), apps/web/src/components/chat/ChatComposer.tsx (form and shoulder-tab render sites), apps/web/src/components/chat/ComposerStashBadge.tsx, apps/web/src/components/chat/ComposerTasksBadge.tsx, apps/web/src/components/ChatView.tsx (glass shell/host wrappers, composer overlay).

Checks performed and cleared:

  • Cascade/layer scope: the new rule sits inside @layer components (564-1347), so call-site Tailwind utilities still win; no specificity conflict with existing rules.
  • Visual composition: when a shoulder tab is present, the existing :has(:is(.chat-composer-top-drawer, .chat-composer-shoulder-tab)) rules already disable .chat-composer-glass-shell::before, .chat-composer-glass-host::after and the host shadow, so the extra 1.75rem of shell height is not painted with stray glass, outline, or clip-path distortion.
  • Selector matching: shoulder tabs only carry chat-composer-shoulder-tab in placement="tab" mode (inline fallbacks use Button primitives without the class, asserted by ComposerTasksBadge.test.tsx), and ComposerStashBadge returns null at count === 0, so :has() does not match spuriously. Top-drawer and collapsed-mobile states never render tabs, so no double reservation.
  • Accessibility/interaction: no control semantics, focus, or keyboard behavior changed by this patch.

The remaining concern is ownership/duplication only, not a rendering regression.


🔍 Agent activity — 1 tool call

💭 I'm checking the diff that adds a global CSS rule targeting chat composer forms containing a shoulder tab element, and I want to see the surrounding context in index.css around line 780 to understand which block it belongs in and whether it should instead live in the owning component. I want to check lines 1030-1180 in index.css for existing rules around shoulder tabs and composer padding, and look for where data-chat-composer-form is used. I notice the data-chat-composer-form attribute only shows up in a closest selector in ChatComposer.tsx, so it must be set elsewhere. I should search for "chat-composer-form" to find where it's actually defined. I want to check index.css around lines 860-900 and the ChatComposer.tsx form element to find where data-chat-composer-form is applied and how padding-top is currently handled. I'm trying to find where that data attribute gets set, searching the repo for its usage, but ChatComposer.tsx seems to be flagged as binary by grep, which is blocking my search. Let me just open the file directly and look around the form element instead. I'm wondering if the glass shell classes might be applied through a provider helper rather than directly in the TSX files. The real issue I'm flagging is that the new global CSS rule adding top padding when a shoulder tab is present should really live as owner-specific spacing in ChatComposer.tsx rather than as a global rule. The component already tracks whether shoulder tabs render via its own state, so duplicating that logic in a :has() selector violates the principle that source-level logic should take precedence over clever selectors tied to consumer class strings. I'm also checking whether the padding actually solves the clipping problem — since the tabs are absolutely positioned relative to an inner div, adding padding to the form pushes that inner div down enough to keep the tab within bounds, which seems like a reasonable fix mechanically, though the ownership concern still stands. I want to verify there's no regression from the stash badge rendering conditionally, and whether the drawer state avoids double-padding when tabs aren't shown — that part checks out. Now I'm tracing which @layer this CSS block sits inside, since that affects cascade specificity against the form's Tailwind utility classes. Since the new rule sits in the components layer, utilities still take precedence at call sites, so that's fine. I'm trying to confirm this is the correct fix for the shoulder tab overflow issue—likely the tab at -top-7 was clipping or overlapping content above, and the added padding-top reserves space for it. I want to check whether this padding could cause a regression given the form's width and margin constraints. I'm also checking whether the inline placement variant of the badges carries the shoulder-tab class, since the test expects it not to for the inline fallback—I need to look at ComposerStashBadge's inline variant to verify consistency with ComposerTasksBadge. Since ChatComposer already computes showTasksTab and showStashTab, the owning component can apply a conditional Tailwind class like pt-7 directly on the form rather than relying on a global :has() selector, which fits the principle that one-owner presentation logic belongs in the owning module as static classes. I'm also double-checking the actual geometry: the tab sits absolute -top-7 inside a relative container, so adding padding-top to the form pushes that container down by exactly the tab's offset, aligning the tab flush with the form's padding-box edge. I want to verify this spacing math lines up correctly with the tab's h-8 height and pb-1 overlap before treating this as a clean fix. I'm also checking whether the glass shell styling is applied anywhere in the TSX, since the earlier search found no "glass" references in apps/web — it might be constructed dynamically through a composer frame class name, so I should search for "glass-shell" directly. I realize the glass shell wrapping the form has its own ::before painting the glass surface, but that's disabled when a shoulder tab is present since the main surface paints its own glass instead. So adding top padding to the form just grows the shell taller without creating any visible extra glass area. But there's also the context-strip variant with a clip-path shape, and when the shell's ::before is hidden, the context strip paints its own glass via a separate ::before rule, so that path should still work fine. I want to check ChatView around lines 6480-6560 to see if there's already some existing spacing mechanism reserved in the layout before I add new padding. I'm tracing how the glass shell's ::before pseudo-element gets suppressed when a shoulder tab exists, since that rule hides the glass overlay across the extra space — but I notice the glass-host's ::after is also disabled in that case, while the main surface paints its own layer instead. For the with-context variant, the continuous clip-path spanning composer and strip is similarly hidden when a shoulder tab is present, so the composer and context strip paint their borders separately without distortion from the added shell height. I'm now checking whether the glass-host's box-shadow rule introduces any similar positioning concern. Since the shoulder-tab state already disables shell-level painting, the visual result holds together consistently. The real question becomes ownership: this padding-top reservation is ordinary one-owner spacing that belongs in ChatComposer.tsx as Tailwind classes rather than a :has() selector tied to descendant class names, especially since the 1.75rem value duplicates a -top-7 offset already used elsewhere. But I notice the existing index.css already uses :has(.chat-composer-shoulder-tab) for glass composition, which weakens my case somewhat — there's precedent for this pattern. Still, that precedent applies to cross-component coordination (ChatView reacting to tab presence it can't otherwise know about), whereas here ChatComposer itself renders the tabs and already has direct access to that state, so a prop-driven solution is cleaner and more appropriate. I'm also checking for concrete regressions: the form's padding-top applies whenever a tab exists, tabs aren't rendered in the mobile collapsed composer state so that's fine, and I'm considering whether view-transition snapshot geometry for mobile route transitions could be affected, though nothing concrete stands out yet. Since the overlay is bottom-docked, growth extends upward and pushes the banner stack up, which resolves the overlap issue. But in the draft hero state, the overlay is centered instead, so adding top padding would shift the composer down slightly — and since stash badges can appear even in draft state, I need to check whether that minor shift actually matters there. The headline above the composer is anchored to the bottom of that same container, so it moves up along with the container's growth, which seems fine. The real question is whether the ownership violation I found is severe enough to fail the check, since the rules only call for failing on clear consistency or regression risks, not aesthetic nitpicks — and padding-spacing decisions on a form that belongs to a single owner component seem like exactly the kind of local geometry the ownership rules say should live in that owning module. Since ChatComposer already computes the tab-presence condition internally, the cleanest fix is to move the conditional padding into ChatComposer itself rather than leaving it in index.css. I'll report this as a single inline finding with a suggested source-level fix and mark the check as failed, but first I want to pull the exact conditional logic from ChatComposer around where the tab badges are rendered so my suggested fix matches the real code. The smallest fix is deriving a boolean for whether a shoulder tab exists and applying top padding conditionally on the form. I also want to flag that the 1.75rem value duplicates the offset used in two badge components, so keeping it in global CSS risks drift between them. I'll post an inline comment on those specific lines in index.css.

1. modify_pr — One ownership finding on the new composer padding rule. Details inline.


Agent Credits: 125 credits