diff --git a/docs/SUPPORTED-PROPS.md b/docs/SUPPORTED-PROPS.md
index e2fe9ea1..d9492acb 100644
--- a/docs/SUPPORTED-PROPS.md
+++ b/docs/SUPPORTED-PROPS.md
@@ -95,8 +95,8 @@ CSS contract: `src/style/components/table.less` hangs EVERY cell's padding off `
| Prop | Meaning |
| --- | --- |
| `className` | Appended last, after `table`. `TableView` builds it from the meta `styles`/`fill`/`vertical` attributes; consumer metas add `as-layout`, `no-header`, `highlight-N-last` and the sticky-column tokens through the same channel. |
-| `inverted` | Dark table. Emitted as the `inverted` class, which `table.less` and `expand.less` both select on. Reached only from `ErrorTable.js` — a §9.9-H1 orphan, so the prop is kept but its fate is that deletion's to decide, not this step's. |
-| `striped` | Zebra rows, emitted as the `striped` class. Same single call site as `inverted`, and also genuinely styled — which is why neither was dropped with the rest. |
+| `inverted` | Dark table. Emitted as the `inverted` class, which `table.less` and `expand.less` both select on. **KEPT — decided at §9.9-H1 (2026-09-17), which is where step 1 left the call.** Its one in-repo caller was `ErrorTable.js`, and H1 deleted it, so the "Attributes at the call sites" table above no longer lists this prop — that table reports what the CODEBASE passes, not what the component accepts. The prop itself is untouched: `Table.js` still destructures it and still emits the class, the CSS still selects on it, and a consumer meta can still set it. Dropping it because our own last caller went away would have been a breaking change bought for nothing. |
+| `striped` | Zebra rows, emitted as the `striped` class. Same story as `inverted` exactly: genuinely styled (`table.striped tr:nth-child(2n)`), which is why neither was dropped with the rest, and still accepted after H1 removed their shared call site. |
**Consumed by every subcomponent (1).** All 6 share one implementation, so this list applies to each of them identically.
@@ -108,7 +108,7 @@ CSS contract: `src/style/components/table.less` hangs EVERY cell's padding off `
**Passthrough.** `style`, `colSpan`, `scope`, `id`, `data-*` and every event handler still reach the element untouched — they always did, because Semantic did not handle them either, so they ride the rest spread exactly as before. There is no `forwardRef`: nothing in `src` passes a ref to a table element, so the parameter would have had no caller.
-**Dropped (4) — the semver record.** Props semantic-ui-react handled that this implementation deliberately does not. All of them remain REACHABLE from a consumer meta: the component is rendered with open spreads (`...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...props`, `...rest`), so an attribute nobody anticipated on a meta node still arrives here as a prop. That is why the component strips them explicitly and warns once per prop in development. Stripping matters because the value would otherwise reach a real element as an attribute — a string-valued one lands lowercase (`verticalAlign="top"` rendered `verticalalign="top"`) and a boolean draws React's "Received `true` for a non-boolean attribute" warning, both of them junk the DOM contract's tripwires exist to keep out. Warning matters because a meta still carrying one would otherwise never learn it stopped working, and React's own unknown-prop warning cannot be relied on: it is silent for a lowercase name.
+**Dropped (4) — the semver record.** Props semantic-ui-react handled that this implementation deliberately does not. All of them remain REACHABLE from a consumer meta: the component is rendered with open spreads (`...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...rest`), so an attribute nobody anticipated on a meta node still arrives here as a prop. That is why the component strips them explicitly and warns once per prop in development. Stripping matters because the value would otherwise reach a real element as an attribute — a string-valued one lands lowercase (`verticalAlign="top"` rendered `verticalalign="top"`) and a boolean draws React's "Received `true` for a non-boolean attribute" warning, both of them junk the DOM contract's tripwires exist to keep out. Warning matters because a meta still carrying one would otherwise never learn it stopped working, and React's own unknown-prop warning cannot be relied on: it is silent for a lowercase name.
| Prop | Why it is gone |
| --- | --- |
@@ -123,12 +123,12 @@ Those four were the *published* ones — they had curated entries on this page w
| Component | Attributes at the call sites | Spreads | Rendered by |
| --- | --- | --- | --- |
-| `Table` | `className`, `inverted`, `striped` | `...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)`, `...props` | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` |
-| `Table.Header` | `className` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` |
-| `Table.HeaderCell` | `className`, `colSpan`, `key`, `style` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` |
-| `Table.Row` | `className`, `key` | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` |
-| `Table.Cell` | `className`, `colSpan`, `key`, `scope`, `style` | `...rest` | `core/components/ErrorTable.js`, `core/pages/main/components/LocalDraftTableRow.js`, `core/pages/main/components/TableView.js`, `core/pages/main/mapper.js` |
-| `Table.Body` | — | — | `core/components/ErrorTable.js`, `core/pages/main/components/TableView.js` |
+| `Table` | `className` | `...omitProps(props, ENGINE_PROPS, FIELD_ONLY_PROPS)` | `core/pages/main/components/TableView.js` |
+| `Table.Header` | `className` | — | `core/pages/main/components/TableView.js` |
+| `Table.HeaderCell` | `className`, `colSpan`, `key`, `style` | — | `core/pages/main/components/TableView.js` |
+| `Table.Row` | `className`, `key` | — | `core/pages/main/components/TableView.js` |
+| `Table.Cell` | `className`, `colSpan`, `key`, `style` | `...rest` | `core/pages/main/components/LocalDraftTableRow.js`, `core/pages/main/components/TableView.js`, `core/pages/main/mapper.js` |
+| `Table.Body` | — | — | `core/pages/main/components/TableView.js` |
| `Table.Footer` | — | — | *nothing* |
`mapper.js`'s spread onto `Table.Cell` is a meta node's whole rest bag and is still unfiltered at the call site — the filter is now inside the cell, which is why it is safe. All three unfiltered boundaries on this surface are now closed inside the component: the table cell at step 1, the tooltip at step 2 part 3, and the dropdown at step 3 part 2, which strips twice — once in the wrapper and once in `Listbox` at the element.
diff --git a/docs/UPGRADE-PLAN.md b/docs/UPGRADE-PLAN.md
index 8be5a614..c7dfdc86 100644
--- a/docs/UPGRADE-PLAN.md
+++ b/docs/UPGRADE-PLAN.md
@@ -29,7 +29,7 @@ Standing decisions:
2. **`moment` stays** as a peer dependency — no dayjs migration. The requested native-replacement feasibility analysis (§9.7-F2) concludes it is possible and well-bounded (~400–600 lines behind an adapter seam), but it is parked behind an explicit decision gate; the only near-term action is funneling usage through a single internal adapter module.
3. **The published JavaScript API stays a single callable value.** `require('eis-ui-render')` returns the function directly (there is no runtime `.default` or `.UIRender` property); TypeScript models this with `export =`, which also supports a default import when module interop is enabled. Named interfaces and type aliases remain available through the merged namespace, but a named `UIRender` value export is not restored. Script-tag/global typing remains tied to the separate UMD-support gate in §10.
-The **project-structure analysis** (§9.9) found: the documented `ui-*-pack` alias system is dead (zero imports in the codebase — CLAUDE.md was stale on this; fixed alongside this plan), the engine lives under an app-boilerplate-era `core/pages/main/` path, and 12 orphan components (9 direct, plus the `ErrorTable`/`Square`/pack-`TabList` cluster reachable only from other orphans) plus a dead `style/unused/` tree can simply be deleted. The workstream re-homes the engine to `core/engine/` ahead of the §9.3 decomposition, isolates the demo, and locks layer direction in with lint.
+The **project-structure analysis** (§9.9) found: the documented `ui-*-pack` alias system is dead (zero imports in the codebase — CLAUDE.md was stale on this; fixed alongside this plan), the engine lives under an app-boilerplate-era `core/pages/main/` path, and 12 orphan components (9 direct, plus the `ErrorTable`/`Square`/pack-`TabList` cluster reachable only from other orphans) plus a dead `style/unused/` tree ~~can simply be deleted~~ **— deleted 2026-09-17 (§9.9-H1).** Those files are no longer in the tree, so the counts here are a historical record rather than files to go looking for; the engine `TabList` under `core/pages/main/` survives (the mapper resolves it) and the pack `Tabs.js` is still present, so the pack/engine duplication is only half resolved. The workstream re-homes the engine to `core/engine/` ahead of the §9.3 decomposition, isolates the demo, and locks layer direction in with lint.
§9.6 defines a **full TypeScript migration** (infra step → utils/contract → components/modules riding other workstreams → engine last → guarded public-API switchover), which **retires the `prop-types` runtime dependency** as its exit criterion (E5 — React 19 ignores propTypes anyway, and the shapes currently ship in the production bundle). The plan also adds **Appendix C** — a consolidated checklist of every verification it depends on, so no check lives only in review discussions.
@@ -135,7 +135,7 @@ A second audit pass — against the working tree, the build configs, a full test
| 11 | **Hybrid dependency model:** everything except `react`/`react-dom`/`moment` is **both** bundled into the UMD **and** declared in `dependencies` — npm hosts install full copies of the form stack, `rc-picker` etc. that the bundle never uses. **The SUIR half is closed:** `semantic-ui-react` was the entry whose own `react ≤^18` peer capped the host's React, and §9.7-F1 step 3½ removed it. The pattern itself remains for the other packages, and ~~`react-final-form` 6.5.9 is now the only remaining `react ≤^18` peer~~ — **inventory corrected 2026-09-17: there are two, not one.** A scan of every installed package's `peerDependencies.react` finds both `react-final-form` 6.5.9 and `react-final-form-arrays` 3.1.4 declaring the same `^16.8.0` / `^17.0.0` / `^18.0.0` cap, and both are in our own `dependencies`. Every other react peer in the tree is open-ended (`rc-picker` and the `rc-*` packages `>=16.9.0`; `react-markdown` `>=16` and `react-syntax-highlighter` `>= 0.14.0`, which are devDependencies anyway). The remediation is unchanged — §9.7-F4 bumps the final-form packages together — but the same off-by-one wording stands at §8-FP2a and in the `react-19-advisory` comment in `ci.yml`, and both need the second package added. | §9.7-F1 step 3½ — done, §9.7-F3 gate, §8 |
| 12 | **moment IS on the public API.** `README:12,18` and `docs.md:24–25` document that the library "accepts `moment` instances on its API" (with cross-copy `instanceof` caveats), and rc-picker callbacks leak moment objects outward — `onSelect` ← `onCalendarChange` (`InputDate.js:96`) plus passthrough `disabledDate`/`cellRender`/… via `{...props}` (`InputDate.js:89`). The earlier "moment never crosses the public API" claim was wrong; corrected in §9.7-F2. | §9.7-F2 |
| 13 | **Quality baselines measured (2026-07-21):** tests 76 suites / 1215 tests green in 8.3 s (zero snapshots); `lint:css` clean; `npx eslint src` fails (11 errors / 28 warnings; no lint script exists); `npm audit --omit=dev` **0** vulnerabilities; full `npm audit` **20** (2 critical, 7 high — dev tooling); **13 devDependencies with zero references** in code/configs: `tsconfig-paths-webpack-plugin`, `backoff`, `history`, `html-loader`, `minimist`, `path-browserify`, `postcss-scss`, `raw-loader`, `remark-loader`, `rimraf`, `sass`, `sass-loader`, `webpack-node-externals` (verify `dot-prop-immutable` too). | Phase 0.9, §9.9-H1 |
-| 14 | **Orphan set is 12, not 9:** the 9 direct orphans re-confirmed, plus `ErrorTable` (imported only by orphan `ErrorContent`), `Square` (only by orphan `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). ~~Engine `tester/` fixtures are referenced by nothing.~~ — the `tester/` pair has since been deleted (§9.9-H1). | §9.9-H1, §9.2 |
+| 14 | ~~**Orphan set is 12, not 9:**~~ **CLOSED 2026-09-17 (§9.9-H1).** All 12 are deleted from `src/core/components/` (`Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `ErrorTable`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Square`, `TabList`, `Tags`) — the count is a historical record now, not a set of files to look for; `ErrorContent` imported `ErrorTable`, so those two had to go together. The engine `TabList` (`src/core/pages/main/components/TabList.js`) is untouched and is still what `mapper.js:33` resolves, and the pack `Tabs.js` survives, so the pack/engine duplication is only half resolved. Deleting `ErrorTable` also removed the only in-repo call site of `Table`'s `inverted`/`striped`; the H1 decision was to **keep** both props — `Table.js` still destructures them, the CSS still selects on them, and consumer meta can still set them — they merely no longer appear in the generated "Attributes at the call sites" table in `docs/SUPPORTED-PROPS.md`, which reports what the codebase passes, not what the component accepts. The finding as it stood: the 9 direct orphans re-confirmed, plus `ErrorTable` (imported only by orphan `ErrorContent`), `Square` (only by orphan `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). ~~Engine `tester/` fixtures are referenced by nothing.~~ — the `tester/` pair has since been deleted (§9.9-H1). | §9.9-H1, §9.2 |
| 15 | **Hardcoded version strings** `data-version="0.34.2"` in `AppWrapper.js:10` and `types/UIRender.tsx:76` — drift on every release; should come from `package.json` at build time. | §9.9-H6 |
| 16 | **One application's field names were compiled into the engine.** *Resolved.* When a popup could not resolve its `relativePath`, `rules.js` probed the consumer's data for two literal paths belonging to a single host app and adopted whichever matched. It never matched in 1928 tests — but for data that *did* carry one of those keys it bound the popup to a table the opener had nothing to do with (the two candidates were tried in order, so a row from the second table got the first), re-creating by data shape the rebinding that commit `89bac56` removed. Deleted; an unresolved scope now warns instead of guessing, and meta states the scope either by declaring the `Popup` inside the row or via `{relativePath}` in the `popupOpen` args. Three comment/JSDoc examples naming the same fields were neutralised. | §9.3 |
| 17 | **The date field is selected by `type`, and its view constant is dead.** `renders.js:38-40` overrides whatever the view switch chose whenever `type === 'date'`, so a date picker is reachable from meta as `{view: 'Input', type: 'date'}` — any `view` the mapper does not claim works, which is why grepping example metas for `"view": "Date"` finds nothing. Meanwhile `FIELD.TYPE.DATE` (`modules/form/constants.js:16`) has **zero readers** — `view: 'Date'` renders the "field does not exist" placeholder. Note `FIELD.RENDER.DATE` (`variables/fields.js:47`) is a different, live thing: a read-only date formatter. Either give the view constant a branch or delete it; the two-`'Date'` split is a docs-truth trap. The rendered picker is an rc-picker text input plus a JS overlay with no native `type="date"` attribute — the reason the §5 overlay QA item exists. | §9.9-H1, §9.9-H2 |
@@ -261,8 +261,12 @@ container while the bundled dependencies keep their own native `document` listen
(§2.6-17); the rendered DOM is an rc-picker text input plus a JS overlay with no native `type="date"`
attribute, which is precisely why this item mattered for React 17.
- [x] **Tabs, Collapse, Expand** — tab switch swaps panel content; example rows expand and collapse.
- *Carousel autoplay is moot: `Carousel` is not registered in `mapper.js` and is one of the §2.6-14
- orphans slated for deletion under §9.9-H1, so no meta can render it.*
+ ~~*Carousel autoplay is moot: `Carousel` is not registered in `mapper.js` and is one of the §2.6-14
+ orphans slated for deletion under §9.9-H1, so no meta can render it.*~~ **CLOSED 2026-09-17.** The
+ deletion landed: `src/core/components/Carousel.js` is gone — §9.9-H1 removed all 12 §2.6-14 orphans —
+ so there is no Carousel left to exercise. The pack `Collapse` (`src/core/components/Collapse.js`) went
+ in the same batch; it was never registered in `mapper.js` either, and the expand/collapse behaviour
+ verified here is the live `Expand` / `ExpandList` path, which the deletion did not touch.
- [x] **Table: sorting, pagination, inline edit rows** — sorting cycles asc → unsorted → desc on a header
whose table declares `sorts` (`TableView.js` gives a header `onClick` only then; the `adminCosts`
table inside the `all` example's Admin Expenses section is the one that does), verified by both the
@@ -327,7 +331,7 @@ check, ~~and the §10 decision on whether React 17 ships as its own release~~. *
- **Steps 1, 3-5 landed together** (they cannot be split — RTL ≥13 requires React ≥18): React and React DOM 18.3.1, RTL 16 with an explicit `@testing-library/dom` 10 peer, `@types/react`/`@types/react-dom` 18, peers widened to `^16.14.0 || ^17.0.0 || ^18.0.0`. The type-consumer matrix was repointed — `@types/react` is 18 now, so the 16 and 17 slots come from locked aliases — and all six interop/CommonJS combinations still pass.
- **The RTL 12→16 migration cost two test files, not the 1-2 weeks budgeted here.** Both failures shared the predicted cause: under `createRoot` a state update outside `act()` is no longer flushed before the assertion. `ProgressBar` drove its own `setTimeout` via `jest.runAllTimers()`; `Slider` dispatched a raw `MouseEvent` to reach `onPointerDown`. No product code was involved. **140 suites / 1946 tests, zero `act` warnings.**
-- **Step 1's `defaultProps` prediction was right, and scanning jest output would have missed it:** the suites mock `console.error`, so the warnings were swallowed and the output looked clean. A probe with a recording spy showed React 18.3 warns for both `TooltipPop` and `Image`; both moved to default parameters (`Image` now forwards `decoding`/`loading` explicitly). `ImageSwatch` keeps its `defaultProps` — it is an orphan nothing renders, so it cannot warn; deletion is §9.9-H1's job. One test asserted `TooltipPop.defaultProps.delay`, i.e. the mechanism rather than the behaviour, and was rewritten against what Semantic actually receives.
+- **Step 1's `defaultProps` prediction was right, and scanning jest output would have missed it:** the suites mock `console.error`, so the warnings were swallowed and the output looked clean. A probe with a recording spy showed React 18.3 warns for both `TooltipPop` and `Image`; both moved to default parameters (`Image` now forwards `decoding`/`loading` explicitly). ~~`ImageSwatch` keeps its `defaultProps` — it is an orphan nothing renders, so it cannot warn; deletion is §9.9-H1's job.~~ **CLOSED 2026-09-17.** §9.9-H1 deleted `src/core/components/ImageSwatch.js`, so that third `defaultProps` site was resolved by deletion rather than conversion; the sentence as it stood on 2026-08-20 is struck through. One test asserted `TooltipPop.defaultProps.delay`, i.e. the mechanism rather than the behaviour, and was rewritten against what Semantic actually receives.
- **Step 2 done:** `src/main.jsx` mounts via `createRoot`, so the demo renders with automatic batching rather than 17-compat mode.
- **Step 6, the batching pass, found no regression.** The unit suite now runs under `createRoot` (batching in effect) and is green. In the browser, on the demo: the form flow — date picker overlay, validation clearing, both submit branches, `addData` committing a row while the draft clears, `removeData` taking the right row — all behave as they did on 17; and the cascading Select, the most batching-sensitive path in the library because its reset calls `onChange` from inside an effect, correctly reset Product from `Alpha` to `Delta` when Category changed, with the dependent table following. No console errors. `flushSync` was not needed anywhere.
- **Step 7:** the risky interactive views (Dropdown, Popup, rc-picker, Tabs, Table sorting/pagination/inline edit) were exercised on 18 through the §5 checklist. The two console warnings the demo does emit are React Router v6 future-flag notices — demo-only, unrelated to React 18.
@@ -348,15 +352,15 @@ check, ~~and the §10 decision on whether React 17 ships as its own release~~. *
*Console volume is small and misleading.* React aggregates per lifecycle kind and dedupes per component, so the demo produced **one** warning (`Expand, Tabs`) and a form-heavy meta produced **three** (one per lifecycle: `UNSAFE_componentWillReceiveProps` naming `Expand, InputNative, TableView, Tabs, UIRender, WithForm`; `UNSAFE_componentWillMount` and `UNSAFE_componentWillUpdate` naming `UIRender`). The count to plan against is the components, not the messages.
-*The work item is 29 `UNSAFE_*` call sites across 14 files*, one of which (`Carousel`) is an orphan that cannot warn and is deleted under §9.9-H1:
+~~*The work item is 29 `UNSAFE_*` call sites across 14 files*, one of which (`Carousel`) is an orphan that cannot warn and is deleted under §9.9-H1:~~ **UPDATED 2026-09-17** — §9.9-H1 landed, so the two orphan rows below no longer exist in the tree. *The work item is now 26 `UNSAFE_*` call sites across 12 files* (`Carousel`'s 1 site and `Collapse`'s 2 went with the deleted files; the pre-deletion count is struck through above):
| Sites | File | Note |
|---|---|---|
| 12 | `pages/main/rules.js` | the engine, including the prototype-patching machinery — §9.3 step 5 is exactly this |
| 4 | `modules/form/utils.js` | `WithForm` plus patched lifecycles |
-| 2 | `components/Collapse.js` | |
+| ~~2~~ 0 | ~~`components/Collapse.js`~~ | **DELETED 2026-09-17** under §9.9-H1 — orphan, never registered in `mapper.js`; these 2 sites no longer exist. |
| 1 each | `pages/main/components/{Tabs,TableView}.js`, `modules/form/views/AutoSave.js`, `components/utils/interactions.js`, `components/{Tabs,ProgressSteps,ProgressBar,InputNative,Expand,Counter}.js` | leaf props→state derivations, mechanically convertible (§2.3) |
-| 1 | `components/Carousel.js` | orphan — not registered in `mapper.js`, deleted under H1 |
+| ~~1~~ 0 | ~~`components/Carousel.js`~~ | orphan — not registered in `mapper.js`; **DELETED 2026-09-17** under §9.9-H1, so this site no longer exists. |
Six of those components were observed warning in practice (`Expand`, `Tabs`, `InputNative`, `TableView`, `UIRender`, `WithForm`); the rest are live code that simply needs the right prop change to surface. Outside StrictMode, React 18.3 emits **zero** warnings for any of them — the renamed `UNSAFE_*` forms are fully supported, which is why the upgrade did not need this work.
@@ -372,7 +376,7 @@ Sequencing: StrictMode becomes the *acceptance criterion* of workstream §9.3 (e
| Legacy context (removed in 19) | ✅ none | Zero `contextTypes` declarations. **Observed side effect of the removal (2026-08-20):** React 18 invoked every function component as `Component(props, secondArg)`, where `secondArg` was the frozen `emptyContextObject` (`{}`) returned by `getMaskedContext`; 19 passes literal `void 0`. Impact was **test-only** — four assertion sites across three test files pinned that second argument with a trailing `expect.anything()` (the other two of the five suites failing on 19 were the warning-text ones in the row below). All are now version-agnostic: assert `mock.calls[0][0]`, never the positional pair, because `toHaveBeenCalledWith(props)` fails on **arity** against a real `(props, undefined)` call. No `src/` code reads a second parameter, and the `key={typeof index !== 'object' ? index : undefined}` guard at `src/core/ui-render/Render.js:47` exists precisely to keep that legacy `{}` out of the `key` slot when React (rather than a mapper) calls `Render`. |
| `findDOMNode` (removed in 19) | ✅ none in `src/` | Moot for SUIR once §9.7-F1 steps 1–3 land. |
| `ReactDOM.render` (removed in 19) | 🔶 fixed by Phase 2 | Demo entry only. |
-| `defaultProps` on function components (ignored in 19) | 🔶 3 occurrences | `TooltipPop.js:23`, `ImageSwatch.js:27`, `Image.js:27` → convert to default parameters (done in Phase 2 — §6 step 1). `ImageSwatch` is an orphan slated for deletion (§9.9-H1). |
+| `defaultProps` on function components (ignored in 19) | ~~🔶 3 occurrences~~ ☑ **none left — CLOSED 2026-09-17** | `TooltipPop.js:23`, `ImageSwatch.js:27`, `Image.js:27` → convert to default parameters (done in Phase 2 — §6 step 1). ~~`ImageSwatch` is an orphan slated for deletion (§9.9-H1).~~ **CLOSED 2026-09-17:** `TooltipPop` and `Image` were converted in Phase 2 and `src/core/components/ImageSwatch.js` was deleted by §9.9-H1, so none of the three sites survive — the file list above is the historical record of where they were. |
| `propTypes` (validation removed entirely in 19) | 🔶 40 importing files | No crash — silently ignored. Fully resolved by §9.6-E5: propTypes are deleted per TS conversion and the `prop-types` dependency is removed at the end. **Observed on 19:** the two `is marked as required` warnings that 18 emits for `formProps` and `instance` (attached by `withFormSetup`, `src/core/modules/form/utils.js:483–484`) simply vanish. They report a **real unmet prop contract** that 19 silences — the disappearance is loss of a diagnostic, not a fix, so E5 must not read it as the contract being met. |
| Dev warning text changed in 19 (no component stack, no `Warning:` prefix) | ~~🔶 **new finding**~~ ☑ **CLOSED 2026-09-17.** Match-on-message-body is implemented and documented at both console probes: `src/demo/pages/__tests__/Examples.registry-and-rendering.test.js` (comment above `EXPECTED_CONSOLE_ERROR_PATTERNS`; all 9 allowlist entries are message-body fragments) and `src/demo/pages/__tests__/NestedDataKind.interactions.test.js`. | React 18's dev `printWarning` appended `getStackAddendum()` as an extra `%s` argument and prefixed `"Warning: "`; 19 emits the bare message. Consequence: any assertion — or console-probe allowlist — that matches a **component name or file path inside warning text** changes meaning across versions and is unreliable as a gate; match the message body only. This is the same recording-spy technique that caught the `defaultProps` warnings in Phase 2 (§6 step 1 notes), so it applies to the audit method, not just to committed tests. |
| `UNSAFE_*` lifecycles | ✅ still supported in 19 | But StrictMode-hostile; §9.3 is the prerequisite for StrictMode/concurrent adoption on 19, not for the 19 flip itself. |
@@ -407,7 +411,7 @@ Every workstream below is a series of small, independently shippable, reversible
### 9.2 Workstream A — Class → hooks migration (leaf-first)
-**Motivation:** 21 class components, most carrying `UNSAFE_componentWillReceiveProps` that is a simple props→state derivation. Hooks versions are smaller, StrictMode-safe, and testable.
+**Motivation:** ~~21 class components~~, most carrying `UNSAFE_componentWillReceiveProps` that is a simple props→state derivation. Hooks versions are smaller, StrictMode-safe, and testable. **Count superseded 2026-09-17 (§9.9-H1):** the deletion took **4 class components** out of this population — `Carousel`, `Collapse`, and the *two* declared in `Square.js` — so the figure above predates it. The exact replacement number is deliberately not written here: "21" was measured with a methodology this plan never recorded (the tree then held 39 `class … extends Component` declarations across 24 files, 35 across 21 files now), so re-derive it against a stated rule when Workstream A actually starts rather than inheriting an unreproducible count. What is firm is the delta and the `UNSAFE_*` figure below, both measured.
**Rules of engagement:**
- One component per PR. Refactor only components with existing tests (write them first otherwise).
@@ -417,8 +421,8 @@ Every workstream below is a series of small, independently shippable, reversible
**Suggested order (dependency- and risk-sorted):**
-1. Leaf presentational, mechanical conversions: `Expand`, `Counter`, `ProgressBar`, `ProgressSteps`, `InputNative`, `Tabs` (components pack; gate `Tabs` on the H6 duplicate audit first). `Carousel` and `Collapse` turned out to be orphans (§9.9-H1) — **delete instead of migrating**; `Square` is already a function component, and the pack `TabList` joined the orphan list (mapper uses the engine copy — §2.6-14).
-2. Function-component `defaultProps` → default parameters: **done in Phase 2** (`TooltipPop`, `Image` — §6 step 1; React 18.3 warns on them); the third site, `ImageSwatch`, is an orphan resolved by deletion (§9.9-H1).
+1. Leaf presentational, mechanical conversions: `Expand`, `Counter`, `ProgressBar`, `ProgressSteps`, `InputNative`, `Tabs` (components pack; gate `Tabs` on the H6 duplicate audit first). ~~`Carousel` and `Collapse` turned out to be orphans (§9.9-H1) — **delete instead of migrating**; `Square` is already a function component, and the pack `TabList` joined the orphan list (mapper uses the engine copy — §2.6-14).~~ **CLOSED 2026-09-17** for those four: `Carousel`, `Collapse`, `Square` and the pack `TabList` are deleted, so the two `UNSAFE_*` migrations `Carousel` and `Collapse` carried are off this workstream permanently and the engine `TabList` (`src/core/pages/main/components/TabList.js`, the copy `mapper.js` uses) is untouched. `Tabs` is **still open**: the pack copy `src/core/components/Tabs.js` survived the deletion, so the H6 duplicate is only half resolved and the gate above still applies.
+2. Function-component `defaultProps` → default parameters: **done in Phase 2** (`TooltipPop`, `Image` — §6 step 1; React 18.3 warns on them); ~~the third site, `ImageSwatch`, is an orphan resolved by deletion (§9.9-H1).~~ **CLOSED 2026-09-17** — `ImageSwatch` is deleted, so the third site is resolved and this step has nothing left in it.
3. Page-level: `pages/main/components/Tabs`, `TableView`, `LocalDraftTableRow`.
4. Module-level: `AutoSave`, `ToggleField`, `asInputDateField`, `Upload` views.
5. **Not in this workstream:** `rules.js`, `form/utils.js`, `mapper.js`, `Render.js` → §9.3.
@@ -647,7 +651,7 @@ The wrappers' external APIs are the contract; `mapper.js` and meta authors never
**What it got wrong, or under-stated.** (a) The tier-1 surface was published as "5 prop names across 7 components" including `verticalAlign` — but `verticalAlign` was tier-1 only because *our own* `LocalDraftTableRow` passed it, never because a meta did, and it did nothing. The real exercised surface is four names (`children`, `className`, `style`, `colSpan`), and "tier 1" needs reading as "something passes it", not "something depends on it". (b) `fixedHeader` was described as unused; it is also **non-functional** — both classes it emits have zero occurrences in `static/all.css` and in `src/style`, so it rendered two unstyled `
`s. Deleted with its test rather than reimplemented. (c) The step notes did not anticipate that removing the wrapper would require restructuring the `docs:props` generator: `Table` cannot stay in a `WRAPPERS` list that asserts a SUIR import, so the page now has two kinds of section (in-house vs wrapper) and the in-house half derives the element per subcomponent, the shared destructure, and the *absence* of any SUIR reference. That was the largest single piece of work in the step, and it is worth budgeting for again at steps 2 and 3. **The budget was spent and the restructuring is finished (verified 2026-09-17):** `scripts/generate-wrapper-prop-reference.js:89` is now `const WRAPPERS = []` and `Table`, `TooltipPop` and `Dropdown` are all `IN_HOUSE` entries (`:96` onward) — the comment at `:80` saying "Only `Dropdown` is still a wrapper" is itself superseded by `:84`, which records that it now renders the in-house `Listbox` and has moved to `IN_HOUSE`. The two lists check each other: `readInHouse` throws if an `IN_HOUSE` entry reaches it.
- **Decisions recorded.** `verticalAlign` DROPPED (SUIR emitted `top aligned`; no loaded CSS selects on `aligned`, and no `.top` rule can match a cell, so the 15 cells that asked for it already rendered at the `
` default — pixel-identical, and both call sites went with it). `inverted`/`striped` KEPT (the only modifiers any call site passes, and both genuinely styled). `fixedHeader` DELETED. `as`/`celled`/`textAlign` DROPPED. `TableView.js`'s `sellStyles` typo resolved. Semver: on this evidence the step is **minor** — every dropped prop is either unused, unstyled, or both, and every prop any meta in either corpus passes still works; Step 5 records the call for the whole exit. **RECORDED 2026-09-11: the owners chose MINOR for the whole exit** — `docs/SUPPORTED-PROPS.md:265` ("THE SEMVER CALL IS MADE: the owners chose to ship the exit as a MINOR (2026-09-11)"), and step 5 reasons from it at line 846 ("a MINOR that removes published props has to be actionable"). The forward reference is discharged; the bump itself is still pending only because the release is deferred.
+ **Decisions recorded.** `verticalAlign` DROPPED (SUIR emitted `top aligned`; no loaded CSS selects on `aligned`, and no `.top` rule can match a cell, so the 15 cells that asked for it already rendered at the `
` default — pixel-identical, and both call sites went with it). `inverted`/`striped` KEPT (~~the only modifiers any call site passes, and~~ both genuinely styled — **updated 2026-09-17:** §9.9-H1 deleted `ErrorTable.js`, which WAS that call site, so no in-repo caller passes them any more and the generated "Attributes at the call sites" table no longer lists them; the props themselves are untouched and the KEPT decision stands, which is exactly the call this step deferred to H1). `fixedHeader` DELETED. `as`/`celled`/`textAlign` DROPPED. `TableView.js`'s `sellStyles` typo resolved. Semver: on this evidence the step is **minor** — every dropped prop is either unused, unstyled, or both, and every prop any meta in either corpus passes still works; Step 5 records the call for the whole exit. **RECORDED 2026-09-11: the owners chose MINOR for the whole exit** — `docs/SUPPORTED-PROPS.md:265` ("THE SEMVER CALL IS MADE: the owners chose to ship the exit as a MINOR (2026-09-11)"), and step 5 reasons from it at line 846 ("a MINOR that removes published props has to be actionable"). The forward reference is discharged; the bump itself is still pending only because the release is deferred.
**Gates.** The markup-independent behavioural layer was run BEFORE the snapshots were regenerated and was green on the new markup while the old baseline was still red: `examples.behavior-contract` + `UIRender.table-behavior` + `UIRender.listbox-behavior` + `UIRender.overlay-behavior`, 4 suites / 104 tests. Then one `-u`, then all three React legs at identical totals (158 suites / 2396 tests / 38 snapshots on 16.14, 17 and 18.3), per-file coverage (`Table.js` at 100/100/100/100, its own new threshold), `lint:js`, `lint:css`, `docs:views:check`, `docs:props:check`. The DOM zero-tripwires (`FIXED_PROP_LEAKS`, `FIXED_MARKUP_JUNK`) stayed at zero and the `name`-only-on-a-form-control invariant stayed at 57. **Still owed and not delivered by this step:** the mandatory Playwright visual/keyboard/a11y suite (§9.7-F1.3) does not exist in the repo, so the `.ui.table` padding contract is verified here by reading the compiled CSS and by class-string assertions, not by rendered pixels.
@@ -878,7 +882,7 @@ One measurement worth carrying into Step 4's estimate: the five loaded semantic-
| `TextDateValue.js:8` | `moment(value).format(config.dateFormat \|\| 'DD/MM/YYYY')` | same |
| `InputDate.js:58–65` | `moment(value)` parse; `moment(str, dateFormat).format('YYYY-MM-DD')` normalize-on-change | token **parser** (parse-by-format) |
| `InputDate.js:10,88,94` | rc-picker with `generateConfig` from `rc-picker/lib/generate/moment`; input `format={[dateFormat, 'YYYY-MM-DD']}` | custom `GenerateConfig` |
-| `time.js` (`formatTime`, `toHours`) | `moment(t).format(f)` | **no production callers** (referenced only by `time.test.js`); `formatDuration` there is already moment-free |
+| `time.js` (~~`formatTime`, `toHours`~~ — **DELETED 2026-09-17**) | ~~`moment(t).format(f)`~~ — none remain | ~~**no production callers** (referenced only by `time.test.js`); `formatDuration` there is already moment-free~~ — **CLOSED 2026-09-17:** both functions were deleted on `h1-dead-weight`, together with `time.js`'s thereby-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and the 12 tests that covered them. Consequence for this analysis: `moment` is now imported **nowhere** under `src/core/utils/` — the remaining `formatDuration` (and `.shortEnglish`) was already moment-free, so the whole utils layer is moment-free and only the four component/rc-picker rows above are left on this table |
**Contract constraints (corrected by the re-audit — less favorable than first assessed, §2.6-12):** moment **does** cross the public API. The project's own docs promise it (`README:12,18` — the API "accepts `moment` instances", with cross-copy `instanceof` caveats; `docs.md:24–25`); `InputDate` feeds any `props.value` into `moment(value)` (instances included); and rc-picker callbacks emit moment objects outward — `onSelect` (wired to `onCalendarChange`, `InputDate.js:96`) and every passthrough such as `disabledDate`/`cellRender` (`{...props}` at `InputDate.js:89`). Also contract: `dateFormat` **tokens** are moment syntax (note the `dateFormat` *prop* ~~is currently dead — §2.6-2 — only the context default applies until §9.4 fixes the wiring~~ — **CLOSED 2026-09-17:** §9.4 repaired the wiring, as §9.4-C-config already records. `rules.js:240` declares `dateFormat: type.String`, `:339` destructures it and `:384` publishes it via ``; `providers/ConfigOverride.jsx` merges it into `ConfigContext`; `components/TextDateValue.js:21` reads `moment(value).format(dateFormat || config.dateFormat || 'DD/MM/YYYY')`, so an explicit prop wins over the context default. Pinned by `pages/main/__tests__/UIRender.config-channel.test.js:63`, "formats values with the dateFormat it was given, not the context default"). Consequence: a native adapter changes observable callback types and is a **breaking change** for hosts that use those callbacks or pass instances — not the drop-in swap the first draft assumed.
@@ -897,7 +901,7 @@ One measurement worth carrying into Step 4's estimate: the five loaded semantic-
Feasible and well-bounded (**~400–600 lines + tests; ~1–2 weeks + regression — less with option (a) for the picker**) because *internal* usage is narrow — but per §2.6-12 the **external** surface does leak moment, so the flip is expected to be **semver-major** (or shipped behind a compatibility shim). And since moment is externalized, the library itself gains nothing — the benefit accrues only to host applications that want moment out of *their* bundles.
-- **Now (non-breaking, cheap):** funnel the three component call-sites through a single internal `dateAdapter` module (an extension of `ui-utils-pack/time.js`), and document the supported `dateFormat` token subset. Also: `formatTime`/`toHours` have no production callers — delete or fold into the adapter.
+- **Now (non-breaking, cheap):** funnel the three component call-sites through a single internal `dateAdapter` module (an extension of `ui-utils-pack/time.js`), and document the supported `dateFormat` token subset. ~~Also: `formatTime`/`toHours` have no production callers — delete or fold into the adapter.~~ — **CLOSED 2026-09-17:** done on `h1-dead-weight`, and the choice was **delete**, not fold-in — both functions went, along with `time.js`'s thereby-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests (jest 2540 → 2528). `src/core/utils/` now imports `moment` nowhere, so the `dateAdapter` seam would be extending a moment-free `time.js`. The rest of this bullet — funnelling the three component call-sites through `dateAdapter`, and documenting the supported `dateFormat` token subset — is **still open**.
- **Later (at a future breaking-change window, or on host demand):** implement the native adapter behind the seam; flip the internal default only when the parity suite is green; demote `moment` to `peerDependenciesMeta.optional` so hosts *may* drop it — hosts that keep it see zero change.
- **Triggers to revisit:** hosts asking to shed moment; rc-picker deprecating its moment config; `Temporal` reaching Baseline.
@@ -956,8 +960,8 @@ The form stack splits into a React-free core and React bindings:
|---|---|
| 1 | **The `ui-*-pack` alias system is dead** — **0 imports** use `ui-react-pack`/`ui-modules-pack`/`ui-utils-pack`; no webpack config defines those aliases (only `theme.config` and `process` are aliased); `tsconfig-paths-webpack-plugin` (devDep) is referenced by no config. CLAUDE.md's alias claim was stale (fixed alongside this plan). Actual convention: relative imports + a small `components` barrel used by the engine for `{ cn, type }`. |
| 2 | **The engine lives under `core/pages/main/`** — an app-era address for the library's heart (`rules.js`, `mapper.js`, `Data.js`, `dataKindPush.js`, engine-local `utils.js` and `components/`), while the recursive renderer sits separately in `core/ui-render/`. Images (`static/images`) also live inside it; the `tester/` dev fixtures that used to were deleted under H1. |
-| 3 | **12 orphan components**: 9 direct — `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` (no imports, no JSX usage, not registered in `mapper.js`) — plus a cluster reachable only from orphans: `ErrorTable` (imported solely by `ErrorContent`), `Square` (solely by `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`). |
-| 4 | **Dead style trees:** `src/style/unused/` (10 files incl. `_policy`/`_classic` pairs) imported by nothing; `override/` carries `.variables`/`.overrides` for semantic modules that are commented out (§9.7-F1 step 4 finishes that job); icomoon build artifacts (`fonts/icons/demo.html` + demo files, ~1.2k lines) checked in. |
+| 3 | ~~**12 orphan components**: 9 direct — `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` (no imports, no JSX usage, not registered in `mapper.js`) — plus a cluster reachable only from orphans: `ErrorTable` (imported solely by `ErrorContent`), `Square` (solely by `Carousel`), and the pack `TabList` (mapper uses the engine copy, `mapper.js:33`).~~ **CLOSED 2026-09-17 — all 12 deleted from `src/core/components/` under H1.** The list above is history, not a file inventory: none of those files exist any more, so nobody should go hunting for them. Two details the deletion settled: `ErrorTable` could not go alone (`ErrorContent` was its only importer, so both went in one move), and only the **components-pack** `TabList` went — the engine copy at `src/core/pages/main/components/TabList.js`, which is what `mapper.js:33` resolves, survives untouched. |
+| 4 | **Dead style trees:** ~~`src/style/unused/` (10 files incl. `_policy`/`_classic` pairs) imported by nothing~~ — **CLOSED 2026-09-17:** the directory and all 10 files are deleted under H1; `override/` carries `.variables`/`.overrides` for semantic modules that are commented out (§9.7-F1 step 4 finishes that job); ~~icomoon build artifacts (`fonts/icons/demo.html` + demo files, ~1.2k lines) checked in~~ — **CLOSED 2026-09-17:** `demo.html`, `demo-files/` (`demo.css`, `demo.js`) and `Read Me.txt` are deleted. Kept under `fonts/icons/` as a conscious call rather than an oversight: `style.less` and `variables.less` (both on the LESS import graph), `fonts/` (webpack copies it), and `style.css` + `selection.json` as the icon-regeneration set — `selection.json` is icomoon's project file and the only round-trip path back to the icon set. Loose end worth revisiting: now that `demo.html` is gone, `style.css` is referenced by nothing. |
| 5 | **Demo entry files sit at `src/` root** (`index.js` → `main.jsx` → `App.jsx`) next to an otherwise self-contained `src/demo/`, blurring the library/demo boundary. |
| 6 | **Layering violations:** a genuine engine↔form-modules **import cycle** (`form/utils.js:13–14` ⇄ `rules.js:3`, §2.6-4), plus `components/Text.js:4` importing `../modules/variables` (`ISO_8601_COMPLETE_DATE`). Otherwise directions are clean: core never imports demo; utils imports nothing upward. |
| 7 | **Duplicate basenames blur navigation:** two `Tabs.js` and two `TabList.js` (components pack vs engine `components/`); three `utils.js` files (engine, form module, `components/charts/`) plus the utility directories `components/utils/` and `core/utils`; generic `constants.js`/`translations.js`/`styles.js` scattered. Naming collision: `pages/main/components/Popup.js` is actually a **modal**, while `TooltipPop` wraps SUIR *Popup*. |
@@ -966,10 +970,10 @@ The form stack splits into a React-free core and React bindings:
#### H1 — Delete dead weight (cheap, do first)
-- The **12 orphan components** (9 direct + the `ErrorTable`/`Square`/pack-`TabList` cluster, §2.6-14). Gate: grep consumer metas for these names as `view` values first — they are not registered in `mapper.js`, so no meta can render them, but verify before deleting. Bonus effects: `Carousel`/`Collapse` drop two `UNSAFE_` migrations from Workstream A; `ImageSwatch` removes one of the three `defaultProps` sites (§8); the pack `TabList` resolves an H6 duplicate.
-- `src/style/unused/` (10 files); ~~`override/_policy.less` / `_classic.less` (verify unreferenced)~~ — **CLOSED 2026-09-17 (same item as AppC-H1-2, which already recorded it done; this backlog line was simply not updated).** Both files are gone from `src/style/override/`, removed by 9f393959 "F1 step 4: delete semantic-ui-less and the theme.config machinery (#46)" along with the rest of the override tree. Not to be confused with `src/style/unused/_policy.less` / `_classic.less`, which still exist and are covered by the `src/style/unused/` item at the head of this bullet. Still open: icomoon demo artifacts under `fonts/icons/`.
+- ~~The **12 orphan components** (9 direct + the `ErrorTable`/`Square`/pack-`TabList` cluster, §2.6-14). Gate: grep consumer metas for these names as `view` values first — they are not registered in `mapper.js`, so no meta can render them, but verify before deleting. Bonus effects: `Carousel`/`Collapse` drop two `UNSAFE_` migrations from Workstream A; `ImageSwatch` removes one of the three `defaultProps` sites (§8); the pack `TabList` resolves an H6 duplicate.~~ — **CLOSED 2026-09-17:** all 12 are deleted from `src/core/components/` (`Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `ErrorTable`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Square`, `TabList`, `Tags`); `ErrorTable` could not go alone because `ErrorContent` imported it, so both went together. The bonus effects landed as written (two `UNSAFE_` migrations off Workstream A, one of the three `defaultProps` sites off §8). **One correction to the old text:** deleting the pack `TabList` resolves only *half* the H6 duplicate — the pack `Tabs` (`src/core/components/Tabs.js`) is still present, so H6 is not closed. **Decision this item owned, taken 2026-09-17:** `Table`'s `inverted`/`striped` are **KEPT**. `ErrorTable` was their only in-repo call site, so the generated "Attributes at the call sites" table in `docs/SUPPORTED-PROPS.md` no longer lists them — that table reports what the *codebase passes*, not what the component *accepts*. `Table.js` still destructures both, the CSS still selects on them (`table:not(.as-layout).inverted`, `table.striped tr:nth-child(2n)`), and a consumer meta can still set them; `scripts/wrapper-prop-curation.js` now records this. Verified after: jest **167 suites / 2528 tests** (was 2540 — the drop is exactly the 12 deleted `time.js` tests below, not anything to do with the components), and `lint:js`, `lint:css`, `css:fixture:check`, `docs:props:check`, `docs:views:check`, `build-lib`, `build-css` all green with the published `static/all.css` sha256 unchanged at `dcdb0a40a6d8ee79929bc40a9335b04f8222e7a235a872242c87ecffa9332723`.
+- ~~`src/style/unused/` (10 files)~~ — **CLOSED 2026-09-17:** the directory and all 10 files are deleted on `h1-dead-weight`. ~~`override/_policy.less` / `_classic.less` (verify unreferenced)~~ — **CLOSED 2026-09-17 (same item as AppC-H1-2, which already recorded it done; this backlog line was simply not updated).** Both files are gone from `src/style/override/`, removed by 9f393959 "F1 step 4: delete semantic-ui-less and the theme.config machinery (#46)" along with the rest of the override tree. Not to be confused with `src/style/unused/_policy.less` / `_classic.less`, which still exist and are covered by the `src/style/unused/` item at the head of this bullet. ~~Still open: icomoon demo artifacts under `fonts/icons/`.~~ **CLOSED 2026-09-17:** `demo.html`, `demo-files/` (`demo.css`, `demo.js`) and `Read Me.txt` are deleted. `style.less`, `variables.less`, `fonts/`, `style.css` and `selection.json` were kept deliberately — see H0-4 for which and why, including the loose end that `style.css` is now referenced by nothing.
- ~~the unreferenced engine `tester/` fixtures~~ — **deleted.** They were worse than merely dead: `test_data.js` and `test_meta.js` re-exported from `../examples/…`, and no `examples/` directory exists under `src/core/pages/main/` (the fixtures live in `src/demo/examples/`), so either file would have failed module resolution the moment anything imported it. Nothing did. `eslint-config-react-app` could not see it because `import/no-unresolved` is off. Removing them also took two permanently-0% files out of the coverage report; global coverage after deletion is 94.29% statements / 89.38% branches / 92.71% functions / 94.9% lines.
-- `formatTime`/`toHours` in `time.js` (no production callers, §9.7-F2).
+- ~~`formatTime`/`toHours` in `time.js` (no production callers, §9.7-F2).~~ — **CLOSED 2026-09-17:** both are deleted from `src/core/utils/time.js`, along with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }`; their test blocks went too (12 tests, the whole 2540 → 2528 delta). `formatDuration` (and `.shortEnglish`) stay, and were already moment-free. **Consequence for §9.7-F2:** `moment` is now imported **nowhere** under `src/core/utils/` — the only remaining internal importers are the three component call sites (`Text.js`, `TextDateValue.js`, `InputDate.js`), which is exactly the set the F2 `dateAdapter` funnel has to cover.
- `FIELD.TYPE.DATE` (`modules/form/constants.js:16`) — defined, never read, and `view: 'Date'` therefore renders a placeholder instead of the date field the name implies (§2.6-17). Deleting it is the honest move unless the view spelling is meant to be supported, in which case it needs a branch in `renders.js` rather than a constant.
- ~~The **13 zero-reference devDependencies**~~ — **done in Phase 0.9:** all 13 plus `dot-prop-immutable` removed, verified by pruning `node_modules` to the lockfile with `npm ci` and re-running every pipeline including `build-css` and the watch build.
@@ -995,7 +999,7 @@ Fix the direct violation (move `ISO_8601_COMPLETE_DATE` into `core/utils`); the
- `pages/main/components/Popup.js` → `Modal.js` (it *is* a modal; frees the name collision with tooltip-Popup).
- `TooltipPop` → `Tooltip` during its F1 step 2 rewrite.
- Engine `utils.js` dissolves into named modules during §9.3 (`dataMapping.js`, `errorMapping.js`, …).
-- `Tabs`/`TabList` pairs: the re-audit confirmed mapper uses the engine copies and the pack `TabList` is an orphan (delete via H1); audit the pack `Tabs` the same way before its §9.2 migration.
+- `Tabs`/`TabList` pairs: the re-audit confirmed mapper uses the engine copies and the pack `TabList` is an orphan ~~(delete via H1)~~ — **deleted 2026-09-17 under H1**, so the `TabList` duplicate is gone and `pages/main/components/TabList.js` is the only one left. **Half of this item remains open:** the pack `Tabs` (`src/core/components/Tabs.js`) still sits alongside the engine `Tabs`, so audit it the same way before its §9.2 migration — do not read the `TabList` deletion as closing H6's duplicate-basename item.
- **Completed version guard:** `npm version` synchronizes the runtime wrapper and demo-shell literals from `package.json`; `prepack` rejects drift before rebuilding. The removed type shim is no longer a version site (§2.6-15).
#### H7 — Build config consolidation
@@ -1028,7 +1032,7 @@ src/
utils/ # pure utils (+ dateAdapter from F2); absorbs core/common AFTER untangling (H0-9)
contexts/ providers/ services/
demo/ # all demo code incl. entries (H3)
- style/ # minus unused/ and icomoon artifacts
+ style/ # unused/ gone (H1); icomoon demo files gone, style.css + selection.json kept
```
**Sequencing:** H1/H2 — immediately, in any quiet moment (Phase 4 window). H3/H4 — right after Phase 2 ships, as dedicated pure-move commits in a lull between feature branches (see R11). H5 rides the 4-S window (it is one ESLint-config PR); H6–H7 — opportunistic. **H4 must precede Phase 6.**
@@ -1103,8 +1107,8 @@ Phases 3 and 4 can partially overlap. Tracks **5a/5b** (SUIR exit) and **6** (en
- **Docs hardcoding React version:** `src/demo/markdowns/docs.md:16,37,41` · `README.md` (plus a prose peer-deps note at `changelog.md:50`).
- **SUIR JS surface — ~~3 of 54 pack files~~ → 0 (re-measured 2026-09-17).** `grep -rn 'semantic-ui-react' src/core/components/{Table,TooltipPop,Dropdown}.js` returns only explanatory comments (`Table.js:9,:96` · `TooltipPop.js:7,:12,:103,:111,:118,:171,:318` · `Dropdown.js:26,:69`) — no import in any of the three. The inventory as it stood: `Table.js` (subcomponent users: `mapper.js:170`, `TableView.js`, `LocalDraftTableRow.js`, `ErrorTable.js`) · `TooltipPop.js` (`Render.Tooltip`, `mapper.js:44,483`) · `Dropdown.js` (`mapper.js:540–544`).
- **semantic-ui-less modules actually imported** (`src/style/override/_semantic.less`) — **superseded 2026-09-17: there are none; the package itself is gone (9f393959).** `_semantic.less` now imports `../vendor/semantic-reset` and `../vendor/semantic-dropdown`, each followed by our own overrides; `ls src/style/override/globals` = `reset.overrides`, `reset.variables`, `site.variables` and `ls src/style/override/modules` = `dropdown.overrides`, `dropdown.variables`. The label/menu/popup modules were measured unused and deleted at F1 step 4's first half (9a531871). The inventory as it stood: `globals/reset`, `elements/label` (chips), `collections/menu` (own Pagination), `modules/dropdown`, `modules/popup`; `transition` already replaced by an in-house `transition.less` — **that last clause is still true** (`@import 'transition'` closes `_semantic.less`).
-- **moment call sites:** `Text.js:43` · `TextDateValue.js:8` · `InputDate.js:58–65` (+ rc-picker moment `generateConfig` at :10/:88) · `time.js` `formatTime`/`toHours` (test-only, no production callers).
-- **Structure facts (§9.9):** `ui-*-pack` alias imports: **0** across `src` · ~~`tsconfig-paths-webpack-plugin`: referenced by no config~~ **REMOVED 2026-08-10 (00d67612, the Phase 0.5/0.9 sweep): it is not a devDependency at all any more — `grep -n 'tsconfig-paths' package.json` returns nothing (re-verified 2026-09-17)** · orphan components ×12: `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` + cluster `ErrorTable`/`Square`/pack-`TabList` · layering: engine⇄form-modules cycle (`form/utils.js:13–14` ⇄ `rules.js:3`) + `components/Text.js:4` → `../modules/variables` · demo entries at `src/` root: `index.js`, `main.jsx`, `App.jsx`.
+- **moment call sites:** `Text.js:43` · `TextDateValue.js:8` · `InputDate.js:58–65` (+ rc-picker moment `generateConfig` at :10/:88) · ~~`time.js` `formatTime`/`toHours` (test-only, no production callers)~~ — **CLOSED 2026-09-17 (H1, branch `h1-dead-weight`): both functions are DELETED**, together with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests; `formatDuration` (and `.shortEnglish`) stay. Do not go looking for the two functions — read the struck clause as the inventory it was. **Consequence for §9.7-F2:** the remaining moment surface is exactly the three components plus the rc-picker `generateConfig`, and **moment is now imported nowhere in `src/core/utils/`**.
+- **Structure facts (§9.9):** `ui-*-pack` alias imports: **0** across `src` · ~~`tsconfig-paths-webpack-plugin`: referenced by no config~~ **REMOVED 2026-08-10 (00d67612, the Phase 0.5/0.9 sweep): it is not a devDependency at all any more — `grep -n 'tsconfig-paths' package.json` returns nothing (re-verified 2026-09-17)** · ~~orphan components ×12: `Avatar`, `Badge`, `Carousel`, `Collapse`, `ErrorContent`, `FloatNumber`, `ImageSwatch`, `MenuButton`, `Tags` + cluster `ErrorTable`/`Square`/pack-`TabList`~~ **CLOSED 2026-09-17 (H1, branch `h1-dead-weight`): all 12 files are DELETED from `src/core/components/`** — the count is now history, not a set of files anyone can open. `ErrorTable` could not go alone (`ErrorContent` imported it), so the pair went together. Only the **pack** `TabList` went: the engine `TabList` (`src/core/pages/main/components/TabList.js`) is untouched and mapper still uses it, which resolves **half** of the H6 duplicate — pack `Tabs.js` is still on disk, so **H6 is not closed**. Re-running the Appendix B orphan scan now reports no zero-count pack file except the `index.js` barrel, which is a scan artefact · layering: engine⇄form-modules cycle (`form/utils.js:13–14` ⇄ `rules.js:3`) + `components/Text.js:4` → `../modules/variables` · demo entries at `src/` root: `index.js`, `main.jsx`, `App.jsx`.
- **Public-surface facts (§2.6):** runtime entry `library/index.js → main.js` (`AppProvider → AppWrapper → engine`) · `require(dist)` returns one callable function with no `.default`/`.UIRender`; declarations use `export =`, retain named types through a merged namespace, and pass locked React 16/17/18 checks in interop and direct-CommonJS modes · ~~`dateFormat` prop is still dead (`Render.js:45`)~~ **CLOSED (re-verified 2026-09-17): the config channel works.** `Render.js:55–59` drops the prop *deliberately* — the format is configuration and travels through `ConfigContext`, which `rules.js:384` feeds via ``, and `TextDateValue.js:21` reads `dateFormat || config.dateFormat`; pinned by `UIRender.config-channel.test.js:63` and announced at `changelog.md:32` ("**The `dateFormat`, `currency` and `language` props now work.**"). The cited `Render.js:45` no longer holds the destructure either — it is at :59 · fixed-id portal root (`AppWrapper.js:17`) · `prepack` checks version sync and rebuilds; ~~the measured tarball is 295 files / 7.25 MB unpacked / 2.53 MB packed~~ **Figures re-measured 2026-09-17** through the repo's own gate (`scripts/check-package-budget.js`, i.e. `npm pack --dry-run --json --ignore-scripts`): **299 files / 6.44 MB unpacked / 2.39 MB packed** — still inside the 330 / 8 MB / 3 MB budgets, assets shipped once in the root `static/` payload with `dist/static/*.css` as `@import` re-exports, source maps kept by decision, and CI enforcing both the budgets and a packed-tarball server-render smoke.
## Appendix B — Verification commands
@@ -1140,6 +1144,14 @@ npm run lint:js
npm run docs:props:check
# Structure: orphan component scan (§9.9-H1 — count of importing files per component; 0 = orphan)
+# Since H1 landed (2026-09-17) the 12 named orphans are DELETED, so this is a regression guard, not a
+# to-delete list. Two traps in reading its output, both measured 2026-09-17:
+# - the only remaining 0 is `index.js`, an artefact — the barrel is imported by directory path
+# (`from '../components'`), never as `/index'`;
+# - the pattern `/${n}'` ignores the directory, so a pack file shares its count with any same-named
+# file elsewhere. `Tabs` reads 3, but `mapper.js` and `pages/main/components/TabList.js` both import
+# the ENGINE `./Tabs`; the pack `Tabs.js` has exactly one importer, `src/demo/components/NavTabs.jsx`
+# (demo chrome, not mapper-reachable). Check the importers, not just the count.
for f in src/core/components/*.js; do n=$(basename "$f" .js); \
c=$(rg -l "/${n}'" src --glob '!*.test.*' | grep -v "components/${n}.js" | wc -l); echo "$c $n"; done | sort -n
@@ -1192,8 +1204,8 @@ Every check this plan depends on, in one place. ✅ = already verified during th
- ✅ No legacy React APIs in `src`: string refs, legacy context, `findDOMNode`, `unstable_*`, `react-dom/test-utils`, `element.ref` reads — all still zero, re-measured 2026-09-17. **`e.persist()` corrected 2026-09-17:** this line used to list `e.persist()` among them, and stopped being true on 2026-08-20, when one deliberate call was *added* to fix a React 16 event-pooling bug — `persistEvent()` at `src/core/modules/upload/views/Upload.js:34`, called at `:100` and `:105`, covered by `Upload.interactions.test.js:98-111`. §2.4 and §5 already record it as the one intended call site; only this line was never updated. The call site is correct and stays.
- ✅ All runtime deps declare React 17/18 peers (lock-verified); ~~`@testing-library/react` 12 is the only `<18` blocker~~ **SUPERSEDED 2026-09-17 (Phase 2):** the first clause still holds, but the blocker is gone and inverted — RTL is now `^16.3.2`, whose peers read `react`/`react-dom` `^18.0.0 || ^19.0.0`, so it no longer blocks 18, it requires it. Read the struck half as the audit fact it was, not as current state
- ✅ ~~SUIR surface: imports confined to `src/core/components`; exactly 3 wrappers; 5 semantic LESS modules active~~ **SUPERSEDED 2026-09-17 (§9.7-F1 steps 1–4):** all three clauses now hold only in a stronger form, so the line as written is false — re-measured 2026-09-17: **zero** `semantic-ui-react` imports anywhere in `src` (the package is not installed, is in neither dependency block, and `no-restricted-imports` covers all of `src` with a message); **zero** SUIR wrappers — `Table.js`, `TooltipPop.js` and `Dropdown.js` are in-house and `Listbox.js` is new; and **no** `semantic-ui-less` module is imported at all — `override/_semantic.less` imports the two vendored files under `src/style/vendor/` (`globals/reset`, `modules/dropdown`), step 4's first half having deleted `modules/popup`, `elements/label` and `collections/menu`, and every remaining `semantic-ui-less` mention in `src/style` is prose or a provenance header. The ongoing-invariant line "SUIR isolation grep: **nothing at all**" below already states the stronger version
-- ✅ moment surface: 3 components + rc-picker `generateConfig`; `formatTime`/`toHours` have no production callers
-- ✅ Structure: `ui-*-pack` alias imports = 0; `tsconfig-paths-webpack-plugin` referenced by no config; 12 orphan components incl. the `ErrorTable`/`Square`/pack-`TabList` cluster; layering = engine⇄form-modules cycle + `Text.js:4`
+- ✅ moment surface: 3 components + rc-picker `generateConfig`; ~~`formatTime`/`toHours` have no production callers~~ **SUPERSEDED 2026-09-17 (H1): both are DELETED from `src/core/utils/time.js`**, with the file's now-unused `import moment` and `import { FORMAT_TIME_FOR_HUMAN }` and their 12 tests (`formatDuration`/`.shortEnglish` stay). The first clause still holds — `Text.js`, `TextDateValue.js`, `InputDate.js` + the rc-picker `generateConfig` are now the *whole* surface — and the F2-relevant consequence is that **moment is imported nowhere in `src/core/utils/`**. Read the struck half as the 2026-07-21 audit fact it was
+- ✅ Structure: `ui-*-pack` alias imports = 0; `tsconfig-paths-webpack-plugin` referenced by no config; ~~12 orphan components incl. the `ErrorTable`/`Square`/pack-`TabList` cluster~~ **SUPERSEDED 2026-09-17: all 12 were DELETED under H1** — the count is a record of what the audit found, not files on disk. The engine `TabList` survives (mapper uses it) and pack `Tabs.js` survives too, so the H6 duplicate is only half reconciled; layering = engine⇄form-modules cycle + `Text.js:4`
- ✅ Re-audit (2026-07-21): tests 76 suites / 1215 green in 8.3 s; `lint:css` clean; eslint fails (11 errors / 28 warnings); prod `npm audit` 0, full audit 20; tarball 579 files / 11.6 MB with duplicated assets + maps; published types ≠ runtime; `dateFormat` prop dead; built CSS ships unscoped `html`/`body`/`*`; moment crosses the public API (README-documented + picker callbacks)
- ✅ 19-ready form stack exists upstream (`react-final-form` 7.0.1 / `final-form` 5.0.1 / `final-form-arrays` 4.0.1 / `react-final-form-arrays` 5.0.0 — npm-registry-verified)
- ✅ Single `cloneElement` site passes plain props only (ref-as-prop safe)
@@ -1299,7 +1311,7 @@ Every check this plan depends on, in one place. ✅ = already verified during th
- ☐ Orphan component names grepped across consumer metas as `view` values
- ☑ `override/_policy.less` / `_classic.less` confirmed unreferenced — and DELETED at step 4 with 87 other dead files, the set determined by the LESS import graph rather than by grep
- ☒ `less-plugin-functions`: removed in a branch → all four pipelines still build → devDep dropped (§9.8). **Attempted and CLOSED as not-doable-as-written (2026-09-11):** the build fails without it at `_variables.less:264` on `round()`. Re-opening this means replacing those nine call sites first.
-- ☐ Pack `Tabs`/`TabList` audited for orphan status before reconciling the duplicates (H6)
+- 🔶 ~~Pack `Tabs`/`TabList` audited for orphan status before reconciling the duplicates (H6)~~ **HALF DONE 2026-09-17 (H1):** the pack `TabList` was audited, confirmed an orphan (mapper imports the engine copy, `mapper.js:33`) and DELETED with the rest of the H1 set; the engine `TabList` (`pages/main/components/TabList.js`) is untouched. **Still open — do not read H6 as closed:** the pack `Tabs.js` is still on disk. Measured 2026-09-17, its only importer is `src/demo/components/NavTabs.jsx` (demo chrome), while `mapper.js:34` and the engine `TabList.js:3` both import the engine `./Tabs` — so it is not mapper-reachable, but it is not an orphan either, and the duplicate is still unreconciled
### F4 — form-stack major bump
diff --git a/scripts/wrapper-prop-curation.js b/scripts/wrapper-prop-curation.js
index 459bcafa..1d2e3577 100644
--- a/scripts/wrapper-prop-curation.js
+++ b/scripts/wrapper-prop-curation.js
@@ -103,10 +103,17 @@ const IN_HOUSE_CURATION = {
+ '`styles`/`fill`/`vertical` attributes; consumer metas add `as-layout`, `no-header`, '
+ '`highlight-N-last` and the sticky-column tokens through the same channel.',
inverted: 'Dark table. Emitted as the `inverted` class, which `table.less` and '
- + '`expand.less` both select on. Reached only from `ErrorTable.js` — a §9.9-H1 orphan, '
- + 'so the prop is kept but its fate is that deletion\'s to decide, not this step\'s.',
- striped: 'Zebra rows, emitted as the `striped` class. Same single call site as `inverted`, '
- + 'and also genuinely styled — which is why neither was dropped with the rest.',
+ + '`expand.less` both select on. **KEPT — decided at §9.9-H1 (2026-09-17), which is '
+ + 'where step 1 left the call.** Its one in-repo caller was `ErrorTable.js`, and H1 '
+ + 'deleted it, so the "Attributes at the call sites" table above no longer lists this '
+ + 'prop — that table reports what the CODEBASE passes, not what the component accepts. '
+ + 'The prop itself is untouched: `Table.js` still destructures it and still emits the '
+ + 'class, the CSS still selects on it, and a consumer meta can still set it. Dropping '
+ + 'it because our own last caller went away would have been a breaking change bought '
+ + 'for nothing.',
+ striped: 'Zebra rows, emitted as the `striped` class. Same story as `inverted` exactly: '
+ + 'genuinely styled (`table.striped tr:nth-child(2n)`), which is why neither was '
+ + 'dropped with the rest, and still accepted after H1 removed their shared call site.',
},
partProps: {
className: 'Passed through verbatim, or the attribute is omitted entirely when it is '
diff --git a/src/core/components/Avatar.js b/src/core/components/Avatar.js
deleted file mode 100644
index 05ac15e4..00000000
--- a/src/core/components/Avatar.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import classNames from '../utils/classNames'
-import PropTypes from 'prop-types'
-import React from 'react'
-import Icon from './Icon'
-import Text from './Text'
-import View from './View'
-
-/**
- * Avatar - Component.
- *
- * @param {String} name - name
- * @param {String} [status] - online status, one of ['online', 'dueling', 'paused', 'stopped']
- * @param {Boolean} [hideName] - whether to hide the name underneath
- * @param {Boolean} [small] - whether to add 'small' css class
- * @param {Boolean} [large] - whether to add 'large' css class
- * @param {Boolean} [larger] - whether to add 'larger' css class
- * @param {Boolean} [secure] - whether to disable images
- * @param {Function} [onClick] - function to call
- * @param {String} [src] - image URL
- * @param {String} [className] - css class name to apply
- * @param {Object} [style] - css props
- * @param {Object} [children] - extra content to render inside avatar
- * @returns {Object} - React component
- */
-export function Avatar ({
- name = '',
- status = '',
- hideName = true,
- src = '',
- secure,
- small,
- large,
- larger,
- className,
- onClick,
- style,
- children,
-}) {
- const hasImage = src && !secure
- return (
-
-
-
- {hasImage
- ? {name} // for screen readers
- : {name.charAt(0).toUpperCase() || }
- }
-
-
- {!hideName && {name}}
- {children}
-
- )
-}
-
-Avatar.propTypes = {
- name: PropTypes.string.isRequired,
- hideName: PropTypes.bool,
- small: PropTypes.bool,
- large: PropTypes.bool,
- larger: PropTypes.bool,
- secure: PropTypes.bool,
- src: PropTypes.string,
- onClick: PropTypes.func,
- children: PropTypes.any,
-}
-
-export default React.memo(Avatar)
diff --git a/src/core/components/Badge.js b/src/core/components/Badge.js
deleted file mode 100644
index 4f3ee0fb..00000000
--- a/src/core/components/Badge.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import classNames from '../utils/classNames'
-import PropTypes from 'prop-types'
-import React from 'react'
-import { shortNumber } from '../utils'
-import View from './View'
-
-/**
- * Badge Counter - Pure Component.
- */
-export function Badge ({
- count,
- digits = 2,
- className,
- ...props
-}) {
- const counter = (digits && count > 9) ? shortNumber(count, digits) : count
- // noinspection JSConstructorReturnsPrimitive
- return counter ? {counter} : null
-}
-
-Badge.propTypes = {
- count: PropTypes.number.isRequired,
- digits: PropTypes.number,
- className: PropTypes.string,
-}
-
-export default React.memo(Badge)
diff --git a/src/core/components/Carousel.js b/src/core/components/Carousel.js
deleted file mode 100644
index 6975c621..00000000
--- a/src/core/components/Carousel.js
+++ /dev/null
@@ -1,122 +0,0 @@
-import classNames from '../utils/classNames'
-import PropTypes from 'prop-types'
-import React, { PureComponent } from 'react'
-import { isFunction } from '../utils'
-import { formatDuration, } from '../utils/time'
-import Icon from './Icon'
-import { imageSrc } from './Image'
-import Loading from './Loading'
-import Row from './Row'
-import ScrollView from './ScrollView'
-import Square from './Square'
-import Text from './Text'
-import { withTimer } from './utils'
-import View from './View'
-
-/**
- * Carousel - Self Contained Container.
- */
-@withTimer
-export default class Carousel extends PureComponent {
- static propTypes = {
- items: PropTypes.arrayOf(PropTypes.oneOfType([
- PropTypes.shape({
- avatar: PropTypes.string, // small image url
- src: PropTypes.string, // full image url
- }),
- PropTypes.shape({ // same as Image.propTypes
- name: PropTypes.string.isRequired,
- path: PropTypes.string,
- }),
- ])).isRequired,
- activeIndex: PropTypes.number, // opened tab index (controlled)
- defaultIndex: PropTypes.number, // opened tab index initially (uncontrolled)
- hideImage: PropTypes.bool, // whether to hide background image for active item
- hideItems: PropTypes.bool, // whether to hide list of items below
- hideCount: PropTypes.bool, // whether to hide item count at the top left
- hideControls: PropTypes.bool, // whether to hide prev and next arrows
- onChange: PropTypes.func, // callback when active item index changes, receives activeItem ID if given, or index
- square: PropTypes.bool, // whether to render as square
- className: PropTypes.string,
- itemClass: PropTypes.string, // css class names to add to active item
- children: PropTypes.any, // extra content to render inside Carousel, receives active item props as argument
- }
-
- state = {
- activeIndex: Math.max(this.props.activeIndex || this.props.defaultIndex || 0, 0),
- transition: false,
- }
-
- UNSAFE_componentWillReceiveProps (next) {
- const {activeIndex, items} = next
- if (activeIndex != null && activeIndex !== this.props.activeIndex) this.handleClickItem(activeIndex)
-
- // Handle use case when parent changes layout and carousel has less panels than previously set active index
- if (this.state.activeIndex >= items.length) this.setState({activeIndex: 0})
- }
-
- handleClickItem = (activeIndex) => {
- this.setState({transition: true})
- this.setTimeout(() => {
- this.setState({activeIndex, transition: false})
- const {onChange, items} = this.props
- onChange && onChange((items[activeIndex] || {}).id || activeIndex)
- }, 50)
- }
-
- handleClickPrev = () => {
- let { activeIndex } = this.state
- activeIndex = activeIndex ? activeIndex - 1 : this.props.items.length - 1
- this.handleClickItem(activeIndex)
- }
-
- handleClickNext = () => {
- let {activeIndex} = this.state
- activeIndex = (activeIndex === this.props.items.length - 1) ? 0 : activeIndex + 1
- this.handleClickItem(activeIndex)
- }
-
- render () {
- const {
- items,
- hideImage, hideItems, hideCount, hideControls,
- itemClass, children, className, style, fill, square,
- loading = false,
- } = this.props
- const {activeIndex, transition} = this.state
- if (!items.length) return null
- const activeItem = items[activeIndex] || {}
- const backgroundImage = !hideImage ? `url('${imageSrc(activeItem)}')` : ''
- const dateAgo = activeItem.time ? formatDuration(Date.now() - activeItem.time, {shorten: true, largest: 1}) : ''
- const Canvas = square ? Square.Row : Row
- return (
-
-
- {!hideItems && items.length > 1 &&
-
- {items.map((item, index) => {
- const style = {backgroundImage: `url('${imageSrc(item)}')`}
- const active = index === activeIndex
- return this.handleClickItem(index))}
- />
- })}
-
- }
- {loading && }
-
- )
- }
-}
diff --git a/src/core/components/Collapse.js b/src/core/components/Collapse.js
deleted file mode 100644
index 62fb2d37..00000000
--- a/src/core/components/Collapse.js
+++ /dev/null
@@ -1,94 +0,0 @@
-import classNames from '../utils/classNames'
-import PropTypes from 'prop-types'
-import React, { PureComponent } from 'react'
-import { removeNilValues } from '../utils'
-import Icon from './Icon'
-import Row from './Row'
-import View from './View'
-
-/**
- * Collapse / Expand - Self Contained Container.
- * @example:
- * }
- * contentRender={isOpen => (isOpen && Expanded content)}
- * />
- * }
- * contentOpened={Expanded content}
- * contentClosed={Closed content}
- * />
- */
-export default class Collapse extends PureComponent {
- static propTypes = {
- isOpen: PropTypes.bool,
- horizontal: PropTypes.bool,
- minSize: PropTypes.string, // min-height or min-width css property when collapsed, default is `0`
- maxSize: PropTypes.string, // max-height or max-width css property when expanded, default is `100%`
- toggleClass: PropTypes.string, // css class names
- toggleRender: PropTypes.func, // toggle render function, receives `isOpen` as argument
- toggleOpened: PropTypes.any, // toggle content to render in expanded state
- toggleClosed: PropTypes.any, // toggle content to render in collapsed state
- contentClass: PropTypes.string, // css class names
- contentRender: PropTypes.func, // content render function, receives `isOpen` as argument
- contentOpened: PropTypes.any, // content to render in expanded state, if `render()` not given
- contentClosed: PropTypes.any, // content to render in collapsed state, if `render()` not given
- className: PropTypes.string, // css class names
- style: PropTypes.object, // css styles
- children: PropTypes.any, // additional content to render inside component
- }
-
- static defaultProps = {
- toggleOpened: ,
- toggleClosed: ,
- }
-
- state = {
- isOpen: false,
- size: '0',
- }
-
- handleToggle = () => {
- const {isOpen} = this.state
- const {minSize, maxSize, contentClosed} = this.props
- const size = !isOpen ? (maxSize || '100%') : (minSize || (contentClosed ? 'min-content' : '0'))
- this.setState({ isOpen: !isOpen, size })
- }
-
- setOpenState = ({ isOpen, minSize, maxSize, contentClosed } = this.props) => {
- if (minSize == null && contentClosed) minSize = 'min-content'
- if (
- (isOpen !== null && isOpen !== this.state.isOpen) ||
- (!this.state.isOpen && minSize !== null && minSize !== this.state.size) ||
- (this.state.isOpen && maxSize !== null && maxSize !== this.state.size)
- ) this.setState(removeNilValues({ isOpen, size: isOpen ? maxSize : minSize }))
- }
-
- UNSAFE_componentWillMount () {
- this.setOpenState()
- }
-
- UNSAFE_componentWillReceiveProps (nextProps, nextContext) {
- this.setOpenState(nextProps)
- }
-
- render () {
- const {
- toggleRender, toggleOpened, toggleClosed,
- contentRender, contentOpened, contentClosed,
- horizontal, className, style, toggleClass, contentClass, children,
- } = this.props
- const {isOpen, size} = this.state
- const toggle = toggleRender ? toggleRender(isOpen) : (isOpen ? toggleOpened : toggleClosed)
- const content = contentRender ? contentRender(isOpen) : (isOpen ? contentOpened : contentClosed)
- const contentStyle = {[horizontal ? 'maxWidth' : 'maxHeight']: size}
- const Container = horizontal ? Row : View
- return (
-
- {toggle}
- {content}
- {children}
-
- )
- }
-}
diff --git a/src/core/components/ErrorContent.js b/src/core/components/ErrorContent.js
deleted file mode 100644
index f1926cd0..00000000
--- a/src/core/components/ErrorContent.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import PropTypes from 'prop-types'
-import React, { Fragment } from 'react'
-import { hasListValue } from '../utils'
-import ErrorTable from './ErrorTable'
-import View from './View'
-
-/**
- * Error Alert Message Content - Pure Component.
- *
- * @param {Array} items - error objects
- * @param {*} props - other attributes to pass to `
`
- * @returns {Object} - React table component
- */
-export function ErrorContent ({
- items,
- ...props
-}) {
- // Filter out User vs Server Errors
- const serverErrors = items.filter(({status, statusCode, code}) => (((status || statusCode || code) >= 500) || ((status || statusCode || code) == null)))
- const userErrors = items.filter(({status, statusCode, code}) => ((status || statusCode || code) < 500))
- return (
-
- {hasListValue(serverErrors) &&
-
-
-
- }
- {hasListValue(userErrors) &&
-
-
-
- }
-
- )
-}
-
-ErrorContent.propTypes = {
- items: PropTypes.arrayOf(PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.number,
- PropTypes.shape({
- id: PropTypes.any,
- status: PropTypes.number,
- title: PropTypes.string,
- detail: PropTypes.string,
- message: PropTypes.string
- })
- ])).isRequired
-}
-
-export default React.memo(ErrorContent)
diff --git a/src/core/components/ErrorTable.js b/src/core/components/ErrorTable.js
deleted file mode 100644
index b34e025a..00000000
--- a/src/core/components/ErrorTable.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import PropTypes from 'prop-types'
-import React from 'react'
-import { get, hasListValue } from '../utils'
-import Table from './Table'
-
-/**
- * Error Table Listing - Pure Component.
- *
- * @param {Array} items - error objects
- * @param {Boolean} isServerError - whether the error is server related or user related
- * @param {Boolean} inverted - whether to add `inverted` class
- * @param {*} props - other attributes to pass to `