Skip to content

feat(create-bestax): agent-validated guidance for skills, scaffold CLAUDE.md, and catalog - #365

Merged
allxsmith merged 8 commits into
mainfrom
feat/skill-loop-guidance
Jul 29, 2026
Merged

allxsmith merged 8 commits into
mainfrom
feat/skill-loop-guidance

Conversation

@allxsmith

@allxsmith allxsmith commented Jul 24, 2026 •

Copy link
Copy Markdown
Owner

Part of #363. The validated tooling changes from the 10-iteration cold-start eval loop (baseline 85/100 → revised-runs mean 95.2, builder cost −43%). Every factual claim was verified against bulma-ui/src before being written into guidance, and every change was validated by at least one subsequent cold-start build. Full evidence: report.md on the archived branch.

What changed (18 files, +230/−88 vs the experiment's base)

  • scripts/gen-component-catalog.mjs (+ regenerated catalog): corrected the header's false "every component accepts helper props" claim (verified sub-part exceptions); documents the alias-grep technique + dist/types path (kills a ~6-call node_modules hunt per session); spine pointer.
  • bestax-layout-scaffold: Th/Td textAlign/textWeight idiom + Span/Paragraph/Strong wrappers (biggest single win — raw utility classNames 42→0, permanently); interactive-extras API block (Collapse/Tabs/Dropdown/Steps state props, Steps items shape, Tabs.Tab index, Tabs.Content containment, Reveal cascade scope); ≤10-line decorative-CSS budget with copy-paste few-shot + zero-CSS featured-ring recipe (scoped Theme bulmaVars); markerless-list + no-resets facts; has-navbar-fixed-top at the point of use.
  • bestax-theming: variant-flag/analogy ban with verified rosters (isLight = Button/LinkButton/Notification only; Tag sizes; Tags centering); types-wider-than-CSS warning with confirmed examples; corrected two factually false rows in themeable-components.md; font-loading note.
  • bestax-form: isFullWidth(Button) vs isFullwidth(Select/Table/File) casing trap; message/messageColor ownership; label a11y warning — the checklist previously asserted a label association the library doesn't make; now teaches id + labelProps={{htmlFor}}.
  • bestax-icons / bestax-custom-component: ariaLabel (Icon/Delete/Slider/Carousel only) vs standard aria-label; spine applies to compositions, usePrefixedClassNames optional for zero-CSS ones.
  • CLAUDE_MD() template: spine rule; compound-sub-part rule (+ Tabs.Tab index); PM-match-the-lockfile; sanctioned companion-class exception; absolute-path nudge. Rendered ~60 lines (was ~47).

Skill bundle stayed in budget (4,162 → 4,260 lines). Conflict with #356 on main resolved (both house-style bullets kept).

⚠️ Sequencing vs open PRs #357 and #355

Both touch the same files and will conflict with this PR textually (not semantically):

Recommend merging this PR first (larger surface), then rebasing #357/#355 onto it.

Verification

  • pnpm all green locally (build, typecheck, test+coverage, bundle:stats, lint, format:check, storybook build).
  • pnpm gen:catalog:check green — catalog regenerated on this branch against main's updated docs pages.
  • create-bestax's 197 unit tests pass; template render verified across flavor/icon variants.
  • Note for review: merged skill changes may warrant matching bulma-ui/src/skill-examples/ showcase updates per skills/CLAUDE.md (checked: no showcase uses the patterns corrected here, so none required now).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation
    • Expanded component “house style” and theming guidance, including Bulma utility/class rules, compound sub-part composition limits, and reusable component composition patterns.
    • Clarified form validation and accessibility details (label/id + labelProps, and full-width prop casing).
    • Improved accessibility documentation for icons (including default aria-label behavior) and updated examples to hide decorative icons from assistive tech.
    • Strengthened layout/scaffold rules (helper-props-only styling, fixed-top navbar setup, and responsive layout conventions).
    • Added stronger browser verification guidance using production builds and server-side render checks.
    • Refreshed component catalog guidance for consistent usage.

…AUDE.md, and catalog

Distilled from a 10-iteration cold-start eval loop (baseline 85/100 ->
revised mean 95.2, builder cost -43%). Every fact verified against
bulma-ui source before writing; every change validated by at least one
subsequent cold-start build. Full evidence on branch
chore/skill-improvement-loop (experiment/skill-loop/report.md).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Updated generated scaffold instructions and documentation for custom components, forms, icons, layouts, and theming. Changes clarify helper props, compound components, accessibility wiring, fixed-top navbars, CSS constraints, color behavior, and font loading.

Changes

Guidance documentation

Layer / File(s) Summary
Generated and custom-component guidance
create-bestax/src/constants.ts, scripts/gen-component-catalog.mjs, skills/bestax-custom-component/*
Expanded guidance for helper props, compound sub-parts, reusable composition, absolute reference paths, accessibility examples, and headless verification.
Form and accessibility guidance
skills/bestax-form/*, skills/bestax-icons/SKILL.md
Clarified validation prop ownership, label association, prop casing, Select options, and component-specific ARIA props.
Layout scaffold guidance
skills/bestax-layout-scaffold/*
Documented fixed-top navbar setup, helper-prop styling, CSS limits, sizing rules, card footer composition, and scaffold checklist requirements.
Theming and color guidance
skills/bestax-theming/*
Expanded dark-mode, color-union, helper-prop, CSS-variable, and font-loading documentation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: needs-human-review

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change: updated agent-validated guidance and CLAUDE.md/catalog docs for create-bestax.
Description check ✅ Passed The description is detailed and covers the change summary, affected packages, verification, and related merge context, though it doesn't follow the template exactly.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/skill-loop-guidance

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

skills/bestax-custom-component/examples/stat-card.tsx

Parsing error: "parserOptions.project" has been provided for @typescript-eslint/parser.
The file was not found in any of the provided project(s): skills/bestax-custom-component/examples/stat-card.tsx


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://51e47d8b.bestax.pages.dev

@allxsmith

Copy link
Copy Markdown
Owner Author

@coderabbitai Full Review

@coderabbitai

coderabbitai Bot commented Jul 24, 2026 •

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 12 minutes.

Comment thread skills/bestax-layout-scaffold/SKILL.md Outdated

@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 — 1 blocking · 1 advisory

# Severity Area Finding Location
1 🟡 Minor Correctness Featured-card recipe scopes --bulma-box-shadow, but .card reads --bulma-card-shadow — no ring renders on a <Card> skills/bestax-layout-scaffold/SKILL.md:88
2 🔵 Advisory Robustness The greyscale "typechecks but ships no CSS" note and the ~800 KB/82 KB CSS figure aren't source-verifiable in this pass; both are defensively worded (grep-first / measure-first), so low risk skills/bestax-theming/references/themeable-components.md:11, skills/bestax-layout-scaffold/SKILL.md:131

Overall: This is a documentation/skills PR whose entire value rests on factual accuracy against bulma-ui/src, so I chased the concrete claims to source rather than reading the prose. The vast majority check out exactly — isFullWidth(Button)/isFullwidth(Select/File/Table)/fullwidth(Tabs) casing, isLight roster (Button/LinkButton/Notification only), Tag size union (no small), ariaLabel roster (Icon/Delete/Slider/Carousel only), the compound sub-part helper-prop split (Card/Modal/Message take none; Menu/Table/Hero do), Th/Td dropping the text-color helper, Tabs.Tab/Tabs.Content.Item index requirement, Steps.Step/items shape, Card auto-wrapping footer items, Box color->has-text-*, the label-prop-doesn't-wire-htmlFor a11y correction, Column numeric-vs-string sizes, gap number|string, the flex literals, Reveal cascade direct-children, and the --bulma-scheme-main-bis/-ter vars all match source. The one real defect is the featured-card ring recipe targeting the wrong shadow variable. The human should focus first on that one-line fix (posted inline).

Residual risk: the failure class is "factually wrong guidance."

  • I enumerated and verified the highest-density claims across every changed skill file against bulma-ui/src and the shipped Bulma CSS; all held except finding #1.
  • The two advisory items (greyscale-has-no-CSS caveat, CSS byte size) are the only claims I couldn't confirm from source here — both are written defensively ("grep the shipped CSS before relying"; "measure first"), so a stale value degrades to a hint, not a broken recipe.
  • No runtime/build risk: 202 create-bestax tests pass and the working tree stays clean under gen:catalog (catalog in sync).

🏄 Duuude, this one's a clean set — someone actually paddled out and checked every wave against the reef instead of trusting the map. One little board-ding: they told folks to wax a card with the box shadow knob, so that highlight ring just won't catch. Patch that single line and it's all-time, send it. 🤙

… --bulma-shadow

The recipe scoped --bulma-box-shadow (and named --bulma-card-shadow as
equally reachable), but .box/.card re-declare those on their own selector,
so an ancestor Theme never wins — verified in a browser: the ring did not
render for either component. Overriding the upstream --bulma-shadow token
does work for both. This also restores consistency with the rule already
stated in bestax-theming/references/css-variables.md.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@allxsmith

Copy link
Copy Markdown
Owner Author

Thanks — finding #1 is correct, and verification showed it's worse than reported: the recipe was inert for Box too, not just Card. Fixed in 71c03bc.

What I did: rather than reason from the cascade, I served the shipped bulma@1.0.4 CSS and read getComputedStyle().boxShadow for each combination:

Scoped var (on an ancestor) Element Rendered ring?
--bulma-box-shadow .box ❌ default shadow
--bulma-card-shadow .card ❌ default shadow
--bulma-box-shadow .card (the shipped recipe) ❌ default shadow
--bulma-shadow .card ✅ rgb(0,209,178) 0 0 0 2px
--bulma-shadow .box ✅ ring renders

Cause: .box/.card re-declare their own shadow var on their own selector (bulma.css:3306, :6555), so an ancestor's value is only inherited and always loses. The same holds for --bulma-box-radius; --bulma-card-radius is a literal (0.75rem) with no ancestor route at all. What works from an ancestor is the upstream token the local declaration references — --bulma-shadow.

This also restores internal consistency: bestax-theming/references/css-variables.md:106 already stated this rule correctly ("a value set on a wrapping ancestor — including Theme's bulmaVars — … will NOT take effect"). The layout recipe contradicted its own sibling skill.

Advisory #2: both claims were source-verified when written and I re-checked the greyscale one just now — the shipped CSS has no .progress.is-grey rule while grey is in validColors (that's the basis for #367). The ~800 KB/82 KB figure mirrors bestax-optimize's own numbers and is worded measure-first, so I've left it.


Meta-finding worth recording (it says something about the experiment, not just this line): runs i08, i09 and i10 all adopted this recipe — as --bulma-card-shadow — so all three shipped a featured card whose ring never rendered, and three separate graders scored it as a correct "sanctioned zero-CSS pattern." Nothing in the loop could see a rendered page, so a visually-inert recipe was unfalsifiable. That's the sharpest possible evidence for the browser-tool recommendation in #363/#366; I've added the correction and that lesson to report.md on the harness PR (26180e9). It also means the improver's original "verified by live render" claim only confirmed the wrapper carried the var, not that anything changed — a verification-depth trap worth remembering for future loops.

@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://783de1af.bestax.pages.dev

@coderabbitai coderabbitai 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.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@create-bestax/src/constants.ts`:
- Around line 149-151: Update the component-spine recipe to require
useBulmaClasses, merge its returned bulmaHelperClasses into the component
className, and spread the returned DOM-safe rest props. Apply this consistently
at create-bestax/src/constants.ts lines 149-151,
scripts/gen-component-catalog.mjs lines 253-255, and
skills/bestax-custom-component/SKILL.md lines 61-65, aligning the prose and
generated catalog with the working example in the skill.

In `@skills/bestax-custom-component/examples/stat-card.tsx`:
- Around line 67-68: Update the decorative icon props in the stat-card component
to remove ariaLabel and set aria-hidden="true", leaving the visible label and
value unchanged.

In `@skills/bestax-form/SKILL.md`:
- Around line 206-208: Update the completion checklist to accept the documented
production-build plus Node renderToString fallback when no browser is available.
Require verification of emitted HTML classes/states in that path, and explicitly
mark the browser visual inspection as outstanding rather than requiring it
unconditionally.
- Around line 14-16: Update the Field API description in the validation guidance
to state that FieldProps includes color, while message and messageColor are not
supported by Field. Keep the recommendation to apply validation styling through
the convenience input components’ own props and preserve the existing label
distinction.

In `@skills/bestax-layout-scaffold/SKILL.md`:
- Around line 69-86: Update the decorative CSS guidance around the hero-wash and
section-alt examples so the example stays within the stated 10-line limit,
including comments. Prefer compacting the sample CSS while preserving its
existing Bulma-derived values and visual behavior; otherwise revise the limit to
match the example consistently.

In `@skills/bestax-theming/references/themeable-components.md`:
- Around line 21-24: Clarify the Box entry in the color-props documentation:
either remove Box from the helper-collision list so it matches the
Button/Card/Hero/Section contrast, or explicitly state that Box’s color prop
maps to has-text-* and does not apply a filled .box.is-* modifier. Keep the
documented behavior for the other components unchanged.
- Around line 21-24: Update the theming guidance to remove Input from the Span
wrapper workaround, since InputBase renders a native input. Keep wrapper
guidance only for Tag, Td, and Th, and revise the Input table and surrounding
prose to document its supported text-styling options instead.

In `@skills/bestax-theming/SKILL.md`:
- Around line 56-60: Remove LinkButton from the isLight support claim in
skills/bestax-theming/SKILL.md and update the corresponding entry in
skills/bestax-theming/references/themeable-components.md at line 56; document
isLight only for Button and Notification, while preserving the existing Tag and
other variant guidance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 324e74cd-9c1c-4d98-a01e-6b7d73cb81a7

📥 Commits

Reviewing files that changed from the base of the PR and between 71fb96f and 71c03bc.

📒 Files selected for processing (18)
  • create-bestax/src/constants.ts
  • scripts/gen-component-catalog.mjs
  • skills/bestax-custom-component/SKILL.md
  • skills/bestax-custom-component/examples/stat-card.tsx
  • skills/bestax-custom-component/references/api.md
  • skills/bestax-custom-component/references/component-catalog.md
  • skills/bestax-form/SKILL.md
  • skills/bestax-form/references/api.md
  • skills/bestax-icons/SKILL.md
  • skills/bestax-layout-scaffold/SKILL.md
  • skills/bestax-layout-scaffold/examples/app-shell.tsx
  • skills/bestax-layout-scaffold/examples/card-grid.tsx
  • skills/bestax-layout-scaffold/references/archetypes.md
  • skills/bestax-layout-scaffold/references/layout-components.md
  • skills/bestax-theming/SKILL.md
  • skills/bestax-theming/examples/theme-config.tsx
  • skills/bestax-theming/references/css-variables.md
  • skills/bestax-theming/references/themeable-components.md

Comment thread create-bestax/src/constants.ts Outdated
Comment thread skills/bestax-custom-component/examples/stat-card.tsx Outdated
Comment thread skills/bestax-form/SKILL.md Outdated
Comment thread skills/bestax-form/SKILL.md
Comment thread skills/bestax-layout-scaffold/SKILL.md
Comment thread skills/bestax-theming/references/themeable-components.md Outdated
Comment thread skills/bestax-theming/SKILL.md
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

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

Comment thread skills/bestax-theming/SKILL.md Outdated
…r guidance

Addresses the CodeRabbit review on #365. Each finding re-verified against source:

- Component spine: the shorthand said "spread `...rest`" without naming
  `useBulmaClasses`, leaving `rest` undefined for a reader — spreading the raw
  props instead leaks helper props onto the DOM and emits none of their classes.
  Now names the hook at all three sites (scaffold CLAUDE.md, catalog generator,
  SKILL.md prose) to match the working template.
- `Field` does accept `color` (Field.tsx:42); only `message`/`messageColor` are
  absent. Corrected the claim.
- `LinkButtonProps` explicitly omits `isLight` (LinkButton.tsx:13), so drop
  LinkButton from the isLight claim; note it on the reference row too.
- Box/Card/Section emit no `is-<color>` rule — their `color` flows into
  `useBulmaClasses` as the text helper, so lumping them with Button/Hero as
  "modifier displaced the helper" was wrong. (CodeRabbit flagged Box; Card and
  Section have the same shape.)
- `Input` renders a native `<input>`, so the `<Span textColor>` wrapper cannot
  color its value. Bulma declares `--bulma-input-*` on `.input` itself, so an
  ancestor `<Theme>` can't reach them either — point at the upstream
  `--bulma-text-strong-l`, same rule as the shadow fix in 71c03bc.
- stat-card's icon is decorative next to a visible label; `ariaLabel` made it
  announce twice. Now `aria-hidden="true"`, matching bestax-icons' own guidance.

Not changed: the decorative-CSS example is exactly 10 lines, within the stated
"≤10 lines" budget — the reported 11-line overrun does not reproduce.

@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 — 1 blocking · 1 advisory

# Severity Area Finding Location
1 🟡 Minor Correctness isLight claimed on LinkButton, which explicitly Omits it — self-contradicts the PR's own reference table skills/bestax-theming/SKILL.md:57
2 🔵 Advisory Correctness "Sub-parts take only className + HTML attributes" is imprecise for Tabs.Tab (also takes index, disabled, icon, iconLibrary, …) — the load-bearing "no Bulma helper props" claim is correct references/component-catalog.md, custom-component/SKILL.md, custom-component/references/api.md

Overall: This is a large but low-risk guidance PR — every change is skill/docs Markdown plus the create-bestax CLAUDE.md template and the catalog generator. I chased the substantive API claims to source (bulma-ui/src) rather than reading the prose in isolation: verified full-width casing (isFullWidth/isFullwidth/fullwidth), ariaLabel roster (Icon/Delete/Slider/Carousel), Tag size union + no isLight, compound-sub-part helper-prop split (Card/Modal/Tabs/Message = none; Table/Menu/Hero/most-Navbar = yes), Tabs.Tab/Content.Item required index, Tabs.Content context containment, Steps.Step/items shape, Collapse/Dropdown/Reveal state props, Box color→has-text-*, grey/bis/ter typecheck-but-no-CSS, the --bulma-shadow featured-ring upstream-token route, and Skeleton skipping helper props — all accurate. The card-grid footer={product.price} change is also correct (Card wraps each footer item in .card-footer-item itself, so the old manual span double-wrapped). The single defect is the LinkButton isLight line, which contradicts the PR's own themeable-components.md and would lead an agent to emit a TS error. create-bestax tests: 202/202 pass. Human should focus on finding #1.

Residual risk: the failure class here is false API claims in shipped agent guidance.

  • I re-derived each load-bearing claim from bulma-ui/src interfaces / compiled Bulma CSS via three independent verification passes; only the LinkButton isLight row failed. The nearby reference table and the migrate skill already state it correctly, so the blast radius is one line.
  • Minor phrasing imprecision (advisory #2) is the "only className + HTML attributes" shorthand — technically Tabs.Tab also takes index/icon props, but the PR itself calls out the index requirement adjacent to every occurrence, so no agent is misled on the practical point.
  • I did not exhaustively re-verify every cell of the 30-row verbatim color/size union table (rows unchanged from the prior version were spot-checked, not line-by-line); a stray typo there would be invisible to CI. Flagged as the thinnest coverage, not a found defect.

🏄 Mellow set of well-formed waves, dude — 18 files of guidance and nearly every claim held up against the source break. One rogue LinkButton isLight ripple to smooth out and it's a clean ride to shore. 🌊

…ore than className

Addresses deep-review advisory #2 on #365. `TabProps` (Tabs.tsx:282-294) takes
`index`, `disabled`, `icon`, `iconLibrary`, `iconVariant`, `iconSize`, and
`iconFeatures` on top of `className` + HTML attributes, so "take only `className`
+ HTML attributes" was imprecise at all four sites. The load-bearing claim — no
Bulma helper props on `Card.*`/`Modal.*`/`Tabs.*`/`Message.*` — is unchanged.

Also surfaces `Tabs.Tab`'s built-in icon props, which the old wording implied did
not exist and would have led an agent to nest an `<Icon>` there.
@allxsmith

Copy link
Copy Markdown
Owner Author

Addressed both review rounds. Two commits, pnpm all green locally before each.

Deep review (819f1f4 + 2fa7820)

# Finding Outcome
1 isLight claimed on LinkButton Fixed — LinkButtonProps omits it (LinkButton.tsx:11-13). Also added the omission to the reference table row, so it is stated rather than merely absent.
2 "sub-parts take only className + HTML attributes" imprecise for Tabs.Tab Fixed at all four sites. TabProps (Tabs.tsx:282-294) also takes index, disabled, icon, iconLibrary, iconVariant, iconSize, iconFeatures. The load-bearing claim — no Bulma helper props on Card.*/Modal.*/Tabs.*/Message.* — is unchanged. The old wording also implied Tabs.Tab had no icon support, which would push an agent to nest an <Icon> there; that is now called out.

CodeRabbit — 7 of 8 fixed, 1 skipped. Details in the threads; two are worth surfacing here because verification widened them:

  • Box color — CodeRabbit flagged Box, but Card and Section have the identical shape. Only Button and Hero emit is-${color} at all; Box/Card/Section let color fall through into useBulmaClasses as the text helper. The "modifier displaced the helper" framing was wrong for all three.
  • Input / <Span textColor> — correct, and the obvious next suggestion fails too: --bulma-input-* is declared on .control, .input, .textarea, .select themselves (bulma.css:5438-5473), so scoping it from an ancestor <Theme> never wins. Exactly the trap behind the earlier card-shadow finding. The doc now points at the upstream --bulma-text-strong-l, which is ancestor-reachable.
  • Component spine — the shorthand said "spread ...rest" without naming useBulmaClasses, leaving rest undefined for a reader; spreading the raw props instead leaks helper props onto the DOM and emits none of their classes. Fixed at all three sites.

Skipped: the decorative-CSS budget comment. The example is exactly 10 lines, which satisfies the stated "≤10 lines" limit — the reported 11-line overrun does not reproduce. Thread left open rather than resolved.

Leaving the merge to @allxsmith.

Two conflicts, both "each side added a different bullet in the same spot":

- create-bestax/src/constants.ts — main added the controlled-Burger Navbar
  bullet (#355); this branch added the component-spine bullet. Kept both.
- skills/bestax-layout-scaffold/SKILL.md — this branch's block is the enriched
  superset, so took it wholesale and swapped its shorter fixed-top Navbar bullet
  for main's fuller one, which also covers Navbar.Burger/Navbar.Menu being
  controlled. No guidance from either side dropped.

pnpm all green on the merged tree (20/20 turbo tasks, 4065 tests).
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

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

Picks up #357 (inline-style -> helper-prop mapping table). Four conflict hunks,
all "both sides rewrote the same never-inline-style rule":

- create-bestax/src/constants.ts — took main's table-based treatment wholesale
  (strictly richer than this branch's prose), then re-added the two things it
  lacks: the Span/Paragraph/Strong wrapper rule with the <html>/<body>
  companion-class exception, and this PR's compound-sub-part bullet.
- bestax-layout-scaffold/SKILL.md (x2) — pointed the style bullet at main's new
  mapping table instead of repeating the spacing/color examples it now covers,
  and kept this branch's wrapper + Th/Td specifics and the decorative-CSS budget.
  Same merge in the checklist item.
- bestax-custom-component/SKILL.md — kept main's fuller no-inline-style checklist
  item plus this branch's "every reusable component gets the spine".

No guidance dropped from either side; the decorative-CSS example is still 10
lines, within its stated budget. pnpm all green (20/20 tasks, 4069 tests).
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://31e33734.bestax.pages.dev

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
skills/bestax-custom-component/SKILL.md (2)

130-130: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the display: none mapping in both skills.

visibility="hidden" preserves layout space and is not equivalent to display: none.

  • skills/bestax-custom-component/SKILL.md#L130-L130: remove visibility="hidden" as a direct replacement.
  • skills/bestax-layout-scaffold/SKILL.md#L127-L127: document a true display helper or named CSS class for actual removal from layout.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/bestax-custom-component/SKILL.md` at line 130, Correct the
display-none guidance in both skills: in skills/bestax-custom-component/SKILL.md
lines 130-130, remove visibility="hidden" as a direct replacement while
retaining valid responsive display props; in
skills/bestax-layout-scaffold/SKILL.md lines 127-127, document a true display
helper or named CSS class that removes the element from layout.

113-130: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Keep always-loaded SKILL.md files concise.

Both changes embed detailed helper-prop reference tables that belong under references/, not in the always-loaded skill instructions.

  • skills/bestax-custom-component/SKILL.md#L113-L130: move the styling ladder and mapping table to a reference file.
  • skills/bestax-layout-scaffold/SKILL.md#L107-L132: move the duplicate mapping table to a reference file and link it from the skill.
    As per coding guidelines, keep SKILL.md short and put detailed guidance in references/.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@skills/bestax-custom-component/SKILL.md` around lines 113 - 130, Keep
skills/bestax-custom-component/SKILL.md lines 113-130 concise by moving the
styling ladder and helper-prop mapping table into a suitable references file,
then link to it from SKILL.md. Apply the same change to the duplicate mapping
table in skills/bestax-layout-scaffold/SKILL.md lines 107-132, ensuring both
skill files retain concise guidance and reference the shared detailed
documentation.

Sources: Coding guidelines, Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@skills/bestax-custom-component/SKILL.md`:
- Line 130: Correct the display-none guidance in both skills: in
skills/bestax-custom-component/SKILL.md lines 130-130, remove
visibility="hidden" as a direct replacement while retaining valid responsive
display props; in skills/bestax-layout-scaffold/SKILL.md lines 127-127, document
a true display helper or named CSS class that removes the element from layout.
- Around line 113-130: Keep skills/bestax-custom-component/SKILL.md lines
113-130 concise by moving the styling ladder and helper-prop mapping table into
a suitable references file, then link to it from SKILL.md. Apply the same change
to the duplicate mapping table in skills/bestax-layout-scaffold/SKILL.md lines
107-132, ensuring both skill files retain concise guidance and reference the
shared detailed documentation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ac075d67-c0f0-43a9-a7b0-232817f1a0d5

📥 Commits

Reviewing files that changed from the base of the PR and between 71c03bc and ec534bf.

📒 Files selected for processing (11)
  • create-bestax/src/constants.ts
  • scripts/gen-component-catalog.mjs
  • skills/bestax-custom-component/SKILL.md
  • skills/bestax-custom-component/examples/stat-card.tsx
  • skills/bestax-custom-component/references/api.md
  • skills/bestax-custom-component/references/component-catalog.md
  • skills/bestax-form/SKILL.md
  • skills/bestax-layout-scaffold/SKILL.md
  • skills/bestax-layout-scaffold/references/archetypes.md
  • skills/bestax-theming/SKILL.md
  • skills/bestax-theming/references/themeable-components.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • skills/bestax-custom-component/references/api.md
  • skills/bestax-custom-component/references/component-catalog.md
  • create-bestax/src/constants.ts
  • skills/bestax-theming/references/themeable-components.md

@allxsmith
allxsmith merged commit 6fd06ae into main Jul 29, 2026
32 checks passed
@allxsmith
allxsmith deleted the feat/skill-loop-guidance branch July 29, 2026 00:28
github-actions Bot pushed a commit that referenced this pull request Jul 29, 2026
# [3.8.0](https://github.com/allxsmith/bestax/compare/create-bestax@3.7.1...create-bestax@3.8.0) (2026-07-29)

### Features

* **create-bestax:** agent-validated guidance for skills, scaffold CLAUDE.md, and catalog ([#365](#365)) ([6fd06ae](6fd06ae)), closes [#2](#2)
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.8.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

allxsmith added a commit that referenced this pull request Jul 31, 2026
`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

The checked-in `component-catalog.md` still had #365's text, so
`gen:catalog:check` regenerated it shorter and failed `git diff --exit-code` —
this is why Build and Test is red on this PR.

Restores the generator to main's version. The catalog now regenerates
byte-identical to the committed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ
allxsmith added a commit that referenced this pull request Jul 31, 2026
`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ
allxsmith added a commit that referenced this pull request Jul 31, 2026
`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ
allxsmith added a commit that referenced this pull request Jul 31, 2026
`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ
allxsmith added a commit that referenced this pull request Jul 31, 2026
…420)

* docs: add the API page generator, its CI gates and review aids

Lands the tooling for generating the derivable parts of docs/docs/api, with no
page generated yet: MANAGED_CATEGORIES and ORDERED_CATEGORIES both start empty
and grow one category per follow-up, so the 87 page diffs stay reviewable
instead of arriving as one 199-file change.

Four regions per page, delimited by `<!-- bestax:generated <id> -->` markers —
`overview` (the component's TSDoc summary), `import` (the public barrel),
`props` (the `<X>Props` interfaces via the TypeScript compiler API) and
`cssvars` (a new section parsed from the SCSS). Everything outside a marker pair
is hand-written and preserved byte-for-byte; deleting a pair opts that region
out, and `docs-section-order` makes that visible rather than silent.

Three choices keep the output reading as hand-written rather than as typedoc:
own members only, with the one catch-all `...` row the pages already wrote by
hand; types from AST source text, never `checker.typeToString`, which expands
`(typeof validColors)[number]` into 19 literals; and wide colour unions rendered
as a link to the existing Valid values page.

CI gates live in check-conformance.mjs, which already runs in CI, rather than a
new workflow step: `docs-generated` recomputes each managed page in memory and
diffs, `docs-section-order` holds the order and marker presence. Both were
verified by tampering.

Also closes two gate gaps this tooling exposed. `pnpm all` ran
`turbo run format:check`, which only runs the per-package scripts — none cover
`scripts/`, `docs/scripts/` or any `.md`; CI runs the root script, which does, so
CI could be red while `pnpm all` was green. And nothing linted `scripts/` at
all. `all` now calls the root format check and `lint` also runs eslint over
`scripts` and `docs/scripts`; verified by planting an unused variable.

Two review aids ship alongside, both one-shot and both used to validate the
migration that follows: `check-docs-parity.mjs` fails on any prop, default,
description word, code span, URL, live example or prose line that a generated
page drops relative to its hand-written self, and `check-docs-wording.mjs`
buckets every previously-documented prop by how its description changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

The checked-in `component-catalog.md` still had #365's text, so
`gen:catalog:check` regenerated it shorter and failed `git diff --exit-code` —
this is why Build and Test is red on this PR.

Restores the generator to main's version. The catalog now regenerates
byte-identical to the committed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct four prop-table defects in the API page extractor

Found by comparing every generated page against its origin/main original in a
browser, and against the interfaces the tables claim to describe.

- Bare alias names where the hand-written tables had real types. A one-member
  "union" (`type CellSpanValue = number`) was never a union node, and a union
  naming another alias (`BulmaFixedGridColsProp = BulmaFixedGridCols |
  'auto'`) failed the all-members-simple test. Both fell through to the bare
  name with no `**Types:**` footnote, so cell.md's `colSpan`/`rowSpan` said
  `CellSpanValue` where main said `number`, and grid.md's `fixedCols` was
  opaque beside five siblings expanded to `0 | … | 12` in the same table.
  Member aliases now resolve to a fixpoint.

- `children` synthesized for components that never render it. The row was
  emitted for any interface with a DOM base, but inheriting `children` is not
  rendering it: Divider spreads onto `<hr>`, so the row documented the one
  thing React throws on ("hr is a void element tag and must neither have
  `children`…"), and Icon always supplies its own JSX children, so anything
  passed is silently dropped. Emitted only where the implementation names
  `children`; the catch-all row still covers pass-through cases.

- Sub-components dropped when their props are an inline DOM type rather than a
  named `*Props` interface. `Navbar.Divider` and `Pagination.Ellipsis` were
  omitted from the generated Subcomponents lists entirely — which is why both
  pages still carried a hand-written duplicate list. They are now listed (with
  no table, since they add no props of their own).

- `never` and `false | true` merged from the branches of a discriminated
  union. slider.md rendered `minDistance` as `never | number` and `range` as
  `false | true`; the forbidding branch's `never` is noise standing where the
  reader needs a type.

Verified: generator idempotent, all ten conformance checks green,
check-docs-parity still reports 0 prop/default/description losses across all
87 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): fail the alias fixpoint instead of degrading to bare names

The deep review on #421 flagged that the alias-resolution fixpoint degrades
silently: if a chain needs more than the round bound, the survivors fall back
to a bare name plus a `**Types:**` footnote rather than the expansion, and
nothing says so.

Running out of rounds is a different condition from settling. An alias that
genuinely cannot expand stops making progress and the loop exits clean — that
path is unchanged. But if the bound cuts the loop off while it is still
resolving, the survivors render as bare identifiers, which is the exact
regression this generator exists to prevent. That case now throws, naming the
unresolved aliases.

The bound is a named constant (`ALIAS_FIXPOINT_ROUNDS`) so the error can point
at it. Chains in this repo settle in two rounds; the bound only exists to stop
a mutually-recursive pair spinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): make the marker stripper fail instead of no-opping silently

The deep review asked whether this step fails loudly when it matches nothing.
It did not — it logged "removed 0 marker(s) from 0 file(s)" and exited 0.

That is the one failure this step exists to prevent. Its own header records
why it is a build step and not a plugin: the first attempt was a `postBuild`
hook, `postBuild` runs under `Promise.all`, it raced ahead of
docusaurus-plugin-llms and silently found nothing to strip. A quiet no-op here
ships ~600 markers into llms-full.txt and every per-page `.md` twin with a
green build.

Stripping nothing is only correct when there was nothing to strip, so the
check compares against the SOURCE pages: zero stripped AND zero markers in
docs/docs/api is the legitimate "no managed categories yet" state (which is
this branch, with MANAGED_CATEGORIES empty). Zero stripped while the source
carries markers means the built markdown moved, the marker format changed, or
the ordering regressed — that now exits 1 and says which.

Also answers the other half of the question: the glob only ever reaches `.md`
files plus llms.txt / llms-full.txt, so it cannot touch built HTML.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): close the theme.md fence my earlier edit left open

CI caught this and it is my regression, not a pre-existing one: main's
`theme.md` passes `prettier --check` at its real path.

Removing the stray four-backtick fence pair around "Theme with Styling" took
out a fence that was load-bearing. The outer ```` closed the inner ```tsx
block (a longer fence closes a shorter one), so deleting the pair left the
StyledTheme example unterminated — it swallowed the "### Nested Themes"
heading and the block after it, and `prettier --check` failed on the file.

Closing the ```tsx block explicitly gives the structure the section was always
meant to have: the heading renders as a heading and each example is its own
closed block. `pnpm run format:check` is green across the repo.

Worth recording how this got through: I ran typecheck, lint, tests and the docs
build locally but not the ROOT `format:check` — which is exactly the "pnpm all
green, CI red" gap this stack exists to close, and the gap only closed for
`lint` two commits ago. The root format check is in my gate from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* docs: restore the flatten-llms-tabs description this stack replaced

Fourth instance of the same silent-deletion pattern, found by the deep review
on #420. `docs/CLAUDE.md`'s "LLM docs pipeline" section documented
`scripts/flatten-llms-tabs.mjs`; this stack swapped in the
`strip-generated-markers.mjs` paragraph in its place rather than alongside it.

The script is not dead. It is still the FIRST step in docs' build chain
(`docusaurus build && node scripts/flatten-llms-tabs.mjs && node
scripts/strip-generated-markers.mjs`), still covered by
`flatten-llms-tabs.test.mjs`, and does something unrelated to what displaced
it — flattening `<PackageManagerTabs>`/`<Tabs>` MDX so the JSX does not land
verbatim in `llms.txt`/`llms-full.txt`. Nothing else in the repo documented it,
so a reader had no way to learn it exists or why it cannot be a Docusaurus
plugin.

Both steps are now described in the order the chain runs them.

Same mechanism as the `gen-component-catalog.mjs` preamble, the manifest
downgrades and the deleted `style-mapping-sync` check: a replacement where an
addition was meant, with no conflict to force a second look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
allxsmith added a commit that referenced this pull request Jul 31, 2026
…#421)

* docs: add the API page generator, its CI gates and review aids

Lands the tooling for generating the derivable parts of docs/docs/api, with no
page generated yet: MANAGED_CATEGORIES and ORDERED_CATEGORIES both start empty
and grow one category per follow-up, so the 87 page diffs stay reviewable
instead of arriving as one 199-file change.

Four regions per page, delimited by `<!-- bestax:generated <id> -->` markers —
`overview` (the component's TSDoc summary), `import` (the public barrel),
`props` (the `<X>Props` interfaces via the TypeScript compiler API) and
`cssvars` (a new section parsed from the SCSS). Everything outside a marker pair
is hand-written and preserved byte-for-byte; deleting a pair opts that region
out, and `docs-section-order` makes that visible rather than silent.

Three choices keep the output reading as hand-written rather than as typedoc:
own members only, with the one catch-all `...` row the pages already wrote by
hand; types from AST source text, never `checker.typeToString`, which expands
`(typeof validColors)[number]` into 19 literals; and wide colour unions rendered
as a link to the existing Valid values page.

CI gates live in check-conformance.mjs, which already runs in CI, rather than a
new workflow step: `docs-generated` recomputes each managed page in memory and
diffs, `docs-section-order` holds the order and marker presence. Both were
verified by tampering.

Also closes two gate gaps this tooling exposed. `pnpm all` ran
`turbo run format:check`, which only runs the per-package scripts — none cover
`scripts/`, `docs/scripts/` or any `.md`; CI runs the root script, which does, so
CI could be red while `pnpm all` was green. And nothing linted `scripts/` at
all. `all` now calls the root format check and `lint` also runs eslint over
`scripts` and `docs/scripts`; verified by planting an unused variable.

Two review aids ship alongside, both one-shot and both used to validate the
migration that follows: `check-docs-parity.mjs` fails on any prop, default,
description word, code span, URL, live example or prose line that a generated
page drops relative to its hand-written self, and `check-docs-wording.mjs`
buckets every previously-documented prop by how its description changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the layout, columns and grid API pages

First categories through the generator, plus `helpers/` reordered but not
generated — four of its six pages document hooks with `## API` and no `## Props`
at all, and `config.md`/`theme.md` keep their hand-written tables (theme.md's
Props section is a ~350-line prose sub-API, not a table).

`@property` blocks move onto the interface members as inline TSDoc, so a
description is verifiable by position and reaches users' editors and the shipped
.d.ts. Descriptions were seeded FROM the docs pages, not the other way round:
the hand-written tables are consistently the richer text, and the page stays
authoritative wherever the two disagree.

Component TSDoc summaries were likewise seeded from the pages — the existing ones
("Bulma Hero component root.", "Container component for Bulma.") were worse than
the prose they would have replaced. That text ships in the .d.ts, so read the
`component-catalog.md` diff: it is the canary for this step.

Verified: check-docs-parity reports 0 losses across all 87 pages; the generator
is idempotent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

The checked-in `component-catalog.md` still had #365's text, so
`gen:catalog:check` regenerated it shorter and failed `git diff --exit-code` —
this is why Build and Test is red on this PR.

Restores the generator to main's version. The catalog now regenerates
byte-identical to the committed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct four prop-table defects in the API page extractor

Found by comparing every generated page against its origin/main original in a
browser, and against the interfaces the tables claim to describe.

- Bare alias names where the hand-written tables had real types. A one-member
  "union" (`type CellSpanValue = number`) was never a union node, and a union
  naming another alias (`BulmaFixedGridColsProp = BulmaFixedGridCols |
  'auto'`) failed the all-members-simple test. Both fell through to the bare
  name with no `**Types:**` footnote, so cell.md's `colSpan`/`rowSpan` said
  `CellSpanValue` where main said `number`, and grid.md's `fixedCols` was
  opaque beside five siblings expanded to `0 | … | 12` in the same table.
  Member aliases now resolve to a fixpoint.

- `children` synthesized for components that never render it. The row was
  emitted for any interface with a DOM base, but inheriting `children` is not
  rendering it: Divider spreads onto `<hr>`, so the row documented the one
  thing React throws on ("hr is a void element tag and must neither have
  `children`…"), and Icon always supplies its own JSX children, so anything
  passed is silently dropped. Emitted only where the implementation names
  `children`; the catch-all row still covers pass-through cases.

- Sub-components dropped when their props are an inline DOM type rather than a
  named `*Props` interface. `Navbar.Divider` and `Pagination.Ellipsis` were
  omitted from the generated Subcomponents lists entirely — which is why both
  pages still carried a hand-written duplicate list. They are now listed (with
  no table, since they add no props of their own).

- `never` and `false | true` merged from the branches of a discriminated
  union. slider.md rendered `minDistance` as `never | number` and `range` as
  `false | true`; the forbidding branch's `never` is noise standing where the
  reader needs a type.

Verified: generator idempotent, all ten conformance checks green,
check-docs-parity still reports 0 prop/default/description losses across all
87 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): regenerate the grid pages with the corrected type cells

Picks up the extractor fix from the base branch. `cell.md`'s `colSpan` and
`rowSpan` go back to `number` — they had regressed to a bare `CellSpanValue`,
which aliases exactly `number` and so told the reader strictly less. `grid.md`'s
`fixedCols` expands to `0 | … | 12 | 'auto'` instead of a bare
`BulmaFixedGridColsProp`; it was the only opaque cell in a table whose five
sibling props were already fully expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): fail the alias fixpoint instead of degrading to bare names

The deep review on #421 flagged that the alias-resolution fixpoint degrades
silently: if a chain needs more than the round bound, the survivors fall back
to a bare name plus a `**Types:**` footnote rather than the expansion, and
nothing says so.

Running out of rounds is a different condition from settling. An alias that
genuinely cannot expand stops making progress and the loop exits clean — that
path is unchanged. But if the bound cuts the loop off while it is still
resolving, the survivors render as bare identifiers, which is the exact
regression this generator exists to prevent. That case now throws, naming the
unresolved aliases.

The bound is a named constant (`ALIAS_FIXPOINT_ROUNDS`) so the error can point
at it. Chains in this repo settle in two rounds; the bound only exists to stop
a mutually-recursive pair spinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): make the marker stripper fail instead of no-opping silently

The deep review asked whether this step fails loudly when it matches nothing.
It did not — it logged "removed 0 marker(s) from 0 file(s)" and exited 0.

That is the one failure this step exists to prevent. Its own header records
why it is a build step and not a plugin: the first attempt was a `postBuild`
hook, `postBuild` runs under `Promise.all`, it raced ahead of
docusaurus-plugin-llms and silently found nothing to strip. A quiet no-op here
ships ~600 markers into llms-full.txt and every per-page `.md` twin with a
green build.

Stripping nothing is only correct when there was nothing to strip, so the
check compares against the SOURCE pages: zero stripped AND zero markers in
docs/docs/api is the legitimate "no managed categories yet" state (which is
this branch, with MANAGED_CATEGORIES empty). Zero stripped while the source
carries markers means the built markdown moved, the marker format changed, or
the ordering regressed — that now exits 1 and says which.

Also answers the other half of the question: the glob only ever reaches `.md`
files plus llms.txt / llms-full.txt, so it cannot touch built HTML.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(bulma-ui): correct the inverted Level.isMobile description

Bulma's `.level.is-mobile` sets `display: flex; flex-direction: row`
(`bulma/sass/layout/level.scss:27-30`) — it keeps the level HORIZONTAL on
mobile. A level without it stacks vertically below tablet. The description said
the opposite: "Enables mobile layout (stacks vertically on mobile)", and the
Usage prose repeated it.

The wording is pre-existing on main, in both the props table and the prose, so
this stack did not introduce it. It matters here because the migration seeds
TSDoc from the doc pages, which launders a page-level error into the shipped
`.d.ts` and users' editor tooltips — a wider blast radius than the page alone.

Corrected at the source, so the generated row follows, and in the hand-written
Usage paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): close the theme.md fence my earlier edit left open

CI caught this and it is my regression, not a pre-existing one: main's
`theme.md` passes `prettier --check` at its real path.

Removing the stray four-backtick fence pair around "Theme with Styling" took
out a fence that was load-bearing. The outer ```` closed the inner ```tsx
block (a longer fence closes a shorter one), so deleting the pair left the
StyledTheme example unterminated — it swallowed the "### Nested Themes"
heading and the block after it, and `prettier --check` failed on the file.

Closing the ```tsx block explicitly gives the structure the section was always
meant to have: the heading renders as a heading and each example is its own
closed block. `pnpm run format:check` is green across the repo.

Worth recording how this got through: I ran typecheck, lint, tests and the docs
build locally but not the ROOT `format:check` — which is exactly the "pnpm all
green, CI red" gap this stack exists to close, and the gap only closed for
`lint` two commits ago. The root format check is in my gate from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* docs: restore the flatten-llms-tabs description this stack replaced

Fourth instance of the same silent-deletion pattern, found by the deep review
on #420. `docs/CLAUDE.md`'s "LLM docs pipeline" section documented
`scripts/flatten-llms-tabs.mjs`; this stack swapped in the
`strip-generated-markers.mjs` paragraph in its place rather than alongside it.

The script is not dead. It is still the FIRST step in docs' build chain
(`docusaurus build && node scripts/flatten-llms-tabs.mjs && node
scripts/strip-generated-markers.mjs`), still covered by
`flatten-llms-tabs.test.mjs`, and does something unrelated to what displaced
it — flattening `<PackageManagerTabs>`/`<Tabs>` MDX so the JSX does not land
verbatim in `llms.txt`/`llms-full.txt`. Nothing else in the repo documented it,
so a reader had no way to learn it exists or why it cannot be a Docusaurus
plugin.

Both steps are now described in the order the chain runs them.

Same mechanism as the `gen-component-catalog.mjs` preamble, the manifest
downgrades and the deleted `style-mapping-sync` check: a replacement where an
addition was meant, with no conflict to force a second look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
allxsmith added a commit that referenced this pull request Jul 31, 2026
* docs: add the API page generator, its CI gates and review aids

Lands the tooling for generating the derivable parts of docs/docs/api, with no
page generated yet: MANAGED_CATEGORIES and ORDERED_CATEGORIES both start empty
and grow one category per follow-up, so the 87 page diffs stay reviewable
instead of arriving as one 199-file change.

Four regions per page, delimited by `<!-- bestax:generated <id> -->` markers —
`overview` (the component's TSDoc summary), `import` (the public barrel),
`props` (the `<X>Props` interfaces via the TypeScript compiler API) and
`cssvars` (a new section parsed from the SCSS). Everything outside a marker pair
is hand-written and preserved byte-for-byte; deleting a pair opts that region
out, and `docs-section-order` makes that visible rather than silent.

Three choices keep the output reading as hand-written rather than as typedoc:
own members only, with the one catch-all `...` row the pages already wrote by
hand; types from AST source text, never `checker.typeToString`, which expands
`(typeof validColors)[number]` into 19 literals; and wide colour unions rendered
as a link to the existing Valid values page.

CI gates live in check-conformance.mjs, which already runs in CI, rather than a
new workflow step: `docs-generated` recomputes each managed page in memory and
diffs, `docs-section-order` holds the order and marker presence. Both were
verified by tampering.

Also closes two gate gaps this tooling exposed. `pnpm all` ran
`turbo run format:check`, which only runs the per-package scripts — none cover
`scripts/`, `docs/scripts/` or any `.md`; CI runs the root script, which does, so
CI could be red while `pnpm all` was green. And nothing linted `scripts/` at
all. `all` now calls the root format check and `lint` also runs eslint over
`scripts` and `docs/scripts`; verified by planting an unused variable.

Two review aids ship alongside, both one-shot and both used to validate the
migration that follows: `check-docs-parity.mjs` fails on any prop, default,
description word, code span, URL, live example or prose line that a generated
page drops relative to its hand-written self, and `check-docs-wording.mjs`
buckets every previously-documented prop by how its description changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the layout, columns and grid API pages

First categories through the generator, plus `helpers/` reordered but not
generated — four of its six pages document hooks with `## API` and no `## Props`
at all, and `config.md`/`theme.md` keep their hand-written tables (theme.md's
Props section is a ~350-line prose sub-API, not a table).

`@property` blocks move onto the interface members as inline TSDoc, so a
description is verifiable by position and reaches users' editors and the shipped
.d.ts. Descriptions were seeded FROM the docs pages, not the other way round:
the hand-written tables are consistently the richer text, and the page stays
authoritative wherever the two disagree.

Component TSDoc summaries were likewise seeded from the pages — the existing ones
("Bulma Hero component root.", "Container component for Bulma.") were worse than
the prose they would have replaced. That text ships in the .d.ts, so read the
`component-catalog.md` diff: it is the canary for this step.

Verified: check-docs-parity reports 0 losses across all 87 pages; the generator
is idempotent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the elements API pages

30 pages. Two fixes to the extractor that this category forced, both found by
check-docs-parity rather than by reading pages:

- Heritage through `Omit`/`Pick` and imported (alias) symbols. `LinkButtonProps
  extends Omit<ButtonProps, …>` resolved the symbol of `Omit` — a lib type — so
  all 14 of its inherited props landed in the catch-all row instead of a table.
- `Table`'s six sub-components are imported rather than declared locally, so the
  page rendered no sub-tables at all and the cell components' props vanished.

Props the old tables documented but no interface declares (`href` on Link,
`value` on ListItem, the `<ol>` attributes on OrderedList, `skeleton` from the
helper props) are parked as `@extraProp`, carrying their type and default. That
keeps the page's information in the source, where the rest of it now lives.

`Button`'s component JSDoc was attached to an unrelated `const` rather than to
`Button`, so its summary never reached the page; moved.

Verified: 0 parity losses across all 87 pages; of the 215 previously-documented
props on these pages, 178 read identically and 37 keep their sentence with more
appended — none replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

The checked-in `component-catalog.md` still had #365's text, so
`gen:catalog:check` regenerated it shorter and failed `git diff --exit-code` —
this is why Build and Test is red on this PR.

Restores the generator to main's version. The catalog now regenerates
byte-identical to the committed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct four prop-table defects in the API page extractor

Found by comparing every generated page against its origin/main original in a
browser, and against the interfaces the tables claim to describe.

- Bare alias names where the hand-written tables had real types. A one-member
  "union" (`type CellSpanValue = number`) was never a union node, and a union
  naming another alias (`BulmaFixedGridColsProp = BulmaFixedGridCols |
  'auto'`) failed the all-members-simple test. Both fell through to the bare
  name with no `**Types:**` footnote, so cell.md's `colSpan`/`rowSpan` said
  `CellSpanValue` where main said `number`, and grid.md's `fixedCols` was
  opaque beside five siblings expanded to `0 | … | 12` in the same table.
  Member aliases now resolve to a fixpoint.

- `children` synthesized for components that never render it. The row was
  emitted for any interface with a DOM base, but inheriting `children` is not
  rendering it: Divider spreads onto `<hr>`, so the row documented the one
  thing React throws on ("hr is a void element tag and must neither have
  `children`…"), and Icon always supplies its own JSX children, so anything
  passed is silently dropped. Emitted only where the implementation names
  `children`; the catch-all row still covers pass-through cases.

- Sub-components dropped when their props are an inline DOM type rather than a
  named `*Props` interface. `Navbar.Divider` and `Pagination.Ellipsis` were
  omitted from the generated Subcomponents lists entirely — which is why both
  pages still carried a hand-written duplicate list. They are now listed (with
  no table, since they add no props of their own).

- `never` and `false | true` merged from the branches of a discriminated
  union. slider.md rendered `minDistance` as `never | number` and `range` as
  `false | true`; the forbidding branch's `never` is noise standing where the
  reader needs a type.

Verified: generator idempotent, all ten conformance checks green,
check-docs-parity still reports 0 prop/default/description losses across all
87 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): drop the stale duplicate tables the elements pages kept

`table.md` and `figure.md` still carried their pre-migration sub-component
tables immediately after the generated block, so every sub-component was
documented twice with disagreeing values — `isSelected` defaulted to `false`
in the generated table and `—` in the legacy copy, and figure's stale
`Figure.Caption Props` still dumped the 19-member colour union inline that
this migration exists to remove. The generated tables supersede both.

`delete.md`, `divider.md` and `icon.md` lose a synthesized `children` row for
props those components do not render — `<hr>` throws on children and `Icon`
overrides them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): regenerate the grid pages with the corrected type cells

Picks up the extractor fix from the base branch. `cell.md`'s `colSpan` and
`rowSpan` go back to `number` — they had regressed to a bare `CellSpanValue`,
which aliases exactly `number` and so told the reader strictly less. `grid.md`'s
`fixedCols` expands to `0 | … | 12 | 'auto'` instead of a bare
`BulmaFixedGridColsProp`; it was the only opaque cell in a table whose five
sibling props were already fully expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): add missing DOM_ELEMENT_LABELS entries and stop mislinking Helper Props

Three elements (Divider/hr, Pre, Progress) fell back to the generic "HTML"
label because DOM_ELEMENT_LABELS had no entry for their concrete interfaces.
Separately, skeleton.md's catch-all row linked to Helper Props even though
SkeletonProps doesn't extend BulmaClassesProps and Skeleton never calls
useBulmaClasses — catchAllRow() now reports whether BulmaClassesProps is
actually in the heritage, and the generator only renders the link when it is.

Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): fail the alias fixpoint instead of degrading to bare names

The deep review on #421 flagged that the alias-resolution fixpoint degrades
silently: if a chain needs more than the round bound, the survivors fall back
to a bare name plus a `**Types:**` footnote rather than the expansion, and
nothing says so.

Running out of rounds is a different condition from settling. An alias that
genuinely cannot expand stops making progress and the loop exits clean — that
path is unchanged. But if the bound cuts the loop off while it is still
resolving, the survivors render as bare identifiers, which is the exact
regression this generator exists to prevent. That case now throws, naming the
unresolved aliases.

The bound is a named constant (`ALIAS_FIXPOINT_ROUNDS`) so the error can point
at it. Chains in this repo settle in two rounds; the bound only exists to stop
a mutually-recursive pair spinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): make the marker stripper fail instead of no-opping silently

The deep review asked whether this step fails loudly when it matches nothing.
It did not — it logged "removed 0 marker(s) from 0 file(s)" and exited 0.

That is the one failure this step exists to prevent. Its own header records
why it is a build step and not a plugin: the first attempt was a `postBuild`
hook, `postBuild` runs under `Promise.all`, it raced ahead of
docusaurus-plugin-llms and silently found nothing to strip. A quiet no-op here
ships ~600 markers into llms-full.txt and every per-page `.md` twin with a
green build.

Stripping nothing is only correct when there was nothing to strip, so the
check compares against the SOURCE pages: zero stripped AND zero markers in
docs/docs/api is the legitimate "no managed categories yet" state (which is
this branch, with MANAGED_CATEGORIES empty). Zero stripped while the source
carries markers means the built markdown moved, the marker format changed, or
the ordering regressed — that now exits 1 and says which.

Also answers the other half of the question: the glob only ever reaches `.md`
files plus llms.txt / llms-full.txt, so it cannot touch built HTML.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(bulma-ui): correct the inverted Level.isMobile description

Bulma's `.level.is-mobile` sets `display: flex; flex-direction: row`
(`bulma/sass/layout/level.scss:27-30`) — it keeps the level HORIZONTAL on
mobile. A level without it stacks vertically below tablet. The description said
the opposite: "Enables mobile layout (stacks vertically on mobile)", and the
Usage prose repeated it.

The wording is pre-existing on main, in both the props table and the prose, so
this stack did not introduce it. It matters here because the migration seeds
TSDoc from the doc pages, which launders a page-level error into the shipped
`.d.ts` and users' editor tooltips — a wider blast radius than the page alone.

Corrected at the source, so the generated row follows, and in the hand-written
Usage paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): close the theme.md fence my earlier edit left open

CI caught this and it is my regression, not a pre-existing one: main's
`theme.md` passes `prettier --check` at its real path.

Removing the stray four-backtick fence pair around "Theme with Styling" took
out a fence that was load-bearing. The outer ```` closed the inner ```tsx
block (a longer fence closes a shorter one), so deleting the pair left the
StyledTheme example unterminated — it swallowed the "### Nested Themes"
heading and the block after it, and `prettier --check` failed on the file.

Closing the ```tsx block explicitly gives the structure the section was always
meant to have: the heading renders as a heading and each example is its own
closed block. `pnpm run format:check` is green across the repo.

Worth recording how this got through: I ran typecheck, lint, tests and the docs
build locally but not the ROOT `format:check` — which is exactly the "pnpm all
green, CI red" gap this stack exists to close, and the gap only closed for
`lint` two commits ago. The root format check is in my gate from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* docs: restore the flatten-llms-tabs description this stack replaced

Fourth instance of the same silent-deletion pattern, found by the deep review
on #420. `docs/CLAUDE.md`'s "LLM docs pipeline" section documented
`scripts/flatten-llms-tabs.mjs`; this stack swapped in the
`strip-generated-markers.mjs` paragraph in its place rather than alongside it.

The script is not dead. It is still the FIRST step in docs' build chain
(`docusaurus build && node scripts/flatten-llms-tabs.mjs && node
scripts/strip-generated-markers.mjs`), still covered by
`flatten-llms-tabs.test.mjs`, and does something unrelated to what displaced
it — flattening `<PackageManagerTabs>`/`<Tabs>` MDX so the JSX does not land
verbatim in `llms.txt`/`llms-full.txt`. Nothing else in the repo documented it,
so a reader had no way to learn it exists or why it cannot be a Docusaurus
plugin.

Both steps are now described in the order the chain runs them.

Same mechanism as the `gen-component-catalog.mjs` preamble, the manifest
downgrades and the deleted `style-mapping-sync` check: a replacement where an
addition was meant, with no conflict to force a second look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>
allxsmith added a commit that referenced this pull request Jul 31, 2026
* docs: add the API page generator, its CI gates and review aids

Lands the tooling for generating the derivable parts of docs/docs/api, with no
page generated yet: MANAGED_CATEGORIES and ORDERED_CATEGORIES both start empty
and grow one category per follow-up, so the 87 page diffs stay reviewable
instead of arriving as one 199-file change.

Four regions per page, delimited by `<!-- bestax:generated <id> -->` markers —
`overview` (the component's TSDoc summary), `import` (the public barrel),
`props` (the `<X>Props` interfaces via the TypeScript compiler API) and
`cssvars` (a new section parsed from the SCSS). Everything outside a marker pair
is hand-written and preserved byte-for-byte; deleting a pair opts that region
out, and `docs-section-order` makes that visible rather than silent.

Three choices keep the output reading as hand-written rather than as typedoc:
own members only, with the one catch-all `...` row the pages already wrote by
hand; types from AST source text, never `checker.typeToString`, which expands
`(typeof validColors)[number]` into 19 literals; and wide colour unions rendered
as a link to the existing Valid values page.

CI gates live in check-conformance.mjs, which already runs in CI, rather than a
new workflow step: `docs-generated` recomputes each managed page in memory and
diffs, `docs-section-order` holds the order and marker presence. Both were
verified by tampering.

Also closes two gate gaps this tooling exposed. `pnpm all` ran
`turbo run format:check`, which only runs the per-package scripts — none cover
`scripts/`, `docs/scripts/` or any `.md`; CI runs the root script, which does, so
CI could be red while `pnpm all` was green. And nothing linted `scripts/` at
all. `all` now calls the root format check and `lint` also runs eslint over
`scripts` and `docs/scripts`; verified by planting an unused variable.

Two review aids ship alongside, both one-shot and both used to validate the
migration that follows: `check-docs-parity.mjs` fails on any prop, default,
description word, code span, URL, live example or prose line that a generated
page drops relative to its hand-written self, and `check-docs-wording.mjs`
buckets every previously-documented prop by how its description changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the layout, columns and grid API pages

First categories through the generator, plus `helpers/` reordered but not
generated — four of its six pages document hooks with `## API` and no `## Props`
at all, and `config.md`/`theme.md` keep their hand-written tables (theme.md's
Props section is a ~350-line prose sub-API, not a table).

`@property` blocks move onto the interface members as inline TSDoc, so a
description is verifiable by position and reaches users' editors and the shipped
.d.ts. Descriptions were seeded FROM the docs pages, not the other way round:
the hand-written tables are consistently the richer text, and the page stays
authoritative wherever the two disagree.

Component TSDoc summaries were likewise seeded from the pages — the existing ones
("Bulma Hero component root.", "Container component for Bulma.") were worse than
the prose they would have replaced. That text ships in the .d.ts, so read the
`component-catalog.md` diff: it is the canary for this step.

Verified: check-docs-parity reports 0 losses across all 87 pages; the generator
is idempotent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the elements API pages

30 pages. Two fixes to the extractor that this category forced, both found by
check-docs-parity rather than by reading pages:

- Heritage through `Omit`/`Pick` and imported (alias) symbols. `LinkButtonProps
  extends Omit<ButtonProps, …>` resolved the symbol of `Omit` — a lib type — so
  all 14 of its inherited props landed in the catch-all row instead of a table.
- `Table`'s six sub-components are imported rather than declared locally, so the
  page rendered no sub-tables at all and the cell components' props vanished.

Props the old tables documented but no interface declares (`href` on Link,
`value` on ListItem, the `<ol>` attributes on OrderedList, `skeleton` from the
helper props) are parked as `@extraProp`, carrying their type and default. That
keeps the page's information in the source, where the rest of it now lives.

`Button`'s component JSDoc was attached to an unrelated `const` rather than to
`Button`, so its summary never reached the page; moved.

Verified: 0 parity losses across all 87 pages; of the 215 previously-documented
props on these pages, 178 read identically and 37 keep their sentence with more
appended — none replaced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* docs(bulma-ui): generate the components and form API pages

The last 41 pages, completing the migration: all 87 now share one section order
and one table shape.

Four extractor fixes this pair forced, each found by check-docs-parity:

- Props types that are type ALIASES, not interfaces. `ControlProps` and
  `SliderProps` are unions, and treating a props type as necessarily an interface
  rendered both pages with no table at all — 42 props.
- Defaults destructured in the function body rather than the parameter (`Slider`,
  `TimeInputBase`), and defaults belonging to a base module a thin wrapper
  renders (`TimeInput` -> `TimeInputBase`). `editable` read as `false` on
  timeinput.md where the source says `true`.
- Type cells no longer show a bare alias name: short unions inline, long ones get
  a `**Types:**` footnote built from the alias's own TSDoc.
- `**Subcomponents:**` renders each sub's summary sentence, which the pages
  carried and a bare name list dropped; a sub with its own page is linked rather
  than restated.

Two pages needed a hand edit first: tabs.md and taginput.md had prose sitting
BETWEEN prop tables, where the marker design has nowhere to put it.

Verified across all 87 pages: 0 parity losses, and of 1083 previously-documented
props 953 read identically, 117 keep their sentence with more appended, 12 have
it intact inside a longer one, 1 changed (a comma).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011c6ipfvBsYgF3Jf91ZzSr7

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

The checked-in `component-catalog.md` still had #365's text, so
`gen:catalog:check` regenerated it shorter and failed `git diff --exit-code` —
this is why Build and Test is red on this PR.

Restores the generator to main's version. The catalog now regenerates
byte-identical to the committed file.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: restore the component-catalog preamble this stack reverted

`scripts/gen-component-catalog.mjs` carried a copy of the catalog preamble
predating #365, so regenerating deleted 21 lines of shipped guidance: the
per-component value-union warning, where to find the installed `.d.ts`, the
`Skeleton` helper-prop exception, which sub-component families accept helper
props, and the custom-component composition spine.

This branch regenerated the catalog with that stale generator, committing the
loss into a shipped skill artifact. Restores the generator to main's version
and regenerates; the preamble matches main again and the component one-liners
are unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct four prop-table defects in the API page extractor

Found by comparing every generated page against its origin/main original in a
browser, and against the interfaces the tables claim to describe.

- Bare alias names where the hand-written tables had real types. A one-member
  "union" (`type CellSpanValue = number`) was never a union node, and a union
  naming another alias (`BulmaFixedGridColsProp = BulmaFixedGridCols |
  'auto'`) failed the all-members-simple test. Both fell through to the bare
  name with no `**Types:**` footnote, so cell.md's `colSpan`/`rowSpan` said
  `CellSpanValue` where main said `number`, and grid.md's `fixedCols` was
  opaque beside five siblings expanded to `0 | … | 12` in the same table.
  Member aliases now resolve to a fixpoint.

- `children` synthesized for components that never render it. The row was
  emitted for any interface with a DOM base, but inheriting `children` is not
  rendering it: Divider spreads onto `<hr>`, so the row documented the one
  thing React throws on ("hr is a void element tag and must neither have
  `children`…"), and Icon always supplies its own JSX children, so anything
  passed is silently dropped. Emitted only where the implementation names
  `children`; the catch-all row still covers pass-through cases.

- Sub-components dropped when their props are an inline DOM type rather than a
  named `*Props` interface. `Navbar.Divider` and `Pagination.Ellipsis` were
  omitted from the generated Subcomponents lists entirely — which is why both
  pages still carried a hand-written duplicate list. They are now listed (with
  no table, since they add no props of their own).

- `never` and `false | true` merged from the branches of a discriminated
  union. slider.md rendered `minDistance` as `never | number` and `range` as
  `false | true`; the forbidding branch's `never` is noise standing where the
  reader needs a type.

Verified: generator idempotent, all ten conformance checks green,
check-docs-parity still reports 0 prop/default/description losses across all
87 pages.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): drop the stale duplicate tables the elements pages kept

`table.md` and `figure.md` still carried their pre-migration sub-component
tables immediately after the generated block, so every sub-component was
documented twice with disagreeing values — `isSelected` defaulted to `false`
in the generated table and `—` in the legacy copy, and figure's stale
`Figure.Caption Props` still dumped the 19-member colour union inline that
this migration exists to remove. The generated tables supersede both.

`delete.md`, `divider.md` and `icon.md` lose a synthesized `children` row for
props those components do not render — `<hr>` throws on children and `Icon`
overrides them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): regenerate the grid pages with the corrected type cells

Picks up the extractor fix from the base branch. `cell.md`'s `colSpan` and
`rowSpan` go back to `number` — they had regressed to a bare `CellSpanValue`,
which aliases exactly `number` and so told the reader strictly less. `grid.md`'s
`fixedCols` expands to `0 | … | 12 | 'auto'` instead of a bare
`BulmaFixedGridColsProp`; it was the only opaque cell in a table whose five
sibling props were already fully expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(bulma-ui): stop three props tables documenting props the components reject

Found by comparing each generated page against its origin/main original in a
browser and against the interface it claims to describe.

`@extraProp` tags placed on the wrong interface published props that do not
exist. `RateProps` carried `index`, `isActive` and `isHovered` — fields of
`RateIconProps`, the object handed to `customIcon`, which `Rate` never
destructures; `CarouselProps` carried `active`, which belongs to
`CarouselItemProps`. Passing either lands an unknown attribute on the root
element and React warns. Both sets are already documented correctly in their
own sub-tables.

`DateTimeInput`'s three `@extraProp` tags omitted the `{type}` braces, so
`name`, `form` and `required` rendered with an empty Type cell and `required`
lost its default — the `DateInput` and `TimeInput` siblings had them.

Also restores prose the pages carried and the source TSDoc had lost, so the
generated Subcomponents bullets keep it: the `Panel.Icon` "accepts all Icon
props" note, `Navbar.Divider` and `Pagination.Ellipsis`'s descriptions, and
the five `Sidebar.*` summaries. Per this stack's own rule, where the page and
the comment disagree the page is authoritative.

Section order restored to Accessibility → Related on `collapse`, `loading`,
`tooltip` and `switch`, which had it inverted against the other 83 pages, and
the stale duplicate blocks removed from `navbar`, `panel`, `pagination` and
`sidebar` — `sidebar` was emitting duplicate heading anchors, which broke its
table of contents and deep links.

Verified: generator idempotent, ten of ten conformance checks green, docs
build clean, bulma-ui typecheck/lint/tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): add missing DOM_ELEMENT_LABELS entries and stop mislinking Helper Props

Three elements (Divider/hr, Pre, Progress) fell back to the generic "HTML"
label because DOM_ELEMENT_LABELS had no entry for their concrete interfaces.
Separately, skeleton.md's catch-all row linked to Helper Props even though
SkeletonProps doesn't extend BulmaClassesProps and Skeleton never calls
useBulmaClasses — catchAllRow() now reports whether BulmaClassesProps is
actually in the heritage, and the generator only renders the link when it is.

Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): carry the catch-all fix through the rest of the stack

`scripts/lib/props-extract.mjs` and `scripts/gen-api-docs.mjs` are shared by
every branch here, so landing the `DOM_ELEMENT_LABELS` additions and the
`catchAll -> {text, helpers}` change on #422 alone left #420 and #421 without
them and made #423 — which is stacked above #422 — revert them. Same file,
same content, applied across the stack so the tip is consistent.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): expand DateFormatOption instead of leaving a bare alias

The three datetime pages had regressed from main's
`string | Intl.DateTimeFormatOptions` to a bare `DateFormatOption`, which is
declared in `form/_pickerInternals/` and is not exported from the barrel — so a
reader could neither import it nor look it up, and no `**Types:**` footnote
covered it either.

A union member naming a type the alias index cannot resolve
(`Intl.DateTimeFormatOptions` is a qualified name, not an indexed identifier)
now falls back to its own source text. Blast radius is exactly the three
`format` cells; no other page changes.

Found by an exhaustive cell-by-cell diff of all 87 generated pages against
their origin/main originals, which also settles the rest: one genuine prop row
lost across the whole migration (`card.md`'s `m`/`p`), and every remaining type
or default difference is either an improvement over drifted prose or a bare
alias that does carry a footnote.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): rebase the manifests onto main and address the review findings

**Manifests.** These branches were cut before 19 commits landed on main and
were never refreshed, so relative to their OWN merge base they downgraded
`@commitlint/cli` 21.2.1→21.0.2, `@commitlint/config-conventional` 21.2.0→21.0.2,
`@semantic-release/github` 12.0.9→12.0.2, `semantic-release` 25.0.8→25.0.2,
`prettier` 3.9.6→3.9.4, `react`/`react-dom` 19.2.8→19.2.3, `@docusaurus/*`
3.10.2→3.9.2 and `@fortawesome/fontawesome-free` 7.3.1→7.2.0 — and dropped
docs' `"test": "node --test \"scripts/*.test.mjs\""`, silently disabling the
`flatten-llms-tabs` tests #408 added. Because the branches modify those lines
rather than merely trailing main, a merge would have carried the downgrades in.

`package.json`, `docs/package.json` and `pnpm-lock.yaml` are now main's, with
only this stack's own script additions re-applied on top: `gen:api-sources`,
`gen:api-docs`, `gen:api-docs:check`, `gen`, the `lint` widening over `scripts`
and `docs/scripts`, the root `format:check` fix in `all`, and the
marker-stripping step chained after the llms flattener. The stack adds no
dependency of its own, so main's lockfile is exactly right.

**Review findings.** From the CodeRabbit and Claude deep reviews on #421/#422:

- The alias fixpoint resolved the mixed unions and the `indirect` renames in
  two separate passes, so a mixed union naming a forward-only alias — or a
  rename pointing at a mixed alias — stayed opaque forever. Both reviewers
  flagged it independently. The two now interleave in one bounded fixpoint.
- `rendersChildren` matched `/\bchildren\b/` against raw source text, which
  counts the word in a comment or an unrelated string. It is an AST walk now.
- Six `gapSize*` props on `Columns.tsx` carried TWO JSDoc blocks — the
  migration stacked the new one on top of the old instead of replacing it, so
  dead comments shipped in the `.d.ts`. Collapsed, keeping the tie-break
  clause the first block had and the page had lost.
- A small local interface named in a type cell now renders its object shape:
  `icontext.md`'s `items` goes back to main's `{ iconProps: IconProps; text?:
  string }[]` from a bare `IconTextItem`, and `slider.md`'s `marks` improves on
  both to `{ value: number; label?: React.ReactNode }[]`. Neither interface is
  exported from the barrel, so the bare name was unlookupable.
- Two pre-existing `helpers/` defects, relocated by this stack's reorder and
  worth fixing while the pages are open: a stray four-backtick fence in
  `theme.md` trapped the "Theme with Styling" heading and its example inside a
  code block, and `usebulmaclasses.md` claimed a `className` the example never
  passes and `has-text-info-mobile` for `color: 'link'`.

Both refactors are behaviour-preserving: the generator emits byte-identical
output for them today. Verified: generator idempotent, ten of ten conformance
checks green, `gen:catalog:check` exits 0, docs build clean, bulma-ui
typecheck/lint/tests pass, and the 87-page cell-by-cell diff against main is
unchanged at one lost prop row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): restore bestax-migrate's manifest and the lockfile from main

Rebasing the root and docs manifests left `bestax-migrate/package.json` behind,
and regenerating the lockfile against it reintroduced a stale entry: the branch
still lists `@allxsmith/bestax-bulma` under `dependencies`, where #412/#417
moved it to `devDependencies`, and is missing the `prepack`/`postpack`
pack-manifest hooks those PRs added.

The branch never modified that file — it only trails main — so taking main's
copy is a clean fast-forward. `pnpm-lock.yaml` is now byte-identical to main's
and `pnpm install --frozen-lockfile` succeeds against it, which is the check CI
runs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): fail the alias fixpoint instead of degrading to bare names

The deep review on #421 flagged that the alias-resolution fixpoint degrades
silently: if a chain needs more than the round bound, the survivors fall back
to a bare name plus a `**Types:**` footnote rather than the expansion, and
nothing says so.

Running out of rounds is a different condition from settling. An alias that
genuinely cannot expand stops making progress and the loop exits clean — that
path is unchanged. But if the bound cuts the loop off while it is still
resolving, the survivors render as bare identifiers, which is the exact
regression this generator exists to prevent. That case now throws, naming the
unresolved aliases.

The bound is a named constant (`ALIAS_FIXPOINT_ROUNDS`) so the error can point
at it. Chains in this repo settle in two rounds; the bound only exists to stop
a mutually-recursive pair spinning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): make the marker stripper fail instead of no-opping silently

The deep review asked whether this step fails loudly when it matches nothing.
It did not — it logged "removed 0 marker(s) from 0 file(s)" and exited 0.

That is the one failure this step exists to prevent. Its own header records
why it is a build step and not a plugin: the first attempt was a `postBuild`
hook, `postBuild` runs under `Promise.all`, it raced ahead of
docusaurus-plugin-llms and silently found nothing to strip. A quiet no-op here
ships ~600 markers into llms-full.txt and every per-page `.md` twin with a
green build.

Stripping nothing is only correct when there was nothing to strip, so the
check compares against the SOURCE pages: zero stripped AND zero markers in
docs/docs/api is the legitimate "no managed categories yet" state (which is
this branch, with MANAGED_CATEGORIES empty). Zero stripped while the source
carries markers means the built markdown moved, the marker format changed, or
the ordering regressed — that now exits 1 and says which.

Also answers the other half of the question: the glob only ever reaches `.md`
files plus llms.txt / llms-full.txt, so it cannot touch built HTML.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): correct three defects the #420 deep review found

**The helper-props shortcut swallowed the DOM base.** `classifyTypeNode` tested
`/BulmaClassesProps/` against the whole node's source text, so it fired for
`Omit<React.HTMLAttributes<HTMLElement>, keyof BulmaClassesProps>` — where the
name appears as the omit KEY LIST, not as the base being omitted. It returned
before the `Omit` branch below could unwrap `React.HTMLAttributes<HTMLElement>`,
so `HTMLElement` never reached `catchAllRow` and the catch-all lost its "All
standard HTML attributes" half. That is the mechanism behind the menu.md (4
tables) and dropdown.md (2) regression against main. Matched structurally now:
the node itself must name `BulmaClassesProps`, or be an `Omit`/`Pick` whose
first type argument does.

**`impliedFalse` applied to controlled-mode booleans.** An optional `boolean`
with no destructured default is documented as `false`, which is right for a
flag and wrong for a controlled prop, where `undefined` is what selects
UNCONTROLLED mode. `collapse.md` claimed `open` defaults to `false` while its
own description says "If provided, component is controlled" — two statements
that cannot both hold. A prop whose description says it controls the component
now keeps an empty Default.

**`pnpm all` never ran the widened lint.** `all` invoked `turbo run … lint`
directly; `turbo.json` declares no `//#lint` root task and only bulma-ui,
create-bestax and bestax-migrate define one (each scoped to its own `src`), so
`eslint scripts docs/scripts` — which exists only in the ROOT `lint` script —
was skipped. That is the same "pnpm all green, CI red" gap this stack fixed for
`format:check`, left in place for `lint`; CI caught it only because `ci.yml`
runs `pnpm run lint` as its own step. Routed through `pnpm run lint`, and
verified by planting an unused variable in `scripts/`.

Verified: generator idempotent, ten of ten conformance checks green,
`gen:catalog:check` exits 0, and the 87-page cell-by-cell diff against main is
unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(bulma-ui): correct the inverted Level.isMobile description

Bulma's `.level.is-mobile` sets `display: flex; flex-direction: row`
(`bulma/sass/layout/level.scss:27-30`) — it keeps the level HORIZONTAL on
mobile. A level without it stacks vertically below tablet. The description said
the opposite: "Enables mobile layout (stacks vertically on mobile)", and the
Usage prose repeated it.

The wording is pre-existing on main, in both the props table and the prose, so
this stack did not introduce it. It matters here because the migration seeds
TSDoc from the doc pages, which launders a page-level error into the shipped
`.d.ts` and users' editor tooltips — a wider blast radius than the page alone.

Corrected at the source, so the generated row follows, and in the hand-written
Usage paragraph.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* fix(docs): close the theme.md fence my earlier edit left open

CI caught this and it is my regression, not a pre-existing one: main's
`theme.md` passes `prettier --check` at its real path.

Removing the stray four-backtick fence pair around "Theme with Styling" took
out a fence that was load-bearing. The outer ```` closed the inner ```tsx
block (a longer fence closes a shorter one), so deleting the pair left the
StyledTheme example unterminated — it swallowed the "### Nested Themes"
heading and the block after it, and `prettier --check` failed on the file.

Closing the ```tsx block explicitly gives the structure the section was always
meant to have: the heading renders as a heading and each example is its own
closed block. `pnpm run format:check` is green across the repo.

Worth recording how this got through: I ran typecheck, lint, tests and the docs
build locally but not the ROOT `format:check` — which is exactly the "pnpm all
green, CI red" gap this stack exists to close, and the gap only closed for
`lint` two commits ago. The root format check is in my gate from here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* docs: restore the flatten-llms-tabs description this stack replaced

Fourth instance of the same silent-deletion pattern, found by the deep review
on #420. `docs/CLAUDE.md`'s "LLM docs pipeline" section documented
`scripts/flatten-llms-tabs.mjs`; this stack swapped in the
`strip-generated-markers.mjs` paragraph in its place rather than alongside it.

The script is not dead. It is still the FIRST step in docs' build chain
(`docusaurus build && node scripts/flatten-llms-tabs.mjs && node
scripts/strip-generated-markers.mjs`), still covered by
`flatten-llms-tabs.test.mjs`, and does something unrelated to what displaced
it — flattening `<PackageManagerTabs>`/`<Tabs>` MDX so the JSX does not land
verbatim in `llms.txt`/`llms-full.txt`. Nothing else in the repo documented it,
so a reader had no way to learn it exists or why it cannot be a Docusaurus
plugin.

Both steps are now described in the order the chain runs them.

Same mechanism as the `gen-component-catalog.mjs` preamble, the manifest
downgrades and the deleted `style-mapping-sync` check: a replacement where an
addition was meant, with no conflict to force a second look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

* chore: remove the one-shot migration scripts now the migration has landed

Four scripts, 1,890 lines, invoked by nothing — not `package.json`, not
`ci.yml`, not `check-conformance.mjs`, and not any `.md`:

  scripts/check-docs-parity.mjs       421
  scripts/check-docs-wording.mjs      197
  scripts/migrate-api-pages.mjs       490
  scripts/codemod-property-tsdoc.mjs  782

`check-docs-parity.mjs` said so itself: "This is a migration tool, not
permanent CI: once the migration lands, `origin/main` becomes the new baseline
and `docs-generated` in check-conformance.mjs holds the line instead." With all
seven categories now migrated, that is exactly where things stand.

Two reasons this is more than tidying. #420 widened `lint` to cover `scripts/`,
so these would be linted and prettier-checked on every CI run and carried
through every eslint/prettier/Node upgrade for no ongoing value. And after
merge they would not fail, they would mislead: `check-docs-parity` defaults to
`--base=origin/main`, so it would diff main against itself and report "0 losses
across 87 pages" — a check that reads as evidence while proving nothing.

Ran one final time before deletion, as the last regression check on the
generated pages: 6 prose flags across 2 files, all previously reviewed and
intentional — navbar's `**Key Subcomponents:**` heading, superseded by the
generated list that now includes `Navbar.Divider`, and sidebar's five "Accepts
all standard <X> HTML attributes" lines, whose element names now live in the
generated sub-tables' catch-all rows.

Nothing else references them; git history keeps them if a future category ever
needs re-migrating.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>
bestax-release-bot Bot pushed a commit that referenced this pull request Aug 1, 2026
# [2.0.0](https://github.com/allxsmith/bestax/compare/bestax-migrate@1.0.0...bestax-migrate@2.0.0) (2026-08-01)

### Bug Fixes

* **bestax-migrate:** give the kitchen-sink e2e a per-process scratch dir ([2211ea5](2211ea5))
* **bestax-migrate:** reject pnpm's workspace alias form instead of unwrapping it ([de6a900](de6a900))
* **bestax-migrate:** require the pack script to exist, not just be named ([5315efe](5315efe))
* **bestax-migrate:** resolve bare workspace: and guard the catalog: protocol ([7fda9db](7fda9db)), closes [#417](#417) [#412](#412)
* **bestax-migrate:** resolve workspace: specifiers before publishing ([782829a](782829a)), closes [bestax-migrate#test](https://github.com/bestax-migrate/issues/test) [#412](#412)
* **bestax-migrate:** stop the pack hooks excusing a catalog: devDependency ([4127ead](4127ead)), closes [#412-shaped](#412)
* **create-bestax:** concrete inline-style → helper-prop mapping for the never-inline rule ([#357](#357)) ([5f72a90](5f72a90)), closes [#350](#350) [#350](#350)
* **docs:** stop cssnano stripping Font Awesome [@font-face](https://github.com/font-face), add [#3](#3) CSS framework blog post ([#401](#401)) ([5d114e1](5d114e1)), closes [#400](#400)

### chore

* **deps:** consolidate the dependabot backlog, require Node 22 in both CLIs ([#447](#447)) ([e68148c](e68148c)), closes [#427](#427) [#428](#428) [#431](#431) [#432](#432) [#440](#440) [#393](#393)

### Features

* **bestax-migrate:** require Node 22 and take chalk 6 ([#449](#449)) ([4c0e1e2](4c0e1e2)), closes [#447](#447)
* **bulma-ui:** ship agent-discovery files in the npm tarball ([#345](#345)) ([4b58739](4b58739)), closes [#344](#344) [#344](#344) [#344](#344)
* **create-bestax:** add controlled-Burger Navbar to the landing archetype ([#355](#355)) ([36d4d09](36d4d09)), closes [#348](#348)
* **create-bestax:** agent-validated guidance for skills, scaffold CLAUDE.md, and catalog ([#365](#365)) ([6fd06ae](6fd06ae)), closes [#2](#2)
* **create-bestax:** require Node 22 and take chalk 6 ([#448](#448)) ([90fced2](90fced2)), closes [#447](#447)
* **create-bestax:** scaffold .claude/launch.json with the AI skills opt-in ([#343](#343)) ([189135a](189135a))
* **create-bestax:** set scaffolded index.html title to the project name ([#356](#356)) ([3bfbea3](3bfbea3)), closes [#349](#349) [#349](#349)

### BREAKING CHANGES

* **bestax-migrate:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1. This applies to the runtime the codemod executes
on, not to the app being migrated.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **create-bestax:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **deps:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh

* feat(bestax-migrate): require Node 22 and take chalk 6

chalk 6 drops support for Node below 22. The API surface this package uses is
unchanged, so no calling code changes.

The version guard in src/index.ts moves ahead of every import and no longer
depends on anything: import declarations are hoisted and evaluated before any
statement in the module, and chalk 6 itself requires Node >= 22, so a static
import would fail to load on exactly the runtimes the guard exists to catch.
./cli.js is now imported dynamically for the same reason.

@babel/parser deliberately stays on 7.x. Babel 8 removes the
`deprecatedImportAssert` plugin with no replacement, and this package parses
the legacy `import x from 'y' assert { type: 'json' }` form on purpose — a
codemod that migrates older codebases must not crash on the syntax those
codebases still contain. There is a regression test for it ("parses the legacy
import-assert syntax"), which Babel 8 fails outright. jscodeshift 17 bundles
its own Babel 7 regardless, so staying on 7 also keeps a single parser in the
tree rather than two.
* **deps:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Aug 7, 2026
## [5.8.1](https://github.com/allxsmith/bestax/compare/@allxsmith/bestax-bulma@5.8.0...@allxsmith/bestax-bulma@5.8.1) (2026-08-07)

### Bug Fixes

* **bestax-migrate:** give the kitchen-sink e2e a per-process scratch dir ([2211ea5](2211ea5))
* **bestax-migrate:** reject pnpm's workspace alias form instead of unwrapping it ([de6a900](de6a900))
* **bestax-migrate:** require the pack script to exist, not just be named ([5315efe](5315efe))
* **bestax-migrate:** resolve bare workspace: and guard the catalog: protocol ([7fda9db](7fda9db)), closes [#417](#417) [#412](#412)
* **bestax-migrate:** resolve workspace: specifiers before publishing ([782829a](782829a)), closes [bestax-migrate#test](https://github.com/bestax-migrate/issues/test) [#412](#412)
* **bestax-migrate:** stop the pack hooks excusing a catalog: devDependency ([4127ead](4127ead)), closes [#412-shaped](#412)
* **bulma-ui:** deprecate CSS-less color values, warn in dev, fix has-text fall-through ([fb111eb](fb111eb))
* **bulma-ui:** fail closed on missing process and scope color guidance to real props ([117c0c0](117c0c0))
* **create-bestax:** concrete inline-style → helper-prop mapping for the never-inline rule ([#357](#357)) ([5f72a90](5f72a90)), closes [#350](#350) [#350](#350)
* **create-bestax:** validate at submit in the bestax-form signup example ([0b9518f](0b9518f))
* **create-bestax:** wire labeled controls in the skill showcase story ([af49a16](af49a16))
* **docs:** announce the hero copy, and stop remounting the icons ([98e2cb0](98e2cb0)), closes [#434](#434)
* **docs:** correct the frozen-install translation and reject leaked fences ([1883de3](1883de3))
* **docs:** drop dead nomodule ionicons fallback ([82be3e4](82be3e4))
* **docs:** harden PackageManagerTabs and document how to author it ([5b0d3e6](5b0d3e6)), closes [#434](#434)
* **docs:** harden the hero copy button and share the tab storage key ([9e16cd7](9e16cd7))
* **docs:** make the hero package-manager switcher a real radiogroup ([aa14ff2](aa14ff2)), closes [#434](#434)
* **docs:** stop cssnano stripping Font Awesome [@font-face](https://github.com/font-face), add [#3](#3) CSS framework blog post ([#401](#401)) ([5d114e1](5d114e1)), closes [#400](#400)

### chore

* **deps:** consolidate the dependabot backlog, require Node 22 in both CLIs ([#447](#447)) ([e68148c](e68148c)), closes [#427](#427) [#428](#428) [#431](#431) [#432](#432) [#440](#440) [#393](#393)

### Features

* **bestax-migrate:** require Node 22 and take chalk 6 ([#449](#449)) ([4c0e1e2](4c0e1e2)), closes [#447](#447)
* **create-bestax:** add controlled-Burger Navbar to the landing archetype ([#355](#355)) ([36d4d09](36d4d09)), closes [#348](#348)
* **create-bestax:** agent-validated guidance for skills, scaffold CLAUDE.md, and catalog ([#365](#365)) ([6fd06ae](6fd06ae)), closes [#2](#2)
* **create-bestax:** require Node 22 and take chalk 6 ([#448](#448)) ([90fced2](90fced2)), closes [#447](#447)
* **create-bestax:** set scaffolded index.html title to the project name ([#356](#356)) ([3bfbea3](3bfbea3)), closes [#349](#349) [#349](#349)
* **docs:** add package-manager switches to the homepage hero ([374caf8](374caf8))
* **docs:** add PackageManagerTabs and register it globally ([23c9989](23c9989))
* **docs:** show all posts in the blog sidebar ([d29e9c6](d29e9c6))

### Performance Improvements

* **docs:** defer live previews until they scroll into view ([d6bf87b](d6bf87b))
* **docs:** share one parsed stylesheet set across every live preview ([feb993a](feb993a))

### BREAKING CHANGES

* **bestax-migrate:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1. This applies to the runtime the codemod executes
on, not to the app being migrated.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **create-bestax:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **deps:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh

* feat(bestax-migrate): require Node 22 and take chalk 6

chalk 6 drops support for Node below 22. The API surface this package uses is
unchanged, so no calling code changes.

The version guard in src/index.ts moves ahead of every import and no longer
depends on anything: import declarations are hoisted and evaluated before any
statement in the module, and chalk 6 itself requires Node >= 22, so a static
import would fail to load on exactly the runtimes the guard exists to catch.
./cli.js is now imported dynamically for the same reason.

@babel/parser deliberately stays on 7.x. Babel 8 removes the
`deprecatedImportAssert` plugin with no replacement, and this package parses
the legacy `import x from 'y' assert { type: 'json' }` form on purpose — a
codemod that migrates older codebases must not crash on the syntax those
codebases still contain. There is a regression test for it ("parses the legacy
import-assert syntax"), which Babel 8 fails outright. jscodeshift 17 bundles
its own Babel 7 regardless, so staying on 7 also keeps a single parser in the
tree rather than two.
* **deps:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.8.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

bestax-release-bot Bot pushed a commit that referenced this pull request Aug 12, 2026
# 1.0.0 (2026-08-12)

* feat(bulma-ui)!: remove bestax-bulma-prefixed CSS variant ([94baa34](94baa34))
* feat(create-bestax)!: require Node.js 18+ and align with bestax-bulma v2 ([#118](#118)) ([b22f183](b22f183))

### Bug Fixes

* add comprehensive rules to prevent bulma-ui versioning on non-bulma-ui commits ([#122](#122)) ([525ccfa](525ccfa)), closes [#119](#119)
* **bestax-mcp:** derive the near-miss guidance from the skill, and only when it helps ([1141cca](1141cca))
* **bestax-mcp:** do not split a helper-prop table cell on an escaped pipe ([bdac820](bdac820))
* **bestax-mcp:** lead get_helper_props with the inline-style prohibition ([ffc627a](ffc627a))
* **bestax-mcp:** make list_components point at the next step ([8ddb2fd](8ddb2fd))
* **bestax-mcp:** make tests and cached builds work from a clean checkout ([6e63820](6e63820)), closes [bestax-mcp#build](https://github.com/bestax-mcp/issues/build)
* **bestax-mcp:** name list_components as the entry point, not search_bestax ([206380b](206380b))
* **bestax-mcp:** name the three near-miss components in the list_components footer ([1c7af67](1c7af67))
* **bestax-mcp:** route helper questions to the tool that answers them ([cd6ce12](cd6ce12))
* **bestax-mcp:** validate the one input that is not ours, and bound the rest ([3e1adc9](3e1adc9))
* **bestax-migrate:** give the kitchen-sink e2e a per-process scratch dir ([2211ea5](2211ea5))
* **bestax-migrate:** reject pnpm's workspace alias form instead of unwrapping it ([de6a900](de6a900))
* **bestax-migrate:** require the pack script to exist, not just be named ([5315efe](5315efe))
* **bestax-migrate:** resolve bare workspace: and guard the catalog: protocol ([7fda9db](7fda9db)), closes [#417](#417) [#412](#412)
* **bestax-migrate:** resolve workspace: specifiers before publishing ([782829a](782829a)), closes [bestax-migrate#test](https://github.com/bestax-migrate/issues/test) [#412](#412)
* **bestax-migrate:** stop the pack hooks excusing a catalog: devDependency ([4127ead](4127ead)), closes [#412-shaped](#412)
* **bulma-ui:** a11y + case-insensitive Taginput matching from PR review ([d576829](d576829))
* **bulma-ui:** accept router props like `to` on Navbar.Item without casts ([#311](#311)) ([b78856b](b78856b)), closes [#306](#306)
* **bulma-ui:** Add build step to publish in ci.yml ([e3707fc](e3707fc))
* **bulma-ui:** add fontawesome-free as explicit devDependency ([a4a5389](a4a5389))
* **bulma-ui:** add missing exports ([0d16633](0d16633))
* **bulma-ui:** Add Skeleton to exports ([e481599](e481599))
* **bulma-ui:** another attempt to fix semantic release builds with ci.yml ([cc3a3e2](cc3a3e2))
* **bulma-ui:** another attempt to fix semantic release builds with ci.yml ([314bc39](314bc39))
* **bulma-ui:** another attempt to fix semantic release builds with ci.yml ([c930693](c930693))
* **bulma-ui:** associate Autocomplete and Taginput labels with their inner inputs ([7ae37d4](7ae37d4))
* **bulma-ui:** associate Autocomplete and Taginput labels with their inner inputs ([384bd38](384bd38))
* **bulma-ui:** associate the form label prop with its control via a generated id ([e6686af](e6686af))
* **bulma-ui:** complete domain migration and fix semantic-release configuration ([#64](#64)) ([f4cd71d](f4cd71d))
* **bulma-ui:** correct blog post examples and add Modal compound components ([#81](#81)) ([559c2e3](559c2e3))
* **bulma-ui:** correct NPM_TOKEN env variable in ci.yml ([94b48b4](94b48b4))
* **bulma-ui:** cover horizontal-layout group label association ([ef3ca9f](ef3ca9f))
* **bulma-ui:** deprecate CSS-less color values, warn in dev, fix has-text fall-through ([fb111eb](fb111eb))
* **bulma-ui:** fail closed on missing process and scope color guidance to real props ([117c0c0](117c0c0))
* **bulma-ui:** Fix release.config.js to include package-lock.json ([390da59](390da59))
* **bulma-ui:** fix standalone Badge pointer-events, pulse halo, and falsy content ([#295](#295)) ([a9db031](a9db031)), closes [#264](#264)
* **bulma-ui:** full classPrefix support across layout/grid + prefix utils ([4ce0b53](4ce0b53))
* **bulma-ui:** honor the htmlFor opt-out in the convenience hook and tighten the association docs ([92aa622](92aa622))
* **bulma-ui:** improve npm package discoverability with optimized keywords and badges ([#72](#72)) ([8c7a696](8c7a696))
* **bulma-ui:** Initial semantic release changes ([b78d785](b78d785))
* **bulma-ui:** keep Taginput's fallback name unless the label targets its input ([73cec33](73cec33))
* **bulma-ui:** keep Taginput's fallback name unless the label targets its input ([ca5996a](ca5996a))
* **bulma-ui:** migrate domain from bestax.cc to bestax.io ([#64](#64)) ([4870b1e](4870b1e))
* **bulma-ui:** migrate ionicons to v8 to unblock publish and Storybook ([927a55b](927a55b)), closes [#142](#142)
* **bulma-ui:** name Rate, Checkboxes, and Radios groups from their labels via aria-labelledby ([dce0ee7](dce0ee7))
* **bulma-ui:** name Rate, Checkboxes, and Radios groups from their labels via aria-labelledby ([#497](#497)) ([5c4222e](5c4222e))
* **bulma-ui:** name the three near-miss components in AGENTS.md ([c63f491](c63f491)), closes [#344](#344)
* **bulma-ui:** never let labelProps.htmlFor wire a group label to a control ([3b3aaaf](3b3aaaf))
* **bulma-ui:** publish rewritten README to npm ([9810081](9810081))
* **bulma-ui:** publish with npm provenance attestation ([172da62](172da62)), closes [#180](#180)
* **bulma-ui:** reference llms docs from README and package.json ([#198](#198)) ([db8aab3](db8aab3))
* **bulma-ui:** reject predicate-blocked values during manual entry ([a8f6e28](a8f6e28))
* **bulma-ui:** resolve flex item properties and Card compound component issues ([#55](#55)) ([e774da3](e774da3))
* **bulma-ui:** resolve flex item properties and Card compound component issues ([#55](#55)) ([7641a53](7641a53))
* **bulma-ui:** resolve react-hooks v7 and [@eslint-react](https://github.com/eslint-react) findings ([14caaaf](14caaaf))
* **bulma-ui:** resolve security vulnerabilities and update dependencies ([#128](#128)) ([112f6e4](112f6e4)), closes [#127](#127)
* **bulma-ui:** restrict semantic-release to bulma-ui scoped commits only ([2d67bf9](2d67bf9)), closes [#62](#62)
* **bulma-ui:** retry failed Avatar src, flatten Fragment children in Avatars, RTL-safe overlap ([#297](#297)) ([c00b9db](c00b9db))
* **bulma-ui:** route every hardcoded class through the prefix helpers; add classPrefix sweep test ([#301](#301)) ([a50b134](a50b134)), closes [#286](#286)
* **bulma-ui:** setup gpg signing with semantic-release ([3e24722](3e24722))
* **bulma-ui:** strip redundant library prefix from Icon name ([#242](#242)) ([dbe3622](dbe3622)), closes [#189](#189)
* **bulma-ui:** trigger release to publish via OIDC trusted publishing ([e2d09c5](e2d09c5))
* **bulma-ui:** update bundle size claims to accurate 21KB gzipped ([#66](#66)) ([6e381bd](6e381bd))
* **bulma-ui:** update package-lock.json ([853d585](853d585))
* **bulma-ui:** update package.json for better seo, exports, types, engines, funding, etc ([98cbc56](98cbc56))
* **bulma-ui:** use createRequire for ESM compatibility in Storybook 10 ([#130](#130)) ([b27e60e](b27e60e)), closes [#129](#129)
* **ci:** collect screenshots as artifacts and commit in single batch to avoid conflicts ([27b259d](27b259d))
* **ci:** ensure npm install uses fresh downloads with --prefer-online ([1f2e15d](1f2e15d))
* **ci:** properly extract base path for recursive file search ([e0330ff](e0330ff))
* **ci:** use find command instead of glob module in verified-commit action ([0e2d159](0e2d159))
* **ci:** use npm ci for scaffolded app dependencies ([35652c8](35652c8))
* **create-bestax:** concrete inline-style → helper-prop mapping for the never-inline rule ([#357](#357)) ([5f72a90](5f72a90)), closes [#350](#350) [#350](#350)
* **create-bestax:** correct browser title to prioritize Bestax branding ([#106](#106)) ([23aa535](23aa535)), closes [#105](#105)
* **create-bestax:** correct template path resolution from ../../ to ../ ([65b4493](65b4493)), closes [#78](#78)
* **create-bestax:** dark-mode contrast rules in theming/layout skills and docs ([#303](#303)) ([490bf21](490bf21)), closes [#194](#194) [#195](#195)
* **create-bestax:** exclude templates directory from linting and typecheck ([18fec0b](18fec0b))
* **create-bestax:** fail fast with guidance instead of hanging when stdin is not a TTY ([#293](#293)) ([46a172d](46a172d)), closes [#192](#192)
* **create-bestax:** move templates into package directory and update docs ([195bf01](195bf01)), closes [#78](#78)
* **create-bestax:** point scaffolded CLAUDE.md at llms docs; document skills ([#198](#198)) ([b2e0514](b2e0514))
* **create-bestax:** publish with npm provenance attestation ([21ffe8f](21ffe8f)), closes [#180](#180)
* **create-bestax:** put the near-miss guidance where every session sees it ([6db49f3](6db49f3))
* **create-bestax:** read version from package.json instead of hardcoded value ([#109](#109)) ([8605699](8605699))
* **create-bestax:** refresh README and bump scaffolded bestax-bulma to ^5 ([4e19e86](4e19e86))
* **create-bestax:** reject dot-only project names, pin icon versions, bundle bestax-icons skill ([#310](#310)) ([ddff8e5](ddff8e5))
* **create-bestax:** scaffold @allxsmith/bestax-bulma ^4.0.0 ([1d3b802](1d3b802))
* **create-bestax:** scaffold bundled bestax CSS flavors, not stock Bulma ([43621dc](43621dc))
* **create-bestax:** ship improved bundled skills + component catalog ([#199](#199)) ([a1515c2](a1515c2))
* **create-bestax:** shrink the near-miss block and pin the copies together ([d582da5](d582da5))
* **create-bestax:** skills-sync conformance gate + theming skill reference backfill ([#326](#326)) ([9584133](9584133)), closes [#285](#285)
* **create-bestax:** stop the skills teaching a Theme call that does not compile ([2935bb2](2935bb2))
* **create-bestax:** synchronize version with bestax-bulma to 2.4.0 ([623ee79](623ee79)), closes [#96](#96)
* **create-bestax:** teach the skills the three components Bulma hides ([22dcff7](22dcff7))
* **create-bestax:** update template dependency to ^2.4.0 ([200971d](200971d))
* **create-bestax:** use scenario-specific screenshot directories to prevent overwrites ([#108](#108)) ([c675957](c675957)), closes [#107](#107)
* **create-bestax:** validate at submit in the bestax-form signup example ([0b9518f](0b9518f))
* **create-bestax:** wire labeled controls in the skill showcase story ([af49a16](af49a16))
* **docs:** announce the hero copy, and stop remounting the icons ([98e2cb0](98e2cb0)), closes [#434](#434)
* **docs:** correct Content Signals syntax in robots.txt ([#134](#134)) ([85dd9de](85dd9de))
* **docs:** correct the frozen-install translation and reject leaked fences ([1883de3](1883de3))
* **docs:** drop dead nomodule ionicons fallback ([82be3e4](82be3e4))
* **docs:** emit per-page markdown so llms.txt links resolve ([#200](#200)) ([7877083](7877083))
* **docs:** escape apostrophe in QuickStart notification text ([25d6d72](25d6d72))
* **docs:** generate llms.txt so the advertised homepage link resolves ([9fae464](9fae464)), closes [#177](#177)
* **docs:** give every batch run its own port — slot reuse was corrupting runs ([6ef1755](6ef1755))
* **docs:** harden PackageManagerTabs and document how to author it ([5b0d3e6](5b0d3e6)), closes [#434](#434)
* **docs:** harden the hero copy button and share the tab storage key ([9e16cd7](9e16cd7))
* **docs:** improve homepage hero layout and button spacing ([5f7a5a7](5f7a5a7))
* **docs:** make the eval batch resumable after a container restart ([d56229e](d56229e))
* **docs:** make the hero package-manager switcher a real radiogroup ([aa14ff2](aa14ff2)), closes [#434](#434)
* **docs:** move robots.txt to correct deployment location ([#90](#90)) ([1e2aeee](1e2aeee))
* **docs:** rebrand and reorganize Storybook ([#83](#83)) ([dfb9937](dfb9937))
* **docs:** remove Google Analytics and add robots.txt ([94776f7](94776f7))
* **docs:** stop cssnano stripping Font Awesome [@font-face](https://github.com/font-face), add [#3](#3) CSS framework blog post ([#401](#401)) ([5d114e1](5d114e1)), closes [#400](#400)
* **docs:** update Storybook logo path to /img/logo.svg for deployed site ([bf59758](bf59758))
* **e2e:** correct notification CSS selectors to use contains instead of ends-with ([182acc1](182acc1))
* implement independent package versioning strategy ([#111](#111)) ([7819c73](7819c73)), closes [#110](#110)
* prevent bulma-ui from versioning on create-bestax commits ([#120](#120)) ([4dfaf9c](4dfaf9c)), closes [#119](#119)
* resolve React Hooks violations and ESLint configuration issues ([32d2931](32d2931))
* upgrade Turbo, Storybook, and Docusaurus dependencies ([5b4ebdd](5b4ebdd)), closes [#98](#98)

### chore

* **deps:** consolidate the dependabot backlog, require Node 22 in both CLIs ([#447](#447)) ([e68148c](e68148c)), closes [#427](#427) [#428](#428) [#431](#431) [#432](#432) [#440](#440) [#393](#393)

### Documentation

* fix stale versioning and coverage docs; drop CLAUDE.md stale-docs flags ([71c4583](71c4583))

### Features

* add theme system and config provider with comprehensive test coverage ([f3ca7f0](f3ca7f0))
* **bestax-mcp:** serve component docs, props, examples and skills over MCP ([c2abcc4](c2abcc4))
* **bestax-migrate:** react-bulma-components → bestax-bulma codemod CLI, skill, and docs ([#333](#333)) ([e04a12b](e04a12b)), closes [#1e6b99](https://github.com/allxsmith/bestax/issues/1e6b99)
* **bestax-migrate:** require Node 22 and take chalk 6 ([#449](#449)) ([4c0e1e2](4c0e1e2)), closes [#447](#447)
* **bulma-ui:** add Avatar, Avatars, and Badge components ([#257](#257)) ([0817018](0817018)), closes [#256](#256)
* **bulma-ui:** add colorMode dark-mode prop to Theme ([4acc41e](4acc41e)), closes [#174](#174)
* **bulma-ui:** add consistent gap prop to Columns, aliasing gapSize ([#300](#300)) ([6c36455](6c36455)), closes [#282](#282)
* **bulma-ui:** add cursor helper, closeDelay prop, and polish Tooltip stories ([37945b5](37945b5))
* **bulma-ui:** add extra components, form elements, and SCSS styles ([59daf28](59daf28))
* **bulma-ui:** add HTML element wrapper components ([#135](#135)) ([#136](#136)) ([20fb16d](20fb16d))
* **bulma-ui:** add manual-entry stories for format, bounds, and blocked-value variations ([e93d51c](e93d51c))
* **bulma-ui:** add Reveal component for scroll-triggered animations ([#255](#255)) ([a89c574](a89c574))
* **bulma-ui:** Add skeletons ([6c46e4b](6c46e4b))
* **bulma-ui:** add themed Checkbox/Radio, convenience Field components, and Autocomplete cleanup ([3c57a5a](3c57a5a))
* **bulma-ui:** add typing-first story variants for all picker property variations ([078433f](078433f))
* **bulma-ui:** associate Field's label with a composed base control ([219f631](219f631))
* **bulma-ui:** avatar/badge a11y batch — decorative alt, accessible names, live region, button type, surplus i18n, focus ring ([#298](#298)) ([508477f](508477f)), closes [#266](#266) [#266](#266)
* **bulma-ui:** change the default primary color to [#1](#1 ([8872620](8872620)), closes [#1e6b99](https://github.com/allxsmith/bestax/issues/1e6b99) [#1e6b99](https://github.com/allxsmith/bestax/issues/1e6b99)
* **bulma-ui:** compound (dot-notation) sub-components for all parent/child families via shared withSubComponents helper ([#331](#331)) ([07516c5](07516c5))
* **bulma-ui:** dim and blur the calendar behind the Datetimepicker time wheels ([3d90619](3d90619))
* **bulma-ui:** finalize the 3.0 component set ([87ccc0e](87ccc0e))
* **bulma-ui:** make Button and Link as prop polymorphic (React.ElementType) ([#238](#238)) ([ce90304](ce90304)), closes [#188](#188)
* **bulma-ui:** require React 18 as the minimum supported version ([c7251b0](c7251b0))
* **bulma-ui:** ship agent-discovery files in the npm tarball ([#345](#345)) ([4b58739](4b58739)), closes [#344](#344) [#344](#344) [#344](#344)
* **ci:** add verified-commit action for GPG-signed commits ([d078dfa](d078dfa))
* **create-bestax:** add bestax-optimize skill for shrinking built CSS ([#329](#329)) ([f597b9f](f597b9f))
* **create-bestax:** add CLI tool with Vite templates and automated publishing ([9748c3d](9748c3d))
* **create-bestax:** add controlled-Burger Navbar to the landing archetype ([#355](#355)) ([36d4d09](36d4d09)), closes [#348](#348)
* **create-bestax:** add cross-platform emoji support with figures ([#103](#103)) ([15567d9](15567d9))
* **create-bestax:** add README with templates location note ([8ddc73d](8ddc73d))
* **create-bestax:** add visual regression testing and synchronized versioning ([17e1e22](17e1e22)), closes [#94](#94)
* **create-bestax:** agent-validated guidance for skills, scaffold CLAUDE.md, and catalog ([#365](#365)) ([6fd06ae](6fd06ae)), closes [#2](#2)
* **create-bestax:** bestax-icons skill — teach agents the icon system ([#302](#302)) ([61c8ef2](61c8ef2)), closes [#287](#287)
* **create-bestax:** improve favicon visibility and add distinct branding ([621590d](621590d)), closes [#100](#100)
* **create-bestax:** modernize templates (Vite 8, ESLint 10, TS 6) + add working lint config ([4537629](4537629)), closes [#167](#167)
* **create-bestax:** offer to install the bestax AI skills when scaffolding ([625b7bf](625b7bf)), closes [#174](#174)
* **create-bestax:** require Node 22 and take chalk 6 ([#448](#448)) ([90fced2](90fced2)), closes [#447](#447)
* **create-bestax:** scaffold .claude/launch.json with the AI skills opt-in ([#343](#343)) ([189135a](189135a))
* **create-bestax:** scaffold-aware CLAUDE.md with setup facts and house style ([#271](#271)) ([c1681b0](c1681b0))
* **create-bestax:** set scaffolded index.html title to the project name ([#356](#356)) ([3bfbea3](3bfbea3)), closes [#349](#349) [#349](#349)
* **docs:** add Google Analytics tracking for usage insights ([#68](#68)) ([90ab951](90ab951))
* **docs:** add package-manager switches to the homepage hero ([374caf8](374caf8))
* **docs:** add PackageManagerTabs and register it globally ([23c9989](23c9989))
* **docs:** add pronunciation guide and dark mode support ([#58](#58)) ([48a8916](48a8916))
* **docs:** aggregate-runs.mjs — distribution stats across a runs directory ([5de9c07](5de9c07))
* **docs:** batch runner for the eval harness, with the concurrency fixes it needed ([f8e268c](f8e268c))
* **docs:** migrate from GitHub Pages to Cloudflare Pages ([#132](#132)) ([2154672](2154672)), closes [#131](#131)
* **docs:** rubric v2 and a brief that demands the components beyond Bulma ([e6047be](e6047be))
* **docs:** show all posts in the blog sidebar ([d29e9c6](d29e9c6))
* **form:** add Datepicker, Timepicker, and Datetimepicker components ([c6684e6](c6684e6))

### Performance Improvements

* **bestax-mcp:** stop get_helper_props costing half the session ([b865651](b865651))
* **docs:** defer live previews until they scroll into view ([d6bf87b](d6bf87b))
* **docs:** share one parsed stylesheet set across every live preview ([feb993a](feb993a))

### BREAKING CHANGES

* **bestax-migrate:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1. This applies to the runtime the codemod executes
on, not to the app being migrated.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **create-bestax:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* **deps:** create-bestax now requires Node.js 22 or newer. Node 18 and 20
are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh

* feat(bestax-migrate): require Node 22 and take chalk 6

chalk 6 drops support for Node below 22. The API surface this package uses is
unchanged, so no calling code changes.

The version guard in src/index.ts moves ahead of every import and no longer
depends on anything: import declarations are hoisted and evaluated before any
statement in the module, and chalk 6 itself requires Node >= 22, so a static
import would fail to load on exactly the runtimes the guard exists to catch.
./cli.js is now imported dynamically for the same reason.

@babel/parser deliberately stays on 7.x. Babel 8 removes the
`deprecatedImportAssert` plugin with no replacement, and this package parses
the legacy `import x from 'y' assert { type: 'json' }` form on purpose — a
codemod that migrates older codebases must not crash on the syntax those
codebases still contain. There is a regression test for it ("parses the legacy
import-assert syntax"), which Babel 8 fails outright. jscodeshift 17 bundles
its own Babel 7 regardless, so staying on 7 also keeps a single parser in the
tree rather than two.
* **deps:** bestax-migrate now requires Node.js 22 or newer. Node 18 and
20 are both past end-of-life. Running it on an older runtime prints an explicit
upgrade message and exits 1.

Claude-Session: https://claude.ai/code/session_01TGA6sFTUGsJ6oXhfpjKEnh
* footer requirement, and the commitlint scope rule
- CONTRIBUTING.md: replace the type-less commit example with a
  commitlint-valid conventional format (verified against commitlint);
  correct all four coverage mentions to the real jest thresholds
  (bulma-ui 99%, create-bestax 95%/78% branches); fix the npm package
  name (@allxsmith/bestax-bulma, plus create-bestax) and link VERSIONING.md
- CLAUDE.md: remove the stale-docs warning and asides now that the
  underlying docs are correct; point at VERSIONING.md again

Closes #206.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0131uD6QKmAij7Byk3SByyLh
* the @allxsmith/bestax-bulma/versions/bestax-bulma-prefixed.css
export is removed. Use versions/bestax-prefixed.css with classPrefix="bestax-".
* **bulma-ui:** React 16 and 17 are no longer supported; the minimum
supported React version is now 18.
* **bulma-ui:** Snackbar has been removed and merged into Toast; use Toast
with its positioning and queue props instead.
* **bulma-ui:** form controls now auto-wrap in Field/Control, and Checkbox
and Radio ship new themed visuals. See the 2.x -> 3.x migration guide.
* This version requires Node.js 18.0.0 or higher. The CLI now enforces this requirement and will exit with an error message if running on older Node.js versions. This aligns create-bestax with the bestax-bulma v2.x ecosystem.

* fix(create-bestax): correct Prettier formatting in index.ts
* None - all changes are additive and backward compatible
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant