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
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The modernization roadmap (React 17/18 upgrade, `semantic-ui-react` exit, projec

### Internal layering and imports

All internal imports use **relative paths** — there are no `ui-*-pack` webpack aliases (the only resolve aliases are `theme.config` for semantic-ui-less theming and `process`). The historical "pack" names survive as directory layers:
All internal imports use **relative paths** — there are no `ui-*-pack` webpack aliases (the only resolve alias left is `process`; the three `theme.config` aliases went with `semantic-ui-less` at §9.7-F1 step 4). The historical "pack" names survive as directory layers:

| Layer (historical name) | Path |
|---|---|
Expand Down Expand Up @@ -77,10 +77,10 @@ Examples live in `src/demo/examples/` (e.g., `example_meta.json` / `example_data

## Tech Stack

- React 16 (peer dependency); no `semantic-ui-react` in `src` — the components are in-house (§9.7-F1). The `semantic-ui-less` STYLES are still loaded until step 4, so components emit Semantic's class tokens on purpose.
- 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 (semantic-ui-less inline-JS + `less-plugin-functions` toolchain — 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 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.
- 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 All @@ -89,6 +89,6 @@ Examples live in `src/demo/examples/` (e.g., `example_meta.json` / `example_data

## Gotchas

- `npm run build-css` copies `src/style/override/theme.config` into `node_modules/semantic-ui-less/` before compiling (mutates `node_modules`); output goes to `public/static/ui-render.built.css`. **`src/style/__tests__/setup.js` makes the same copy**, so an ordinary `npx jest` mutates `node_modules` too — which used to matter more than it looks: the webpack builds depended on that copy existing, because `theme.config` is aliased to our own file and its `@import "theme.less"` resolves relative to the importing file, where no `theme.less` exists. `verify` passed only because `test:coverage` runs before `build`; a job going straight from `npm ci` to a build failed. Fixed 2026-09-02 by giving less-loader `paths` in both webpack configs, so a build no longer needs the copy — but the test-time mutation is still there.
- `npm run build-css` compiles `src/style/index.less` to `public/static/ui-render.built.css`. **It no longer mutates `node_modules`,** and neither does `npx jest`: both used to copy `theme.config` into `node_modules/semantic-ui-less/` because Semantic's definitions import it from inside their own package. §9.7-F1 step 4 removed the package, so the copy, the shared helper that made it and the three webpack `theme.config` aliases are all gone. The jest `setupFiles` entry survives as a documented no-op.
- Jest has no path-alias mapping (`jest.config.js`) — only relative imports resolve in tests.
- `isFunction()` from core utils rejects cross-realm functions such as `jest.fn()` — use plain functions in tests.
12 changes: 11 additions & 1 deletion docs/UPGRADE-PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,17 @@ The wrappers' external APIs are the contract; `mapper.js` and meta authors never

**AND ONE TRAP IN THE GATE ITSELF, worth recording because it looked exactly like a product bug.** Two separate false alarms came from the measurement, not the CSS. The generator detects live imports by line shape; it did not recognise `@import (multiple) "..."`, so the baseline compile kept our overrides while the full compile also had them — 47 rules subtracted themselves away and the gate reported them MISSING FROM THE OUTPUT when they were only missing from the MEASUREMENT. Earlier, two of the four apparent "duplicates" turned out to occur twice on master as well; chasing them cost real time. A gate that measures by difference needs its difference to be exact, and both lessons are now comments in `generate-semantic-css-fixture.js`.

What remains of step 4: deleting `semantic-ui-less` and its `theme.config` machinery — three webpack aliases (the plan names two; `webpack.watch.config.mjs` is the third), the `node_modules` copy in `scripts/install-theme-config.js` and its three call sites, and the now-unreferenced `override/` tree.
**THE DEPENDENCY IS GONE — STEP 4 COMPLETE (2026-09-15).** `semantic-ui-less` is out of `devDependencies` and out of `package-lock.json`, and the published `static/all.css` still has the SAME sha256 (`dcdb0a40…`) it had before any of step 4 — the whole exit is invisible to a consumer's rendering.

What went with it: **three** webpack `theme.config` aliases (this plan named two — `webpack.watch.config.mjs` was the third), the `node_modules` `lessOptions.paths` entries in the demo and library configs, `scripts/install-theme-config.js` and its three call sites, and **89 of the 97 files** under `src/style/override/`.

**The deletion was decided by the import graph, not by reading.** `less.render` reports the files it actually read; run against `index.less` it showed 0 `semantic-ui-less` files and only 4 live files under `override/`. Eight are kept: those four plus the four inputs the vendoring compile reads (`globals/reset.variables`, `globals/site.variables`, `modules/dropdown.variables`, `theme.config`) — kept deliberately so `scripts/vendor-semantic-css.js`'s stated recipe (`npm i --no-save semantic-ui-less@2.5.0 && node scripts/…`) remains TRUE rather than becoming a note about git history. Vendored third-party code without a reproducible derivation is a dead end.

**`npx jest` no longer mutates `node_modules`** — the gotcha CLAUDE.md carried for months. The jest `setupFiles` entry survives as a documented no-op.

**The LESS 3 pin does NOT dissolve with the package**, which this plan predicted it would (R8, §9.8). Both anchors turn out to be ours, measured: `javascriptEnabled` is required by `_variables.less:23` and `less-plugin-functions` by `round()` at `_variables.less:264`. Unpinning LESS is separate work with two named targets.

One thing CI caught that a local check had missed: `lint:css` globs `src/style/**/*.less`, so the vendored files entered it and reported 2,295 violations of conventions upstream never followed. `.stylelintignore` excludes `src/style/vendor/` — generated third-party CSS is not where style decisions are made. The local check had been `npm run lint:css | tail -1`, which shows output and hides the exit code; gates are verified by exit code now.

- **Step 5 — cleanup + release.** The `semantic-ui-react` dependency is already gone (Step 3½); this step drops `semantic-ui-less`, finishes changelog + supported-prop documentation, and records the bundle delta (−30 KB+ per the source's own estimates: 27 KB Dropdown + 4 KB Table + Popup). **SEMVER — DECIDED (2026-09-11): this ships as a MINOR.** The maintainers' call, and it is recorded here so the earlier reasoning is not mistaken for the decision. What the plan said before: Step 0 found no unsupported passthrough props in the wild, so on that evidence a minor — with the tier-2 decision as what could turn it major, since dropping a published propType is breaking even when nothing observed uses it. Steps 2 and 3 then did drop published propTypes (19 tooltip names, and `search`/`multiple`/`allowAdditions` with their companions), and the analysis in those steps argued for a major on that basis. The owners weighed it against the evidence that nothing in either corpus or the consumer-only record declares any of them, and chose minor. `docs/SUPPORTED-PROPS.md` still carries the full `Dropped` inventory per component, which is where a consumer looks regardless of the version number.

Expand Down
20 changes: 1 addition & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@
"react-types-17": "npm:@types/react@17.0.93",
"remark-gfm": "^3.0.1",
"remark-toc": "^9.0.0",
"semantic-ui-less": "2.5.0",
"serve": "^14.2.4",
"style-loader": "^3.3.4",
"stylelint": "^16.2.1",
Expand Down
5 changes: 1 addition & 4 deletions scripts/build-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ const ROOT = path.resolve(__dirname, '..');
const STYLE_DIR = path.join(ROOT, 'src/style');
const OUT_DIR = path.join(ROOT, 'public/static');

// Was a local `copyFileSync` pair; shared with `src/style/__tests__/setup.js` and the CSS fixture
// generator since 2026-09-14, which is also where the reasoning lives.
const { installThemeConfig: setupSemanticThemeConfig } = require('./install-theme-config.js');


async function compileLess(entryFile) {
const source = fs.readFileSync(entryFile, 'utf8');
Expand Down Expand Up @@ -55,7 +53,6 @@ async function buildFile(entryFile, outFile, { prefix = false } = {}) {

async function main() {
console.log('Building CSS...');
setupSemanticThemeConfig();

// Compile all styles (including Semantic UI) with .ui-render prefix
await buildFile(
Expand Down
8 changes: 0 additions & 8 deletions scripts/generate-semantic-css-fixture.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const path = require('path');
const less = require('less');
const postcss = require('postcss');
const LessPluginFunctions = require('less-plugin-functions');
const { installThemeConfig } = require('./install-theme-config.js');

const ROOT = path.resolve(__dirname, '..');
const STYLE_DIR = path.join(ROOT, 'src/style');
Expand All @@ -56,13 +55,6 @@ const WRITE_COMMAND = 'npm run css:fixture';
const LIVE_IMPORT = /^\s*&\s*\{\s*@import\s+(?:\([^)]*\)\s*)?"/;

async function compile (semanticSource) {
// REQUIRED, and its absence is what made this script pass locally and fail on CI: Semantic's
// definitions import `'../../theme.config'` from inside their own package, which ships only
// `theme.config.example`. Locally a prior `npx jest` had already placed ours through the same
// helper; on a clean checkout nothing had, and the compile died with
// `'../../theme.config' wasn't found`.
installThemeConfig();

const original = fs.readFileSync(SEMANTIC, 'utf8');
if (semanticSource !== null) fs.writeFileSync(SEMANTIC, semanticSource);
try {
Expand Down
52 changes: 0 additions & 52 deletions scripts/install-theme-config.js

This file was deleted.

30 changes: 29 additions & 1 deletion scripts/vendor-semantic-css.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@
* ONE-SHOT GENERATOR for §9.7-F1 step 4's second half: writes the CSS this repository takes over
* from `semantic-ui-less` into `src/style/vendor/`.
*
* IT CANNOT RUN AS THE REPOSITORY STANDS, and that is deliberate rather than an oversight. The
* final commit of step 4 removed `semantic-ui-less` from `devDependencies`, so re-deriving the
* vendored files means installing it again first:
*
* npm i --no-save semantic-ui-less@2.5.0 && node scripts/vendor-semantic-css.js
*
* It is kept because vendored third-party code without a reproducible derivation is a dead end:
* the next person who needs an upstream fix, or who doubts a value in `src/style/vendor/`, can
* reproduce the files exactly instead of editing them by hand. The parity gate
* (`css.semantic-parity.test.js`) tells them whether what they produced matches what ships.
*
* WHY THE ISOLATED COMPILE, AND NOT A SUBTRACTION FROM THE FULL BUILD. Subtraction is the obvious
* method and it is wrong here, for a reason worth stating because it is not obvious until it bites.
* Our own LESS extends Semantic's selectors (`input.less` does `&:extend(.input-tag all)` and
Expand Down Expand Up @@ -49,7 +60,24 @@ const fs = require('fs');
const path = require('path');
const less = require('less');
const LessPluginFunctions = require('less-plugin-functions');
const { installThemeConfig } = require('./install-theme-config.js');
/**
* Puts our `theme.config` where `semantic-ui-less` looks for it. Inlined here when the shared
* helper was deleted with the rest of the `node_modules` mutation machinery: this script is now the
* ONLY thing that needs it, and only while re-deriving the vendored files against a temporarily
* reinstalled package. Nothing in the build or the test run writes to `node_modules` any more.
*/
function installThemeConfig () {
const source = path.join(__dirname, '../src/style/override/theme.config');
const destination = path.join(__dirname, '../node_modules/semantic-ui-less/theme.config');
if (!fs.existsSync(source)) return;
const wanted = fs.readFileSync(source);
let current = null;
try { current = fs.readFileSync(destination); } catch (error) { /* absent on a fresh install */ }
if (current !== null && current.equals(wanted)) return;
const temporary = `${destination}.${process.pid}.tmp`;
fs.writeFileSync(temporary, wanted);
fs.renameSync(temporary, destination);
}

const OUR_OVERRIDE_DIR = path.join(__dirname, '../src/style/override');

Expand Down
15 changes: 15 additions & 0 deletions src/demo/markdowns/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,21 @@

#### Dependencies

- **Semantic UI is gone entirely — and nothing about how anything looks has changed.** The last
piece was the stylesheet: two Semantic CSS modules were still being compiled from
`semantic-ui-less` at build time. Their output now lives in this repository, and the package is
removed.

The evidence that this is invisible to you is the strongest kind available: the published
`static/all.css` is **byte-for-byte identical** — same SHA-256 — before and after. Not "looks the
same"; the same file.

What this means in practice: installing this library no longer pulls Semantic UI in any form,
and the LESS toolchain no longer writes into your `node_modules` during a build or a test run.
Attribution for the vendored CSS is in `THIRD-PARTY-NOTICES.md`, which ships with the package.



- **`semantic-ui-react` is no longer a dependency.** Installing this package used to install
Semantic UI React with it — a full copy that the bundle stopped using once `Table`, `Tooltip`
and `Select`/`Dropdown` were reimplemented in-house. It is gone from `dependencies`, so it is
Expand Down
1 change: 0 additions & 1 deletion src/style/__tests__/css.dropdown-contract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ const scopedRuleCount = node => rules.filter(rule => {

beforeAll(async () => {
// The same LESS invocation as `css.tooltip-contract.test.js`; `setup.js` (a global
// `setupFiles` entry) has already placed `theme.config` for semantic-ui-less.
const compiled = await less.render(fs.readFileSync(ENTRY, 'utf8'), {
filename: ENTRY,
paths: [STYLE_DIR],
Expand Down
1 change: 0 additions & 1 deletion src/style/__tests__/css.pipeline.parity.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,6 @@ let standaloneCss;

beforeAll(async () => {
// Same LESS invocation `scripts/build-css.js` uses. `src/style/__tests__/setup.js` (a global
// `setupFiles` entry) has already placed `theme.config` where semantic-ui-less expects it.
const result = await less.render(fs.readFileSync(ENTRY, 'utf8'), {
filename: ENTRY,
paths: [STYLE_DIR],
Expand Down
Loading
Loading