refactor(web/heroui): drop Button/Chip startContent + endContent (Phase 5)#13
Conversation
…se 5)
HeroUI v3 removed the startContent / endContent props on Button & Chip.
Icons now have to live in `children`, with the component's built-in
`gap-2` doing the spacing. Without this rewrite the props were silently
ignored — ~135 buttons across the app rendered without their leading
or trailing icon.
Migration is mechanical:
<Button startContent={<X />}>label</Button>
→
<Button>
<X />
label
</Button>
<Button endContent={<Y />}>label</Button>
→
<Button>
label
<Y />
</Button>
So we ship it as a one-shot codemod (idempotent — `bun scripts/heroui-v3-startend-content-codemod.mjs` reports 0 edits on a clean tree). 55 files / 135 attribute edits.
The codemod uses @babel/parser + @babel/traverse to walk every JSX file
under web/src and:
- finds JSXAttribute nodes named startContent / endContent;
- deletes the attribute (also swallowing its surrounding whitespace
so we don't leave blank lines);
- injects the attribute's expression as the first / last child of
the surrounding JSX element.
Special cases handled:
- Self-closing elements (`<Foo startContent={<X/>} />`) are converted
to children form (`<Foo><X/></Foo>`).
- Non-JSX expressions (e.g. `getOAuthProviderIcon(provider.icon, 20)`)
are wrapped in `{ ... }` so they live inside a JSX expression
container instead of being parsed as plain text — this otherwise
would have silently broken the custom-OAuth login button.
Verification: `rg "startContent=|endContent=" web/src/` is empty.
Made-with: Cursor
4f42d34 to
58cde6a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4f42d34992
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| traverse(ast, { | ||
| JSXAttribute(path) { | ||
| const name = path.node.name?.name; | ||
| if (name !== 'startContent' && name !== 'endContent') return; |
There was a problem hiding this comment.
Scope start/end codemod to Button and Chip only
This rewrite currently matches any JSX attribute named startContent/endContent without checking the host component, so it also transformed non-Button usage (for example Input in TopupHistoryModal.jsx and OllamaModelModal.jsx). Phase 5 and this script’s own header describe the migration as Button/Chip-specific; applying it globally converts valid slot props into plain children, which can drop adornments or conflict with Input’s expected child structure (Input.Control/Input.Element). Please gate the transform by the JSX element name before editing attributes.
Useful? React with 👍 / 👎.
Closes Phase 5 of
web/TODO.md— the last open phase. After this landsthe only HeroUI v3 cleanup work that remains is downstream visual
review (no codemodable items left).
Summary
HeroUI v3 removed the
startContent/endContentprops on Button &Chip. Icons now have to live in
children, with the component'sbuilt-in
gap-2doing the spacing. Without this rewrite the propswere silently ignored — ~135 buttons across the app rendered without
their leading or trailing icon.
The migration is mechanical:
So we ship it as a one-shot codemod —
web/scripts/heroui-v3-startend-content-codemod.mjsis idempotent(reports 0 edits on a clean tree). It runs
@babel/parserover every.jsx/.tsxfile underweb/srcand:JSXAttributenodes namedstartContent/endContent;don't leave blank lines);
the surrounding JSX element;
<Foo startContent={<X/>} />) tochildren form (
<Foo><X/></Foo>);{ ... }so they live inside a JSXexpression container instead of being parsed as plain text — this
otherwise would have silently broken the custom-OAuth login button
(
getOAuthProviderIcon(provider.icon, 20)).55 files / 135 attribute edits.
Verification
ESLint goes from 94 errors on origin/main to 94 (no regression — the
deleted props were silently ignored, not lint-flagged).
Conflict notes
This branch was created off
origin/mainindependently of PR #10 / #11.Five files overlap with PR #10 (CheckinCalendar, ModelTestModal,
MultiKeyManageModal, UserSubscriptionsModal, SettingsChannelAffinity)
and six with PR #11 (DeploymentAccessGuard, CustomOAuthSetting, four
Settings/Dashboard files). The codemod is idempotent, so the cleanest
resolution is: merge #10 / #11 first, then re-run the codemod on the
updated main and force-push this branch — rather than hand-resolving
135 mechanical edits.
Test plan
bun run dev— every Button / Chip that used to declare anicon prop still shows it.
icons (custom OAuth one was the trickiest case).
sub-tabs — buttons show + / save / chevron icons as before.
This PR was generated automatically by Cursor.
Made with Cursor