refactor: drop the vendored chat and the copied brand tokens - #25
Conversation
These were the vendored AI Elements versions, which export fifteen, eight and two symbols where Buddy renders two, three and one. Ours carry the same markup and drop the rest, which is what let the scroll area, the button group and the separator go with them. The suggestion strip's ScrollArea container was never rendered: both callers lay the strip out themselves.
The prompt input was 1307 lines exporting a model picker, a command palette and an action menu that Buddy never rendered; it kept the tooltip, hover card, select, dropdown menu, command and dialog components alive between them. Reduced to the eleven symbols the chat imports, all six fall out, and with them radix-ui, cmdk and streamdown. Attachments are ours now as well. Buddy accepts images and lays them out as a grid, so the variant system and the media-type table are gone. Nothing left needs a primitive library: the composer is a form, a textarea and buttons. Base UI remains the choice when something does need one.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: QUIET Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
Included review availability: Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughThe change replaces legacy AI element modules with chat components for attachments, conversations, messages, and suggestions. Buddy imports now use the chat paths. Prompt input APIs and tooltip behavior were reduced. Unused Radix-based UI modules and dependencies were removed. Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Docstring CoverageExplanation Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 8 files. (5 skipped: 5 unsupported.)
Comment |
There was a problem hiding this comment.
Note
Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.
🟡 Other comments (2)
apps/website/src/react/components/chat/attachments.tsx-98-98 (1)
98-98: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winKeep the remove button visible without hover.
AttachmentRemoveis the only visible per-file removal control inBuddyWorkspace.opacity-0hides it untilgroup-hover:opacity-100; the sharedButtonfocus ring cannot make the fully transparent button visible. Touch users cannot rely on hover, and sighted keyboard users cannot see the control when it receives focus. The Backspace shortcut only removes the last attachment and is not a visible substitute.- "absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 opacity-0 backdrop-blur-sm transition-opacity group-hover:opacity-100 hover:bg-background [&>svg]:size-3", + "absolute top-2 right-2 size-6 rounded-full bg-background/80 p-0 backdrop-blur-sm hover:bg-background [&>svg]:size-3",apps/website/src/react/components/chat/conversation.tsx-38-49 (1)
38-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winAdd an accessible name to the scroll button.
When
ConversationScrollButtonis rendered withoutaria-label, its icon-only native button has no accessible name.BuddyPoc.tsxuses this default form, andArrowDownIconis hidden from assistive technology by default. Add a default label before{...props}so callers can override it.Proposed fix
<Button + aria-label="Scroll to latest message" className={cn(
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: QUIET
Plan: Advanced
Run ID: c7e5afe6-ee47-401d-81eb-8cb2f9d49c1c
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
apps/website/package.jsonapps/website/src/react/components/ai-elements/attachments.tsxapps/website/src/react/components/ai-elements/conversation.tsxapps/website/src/react/components/ai-elements/message.tsxapps/website/src/react/components/ai-elements/suggestion.tsxapps/website/src/react/components/buddy/BuddyPoc.tsxapps/website/src/react/components/buddy/BuddyWorkspace.tsxapps/website/src/react/components/chat/attachments.tsxapps/website/src/react/components/chat/conversation.tsxapps/website/src/react/components/chat/message.tsxapps/website/src/react/components/chat/prompt-input.tsxapps/website/src/react/components/chat/suggestion.tsxapps/website/src/react/components/ui/button-group.tsxapps/website/src/react/components/ui/button.tsxapps/website/src/react/components/ui/command.tsxapps/website/src/react/components/ui/dialog.tsxapps/website/src/react/components/ui/dropdown-menu.tsxapps/website/src/react/components/ui/hover-card.tsxapps/website/src/react/components/ui/scroll-area.tsxapps/website/src/react/components/ui/select.tsxapps/website/src/react/components/ui/separator.tsxapps/website/src/react/components/ui/tooltip.tsx
💤 Files with no reviewable changes (14)
- apps/website/package.json
- apps/website/src/react/components/ui/command.tsx
- apps/website/src/react/components/ui/separator.tsx
- apps/website/src/react/components/ai-elements/suggestion.tsx
- apps/website/src/react/components/ui/select.tsx
- apps/website/src/react/components/ui/button-group.tsx
- apps/website/src/react/components/ui/hover-card.tsx
- apps/website/src/react/components/ai-elements/conversation.tsx
- apps/website/src/react/components/ui/scroll-area.tsx
- apps/website/src/react/components/ai-elements/message.tsx
- apps/website/src/react/components/ai-elements/attachments.tsx
- apps/website/src/react/components/ui/tooltip.tsx
- apps/website/src/react/components/ui/dropdown-menu.tsx
- apps/website/src/react/components/ui/dialog.tsx
Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.
Tailwind wraps group-hover in `@media (hover:hover)`, so on a phone the rule never applies and the button stayed at opacity 0 with no other way to remove a specific photo: Backspace only drops the last one. An opacity-0 button also takes its focus ring down with it, so a keyboard could reach the control but not see it. The scroll button had no accessible name where BuddyPoc renders it without one, and its icon is aria-hidden.
theme.css held its own copy of both: eighteen hand-maintained colour values and the whole Plate 89 block. The values were identical to the package's, so the site keeps its --o89-* names and resolves them from --color-* instead of restating them, and the 796 call sites are untouched. The fork existed because the brand sheet follows a light operating-system preference and the website is dark only. data-theme="dark" on the document pins it, which is what the data site does; Storybook renders in its own document, so its preview sets the same attribute. The plate arrives slightly different from the copy that drifted: the action rim is the package's 0.85 rather than 0.9, and two transitions are 0.22s and 0.24s rather than 0.25s and 0.3s.
Change
Replaces the vendored AI Elements chat components with our own and removes Radix from the site.
The Buddy chat imported 22 symbols from 2,187 lines of vendored code. The transcript, the message and the suggestion chip are now ours at a fraction of the size, carrying the same markup; the attachment grid is ours too, without the variant system and media-type table that Buddy never used, since it accepts images and lays them out one way.
The prompt input was the load-bearing piece: 1,307 lines exporting a model picker, a command palette and an action menu that nothing rendered, and those exports were the only reason the tooltip, hover card, select, dropdown menu, command and dialog components existed. Reduced to the eleven symbols the chat imports, all six became unreferenced and are deleted, along with
radix-ui,cmdkandstreamdown. The file-handling core — accept, size and count validation, drag, drop and paste — is kept rather than rewritten, because that is the part with real behaviour behind it and rewriting it would have risked the most for the least.What is left needs no primitive library at all: the composer is a form, a textarea and buttons, and
Buttonno longer composes into another element so its slot is gone with the rest of Radix. Base UI stays the choice for when something does need a primitive, which is what/opsin the data repo uses; adding the dependency here while nothing imports it would only be decoration.Net: 600 insertions against 5,222 deletions.
Validation
just checkpasses (exit 0): lint, tests, type check, site build and Storybook build.node .storybook/verify.mjsreports 55 stories rendering with no page errors, and its six screenshots match the same run againstmain.The screenshots are not a pixel-exact oracle here, which is worth knowing independently of this change. Two runs of the same build differ: the Buddy stories vary by tens to a few hundred pixels, in varying places, most often at the avatar and in the map area. A run either matches exactly or differs by one of a few recurring amounts. Comparisons were therefore repeated, and were also checked with the noisiest region masked; the change reads as identical whenever the noise settles, and the noise is present on
mainalone.Two accessibility fixes came out of review and are not covered by those screenshots: a crawl of all 55 stories finds zero attachment tiles and zero scroll buttons rendered, because both need application state the stories do not reach. They are verified instead by the compiled CSS —
.focus-visible\:opacity-100:focus-visible{opacity:1}and@media (pointer:coarse){…{opacity:1}}are both emitted — and the same stylesheet is what shows the original defect: Tailwind wrapsgroup-hoverin@media (hover:hover), so on a touch device the reveal rule never applied and the only per-photo remove control sat atopacity: 0permanently.A census of rendered
data-slotattributes across every story was used to establish what actually ships before deleting anything: onlybuttonandinput-groupappear. Triggers render whether or not an overlay is open, so the absence oftooltip-trigger,select-triggerand the rest is evidence those components were unreachable, with the caveat that stories do not cover every application state.Not verified: overlay components in an opened state, because none survive. No Changeset — the site is not a published package.
Also: colour and the plate come from the package
styles/theme.cssheld its own copy of both — eighteen hand-maintained colour values and the whole Plate 89 block, alongside a dated palette snapshot inassets/brand/brand-tokens.css. The values were identical to the package's, checked token by token, so the site keeps its--o89-*names and resolves them from--color-*rather than restating them. The 796 call sites are untouched.The fork was deliberate: the brand sheet follows a light operating-system preference and this site is dark only.
data-theme="dark"on the document pins it, which is what the data site already does. Storybook renders in its own document, so its preview sets the same attribute — without that every story would read on paper.Adopting the package brings three differences the local copy had drifted away from: the action plate's rim is
rgba(255,255,255,0.85)rather than0.9, and two transitions are0.22sand0.24srather than0.25sand0.3s. The rim is the only visible one, and it is 322 pixels on a 1px edge of one button.Verified: all six screenshots byte-identical, all 22
--o89-*tokens resolve with none empty,data-themereadsdarkin every story, and a crawl confirms 39 plates, 15 action plates, 34 wraps and 15 text links actually render across the stories — the six screenshotted views contain none of them, so the plate was checked by rebuilding the previous state and diffing a story that does. That diff is the 322 pixels above and nothing else.