Skip to content

docs(bulma-ui): generate the elements API pages (3/4) - #422

Merged
allxsmith merged 33 commits into
mainfrom
claude/api-docs-elements
Jul 31, 2026
Merged

allxsmith merged 33 commits into
mainfrom
claude/api-docs-elements

Conversation

@allxsmith

Copy link
Copy Markdown
Owner

Pull Request

3 of 4, stacked on #421. Merge #420 → #421 → this. The diff shown here is incremental.

Description

30 pages. Two fixes to the extractor that this category forced — both found by check-docs-parity, not 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. An imported base arrives as an alias symbol whose declarations are the import specifier, not the interface, so getAliasedSymbol() is now followed.
  • Table's six sub-components are imported rather than declared locally, so the page rendered no sub-tables at all and Thead/Tbody/Tfoot/Tr/Th/Td's props vanished. Cross-module subs now resolve through the barrel.

Props the old tables documented but no interface declares — href/target/rel on Link, value on ListItem, the <ol> attributes on OrderedList, skeleton from the helper props — are parked as @extraProp, carrying their type and default. Lossless by construction, and it puts the text in the source where the rest of it now lives.

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

Verification

  • check-docs-parity — 0 losses across all 87 pages
  • check-docs-wording, restricted to these pages: of 215 previously-documented props, 178 read identically and 37 keep their sentence with more appended. None replaced.
  • pnpm all green

Related Issue(s)

Refs #399

Type of Change

  • Documentation
  • Bug fix
  • New feature
  • Refactor
  • Performance
  • Build tooling

Affected packages:

  • bulma-ui (@allxsmith/bestax-bulma) — comments only, plus the Button JSDoc relocation
  • docs (@allxsmith/bestax-docs)
  • create-bestax

Checklist

  • My code follows the project style guidelines
  • I have performed a self-review of my code
  • All new and existing tests passed (comments only — no runtime behaviour changes)
  • I have added/updated documentation as needed
  • I have added tests that prove my fix is effective — n/a
  • I have updated Storybook stories as needed — n/a
  • Any relevant dependencies are updated — none

Additional Context

elements/block.md is the page that exposed why a phased rollout is worse than either order applied consistently — it still had Props near the top while layout/ had moved it down. From this PR its category is uniform with the rest.


Generated by Claude Code

claude added 3 commits July 30, 2026 00:08
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
@coderabbitai

coderabbitai Bot commented Jul 30, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@allxsmith, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 46 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 83df9e80-eaf3-4ed8-bb71-3e00865cc5aa

📥 Commits

Reviewing files that changed from the base of the PR and between 21ea3f6 and 28d9664.

📒 Files selected for processing (67)
  • bulma-ui/src/elements/Block.tsx
  • bulma-ui/src/elements/Box.tsx
  • bulma-ui/src/elements/Button.tsx
  • bulma-ui/src/elements/Buttons.tsx
  • bulma-ui/src/elements/Code.tsx
  • bulma-ui/src/elements/Content.tsx
  • bulma-ui/src/elements/Delete.tsx
  • bulma-ui/src/elements/Divider.tsx
  • bulma-ui/src/elements/Emphasis.tsx
  • bulma-ui/src/elements/Figure.tsx
  • bulma-ui/src/elements/Icon.tsx
  • bulma-ui/src/elements/IconText.tsx
  • bulma-ui/src/elements/Image.tsx
  • bulma-ui/src/elements/Link.tsx
  • bulma-ui/src/elements/LinkButton.tsx
  • bulma-ui/src/elements/ListItem.tsx
  • bulma-ui/src/elements/Notification.tsx
  • bulma-ui/src/elements/OrderedList.tsx
  • bulma-ui/src/elements/Paragraph.tsx
  • bulma-ui/src/elements/Pre.tsx
  • bulma-ui/src/elements/Progress.tsx
  • bulma-ui/src/elements/Skeleton.tsx
  • bulma-ui/src/elements/Span.tsx
  • bulma-ui/src/elements/Strong.tsx
  • bulma-ui/src/elements/SubTitle.tsx
  • bulma-ui/src/elements/Table.tsx
  • bulma-ui/src/elements/Tag.tsx
  • bulma-ui/src/elements/Tags.tsx
  • bulma-ui/src/elements/Tbody.tsx
  • bulma-ui/src/elements/Td.tsx
  • bulma-ui/src/elements/Tfoot.tsx
  • bulma-ui/src/elements/Th.tsx
  • bulma-ui/src/elements/Thead.tsx
  • bulma-ui/src/elements/Title.tsx
  • bulma-ui/src/elements/Tr.tsx
  • bulma-ui/src/elements/UnorderedList.tsx
  • docs/docs/api/elements/block.md
  • docs/docs/api/elements/box.md
  • docs/docs/api/elements/button.md
  • docs/docs/api/elements/buttons.md
  • docs/docs/api/elements/code.md
  • docs/docs/api/elements/content.md
  • docs/docs/api/elements/delete.md
  • docs/docs/api/elements/divider.md
  • docs/docs/api/elements/emphasis.md
  • docs/docs/api/elements/figure.md
  • docs/docs/api/elements/icon.md
  • docs/docs/api/elements/icontext.md
  • docs/docs/api/elements/image.md
  • docs/docs/api/elements/link.md
  • docs/docs/api/elements/linkbutton.md
  • docs/docs/api/elements/listitem.md
  • docs/docs/api/elements/notification.md
  • docs/docs/api/elements/orderedlist.md
  • docs/docs/api/elements/paragraph.md
  • docs/docs/api/elements/pre.md
  • docs/docs/api/elements/progress.md
  • docs/docs/api/elements/skeleton.md
  • docs/docs/api/elements/span.md
  • docs/docs/api/elements/strong.md
  • docs/docs/api/elements/subtitle.md
  • docs/docs/api/elements/table.md
  • docs/docs/api/elements/tag.md
  • docs/docs/api/elements/tags.md
  • docs/docs/api/elements/title.md
  • docs/docs/api/elements/unorderedlist.md
  • scripts/lib/api-sources.mjs

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

❤️ Share

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

Copy link
Copy Markdown
Owner Author

@coderabbitai full review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

@allxsmith I will perform a complete review of pull request #422, including the incremental changes in this stack.

✅ Action performed

Full review finished.


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

allxsmith and others added 3 commits July 31, 2026 02:00
`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
Comment thread docs/docs/api/elements/table.md Outdated
Comment on lines +351 to +388
### Thead / Tbody / Tfoot

| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `children` | `ReactNode` | — | Section content (typically `Tr` rows). |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

### Tr

| Prop | Type | Default | Description |
| ------------ | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `isSelected` | `boolean` | — | Adds Bulma's `is-selected` class. |
| `color` | `TableColor` | — | Bulma color modifier for the row. |
| `children` | `ReactNode` | — | Row content (typically `Th`/`Td`). |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

### Th

| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `isAligned` | `'left' \| 'right' \| 'centered'` | — | Text alignment. |
| `width` | `string \| number` | — | Cell width (e.g., `'100px'`, `100`). |
| `color` | `TableColor` | — | Bulma color modifier for the header cell. |
| `children` | `ReactNode` | — | Header cell content. |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

### Td

| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `color` | `TableColor` | — | Bulma color modifier for the cell. |
| `children` | `ReactNode` | — | Cell content. |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate sub-component prop tables render on the published Table page — 🟡 Minor · Correctness

What: The generated props region already emits full tables for every sub-component (Table.Thead, Table.Tbody, Table.Tfoot, Table.Tr, Table.Th, Table.Td) inside the marker pair. These hand-written ### Thead / Tbody / Tfoot, ### Tr, ### Th, ### Td tables sit below the `` marker, so per the region rules (docs/CLAUDE.md: "prose below the closing marker is preserved") they are kept verbatim — producing a second, stale copy of the same props under the single `## Props` section.

Why it matters: Readers see each cell/row/section documented twice, and the two copies disagree — e.g. generated Table.Tr gives isSelected a default of false (line 312) while this copy shows — (line 364); the generated Table.Th isAligned description is fuller than the duplicate's "Text alignment." The duplicate is unmanaged, so it silently drifts as the interfaces change. check-docs-parity/conformance treat extra content as non-losses, so this passed CI.

Fix: Delete the stale hand-written tables; the generated block is the source of truth.

Suggested change
### Thead / Tbody / Tfoot
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `children` | `ReactNode` | — | Section content (typically `Tr` rows). |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |
### Tr
| Prop | Type | Default | Description |
| ------------ | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `isSelected` | `boolean` | — | Adds Bulma's `is-selected` class. |
| `color` | `TableColor` | — | Bulma color modifier for the row. |
| `children` | `ReactNode` | — | Row content (typically `Th`/`Td`). |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |
### Th
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `isAligned` | `'left' \| 'right' \| 'centered'` | — | Text alignment. |
| `width` | `string \| number` | — | Cell width (e.g., `'100px'`, `100`). |
| `color` | `TableColor` | — | Bulma color modifier for the header cell. |
| `children` | `ReactNode` | — | Header cell content. |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |
### Td
| Prop | Type | Default | Description |
| ----------- | ----------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `color` | `TableColor` | — | Bulma color modifier for the cell. |
| `children` | `ReactNode` | — | Cell content. |
| ... | All standard props and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |
Note: same defect on figure.md

figure.md has the identical issue — a ### Figure.Caption Props table below the close marker duplicating the generated Figure.Caption table. Fixed in a separate comment there.

Comment thread docs/docs/api/elements/figure.md Outdated
Comment on lines +186 to +194
### Figure.Caption Props

| Prop | Type | Default | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `textColor` | `'primary'` \| `'link'` \| `'info'` \| `'success'` \| `'warning'` \| `'danger'` \| `'black'` \| `'black-bis'` \| `'black-ter'` \| `'grey-darker'` \| `'grey-dark'` \| `'grey'` \| `'grey-light'` \| `'grey-lighter'` \| `'white'` \| `'light'` \| `'dark'` \| `'inherit'` \| `'current'` | — | Text color helper. |
| `bgColor` | `'primary'` \| `'link'` \| `'info'` \| `'success'` \| `'warning'` \| `'danger'` \| `'black'` \| `'black-bis'` \| `'black-ter'` \| `'grey-darker'` \| `'grey-dark'` \| `'grey'` \| `'grey-light'` \| `'grey-lighter'` \| `'white'` \| `'light'` \| `'dark'` \| `'inherit'` \| `'current'` | — | Background color helper. |
| `children` | `React.ReactNode` | — | Caption text to render. |
| ... | All standard `<figcaption>` and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicate Figure.Caption prop table renders on the published Figure page — 🟡 Minor · Correctness

What: The generated props region already documents Figure.Caption in full (lines 174–182, inside the marker pair). This hand-written ### Figure.Caption Props table sits below ``, so it's preserved verbatim and appears as a second, unmanaged copy under the same ## Props heading.

Why it matters: The caption's props are shown twice with divergent detail (the generated copy links to the shared Bulma-color type; this copy inlines the full color union and different descriptions), and the stale copy will drift as FigureCaptionProps changes. Same root cause as the table.md duplication — the migration didn't remove the pre-existing manual sub-component tables when the generator began emitting them.

Fix: Delete the stale table (and its ### Figure.Caption Props heading); the generated block owns it.

Suggested change
### Figure.Caption Props
| Prop | Type | Default | Description |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------ |
| `className` | `string` | — | Additional CSS classes. |
| `textColor` | `'primary'` \| `'link'` \| `'info'` \| `'success'` \| `'warning'` \| `'danger'` \| `'black'` \| `'black-bis'` \| `'black-ter'` \| `'grey-darker'` \| `'grey-dark'` \| `'grey'` \| `'grey-light'` \| `'grey-lighter'` \| `'white'` \| `'light'` \| `'dark'` \| `'inherit'` \| `'current'` | — | Text color helper. |
| `bgColor` | `'primary'` \| `'link'` \| `'info'` \| `'success'` \| `'warning'` \| `'danger'` \| `'black'` \| `'black-bis'` \| `'black-ter'` \| `'grey-darker'` \| `'grey-dark'` \| `'grey'` \| `'grey-light'` \| `'grey-lighter'` \| `'white'` \| `'light'` \| `'dark'` \| `'inherit'` \| `'current'` | — | Background color helper. |
| `children` | `React.ReactNode` | — | Caption text to render. |
| ... | All standard `<figcaption>` and Bulma helper props | | (See [Helper Props](../helpers/usebulmaclasses)) |

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deep review — 2 blocking · 0 advisory

# Severity Area Finding Location
1 🟡 Minor Correctness Generated sub-component prop tables are duplicated by stale hand-written tables left below the props close marker docs/docs/api/elements/table.md:351
2 🟡 Minor Correctness Same defect: Figure.Caption props table duplicated below the close marker docs/docs/api/elements/figure.md:186

Overall: The change is sound. All 36 .tsx edits are provably comment-only — the single non-comment line is the semantically-identical relocation of const validButtonColors in Button.tsx, so there is zero runtime risk. The extractor claims (Omit/alias heritage, cross-module Table subs) live in the stacked base, and their downstream effect verifies here: LinkButton now tables its 14 inherited props and the Button summary reaches its page. The one real issue is on the two sub-component pages (Table, Figure): the generator now emits the sub-component tables inside the markers, but the pre-existing manual sub-component tables below the close marker were never removed, so each renders twice — with disagreeing values (e.g. Table.Tr isSelected default false vs —). The human should focus there; deleting the four stale Table sections and the one stale Figure.Caption section resolves it. pnpm all and check-docs-parity do not catch this because extra content is not a "loss."

Residual risk:

  • Other pages carrying the same duplication? — Refuted. Swept all 30 element pages for prop-table headers: every page has exactly 1 except table.md (11) and figure.md (3), both flagged; no page has a stray table above the open marker either.
  • A .tsx edit that silently changes behavior? — Refuted. Filtered every added/removed non-comment line across all 36 files; the only hit is the validButtonColors move, which is order-independent (the const is used later, initializer unchanged).
  • Skills catalog gone stale from the changed Overview sentences? — Refuted. The committed component-catalog.md already carries the new one-liners for all sampled components (Button, Link, Table, Content, OrderedList, SubTitle, Skeleton); tree is clean, so gen:catalog:check stays green.

🏄 Clean set, brah — 30 pages roll in smooth and the source edits are pure comment foam, no runtime undertow. Just two gnarly double-exposures on the Table and Figure breaks where the old tables did not wipe out. Sweep those duplicates and this one is totally cruisable.

allxsmith and others added 3 commits July 31, 2026 02:36
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

Copy link
Copy Markdown
Owner Author

Review — all 30 elements pages verified against main in a browser

Built origin/main and the stack tip locally and compared every page in Chromium (bestax.io and the previews are unreachable from this session, and stacked PRs get no preview deploy).

Zero prop losses across all 30 pages, and every @extraProp in bulma-ui/src/elements/ is runtime-real — I rendered each one: Link href/target/rel → <a href="/x" target="_blank" rel="noopener">, ListItem value → <li value="7">, OrderedList type/start/reversed → <ol type="a" start="3" reversed>, Title/SubTitle skeleton → class="title is-skeleton". linkbutton.md resolves extends Omit<ButtonProps, …> correctly — the three omitted props are absent and color is narrowed to the 10-member override. The 19-member colour union is gone from every generated table. Live-example counts match main page-for-page (button 23/23, table 7/7, icon 11/11), all mounting into shadow roots with no NEW-only console errors.

Fixed and pushed here

  • table.md and figure.md still carried their pre-migration tables immediately after the generated block. table.md rendered 11 props tables where 7 are intended, with the two copies disagreeing — isSelected defaulted to false in the generated table and — in the legacy one, and the legacy copies used a bare TableColor with no explanation while the generated ones explain it. figure.md's stale Figure.Caption Props still dumped the 19-member colour union inline — precisely what this migration exists to delete. Both removed; the generated tables supersede them and keep the element names in their catch-all rows.
  • Three synthesized children rows for props the components don't render. divider.md was the serious one: Divider spreads onto <hr>, so the row documented the one thing React refuses — verified live, hr is a void element tag and must neither have children nor use dangerouslySetInnerHTML. icon.md's was silently discarded (Icon always supplies its own JSX children — render(<Icon name="star">HELLO</Icon>) drops HELLO), and delete.md's renders but lands text on top of Bulma's pseudo-element ✕.

The extractor gate that fixes this lives on #420; this branch carries the regenerated pages. 10/10 conformance, parity still 0 prop losses.

Remaining on these pages — @claude, details and the rest of the list are on #420

  • skeleton.md's catch-all links Helper Props, but SkeletonProps only extends React.HTMLAttributes and Skeleton never calls useBulmaClasses. Verified: <Skeleton m="4" textColor="primary" /> → <div class="skeleton-block" m="4" textcolor="primary"> plus a React unknown-prop error. Main said "Passed through to the root element", which was right.
  • Eight pages lost the concrete element name from the catch-all (<code>, <hr>, <em>, <figure>, <pre>, <progress>, <strong>). Three are a one-line fix — DOM_ELEMENT_LABELS has no entry for HTMLHRElement, HTMLPreElement or HTMLProgressElement.
  • icontext.md's items regressed from the self-describing { iconProps: IconProps, text?: string }[] to a bare IconTextItem[] with no footnote, and the interface isn't exported. tag.md handles the analogous TagColor correctly with a **Types:** block.

Generated by Claude Code

@claude

claude Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Claude finished @allxsmith's task in 6m 35s —— View job


Remaining items from the review — resolved 2 of 3, 1 needs a design call

Fixed and pushed (8a6e640)

Both were confirmed by reading the actual source, not just the pages:

  • DOM_ELEMENT_LABELS missing three entries. scripts/lib/props-extract.mjs had no key for HTMLHRElement, HTMLPreElement, or HTMLProgressElement, so Divider (extends React.HTMLAttributes<HTMLHRElement>), Pre, and Progress fell back to the generic HTML label in their catch-all row. Added all three — divider.md, pre.md, progress.md now say `<hr>`/`<pre>`/`<progress>` instead of generic HTML.
  • skeleton.md linked Helper Props it doesn't have. SkeletonProps extends React.HTMLAttributes<HTMLDivElement> only — no BulmaClassesProps, and Skeleton.tsx never calls useBulmaClasses. But gen-api-docs.mjs's catch-all row unconditionally rendered See [Helper Props](...) whenever any catch-all text existed, DOM-only or not. catchAllRow() in props-extract.mjs now returns { text, helpers } instead of a bare string, and the generator only emits the link when helpers is true. Verified this is the only elements page affected — every other component in the category does route through BulmaClassesProps.

Confirmed idempotent: re-running pnpm gen:api-docs after each change reports 0 updated. pnpm run lint, typecheck, format:check, and check:conformance are all green; the only lint warnings are pre-existing ones in Autocomplete.tsx/Slider.tsx/Taginput.tsx, unrelated to this change.

Not fixed here: icontext.md's items

Root cause confirmed: IconTextItem (bulma-ui/src/elements/IconText.tsx:14) is a plain interface, not a type alias, and it isn't exported from src/index.ts. The generator's footnote system (allAliases() in props-extract.mjs) only indexes type aliases — interfaces never enter it — so items: IconTextItem[] renders as a bare, undefined identifier with no **Types:** footnote, unlike TagColor on tag.md.

This is a capability gap in the shared extractor (would need allAliases() to also walk interface declarations, or IconTextItem to be redefined as an exported type alias) rather than a one-line data-table fix like the two above, and it isn't scoped to just this PR's pages — I didn't want to widen #422's footprint with a change to shared extractor logic that other stacked/future PRs also depend on. Recommend tracking it alongside the rest of the list on #420.

… 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>

Copy link
Copy Markdown
Owner Author

@coderabbitai full review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

@allxsmith I will perform a complete review of pull request #422, including its incremental changes.

✅ Action performed

Full review finished.


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

@allxsmith allxsmith added deep-review and removed deep-review labels Jul 31, 2026 — with Claude
allxsmith and others added 2 commits July 31, 2026 03:23
`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
allxsmith added a commit that referenced this pull request Jul 31, 2026
`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

Copy link
Copy Markdown
Owner Author

Picked up 8a6e640 — the DOM_ELEMENT_LABELS additions and the catchAll → {text, helpers} change are both right, and returning helpers rather than sniffing the rendered string is the better shape.

One stacking hazard worth flagging, now handled: scripts/lib/props-extract.mjs and scripts/gen-api-docs.mjs are shared by all four branches, so landing the fix here alone left #420 and #421 without it and made #423 revert it — the tip is stacked above this branch and carried the older file. I've applied the identical content to #420, #421 and #423 and regenerated; all four now hash-match on both files.

Regenerating on #423 turned up that the Helper Props mislink was wider than the elements pages — card, message, modal, navbar, pagination, panel, sidebar and tabs were mislinking it too, and are fixed by the same change.

General note for this stack: a fix to anything under scripts/ needs applying to every branch (or the stack rebasing), otherwise the tip silently reverts it and the merged result loses the fix.


Generated by Claude Code

allxsmith and others added 3 commits July 31, 2026 04:46
**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
**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
@allxsmith allxsmith added deep-review and removed deep-review labels Jul 31, 2026 — with Claude

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Deep review — 0 blocking · 2 advisory

# Severity Area Finding Location
1 🔵 Advisory Robustness namesChildren matches any children identifier in the impl (incl. an unrelated node.children access), so a future component could get a spurious synthesized children row scripts/lib/props-extract.mjs:299
2 🔵 Advisory Robustness controlled default-suppression is a broad /\bcontrolled\b/i text match — a boolean prop that merely mentions "controlled" in prose would silently lose its synthesized false default scripts/lib/props-extract.mjs:927

Overall: The change is sound. The extractor edits are careful, heavily commented with the exact regression each guards against, and — crucially — fail loud rather than silently degrading (the alias fixpoint throws instead of emitting bare names). I could not run node/pnpm in this sandbox, so I verified against the committed generated output instead: every claim in the PR body checks out against the real source. The riskiest surface is props-extract.mjs, where wrong-but-present output (as opposed to missing output) escapes the parity gate — so I traced the highest-leverage paths by hand (Omit/Pick heritage, cross-module subs, the catchAll.helpers split, namesChildren). All landed correctly. A human should focus first on props-extract.mjs and skim linkbutton.md/table.md/skeleton.md, which exercise the three trickiest new branches.

Residual risk:

  • A helper-props link wrongly dropped by the new structural isHelpers check. Refuted: of all regenerated element pages, exactly one (skeleton.md) lost "Bulma helper props", and Skeleton.tsx genuinely extends only React.HTMLAttributes<HTMLDivElement> — no useBulmaClasses. Every component that reaches helpers transitively (e.g. LinkButton via Omit<ButtonProps>) still shows the link.
  • A children row emitted for a void element (the Divider bug shape) elsewhere. Refuted: cross-checked all void-ish elements — Icon/Divider/Delete carry no children identifier and get no row; Progress/Image declare children as their own interface member (legitimate). No synthesized false positives in the set.
  • The alias fixpoint cutting off mid-resolution and degrading to bare names. Refuted: it throws on residual progress rather than degrading, the committed docs generated cleanly (they match the extractor), and repo chains settle in 2 rounds against a bound of 8.

🏄 Chill, clean set, brah — the extractor's paddling straight through gnarly Omit/alias breaks that used to wipe it out, and every wave I dropped in on came up glassy. Two little ripples on the horizon, but nothing that'll close out the lineup. Send it.

allxsmith and others added 4 commits July 31, 2026 05:03
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

Copy link
Copy Markdown
Owner Author

Round 4 — 0 blocking, and both advisories answered with measurements

Thanks for attacking the two things I asked you to attack. One held, one didn't, and I'd rather record both.

Refuted by your own check — isHelpers. You confirmed exactly one page (skeleton.md) lost "Bulma helper props", and that Skeleton.tsx genuinely extends only React.HTMLAttributes<HTMLDivElement> with no useBulmaClasses, while components reaching helpers transitively (LinkButton via Omit<ButtonProps>) keep the link. That's the right test and it's the answer I wanted — the structural predicate does what the substring test was supposed to.

Advisory 2, the controlled regex — measured, and it is currently exact. You were right to flag the heuristic class; I flagged it against myself for the same reason. So I counted rather than argued. Across all 87 pages, exactly three optional-boolean rows have controlled in their description:

page prop default description
components/collapse.md open — Controlled open state. If provided, component is controlled.
components/dropdown.md active — Whether the dropdown is open (controlled).
form/switch.md checked — Controlled checked state.

All three are genuinely controlled props, so there are zero false positives today. Two of them (collapse.open, dropdown.active) are restored to what main says — the synthesized false was the regression. switch.checked was already —.

The fragility you describe is real but latent: a future plain flag whose prose happens to say "controlled" would silently lose its false. I've left the heuristic in place because the alternative — an explicit opt-out tag — adds ceremony to three props, and the failure mode is a missing default rather than a wrong one. Worth revisiting if the count grows; recorded here so the next person has the number.

Advisory 1, namesChildren matching any children identifier (including an unrelated node.children access) — accepted as a real latent issue, same shape. Your own sweep found no false positive in the current set, and the containment is that it can only add a row the catch-all already covers, never drop one. Left as-is.

Also on this branch since your review: main had moved and the stack had gone stale. Merging main down surfaced that the stack had deleted the style-mapping-sync check (#350) entirely — silently, since only the header comment conflicted. Restored; all twelve checks now run and pass here.


Generated by Claude Code

Copy link
Copy Markdown
Owner Author

@claude deep review again — head is now 4789784, past the c29a6a2 you last read.

Since your review:

  • Your two advisories are answered rather than silently kept. The controlled heuristic: I counted instead of arguing, and it hits exactly three optional-boolean rows across all 87 pages — collapse.open, dropdown.active, switch.checked — all genuinely controlled, zero false positives, and two of them restored to what main says. namesChildren matching any children identifier: accepted as latent, contained by the fact that it can only add a row the catch-all already covers, never drop one. Both left in place, both recorded.
  • Main was merged down the stack, and that surfaced a deleted conformance check — style-mapping-sync ([Feature] create-bestax: the "never inline style={{}}" rule needs a concrete style-to-helper-prop mapping #350) had been removed from the stack outright and merged silently. Restored; twelve checks now run and pass here.

Please focus on:

  1. Your own refutation, re-checked under the merge. You confirmed skeleton.md is the only page that correctly loses "Bulma helper props" and that LinkButton keeps it via Omit<ButtonProps>. The merge brought main's check-conformance.mjs changes in — please confirm that still holds on the merged tree.
  2. The elements pages after the merge — everything regenerated, so they should be a fixed point.
  3. Description accuracy, the failure class that produced the one blocking finding on docs(bulma-ui): generate the layout, columns and grid API pages (2/4) #421: Level.isMobile was documented backwards (pre-existing on main, laundered into the shipped .d.ts by the migration). The same risk applies to every behavioural prop on the elements pages. Sibling-comparison against Bulma's SCSS is the check that caught it.

On advisories: short and actionable, please. Skip anything that only records what the sandbox blocked from running — I run the full gate locally each round and report it, so those notes displace signal.


Generated by Claude Code

@claude

claude Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Claude encountered an error after 7m 3s —— View job


Deep review in progress — head 4789784

  • Confirm skeleton.md/helper-props refutation still holds on the merged tree (check-conformance.mjs)
  • Verify elements pages are a fixed point after the merge (pnpm gen:api-docs, diff check)
  • Check description accuracy across elements pages — sibling-comparison against Bulma SCSS for behavioural props (the Level.isMobile failure class)
  • Post findings

Copy link
Copy Markdown
Owner Author

@claude one correction before you start — you are not actually blocked from executing things.

claude-review.yml runs pnpm install --frozen-lockfile before handing over, so node_modules is present, and its --allowedTools grants Bash(pnpm run:*), Bash(pnpm test:*), Bash(pnpm exec:*), Bash(pnpm --filter:*) and the gh verbs. What it does not grant is Bash(node:*) — and every command in the PR descriptions, CLAUDE.md and my comments is spelled node scripts/…, which is why it keeps getting denied.

Use the aliases:

you wanted run this instead
node scripts/gen-api-docs.mjs + diff (idempotence) pnpm run gen:api-docs:check
node scripts/check-conformance.mjs pnpm run check:conformance
node scripts/gen-component-catalog.mjs + diff pnpm run gen:catalog:check
lint / typecheck / tests pnpm run lint, pnpm run typecheck, pnpm test

Your review said "wrong-but-present output escapes the parity gate, so I traced the highest-leverage paths by hand" — that reasoning was right, and it's exactly why running the generator matters: pnpm run gen:api-docs:check regenerates and git diff --exit-codes, which catches wrong-but-present output that hand-tracing can miss.

Only check-docs-parity.mjs and check-docs-wording.mjs have no alias and are genuinely unreachable; both are slated for deletion before merge.

If a command still gets denied, name it and I'll get it allowlisted.


Generated by Claude Code

@claude

claude Bot commented Jul 31, 2026 •

Copy link
Copy Markdown

Claude finished @allxsmith's task in 4m 41s —— View job


Deep review — head 4789784 — 1 advisory (pre-existing, same failure class as Level.isMobile)

  • Corrected my earlier tool usage — ran everything through the pnpm run aliases, not bare node scripts/...
  • Installed deps (pnpm install --frozen-lockfile) and confirmed node_modules/node execution work in this sandbox
  • Confirmed skeleton.md/helper-props refutation still holds on the merged tree
  • Verified elements pages are a fixed point after the merge
  • Description accuracy sweep — sibling-compared behavioural props against Bulma's SCSS

All requested checks ran for real this round, not by inspection:

1. check:conformance on the merged tree — 12/12 green, including style-mapping-sync, confirming the restore held:

✓ listings-sync ✓ docs-sections ✓ docs-section-order ✓ docs-generated ✓ scss-conformance
✓ skills-sync ✓ style-mapping-sync ✓ story-per-component ✓ compound-family
✓ autodocs-tag ✓ publishable-manifests ✓ inline-style

2. gen:api-docs:check — fixed point confirmed: 40 generated page(s), 0 updated.

3. Your own refutation, re-checked under the merge — still holds. skeleton.md's catch-all row (line 253) reads All standard <div> attributes with no Helper Props link; SkeletonProps still extends only React.HTMLAttributes<HTMLDivElement>. linkbutton.md (line 192) still carries ... → All standard <button> attributes and Bulma helper props → See Helper Props, correctly inherited via Omit<ButtonProps, …>.

4. Description accuracy — one real finding, same failure class as Level.isMobile:

docs/docs/api/elements/tags.md:172 / bulma-ui/src/elements/Tags.tsx:18 — isMultiline: "Allow tags to wrap onto multiple lines." The prop renders Bulma's are-multiline class (Tags.tsx:46), but that class doesn't exist anywhere in bulma@1.0.4's CSS (grep -rni multiline bulma/sass/ finds only is-grouped-multiline on .field and is-multiline on .columns — neither is .tags). .tags itself (bulma/sass/elements/tag.scss:25) sets flex-wrap: wrap unconditionally, with no nowrap anywhere to gate it. So .tags already wraps onto multiple lines with or without the prop — isMultiline is a no-op in this Bulma version, and the description implies it's opt-in when it isn't.

Not inverted like Level.isMobile (setting it doesn't break anything, and it's harmless to keep using), but the same "prose says X, SCSS says otherwise" shape, and pre-existing: git diff origin/claude/api-docs-layout HEAD -- bulma-ui/src/elements/Tags.tsx shows only the JSDoc relocation this stack does everywhere — the sentence text itself is untouched, on main before this PR touched the file. Recommend tracking alongside the rest of the #420 list rather than fixing in this stack, per the same reasoning you gave for the Table.Tfoot advisory.

Also swept and cleared (matched Bulma's SCSS, no inversions): Table (isBordered/isStriped/isNarrow/isHoverable/isFullwidth/isResponsive/isSelected), Button/LinkButton (isOutlined/isInverted/isLight/isRounded/isLoading/isStatic/isFullWidth/isFocused/isActive/isHovered/isDisabled), Buttons (isCentered/isRight — group justify-content), Image (isRounded, isRetina — the latter is a React-side srcSet behavior, not a CSS class, and matches Bulma's documented retina pattern), Title/SubTitle (isSpaced — correctly preserves margin-bottom when followed by a subtitle/title, not inverted), Tag (isRounded/isDelete/isHoverable), Link (isActive), Notification (isLight).

allxsmith and others added 2 commits July 31, 2026 05:23
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
allxsmith added a commit that referenced this pull request Jul 31, 2026
…420)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Base automatically changed from claude/api-docs-layout to main July 31, 2026 05:51
# Conflicts:
#	scripts/lib/api-sources.mjs
@github-actions

Copy link
Copy Markdown
Contributor

Preview Deployment

Preview URL: https://89ff8dc2.bestax.pages.dev

@allxsmith
allxsmith merged commit 5d166e6 into main Jul 31, 2026
12 checks passed
@allxsmith
allxsmith deleted the claude/api-docs-elements branch July 31, 2026 06:02
allxsmith added a commit that referenced this pull request Jul 31, 2026
* docs: add the API page generator, its CI gates and review aids

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Alex Smith <allxsmith@users.noreply.github.com>
@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 4.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 5.8.1 🎉

The release is available on:

Your semantic-release bot 📦🚀

@bestax-release-bot

Copy link
Copy Markdown

🎉 This PR is included in version 1.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants