Repository navigation
EPBDS-16211 E3: transforms, mapper, rules and form/utils, as TypeScript - #144
Merged
Merged
Conversation
This finishes E3, and E2 with it: src/core and src/library hold no JavaScript outside their tests. - A meta node is typed as open JSON, `any` where the engine reads and rewrites it by key. validateMeta checks it at runtime, and the published types describe it. - The util isObject type guard narrowed such a node to Record<string, unknown>, so transforms and rules use a non-narrowing alias of it. - Decorator and withFormSetup subclass the class they are handed, as written. UIRender declares by interface merging what the layers give its instance, so no instance gets an own field in front of a layer's getter. - FIELD's type lists every registry the engine writes. Fixed, because the checker found it: a misconfigured Table.group never said what was wrong. The mapper read a `popup` key off AppContext, and the context has none; only the suites that rendered the mapper by hand faked one. Each check threw "Cannot read properties of undefined (reading 'setPopupState')", which the error boundary then showed. The checks now throw their own message, with both required ids reported together. A new suite fails on the previous code, and the two that faked the key run against the real context. Opening the popup instead was not done: the call omits isOpen, and a state update during render can loop. Found, and left for its own change: popupOpen's options and a popup's own props reach popupAlert, and nothing reads them. Every new cast was removed in turn to prove it is needed. Two were not and are gone. A probe of 11 wrong call sites fails on all 11. Measured: the corpus is identical, with 107 / 1012 at mount, 27 / 64 on an edit and the DOM unchanged. dist/index.js went from 322,926 to 322,919 bytes.
AlexSamBY
added a commit
that referenced
this pull request
Oct 1, 2026
## What changes **E5: every `propTypes` declaration is gone, and so is `prop-types`.** `rg "prop-types" src` returns nothing. The package is out of `devDependencies`, and lint rejects an import of it. ### What is deleted - **39 `propTypes` blocks in 38 files.** The E2 and E3 conversions kept each block on purpose, so this is one deletion rather than forty. - **The semantic proxy `components/types.ts`**, the barrel's `PropTypes` re-export, and every `prop-types` and `type` import. - **The host's and the form layer's handling of prop types.** `documentHost.ts` copied the class's prop types onto the function component, and `withFormSetup` merged them under its own. The four tests that pinned both now pin the rest of what they covered. ### The descriptions survive, in the props types - **They moved into the props types' JSDoc first**, wherever a type did not carry them yet: `TableView`, `Tabs`, `Upload`, `ProgressSteps`, `AutoSave`, `asField`, `asInputDateField` and `ToggleField`. - **`UIRender`'s three undocumented props are documented in the published `UIRender.UIRenderProps`**, where a host reads them: `initialValues`, `form` and `embedded`. The form layer's props are described in a comment where the layer reads them. - **A script compared every commented prop with its TS declaration.** What it still lists is documented by a type alias or by the published type. - **Comments that cited `propTypes` as a fact are reworded:** `Counter`, `Listbox`, `StandaloneTabs`, `documentHost` and the form layer. ### Not done, deliberately: the vocabulary as TS aliases The plan's E5 step 2 asked for the `type` proxy to be recreated as aliases, such as `type Id = string` and `type Px = number`. I measured first: - **The proxy defined 41 names, and the converted files used 16.** - **Twelve of the 16 restate PropTypes itself:** `type.String`, `type.Method`, `type.Of`, and so on. - **Four carry a meaning of their own:** `Id` (used twice), `Fraction`, `UrlOrBase64OrPreview` and `NumberOrString`. - **E2 and E3 already wrote that meaning into the props' JSDoc**, for example "Fraction from 0 to 1" and "URL to link to". An alias of a primitive checks nothing. The vocabulary would have been the same documentation in a second place, and 25 of its names would never have been used. The plan records this decision. ### No diagnostic a test relied on is lost - React 16 to 18 warn in development on a failed prop type. React 19 checks none. - **Two suites allowed prop-type warnings:** four entries in the examples suite, and two in the nested-row suite. - **None of the six fired.** I measured this on React 18, on master, just before the deletion. They are removed, and the nested-row suite now requires no warning at all. - **The `formProps`/`instance` warnings that the plan's §8 flagged were not taken as the contract being met.** The declaration itself was wrong. A nested `Data` document shares its parent's form and is rendered without either prop. #139 made both optional. ### Kept from coming back - **`prop-types` is still installed**, as a dependency of `eslint-plugin-react`, `react-markdown` and the React 16 fixture. An import would therefore resolve and bundle it again. - **`no-restricted-imports` now rejects it in all seven `src` overrides.** I proved this with a probe file in core, in a test and in the demo; each failed with the rule's message. - **`CLAUDE.md` states the rule:** no `propTypes` block. A component's props type, and its JSDoc, are the documentation. ### Also - `README.md` no longer lists `prop-types` among the bundled libraries. - `docs/SUPPORTED-PROPS.md` is regenerated. Only `Dropdown.tsx`'s line count changed, from 381 to 364. - The wrong `colGroup` propType that E3 recorded (`{styles}`) is gone with the rest. The TS type says `style`. - `docs/UPGRADE-PLAN.md` records E5 and closes its items: the §2 audit line, the §8 row, the E-checks row and the Appendix C checklist. The Appendix C command now looks for a dependency entry, since the lint rule names the package seven times. ### Found, and left for a change of its own On React 18, the examples suite's other five allowlist entries match nothing either. Whether any of them still guards something on 16, 17 or 19 is for that change to measure. ## Measured | | master | this PR | |---|---|---| | corpus, 38 examples: commits at mount / `translate` calls | 107 / 1012 | 107 / 1012 | | on an edit: commits / `translate` calls | 27 / 64 | 27 / 64 | | DOM after mount and after an edit | | identical, all 38 | | `dist/index.js` | 322,915 bytes | 311,527 bytes (−11,388, −3.5%) | | `dist/index.js`, gzipped | 107,110 bytes | 104,047 bytes | | `isRequired` in `dist/index.js` | 49 | 0 | | `src/core` + `src/library`, TS | 139 files, 20,203 lines | 138 files, 19,514 lines | Both bundles were built from `git archive` copies in the same directory. Built in the worktree, the same trees measure 322,919 and 311,529 bytes; 322,919 is the figure #144 recorded. ## Gates All exited 0, run on this branch: - `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`, 2842 tests each, with no worker crash; - `test:e2e`, 41 tests; - the manifest contract. The suite still has 2842 tests. Four tests, in three suites, pinned prop-type forwarding and merging. Each now pins the rest of its subject: the names, the class left as written, and the state shape. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
AlexSamBY
added a commit
that referenced
this pull request
Oct 1, 2026
## What changes **This finishes the TypeScript migration.** It meets every clause of the definition of done in `docs/UPGRADE-PLAN.md` §9.6: - `src/core` + `src/library` are TypeScript under `strict`. That was E2 and E3, the last of it in #144. - `allowJs` is off. That is this PR. - The declarations are generated from the source. That was E4, #146. - `propTypes` and `prop-types` are gone. That was E5, #145. The demo and the test suites stay JavaScript, as the definition of done allows. ### `allowJs` is off - **The typecheck program is the TypeScript and nothing else:** 418 files before, 137 now. The 281 that left were 234 `.js`, 9 `.jsx` and 36 `.json` files that tsc resolved but never checked, plus the two guard files deleted below. - **A `.ts` file that imports a `.js` module now fails** with TS7016. I measured this with a probe file, then deleted it. During the migration this was `allowJs: true` with `checkJs: false`, so that a converted file could import an unconverted one. - **The typecheck takes 1.56 s, down from 1.80 s**, the median of three runs each. - `tsconfig.build.json` loses its own `allowJs: false`, which it now inherits. ### The E0 guard is deleted `src/toolchain/` was a two-file fixture. It proved that the checker sees `.ts` and that Babel strips it. Its header asked for its deletion "once real converted modules cover the same ground", and they do: the product's TypeScript is built by all three pipelines and imported by every suite. No `.test.ts` is left, because the tests are JavaScript. ### Three casts that still gave the migration as their reason **`PopupContent` re-typed the renderer because it was "still JavaScript".** I removed the cast, and the checker found what it had been hiding: - the popup hands `Render` a `null` `relativePath` and `relativeIndex`; - `Render` passes them on to its children, because it tests `!== undefined`; - `Render`'s prop types said neither could be `null`. They now allow `null`, and the cast is gone. No behaviour changed. **`Data` and `ToggleField` called their casts `UnconvertedComponent`.** Both still need a cast, for reasons of their own: - a registry slot that `utils` types `unknown`, because it sits below the layer that fills it; - a nested document's `initialValues`, which the form layer types as a record and `Data` as `unknown`. The type is now `OpenComponent`, and the comments give those reasons. `Data`'s import is renamed from `UIRenderWithUISetupJs` to `DocumentWithOwnForm`. Its `as unknown as` cast was double; I checked that a single `as` suffices. ### Docs - `CLAUDE.md` says the migration is complete, and that a `.ts` file cannot import JavaScript. - `docs/UPGRADE-PLAN.md` marks the definition of done as met and records the guard's deletion. - The CI comment on the Typecheck step no longer describes unconverted JavaScript. ## Measured | | master | this PR | |---|---|---| | typecheck program | 418 files | 137 files | | `npm run typecheck`, median of 3 | 1.80 s | 1.56 s | | corpus, 38 examples: commits at mount / `translate` calls | 107 / 1012 | 107 / 1012 | | on an edit: commits / `translate` calls | 27 / 64 | 27 / 64 | | DOM after mount and after an edit | | identical, all 38 | | `dist/index.js` | 311,539 bytes | 311,535 bytes | The 4 bytes are `PopupContent`'s alias. Both bundles were built from `git archive` copies in directories of the same name length. ## Gates All exited 0, run on this branch: - `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`, 2855 tests each, with no worker crash; - `test:e2e`, 41 tests; - the manifest contract. The suite went from 2857 to 2855 tests: the two were the deleted guard's own. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changes
This finishes E3, and E2 with it:
src/coreandsrc/libraryhold no JavaScript outside their tests. The last four files are converted:transforms,mapper,rulesandform/utils.How the engine is typed
A meta node is typed as open JSON:
anywhere the engine reads and rewrites it by key.transforms,mapperandrulestake meta from the host and change it in place. What a node may hold is checked at runtime byvalidateMetaand described by the published types. A narrower type here would claim something the compiler cannot check, about data it never sees. Eachanysays so where it is declared.Everything the engine computes from meta is typed:
asField,withFormand the wrapper;Render.Methodreturns;UIRender's fields.isObjectfromutilsis aliased so that it does not narrow, intransformsandrules. Its type guard narrowed ananynode toRecord<string, unknown>, and every key read after that wasunknown. The alias is captured at module level. That is safe:utilsis outside the engine's import cycle, andCLAUDE.mdnow states which captures are and are not safe.The two layered classes keep their shape.
DecoratorandwithFormSetupeach subclass the class they are handed, over an untyped base.UIRenderdeclares the members the layers give its instance by interface merging, the wayDocumentInstancedoes. No instance gets an own field in front of a layer's getter.FIELD's type lists every registry the engine writes:CROSS_VALIDATE,NORMALIZE,NORMALIZER,PARSERandMETHODS, besidesFUNC,VALIDATEandVALIDATION.Three small edits, none of them observable:
UIRender.contextTypeis a static field instead of an assignment after the class.datasetter no longer returns thesetStateresult, which a setter drops anyway.popupAlertdeclares the options its callers pass. Nothing reads them; see the finding below.Fixed: a misconfigured
Table.groupnever said what was wrongpopupkey offAppContext, and the context has none. Only the two suites that rendered the mapper by hand supplied one.TypeError: Cannot read properties of undefined (reading 'setPopupState'). Measured on master, for all three checks.Incorrect config for Table with {name: "rows"}! Table.group.by must have 'id', got '{}'. The two required ids are reported together, as the two popups were meant to report them.mapper.table-group-misconfig.test.jsis new and fails on the previous code, 3 of 3. The two suites that faked the key now run against the real context.isOpen, so the popup would not show, and a state update made during render can loop. The node failed before and still fails. Only the message is different.Found, and left for a change of its own
popupOpen's options, and a popup's own props, reachpopupAlert, and nothing reads them. The popup shows only a title and content.Casts and probes
mapProps,metaToProps,relativePathFrom,asField,withForm,withFormSetup, the form readers,initSelectStatesFromData,UIRenderandRender.Method. A control line of correct calls compiles. I deleted the probe afterwards.Paths and docs
.tsx/.ts. That covers comments, tests, scripts and the coverage thresholds. History stays as it was: the plan and the changelog.generate-view-reference.jsand the prop-reference contract read these files by path. Both references are regenerated. Comparing them, only those paths changed.mapper.js:184and:486. Those line numbers were already stale; they now point at the current lines.CLAUDE.mddescribes the converted state. It covers the meta-as-JSON rule, the import-cycle rule as it applies now, andengine/utils.ts.docs/UPGRADE-PLAN.mdrecords the batch and marks E3 done.Measured
translatecallstranslatecallsdist/index.jssrc/core+src/library, TSsrc/core+src/library, JSGates
All exited 0, run on this branch:
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, 2842 tests each, with no worker crash;test:e2e, 41 tests;The suite grew by the 3 tests of the new
Table.groupsuite, from 2839 to 2842.🤖 Generated with Claude Code