Conversation
The published types were written by hand in src/library/types, beside a component that did not use them. They are now generated. - src/library/contract.ts holds every published type as an export, moved with git mv from the namespace in types/UIRender.tsx. Each declaration's text is unchanged apart from `export`. - main.tsx types the exported component with UIRenderProps<Data> and writes out its return type, React.ReactElement | null. - npm run gen-ts is scripts/gen-ts.js. tsc emits the declarations of the program rooted at main.tsx into a staging directory, and the script publishes dist/contract.d.ts as tsc wrote it. It writes dist/index.d.ts from main.tsx's declaration: the component renamed UIRender, a namespace re-exporting each contract type, and `export =`, which the source cannot hold because Babel rejects it. - The script refuses an import other than react and the contract, a value in the contract, a default export that is not a function, and anything else in main.tsx's declaration. A new suite pins each refusal and counts the contract's 27 types. - The checker now holds the engine to the contract where they meet: MetaProblem.severity is the two values the validator produces, not string. The golden diff, against master's declarations: the same 27 names, every type identical by an Eq identity check and by mutual assignability, and the callable identical. Two controls, one widening and one narrowing, each failed the check. The one intended change is the component's JSDoc. Measured: consumers compile on @types/react 16, 17, 18 and 19, with both import styles. The corpus is identical. dist/index.js went from 311,527 to 311,539 bytes.
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
E4: the package's declarations are generated from the source. The hand-written
src/library/typesfolder is gone. The types a host gets are now the types the component is checked against.The contract is an ordinary module
src/library/contract.tsholds every published type as an export. I moved them withgit mvfromtypes/UIRender.tsx, where they were members of adeclare namespace. Each declaration's text is unchanged apart fromexportand the indentation, JSDoc included.main.tsx'sRendertakesUIRenderProps<Data>and returnsReact.ReactElement | null, written out.RenderProps, with every keyunknown.npm run gen-tsis now a scriptscripts/gen-ts.jsruns tsc ontsconfig.build.json, which now extends the typecheck config. tsc emits the declarations of the program rooted atmain.tsxinto a staging directory. The script publishes two files:dist/contract.d.ts, the contract's declaration, as tsc wrote it;dist/index.d.ts, written frommain.tsx's declaration. It holds the component renamedUIRender, a namespace that re-exports each contract type withexport import, andexport =.The script writes the second file itself because
export =cannot be in the source: Babel rejects it. It also deletes any otherdist/*.d.ts. webpack keeps declaration files across its clean, so the olddist/UIRender.d.tswould otherwise have shipped.The script refuses what must not be published:
reactand the contract, since a host's compiler would have to resolve it;const; I measured it, and theconstform fails with TS2451;main.tsx's declaration.scripts/__tests__/gen-ts.contract.test.jsis new, with 15 tests. It pins each refusal on a declaration written in the test, without running tsc. It also counts the real contract's 27 types, so adding a public type shows up in review.The checker now holds the engine to the contract where they meet
MetaProblem.severitywasstringin the validator. It is now the two values the validator produces. Loosening it back failsmain.tsx, which hands the problems to a host typed against the contract.Dataopen, while the form layer typesinitialValuesas a record. I checked that the cast is needed and that a singleassuffices.Golden diff, against master's declarations
The baseline is master's emitted
dist/*.d.ts: the Phase 0.6 contract plus the changes accepted since.Eqidentity check and by mutual assignability. The two generics were checked at their default and at a sample type.React.ComponentProps.export. The one intended change is the component's JSDoc. It used to say only that the entry is the function itself; it now also says what the function renders.Probes on the real source
Each one was reverted, and the files were compared byte for byte afterwards.
Render's signature reacheddist/index.d.ts.MetaProblem.severitytostringfailed the typecheck atmain.tsx.itemsin the contract failedtypecheck:contractwith TS2578.gen-ts.What else moved
src/library/contract.agreement.ts. It imports the contract as a module, sonpm run typecheckcovers it.typecheck:contractruns it alone and extends the same config, so the CI step keeps its name and meaning.contract.ts.dist/contract.d.tsis a required file of the tarball.UIRender.tsxorindex.ts, because Babel rejectedexport =. The agreement file, a compile-time test that never runs, is left out of coverage.CLAUDE.mddocumentsgen-tsand the contract, anddocs/UPGRADE-PLAN.mdrecords E4 and closes its items.Measured
index.d.ts,UIRender.d.tsindex.d.ts,contract.d.ts@types/react16 / 17 / 18 / 19 × interop / CommonJStranslatecallstranslatecallsdist/index.jsThe 12 bytes come from
main.tsx's restructuring. Both bundles were built fromgit archivecopies 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, 2857 tests each, with no worker crash;test:e2e, 41 tests;The suite grew by the generator's 15 tests, from 2842 to 2857.
🤖 Generated with Claude Code