Repository navigation
fix(bestax-migrate): keep a title's heading level when its size carries over - #1071
Conversation
…es over
bloomer, rbx and react-bulma-components all rendered a title at the element their tag
prop named, or at their own default, whatever the size. bestax's Title and SubTitle
render the heading their size names unless as="p", so renaming the size changed the
heading outline: bloomer's <Subtitle isSize={4}> became <SubTitle size={4} as="h2">,
an h4, while the prop map said the level was kept. rbx's <Title size={4}> and RBC's
<Heading size={4}> turned their h1 into an h4 the same way.
keepTitleLevel in _shared/specials-utils.ts now decides where the size goes for all
three sources. It stays size only where that renders the element the source did: a
<p>, or the heading the size names. Anywhere else it moves to textSize, which sets
the same font size and leaves the level to as. bloomer's Subtitle still writes out
as="h2", except where size={2} already renders one. A size it cannot read stays size
with a TODO that says to pass it as textSize.
New title-levels fixtures cover each branch per source, and e2e/title-levels.test.ts
renders the output and compares the opening tag with what each library rendered.
The kitchen-sink leftovers carry a dynamic size each, the migrate references and
docs guides say how the size is carried, and the MCP index is regenerated.
…tSize The custom-component skill's ProfileCard wrote <Title size="4" as="h3">, which renders an h4: Title's size picks the heading level unless as="p". It is now <Title as="h3" textSize="4">, the same look at the level the example meant, in the Storybook copy, the skill page's generated code and the docs live-preview copy. Agents copy this example, so it should model the pattern that works. The role line's <SubTitle size="6" as="p"> stays as it is, since as="p" wins.
|
deep-review: fresh This fixes #1069. The bloomer, rbx and react-bulma-components codemods carried a title's size over as Review it in full. Above all:
Post every finding, advisory included, as its own thread. |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 53 minutes. View limit details
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughTitle migrations for Bloomer, rbx, and react-bulma-components use shared logic to preserve rendered heading levels while translating size props. Tests, fixtures, examples, and migration references cover the updated mappings and TODO cases. ChangesTitle level migration
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Bug fix · Severity of issue fixed: Medium Sequence Diagram(s)sequenceDiagram
participant BloomerSpecials
participant RbxSpecials
participant ReactBulmaComponentsSpecials
participant keepTitleLevel
BloomerSpecials->>keepTitleLevel: Pass title size, tag, and default level
RbxSpecials->>keepTitleLevel: Pass title size, as, and default level
ReactBulmaComponentsSpecials->>keepTitleLevel: Pass Heading size, renderAs, and default level
keepTitleLevel-->>BloomerSpecials: Return transformed props and TODO rules
keepTitleLevel-->>RbxSpecials: Return transformed props and TODO rules
keepTitleLevel-->>ReactBulmaComponentsSpecials: Return transformed props and TODO rules
Suggested reviewers: Merge Risk: ⚪ Minimal · up to
|
Preview DeploymentPreview URL: https://9c249088.bestax.pages.dev |
There was a problem hiding this comment.
Deep review — 0 blocking · 5 advisory
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🔵 Advisory | Correctness | textSize renders .is-size-N with !important, so the migrated title now beats app CSS that overrides the unqualified .title — a caveat the .title.is-4 note does not cover |
bestax-migrate/src/sources/_shared/specials-utils.ts:435 |
| 2 | 🔵 Advisory | Correctness | The spread is treated as possibly carrying the tag when deciding the size, but not when appending as — the two halves of the helper disagree |
bestax-migrate/src/sources/_shared/specials-utils.ts:455 |
| 3 | 🔵 Advisory | Robustness | A literal size of 7 matches no branch: it lands on bestax's size, which drops it, so the class is lost silently (pre-existing, now pinned by a test) |
bestax-migrate/src/sources/_shared/specials-utils.ts:367 |
| 4 | 🔵 Advisory | API | Two shipped skills still tell agents "for headings use Title/SubTitle size", the step that produced #1069's <h4> |
bulma-ui/src/skill-examples/ProfileCard.tsx:53 |
| 5 | 🔵 Advisory | Correctness | The rbx and RBC migration guides scope the rule to the library's default <h1>, but it also fires when as/renderAs named another element |
docs/docs/guides/getting-started/migration/rbx.md:58 |
Overall: The change is sound and unusually well pinned. keepTitleLevel gets every branch I could reason through right — a <p> and a size that already names the rendered heading keep size; everything readable moves to textSize; a dynamic size or a textSize collision keeps size and files one TODO each — and the three fixture pairs plus e2e/title-levels.test.ts assert the rendered opening tag against the built library rather than text, which is precisely the check whose absence let the bug ship. All 2389 bestax-migrate tests pass, specials-utils.ts is at 100% lines / 93.5% branches, check:conformance is clean and gen:mcp:check finds nothing stale; the "same Sass size" claim behind is-size-N checks out against Bulma's own stylesheet (both .title.is-N and .is-size-N read dv.$sizes). The riskiest part is not the logic but the oracle: the e2e's expected tags are hand-written claims about libraries this repo deliberately never installs, so nothing mechanical holds them to the pinned SHAs. A human should spot-check those three "what the source rendered" comment blocks, and finding 1, which is the one way a migrated app can still look wrong after this fix.
Residual risk:
- The e2e's expected tags are unverified assertions. This session had no network access, so I could not read
AlgusDark/bloomer@b9af827,dfee/rbx@f2a55cforcouds/react-bulma-components@3fc281aas the brief asked. What I could check: every expectation is internally consistent with claims this repo already made and shipped (bloomer'sSubtitledefaulth2was the pre-existing special's whole reason for existing;HeadingtoTitlewith anh1default is pinned by the existingheading-literalsandcompoundsfixtures; rbx'ssubtitleclass replacingtitlerather than joining it matchesheading-literals.output.tsx'sheading subtitleparagraph). Open — the comparison against the three sources at their pinned SHAs still needs a human or a networked run. - The fourth source was already immune.
bulma-classesconverts raw markup, and itstitle/subtitleentries carrysizeDrivesTag: truewithtagInrestricted to the matchinghNandp(src/sources/bulma-classes/class-map.ts:1544,1633), so atitle is-4sitting on an<h2>never becomessize="4". Refuted. - No second path reaches
Title/SubTitle. Across all three mapping tables,target: 'Title'/'SubTitle'appears only on bloomer'sTitle/Subtitle, rbx'sTitleand RBC'sHeading— each of which now routes throughkeepTitleLevel. Refuted. - The repo-wide sweep holds. Every remaining
Title/SubTitlethat sets bothas="hN"andsizeis deliberate: the two precedence tests inTitle.test.tsx/SubTitle.test.tsx, plus codemod fixtures and doc tables where the two agree. The rest of the library's examples useas="p", whichsizenever overrides. Refuted. textSizesurvives on both components.TitleProps/SubTitlePropsextendBulmaClassesProps,validTextSizesis'1'through'7', and the class is emitted throughcreateBulmaClassHelpers, so a configured class prefix reaches it the same way it reachesis-4. Refuted.- One branch is unreachable per source, by design. bloomer has no
textSizesource prop, so its collision branch cannot fire — and bloomer'sunmappables.mdcorrectly omits the collision recipe that rbx's and RBC's include (RBC does have a universaltextSize,mapping.ts:44). Refuted. - Not a residual risk, for the record: the RBC
heading-truthy path dropssizeonto the plain paragraph it builds, but a<p>carries no heading level and Bulma v1 ships no.headingstyles, so it falls outside this failure class.
🏄 Paddled out expecting a gnarly close-out and found clean glassy water instead — one shared helper doing the work of three, fixtures that actually render instead of squinting at strings, and docs that mostly tell the truth. Five little advisory ripples, nothing that'll pitch you off the board. Send it.
…ame way for as
A literal size of 7 fell through keepTitleLevel to a plain size={7}. Bulma styles title.is-7,
but bestax's size stops at 6, so that was a type error that rendered no size class at all.
textSize takes '7', so a 7 now moves there, including beside a <p>, where size={7} would
also have lost its class. Any other literal outside 1 to 7 gets the TODO a dynamic size
gets, instead of a quiet rename.
The helper also read a spread two ways. For the size it made the element unknown, but it
still appended Subtitle's as="h2" after the spread, so the two halves disagreed. A spread
can only name the element where the source spells its tag prop as, which is rbx. bloomer's
tag and RBC's renderAs inside a spread never reach bestax's as, so for those two the spread
no longer hides the element, and as is only written where the element is known.
The fixtures and the title-levels e2e gain size-7 cases, and the prop maps and unmappables
say where a 7 and an out-of-range literal go.
…s plus textSize Bulma's is-size-N helper is !important and title.is-N is not, so a font size an app's own CSS gives a migrated title, such as .hero .title, can now lose where it used to win. The three migrate prop maps say so next to the class-name note. The custom-component and layout-scaffold skills' inline-style table told agents to size a heading with Title/SubTitle size, the step that produced #1069's h4. The row now says as sets the level and size picks it too, so an h3 at size 4 is as="h3" textSize="4". The rbx and RBC migration guides named only the default h1 where the size moves to textSize. They now say the element the library rendered, which covers an as or renderAs.
…as plus textSize The CLAUDE.md create-bestax writes into a new app carries the same inline-style table as the custom-component and layout-scaffold skills, with the row that told agents to size a heading with Title/SubTitle size. It now says what the skills say: as sets the level and size picks it too, so an h3 at size 4 is as="h3" textSize="4".
|
deep-review: verify Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 795e233. |
Preview DeploymentPreview URL: https://4a2d5943.bestax.pages.dev |
There was a problem hiding this comment.
Deep review (verify) — 5 resolved · 0 open
| No | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🔵 Advisory | Correctness | textSize swaps a non-!important size for an !important one — the caveat is now stated in all three prop maps |
skills/bestax-migrate/references/bloomer/prop-map.md:102 |
| 2 | 🔵 Advisory | Correctness | Spread respected for the size but not for as — both halves now gate on rendered |
bestax-migrate/src/sources/_shared/specials-utils.ts:418 |
| 3 | 🔵 Advisory | Robustness | A literal size of 7 lost its class — it now moves to textSize, and 8 or more gets a TODO |
bestax-migrate/src/sources/_shared/specials-utils.ts:369 |
| 4 | 🔵 Advisory | API | The skill fontSize row still steered agents at size — it now names as plus textSize |
skills/bestax-custom-component/SKILL.md:177 |
| 5 | 🔵 Advisory | Correctness | The migration guides named only the default element — now "the element rbx/RBC rendered" | docs/docs/guides/getting-started/migration/rbx.md:59 |
Overall: This pass settled threads and reviewed no commits. All five of my open threads — every one 🔵 Advisory — are verified fixed against the current code and resolved; nothing is left open, and no new findings were raised. The two code fixes are the substantive ones: TEXT_SIZES now carries the seventh Bulma size, so a size of 7 moves to textSize="7" instead of being renamed into a class-less size; a literal outside 1 to 7 reaches the TODO branch rather than a silent rename; and a spread hides the element only for rbx, the one source whose tag prop is spelled as, so the size decision and the as write read the same unknown.
Residual risk: this pass raises none — it settles threads rather than re-reviewing. For the record, what I checked while verifying:
- Size-7 path — refuted as still broken:
movabletestsTEXT_SIZESwhiletakestestsTITLE_SIZES, so 7 moves even besideas="p";e2e/title-levels.test.ts:43,44,68,91asserts the renderedtitle is-7survives on an h1 and on a p across all three sources. - Out-of-range literal — refuted:
bloomer/__tests__/specials.test.ts:920-926pins a size of 8 to aprop:isSizeTODO, replacing the earlier test that asserted silence. - Spread vs
asdisagreement — refuted:specials-utils.ts:418and:462both gate onrendered; rbx is unaffected, itslevelbeing h1. - Generated artefacts — refuted as stale:
gen:mcp:check,gen:catalog:checkandcheck:conformanceall pass on the head commit. - Test evidence — bestax-migrate: 57 suites, 2394 tests, all green, after building
bulma-ui/dist, which the e2e typecheck needs. - Cascade caveat —
.is-size-Nbeing!importantis a real behavioural difference no code here removes; it is documented in all three prop maps and accepted deliberately, with the reason recorded on the thread.
🏄 Clean set, dude — every wave I called out got ridden all the way in, two of them with real code and tests behind them, not just hand-waving. Threads are closed out and the suite is glassy. Good to go.
…evels # Conflicts: # bestax-mcp/data/skills.json
|
deep-review: verify Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 7e95f94. |
Preview DeploymentPreview URL: https://77bddd5e.bestax.pages.dev |
There was a problem hiding this comment.
Deep review (verify) — 0 resolved · 0 open
No threads left to settle. All five of my inline threads were already resolved by the preceding verify pass; this pass found nothing open and raised nothing new.
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| — | — | — | No open threads; nothing resolved this pass. | — |
Re-check of the five already-settled threads against 7e95f94e
The branch has since merged main twice (795e2338, 2871c2a3) and regenerated (7e95f94e), so I re-confirmed each fix survived the merges rather than taking the earlier verification on trust:
| Thread | Fix still present at HEAD |
|---|---|
textSize swaps a non-!important size for an !important one |
the caveat is in all three prop maps — bloomer/prop-map.md:101, rbx/prop-map.md:139, react-bulma-components/prop-map.md:67 |
Spread respected for the size but not for as |
specials-utils.ts:421 — the else if now also requires that the source not spell its tag prop as |
| A literal size of 7 falls through every branch | specials-utils.ts:370 adds TEXT_SIZES; movable tests it (:434) while takes keeps testing TITLE_SIZES (:438) |
Skill still sends agents to size for headings |
bestax-custom-component/SKILL.md:177, bestax-layout-scaffold/SKILL.md:194, create-bestax/src/constants.ts:143 |
| Guide line names only the default element | rbx.md:59 and react-bulma-components.md:49 now say the size moves wherever bestax size would change the element the source rendered |
Test run: pnpm --filter bestax-migrate run test — 57 suites, 2394 tests passed. (The first run failed 17 suites on TS2307: Cannot find module @allxsmith/bestax-bulma; that is the documented missing-bulma-ui/dist condition, not a defect — building bulma-ui first made it green.)
Overall: This pass settled threads and reviewed no commits. Nothing was left open for it to settle — every finding from the original review had already been fixed and verified, and I re-confirmed all five fixes are still in the tree at 7e95f94e after the two main merges, with the full bestax-migrate suite green. A reader who wants the commits pushed since the original review read needs a fresh review: a deep-review: steer whose first word is fresh, then the label re-applied.
Residual risk: none identified — a verify pass reads no new code, so it introduces no residual risk of its own; the residual-risk analysis for the change itself stands as written in the original review.
🏄 Nothing but glassy water out here, dude — every wave already ridden, paddled back out, and the board is still waxed after two merges with main. Zero open threads, 2394 tests clean. This one is good to go.
|
deep-review: fresh Main is merged in, bringing the refreshed create-bestax visual baselines, so the Visual Regression checks this PR touches are green again. The PR's own changes are the ones the last verify pass settled: the codemod's |
There was a problem hiding this comment.
Deep review — 1 blocking · 2 advisory
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🟡 Minor | Correctness | The prop:size TODO claims bestax renders "the heading its size names" for a literal outside 1 to 7, where bestax instead drops the size class and keeps as / h1 — and names textSize as the destination for a value it cannot take |
bestax-migrate/src/sources/_shared/specials-utils.ts:455 |
| 2 | 🔵 Advisory | Correctness | The rbx and RBC prop-map and unmappables say an unreadable size is always flagged; beside as="p" / renderAs="p" it is kept with no TODO (the bloomer unmappables states that exemption, the bloomer prop-map does not) |
skills/bestax-migrate/references/rbx/prop-map.md:141 |
| 3 | 🔵 Advisory | Coverage | The boolean-literal guard is the only new branch nothing reaches (93.03% branch, line 447 uncovered); a bare isSize gets neither a move nor a TODO and lands as a boolean size |
bestax-migrate/src/sources/_shared/specials-utils.ts:447 |
Overall: The change is sound, and the design is the right one: keepTitleLevel keeps size only where it renders the element the source did (a <p>, or the heading the size names) and moves it to textSize everywhere else, with all three sources sharing one helper rather than three near-copies — exactly what bestax-migrate/CLAUDE.md asks for after #613. The strongest part is e2e/title-levels.test.ts: it renders the migrated output against the built library and compares opening tags including the class list, which is the check the text-only fixtures could never make, and it is what makes the claimed per-branch coverage believable rather than merely asserted. The riskiest surface is not the transform but the prose it generates and ships — the TODO message in finding 1 and the reference wording in finding 2 are both claims about bestax that no test reads, which is how the four false TODOs on #613 got out. Read those two first; everything else here verified green.
Residual risk:
- The same failure class in
bulma-classes, the fourth source, untouched by this diff — refuted:class-map.ts:1544-1559already gates everyis-Ntosizewrite behind atagInof the matching heading orp, andplan.ts:1100skips a modifier whosetagInexcludes the tag being converted, so atitle is-4on anh2never converts into asizethat would re-level it. - A spread that smuggles in the element prop — refuted for the element the codemod writes: for rbx and RBC,
keepTitleLevelleaves the element unknown beside a spread and moves the size conservatively (specials-utils.ts:421), and for bloomer atagorrenderAsinside a spread never becomes the bestaxas, so the element is the one the written props decide. Unit tests cover both halves. - A
textSizealready on the element — refuted: the move is guarded bymovable && !textSize, so no element can emit the prop twice; a collision takes the TODO path instead (rbx/__tests__/specials.test.ts, "flags a size it cannot move"). - The
asthatenforcePolymorphicPropslater drops — open, and unchanged in kind by this PR: anas="span"beside a size now rendersh1where it used to render the heading the size named. rbx rendered a<span>, so neither is right, and the existingprop:asTODO names the tag to restore. - Helper-class specificity — on the record already (resolved thread):
textSizewrites the Bulma helper, which is!important, where the title size class is not, so an app rule such as.hero .titlecan now lose where it used to win. The e2e reads the helper class as the title class, so by construction it cannot see this; the three prop maps say it in prose. - Release scoping — refuted:
ProfileCardhas no runtime code in the published tarball (absent fromdist/index.cjsanddist/index.esm.js; onlydist/types/skill-examples/ProfileCard.d.ts, which a JSX body cannot change), so thedocs(bulma-ui)plusfix(bestax-migrate)split the PR body describes is right.
What I ran: turbo test --filter=bestax-migrate (57 suites / 2394 tests, including the new e2e/title-levels.test.ts), the same for @allxsmith/bestax-bulma and create-bestax (191 / 7934) and for bestax-mcp, plus check:conformance, test:scripts (green once eslint-plugin/dist was built; every measured file at or above its floor) and test:coverage --filter=bestax-migrate. The six reference byte counts in bestax-mcp/data/skills.json match the files on disk.
🏄 Clean little set wave, dude — the codemod finally quit turning the top-of-page heading into a deeper one behind your back, and that new render-the-real-tag e2e is the honest paddle-out that proves it. Just two bits of beach graffiti to touch up in the TODO text and the rbx/RBC reference sheets, and this one is good to go.
…lag a bare one
keepTitleLevel gave a literal outside 1 to 7 the TODO a dynamic size gets, which says the
heading follows the size. bestax's Title ignores a size outside 1 to 6: it renders no size
class and keeps its element, so the level was never at risk. That literal now gets its own
TODO, saying the class goes and to drop the size or set a textSize from '1' to '7'.
A bare isSize, or any other boolean size, was the one shape that got neither a move nor a
TODO, so it became size={true}, which does not typecheck. It now counts as a literal bestax's
size does not take and gets the same TODO, beside a <p> too. A unit test pins it.
The references stop promising a TODO for every size the codemod cannot read: beside a <p>
an unreadable size is left alone. The prop maps now name only the out-of-range literal,
and each unmappables recipe says what to do with one.
|
deep-review: verify Every open thread has a reply: fixed, with the commit, or refuted with a reason. This settles them on 5a687a5. |
Preview DeploymentPreview URL: https://c748fefa.bestax.pages.dev |
There was a problem hiding this comment.
Deep review (verify) — 3 resolved · 0 open
| # | Severity | Area | Thread | Location | Outcome |
|---|---|---|---|---|---|
| 1 | 🟡 Minor | Correctness | TODO claimed a size-driven heading for a size bestax ignores | bestax-migrate/src/sources/_shared/specials-utils.ts:457 |
✅ Verified fixed |
| 2 | 🔵 Advisory | Correctness | rbx and RBC references promised a TODO a <p> never gets |
skills/bestax-migrate/references/rbx/prop-map.md:141 |
✅ Verified fixed |
| 3 | 🔵 Advisory | Coverage | Boolean-size guard reached by no test | bestax-migrate/src/sources/_shared/specials-utils.ts:452 |
✅ Verified fixed |
What I checked
- #1 — the TODO branch now tests
!takesfirst, so an out-of-range literal gets its own message, which names the dropped size class and no heading. It matches the component:validTitleSizesandvalidSubTitleSizesare"1"–"6", sovalidSizeisundefinedand nois-*class renders.bloomer/__tests__/specials.test.ts:927-928pins the message both ways — it contains "renders no size class" and does not contain "heading". - #2 — the unreadable-size clause is deleted from all three prop maps; the surviving sentence ("A literal outside 1 to 7 keeps
sizeand gets a TODO") holds unconditionally, since an out-of-range literal makestakesfalse and so cannot reach the<p>short-circuit. The rbx and RBCunmappables.mddropped "and is flagged", and bloomer keeps its explicit exemption line atunmappables.md:292. - #3 — the
literal.kind !== "boolean"guard is gone and the last arm is a plainelse, so<Title isSize>now gets the out-of-range TODO instead of a silentsize={true}, besidetag="p"too. That line no longer appears among the uncovered branches ofspecials-utils.ts(100 | 93.67 | 100 | 100).
Overall: this pass settled threads and reviewed no commits. All three previously open threads are fixed in the current code, each confirmed by reading the changed logic against Title.tsx and SubTitle.tsx and by a full green run of the bestax-migrate suite with coverage — 57 suites, 2395 tests. Nothing was refuted and nothing is left open.
Residual risk: not assessed — a verify pass settles the prior review findings and raises none. For a review of the commits pushed since, post a deep-review: fresh … steer and re-apply the label.
🏄 Paddled back out to check the three waves I called out last time, and they are all clean rides now — the TODO says what it means, the skill docs stopped over-promising, and that sneaky bare
isSizegot flagged instead of slipping through. Smooth water, good to go.
## [5.34.1](https://github.com/allxsmith/bestax/compare/@allxsmith/bestax-bulma@5.34.0...@allxsmith/bestax-bulma@5.34.1) (2026-10-16) ### Bug Fixes * **bestax-migrate:** keep a title's heading level when its size carries over ([#1071](#1071)) ([7549699](7549699)) * **bulma-ui:** wire Taginput's suggestions as a combobox ([#1084](#1084)) ([e30c33a](e30c33a)), closes [#942](#942) [#998](#998) [#1082](#1082) * **docs:** pin react-scripts in CodeSandbox sandboxes and keep the sandbox notes in step ([#1074](#1074)) ([3ac8a65](3ac8a65)) ### Features * **docs:** open live examples in StackBlitz and CodeSandbox ([#1073](#1073)) ([730db1e](730db1e))
|
🎉 This PR is included in version 5.34.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
## [2.25.4](https://github.com/allxsmith/bestax/compare/bestax-migrate@2.25.3...bestax-migrate@2.25.4) (2026-10-16) ### Bug Fixes * **bestax-mcp:** leave the query behind when a component's own rows answered it ([ec8a2f1](ec8a2f1)) * **bestax-mcp:** say "matching" in the pointer answer only when there is a query ([2d28530](2d28530)) * **bestax-mcp:** say Button's variables style it for the colors and modifiers it accepts ([2b94042](2b94042)) * **bestax-migrate:** keep a title's heading level when its size carries over ([#1071](#1071)) ([7549699](7549699)) * **bulma-ui:** announce Autocomplete's empty content, and keep its listbox to options ([98cfaac](98cfaac)), closes [#1066](#1066) * **bulma-ui:** describe a Tooltip's trigger with its text for screen readers ([5a7eb99](5a7eb99)), closes [#1031](#1031) * **bulma-ui:** drop a key's fill in forced colors, so only its border draws it ([466ad38](466ad38)), closes [#1063](#1063) * **bulma-ui:** hide a Tooltip with its stylesheet alone, without aria-hidden ([d4cbee4](d4cbee4)) * **bulma-ui:** hold Autocomplete's empty announcement while results load ([75d8fdf](75d8fdf)) * **bulma-ui:** keep a loading ToggleButton from toggling ([cf9f6e8](cf9f6e8)) * **bulma-ui:** keep EmptyState's left room only for an indicator, and follow py ([9a615d6](9a615d6)), closes [#1065](#1065) * **bulma-ui:** keep SegmentedControl's fallback tab stop on the first segment after a reorder ([b32df32](b32df32)) * **bulma-ui:** key each key in Kbd.Group by its own React key ([b51c5c8](b51c5c8)), closes [#1063](#1063) * **bulma-ui:** leave the keys alone on a SegmentedControl segment outside its row ([f477666](f477666)) * **bulma-ui:** mark a loading ToggleButton aria-disabled as well as aria-busy ([f87425f](f87425f)) * **bulma-ui:** open Fragments in Kbd.Group before placing its separators ([c595de2](c595de2)), closes [#1063](#1063) * **bulma-ui:** stop the keys a SegmentedControl takes from reaching listeners above it ([6b9e797](6b9e797)) * **bulma-ui:** warn when Tab can't reach a Tooltip trigger that carries the link ([cfd21c7](cfd21c7)) * **bulma-ui:** wire Taginput's suggestions as a combobox ([#1084](#1084)) ([e30c33a](e30c33a)), closes [#942](#942) [#998](#998) [#1082](#1082) * **docs:** address the deep review of the sandbox builders ([b7338d9](b7338d9)) * **docs:** close the CodeSandbox tab when its launch fails ([d1e6d1d](d1e6d1d)) * **docs:** compare the icon link as a string in the sandbox test ([9ac4737](9ac4737)) * **docs:** darken the live preview surface and follow the library stylesheet import ([363badf](363badf)) * **docs:** draw the CodeSandbox button icon as the cube mark ([8389e18](8389e18)) * **docs:** give both sandboxes the example window's dark page and padding ([33dfb4a](33dfb4a)) * **docs:** keep the live preview's spacing outside its dark surface ([7a158e1](7a158e1)) * **docs:** keep the live-preview margin off the homepage previews ([f18eed3](f18eed3)) * **docs:** load the CodeSandbox stylesheet from the CDN and darken example windows ([253f057](253f057)) * **docs:** match the preview's color scheme and keep the copy button on narrow screens ([c4d6d25](c4d6d25)) * **docs:** open CodeSandbox through its JSON define API ([6a65276](6a65276)) * **docs:** open the CodeSandbox tab on the click, and skip the sandbox when popups are blocked ([bcc0634](bcc0634)) * **docs:** pin react-scripts in CodeSandbox sandboxes and keep the sandbox notes in step ([#1074](#1074)) ([3ac8a65](3ac8a65)) * **docs:** restore the homepage preview margin, and reject a CodeSandbox reply without an id ([9e0991f](9e0991f)) * **docs:** send the copy and sandbox buttons the code the reader typed ([019edf0](019edf0)) ### Features * **bestax-mcp:** name the other component's call in get_component's theming hint ([cbedd5f](cbedd5f)) * **bestax-mcp:** point get_css_variables at the component whose element a component renders ([d4be1a8](d4be1a8)) * **bulma-ui:** add Buttons.ToggleButton ([0c8fb10](0c8fb10)) * **bulma-ui:** add EmptyState, the block a list shows when it has nothing in it ([c5929d2](c5929d2)), closes [#1065](#1065) * **bulma-ui:** add Kbd, a key drawn as a keycap, and Kbd.Group for combinations ([98d1578](98d1578)), closes [#1063](#1063) * **bulma-ui:** add SegmentedControl, a row of buttons where only one can be on ([b1be0e8](b1be0e8)), closes [#1040](#1040) * **bulma-ui:** give Dropdown typeahead ([1bb35bf](1bb35bf)), closes [#1035](#1035) * **docs:** open live examples in StackBlitz and CodeSandbox ([654b16c](654b16c)) * **docs:** open live examples in StackBlitz and CodeSandbox ([#1073](#1073)) ([730db1e](730db1e))
|
🎉 This PR is included in version 2.25.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
## [1.15.4](https://github.com/allxsmith/bestax/compare/bestax-mcp@1.15.3...bestax-mcp@1.15.4) (2026-10-16) ### Bug Fixes * **bestax-migrate:** keep a title's heading level when its size carries over ([#1071](#1071)) ([7549699](7549699)) * **bulma-ui:** wire Taginput's suggestions as a combobox ([#1084](#1084)) ([e30c33a](e30c33a)), closes [#942](#942) [#998](#998) [#1082](#1082) * **docs:** pin react-scripts in CodeSandbox sandboxes and keep the sandbox notes in step ([#1074](#1074)) ([3ac8a65](3ac8a65)) ### Features * **docs:** open live examples in StackBlitz and CodeSandbox ([#1073](#1073)) ([730db1e](730db1e))
|
🎉 This PR is included in version 1.15.4 🎉 The release is available on: Your semantic-release bot 📦🚀 |
## [4.2.17](https://github.com/allxsmith/bestax/compare/create-bestax@4.2.16...create-bestax@4.2.17) (2026-10-17) ### Bug Fixes * **bestax-mcp:** leave the query behind when a component's own rows answered it ([ec8a2f1](ec8a2f1)) * **bestax-mcp:** release with every bulma-ui release so the published index keeps up ([#1027](#1027)) ([618e483](618e483)) * **bestax-mcp:** say "matching" in the pointer answer only when there is a query ([2d28530](2d28530)) * **bestax-mcp:** say Button's variables style it for the colors and modifiers it accepts ([2b94042](2b94042)) * **bestax-migrate:** carry size to Buttons, Tags and Message ([#1038](#1038)) ([7549264](7549264)) * **bestax-migrate:** keep a title's heading level when its size carries over ([#1071](#1071)) ([7549699](7549699)) * **bulma-ui:** announce Autocomplete's empty content, and keep its listbox to options ([98cfaac](98cfaac)), closes [#1066](#1066) * **bulma-ui:** deprecate Autocomplete's unread selected and dropdown props ([#1091](#1091)) ([5a90eba](5a90eba)), closes [#1080](#1080) * **bulma-ui:** describe a Tooltip's trigger with its text for screen readers ([5a7eb99](5a7eb99)), closes [#1031](#1031) * **bulma-ui:** drop a Field label's for when nothing it holds takes it ([#1020](#1020)) ([3604c14](3604c14)) * **bulma-ui:** drop a key's fill in forced colors, so only its border draws it ([466ad38](466ad38)), closes [#1063](#1063) * **bulma-ui:** give TimeInput's AM/PM column a real name for screen readers ([2b1122f](2b1122f)) * **bulma-ui:** hide a Tooltip with its stylesheet alone, without aria-hidden ([d4cbee4](d4cbee4)) * **bulma-ui:** hold Autocomplete's empty announcement while results load ([75d8fdf](75d8fdf)) * **bulma-ui:** keep a loading ToggleButton from toggling ([cf9f6e8](cf9f6e8)) * **bulma-ui:** keep a Notification's text clear of its close button ([#1017](#1017)) ([91ebb63](91ebb63)) * **bulma-ui:** keep a picker label's default when its override is undefined ([aa88e50](aa88e50)) * **bulma-ui:** keep EmptyState's left room only for an indicator, and follow py ([9a615d6](9a615d6)), closes [#1065](#1065) * **bulma-ui:** keep SegmentedControl's fallback tab stop on the first segment after a reorder ([b32df32](b32df32)) * **bulma-ui:** keep Theme's component-variable check out of production ([537d90d](537d90d)) * **bulma-ui:** key each key in Kbd.Group by its own React key ([b51c5c8](b51c5c8)), closes [#1063](#1063) * **bulma-ui:** leave the keys alone on a SegmentedControl segment outside its row ([f477666](f477666)) * **bulma-ui:** let Autocomplete's Enter submit, keep focus on a click, and open on a highlight ([#1090](#1090)) ([81f5982](81f5982)), closes [#1078](#1078) [#1089](#1089) [#1079](#1079) [#1081](#1081) [#1081](#1081) * **bulma-ui:** mark a loading ToggleButton aria-disabled as well as aria-busy ([f87425f](f87425f)) * **bulma-ui:** name a composed DateRangeInputBase's group from a Field label ([75aa6cb](75aa6cb)) * **bulma-ui:** name a range Slider's thumbs from a hand-wired Field label's id ([355065b](355065b)) * **bulma-ui:** name Autocomplete's list from a hand-wired Field label's id ([ad46c06](ad46c06)) * **bulma-ui:** name Autocomplete's suggestion list from a surrounding Field's label ([190183f](190183f)), closes [#998](#998) * **bulma-ui:** one Escape closes one layer inside a Modal or Sidebar ([#1086](#1086)) ([3ea7ac8](3ea7ac8)), closes [#1077](#1077) [#1077](#1077) * **bulma-ui:** open Fragments in Kbd.Group before placing its separators ([c595de2](c595de2)), closes [#1063](#1063) * **bulma-ui:** say a label placed in Field.Label reaches no thumbs or list ([07c2a99](07c2a99)) * **bulma-ui:** say the label prop needs wiring to reach thumbs or list in an inner Field ([9b33d8d](9b33d8d)) * **bulma-ui:** say which controls a hand-wired Field label's id reaches ([514127f](514127f)) * **bulma-ui:** stop the keys a SegmentedControl takes from reaching listeners above it ([6b9e797](6b9e797)) * **bulma-ui:** warn when Tab can't reach a Tooltip trigger that carries the link ([cfd21c7](cfd21c7)) * **bulma-ui:** warn when Theme is given a variable Bulma sets on the component ([bb8f9b8](bb8f9b8)), closes [#1021](#1021) * **bulma-ui:** wire Taginput's suggestions as a combobox ([#1084](#1084)) ([e30c33a](e30c33a)), closes [#942](#942) [#998](#998) [#1082](#1082) * **create-bestax:** bring the vite-ts tsconfigs up to create-vite 9 ([#1076](#1076)) ([dc6995c](dc6995c)), closes [#1049](#1049) * **docs:** address the deep review of the sandbox builders ([b7338d9](b7338d9)) * **docs:** close the CodeSandbox tab when its launch fails ([d1e6d1d](d1e6d1d)) * **docs:** compare the icon link as a string in the sandbox test ([9ac4737](9ac4737)) * **docs:** darken the live preview surface and follow the library stylesheet import ([363badf](363badf)) * **docs:** draw the CodeSandbox button icon as the cube mark ([8389e18](8389e18)) * **docs:** give both sandboxes the example window's dark page and padding ([33dfb4a](33dfb4a)) * **docs:** give the mixin note the modifier caveat its lead carries ([1427f08](1427f08)) * **docs:** keep the live preview's spacing outside its dark surface ([7a158e1](7a158e1)) * **docs:** keep the live-preview margin off the homepage previews ([f18eed3](f18eed3)) * **docs:** keep the modifier caveat for mixin variables in the MCP index ([e227663](e227663)) * **docs:** load the CodeSandbox stylesheet from the CDN and darken example windows ([253f057](253f057)) * **docs:** match the preview's color scheme and keep the copy button on narrow screens ([c4d6d25](c4d6d25)) * **docs:** name the one edge of the rule for which variables Theme reaches ([b33ebfa](b33ebfa)) * **docs:** open CodeSandbox through its JSON define API ([6a65276](6a65276)) * **docs:** open the CodeSandbox tab on the click, and skip the sandbox when popups are blocked ([bcc0634](bcc0634)) * **docs:** pin react-scripts in CodeSandbox sandboxes and keep the sandbox notes in step ([#1074](#1074)) ([3ac8a65](3ac8a65)) * **docs:** qualify the guide and the theming skill on what Theme reaches ([9c2347b](9c2347b)) * **docs:** read only a component's own mixin as its home ([f40cd2c](f40cd2c)) * **docs:** refuse a CSS-variable scope the page cannot fully word ([c815602](c815602)) * **docs:** restore the homepage preview margin, and reject a CodeSandbox reply without an id ([9e0991f](9e0991f)) * **docs:** say where component-scoped Bulma variables have to be set ([771d5bd](771d5bd)), closes [#1021](#1021) * **docs:** send the copy and sandbox buttons the code the reader typed ([019edf0](019edf0)) ### Features * **bestax-mcp:** name the other component's call in get_component's theming hint ([cbedd5f](cbedd5f)) * **bestax-mcp:** point get_css_variables at the component whose element a component renders ([d4be1a8](d4be1a8)) * **bulma-ui:** add Buttons.ToggleButton ([0c8fb10](0c8fb10)) * **bulma-ui:** add ContextMenu, a menu that opens on right-click or long press ([#1036](#1036)) ([85a3e77](85a3e77)) * **bulma-ui:** add EmptyState, the block a list shows when it has nothing in it ([c5929d2](c5929d2)), closes [#1065](#1065) * **bulma-ui:** add HoverCard, a card that opens on hover or keyboard focus ([#1030](#1030)) ([0eb41df](0eb41df)) * **bulma-ui:** add Kbd, a key drawn as a keycap, and Kbd.Group for combinations ([98d1578](98d1578)), closes [#1063](#1063) * **bulma-ui:** add SegmentedControl, a row of buttons where only one can be on ([b1be0e8](b1be0e8)), closes [#1040](#1040) * **bulma-ui:** add ToggleButton, a button that stays pressed ([#1041](#1041)) ([582aff0](582aff0)) * **bulma-ui:** give Dropdown typeahead ([1bb35bf](1bb35bf)), closes [#1035](#1035) * **bulma-ui:** let a Tooltip be hovered, dismissed with Escape, and opened by a tap ([#1033](#1033)) ([52aeeda](52aeeda)) * **bulma-ui:** let ConfigProvider set where portaled content goes ([#1032](#1032)) ([5e928ae](5e928ae)) * **docs:** open live examples in StackBlitz and CodeSandbox ([654b16c](654b16c)) * **docs:** open live examples in StackBlitz and CodeSandbox ([#1073](#1073)) ([730db1e](730db1e))
|
🎉 This PR is included in version 4.2.17 🎉 The release is available on: Your semantic-release bot 📦🚀 |
TitleandSubTitlerender the heading theirsizenames unlessas="p", so<Title as="h3" size="4">is an<h4>. Two places in the repo were written as ifasset thelevel. This fixes both, and neither touches the components.
The Codemod
Every source library rendered a title at the element its tag prop named, or at its own default,
whatever the size: bloomer's
Titlean<h1>and itsSubtitlean<h2>, rbx'sTitleandRBC's
Headingan<h1>, subtitle or not. I read each one off the library's source at the SHAits corpus script pins. The codemod renamed the size straight onto bestax's
size, so theheading outline changed under every sized title, and the bloomer prop map said the level was
kept. The issue named bloomer, but rbx and RBC had the same bug, so all three are fixed here
through one shared helper,
keepTitleLevelin_shared/specials-utils.ts.Before: the size became
size, always. bloomer'sSubtitlealso gainedas="h2"wheneverit had no
tag.After: the size stays
sizeonly where that renders the element the source did: a<p>, orthe heading the size names. Anywhere else it becomes
textSize, which sets the same font sizeand leaves the level to
as. So does a 7: Bulma styles.title.is-7, but bestax'ssizestopsat 6, and
textSizetakes'7'. bloomer'sSubtitlestill getsas="h2", except wheresize={2}already renders one, so the output stays minimal. In rbx, where a spread can carryas, a readable size beside one moves even where it names the default<h1>. bloomer'stagand RBC's
renderAsnever reachasfrom inside a spread, so there the spread changes nothing.A size the codemod can't read stays
size, and at a heading it gets a TODO that says to carryit with
textSize. The same TODO covers an rbx or RBC title that already setstextSize. Aliteral bestax's
sizedoesn't take, a bareisSizeincluded, renders no size class, so its TODOsays to drop it or set a
textSizefrom 1 to 7.The bloomer
imports-basicfixture, before and after:textSizewrites Bulma'sis-size-4where the title hadis-4. Both use the same Sass size, sothe look holds, but an app's own CSS that targets
.title.is-4needs the new class too. Andis-size-4is!importantwhere.title.is-4is not, so a font size the app's CSS gives thetitle, such as
.hero .title, can now lose where it used to win. The prop maps say both.What pins it:
title-levelsfixture pair per source covers each branch: the default, a size that namesthe level, a size that doesn't, a tag,
p, and a 7.e2e/title-levels.test.tsmigrates each case, renders it against the built library andcompares the opening tag with what the source library rendered, classes included, reading
is-size-Nasis-N. The existing fixtures only compare text, which is how this shipped.rule is documented.
pexemption, a dynamic tag, a spread in bloomerand in rbx, an out-of-range literal, a bare
isSizeand thetextSizecollision.The skill references now say how the size is carried: a "Title and Subtitle sizes" section in
bloomer's prop map, matching sections in rbx's and RBC's, a recipe for the TODO in each
unmappables.md, and a note on RBC'sHeadingrow. The three docs migration guides say ittoo, and the MCP index is regenerated.
The ProfileCard Example
The custom-component skill's
ProfileCardwrote<Title size="4" as="h3">, an<h4>. It is now<Title as="h3" textSize="4">inbulma-ui/src/skill-examples/ProfileCard.tsx,docs/docs/skills/custom-component.mdxand the docs live-preview copy indocs/src/components/SkillExamples/ProfileCard.jsx. There's no copy underskills/. The roleline's
<SubTitle size="6" as="p">stays, sinceas="p"wins.The guidance that produced it goes too. The inline-style table in the custom-component and
layout-scaffold skills, and its copy in the CLAUDE.md create-bestax writes into a new app, told
agents to size a heading with
size. That row now saysassets the level andsizepicks ittoo, so an
<h3>at size 4 isas="h3" textSize="4".I swept the repo for every
Title/SubTitlethat sets bothasandsize. The rest are fine:HoverCardand the stories, docs, templates and skill examples all useas="p", whichsizenever overrides, and the two
as="h4" size="2"cases inTitle.test.tsxandSubTitle.test.tsxpin the precedence on purpose.
Release
The squash title should carry
fix(bestax-migrate). That releases a bestax-migrate patch, whichis what gets the codemod fix to users. The
bulma-uicommit isdocs(bulma-ui):ProfileCardis a Storybook and docs example with no runtime code in the package, and the.d.tsthe build emits for it doesn't change. Afix(bulma-ui)title would publish bestax-bulmaand bestax-mcp patches that change nothing, and bestax-migrate wouldn't release at all. The
create-bestax CLAUDE.md row and the two skill rows ride the next create-bestax release, the way
skill changes usually do.
Listings
This edits the bestax-migrate, custom-component and layout-scaffold skills, so per "What goes
stale" in the distribution guide, the cursor.directory and ClawHub copies of those skills need
the new text pasted in, and the Tessl
Registry and Skills Directory keep the old scan until it's analysed again. Once the
bestax-migrate patch ships, the Codemod Registry wrapper needs its usual bump and republish.
Checks
Run locally: bestax-migrate's full suite (fixtures, the e2es, the ref roster), create-bestax's
tests,
typecheck,typecheck:tests,lint,format:check,check:conformance,test:scripts(the docs fence,ESLint docs and skill-examples typecheck tests included), bestax-mcp's tests, and
pnpm genwithnothing stale left behind.
Fixes #1069