Skip to content

fix: address attachment upload review findings

MacroscopeApp / Macroscope - UI Consistency failed Aug 24, 2026 in 4m 59s

UI Consistency: 2 issues found

Two findings on the new attachment-upload retry controls.

  • apps/web/src/components/chat/ChatComposer.tsx (~L3266-3277): the failed-upload retry Button conveys upload.reason via the native title attribute instead of the shared Tooltip/TooltipTrigger/TooltipPopup primitives. The repo enforces t3code/no-native-title-tooltip ("Use Tooltip + TooltipTrigger + TooltipPopup from components/ui/tooltip"); the rule only skips this because it ignores capitalized components, while Button forwards title onto the native <button>. The reason string is the only place the specific failure is surfaced, so it is unreachable on touch and keyboard focus, and it renders unlike the Tooltip used a few lines above for the non-persisted draft warning.
  • apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx (L149-159): same native-title tooltip on the screenshot-annotation retry button; needs the shared Tooltip primitive (new import in this file).

The progress overlay pointer-events-none regression flagged in the previous run is fixed on this head commit.

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.

Verification performed:

  • Confirmed size="icon-micro" / variant="ghost-muted" and size="icon-xs" / variant="ghost" are existing apps/web/src/components/ui/button.tsx contract values, and that the new retry buttons match the variant used by the adjacent remove buttons in each surface, so no primitive overrides or ad-hoc geometry were introduced.
  • Confirmed the new progress indicators reuse local static Tailwind at the owner (no global CSS, theme variable, or mask changes in this PR), and that the uploading overlay in ChatComposer.tsx now carries pointer-events-none so the full-tile Preview ${image.name} zoom target stays clickable.
  • Located the enforced convention behind the findings: t3code/no-native-title-tooltip registered at vite.config.ts ("error") with implementation in oxlint-plugin-t3code/rules/no-native-title-tooltip.ts; the rule matches only intrinsic lowercase elements, which is why <Button title=...> slips through. The house pattern (Tooltip + TooltipTrigger render={<Button aria-label=... />} + TooltipPopup) is used in apps/web/src/components/preview/PreviewChromeRow.tsx, apps/web/src/components/chat/MessageCopyButton.tsx, and apps/web/src/components/chat/PanelLayoutControls.tsx. The single existing <Button title=...> precedent (apps/web/src/components/Sidebar.tsx:3533) only duplicates its own aria-label inside a menu popup, so it does not hide unique information the way upload.reason does.
  • Server, contracts, and docs changes were reviewed as context only; they fall outside this check's include patterns.

🔍 Agent activity — 12 tool calls

💭 I'm checking whether the previously flagged pointer-events issue in ChatComposer.tsx was actually fixed—confirming the diff now includes pointer-events-none on that span. Now I'm scanning the other in-scope UI files for any remaining findings. I'm noticing an inconsistency in Button variants across files—ChatComposer uses ghost/icon-xs while ComposerPreviewAnnotationCards uses ghost-muted/icon-micro, so I need to verify these variants actually exist. There's also a discrepancy in retry UI patterns: one uses a plain title attribute for the failure reason while others use a proper Tooltip component. I also want to trace where the new RotateCcwIcon import in ChatComposer is coming from.

1. git_tools — icon-micro|ghost-muted

2. git_tools — RotateCcwIcon

💭 I'm checking where ChatComposer imports RotateCcwIcon from, looking at the import block around that line in the diff.

3. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 Now I want to check the actual ChatComposer thumbnail region and its retry button, looking around lines 3140-3290.

4. browse_code — apps/web/src/components/chat/ChatComposer.tsx

💭 With that fixed, I'm scanning for other consistency issues. The retry button's background opacity values differ slightly from the existing remove button's pattern, but that's a trivial aesthetic difference not worth flagging. Using title={upload.reason} for hover text stands out since the same block already uses the Tooltip primitive elsewhere — worth checking how often native title attributes appear across the codebase versus Tooltip usage. The other items I'm reviewing — progress span styling, the disabled-reason logic, the upload effect, and the conditional prop spread — all look fine and don't raise consistency concerns.

5. git_tools — title={

💭 Using title on buttons is unusual, so I want to check how often the native title attribute actually shows up on Buttons across the repo.

6. git_tools — ^\s+title={?"?[A-Za-z]

💭 Most of these are component props rather than native HTML title attributes, so I should check whether the native title attribute shows up on Button elsewhere in the codebase, and also look for a docs or AGENTS.md convention around tooltips versus title usage.

7. browse_code — In apps/web, is there a documented convention (AGENTS.md, docs, lint rule) about using the Tooltip primitive instead of the native HTML title attribute for hover hints on buttons? Also, are there existing examples of a native title attribute passed to the shared Button component (apps/web/src/components/ui/button.tsx)? Give file paths and line examples.

💭 I'm noticing the repo actually lints against native title tooltips in favor of Tooltip primitives, but the new retry buttons slip past that rule since they use title on a capitalized Button component that spreads it to the native element—so it's a real consistency violation, since it renders as a browser tooltip rather than the app's Tooltip and won't work for touch or keyboard focus. Let me check if there's precedent for this by searching the codebase for other <Button ... title= usages.

8. git_tools — title={

💭 I found one precedent using title on a similar button, but both new retry buttons here use title to convey the failure reason—the primary way users learn why an upload failed. Since there's a repo lint rule against using the native title attribute as a tooltip, and ChatComposer's adjacent overlay already uses Tooltip for the same purpose, this inconsistency is worth flagging.

9. browse_code — apps/web/src/components/Sidebar.tsx

💭 The precedent case involved duplicating an aria-label with a title, which is different — here upload.reason carries unique information not shown elsewhere, so a native title fails keyboard and touch accessibility and breaks the documented rule. Since adjacent code in the same block already uses Tooltip for this exact purpose, this looks like a genuine finding. I should check whether the button's disabled reason already surfaces this info elsewhere — it's generic ("Retry or remove the failed image"), so the specific reason is only in the inaccessible native title, especially bad on touch devices. I'll flag both ChatComposer:3273 and ComposerPreviewAnnotationCards:154 with precise inline comments. For ChatComposer, Tooltip is already imported so an inline fix using TooltipTrigger with a Button render prop is feasible; for the other file, Tooltip isn't imported, so a cross-file diff makes more sense than an inline suggestion. Let me look at the existing Tooltip pattern in PreviewChromeRow.tsx to model the fix.

10. browse_code — apps/web/src/components/preview/PreviewChromeRow.tsx

💭 I'm adding a retry button that appears on failed uploads, styled as a small ghost icon button positioned over the attachment preview, triggering the retry action on click. For this, I'll present a unified diff for the button block and separately note the import addition needed for the Tooltip component. I should also check whether the retry button's sizing and variant match the styling conventions used elsewhere—like the remove button on annotation cards, which likely uses a smaller icon size and muted ghost variant given the tighter card layout compared to the larger thumbnail tile.

11. browse_code — apps/web/src/components/chat/ComposerPreviewAnnotationCards.tsx

💭 I'm checking a few more class ordering and styling details across the composer components, mostly finding these are harmless variations, and now looking at whether ChatView passes any new attachment-related props. I'm also confirming the retry button and progress overlay positions don't overlap since they represent mutually exclusive upload states, and checking that error tooltips don't collide with other UI elements. I've settled on two inline comments to post, both concerning native-title findings in the diff, and now I'm mapping the exact line ranges in each file to make sure my inline suggestions align with the added lines.

12. modify_pr — Two new retry actions convey the upload failure reason through the native title attribute instead of the shared Tooltip primitive. The repo enforces t3code/no-native-title-tooltip ("Use Tooltip + TooltipTrigger + TooltipPopup from components/ui/tooltip") — the rule only skips these because it ignores capitalized components, and Button forwards title straight onto the native <button>. Since upload.reason is the only place the specific failure ("Unsupported image type", "Upload rejected (500)", "Not connected") is surfaced, a native tooltip makes it unreachable on touch and keyboard focus, and it looks unlike the adjacent Tooltip in the same composer block.


Agent Credits: 198 credits