Skip to content

EPBDS-16211: the published package is driven in a browser on React 16.14, 17 and 19 too - #200

Merged
AlexSamBY merged 1 commit into
masterfrom
packed-browser-peers
Oct 9, 2026
Merged

AlexSamBY merged 1 commit into
masterfrom
packed-browser-peers

Conversation

@AlexSamBY

@AlexSamBY AlexSamBY commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Why

Every 0.34.x host runs React 16.14, and no gate drove it in a browser:

  • the jest legs run jsdom;
  • the demo mounts through createRoot;
  • the packed tarball's browser smoke ran on the repository's React 18 only.

What changed

  • scripts/test-packed-browser.js takes --react-dir=<node_modules>. --peers runs the page on 16.14, 17 and 19, from the jest legs' fixtures; the npm script is npm run test:pack:browser:peers, and it is a new step in CI's browser job.
  • On 16 and 17 the page mounts through ReactDOM.render, as those hosts do (scripts/fixtures/packed-mount-legacy.js); on 18 and 19 through createRoot.
  • Each run checks which React rendered.
  • A fourth document on the page covers the parts where React 16/17 event delegation differs:
    • a popup opening into the wrapper's popup root;
    • a date picked in its calendar, which is portalled into <body>;
    • Escape closing the popup;
    • a tooltip opening on hover.
  • Found on the way, and kept: after a calendar pick, focus sits on <body>, so Escape does not close the dialog. 0.34.3 does the same (measured), so the test refocuses the field.

Measured

All 4 tests pass in Chromium on React 18.3.1 (createRoot), 16.14.0 and 17.0.2 (ReactDOM.render), and 19.3.0 (createRoot).

All 22 local gates exit 0 on 5711b23:

  • lint and types: lint:js, lint:css, typecheck, typecheck:contract;
  • generated files: docs:props:check, docs:views:check, css:fixture:check, build-css:check;
  • builds and jest: test:env-flags, test:coverage, build, test:react16, test:react17, test:react19;
  • packaging: test:pack, test:css:built, test:pack:peers, test:types, test:pack:browser;
  • browser: test:e2e, test:e2e:react19;
  • the manifest contract.

🤖 Generated with Claude Code

….14, 17 and 19 too

Every 0.34.x host runs React 16.14, and no gate drove it in a browser: the jest legs run jsdom, the demo
mounts through createRoot, and the packed tarball's browser smoke ran on the repository's React 18. The
smoke now takes --react-dir, and --peers (npm run test:pack:browser:peers, in CI's browser job) runs its
page on 16.14, 17 and 19 from the jest legs' fixtures, mounting through ReactDOM.render on 16 and 17 as
those hosts do. Each run checks the React that rendered.

A fourth document covers what React 16 and 17 handle differently from 18 and 19 (events delegated to the
document rather than the root): a popup, which opens into the wrapper's popup root; a date field in it,
whose calendar rc-picker portals into <body>; and a tooltip on hover, from a timer. All four tests pass
on all four Reacts.

Found on the way, and kept: after a pick in that calendar, focus sits on <body> and Escape does not close
the dialog, which listens inside itself. 0.34.3 did the same (measured), so the test refocuses the field.
@AlexSamBY
AlexSamBY merged commit 24a0900 into master Oct 9, 2026
7 checks passed
@AlexSamBY
AlexSamBY deleted the packed-browser-peers branch October 9, 2026 15:02
AlexSamBY added a commit that referenced this pull request Oct 9, 2026
…s at what is measured, and the plan's stale lines (#203)

## What

The process half of the 2026-10-08 audit: lint for the code nobody
linted, a gate against focused tests, coverage thresholds at what the
suite covers, and the plan's stale lines.

### Lint covers the tooling

`npm run lint:js` now runs over `scripts/`, `e2e/` and the config files
at the root, as well as `src`. That is 63 more files, under
`eslint-config-react-app`'s rules without the React, JSX and hooks ones
(`TOOLING_RULES` in `eslint.config.js`). Each file gets the node,
browser or jest globals of the place it runs. The first run found 16
problems:

- **Fixed, 11.**
- Seven unused names in the browser specs: five imports (`DISMISSAL`,
`TOUCH`, `tooltipA11yWiring`, `topmostAt`, `BUBBLE`), and two `bubble`
helpers nothing called. Their `BUBBLE` imports went with them.
- `scripts/jest-console-guard.js` reassigned its buffer under a function
created in a loop (`no-loop-func`). The buffer is now a constant array
cleared in place, with the same behaviour.
- The two webpack configs export a named value
(`import/no-anonymous-default-export`).
  - A `/* global globalThis */` redeclared a built-in, and is gone.
- **Configuration, 4.**
- `document` was undefined in
`scripts/fixtures/packed-browser-entry.js`, which runs in a page; that
file has browser globals now.
- The hooks rules read a Playwright fixture's `use` as React's `use`;
they are not applied to the tooling.
- **Suppressed, 1.** A string of prose in
`scripts/wrapper-prop-curation.js` names the `${id}-help` template. The
suppression states that reason.

### A focused test fails lint

The ban covers `it.only`, `describe.only`, `test.only`,
`test.describe.only`, `test.concurrent.only`, `.only.each`, `fit` and
`fdescribe`. It applies in `src/**/__tests__`, `scripts/__tests__`,
`e2e/**/*.pw.js` and `e2e/**/*.packed.js`.

A focused test runs alone: the rest of its file is skipped, and the run
still passes. Jest has no `--forbid-only`, and Playwright's `forbidOnly`
is set for CI only. `eslint-plugin-jest` is not installed, so the gate
uses two core rules: `no-restricted-properties` on `.only` and
`no-restricted-globals` on `fit`/`fdescribe`.

### Coverage thresholds at what the suite measures

Each threshold is the measured value, rounded down to a whole percent
(statements/branches/functions/lines):

- global: 89/79/85/90 → **98/95/97/99**;
- `rules.tsx`: 89/76/98/91 → **96/87/100/98**;
- ten more entries rise by 1 to 3 points;
- `function.ts` goes 96 → 100 on branches and 75 → 80 on functions, and
its comment says why;
- six files get entries of their own: `documentHost.ts`, `formData.ts`,
`errorMapping.ts`, `dataMapping.ts`, `popupScope.ts` and
`lodash-lite.ts`.

`jest.config.js` states the rule above the block: a change that covers
less either covers what it adds or lowers the number in the same commit,
saying why.

### The plan's stale lines

13 lines in `docs/UPGRADE-PLAN.md` changed, each one in place, with a
date and the evidence:

- **Sequencing (§6, §9.3, §9.9).** "Never mix an upgrade with a refactor
in one release" is recorded as not held. Nothing has been released since
Phase 2 (the last tag is v0.34.3, 2026-08-12), and §9.2's hooks
migration and §9.3's decomposition landed after it. §9.3's "do not
start" and §9.9's sequencing get the dates the work was done.
- **§9.3 step 2.** "STARTED" and "Still to extract" now name what §9.3
closed without extracting: `transformConfig`/`toOpenLConfig` and
`initSelectStatesFromData`, still in `rules.tsx`.
- **R8.** `javascriptEnabled` went on 2026-10-08, and `size()`/`px()`
are called at 133 sites, not 186.
- **R10.** The audit and the prop list are done. The owners' MINOR call
overruled "anything dropped ⇒ major", and the migration notes are in the
changelog.
- **Appendix B.** The `npm audit` baseline was 0 on 2026-09-30 and 35 on
2026-10-06, and has not been re-measured since. The line also says why
the `"prop-types":` pattern has a colon.
- **Appendix C.**
  - The upstream form-stack line is qualified by F4's finding.
  - There are two `cloneElement` sites, not one.
  - `prop-types` is retired.
  - The React 19 box no longer contradicts itself.

## Measured

- **Lint.** 445 files, 0 problems: 382 in `src` and 63 in the tooling.
Master's versions of five of the fixed files each fail the new
configuration, with the rule named.
- **Focused tests.** A probe with nine focused forms was linted as a
file in each of the three test locations. All nine are reported in each;
`describe.skip` is not, and neither is `.only` outside a test file.
- **The open PRs.**
- Under this configuration, #200's and #201's changed tooling and test
files lint clean.
- #202's one finding is the unused `BUBBLE` this PR removes, in a
different hunk of the same file.
- **Coverage.**
- Two runs, one in the local time zone and one in UTC as on CI, were
identical on every file and metric (214 suites, 2776 tests).
  - The thresholds are the lower of the two, rounded down.
- **Local gates, all exit 0.** That covers all 21 gates plus the
manifest contract, on Node 24.14.0:
- lint and types: `lint:js`, `lint:css`, `typecheck`,
`typecheck:contract`;
- generated files: `docs:props:check`, `docs:views:check`,
`css:fixture:check`, `build-css:check`;
- builds and jest: `test:env-flags`, `test:coverage`, `build`,
`test:react16`, `test:react17`, `test:react19`;
- packaging: `test:pack`, `test:css:built`, `test:pack:peers`,
`test:types`, `test:pack:browser`;
  - browser: `test:e2e`, `test:e2e:react19`.

They ran on this commit's tree before two wording fixes, one in a
comment in `eslint.config.js` and one in the changelog entry. After
those, `lint:js` was rerun, along with the two suites that read those
files (`wrapper-prop-reference.contract.test.js`,
`NavTabs.history.test.js`), all exit 0.
- **Against the open PRs.** `git merge-tree` merges this commit cleanly
with #200, #201 and #202. The file this PR and #202 both change,
`e2e/keyboard-a11y.pw.js`, lints clean once merged.

## Left for the owners

GitHub holds eight branches; merged PR branches are deleted on merge.
Besides `master`, `gh-pages` and the three open PRs' branches, three
remain:

- `main`: a 2021 initial commit holding only `LICENSE`, outside master's
history.
- `h1-dead-weight`: PR #52 was closed unmerged, and its content merged
as #53 from `h1-dead-weight-rebased`.
- `release/v0.34`: the 0.34 patch line §10 names. It carries two commits
outside master's history (#27 and v0.34.3, 2026-08-12) and has no
workflows.

Whether to delete the first two, and whether the third gets CI or a
statement that 0.34 takes no more fixes, is not decided here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
AlexSamBY added a commit that referenced this pull request Oct 12, 2026
…ed packages, and CI that keeps every master run (#205)

## What

The security and CI items of the 2026-10-08 audit.

### What a meta can do, written down

The README gains a **Security model** section. Each claim in it was
checked against the code, and the two strongest also in a test run:

- **A meta can render raw HTML.** A `Text` node given
`dangerouslySetInnerHTML: {__html: '<b id="raw">raw</b>'}` puts `<b
id="raw">` in the page: through the published entry, in jsdom, it was
found by id. No URL scheme is checked in an attribute a meta sets.
- **A meta can call `fetch`.** With `mapArgs`, the built-in `fetch`
action calls the page's `fetch` with exactly the URL the meta names.
With a stand-in `fetch`, the call arrived as
`["https://example.test/mapped"]`. With `args`, the click event goes
first.
- **A data value is text,** escaped by React, unless a meta binds it to
an attribute with `{name: 'path'}`.
- **Upload checks run in the browser only.** `formats` and `maxSize`
filter in the browser, and `uploadFile` receives the files as chosen.
- **Downloads come from the host's `downloadFile`.** The renderer only
saves what that function answers.

`SECURITY.md` says how to report a vulnerability and what is in scope:
anything a data value can do, the published package and what it bundles,
the stylesheet leaving `.ui-render`, and the build and CI. What a meta
may do is out of scope.

**Before this merges, private vulnerability reporting needs turning
on.** `SECURITY.md` points reporters at it, and `gh api
repos/eisgroup/ui-render/private-vulnerability-reporting` answers
`{"enabled":false}`. It is under Settings → Code security. The same API
shows Dependabot security updates, secret scanning and push protection
off too. Those are repository settings, so they are not changed here.

### The bundled packages, audited every week

`npm run audit:bundled` (`scripts/audit-bundled.js`) runs `npm audit`
and keeps only the findings on the packages `dist/sbom.cdx.json` lists,
at the version the bundle carries. That matters because:

- `dependencies` is empty, so `npm audit --omit=dev` audits nothing;
- a full `npm audit` reports the development tree.

It fails on a finding at moderate or above.
`.github/workflows/audit-bundled.yml` runs it on Mondays and on demand,
not on pull requests, so a new advisory cannot turn an unrelated change
red.

### CI

- **`cancel-in-progress` applies to pull requests only.** On master each
push cancelled the run before it: on 2026-10-09 four PRs merged within a
minute, and three of those master commits never finished a run (#200's
24a0900, #201's 0fa9296, #202's a506f0b).
- **Every action is pinned to a commit**, the one its major tag points
to (checked again on 2026-10-12), with the release in a trailing
comment:
- `checkout` v7.0.1, `setup-node` v7.1.0, `cache` v6.1.0,
`upload-artifact` v7.0.2;
  - 19 `uses` in `ci.yml` and 2 in the new workflow.

Dependabot updates the commit and the comment together; GitHub
[documents
this](https://github.blog/changelog/2022-10-31-dependabot-now-updates-comments-in-github-actions-workflows-referencing-action-versions/).

## Measured

- **`npm run audit:bundled` on this branch:** "npm audit: 36 finding(s)
in the installed tree; 19 package(s) bundled, 0 of them with a
finding.", exit 0. The plan's Appendix B now records 36, which it last
counted as 35 on 2026-10-06.
- **`scripts/__tests__/audit-bundled.contract.test.js`, 4 tests,
covering:**
  - a finding on a bundled package at the bundled version is kept;
- a vulnerable copy at another version, and an unbundled package, are
not;
  - npm's severity order.
- **The workflows parse.** `ci.yml` has 7 jobs, and `cancel-in-progress`
reads `${{ github.event_name == 'pull_request' }}`.
- **Local gates on HEAD, all exit 0.** That is all 22 gates plus the
manifest contract, on Node 24.14.0.

🤖 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