Skip to content

EPBDS-16211 H3/H4: isolate the demo, re-home the engine - #63

Merged
AlexSamBY merged 2 commits into
masterfrom
h3-h4-structure
Sep 23, 2026
Merged

AlexSamBY merged 2 commits into
masterfrom
h3-h4-structure

Conversation

@AlexSamBY

Copy link
Copy Markdown
Member

Two structural moves, one commit each. H4 is the hard prerequisite of Phase 6 — it gives the lifecycle decomposition a sane address space instead of scattering new files under pages/main/.

H3 — the demo entry leaves the src root

src/index.js, src/main.jsx, src/App.jsx sat at the top of src/, beside the library they are not part of. Three git mvs, eight relative-import fixes, one webpack entry line.

src/  ->  core/ | demo/ | library/ | style/   (+ toolchain/, __mocks__/)

H4 — pages/main + ui-render → engine

72 files, recorded by git as 72 renames so history follows. core/ now reads common | components | contexts | engine | modules | providers | services | utils; pages/ is gone entirely, because main was its only child.

static/images was not moved out of core — it was deleted

The item assumed it needed a new home. Measurement says otherwise: src/core/pages/main/static/images/ui-architecture.png was byte-identical (same sha256) to public/static/images/ui-architecture.png, nothing imported it, no webpack config copied it, and both references that name it resolve to the URL the demo serves from public/. A dead duplicate, not a misplaced asset.

Imports were rewritten by resolution, not text substitution

This mattered. The two trees sat at different depths — pages/main one level deeper than ui-render — so a blanket find-and-replace would have produced silently wrong ../ counts in one of them. Every specifier was resolved to an absolute target in the pre-move tree, mapped, then re-expressed relative to its new location: 221 specifiers across 69 files.

That approach also caught, for free, the ten per-file coverage thresholds in jest.config.js. They are ./-relative paths, so the resolver saw them; an import-targeted rewrite would have missed them and left test:coverage reporting "coverage data not found" while jest stayed green — the exact failure mode this ticket has now hit seven times.

Six other places held hardcoded paths

Four in scripts/generate-view-reference.js, one in wrapper-prop-curation.js, four comment cross-references in tests. Both doc generators then needed regenerating — they had been reporting the old paths correctly all along. CLAUDE.md's two section headings and its layering rule were updated in the same change.

One false alarm worth not repeating

A test:coverage immediately followed by npx jest in the same loop reported a jest failure that did not reproduce — exit code 0 on a clean run, 168 suites / 2540 tests. They share coverage/. Gates were re-run sequentially to confirm rather than being explained away.

Verified

By exit code, each run on its own: lint:js, lint:css, typecheck, typecheck:contract, css:fixture:check, both docs checks, build (demo — the thing H3 could break), build-lib, build-css, test:pack:budget, test:pack:consumer, jest and test:coverage all exit 0. static/all.css sha256 unchanged.

🤖 Generated with Claude Code

`src/index.js`, `src/main.jsx` and `src/App.jsx` sat at the top of `src/`,
next to the library they are not part of. Moved into `src/demo/`, which is
where they belong and where the directory listing now says so.

    src/  ->  core/ | demo/ | library/ | style/  (+ toolchain/, __mocks__/)

Three `git mv`s, eight relative-import fixes inside the moved files, one
webpack `entry` line. No logic changed.

Every pointer to the old paths was repointed in the same commit —
`CLAUDE.md`'s structure section and six citations in the plan. A historical
note citing a path that no longer resolves is not a record, it is a dead
link. `CLAUDE.md` also said the entry chain used `ReactDOM.render`; it has
mounted through `createRoot` since Phase 2, so that was corrected while the
line was being touched anyway.

Verified by exit code: lint:js, typecheck, docs:props:check, the DEMO build
(the thing this change could actually break), build-lib and the full suite
all exit 0.
`core/pages/main/` + `core/ui-render/` merged into `core/engine/` — 72 files,
recorded by git as 72 renames so history follows. `core/` now reads
`common | components | contexts | engine | modules | providers | services |
utils`, and `pages/` is gone entirely: `main` was its only child. This is the
address space §9.3 needs before it can decompose the lifecycle.

STATIC/IMAGES WAS NOT MOVED OUT OF CORE — IT WAS DELETED. The item assumed it
needed a new home. Measurement says otherwise:
src/core/pages/main/static/images/ui-architecture.png was BYTE-IDENTICAL (same
sha256) to public/static/images/ui-architecture.png, nothing imported it, no
webpack config copied it, and both references that name it —
demo/examples/button-download_meta.js and demo/markdowns/docs.md — resolve to
the URL the demo serves from public/. A dead duplicate, not a misplaced asset.

IMPORTS WERE REWRITTEN BY RESOLUTION, NOT TEXT SUBSTITUTION, and it mattered:
the two trees sat at DIFFERENT DEPTHS — pages/main one level deeper than
ui-render — so a blanket find-and-replace would have produced silently wrong
`../` counts in one of them. Every specifier was resolved to an absolute
target in the pre-move tree, mapped, then re-expressed relative to its new
location: 221 specifiers across 69 files.

That approach also caught, for free, the ten per-file coverage thresholds in
jest.config.js. They are `./`-relative paths, so the resolver saw them; an
import-targeted rewrite would have missed them and left `test:coverage`
reporting "coverage data not found" while jest stayed green — the exact
failure mode this ticket has now hit seven times.

SIX OTHER PLACES held hardcoded paths into the moved trees and were repointed
by hand: four in scripts/generate-view-reference.js, one in
wrapper-prop-curation.js, and four comment cross-references in tests. Both doc
generators then needed regenerating — they had been reporting the old paths
correctly all along. CLAUDE.md's two section headings and its layering rule
were updated in the same change.

One false alarm worth not repeating: a `test:coverage` immediately followed by
`npx jest` in the same loop reported a jest failure that did not reproduce —
exit code 0 on a clean run, 168 suites / 2540 tests. They share coverage/.
Gates re-run sequentially to confirm.

Verified by exit code, each run on its own: lint:js, lint:css, typecheck,
typecheck:contract, css:fixture:check, both docs checks, build (demo),
build-lib, build-css, test:pack:budget, test:pack:consumer, jest and
test:coverage all exit 0. static/all.css sha256 unchanged.
@AlexSamBY
AlexSamBY merged commit ba46b12 into master Sep 23, 2026
5 checks passed
@AlexSamBY
AlexSamBY deleted the h3-h4-structure branch September 23, 2026 09:46
AlexSamBY added a commit that referenced this pull request Oct 2, 2026
## What changes

These are the two findings that #151 recorded and left for a change of
their own. Both are in `docs/UPGRADE-PLAN.md`.

### The orphan scan in Appendix B reads TypeScript again

**It had checked no component since the TypeScript migration.** It
looped over `src/core/components/*.js`, and the pack has no `.js` file
now: it has 42 `.ts`/`.tsx` files.
- **In zsh**, the glob fails: `no matches found:
src/core/components/*.js`, and nothing is printed.
- **In bash**, the loop runs once, on the literal pattern. It prints one
line, `198 *`: the pattern becomes `/*'`, which matches every file that
contains a `'`.

**The new command finds the pack's `.ts` and `.tsx` files** and strips
either extension. It matches an import in either quote style, and leaves
out the component's own file under either extension.

How I checked it:
- **Run as the plan prints it,** in bash and in zsh, it scans all 42
files, with the same output in both shells.
- **It reports nothing as an orphan today:** no pack file reads 0.
- **It can still fail:** a file I planted in the pack with no importer
read 0. I then deleted it.

**Its notes on reading the output are re-measured.** The old ones
described what the scan showed on 2026-09-17:
- **`index`** used to be "the only remaining 0". It now reads 5, and
none of the five is the pack's barrel: three import the engine's
`index`, and two are doc comments. The barrel itself is imported by
directory path (`from '../components'`), which the pattern cannot see.
- **The same-name trap still applies, with a current example.** `files`
reads 5, and two of the five import `modules/variables/files.ts`. The
old example, `Tabs`, went with the H6 rename.
- **A count includes the demo.** `StandaloneTabs` reads 1, and that one
importer is the demo's `NavTabs`.

### Appendix A's structure facts

Each fact that is gone is struck through and followed by the change that
removed it, as the plan marks its other closed items:
- **The engine ⇄ form-modules cycle** went with #69 (§9.3 step 2,
2026-09-23).
- **`components/Text.js:4` importing `modules`** went with #64 (H5,
2026-09-23). The layer lint rejects both of these imports now.
- **The demo entries at the `src/` root** moved to `src/demo/` with #63
(H3, 2026-09-23).

Two more fixes in the same bullet:
- **The orphan-scan result** is the re-run above. It used to say the
barrel read 0.
- **The engine `TabList`'s path** now also gives its current one,
`src/core/engine/components/TabList.tsx`. This one was not in #151's
list.

There is no code change.

## Gates

All exited 0 on this branch, `test:coverage` on its second run:
- `lint:js`, `lint:css`, `typecheck`, `typecheck:contract`;
- `docs:props:check`, `docs:views:check`, `css:fixture:check`;
- `test:env-flags`, `build`;
- `test:coverage`, thresholds included;
- `test:pack`, `test:pack:peers`, `test:types`;
- `test:react16`, `test:react17`, `test:react19`, 2854 tests each, with
no worker crash;
- `test:e2e`, 41 tests;
- the manifest contract.

**The first `test:coverage` run failed one test, and this PR is not the
cause.** `examples.strict-mode.test.js` compares each example's DOM
after an edit, with and without StrictMode. On `tableForm` the two runs
read different DOM:
- the plain run read the date picker still open, with the typed text;
- the strict run read it closed, with the value reverted.

This is the same kind of race #149 fixed, through animation frames
instead of timers:
- The picker opens, aligns and closes on animation frames, through
`rc-util/raf`. #149 left `requestAnimationFrame` on real time.
- The 400 ms the test waits after an edit are virtual, and they take 1–3
ms of real time. Whether a real frame lands inside them is chance. With
the test's own timer setup and no coverage, 2 of 3 plain runs had no
frame there, and read the picker open.
- With frames suppressed, both modes read the picker open. So the
comparison depends on whether a frame ran.

The rerun passed all 2854 tests. The fix belongs in the test, in a
change of its own.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant