docs: audit the ui library and record the 0.8.6 migration
A measured pass rather than a bulk rewrite. Numbers come from the source and from a browser. The migration entry covers what has accumulated since 0.8.5 and would otherwise reach upgraders unannounced: the Tabs output contract, the Sidebar BEM rename, the layout components leaving Tailwind so their rendered class lists changed, LayoutSplitter and CustomScrollbar changing props and outputs, the ui.css families that were removed, and the theme tokens that now paint where they previously resolved to nothing. The audit records what is still wrong, with counts: 32 outputs across 16 components that nothing emits, 23 components still on the scaffold pattern, 66 without a local style block and therefore dependent on ui.css, and 10 still using Tailwind. A test pins the dead-output count at 32 as a ceiling that only moves down, so rebuilding a component tightens it and no new one can be added quietly. It also records what is not worth doing. Splitting the runtime saves 3 to 4 kB gzipped on a first visit to a file cached for a year, and hydration costs 1.5 ms for 21 scopes across 4325 elements, so neither is a real problem. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3100,3 +3100,67 @@ test("every wire color token a component references is defined by the theme", ()
|
||||
|
||||
expect([...missing].map(([token, users]) => `${token} <- ${users.join(", ")}`)).toEqual([]);
|
||||
});
|
||||
|
||||
test("no component gains an output that nothing ever emits", () => {
|
||||
/*
|
||||
* LayoutSplitter declared resizeStart, resize and resizeEnd with no pointer
|
||||
* handling at all: a caller wired up @resize and received nothing, for ever,
|
||||
* with no error. The same shape survives in other components, so this pins
|
||||
* the count rather than letting it grow while the rest are rebuilt.
|
||||
*
|
||||
* Native event names are excluded. The runtime binds a DOM-listener fallback
|
||||
* on component tags, so declaring click or input as an output does reach a
|
||||
* parent binding through bubbling.
|
||||
*/
|
||||
const native = new Set([
|
||||
"click",
|
||||
"focus",
|
||||
"blur",
|
||||
"input",
|
||||
"change",
|
||||
"submit",
|
||||
"copy",
|
||||
"paste",
|
||||
"load",
|
||||
"error",
|
||||
"scroll",
|
||||
"toggle",
|
||||
"drop",
|
||||
"dragstart",
|
||||
"dragend",
|
||||
"dragenter",
|
||||
"dragleave",
|
||||
"dragover",
|
||||
"keydown",
|
||||
"keyup",
|
||||
"select",
|
||||
]);
|
||||
const runtime = readFileSync(
|
||||
join(uiComponentsDir(), "..", "..", "csr", "src", "reactive-runtime.ts"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const offenders: string[] = [];
|
||||
for (const name of uiComponentNames()) {
|
||||
const source = readFileSync(uiComponentPath(name), "utf8");
|
||||
const block = /^ {2}outputs \{([\s\S]*?)^ {2}\}/m.exec(source);
|
||||
if (!block) continue;
|
||||
for (const match of block[1]!.matchAll(/^\s*([A-Za-z][A-Za-z0-9_]*)\s*\(/gm)) {
|
||||
const output = match[1]!;
|
||||
if (source.slice(block.index).includes(`output.${output}(`)) continue;
|
||||
if (source.includes("output[")) continue;
|
||||
if (native.has(output.toLowerCase())) continue;
|
||||
// Emitted by a runtime controller written for this component.
|
||||
if (runtime.includes(`"${output}"`) && new RegExp(name, "i").test(runtime)) continue;
|
||||
offenders.push(`${name}.${output}`);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A ceiling, not a target. It only ever moves down: rebuilding one of these
|
||||
* components should tighten it.
|
||||
*/
|
||||
expect(offenders.length).toBeLessThanOrEqual(32);
|
||||
expect(offenders).not.toContain("LayoutSplitter.sizeChange");
|
||||
expect(offenders).not.toContain("CustomScrollbar.scroll");
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user