Lint every UI package, fix log socket leaks and CloneDto drift

Closes #787 (closed).

Only packages/ce/src was linted, so 235 of 314 UI files never saw ESLint and packages/shared had no linter and no test runner at all. This turns lint on everywhere, fixes what that surfaced, and clears the leaks and type drift the audit listed.

Lint and test every package (17a, 17b)

The flat config both packages share moves to ui/eslint.config.base.mjs; ce and shared spread it and keep only their own overrides (cypress rules, CommonJS build scripts). Plugin deps live at the workspace root, so there is one version of each. check-code-style runs pnpm -r run lint.

Turning it on surfaced 105 errors in shared, now zero. Mostly mechanical: escaped JSX entities, rel on target="_blank" links, const over let, displayName on forwardRef/memo wrappers, Object.prototype.hasOwnProperty.call, statements in place of unused expressions, @ts-expect-error with a reason.

The 28 react-hooks/exhaustive-deps findings stay warnings. Fixing them changes behaviour and belongs with the hooks they flag, not here.

shared also gets Vitest, happy-dom and testing-library, with suites for the error boundary and the clone formatter. ce-unit-tests becomes ui-unit-tests and runs pnpm -r run test.

Log socket and observer leaks (18)

useWsScroll listened for DOMNodeInserted, which browsers removed, so auto-scroll was dead. It is now a MutationObserver; the tagName !== 'DIV' guard becomes a nodeName filter over the added nodes, so the snackbar div is still ignored.

The socket leak was worse than the issue describes: restartConnection reconnects without closing, so every filter toggle leaked a socket, not just every mount. wsLogs now tracks the single active socket and closes it before reconnecting and on unmount. A generation counter discards a socket that opens after the caller already left, since the token fetch is async. The MutationObserver in Logs/index.tsx is disconnected too.

Error boundary (19)

A render error anywhere unmounted the whole tree and left a blank page with nothing naming the cause. ErrorBoundary lands in shared and wraps the CE root.

Configuration re-render churn (20)

The page deep-cloned the store config on every render. It is behind useMemo(…, [config]) now — the store replaces the whole object on each fetch, so its identity is the right key, and the dependency array still dereferences config during render so MobX tracking is preserved. The Simple/Expert default is seeded from a primitive instead of the cloned object.

CloneDto (21a)

models.Clone reports revision and deleteAt, and its metadata reports logicalSize and maxIdleMinutes; none were declared. Added, with deleteAt parsed into a date alongside the other timestamps.

Type errors this surfaced

Removing any from GetFullConfig and React.ChangeEvent<{}> exposed three real errors, fixed in the same commit: an org prop that could be undefined, a getFullConfigError assignment that could be undefined, and a @ts-ignore in styles/theme.ts that had stopped being necessary.

Not included

21b — the license field and the 132-file "Proprietary and confidential" header sweep. It needs maintainer sign-off because shared is published to npm, so it stays a follow-up.

Merge request reports

Loading
Loading