Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Examples live in `src/demo/examples/` (e.g., `example_meta.json` / `example_data
- React 16 (peer dependency). **No Semantic UI at all**: the components went in-house at §9.7-F1 steps 1-3 and the CSS at step 4, where the two modules still in use were compiled into `src/style/vendor/` and the package removed. Components still emit Semantic's class tokens (`ui selection dropdown`, `ui table`) because the vendored CSS selects on them.
- react-final-form for form state management
- moment for dates (peer dependency, externalized); charts are custom SVG (`src/core/components/charts/` — no recharts)
- LESS for styling, compiled via webpack (entry: `src/style/index.less`). Semantic UI theme overrides at `src/style/override/`. PostCSS prefixwrap scopes all CSS under `.ui-render`. Less is pinned to 3.x, and after step 4 both reasons are OURS rather than Semantic's (measured): `javascriptEnabled` is required by `_variables.less:23`, a `` `Math.random()` `` cache-buster, and `less-plugin-functions` by `round()` at `_variables.less:264`. See `docs/UPGRADE-PLAN.md` §9.8 before changing.
- LESS for styling, compiled via webpack (entry: `src/style/index.less`). Semantic UI theme overrides at `src/style/override/`. PostCSS prefixwrap scopes all CSS under `.ui-render`. LESS is on **4.x** — the 3.x pin was removed at §9.8 with byte-identical output. Every compile takes its options from `scripts/less-options.js`; do not set them locally. Three things there are load-bearing and each has its reason in the file: `math: 'always'` (LESS 4 changed division), `javascriptEnabled` (our own `` `Math.random()` `` font cache-buster at `_variables.less:23`, not Semantic's), and requiring the NODE build explicitly, because LESS 4's `browser` field plus jest's jsdom environment otherwise loads a build that fetches imports over XHR. `less-plugin-functions` makes `size()`/`px()` callable at 186 sites and needs `scripts/less-plugin-compat.js` to run on LESS 4.
- Node.js v24 (see `.nvmrc`)
- ESLint with `react-app` config (configured in package.json). `lint:js` runs with `--max-warnings 0`, so a new warning fails CI — fix it, or suppress it with a comment stating why the rule is wrong. Never blanket-disable: one tolerated warning here turned out to be a real crash (see `docs/UPGRADE-PLAN.md` §11 R18).
- Babel config lives only in `babel.config.js` and is shared by the library build, the demo build and jest. Do not add `presets` to a `babel-loader` `options` block: a loader-level entry **replaces** the shared one for the same plugin identifier, silently dropping the shared options. Only demo-specific dev transforms (`react-refresh/babel`) belong inline.
Expand Down
16 changes: 14 additions & 2 deletions docs/UPGRADE-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,19 @@ The form stack splits into a React-free core and React bindings:
- **Babel targets (verified — real, library-side):** `babel.config.js` sets `targets: { node: 'current' }`, which governs the **library and watch** builds; the demo config carries its own inline presets (browserslist applies there) and Jest is env-split (§2.6-10). Published `dist/` therefore contains syntax as modern as the build machine's Node while `package.json`'s `browserslist` is never consulted for it. Fix: env-split the root config (test → `node: current`; build → browserslist) **and** collapse the demo's duplicated inline presets into it — **scheduled as Phase 0 step 0.5**. (If all hosts are evergreen-only, document that decision instead.)
- **Automatic JSX runtime:** peer floor 16.14 makes `@babel/preset-react` `runtime: 'automatic'` safe across the whole support range. **Prerequisite:** add `react/jsx-runtime` (and `react/jsx-dev-runtime`) to the library/watch `externals` first — the current exact-match externals (`react`, `react-dom`) would NOT catch the new subpath imports, and webpack would silently bundle React's JSX runtime from devDependencies into the UMD. These subpath externals have no meaningful script-tag global — one more input to the §10 UMD script-tag gate. Do as one mechanical PR after Phase 2.
- **Legacy decorators:** leave as-is; they retire naturally as §9.2/§9.3 convert their host modules. Churn for its own sake is against the ground rules (§9.1).
- **LESS 3.13 pin**: the pin is anchored to the semantic-ui-less toolchain (inline-JS evaluation via `javascriptEnabled` + `theme.config` machinery) and to `less-plugin-functions`. Two findings: (a) after the SUIR CSS exit (§9.7-F1 step 4) the semantic-side constraints disappear; (b) ~~**no custom `.function-…` definitions were found under `src/style`** — `less-plugin-functions` may be vestigial~~ — **REFUTED (2026-09-11, measured).** It is load-bearing: compiling without it in a clean process fails at `src/style/_variables.less:264`, `error evaluating function 'round'`. Searching for `.function-…` definitions was the wrong probe — the plugin also REPLACES built-ins, and `round()` is called on the unit-carrying values `size()`/`px()` produce (nine call sites across `_variables.less` and `_mixins.less`). A caveat for anyone re-checking this: the plugin registers its functions GLOBALLY, so a second `less.render` in the same process still sees them and reports a false pass — test it in a fresh process. Finding (a) stands, so the pin may still relax after step 4, but dropping this plugin is its own piece of work, not a freebie. Re-evaluate right after F1 step 4.
- **LESS 3.13 pin — REMOVED 2026-09-15. The project is on LESS 4.** The bump produces the byte-identical published `static/all.css` (`dcdb0a40…`), verified by compiling the same source through both majors before touching the manifest.

**Neither anchor was what this section said it was.** `javascriptEnabled` is required by `_variables.less:23` — our own `` `Math.random()` `` font cache-buster, which reaches `static/font.css` and never `all.css` — so it STAYS, and removing it is a separate decision about how fonts are cached. `less-plugin-functions` is not vestigial either: it is what makes `size()` and `px()` callable, at **186 sites across 30 files**, so it stays too.

**The real blocker was one line of the plugin's, under its own comment calling it "the most ugly hack ever":** `DetachedSet.prototype.type = 'NotDetachedRuleset'`. LESS 4 defines that property with only a getter, so the assignment throws and every compile dies. `scripts/less-plugin-compat.js` makes the assignment a no-op rather than making it work — the hack exists so a mixin can return a detached ruleset, and ours return plain values. Costed against the alternatives: rewriting 186 call sites changes a computed value at every one and destroys the readability `size()` exists for; a fork or `patch-package` adds a thing to maintain.

**Two further changes the bump forced, both worth knowing:**
- `math: 'always'`. LESS 4 defaults to `parens-division`, under which `1/4` in `_variables.less:236` stops being a quotient and `round` receives a non-number. `always` is LESS 3's semantics and is what keeps the output identical.
- The node build must be required EXPLICITLY. LESS 4's manifest carries a `browser` field, and jest's `jsdom` environment resolves it — four CSS suites then failed with a jsdom `AggregateError` from a socket, because the browser build fetches `@import`s over XHR. That error looks nothing like a LESS problem.

All three now live in `scripts/less-options.js`, the one module every LESS compile imports. Ten sites used to set these options independently — the same shape that already bit us with the `theme.config` copy.

~~The pin is anchored to the semantic-ui-less toolchain (inline-JS evaluation via `javascriptEnabled` + `theme.config` machinery) and to `less-plugin-functions`.~~ Two findings: (a) after the SUIR CSS exit (§9.7-F1 step 4) the semantic-side constraints disappear; (b) ~~**no custom `.function-…` definitions were found under `src/style`** — `less-plugin-functions` may be vestigial~~ — **REFUTED (2026-09-11, measured).** It is load-bearing: compiling without it in a clean process fails at `src/style/_variables.less:264`, `error evaluating function 'round'`. Searching for `.function-…` definitions was the wrong probe — the plugin also REPLACES built-ins, and `round()` is called on the unit-carrying values `size()`/`px()` produce (nine call sites across `_variables.less` and `_mixins.less`). A caveat for anyone re-checking this: the plugin registers its functions GLOBALLY, so a second `less.render` in the same process still sees them and reports a false pass — test it in a fresh process. Finding (a) stands, so the pin may still relax after step 4, but dropping this plugin is its own piece of work, not a freebie. Re-evaluate right after F1 step 4.

### 9.9 Workstream H — Project structure & housekeeping

Expand Down Expand Up @@ -1059,7 +1071,7 @@ Phases 3 and 4 can partially overlap. Tracks **5a/5b** (SUIR exit) and **6** (en
| R5 | Hosts stuck on React 16 get broken by an inadvertent 17+-only API | Low | Medium | Keep 16.14 floor; React 16/17 CI mount smoke (§9.5) |
| R6 | `dist/` ships syntax too modern for consumer browser targets (Babel `node: current` for builds) | Low–Medium | Medium | Fixed in Phase 0 step 0.5 (env split); verify the actual host browser matrix |
| R7 | Native date adapter (if ever pursued) diverges from moment semantics — parse leniency, local-vs-UTC off-by-one | Medium (only if pursued) | Medium | moment stays by default (F2 decision); golden parity suite vs moment gates any flip; date-only strings parsed from parts in local time; moment demoted to *optional* peer only at a major |
| R8 | LESS 3 pin blocks future style tooling | Low | Low–Med | Partly dissolves after F1 step 4 (§9.8). **Two of the three anchors turn out to be OURS, not semantic's** (measured 2026-09-11): `javascriptEnabled` is required by `_variables.less:23` and `less-plugin-functions` by `round()` at `_variables.less:264`. So step 4 removes the `theme.config` machinery but leaves both flags standing; unpinning LESS is a separate, small piece of work with two named targets rather than a consequence of the CSS exit |
| ~~R8~~ | ~~LESS 3 pin blocks future style tooling~~ — **RETIRED 2026-09-15: the project is on LESS 4**, with byte-identical output. What remains is not a pin: `javascriptEnabled` (our font cache-buster) and `less-plugin-functions` (`size()`/`px()` at 186 sites) are both kept deliberately, and a one-line compatibility shim lets the latter run on LESS 4 | — | — | Partly dissolves after F1 step 4 (§9.8). **Two of the three anchors turn out to be OURS, not semantic's** (measured 2026-09-11): `javascriptEnabled` is required by `_variables.less:23` and `less-plugin-functions` by `round()` at `_variables.less:264`. So step 4 removes the `theme.config` machinery but leaves both flags standing; unpinning LESS is a separate, small piece of work with two named targets rather than a consequence of the CSS exit |
| R9 | `Dropdown` replacement misses feature/a11y parity (keyboard matrix, cascading resets, `upward` auto-flip, the `.ui.selection.dropdown` class contract) | Medium | High | **Step 0 shipped the parity checklist** (`docs/SUPPORTED-PROPS.md`, 21 tier-1 props still forwarded after step 1, drift-checked) and re-scoped the risk: search+deburr, multi-select chips and additions are used by *nothing* in either corpus, so the exposure is the keyboard/a11y matrix and the cascading flows, not the feature list. Headless engine (downshift) supplies the a11y core; wrapper-owned logic (sanitization, dedup, cascading) is kept untouched; contract suite + example QA gate the swap |
| R10 | Consumer metas rely on undocumented SUIR passthrough props | Medium | Medium | Step-0 audit across example *and consumer* metas; publish the supported-prop list; anything dropped ⇒ major release with migration notes |
| R11 | Structure moves (H3/H4) collide with in-flight feature branches | Medium | Low–Med | Land as pure-move commits (no logic changes) in a quiet window; announce to the team; git follows renames, so history and blame survive |
Expand Down
Loading
Loading