Repository navigation
docs(bulma-ui): generate the components and form API pages (4/4) - #423
Conversation
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
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
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
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
|
Warning Review limit reached
Next review available in: 30 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (94)
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. Comment |
|
@coderabbitai full review Generated by Claude Code |
|
✅ Action performedFull 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 59 minutes. |
`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
`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
`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
`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
| | `children` | `React.ReactNode` | — | Navbar content (compose with subcomponents). | | ||
| | `...` | All standard HTML attributes and Bulma helper props | — | See [Helper Props](../helpers/usebulmaclasses.md) | | ||
|
|
||
| **Subcomponents:** |
There was a problem hiding this comment.
Generated Subcomponents: list is incomplete and now duplicated by a leftover hand-written block — 🟡 Minor · Correctness
What: The generated **Subcomponents:** list (inside the bestax:generated props markers) enumerates only subcomponents that have a dedicated *Props interface. Navbar.Divider is attached via withSubComponents (Navbar.tsx:566) but typed inline as React.FC<React.HTMLAttributes<HTMLHRElement>> with no NavbarDividerProps — so props-extract.mjs's if (!decl) branch (scripts/lib/props-extract.mjs:929) continues past it, dropping it from both the list and the tables. The old hand-written list was re-added outside the marker as **Key Subcomponents:** (line 579), which still lists Navbar.Divider — so the page now renders two near-identical subcomponent lists, one complete, one not.
Why it matters: Readers see redundant lists on the published page, and the generated (canonical) list silently omits a real subcomponent. This is not navbar-only:
pagination.md— generated list dropsPagination.Ellipsis(same inline-typed cause); duplicate**Subcomponents:**block below the markers.panel.md— no missing sub, but the same duplicate**Subcomponents:**block persists outside the markers.
If the redundant outside-marker blocks are later cleaned up (the natural next step), the Navbar.Divider / Pagination.Ellipsis docs disappear entirely, since the generator never emits them.
Fix: Two coupled gaps — pick per the fix agent's scope:
- Remove the stale hand-written
**Key Subcomponents:**/**Subcomponents:**blocks left outside thebestax:generated propsmarkers onnavbar.md,panel.md, andpagination.md(they duplicate the generated region). - Have the extractor still list inline-typed subcomponents (a name + summary row with no prop table, or a catch-all "all standard
<hr>/<li>attributes" row) soNavbar.DividerandPagination.Ellipsissurvive in the generated list rather than being silently dropped.
Evidence
navbar.md generated list (9 subs) omits Navbar.Divider; leftover Key Subcomponents (line 579) lists 10 incl. Navbar.Divider.
pagination.md generated list: Link/List/Previous/Next (4); leftover list: List/Link/Ellipsis/Previous/Next (5).
props-extract.mjs:929 — if (_depth === 0 && impl !== name && mods.has(impl)) only recovers subs declared in another barrel module; a same-module inline-typed sub falls through to continue.
There was a problem hiding this comment.
Deep review — 1 blocking · 1 advisory
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🟡 Minor | Correctness | Generated Subcomponents: list omits inline-typed subs (Navbar.Divider, Pagination.Ellipsis) and a duplicate hand-written list survives outside the markers on navbar/panel/pagination |
docs/docs/api/components/navbar.md:464, pagination.md, panel.md |
| 2 | 🔵 Advisory | Robustness | Discriminated-union pages surface awkward merged type cells (minDistance: never | number) — correct for a catalogue, but reads oddly |
docs/docs/api/form/slider.md:350 |
Overall: This is a clean, mechanical docs-generation PR — the only runtime-adjacent change is 12 lines adding components/form to the ordered/managed category sets in api-sources.mjs; everything else is TSDoc comment rewrites (verified comment-only, no logic touched) and generated markdown. I spot-verified the high-risk accuracy claims and they hold: editable defaults to true on timeinput.md matching TimeInputBase.tsx:184; Slider's body-destructured defaults (min=0, max=100, step=1, disabled=false) all render correctly; the Control/Slider alias-union pages now emit full tables; nested datetime/ pages correctly use ../../helpers/ link depth and the targets exist. The riskiest and only real defect is the subcomponent-enumeration gap in finding #1 — the human should confirm the intended cleanup (remove the stale outside-marker blocks) doesn't drop Navbar.Divider/Pagination.Ellipsis in the process.
Residual risk:
- Parity loss elsewhere: could not run
check-docs-parity/generator (node execution gated in this sandbox), so the "0 losses" claim is unverified empirically. Refuted by manual spot-check of the two hand-edited pages (tabs.md,taginput.md) — prose between prop tables was folded into subcomponent summary rows with no dropped content — plus per-page accuracy checks above. The one systematic gap I did find (inline-typed subs) is filed as #1. - Wrong defaults propagating to published docs: refuted by cross-checking source destructured defaults against the rendered Default column for Slider and TimeInput.
- Broken relative links from the new nested pages: refuted —
datetime/*pages use../../helpers/*and all three targets (valid-values,usebulmaclasses,theme) exist.
🏄 Chill migration, dude — 87 pages riding one clean wave now, defaults and links all dialed in. Just a couple of gnarly duplicate subcomponent lists washed up on the navbar/panel/pagination beach; sweep those and it's smooth glass all the way to merge. 🤙
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
`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
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
…ts 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
Review — all 41 components + form pages verified against main in a browserBuilt The high-risk claims in the description all hold. Fixed and pushed here
Five fabricated Duplicate content removed. Section order restored to Accessibility → Related on Verified after: generator idempotent, 10/10 conformance, Remaining — @claude please pick these up
The remaining generator-level items (catch-all understating Generated by Claude Code |
|
Claude encountered an error after 6m 24s —— View job Working on the 5 remaining items
Investigating each now.
|
… 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>
|
@coderabbitai full review Generated by Claude Code |
|
✅ Action performedFull review finished. |
`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
`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
`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
**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
**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
There was a problem hiding this comment.
Deep review — 0 blocking · 2 advisory
| # | Severity | Area | Finding | Location |
|---|---|---|---|---|
| 1 | 🔵 Advisory | Robustness | Generator idempotency (gen:api-docs:check) could not be run in this session — the write-capable node script needed approval, so it was verified only by CI, not locally. |
scripts/gen-api-docs.mjs |
| 2 | 🔵 Advisory | Correctness | A few source TSDoc rewrites are slightly less precise than what they replaced — e.g. SliderMark.value went from "Position of the mark along the slider range." to "Controlled value.", and single-mode minDistance?: never gained a range-only "@DefaultValue 0" note. Not surfaced on the rendered pages (union merge picks the range description; the marks column shows the nested type), so IDE-tooltip-only. |
bulma-ui/src/form/Slider.tsx:38 |
Overall: The change is sound. I confirmed the load-bearing claim empirically: every bulma-ui/**/*.tsx edit is TSDoc-comment-only — no function body, default, or JSX was touched, so "no runtime behaviour changes" holds (Slider/TimeInputBase defaults were read by the extractor, not moved). The generated pages spot-check correctly against source: TimeInput's editable now reads true (matches TimeInputBase.tsx:184, and the thin wrapper passes it through untouched), ControlProps/SliderProps unions expand into full tables with as/ref merged, columns.md gap is an inline literal union rather than a bare BulmaGapValue, avatar.md size inlines its lookup type, and **Subcomponents:** renders summary sentences while Columns.Column links to its own page. The riskiest surface is the sheer volume (87 pages, 1083 props) — the human should trust the parity/wording gates the PR cites rather than re-reading every row, and glance at the two hand-edited pages (tabs.md, taginput.md) where prose was relocated out of the generated region.
Residual risk:
- Silent content loss across 41 regenerated pages — refuted: the union/intersection extractor now emits every prop it mentions (verified on
Control= 20 rows +as/ref), and the PR gates this withcheck-docs-parity(0 losses) pluscheck-docs-wording(1083 props reconciled, 1 comma change). The two pages with prose between tables were hand-lifted into standalone sections (taginput.mdForm Submission table sits outside the generated markers, coherent). - Wrong defaults leaking from base modules — refuted:
TimeInput → TimeInputBasedefault resolution checked end-to-end;editable=trueis correct and the wrapper does not override it. - Regenerating produces a diff (non-idempotent, would break CI) — could not be reproduced locally (script needed approval); left as advisory #1, but
gen:api-docs:checkis a required gate on this PR so a non-idempotent generator would fail CI, not merge silently.
🏄 Forty-one more pages rolled in clean, dude — all comment-tweaks up top, zero runtime chop underneath, and the tables set down smooth. Idempotency wave I couldn't paddle out to myself, but CI's already on that break. Ship it.
Resolves the conflict in `scripts/check-conformance.mjs`, and restores a conformance check this branch had deleted. `style-mapping-sync` (#350) — the check that pins the inline-style → helper-prop mapping across its three deliberate copies and verifies every prop it names really exists in `bulma-ui/src` — is present at this branch's own merge base (69f9fe7) and on main, but absent from the branch: the header entry, the `MAPPING_FILES` constant, `checkStyleMappingSync` and its `CHECKS` registration were all dropped. Nothing in the PR description mentions removing it, and merging as-is would have deleted a live gate from the repo. Restored verbatim from main. The visible conflict was only the header comment block, where main had added `style-mapping-sync` and `publishable-manifests` entries — the deletion itself merged silently, which is why it was worth looking past the conflict markers. Also picks up main's `publishable-manifests` check (#412) and #442's test-deploy split. All twelve conformance checks now run and pass; the two removals that remain against main are deliberate and documented: `basename` (now unused) and `registerVarsKeys`, which moved to `scripts/lib/scss-vars.mjs` so the API-docs generator shares one SCSS parser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019XkrYiLeAnyLBSa3KSweWJ
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
|
@claude deep review again — head is now Since your review:
Your advisory 2 last round is still open and I'd like your read on whether it's worth acting on: Please focus on:
On advisories: keep them short and actionable. Please skip notes that only record what the sandbox stopped you executing — I run the full gate locally every round (generator idempotence twice, twelve conformance checks, catalog gate, Generated by Claude Code |
|
Claude encountered an error after 11m 48s —— View job Deep review in progress (head
|
|
@claude one correction before you start — you are not actually blocked from executing things.
Use the aliases:
Your last two reviews both logged generator idempotency as unverifiable residual risk and deferred it to CI — Only If a command still gets denied, name it and I'll get it allowlisted. Generated by Claude Code |
|
Claude finished @allxsmith's task in 6m 29s —— View job Deep review — head
|
| Component / prop | Published (wrong) | Source truth | Origin |
|---|---|---|---|
Tabs.expanded — docs/docs/api/components/tabs.md:454 |
"Makes tabs take up the full width equally." | Tabs.tsx:156: 'is-expanded': hasContent && vertical && expanded — only applies when vertical + has Tabs.Content; _tabs.scss:110-114 sets height: 100% on the tab list. It's a vertical-mode height fill, not horizontal width distribution (that's the separate fullwidth prop). Old comment (pre-migration): "Vertical tabs fill full height." |
Pre-existing on hand-written page |
Input/InputBase.isStatic — docs/docs/api/form/input.md:616 (+ prose at :152) |
"Renders input as static (read only, styled)." | InputBase.tsx:78: 'is-static': isStatic — a CSS-only class (Bulma: transparent background/border, no box-shadow). Never touches readOnly — the field stays fully editable unless the caller also passes readOnly/disabled. Old comment: "Renders the input as static text." |
Pre-existing on hand-written page |
Numberinput.bare — docs/docs/api/form/numberinput.md:318 |
"Removes button borders and background." | Numberinput.tsx:162,426: effectiveBare only switches to a display: contents wrapper (skips the outer .field), for composing inside a parent Field. buttonClasses (line 269) never reads bare. Old comment: "Bare mode: no outer .field wrapper, for composing inside a parent Field." |
Pre-existing on hand-written page |
Numberinput.exponential — docs/docs/api/form/numberinput.md:323 |
"Enables exponential step increments when holding buttons." | Numberinput.tsx:202-205: getEffectiveStep() scales step by the current value's magnitude on each discrete onClick — there's no press-and-hold handler anywhere in the file (no onMouseDown/setInterval). Old comment: "Whether the step grows with the value magnitude." |
Pre-existing on hand-written page |
Fix is the same shape as Level.isMobile: restore each prop's pre-migration comment text at the .tsx source (the table then regenerates correctly).
One more — same family as your round-1 @extraProp/dead-prop findings
Autocomplete.dropdown — bulma-ui/src/form/Autocomplete.tsx:65, published at docs/docs/api/form/autocomplete.md:445 as "Render as dropdown style." The prop is declared but never destructured anywhere in the component (Autocomplete.tsx:126-165 destructures everything else); it falls through ...props → useBulmaClasses → rest → spread onto the root <div> (Autocomplete.tsx:424), so <Autocomplete dropdown> renders a stray dropdown="true" DOM attribute and does nothing else. useBulmaClasses has no dropdown handling either. Pre-existing (unchanged text since before the migration, confirmed against 0c48b74) — same failure mode as the Rate/Carousel/DateTimeInput findings you already fixed this round, just missed because it's in form/, not components/.
Sweep coverage
All 41 components/form pages checked, one prop at a time, against their .tsx source and (where the prop maps to a real class) Bulma's own SCSS in node_modules/bulma/sass/. 5 confirmed defects total out of ~1,083 props — the 4 above plus the previously-flagged SliderMark.value. Everything else — Level, Dialog, Loading, Menu's nested-list class scoping, Modal.Close's floating-only sizing, Breadcrumb (byte-checked against Bulma's own breadcrumb.scss), Switch.isRtl, Taginput, DateInput/TimeInput/DateTimeInput polarity (12h/24h, nearbyMonthDays), Field/Control/File addon and icon classes, Select, Radio/Radios, Rate — checked out accurate.
· branch claude/api-docs-components-form
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
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
…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>
…#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>
* 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>
# Conflicts: # scripts/lib/api-sources.mjs
…nded 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
Preview DeploymentPreview URL: https://8def892e.bestax.pages.dev |
|
🎉 This PR is included in version 4.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 2.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 5.8.1 🎉 The release is available on: Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.0.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |

Pull Request
Description
The last 41 pages —
components(22) andform(19) — completing the migration. All 87 pages now share one section order and one table shape.Four extractor fixes this pair forced, each surfaced by
check-docs-parity:ControlPropsandSliderPropsare unions, and treating a props type as necessarily an interface rendered both pages with no table at all — 42 props. Unions and intersections now contribute every prop they mention, which is what a catalogue should do.Slider,TimeInputBase), and defaults belonging to a base module a thin wrapper renders (TimeInput→TimeInputBase).editableread asfalseontimeinput.mdwhere the source saystrue.(typeof xs)[number]andAvatarProps['size']lookups); long ones keep the name and get a**Types:**footnote built from the alias's own TSDoc.columns.md'sgaphad regressed fromnumber | string (0-8)to a bareBulmaGapValue.**Subcomponents:**renders each sub's summary sentence, which the hand-written pages carried ("Top bar for navigation or branding") and a bare name list dropped. A sub-component with its own page is linked rather than restated, soColumns.Columndoes not duplicatecolumn.md's 25 rows.Two pages needed a hand edit first:
components/tabs.mdandform/taginput.mdhad prose sitting between prop tables, where the marker design has nowhere to put it.Verification, across all 87 pages
check-docs-parity— 0 losses: no prop, default, description word, code span, URL, live example or prose line dropped.check-docs-wording, of 1083 previously-documented props:form/file.mdlabel, where I added a commapnpm allgreen; generator idempotentRelated Issue(s)
Refs #399
Type of Change
Affected packages:
@allxsmith/bestax-bulma) — comments only@allxsmith/bestax-docs)Checklist
Additional Context
The wording rule, worth knowing before reviewing descriptions. The merge originally preferred whichever text had the larger word set, which let a stale source comment overwrite a hand-corrected page — "Offset for mobile." became "Mobile column offset." on six rows, and 114 props changed that way. The page is now authoritative for any prop it already documented; the comment only wins when it fully contains the page's sentence. Where they disagree the codemod prints what it discarded, so the difference is reviewed rather than lost.
CSS variables. Six components had none where they should have:
Skeleton,Code,Pre,Strong,DeleteandColumn. Bulma keeps their variables insass/baseandsass/utilities(which the scan skipped), registers them at$variables-hostor inside a@mixin(not on the component's own selector), and the semantic wrappers own a variable family without rendering a Bulma class at all. Host registrations are claimed by key prefix, which keepsbody-*andhr-*out ofCode's table.Blockgenuinely has none —block.scssis four lines that@extend %block.Generated by Claude Code