create-bestax 4.2.17 → 4.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +13 -5
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +1 -1
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/constants.js +6 -1
  7. package/dist/display.d.ts +9 -0
  8. package/dist/display.d.ts.map +1 -1
  9. package/dist/display.js +19 -1
  10. package/dist/file-system.d.ts +16 -3
  11. package/dist/file-system.d.ts.map +1 -1
  12. package/dist/file-system.js +41 -5
  13. package/dist/project-creator.d.ts +16 -1
  14. package/dist/project-creator.d.ts.map +1 -1
  15. package/dist/project-creator.js +35 -6
  16. package/dist/prompts.d.ts +1 -0
  17. package/dist/prompts.d.ts.map +1 -1
  18. package/dist/prompts.js +3 -2
  19. package/dist/validators.d.ts +8 -0
  20. package/dist/validators.d.ts.map +1 -1
  21. package/dist/validators.js +18 -1
  22. package/package.json +3 -3
  23. package/templates/skills/bestax-custom-component/references/component-catalog.md +3 -1
  24. package/templates/skills/bestax-form/SKILL.md +6 -2
  25. package/templates/skills/bestax-form/references/api.md +3 -0
  26. package/templates/skills/bestax-form/references/patterns.md +7 -3
  27. package/templates/skills/bestax-layout-scaffold/SKILL.md +20 -3
  28. package/templates/skills/bestax-layout-scaffold/references/layout-components.md +3 -3
  29. package/templates/skills/bestax-migrate/SKILL.md +23 -2
  30. package/templates/skills/bestax-migrate/references/bloomer/prop-map.md +4 -4
  31. package/templates/skills/bestax-migrate/references/bulma-classes/component-map.md +9 -4
  32. package/templates/skills/bestax-migrate/references/bulma-classes/prop-map.md +1 -1
  33. package/templates/skills/bestax-migrate/references/bulma-classes/unmappables.md +317 -4
  34. package/templates/skills/bestax-migrate/references/css-migration.md +5 -1
  35. package/templates/skills/bestax-migrate/references/rbx/prop-map.md +4 -4
  36. package/templates/skills/bestax-migrate/references/react-bulma-components/unmappables.md +4 -4
  37. package/templates/skills/bestax-optimize/references/modular-build.md +5 -3
  38. package/templates/skills/bestax-theming/references/css-variables.md +43 -0
  39. package/templates/skills/bestax-theming/references/themeable-components.md +2 -0
@@ -70,6 +70,9 @@ Field/Control/message props. `TextArea` adds `rows` and `hasFixedSize`.
70
70
 
71
71
  - `Checkbox` / `Radio` — single controls with `color`, `size`, `checked`/`defaultChecked`,
72
72
  `onChange`, `disabled`.
73
+ - `Checkbox` also takes `indeterminate`, the mixed state of a "Select all" box over a partly
74
+ checked list. It changes the look and the announcement, not the value: `checked` stays the
75
+ value, so set both from the list.
73
76
  - `Checkboxes` — group wrapper managing an **array** value.
74
77
  - `Radios` — group wrapper managing a **single** selected value.
75
78
 
@@ -131,7 +131,7 @@ Notes:
131
131
  // Search box: input + button attached.
132
132
  <Field hasAddons>
133
133
  <Control isExpanded>
134
- <InputBase placeholder="Search" />
134
+ <InputBase placeholder="Search" aria-label="Search" />
135
135
  </Control>
136
136
  <Control>
137
137
  <Button color="primary">Search</Button>
@@ -141,14 +141,18 @@ Notes:
141
141
  // Two controls on one row.
142
142
  <Field grouped>
143
143
  <Control>
144
- <InputBase placeholder="First" />
144
+ <InputBase placeholder="First" aria-label="First name" />
145
145
  </Control>
146
146
  <Control>
147
- <InputBase placeholder="Last" />
147
+ <InputBase placeholder="Last" aria-label="Last name" />
148
148
  </Control>
149
149
  </Field>
150
150
  ```
151
151
 
152
+ A `grouped` or `hasAddons` Field names none of the controls it holds, and a placeholder
153
+ disappears once the user types, so each input above carries an `aria-label`. For a visible
154
+ label instead, see the last item below.
155
+
152
156
  Recipes built on addons, each written out in full in the docs:
153
157
 
154
158
  - **Password reveal** ([docs](https://bestax.io/docs/api/form/input#password-reveal)): the
@@ -97,6 +97,21 @@ Centered; a collection of items → Card grid. For mixed requests, pick the domi
97
97
  "Clear search" put focus back in the field. While data loads show a `Skeleton` or a `Loader`,
98
98
  and a failed load is a `Message` or a `Notification`, never an EmptyState. The archetypes
99
99
  reference has the full pattern under Card grid.
100
+ - **An order's history, a ticket's activity, a changelog or an activity feed is `Timeline`**,
101
+ not a hand-built list with a border down the side: `<Timeline titleAs="h3">` with a
102
+ `<Timeline.Item title="Shipped" time="14 October 2026" dateTime="2026-10-14">` per event, any
103
+ detail as its children, and the whole date in `time`. `titleAs` is the level under the
104
+ section's heading, or `"p"` in a busy feed. For something that moves along on its own, like a
105
+ parcel or a deployment, `active={i}` marks progress and `pending="Out for delivery"` ends it on
106
+ what's under way, oldest first. A journey the user moves through, like a wizard or a checkout,
107
+ stays `Steps`.
108
+ - **A file browser, a checkbox tree (permissions, filters) or an outline you browse is
109
+ `TreeView`**, with `<TreeView.Item value="…" label="…" icon="folder">` items nested inside each
110
+ other: `<TreeView aria-label="Files" selectionMode="single">` with `selected`/`onSelectedChange`
111
+ picks one, and `selectionMode="checkbox"` with `checked`/`onCheckedChange` draws boxes with
112
+ mixed parents, where a parent's value checks its whole branch. Map the data with a recursive
113
+ function, and render an item's children whether it's open or not. Its items aren't links, so
114
+ the app's sidebar and any list of pages stay on `Menu` with `Menu.Item href`.
100
115
  - Link lists (footer nav, sidebars): a bare `UnorderedList` of `ListItem`s is already
101
116
  marker-less and flush — Bulma's reset unstyles `ul` — so no prop or CSS is needed;
102
117
  bullets appear only inside `Content`.
@@ -199,9 +214,11 @@ background comes from the semantic `color` prop, not `bgColor`.
199
214
  | `display: 'none'` | `visibility="hidden"`, or responsive `display*` props (`displayMobile`, `displayTablet`, …) |
200
215
  | `gap: '1rem'` (flex or grid) | `gap="2"`, or one axis: `columnGap`, `rowGap`; gap scale: `1`=0.5rem, `2`=1rem, `4`=2rem, half steps (`"1.5"`) |
201
216
 
202
- No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project
203
- stylesheet (`src/App.css` in a scaffolded app) and pass it via `className` — still never
204
- inline `style`.
217
+ No helper matches (e.g. `maxWidth`, a one-off gradient)? Add a named class to the project's
218
+ global stylesheet and pass it via `className` — still never inline `style`. In a create-bestax
219
+ app that is `src/index.css`, which its entry file imports after bestax's stylesheet, so the
220
+ class wins over a Bulma rule of the same weight. The starter page's `src/App.css` loads after
221
+ bestax's too, but only while the starter's `App` imports it.
205
222
 
206
223
  ## References
207
224
 
@@ -252,9 +252,9 @@ https://bestax.io/docs/guides/features/routing.
252
252
 
253
253
  `<Menu>` is a vertical sidebar menu. Subcomponents: `Menu.Label`, `Menu.List`, `Menu.Item`.
254
254
 
255
- | Prop | Type |
256
- | ----------- | ------------------------------------------------------------------------------------------------- |
257
- | `Menu.Item` | `active?`, `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
255
+ | Prop | Type |
256
+ | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
257
+ | `Menu.Item` | `active?` (the current page: `is-active` plus `aria-current="page"`, so don't add your own), `href?`, `as?` (custom link component); nest a `Menu.List` inside an item for submenus |
258
258
 
259
259
  ```tsx
260
260
  <Menu>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bestax-migrate
3
- description: Migrate an existing React app to @allxsmith/bestax-bulma on Bulma v1, from raw Bulma CSS classes on plain JSX (className="button is-primary") or from an unmaintained React Bulma library (react-bulma-components v4, rbx v2, bloomer 0.6). Run the bestax-migrate codemod, then resolve every TODO(bestax-migrate) comment it leaves using the per-source mapping references. Use when a React app styles its markup with Bulma classes and wants bestax components instead, when a repo imports react-bulma-components, rbx or bloomer, when TODO(bestax-migrate) comments are present in a codebase, or when asked to convert Bulma classNames to bestax-bulma.
3
+ description: Migrate an existing React app to @allxsmith/bestax-bulma on Bulma v1, from raw Bulma CSS classes on plain JSX (className="button is-primary") or in static HTML pages and templates styled with Bulma, or from an unmaintained React Bulma library (react-bulma-components v4, rbx v2, bloomer 0.6). Run the bestax-migrate codemod, then resolve every TODO(bestax-migrate) comment it leaves using the per-source mapping references. Use when a React app styles its markup with Bulma classes and wants bestax components instead, when static HTML pages or templates styled with Bulma should become React components, when a repo imports react-bulma-components, rbx or bloomer, when TODO(bestax-migrate) comments are present in a codebase, or when asked to convert Bulma classNames to bestax-bulma.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -21,6 +21,7 @@ The codemod's first argument names the library you are migrating _from_. Check t
21
21
  | `rbx` v2 (abandoned 2019, pins Bulma **0.7.5** plus four extensions) | `rbx` | [`references/rbx/`](references/rbx/component-map.md) |
22
22
  | `bloomer` 0.6 (archived 2018, Bulma 0.6 era, React 16 + `create-react-class`) | `bloomer` | [`references/bloomer/`](references/bloomer/component-map.md) |
23
23
  | Raw Bulma classes on plain JSX (`<div className="box">`), no React Bulma library | `bulma-classes` | [`references/bulma-classes/`](references/bulma-classes/component-map.md) |
24
+ | Static HTML pages or templates styled with Bulma (`<div class="box">`) | `bulma-classes` | [`references/bulma-classes/`](references/bulma-classes/unmappables.md) |
24
25
 
25
26
  Everything below is written as `<source>`; substitute the argument from that table. The
26
27
  reference paths follow the same split — `references/<source>/component-map.md`,
@@ -38,6 +39,24 @@ Run these steps in order. Don't hand-convert what the codemod converts automatic
38
39
  pnpm dlx bestax-migrate <source> src/ --dry
39
40
  ```
40
41
 
42
+ For HTML pages, name a page, or walk a folder with `-e html` (`-e html,htm` for `.htm` pages
43
+ too; a plain walk skips them,
44
+ since most of it in a React project isn't a page to convert, and reports the Bulma pages it
45
+ skipped as `html-file:not-included`):
46
+
47
+ ```sh
48
+ pnpm dlx bestax-migrate bulma-classes src/pages/login/index.html --dry
49
+ pnpm dlx bestax-migrate bulma-classes -e html src/pages/ --dry
50
+ ```
51
+
52
+ Each page becomes a module beside it (`index.tsx`, or `.jsx` when the package isn't
53
+ TypeScript) whose default export is `<Name>Page`, named from the file (its folder's for an
54
+ `index.html`). The page stays as it was, and the codemod never writes over a module that's
55
+ already there, so a re-run writes nothing. Its head, its `<html>` and `<body>` attributes and
56
+ its scripts become `html-*` TODOs: put the app-wide parts in the app's `index.html` and
57
+ entry, and port each script's work into the component. Under `--css bestax` or `--css bulma`
58
+ the module imports the stylesheet in place of the page's Bulma `<link>`.
59
+
41
60
  2. **Apply it** (same command without `--dry`), then run the project's formatter — the
42
61
  codemod preserves surrounding formatting but doesn't prettify what it rewrites.
43
62
  Besides the components, it also migrates **stylesheets** (CSS imports →
@@ -121,7 +140,9 @@ code per the references, or deliberately keep the old markup with `className` st
121
140
  App Router may render as server components are left alone (`rsc`), and the manifest gains
122
141
  `@allxsmith/bestax-bulma` while the app's Bulma and stylesheets stay (the default
123
142
  `--css keep`). Its rules are `kind:<name>` shaped; see
124
- `references/bulma-classes/unmappables.md`.
143
+ `references/bulma-classes/unmappables.md`. An HTML page converts the same way once it is JSX,
144
+ and what a component can't carry (the head, scripts, inline handlers, `<html>` and `<body>`
145
+ classes such as `has-navbar-fixed-top`) gets an `html-*` rule with its recipe there.
125
146
  - **bloomer**: every export is a flat name and most become dotted bestax compounds
126
147
  (`CardHeaderTitle` → `Card.Header.Title`). Most of its `is*` booleans already are bestax's
127
148
  (`isActive` becomes `active` where bestax names it so; `isFullWidth` becomes `isFullwidth`
@@ -189,10 +189,10 @@ value is flagged. The same conversion covers the modifiers bestax has no prop fo
189
189
  bloomer forwarded no refs. bestax forwards a ref from the form controls, plus `Avatar`, `Button`,
190
190
  `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `EmptyState`, `Kbd`, `Kbd.Group`, `Link`,
191
191
  `LinkButton`, `Loader`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`, `Navbar.Dropdown`,
192
- `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Toast` and
193
- `ToggleButton` — pass `ref` directly on those. "The form controls" means the inputs themselves: the
194
- `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios` wrappers around them forward
195
- nothing.
192
+ `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Timeline`,
193
+ `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item` — pass `ref` directly on
194
+ those. "The form controls" means the inputs themselves: the `Field`, `Field.Label`, `Field.Body`,
195
+ `Checkboxes` and `Radios` wrappers around them forward nothing.
196
196
 
197
197
  A `ref` on anything else is unsupported, and the two React majors fail differently: React 18
198
198
  drops it and logs "Function components cannot be given refs", while React 19 hands it to the
@@ -154,11 +154,16 @@ element is a `.menu-list`, a bestax `Menu.List`, or the bare `<ul>` nested in on
154
154
  so the `<li>`s a `.map()` renders count too. One handed to any other function doesn't, since that
155
155
  function could render it anywhere. `Menu.Item` renders the `<li>` and its `<a>`
156
156
  together, so an item converts when its `<li>` holds one `<a>` and at most one bare `<ul>` after
157
- it, which becomes a `Menu.List`. `is-active` on the `<a>` becomes `active`, and the `<li>`'s own
157
+ it, which becomes a `Menu.List`. `is-active` on the `<a>` becomes `active` beside an
158
+ `aria-current` of the `<a>`'s own, since `active` writes `aria-current="page"` otherwise; without
159
+ one, the item stays markup with an `attr:aria-current` TODO. If the item is the current page, add
160
+ `aria-current="page"` to the `<a>` and re-run. The `<li>`'s own
158
161
  classes stay in `className`, which `Menu.Item` puts on the `<li>`. It puts `id`, `title`, `role`,
159
162
  `tabIndex`, `style` and `data-testid` there too, and everything else on the `<a>`, so an item
160
163
  converts only when its attributes already sit that way. A `.menu-item` gets a
161
- `family:menu-item` TODO. `Menu.List` drops `.menu-list` on a list inside another, so a
164
+ `family:menu-item` TODO. `Menu.List` writes `role="list"` on its `<ul>`, which is the role a
165
+ list already has, so a `.menu-list` with no `role` converts, and one with another `role` stays
166
+ markup with an `attr:role` TODO. `Menu.List` drops `.menu-list` on a list inside another, so a
162
167
  `.menu-list` inside another, or around a bestax `Menu.List`, stays markup with a
163
168
  `context:Menu.List` TODO, and an item's nested list converts only inside a list that is a
164
169
  `Menu.List` or becomes one.
@@ -166,7 +171,7 @@ converts only when its attributes already sit that way. A `.menu-item` gets a
166
171
  ```tsx
167
172
  <ul className="menu-list">
168
173
  <li>
169
- <a className="is-active" href="/team">Team</a>
174
+ <a className="is-active" href="/team" aria-current="page">Team</a>
170
175
  <ul>
171
176
  <li><a href="/team/members">Members</a></li>
172
177
  </ul>
@@ -174,7 +179,7 @@ converts only when its attributes already sit that way. A `.menu-item` gets a
174
179
  </ul>
175
180
  // becomes
176
181
  <Menu.List>
177
- <Menu.Item active href="/team">
182
+ <Menu.Item active href="/team" aria-current="page">
178
183
  Team
179
184
  <Menu.List>
180
185
  <Menu.Item href="/team/members">Members</Menu.Item>
@@ -109,7 +109,7 @@ renders the same `<h2>`.
109
109
  | `.tabs` → `Tabs` | `is-centered`, `is-right`, `is-left` | `align` |
110
110
  | `.tabs` → `Tabs` | `is-small`, `is-medium`, `is-large` | `size` |
111
111
  | `.tabs` → `Tabs` | `is-fullwidth`, `is-boxed`, `is-toggle`, `is-toggle-rounded` | `isFullwidth`, `boxed`, `toggle`, `rounded` (`is-<color>` stays a class: `color` is deprecated) |
112
- | the `<a>` in a menu item → `Menu.Item` | `is-active` | `active` |
112
+ | the `<a>` in a menu item → `Menu.Item` | `is-active`, beside an `aria-current` of the `<a>`'s own | `active` (without one, the item stays markup with an `attr:aria-current` TODO) |
113
113
  | `.pagination` → `Pagination` | `is-small`, `is-medium`, `is-large` | `size` |
114
114
  | `.pagination` → `Pagination` | `is-centered`, `is-right`, `is-rounded` | `align`, `rounded` (`is-<color>` stays a class: `color` is deprecated) |
115
115
  | `.pagination-link` → `Pagination.Link` | `is-current`, beside an `aria-current` of the element's own | `active` (without one, it stays a class; `is-disabled` stays a class) |
@@ -36,9 +36,10 @@ color. Rename or drop the attribute, then re-run.
36
36
 
37
37
  It also covers an attribute the component's props type rejects. `Delete` takes no `type`
38
38
  (`type="button"` is the one value that converts, because `Delete` renders it anyway), and
39
- `Progress` types `value` and `max` as numbers, so `value="half"` stays. Every component types
40
- `tabIndex` as a number too, so `tabIndex="0"` converts as `tabIndex={0}`. A number converts only
41
- when it is spelled the way it renders (`value="40"`, not `value="040"`).
39
+ `Progress` types `value` and `max` as numbers, so `value="half"` stays. `InputBase` and
40
+ `TextAreaBase` type `maxLength` and `minLength` as numbers, and `TextAreaBase` `rows` and `cols`
41
+ too. Every component types `tabIndex` as a number, so `tabIndex="0"` converts as `tabIndex={0}`.
42
+ A number converts only when it is spelled the way it renders (`value="40"`, not `value="040"`).
42
43
 
43
44
  `attr:dangerouslySetInnerHTML` is the same kind of refusal: the element sets its own content,
44
45
  and some bestax components render content of their own beside `children`, which React
@@ -67,6 +68,17 @@ empty one, keeps the item as markup too, and so does an empty `className` on the
67
68
  nothing is left to render its `class=""`. Move the attribute to where `Menu.Item` puts it if
68
69
  that's what you want, then re-run.
69
70
 
71
+ `attr:role` on a `.menu-list` is a `role` other than `list`. `Menu.List` writes `role="list"` on
72
+ its `<ul>` whatever it's given, for Safari, which stops treating a list as one once its markers are
73
+ hidden, so that role would be lost. Keep the list as markup. One with no `role`, or `role="list"`,
74
+ converts, since that's the role a `<ul>` already has.
75
+
76
+ `attr:aria-current` is the `<a>`'s `is-active` with no `aria-current` beside it. `Menu.Item`'s
77
+ `active` writes `aria-current="page"` on the `<a>` unless it has one of its own, so the item would
78
+ gain it. If the item marks the page you're on, which is what `is-active` usually means in a menu,
79
+ add `aria-current="page"` to the `<a>` and re-run, and the item converts with it. If it marks
80
+ something else, keep the markup, or convert it by hand and pass the `aria-current` you want.
81
+
70
82
  ### `defaults:<Target>`
71
83
 
72
84
  The component renders attributes of its own when the element doesn't set them. `Delete`
@@ -270,7 +282,9 @@ The classes in a computed `className` still count for every other rule: a `clsx(
270
282
  `<span>` gets `tag:Box`, and a `clsx('dropdown', …)` gets `family:dropdown`. A computed
271
283
  `className` on the `<select>` inside a `.select`, the `<ul>` inside a `.breadcrumb` or the `<a>`
272
284
  in a menu item gets this TODO too. For the `<a>`, a condition on `is-active` is `Menu.Item`'s
273
- `active`: `<a className={on ? 'is-active' : undefined}>` becomes `<Menu.Item active={on}>`.
285
+ `active`: `<a className={on ? 'is-active' : undefined}>` becomes `<Menu.Item active={on}>`, which
286
+ also writes `aria-current="page"` while `on` holds. Pass an `aria-current` of your own if the item
287
+ marks something other than the current page.
274
288
 
275
289
  ## A family it leaves as markup: `family:<class>`
276
290
 
@@ -338,6 +352,305 @@ alone: every other TODO names a React component to use.
338
352
  another runtime (Preact, Solid), or a package that sets one in its tsconfig or depends on one
339
353
  instead of React. bestax-bulma components are React components.
340
354
 
355
+ ## A page converted from HTML: `html-*`
356
+
357
+ Run on an HTML page (`bestax-migrate bulma-classes page.html`, or a folder with `-e html`, or
358
+ `-e html,htm` to take `.htm` pages too), the
359
+ codemod writes a module beside the page and runs everything above on it, so a page gets the same
360
+ TODOs its markup would as JSX. What a component can't carry gets an `html-*` rule. TODOs about the
361
+ whole page sit above the component, the rest above its `return`, and each ends with the page and
362
+ the line it came from (`(index.html:42)`). The page itself stays as it was, so the code a TODO
363
+ points at is still there to read. A rule names only a tag, an attribute or a fixed word, never
364
+ anything the page says.
365
+
366
+ ### The page's head: `html-head:<tag>`
367
+
368
+ A component renders into the `<body>` of the app's own document, so nothing in the page's
369
+ `<head>` comes with it.
370
+
371
+ - **`html-head:title`**: set the title where the app sets each page's title (its router's or
372
+ framework's head API). Under React 19 the component can render `<title>` itself and React moves
373
+ it into the head; React 18 leaves it in the body, so use `document.title` in an effect there.
374
+ - **`html-head:meta`**: `charset` and `viewport` go once in the app's `index.html`. A page's own
375
+ (a description, Open Graph tags) go where the app sets each page's head.
376
+ - **`html-head:link`**: icons, preconnects and preloads are app-wide, so keep them in
377
+ `index.html`; a `canonical` is per page.
378
+ - **`html-head:style`**: the `<style>` block was dropped. Move its rules into the app's stylesheet.
379
+ - **`html-head:base`**: `<base href>` changed how the page's relative URLs resolved, and the app
380
+ has one document for every route. Make those URLs absolute instead (see `html-url:relative`).
381
+ - **`html-head`**: anything else in the head (a `<noscript>`, a `<template>`). Move it into
382
+ `index.html` if the app needs it.
383
+
384
+ ### Stylesheets and scripts it loads: `html-asset:<kind>`
385
+
386
+ Each `<link rel="stylesheet">` and `<script src>`, by what its URL says it is. A component loads
387
+ neither, so load what the app still needs once for the whole app, from its entry module or
388
+ `index.html`. The TODO quotes the URL.
389
+
390
+ - **`html-asset:bulma`**: Bulma's own stylesheet, with the version its URL names
391
+ (`bulma@0.9.4`, or none for a copy of the page's own).
392
+ - Under the default `--css keep` it is only reported: keep loading Bulma
393
+ (`import 'bulma/css/bulma.min.css'` in the entry, with that version of `bulma` installed, or the
394
+ CDN `<link>` in `index.html`).
395
+ - Under `--css bestax` the module imports `@allxsmith/bestax-bulma/bestax.css` in the link's
396
+ place, and under `--css bulma` it imports `bulma/css/bulma.min.css` and
397
+ `@allxsmith/bestax-bulma/extras.css`, the way the same import in a JSX file is retargeted, so the
398
+ package.json step sees the module's `bulma` import too. One import from each page module is
399
+ fine, since the bundler loads it once. What is left gets the TODO: a page made for Bulma 0.9, or
400
+ one whose URL doesn't say, moves to Bulma 1 (the docs' Bulma 0.9 to 1 guide), and under
401
+ `--css bestax` a page with a primary color class (`is-primary`, `has-text-primary`) changes
402
+ color, since `bestax.css` builds Bulma with bestax's own primary. To keep it, set the
403
+ `--bulma-primary-*` variables as [css-migration.md](../css-migration.md) says, or use
404
+ `--css bulma`. A Bulma 1 page with no primary class gets the import and no TODO. A TypeScript
405
+ build resolves the import through the bundler's CSS types (Vite's `vite/client`), as it does
406
+ every stylesheet import.
407
+ - No import is written in a Next.js app, whose Pages Router takes a global stylesheet only in
408
+ `pages/_app`, nor for a link with a media query (`media="print"`), since an import applies to
409
+ every medium. The TODO says where it goes instead: the root layout or `pages/_app`, or a
410
+ `<link>` with the same `media` in `index.html`.
411
+ - One of Bulma's other builds (`bulma-no-dark-mode`, `bulma-prefixed`, 0.9's `bulma-rtl`) is
412
+ reported in every mode, since the codemod imports only the standard one. bestax's own builds,
413
+ Bulma with its extras, are under `@allxsmith/bestax-bulma/versions/`.
414
+ - **`html-asset:font-awesome`**: Font Awesome, with the major its URL names. bestax's `Icon`
415
+ writes its classes (`fas fa-<name>`) and loads no font of its own, so keep loading it, from the
416
+ `<link>` in `index.html` or from npm: bestax-bulma's optional peer range for
417
+ `@fortawesome/fontawesome-free` is `^6.7.2 || ^7.0.0`, and an older one fails `npm install`
418
+ with `ERESOLVE`.
419
+ - 6 and 7: keep it as it is, or install it and import its `css/all.min.css` from the entry.
420
+ - 5: the CDN link keeps working, since 5 reads the classes `Icon` writes. From npm, take 6 or 7,
421
+ which still read 5's names.
422
+ - 4: its classes are `fa fa-<name>`, and `Icon` writes `fas fa-<name>`, which 4 doesn't style.
423
+ Install 6 or 7 with `css/all.min.css`, then either import `css/v4-shims.min.css` beside it so
424
+ the page's 4 names keep working, or move them to the current names (`fa-envelope-o` is
425
+ `envelope` with `variant="regular"`).
426
+ - A kit (`kit.fontawesome.com`): keep loading it from `index.html`, or install Font Awesome from
427
+ npm instead. A URL with no version: the same, with the v4 shims beside it if the page's names
428
+ are 4's (`fa fa-<name>`).
429
+ - **`html-asset:bulma-extension`**: a `bulma-*` package (or `@creativebulma/bulma-*`). An
430
+ extension's stylesheet and script are one TODO, with both lines. Where bestax has a component
431
+ for what it does, the TODO names it; render that in place of the extension's markup and script,
432
+ whose styles ship in `extras.css` and `bestax.css`, and stop loading the extension once nothing
433
+ the app keeps uses its classes:
434
+
435
+ | Extension | bestax |
436
+ | ------------------------------------------------------- | --------------------------------------------------------------- |
437
+ | `bulma-carousel` | `Carousel`, with a `CarouselItem` for each slide |
438
+ | `bulma-slider` | `Slider` |
439
+ | `bulma-switch` | `Switch` |
440
+ | `bulma-checkradio` | `Checkbox` and `Radio`, which draw their own box |
441
+ | `bulma-tooltip`, `@creativebulma/bulma-tooltip` | `Tooltip` |
442
+ | `bulma-badge`, `@creativebulma/bulma-badge` | `Badge` |
443
+ | `bulma-steps` | `Steps` |
444
+ | `bulma-timeline` | `Timeline` |
445
+ | `bulma-tagsinput`, `@creativebulma/bulma-tagsinput` | `Taginput` |
446
+ | `bulma-calendar` | `DateInput`, `TimeInput`, `DateTimeInput` and `DateRangeInput` |
447
+ | `bulma-accordion` | `Collapses`, with a `Collapse` for each item |
448
+ | `bulma-collapsible`, `@creativebulma/bulma-collapsible` | `Collapse` (`Collapses` for an accordion) |
449
+ | `bulma-pageloader` | `Loading`, `isFullPage` for one over the whole page |
450
+ | `bulma-quickview` | `Sidebar` |
451
+ | `bulma-toast` | `Toast`: `toast.show()` from code, with a `ToastContainer` once |
452
+
453
+ `bulma-extensions`, the bundle, holds several of these: replace the ones in the table, and keep
454
+ loading the bundle only for the rest. **bulma-social** has no bestax component: keep loading it,
455
+ since its classes (`is-twitter`, `is-github`) style Bulma's `.button`, which `Button` renders,
456
+ so they go on `Button` through `className`:
457
+
458
+ ```tsx
459
+ <Button as="a" className="is-github" href="https://github.com/example">
460
+ <Icon name="github" variant="brands" />
461
+ </Button>
462
+ ```
463
+
464
+ Any other extension: keep loading it while the page's markup uses its classes, which go on
465
+ bestax's components through `className`, and under `--css bestax` or `--css bulma` check it
466
+ works with Bulma 1, which an extension can predate.
467
+
468
+ - **`html-asset:jquery`**: jQuery itself (a plugin such as jQuery UI is `html-asset:script`).
469
+ React renders the page now, so what the page's scripts did with jQuery (toggling classes,
470
+ handlers, showing and hiding) becomes state and event props; load jQuery only if a library the
471
+ app keeps still needs it.
472
+ - **`html-asset:webfont`**: a web font's stylesheet (Google Fonts, Bunny Fonts, Adobe Fonts).
473
+ Keep the `<link>` in `index.html` with its preconnects, or self-host the font and import it from
474
+ the entry. Google's Material Icons and Material Symbols are icon fonts, which `Icon` writes with
475
+ `library="material-icons"` or `library="material-symbols"`; keep the link, or install the
476
+ `material-icons` or `material-symbols` package.
477
+ - **`html-asset:stylesheet`**: any other stylesheet. The page's own, beside it (in its folder or
478
+ one below), is imported once for the app in the order the page loads it, so its rules still win
479
+ over Bulma's where they did (a module beside the page imports `css/styles.css` as
480
+ `./css/styles.css`), or its rules move into the app's stylesheet. For one that isn't in the
481
+ page's folder, the TODO says only that: the page's `<base href>`, if it has one, may have loaded
482
+ it from somewhere else, so find the one it meant, or drop it.
483
+ - **`html-asset:script`**: any other script. The component runs none: load a library the app still
484
+ needs in `index.html` (or install it), and port what the page's own scripts did into the
485
+ component. A script of the page's own, beside it, is read the way an inline script is, so one
486
+ that toggles the burger, drives a carousel or switches the theme gets that `html-script:<pattern>`
487
+ TODO instead, quoting the file. One a `../` reaches outside the page's folder isn't read. A
488
+ `<script src>` of a type a browser loads nothing for (`text/template`, `application/json`) isn't
489
+ code: it gets the `html-script` TODO for data (`html-script:json-ld` for JSON-LD), naming its
490
+ file.
491
+
492
+ ### Inline scripts: `html-script:<pattern>`
493
+
494
+ Every inline `<script>` is dropped, and its TODO gives the lines in the page where its code still
495
+ is. One that does something bestax has a component for gets that pattern's rule, and a script
496
+ can get more than one. A script of the page's own, beside it (`<script src="js/scripts.js">`), is
497
+ read for the burger, a carousel and a theme toggle too, and its TODO names the file:
498
+
499
+ - **`html-script:burger`** toggles the navbar burger (`.navbar-burger`, or the `.burger` class
500
+ Bulma 0.x's docs put beside it). Keep the open state in `useState` and pass it to
501
+ `Navbar.Burger` and `Navbar.Menu` (the `family:navbar-burger` TODO on the burger says why the
502
+ markup stays):
503
+
504
+ ```tsx
505
+ function SiteNavbar() {
506
+ const [open, setOpen] = useState(false);
507
+ return (
508
+ <Navbar role="navigation" aria-label="main navigation">
509
+ <Navbar.Brand>
510
+ <Navbar.Item href="/">Home</Navbar.Item>
511
+ <Navbar.Burger
512
+ active={open}
513
+ onClick={() => setOpen(!open)}
514
+ aria-label="menu"
515
+ />
516
+ </Navbar.Brand>
517
+ <Navbar.Menu active={open}>
518
+ <Navbar.Start>
519
+ <Navbar.Item href="/docs">Docs</Navbar.Item>
520
+ </Navbar.Start>
521
+ </Navbar.Menu>
522
+ </Navbar>
523
+ );
524
+ }
525
+ ```
526
+
527
+ - **`html-script:modal`** opens or closes a modal. `Modal` shows while `active` is true and calls
528
+ `onClose` from its background, its close button and Escape, so keep the open state in
529
+ `useState` (the `family:modal` TODO covers rebuilding the markup).
530
+ - **`html-script:dismiss`** removes an element when its `.delete` is clicked. `Notification`
531
+ takes `hasDelete` and `onDelete`, `Tag` takes `onDelete` and `Message` takes `onClose`; render
532
+ the element only while it's in state.
533
+ - **`html-script:carousel`** drives a carousel. `Carousel` takes its slides as `CarouselItem`
534
+ children and renders its own arrows and indicators (`autoplay` for a timed one). Its styles ship
535
+ in bestax's `extras.css` and `bestax.css`, not in Bulma's stylesheet.
536
+ - **`html-script:theme`** switches Bulma's light and dark schemes. The toggle is `Theme`'s: it
537
+ writes `data-theme` on `<html>` from its `colorMode` (`light`, `dark` or `system`), so keep the
538
+ choice in state, save it in `localStorage`, and pass it. The first paint is `index.html`'s:
539
+ `Theme` writes from an effect, after the first paint, so a visitor whose saved choice differs
540
+ from their OS sees the OS's scheme first. A small inline script in the `<head>` of `index.html`
541
+ that reads the saved choice (or the OS's, with none saved) and writes `data-theme` before the
542
+ app loads paints the right one, and the toggle's state starts from what it wrote. The
543
+ bestax-theming skill covers `Theme` and `colorMode`.
544
+ - **`html-script:json-ld`** is structured data for search engines. Render it where the app sets
545
+ each page's head, as
546
+ `<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />`.
547
+ - **`html-script`**: any other inline script, or a `<script type>` that isn't code (a template,
548
+ JSON data). Port what it does into the component as state, effects and event handlers.
549
+
550
+ ### `<html>` and `<body>`: `html-document:<name>`
551
+
552
+ The app's document has its own `<html>` and `<body>`, which a component can't reach.
553
+
554
+ - **`html-document:has-navbar-fixed-top`**, and the same for `has-navbar-fixed-bottom`,
555
+ `has-navbar-fixed-top-desktop`, `has-navbar-fixed-top-touch`, `has-navbar-fixed-bottom-desktop`,
556
+ `has-navbar-fixed-bottom-touch`, `has-spaced-navbar-fixed-top` and
557
+ `has-spaced-navbar-fixed-bottom` (`html-document:has-navbar-fixed-bottom` and so on): Bulma
558
+ pads the page for a fixed navbar from it. Put the class on the `<html>` in `index.html`, since
559
+ bestax's `Navbar` adds none there, whatever its `fixed` says.
560
+ - **`html-document:data-theme`**, **`html-document:theme-dark`**, **`html-document:theme-light`**:
561
+ the page's color scheme. Put it on the `<html>` in `index.html`, or render
562
+ `<Theme colorMode="dark">`, which writes `data-theme` on `<html>`.
563
+ - **`html-document:lang`**, **`html-document:dir`**: set them on the `<html>` in `index.html`.
564
+ - **`html-document:class`**: the page's own classes on `<html>` or `<body>`. Put them on the
565
+ app's own `<body>` (or `<html>`), or on an element around the page's content if the styles
566
+ allow it.
567
+ - **`html-document:id`**, **`html-document:style`**, **`html-document`**: any other attribute
568
+ there, which moves the same way.
569
+
570
+ ### Event handlers: `html-event:<attribute>`
571
+
572
+ An `onclick="…"` and its kind, on any element or on `<body>` (`onload`). React takes a function,
573
+ not code in a string, so the attribute was dropped; the TODO quotes the code and names the prop
574
+ that takes it (`onClick`). Port the code into a handler, reading the element from the event rather
575
+ than `this`. A `<body onload>` becomes an effect.
576
+
577
+ ### Elements: `html-element:<kind>`
578
+
579
+ - **`html-element:custom`**: a custom element (`<my-widget>`) renders as written, but the script
580
+ that defines it isn't loaded by the module: import its definition in the app. A TypeScript
581
+ build also needs the tag declared in `JSX.IntrinsicElements`.
582
+ - **`html-element:obsolete`**: `<center>`, `<font>`, `<marquee>` and their kind still render, but
583
+ React's types list few of them. Replace each with current markup (`has-text-centered` for
584
+ `<center>`, a class for `<font>`).
585
+ - **`html-element:unknown`**: a tag that isn't HTML renders as an unknown inline element, and
586
+ TypeScript rejects it. Replace it with the element it stands for.
587
+ - **`html-element:template`**: `<template>` holds markup for a script to clone. Render that
588
+ markup from the component where the script used it.
589
+ - **`html-element:name`**: a tag JSX can't write (`<o:p>`, from Word), so its children were
590
+ written in its place. Put back the element it stood for, if it mattered.
591
+ - **`html-element:conditional-comment`**: an Internet Explorer conditional comment was dropped;
592
+ no browser React supports reads one.
593
+
594
+ ### URLs: `html-url:<kind>`
595
+
596
+ - **`html-url:javascript`**: a `javascript:` URL won't run (React 19 replaces it with one that
597
+ throws, and React 18 warns). Move the code into an `onClick` handler.
598
+ - **`html-url:relative`**: `img/logo.png` resolved against the page's folder, and in the app it
599
+ resolves against the address the component renders at. The file is there beside the page, so
600
+ copy it into the app's `public/` folder and point at it from the site root (`/img/logo.png`), or
601
+ import it. A link to another page beside this one should point at the route that page becomes.
602
+ Each relative URL in a `srcset` is its own entry, quoted as itself (`` `hero@2x.png` in `srcset` ``).
603
+ Several of these share one TODO line, and each is its own report entry.
604
+ - **`html-url:missing`**: a relative URL whose target isn't beside the page, read the way the
605
+ browser read it, so it was a dead link or a missing file already. Point it somewhere real, or
606
+ drop it.
607
+
608
+ ### Attributes: `html-attr:<kind>`
609
+
610
+ - **`html-attr:name`**: an attribute JSX can't write (`@click`, `:class`, `x-on:click`), or one the
611
+ element already had under another spelling, was left out. A framework's directive is code to port
612
+ into the component.
613
+ - **`html-attr:unknown`**: an attribute React has no prop for by that name, written as it was.
614
+ React renders it. Whether a TypeScript build takes it depends on the element (React's types
615
+ list `valign` on a `<td>` and not on a `<th>`), so the TODO doesn't say; what to do depends on
616
+ the attribute, and the TODO says which:
617
+ - React's own prop in the wrong case (`defaultvalue`, `defaultchecked`), which the page never
618
+ read since it isn't HTML: rename it to the prop (`defaultValue`), or drop it if it isn't
619
+ meant.
620
+ - An obsolete presentational attribute (`valign`, `bgcolor`, `nowrap`), which CSS replaced:
621
+ write it as CSS or a class.
622
+ - Anything else: drop it if nothing reads it, make it a `data-` attribute if a script of the
623
+ page's reads it, and if React's types don't list it on that element, declare it for a
624
+ TypeScript build.
625
+ - **`html-attr:style`**: a `style` declaration a browser can't read (`*zoom: 1`) was left out, as a
626
+ browser leaves it out. Delete it, or rewrite it if it meant something.
627
+ - **`html-attr:important`**: React sets a style without a priority, so the declaration is written
628
+ without its `!important`. If a stylesheet rule beats it now, move it into a class in the app's
629
+ stylesheet.
630
+
631
+ ### Parse errors: `html-parse:<code>`
632
+
633
+ The HTML has an error the browser repaired (`html-parse:duplicate-attribute`,
634
+ `html-parse:end-tag-without-matching-open-element`), reported once for each kind, with how many
635
+ more there are. The module follows the DOM the browser builds from it, which may not be what the
636
+ markup meant, so check that part of the page. A code too long for a rule id is reported as the bare
637
+ `html-parse`.
638
+
639
+ ### The page itself: `html-file:<case>`
640
+
641
+ - **`html-file:exists`**: the module beside the page is already there, and the codemod never
642
+ writes over a file. That makes a second run change nothing. Move or delete the module to convert
643
+ the page again.
644
+ - **`html-file:not-included`**: a folder walk found the page, and it has Bulma classes, but a walk
645
+ takes a page only when `--extensions` lists its extension, since most `.html` files in a React
646
+ project aren't pages to convert (Vite's `index.html`, `public/`). `-e` matches an extension
647
+ exactly, as it does for every file, so `-e html` takes no `.htm` page; the TODO names the one
648
+ the page needs. Name the page, or add its extension to `--extensions` (`-e html,htm` for both).
649
+ - **`html-file:template-syntax`**: the page is a server template (`{{`, `{%`, `<?php`, `<%`), which
650
+ isn't HTML a browser reads as written. Convert the HTML it renders, or the template by hand.
651
+
652
+ A page with no Bulma component class writes nothing and reports nothing.
653
+
341
654
  ## Things that get no TODO
342
655
 
343
656
  - Helper classes on a `<div>` (`<div className="is-flex mt-4">`): bestax has no plain `<div>`
@@ -26,7 +26,11 @@ choice below applies.
26
26
  **Visual note**: `bestax.css` ships with `$primary` set to bestax blue (`#1e6b99`),
27
27
  not Bulma's stock turquoise — `is-primary` surfaces (hero, navbar, buttons) change
28
28
  color. Keep the stock Bulma look with `--css bulma`, or set your own brand color via
29
- `--bulma-primary-*` CSS variables / a Sass build.
29
+ `--bulma-primary-*` CSS variables / a Sass build. Set `--bulma-primary-invert-l` along with
30
+ `--bulma-primary-h`, `--bulma-primary-s` and `--bulma-primary-l`: it is the lightness of the
31
+ text on a primary surface, and bestax's build fixes it for its dark blue, so a light primary set
32
+ through the other three alone keeps white labels. To get stock Bulma's turquoise back on top of
33
+ `bestax.css`, copy those four from Bulma's own `bulma/css/bulma.css`.
30
34
 
31
35
  - **SCSS files**: Bulma 0.9's `@import 'bulma/bulma.sass'` (plus preceding
32
36
  `$var: value !default;` overrides) became Bulma v1 module syntax, with simple literal
@@ -183,10 +183,10 @@ An existing `ref` is passed through untouched — which is safe only where the b
183
183
  one. rbx forwards a ref on every component; bestax does so on the form controls, plus `Avatar`,
184
184
  `Button`, `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `EmptyState`, `Kbd`, `Kbd.Group`,
185
185
  `Link`, `LinkButton`, `Loader`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`, `Navbar.Dropdown`,
186
- `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Toast` and
187
- `ToggleButton`. "The form controls" there means the inputs themselves: the `Field`, `Field.Label`,
188
- `Field.Body`, `Checkboxes` and `Radios` wrappers around them forward nothing, and `Field` is a
189
- target this codemod emits.
186
+ `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`, `Sidebar`, `Timeline`,
187
+ `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item`. "The form controls" there
188
+ means the inputs themselves: the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios`
189
+ wrappers around them forward nothing, and `Field` is a target this codemod emits.
190
190
 
191
191
  Carry a `ref` onto anything else — `Card`, `Box`, `Section`, `Message`, `Tabs` and most of the
192
192
  catalogue — and it is unsupported, but the two React majors fail differently. React 18 drops
@@ -166,10 +166,10 @@ bestax components don't take `domRef`, but many forward a plain `ref` — the fo
166
166
  `Avatar`, `Button`, `Carousel`, `CarouselItem`, `Dialog`, `Dropdown`, `EmptyState`, `Kbd`,
167
167
  `Kbd.Group`, `Link`, `LinkButton`, `Loader`, `Menu.Item`, `Modal`, `Navbar`, `Navbar.Burger`,
168
168
  `Navbar.Dropdown`, `Navbar.Item`, `Navbar.Link`, `SegmentedControl`, `SegmentedControl.Segment`,
169
- `Sidebar`, `Toast` and `ToggleButton`. On those, rename `domRef` to `ref` and it works; do not
170
- restructure the markup. "The form controls" means the inputs themselves: the `Field`, `Field.Label`,
171
- `Field.Body`, `Checkboxes` and `Radios` wrappers around them forward nothing, and `Field` is a
172
- target this codemod emits.
169
+ `Sidebar`, `Timeline`, `Timeline.Item`, `Toast`, `ToggleButton`, `TreeView` and `TreeView.Item`. On
170
+ those, rename `domRef` to `ref` and it works; do not restructure the markup. "The form controls"
171
+ means the inputs themselves: the `Field`, `Field.Label`, `Field.Body`, `Checkboxes` and `Radios`
172
+ wrappers around them forward nothing, and `Field` is a target this codemod emits.
173
173
 
174
174
  Everywhere else the rename is not enough, and the two React majors differ: React 18 drops the
175
175
  ref and logs "Function components cannot be given refs", while React 19 passes it through as