refactor(ts-migration): Phase 11 batch 5 — ui-tree and ui-editor

The eleven files that render and edit the node tree: six in
`src/components/ui-tree/`, five in `src/components/ui-editor/`. The
first `.jsx` -> `.tsx` of this phase.

Renaming raised six errors, all in one file, and one annotation cleared
all six. `UiNodeBoundary` is a class component declared
`extends React.Component` with no type arguments, so both its props and
its state are `{}` — which is why reading `this.props.node` and
`this.state.failed` looked wrong. `UiNodeBoundaryProps` and
`UiNodeBoundaryState` write down what the class already uses: `node` and
`children`, and `failed` plus `forNode`. `forNode` is what stops the
boundary staying latched after a broken node is hidden, so it is part of
the contract rather than an implementation detail.

Nothing else needed anything. That is the earlier batches paying off:
these files consume `lib/ui`, which was typed in batch 1, so the tree and
node values arriving here are already described.

The `.jsx` -> `.tsx` move is the one that can change emitted output —
esbuild's `tsx` loader elides unused imports where the `jsx` loader does
not, which cost a 942-byte bundle change in Phase 10. It did not happen
here: all eleven erase byte-identically, and the bundle hash is
unchanged.

ESLint now reports all eleven under their new extensions, checked by
name rather than by count. That is the failure Phase 0 existed to
prevent — `.tsx` outside the globs would have dropped them silently while
`eslint .` went on exiting 0.

Measured against `55ddaa1`:

  typecheck   9 errors, unchanged; no new error anywhere
  lint        exit 0, 0 errors, 289 warnings, 11/11 linted by name
  npm test    1684/1691, the same 7 failures verbatim
  build       exit 0, identical bundle hash 74d17e2d…
  type erasure  57/57 byte-identical across Phase 11 so far

No baseline artifact touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HBG1wnuRfJKCstGB8Fekr8
This commit is contained in:
2026-09-18 15:28:01 +05:30
parent 55ddaa134a
commit 446df7b37b
11 changed files with 21 additions and 1 deletions

View File

@@ -25,7 +25,27 @@ import React from 'react';
* the node it is holding changes, which is what makes removing the broken node
* put the page right without a reload.
*/
export class UiNodeBoundary extends React.Component {
/**
* `node` is the one this boundary holds; `children` is what it renders while
* that node is fine. Both optional, because the placeholder path reads them
* with `?.` and has always tolerated their absence.
*/
interface UiNodeBoundaryProps {
node?: any;
children?: React.ReactNode;
}
/**
* `forNode` is how the reset works: it records which node the current `failed`
* belongs to, so a boundary does not stay latched after the broken node is
* hidden or removed. It is absent until the first `getDerivedStateFromProps`.
*/
interface UiNodeBoundaryState {
failed: boolean;
forNode?: any;
}
export class UiNodeBoundary extends React.Component<UiNodeBoundaryProps, UiNodeBoundaryState> {
constructor(props) {
super(props);
this.state = { failed: false };