Skip to content

feat(bestax-migrate): convert .icon-text, building its icons from props - #823

Merged
allxsmith merged 6 commits into
mainfrom
feat/bulma-classes-icon-text
Sep 29, 2026
Merged

allxsmith merged 6 commits into
mainfrom
feat/bulma-classes-icon-text

Conversation

@allxsmith

Copy link
Copy Markdown
Owner

Refs #809. This is its .icon-text item. .file is still open there, and I've proposed dropping the .tabs item.

IconText builds each icon from iconProps (items, for more than one) and wraps each text in a <span> of its own, so until now the codemod left .icon-text as markup. It now converts it when its children are .icons, each followed by at most one bare <span> of static text:

<span className="icon-text">
  <span className="icon" aria-label="Home">
    <i className="fas fa-home"></i>
  </span>
  <span>Home</span>
</span>
// becomes
<IconText iconProps={{ library: "fa", name: "home", ariaLabel: "Home" }}>Home</IconText>

How an icon is read. Each .icon's <i> becomes library, name and, where needed, variant and features. That covers Font Awesome, with each of its style classes, and Material Design Icons. library is always written, so a ConfigProvider with another iconLibrary can't change what renders. Whatever the .icon's own classes and attributes become on Icon joins those props, with aria-label as ariaLabel. This is the name form we picked over keeping the <i> as children, because it's what a person would write.

What still keeps it markup:

  • A .icon has to convert to Icon on its own first, so the aria-label rule still holds. That limits the reach: Bulma's own example has no aria-label. Those icons still get their defaults:Icon TODO telling people to add one.
  • Anything else stays markup with a children:IconText TODO: an aria-hidden on the <i>, another icon font, text before an icon, a div.icon-text, and so on. The .icons inside still convert on their own.

Things worth a look:

  • Re-runs. When an icon-text refuses, a .icon inside may convert on its own, and a second run then sees an Icon component where the first saw markup. addTodo dedupes by exact text, so the TODO has to read the same both times. The planner therefore gives the general reason whenever a child converted on its own, and checks the children after the element's own checks, like the tag.
    • For the same reason, a computed className on the icon-text keeps its .icons as markup too.
    • The fixture and a transform test both check that a second run changes nothing.
  • data-* attributes. The props typecheck caught that a .icon with a data-* attribute can't move into iconProps. JSX lets any dashed name through, but an object literal typed as Icon's props only accepts the declared aria- ones. Those stay markup.
  • Formatting. Recast prints the object across several lines, as it does for any object it builds, so run your formatter over the output.

Tests:

  • A render truth test over every glyph shape, an icon's own classes and attributes, and multi-icon layouts. Breaking the glyph reader on purpose fails it.
  • A props typecheck over the same.
  • Planner and glyph-reader unit tests, a fixture pair and transform tests, and an icon-text in the kitchen sink.

Also in this PR:

  • MCP: lookup_bulma_classes describes what IconText builds, and the index carries the new buildsIcons.
  • ESLint plugin: no-bulma-component-class now reports .icon-text as a class the codemod converts.
  • Docs: the skill's component map and unmappables, plus the migration guide.

pnpm all passes locally.

IconText builds each icon from iconProps (items, for more than one) and
wraps each text in a span of its own, so the family stayed markup. It now
converts when its children are .icons, each followed by at most one bare
span of static text. Each .icon becomes one icon's props: its <i> read as
library and name (Font Awesome or Material Design Icons, library always
written so a ConfigProvider can't change it), then what the .icon's own
classes and attributes become on Icon, with aria-label as ariaLabel.

A .icon still has to convert to Icon on its own first, so the aria-label
rule holds. Anything else keeps the markup with a children:IconText TODO,
worded so a re-run, which then finds the .icons converted, says the same.
The truth test renders every glyph and shape it reads; the props test
typechecks them, which is how a data- attribute on a .icon turned out to be
one a props object rejects.

Refs #809
…ent's children

lookup_bulma_classes now tells an agent that IconText is written from the
.icons inside an .icon-text: each one's <i> as library and name, its
aria-label as ariaLabel, and the text after it, in iconProps or items.
The index carries the table's new buildsIcons, and the agree test gives the
planner an icon to build from, as the codemod does.
The regenerated metadata moves icon-text from the families the codemod
leaves as markup to the classes it converts, so no-bulma-component-class
points at bestax-migrate for it.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 56 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Repository: allxsmith/bestax/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 3014d0ba-9308-412a-84a3-f97fcf026857

📥 Commits

Reviewing files that changed from the base of the PR and between ef5db9d and 15c329b.

⛔ Files ignored due to path filters (1)
  • eslint-plugin/src/generated/metadata.ts is excluded by !**/generated/**
📒 Files selected for processing (22)
  • bestax-mcp/data/bulma-classes.json
  • bestax-mcp/data/skills.json
  • bestax-mcp/src/__tests__/bulma-classes-agree.test.ts
  • bestax-mcp/src/bulma-classes.ts
  • bestax-migrate/CLAUDE.md
  • bestax-migrate/e2e/bulma-classes-props.test.ts
  • bestax-migrate/e2e/bulma-classes-truth.test.ts
  • bestax-migrate/e2e/support/icon-text.ts
  • bestax-migrate/fixtures/bulma-classes-kitchen-sink/src/elements.tsx
  • bestax-migrate/src/sources/bulma-classes/__testfixtures__/icon-text.input.tsx
  • bestax-migrate/src/sources/bulma-classes/__testfixtures__/icon-text.output.tsx
  • bestax-migrate/src/sources/bulma-classes/__tests__/glyph.test.ts
  • bestax-migrate/src/sources/bulma-classes/__tests__/plan.test.ts
  • bestax-migrate/src/sources/bulma-classes/__tests__/transform.test.ts
  • bestax-migrate/src/sources/bulma-classes/class-map.ts
  • bestax-migrate/src/sources/bulma-classes/glyph.ts
  • bestax-migrate/src/sources/bulma-classes/plan.ts
  • bestax-migrate/src/sources/bulma-classes/transform.ts
  • docs/docs/guides/getting-started/migration/bulma-classes.md
  • scripts/gen-mcp-index.mjs
  • skills/bestax-migrate/references/bulma-classes/component-map.md
  • skills/bestax-migrate/references/bulma-classes/unmappables.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://8be57c55.bestax.pages.dev

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep review — 0 blocking · 3 advisory

# Severity Area Finding Location
1 🔵 Advisory API The lookup_bulma_classes prose ends with a sufficiency claim that isn't one — "The codemod does that when each .icon holds a bare, empty naming a Font Awesome or Material Design Icons glyph" omits the conditions that actually gate it (the .icon must convert to Icon on its own, i.e. carry an aria-label and no key/ref/non-aria dashed attribute; the element must be a <span>; the only other children must be bare static-text <span>s). The skill's component-map.md states it correctly. Deleting that last sentence leaves the paragraph true. bestax-mcp/src/bulma-classes.ts:789
2 🔵 Advisory Coverage transform.ts:1250 is the one uncovered line of the new code — the branch that keeps a built-in .icon in elements when its own plan carried a TODO alongside its conversion. Reachable today only through LEGACY_09, whose sole entry is tile (className="icon tile"). Package thresholds are met either way. bestax-migrate/src/sources/bulma-classes/transform.ts:1250
3 🔵 Advisory Robustness Two safe under-reaches worth recording: an .icon-text written on one line refuses, because the inline space between children reaches React and childElementsOf bails (a newline-separated one is fine, which is how Bulma's docs and any formatter write it); and a .icon whose className is a clsx/classnames call is never seen as an icon at all, because childFacts gives it tokens: null. Both leave the markup alone rather than converting it wrongly. bestax-migrate/src/sources/bulma-classes/plan.ts:822

Overall: The change is sound and unusually well fenced. The key design decision — read the <i> back into library/name/variant/features rather than keeping it as Icon children — is safe by construction, not just by test: readGlyph picks exactly one non-modifier prefixed token as the name and hands everything else but the style/base token to features, and Icon re-emits [style, prefix+name, ...features], so the rendered class set is always identical to the input's; the only ways out are "two candidates" or "no candidate" → refuse, and the fa-fa-/mdi-mdi- guard covers the one transform Icon applies to name. The riskiest part is the re-run story, and it holds for the reason the PR gives plus one it doesn't spell out: once any child converts on its own, childFacts returns undefined for it (it is no longer an intrinsic tag), so childElementsOf returns undefined wholesale and buildIcons falls straight to the shape message — which is exactly what the alone flag makes the first run say. A human should focus on the MCP sentence in row 1, since that text is what an agent acts on.

Residual risk:

  • A glyph read into props that renders different classes. Refuted structurally (above) and empirically: bulma-classes-truth.test.ts renders all 14 GLYPHS plus every .icon class/attribute shape and compares normalised HTML, bulma-classes-fixtures-render.test.ts renders icon-text.input.tsx against .output.tsx, and bulma-classes-kitchen-sink.test.ts asserts the whole app renders identically, typechecks, and is unchanged by a second run. All green here (2098 tests), as are gen:mcp:check, gen:eslint-meta:check, check:conformance and format:check.
  • Props that don't typecheck. bulma-classes-props.test.ts typechecks the built object for every glyph, every .icon modifier class and every COMMON attribute including data-test (which is refused). The guard is buildIcons' loose check — a dashed name that isn't aria- refuses — and everything else must live in React.HTMLAttributes<HTMLSpanElement>, which IconBaseProps extends. aria-describedby is exercised by the planner and render tests but not by the typecheck sweep; AriaAttributes declares it, so that is a test-surface gap rather than an exposure.
  • A spread or a duplicate key inside iconProps. Refuted: plan refuses hasSpread before anything else, so iconObject never meets a JSXSpreadAttribute (which would crash attributeName); and an attribute sharing a name with an Icon prop (name, library, size, …) refuses at plan.ts:466 because .icon's ownProps lists them, so the glyph's name can never be overwritten by a later key.
  • A TODO that reads differently on a re-run. One shape is still open, and it is narrow: alone is some(child => child.becomes), and becomes reads only conversion, never fold. So an .icon-text holding both a non-converting .icon and a fold sibling (.table-container / .fixed-grid — the only two) would say "see the TODO on each" on run 1 and the general shape message on run 2. I could not construct a realistic input for it, and the fixture plus transform.test.ts cover the re-run shapes that do occur.
  • A TODO left on a node the conversion deleted. Open but benign: the cleanup loop un-plans the .icon children only, not their descendants. An <i> can never convert (i is not in WRAPPERS) but can still carry a legacy:/family: TODO (class="fas fa-home tile"), which would then be reported for markup that is gone — while the class itself survives in features, so the hint stays accurate.
  • Point-in-time evidence in docs or comments: none added.

🏄 Total glassy set, dude — the glyph reader paddles out and comes back with the exact same wave it caught, and the re-run story holds together on its own two fins. Nothing to bail on here; just tidy that one MCP sentence before you kick out.

…nText

It named one condition as if it were the whole of it, and an agent acts on
that text. What IconText is written from stays; when the codemod does it is
the skill's to say, which it does in full.
…O of its own

A .icon with a legacy class converts with a TODO beside its conversion, and
once IconText takes it, the TODO stays and the class goes into its props.
@allxsmith

Copy link
Copy Markdown
Owner Author

On the review's advisories:

  1. Fixed in d9c9007 by deleting the sentence. What IconText is written from stays in the lookup text. When the codemod does it is the skill's to say, and it says it in full.
  2. Covered in fdc7366: a .icon tile inside an .icon-text converts with its legacy:tile TODO kept and tile in its props, which reaches that branch.
  3. Leaving both under-reaches as they are. Each keeps the markup rather than converting it wrong, and the skill's recipe already says that anything between the children keeps it markup.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://c8ae9be3.bestax.pages.dev

@allxsmith allxsmith added the needs-human-review Loop converged (or contested): awaiting owner review + manual merge label Sep 29, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📸 Story screenshots at handoff — fdc7366

No Storybook stories map to this PR's changed files — nothing to screenshot. (workflow run)

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://75709c73.bestax.pages.dev

@allxsmith
allxsmith merged commit 933868f into main Sep 29, 2026
12 checks passed
@allxsmith
allxsmith deleted the feat/bulma-classes-icon-text branch September 29, 2026 04:45
bestax-release-bot Bot pushed a commit that referenced this pull request Sep 29, 2026
# [2.23.0](https://github.com/allxsmith/bestax/compare/bestax-migrate@2.22.1...bestax-migrate@2.23.0) (2026-09-29)

### Features

* **bestax-migrate:** convert .icon-text, building its icons from props ([#823](#823)) ([933868f](933868f))
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.23.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Sep 30, 2026
## [5.18.1](https://github.com/allxsmith/bestax/compare/@allxsmith/bestax-bulma@5.18.0...@allxsmith/bestax-bulma@5.18.1) (2026-09-30)

### Bug Fixes

* **bestax-migrate:** keep a page link as markup when its li is a component's only child ([0e72683](0e72683))
* **bestax-migrate:** rename rbx innerRef to ref on a plain Navbar.Item too ([bb6ace0](bb6ace0)), closes [#734](#734)
* **bestax-migrate:** write a File text with a backslash as a string ([212831e](212831e))
* **bestax-migrate:** write isFullwidth from react-bulma-components too ([01da047](01da047))
* **bestax-migrate:** write isFullwidth rather than its deprecated alias from bloomer ([c2a1d22](c2a1d22)), closes [#680](#680)
* **bulma-ui:** color TimeInput's wheels from the color prop ([#832](#832)) ([ad370c9](ad370c9))
* **bulma-ui:** document the checkbox, radio and switch color and size tuples ([bd39ff3](bd39ff3))
* **bulma-ui:** Dropdown items withhold attributes by tag and answer Enter and Space ([#827](#827)) ([f6aeb2d](f6aeb2d))
* **bulma-ui:** keep radiusless zeroing the radius inside a Theme ([07db6a3](07db6a3)), closes [#829](#829) [#694](#694)
* **bulma-ui:** key the Avatar link warning on the element and attributes ([e628eba](e628eba)), closes [#733](#733) [#733](#733)
* **bulma-ui:** leave rel out of the Avatar link attribute warning ([b059476](b059476)), closes [#733](#733) [#733](#733)
* **bulma-ui:** let several isRoot Themes share the :root style element ([04f399f](04f399f)), closes [#736](#736)
* **bulma-ui:** let warnOnce decide when the Avatar link warning fires ([1a5965c](1a5965c)), closes [#733](#733)
* **bulma-ui:** make Theme's radius the helper it is typed as ([36c73d3](36c73d3)), closes [#694](#694)
* **bulma-ui:** name custom elements among the targets that get Avatar's target and rel ([a962259](a962259)), closes [#733](#733)
* **bulma-ui:** show toasts, notifications and dialogs raised before their container mounts ([#828](#828)) ([2ab4493](2ab4493))
* **bulma-ui:** skip the Avatar target warning on elements with their own target ([27b58f4](27b58f4)), closes [#733](#733)
* **bulma-ui:** strip the Avatar link warning from production builds ([a243fa5](a243fa5)), closes [#733](#733)
* **bulma-ui:** warn about a dropped Avatar href only for an as the caller wrote ([c93b27c](c93b27c)), closes [#733](#733)
* **bulma-ui:** warn in development when Avatar drops an href on a non-link as ([c76e632](c76e632)), closes [#733](#733)
* **bulma-ui:** warn when Avatar drops a target or rel on a non-link as ([50c385e](50c385e)), closes [#733](#733) [#733](#733)
* **eslint-plugin:** report .file as a class the codemod converts ([72a32c9](72a32c9))
* **eslint-plugin:** report the pagination links and ellipsis as classes the codemod converts ([54ba0b3](54ba0b3))
* **eslint-plugin:** report Theme's deprecated radius route accurately ([5605816](5605816)), closes [#694](#694)
* **eslint-plugin:** suggest radiusless for a near miss on Theme ([21aa9a7](21aa9a7)), closes [#694](#694)

### Features

* **bestax-mcp:** say when a component renders the li around its element, or its own text ([8fd3faf](8fd3faf))
* **bestax-mcp:** say when a component renders the whole .file tree from props ([976d8a3](976d8a3))
* **bestax-migrate:** convert .file inside a Field, building its tree from props ([724dff5](724dff5)), closes [#809](#809)
* **bestax-migrate:** convert .icon-text, building its icons from props ([#823](#823)) ([933868f](933868f))
* **bestax-migrate:** convert Pagination's page links and ellipsis in their li's place ([ae720fa](ae720fa)), closes [#809](#809)
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.18.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Sep 30, 2026
# [1.13.0](https://github.com/allxsmith/bestax/compare/bestax-mcp@1.12.0...bestax-mcp@1.13.0) (2026-09-30)

### Bug Fixes

* **bestax-migrate:** rename rbx innerRef to ref on a plain Navbar.Item too ([bb6ace0](bb6ace0)), closes [#734](#734)
* **bestax-migrate:** write a File text with a backslash as a string ([212831e](212831e))
* **bestax-migrate:** write isFullwidth from react-bulma-components too ([01da047](01da047))
* **bestax-migrate:** write isFullwidth rather than its deprecated alias from bloomer ([c2a1d22](c2a1d22)), closes [#680](#680)
* **bulma-ui:** color TimeInput's wheels from the color prop ([#832](#832)) ([ad370c9](ad370c9))
* **bulma-ui:** document the checkbox, radio and switch color and size tuples ([bd39ff3](bd39ff3))
* **bulma-ui:** Dropdown items withhold attributes by tag and answer Enter and Space ([#827](#827)) ([f6aeb2d](f6aeb2d))
* **bulma-ui:** keep radiusless zeroing the radius inside a Theme ([07db6a3](07db6a3)), closes [#829](#829) [#694](#694)
* **bulma-ui:** key the Avatar link warning on the element and attributes ([e628eba](e628eba)), closes [#733](#733) [#733](#733)
* **bulma-ui:** leave rel out of the Avatar link attribute warning ([b059476](b059476)), closes [#733](#733) [#733](#733)
* **bulma-ui:** let several isRoot Themes share the :root style element ([04f399f](04f399f)), closes [#736](#736)
* **bulma-ui:** let warnOnce decide when the Avatar link warning fires ([1a5965c](1a5965c)), closes [#733](#733)
* **bulma-ui:** make Theme's radius the helper it is typed as ([36c73d3](36c73d3)), closes [#694](#694)
* **bulma-ui:** name custom elements among the targets that get Avatar's target and rel ([a962259](a962259)), closes [#733](#733)
* **bulma-ui:** show toasts, notifications and dialogs raised before their container mounts ([#828](#828)) ([2ab4493](2ab4493))
* **bulma-ui:** skip the Avatar target warning on elements with their own target ([27b58f4](27b58f4)), closes [#733](#733)
* **bulma-ui:** strip the Avatar link warning from production builds ([a243fa5](a243fa5)), closes [#733](#733)
* **bulma-ui:** warn about a dropped Avatar href only for an as the caller wrote ([c93b27c](c93b27c)), closes [#733](#733)
* **bulma-ui:** warn in development when Avatar drops an href on a non-link as ([c76e632](c76e632)), closes [#733](#733)
* **bulma-ui:** warn when Avatar drops a target or rel on a non-link as ([50c385e](50c385e)), closes [#733](#733) [#733](#733)
* **eslint-plugin:** report .file as a class the codemod converts ([72a32c9](72a32c9))
* **eslint-plugin:** report Theme's deprecated radius route accurately ([5605816](5605816)), closes [#694](#694)
* **eslint-plugin:** suggest radiusless for a near miss on Theme ([21aa9a7](21aa9a7)), closes [#694](#694)

### Features

* **bestax-mcp:** say when a component renders the whole .file tree from props ([976d8a3](976d8a3))
* **bestax-migrate:** convert .file inside a Field, building its tree from props ([724dff5](724dff5)), closes [#809](#809)
* **bestax-migrate:** convert .icon-text, building its icons from props ([#823](#823)) ([933868f](933868f))
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.13.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Sep 30, 2026
## [1.1.9](https://github.com/allxsmith/bestax/compare/@allxsmith/eslint-plugin-bestax@1.1.8...@allxsmith/eslint-plugin-bestax@1.1.9) (2026-09-30)

### Bug Fixes

* **bestax-migrate:** rename rbx innerRef to ref on a plain Navbar.Item too ([bb6ace0](bb6ace0)), closes [#734](#734)
* **bestax-migrate:** write a File text with a backslash as a string ([212831e](212831e))
* **bestax-migrate:** write isFullwidth from react-bulma-components too ([01da047](01da047))
* **bestax-migrate:** write isFullwidth rather than its deprecated alias from bloomer ([c2a1d22](c2a1d22)), closes [#680](#680)
* **bulma-ui:** color TimeInput's wheels from the color prop ([#832](#832)) ([ad370c9](ad370c9))
* **bulma-ui:** document the checkbox, radio and switch color and size tuples ([bd39ff3](bd39ff3))
* **bulma-ui:** Dropdown items withhold attributes by tag and answer Enter and Space ([#827](#827)) ([f6aeb2d](f6aeb2d))
* **bulma-ui:** keep radiusless zeroing the radius inside a Theme ([07db6a3](07db6a3)), closes [#829](#829) [#694](#694)
* **bulma-ui:** key the Avatar link warning on the element and attributes ([e628eba](e628eba)), closes [#733](#733) [#733](#733)
* **bulma-ui:** leave rel out of the Avatar link attribute warning ([b059476](b059476)), closes [#733](#733) [#733](#733)
* **bulma-ui:** let several isRoot Themes share the :root style element ([04f399f](04f399f)), closes [#736](#736)
* **bulma-ui:** let warnOnce decide when the Avatar link warning fires ([1a5965c](1a5965c)), closes [#733](#733)
* **bulma-ui:** make Theme's radius the helper it is typed as ([36c73d3](36c73d3)), closes [#694](#694)
* **bulma-ui:** name custom elements among the targets that get Avatar's target and rel ([a962259](a962259)), closes [#733](#733)
* **bulma-ui:** show toasts, notifications and dialogs raised before their container mounts ([#828](#828)) ([2ab4493](2ab4493))
* **bulma-ui:** skip the Avatar target warning on elements with their own target ([27b58f4](27b58f4)), closes [#733](#733)
* **bulma-ui:** strip the Avatar link warning from production builds ([a243fa5](a243fa5)), closes [#733](#733)
* **bulma-ui:** warn about a dropped Avatar href only for an as the caller wrote ([c93b27c](c93b27c)), closes [#733](#733)
* **bulma-ui:** warn in development when Avatar drops an href on a non-link as ([c76e632](c76e632)), closes [#733](#733)
* **bulma-ui:** warn when Avatar drops a target or rel on a non-link as ([50c385e](50c385e)), closes [#733](#733) [#733](#733)
* **eslint-plugin:** report .file as a class the codemod converts ([72a32c9](72a32c9))
* **eslint-plugin:** report Theme's deprecated radius route accurately ([5605816](5605816)), closes [#694](#694)
* **eslint-plugin:** suggest radiusless for a near miss on Theme ([21aa9a7](21aa9a7)), closes [#694](#694)

### Features

* **bestax-mcp:** say when a component renders the whole .file tree from props ([976d8a3](976d8a3))
* **bestax-migrate:** convert .file inside a Field, building its tree from props ([724dff5](724dff5)), closes [#809](#809)
* **bestax-migrate:** convert .icon-text, building its icons from props ([#823](#823)) ([933868f](933868f))
bestax-release-bot Bot pushed a commit that referenced this pull request Oct 8, 2026
## [4.2.13](https://github.com/allxsmith/bestax/compare/create-bestax@4.2.12...create-bestax@4.2.13) (2026-10-08)

### Bug Fixes

* **bestax-mcp:** give .image's ratios their reason in the lookup ([3ea4d0c](3ea4d0c))
* **bestax-mcp:** say an .image converts around elements written out, not an expression ([49bb27b](49bb27b))
* **bestax-migrate:** convert an .image around its children only when they're elements ([ff99907](ff99907))
* **bestax-migrate:** flag react-bulma-components Pagination's delta={0} rather than renaming it ([e339ff1](e339ff1))
* **bestax-migrate:** keep a .menu-list around a bestax Menu.List as markup ([2119c3f](2119c3f))
* **bestax-migrate:** keep a Modal part as markup inside an existing bestax Modal ([cfd5f15](cfd5f15))
* **bestax-migrate:** keep a named anchor as markup instead of saying its name does nothing ([74dc360](74dc360)), closes [#fragment](https://github.com/allxsmith/bestax/issues/fragment)
* **bestax-migrate:** keep a page link as markup when its li is a component's only child ([0e72683](0e72683))
* **bestax-migrate:** know a bestax part imported under its flat export ([7a325ee](7a325ee))
* **bestax-migrate:** leave a wrapper tag alone when none of its classes would become a prop ([2397b9d](2397b9d))
* **bestax-migrate:** leave a wrapper tag with no helper class alone, TODOs and all ([1167a29](1167a29))
* **bestax-migrate:** map react-bulma-components Pagination onto the props it now renders from ([6cd4b51](6cd4b51))
* **bestax-migrate:** rename rbx innerRef to ref on a plain Navbar.Item too ([bb6ace0](bb6ace0)), closes [#734](#734)
* **bestax-migrate:** say why .image's ratios stay classes ([2fbfbd5](2fbfbd5))
* **bestax-migrate:** write a File text with a backslash as a string ([212831e](212831e))
* **bestax-migrate:** write a tabIndex string as a number on every root ([546bf8a](546bf8a)), closes [#814](#814)
* **bestax-migrate:** write isFullwidth from react-bulma-components too ([01da047](01da047))
* **bestax-migrate:** write isFullwidth rather than its deprecated alias from bloomer ([c2a1d22](c2a1d22)), closes [#680](#680)
* **bulma-ui:** announce polite notifications through a status region kept from mount ([#835](#835)) ([c93478d](c93478d))
* **bulma-ui:** answer the granularity review on native input and blur ([7a17486](7a17486))
* **bulma-ui:** apply textColor helper class on Navbar Menu, Start, End, and Burger ([#792](#792)) ([feb83c8](feb83c8))
* **bulma-ui:** attach useFocusTrap to a container that mounts after it turns on ([17a944d](17a944d))
* **bulma-ui:** avoid empty class attribute on several components ([#793](#793)) ([7c5c316](7c5c316))
* **bulma-ui:** bring File and Taginput under the bare-Control Field rule ([cdaa6c2](cdaa6c2))
* **bulma-ui:** clear a picker's unused seed whenever its popover closes ([cbe86c5](cbe86c5))
* **bulma-ui:** close a picker's popover on Alt+ArrowUp from inside it ([b6da4ef](b6da4ef))
* **bulma-ui:** color the calendar from the color prop and read TimeInput's wheel variables ([2251e67](2251e67)), closes [#832](#832)
* **bulma-ui:** color TimeInput's wheels from the color prop ([#832](#832)) ([ad370c9](ad370c9))
* **bulma-ui:** commit nothing when a picker popover is dismissed ([0a46209](0a46209))
* **bulma-ui:** commit nothing when Enter confirms untouched picker text ([773e146](773e146))
* **bulma-ui:** commit nothing when focus leaves a picker untouched ([30b4849](30b4849))
* **bulma-ui:** document the checkbox, radio and switch color and size tuples ([bd39ff3](bd39ff3))
* **bulma-ui:** draw the time wheel focus ring where the wheel's mask shows it ([e1d8c9f](e1d8c9f)), closes [#832](#832)
* **bulma-ui:** drop the openOnFocus TSDoc reason that only holds for segments ([efde0d9](efde0d9))
* **bulma-ui:** Dropdown items withhold attributes by tag and answer Enter and Space ([#827](#827)) ([f6aeb2d](f6aeb2d))
* **bulma-ui:** fall back when useFocusTrap's initialFocusRef can't take focus ([c191931](c191931))
* **bulma-ui:** fire the pickers' onOpen and onClose once under StrictMode ([#896](#896)) ([20b51ed](20b51ed))
* **bulma-ui:** give the calendar's day grid rows of a week ([971a196](971a196)), closes [#774](#774)
* **bulma-ui:** give the selected calendar cell a ring that shows on its fill ([b3eecc6](b3eecc6))
* **bulma-ui:** hide a picker's launcher for a loading Control it sits in ([9a3888e](9a3888e))
* **bulma-ui:** hide a picker's launcher only for the spinner it draws ([7a28a0e](7a28a0e))
* **bulma-ui:** hold Portal to looking a selector up on each render ([8fe02ce](8fe02ce))
* **bulma-ui:** hold the year-1 floor under a max before it ([4f6eb18](4f6eb18))
* **bulma-ui:** judge image map areas by their image in useFocusTrap ([af7bc59](af7bc59))
* **bulma-ui:** keep a range's end while the start's digits are coming ([8d1f862](8d1f862))
* **bulma-ui:** keep a read-only date or time picker closed on ArrowDown ([ba74bf7](ba74bf7))
* **bulma-ui:** keep a tab stop in the year list when the year falls outside it ([fe028c1](fe028c1))
* **bulma-ui:** keep an empty picker's seed while a portaled popover is open ([1e66528](1e66528))
* **bulma-ui:** keep an inline date picker's calendar on its released id ([c26da07](c26da07)), closes [#897](#897) [#897](#897)
* **bulma-ui:** keep button defaults through a spread, and type the buttons that had none ([#853](#853)) ([0810482](0810482))
* **bulma-ui:** keep button types and Avatar's accessible name through a spread ([#826](#826)) ([dc798d4](dc798d4))
* **bulma-ui:** keep menuitemcheckbox and menuitemradio items in Dropdown's arrow-key order ([#786](#786)) ([064155a](064155a))
* **bulma-ui:** keep Numberinput's buttons in a row inside Field > Control ([4dff84c](4dff84c))
* **bulma-ui:** keep Numberinput's Field examples in a row and labelled ([c937c88](c937c88))
* **bulma-ui:** keep radiusless zeroing the radius inside a Theme ([07db6a3](07db6a3)), closes [#829](#829) [#694](#694)
* **bulma-ui:** keep the calendar behind DateTimeInput's time wheels out of reach ([c7f674c](c7f674c))
* **bulma-ui:** keep the clock's seconds out of a picked date and time ([0af2e71](0af2e71))
* **bulma-ui:** keep the Field for horizontal and fieldClassName in a bare Control ([e4dc513](e4dc513))
* **bulma-ui:** keep the picker focus rings visible in every color ([612aa09](612aa09))
* **bulma-ui:** keep the range band under the pointer in the calendar ([c1f2008](c1f2008))
* **bulma-ui:** key the Avatar link warning on the element and attributes ([e628eba](e628eba)), closes [#733](#733) [#733](#733)
* **bulma-ui:** key the Control-level warning by its advice ([24cda3d](24cda3d))
* **bulma-ui:** leave keys on a control inside a tab to that control ([4a9f7b6](4a9f7b6))
* **bulma-ui:** leave rel out of the Avatar link attribute warning ([b059476](b059476)), closes [#733](#733) [#733](#733)
* **bulma-ui:** let Autocomplete's clear button give way to a loading spinner ([5c11fb2](5c11fb2))
* **bulma-ui:** let Escape in a popover's year dropdown go back to the grid ([a8b1dbf](a8b1dbf)), closes [#901](#901)
* **bulma-ui:** let File's picked-files count be reworded, and document its name in the label ([e86545c](e86545c))
* **bulma-ui:** let focus follow the keys in an inline calendar, and skip disabled days for the tab stop ([#885](#885)) ([f55af27](f55af27))
* **bulma-ui:** let several isRoot Themes share the :root style element ([04f399f](04f399f)), closes [#736](#736)
* **bulma-ui:** let the arrow keys move through the calendar's year dropdown ([6abc329](6abc329)), closes [#901](#901)
* **bulma-ui:** let the picker bases give way to a loading Control too ([66e803a](66e803a))
* **bulma-ui:** let useFocusTrap go of a container that leaves while it is on ([8c56d7a](8c56d7a))
* **bulma-ui:** let warnOnce decide when the Avatar link warning fires ([1a5965c](1a5965c)), closes [#733](#733)
* **bulma-ui:** make ArrowUp raise a time wheel's value ([c27c310](c27c310))
* **bulma-ui:** make auto leave room for an in-place picker panel's themed offset ([8c534bd](8c534bd)), closes [#904](#904)
* **bulma-ui:** make Tabs keyboard operable with the WAI-ARIA tabs pattern ([fe0e7ef](fe0e7ef))
* **bulma-ui:** make Theme colorMode switch the scheme under the prefixed builds ([#894](#894)) ([2bd6ec2](2bd6ec2))
* **bulma-ui:** make Theme's radius the helper it is typed as ([36c73d3](36c73d3)), closes [#694](#694)
* **bulma-ui:** make useFocusTrap follow radio groups and initialFocusRef past the trap ([d6219d4](d6219d4))
* **bulma-ui:** match a picker's opening focus only inside its calendar ([e3c22ed](e3c22ed))
* **bulma-ui:** merge className into Navbar.Divider instead of overwriting it ([#791](#791)) ([fcc0d51](fcc0d51))
* **bulma-ui:** move focus into DateTimeInput's time wheels as they open ([adfa9db](adfa9db))
* **bulma-ui:** move the year dropdown's tab stop with the arrow keys ([f56daf5](f56daf5))
* **bulma-ui:** name a picker's default icon when its icon size or column moves ([6f6de39](6f6de39))
* **bulma-ui:** name custom elements among the targets that get Avatar's target and rel ([a962259](a962259)), closes [#733](#733)
* **bulma-ui:** name DateRangeInput's default icon in its Control-level warning ([15cbbb8](15cbbb8)), closes [#952](#952)
* **bulma-ui:** name DateRangeInput's default icon in its Control-level warning ([9cbe7ca](9cbe7ca)), closes [#952](#952)
* **bulma-ui:** name Pagination's page links, and read a count that's no number as its default ([0a1cf06](0a1cf06))
* **bulma-ui:** open DateRangeInput's popover on Alt+ArrowDown, as the other pickers ([c0e22ec](c0e22ec))
* **bulma-ui:** open the date and time popovers on Alt+ArrowDown ([6db22cc](6db22cc))
* **bulma-ui:** order the Tabs fallback stop by index and style disabled tabs ([eefd283](eefd283))
* **bulma-ui:** pick a DateInput day at midnight by key as by click ([92c27ca](92c27ca))
* **bulma-ui:** place a portaled picker panel by its coordinates alone ([#886](#886)) ([040299b](040299b))
* **bulma-ui:** place toasts and notifications at the position they were shown with ([#834](#834)) ([62feab9](62feab9))
* **bulma-ui:** prefix the is-current and is-disabled classes on Pagination's parts ([c9a5fae](c9a5fae))
* **bulma-ui:** reach Taginput's delete buttons and Autocomplete's clear button ([21a064b](21a064b))
* **bulma-ui:** read a Pagination current page that's no number as the first ([68e963b](68e963b))
* **bulma-ui:** read clicks and focus inside a shadow root ([474f2cc](474f2cc))
* **bulma-ui:** read picker bounds and months in years 0 to 99 as written ([#890](#890)) ([02fb619](02fb619))
* **bulma-ui:** render the four burger spans Bulma v1 positions ([#785](#785)) ([8e4e1e6](8e4e1e6))
* **bulma-ui:** restore a portaled Modal's focus to an opener in a shadow root ([c564bd3](c564bd3))
* **bulma-ui:** return focus to the input when a date picker closes ([b741264](b741264))
* **bulma-ui:** say a picked DateTimeInput day keeps milliseconds and meets the bounds ([3536467](3536467))
* **bulma-ui:** say Tab reaches DateRangeInput's end input only when typing ([d2d3d1a](d2d3d1a))
* **bulma-ui:** say where Numberinput goes bare and test bare={false} in a Field ([0461631](0461631))
* **bulma-ui:** send a press on DateRangeInput's field to the nearer input ([f06b2b0](f06b2b0))
* **bulma-ui:** settle Popover's deep review findings ([feadaf4](feadaf4))
* **bulma-ui:** show the picked file with File's hasName and add a keyboard focus ring ([7881db4](7881db4))
* **bulma-ui:** show toasts, notifications and dialogs raised before their container mounts ([#828](#828)) ([2ab4493](2ab4493))
* **bulma-ui:** skip the Avatar target warning on elements with their own target ([27b58f4](27b58f4)), closes [#733](#733)
* **bulma-ui:** start a date picker's focus on the focused cell ([#897](#897)) ([6a55714](6a55714))
* **bulma-ui:** stop Bulma's spinners under reduced motion in every stylesheet ([b234712](b234712)), closes [#770](#770)
* **bulma-ui:** stop form wrappers nesting a Field in a bare Control ([2c6d295](2c6d295))
* **bulma-ui:** stop shipping test-only exports, and document notification() ([#781](#781)) ([5f95398](5f95398))
* **bulma-ui:** stop the date pickers at year 1 ([853142a](853142a))
* **bulma-ui:** stop the indeterminate progress bar and skeleton pulse under reduced motion ([bcf5407](bcf5407))
* **bulma-ui:** stop the Rate pop and DateTimeInput scrim under reduced motion ([3a59129](3a59129))
* **bulma-ui:** stop TimeInput reopening on the focus its popover hands back ([1c87c15](1c87c15))
* **bulma-ui:** strip the Avatar link warning from production builds ([a243fa5](a243fa5)), closes [#733](#733)
* **bulma-ui:** warn about a dropped Avatar href only for an as the caller wrote ([c93b27c](c93b27c)), closes [#733](#733)
* **bulma-ui:** warn in development when Avatar drops an href on a non-link as ([c76e632](c76e632)), closes [#733](#733)
* **bulma-ui:** warn when a root Theme drops its className and helpers ([05c10e0](05c10e0))
* **bulma-ui:** warn when a wrapper inside a Control drops its Control-level props ([cbf434b](cbf434b)), closes [#921](#921)
* **bulma-ui:** warn when an inline picker drops its Control-level props ([f0494bd](f0494bd)), closes [#921](#921)
* **bulma-ui:** warn when Avatar drops a target or rel on a non-link as ([50c385e](50c385e)), closes [#733](#733) [#733](#733)
* **create-bestax:** check the react ranges fit the library's peers, not equal them ([8ae5a0c](8ae5a0c)), closes [#950](#950)
* **create-bestax:** import the icon CSS for every Bulma flavor, not only complete ([a712b91](a712b91)), closes [#946](#946)
* **create-bestax:** install react and react-dom so Yarn 1 reports no unmet peers ([9c9814c](9c9814c)), closes [#644](#644) [#950](#950)
* **create-bestax:** name the files the overwrite prompt removes, and stop on a file path ([39b6232](39b6232)), closes [#945](#945)
* **create-bestax:** never empty a non-empty folder under -y, and validate flags first ([917f195](917f195)), closes [#950](#950) [#945](#945) [#950](#950)
* **create-bestax:** warn when the icon CSS import has nowhere to go ([f7f0b64](f7f0b64)), closes [#946](#946) [#946](#946)
* **eslint-plugin:** judge Columns' gap by its own steps, and lengths as lengths ([4786c8d](4786c8d))
* **eslint-plugin:** report .file as a class the codemod converts ([72a32c9](72a32c9))
* **eslint-plugin:** report .image with the message for a class the codemod converts ([d27c19b](d27c19b))
* **eslint-plugin:** report .menu, .menu-label and .menu-list with the converts message ([8ffaccc](8ffaccc))
* **eslint-plugin:** report .select and .breadcrumb as converted ([a9d8082](a9d8082))
* **eslint-plugin:** report Bulma form markup as classes the codemod converts ([128e832](128e832))
* **eslint-plugin:** report Bulma skeletons as converted ([04ca4ee](04ca4ee))
* **eslint-plugin:** report Modal's parts with the message for a class the codemod converts ([87f3c08](87f3c08))
* **eslint-plugin:** report Pagination's converting parts with the converts message ([6b94f14](6b94f14))
* **eslint-plugin:** report the pagination links and ellipsis as classes the codemod converts ([54ba0b3](54ba0b3))
* **eslint-plugin:** report Theme's deprecated radius route accurately ([5605816](5605816)), closes [#694](#694)
* **eslint-plugin:** suggest radiusless for a near miss on Theme ([21aa9a7](21aa9a7)), closes [#694](#694)

### Features

* **bestax-mcp:** look up .image as Image, with the props it takes from its <img> ([3e63230](3e63230))
* **bestax-mcp:** look up .menu and its label and list as Menu's parts ([f9fe7cd](f9fe7cd))
* **bestax-mcp:** look up Card's title and footer items on the tags their as takes ([ba8193e](ba8193e))
* **bestax-mcp:** look up Modal's parts as components ([b71583a](b71583a))
* **bestax-mcp:** look up Pagination, its list and its previous and next links as components ([f1298df](f1298df))
* **bestax-mcp:** look up the bestax component and props for a Bulma class string ([4a4b246](4a4b246)), closes [#744](#744)
* **bestax-mcp:** publish to the official MCP Registry ([74efc7f](74efc7f))
* **bestax-mcp:** say a Bulma field wraps its children only when horizontal ([929bc82](929bc82))
* **bestax-mcp:** say an .image converts around anything but a bare <img> ([60e9219](60e9219))
* **bestax-mcp:** say Menu.List renders .menu-list only at the top level ([dcce74f](dcce74f))
* **bestax-mcp:** say when a component renders the children from a count ([b60c295](b60c295))
* **bestax-mcp:** say when a component renders the element inside it ([4ced25c](4ced25c))
* **bestax-mcp:** say when a component renders the li around its element, or its own text ([8fd3faf](8fd3faf))
* **bestax-mcp:** say when a component renders the whole .file tree from props ([976d8a3](976d8a3))
* **bestax-migrate:** convert .file inside a Field, building its tree from props ([724dff5](724dff5)), closes [#809](#809)
* **bestax-migrate:** convert .icon-text, building its icons from props ([#823](#823)) ([933868f](933868f))
* **bestax-migrate:** convert .menu, its labels and its top-level lists ([02f4db6](02f4db6))
* **bestax-migrate:** convert .select and .breadcrumb with the element inside them ([d76c569](d76c569))
* **bestax-migrate:** convert .tabs and .icon around their children ([#815](#815)) ([3967a80](3967a80))
* **bestax-migrate:** convert a menu list's items to Menu.Item ([#816](#816)) ([b385687](b385687))
* **bestax-migrate:** convert an .image and its <img> to Image ([d4f3673](d4f3673)), closes [#793](#793)
* **bestax-migrate:** convert an .image around anything but a bare <img> ([0a31c5b](0a31c5b)), closes [#801](#801)
* **bestax-migrate:** convert Bulma cards built from their parts ([#752](#752)) ([1b08e17](1b08e17))
* **bestax-migrate:** convert Bulma form fields, controls, inputs and textareas ([3da788d](3da788d)), closes [#743](#743)
* **bestax-migrate:** convert Bulma grids and cells ([#760](#760)) ([22adc7c](22adc7c)), closes [#743](#743)
* **bestax-migrate:** convert Bulma navbars ([#754](#754)) ([02fdd72](02fdd72))
* **bestax-migrate:** convert Bulma skeletons ([2574ace](2574ace))
* **bestax-migrate:** convert Card's title and footer items on the tags their as takes ([20bc0c6](20bc0c6)), closes [#794](#794)
* **bestax-migrate:** convert conditional classes in clsx and classnames calls ([8948cce](8948cce))
* **bestax-migrate:** convert Modal's parts element by element ([4d41ad2](4d41ad2)), closes [#805](#805)
* **bestax-migrate:** convert navbar dividers with classes, and navbar text colors ([#796](#796)) ([c97c7e9](c97c7e9))
* **bestax-migrate:** convert Pagination, its list and its previous and next links ([d75bc70](d75bc70)), closes [#807](#807)
* **bestax-migrate:** convert Pagination's page links and ellipsis in their li's place ([ae720fa](ae720fa)), closes [#809](#809)
* **bestax-migrate:** convert Panel and its heading, tabs and blocks ([#811](#811)) ([0694e54](0694e54))
* **bestax-migrate:** convert the gap, position, overflow, radius and aspect-ratio helpers ([13dcf63](13dcf63))
* **bestax-migrate:** fold .table-container and .fixed-grid into the component inside ([#761](#761)) ([72504f5](72504f5)), closes [#743](#743)
* **bulma-ui:** add Bulma's gap helper props to every component ([#877](#877)) ([1bd4abc](1bd4abc))
* **bulma-ui:** add Bulma's position, overflow, radius and aspect-ratio helper props ([#845](#845)) ([64fb91c](64fb91c))
* **bulma-ui:** add Collapses, an accordion group for Collapse ([#849](#849)) ([a12c823](a12c823))
* **bulma-ui:** add DateRangeInput ([50fcba7](50fcba7)), closes [#774](#774)
* **bulma-ui:** add Loader, Bulma's inline spinner ([c5e9eb0](c5e9eb0)), closes [#770](#770)
* **bulma-ui:** add month and year pickers to DateInput through granularity ([c4cfc2b](c4cfc2b)), closes [#773](#773)
* **bulma-ui:** add Popover ([2e17fac](2e17fac)), closes [#776](#776)
* **bulma-ui:** export ClientOnly and useIsHydrated ([2c0c1e6](2c0c1e6))
* **bulma-ui:** export Portal ([346d8c2](346d8c2))
* **bulma-ui:** export useFocusTrap, counting the stops the browser's Tab visits ([c304fef](c304fef)), closes [#772](#772)
* **bulma-ui:** let Card.Header.Title and Card.FooterItem render Bulma's own tags ([#794](#794)) ([47ecdb6](47ecdb6))
* **bulma-ui:** make columnGap the column gap helper on Theme ([eb2c611](eb2c611)), closes [#694](#694)
* **bulma-ui:** render Pagination from total, current and onPageChange ([f7ddf04](f7ddf04)), closes [#813](#813)
* **eslint-plugin:** add an opt-in rule for plain elements styled with a Bulma component class ([#750](#750)) ([0bb4d80](0bb4d80))
* **eslint-plugin:** check the gap helper props ([b53a749](b53a749))
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.2.13 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-review needs-human-review Loop converged (or contested): awaiting owner review + manual merge released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant