Add ESLint and Prettier, and close the node test gaps

Tooling:
- Prettier as the single formatting authority (110 columns, single quotes,
  measured against the existing style); Markdown excluded because table-cell
  padding turns the README glossary into 1000-character lines.
- Type-aware ESLint ending in eslint-config-prettier, so a rule always means
  "this is a bug", never "this looks different".
- .editorconfig and an `npm run check` that runs typecheck, lint, format and
  tests in one go.

Findings fixed on the first run: three more duplicate imports, four
String(unknown) calls (parameter types narrowed instead of casts), two dead
initialisers, two redundant casts, a console.log for a real error. Two
warnings were false here and are disabled with a reason in place.

Tests 132 -> 269:
- node-catalog.test.ts covers every node with a run(), with a guard that fails
  when a new node has no row.
- behavior-events.test.ts covers the ten event nodes through a running World,
  checking timing rather than existence.
- Input's state machine (press/release/releaseAll) is reachable without DOM
  events now; the DOM handlers are thin adapters.

Compare fell back to == without a configured operator although the schema
default is <; aligned.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-08 10:02:22 +02:00
1 parent f140c2fd77
commit d54a620ebd
113 files changed
+5049 -666

No files matched your search

+31 -6
View File
@@ -16,7 +16,11 @@ export function nodeSignature(project: Project, assetId: string, nodeId: string)
const node = asset.graph.nodes.find((n) => n.id === nodeId);
if (!node) return 'missing';
const def = nodeDef(node.type);
const connected = def ? inlineInputs(def).filter((p) => edgesInto(asset.graph, node.id, p.key).length > 0).map((p) => p.key) : [];
const connected = def
? inlineInputs(def)
.filter((p) => edgesInto(asset.graph, node.id, p.key).length > 0)
.map((p) => p.key)
: [];
return `${node.type}|${connected.join(',')}|${asset.variables.map((v) => v.key).join(',')}`;
}
@@ -35,7 +39,11 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
if (def.params.length > 0) addTitle(host, 'SETTINGS');
for (const param of def.params) {
const effective = resolveParamDef(host.state.project, param, asset);
const row = createFieldRow(effective, node.params[param.key] ?? effective.default, paramHandlers(host, assetId, nodeId, param.key));
const row = createFieldRow(
effective,
node.params[param.key] ?? effective.default,
paramHandlers(host, assetId, nodeId, param.key),
);
host.fields.set(`param:${param.key}`, row);
host.body.add(row.row);
}
@@ -44,13 +52,22 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s
if (free.length > 0) addTitle(host, 'INPUTS (unconnected)');
for (const port of free) {
const inline = inlineInputDef(port);
const row = createFieldRow(inline, node.params[port.key] ?? inline.default, paramHandlers(host, assetId, nodeId, port.key));
const row = createFieldRow(
inline,
node.params[port.key] ?? inline.default,
paramHandlers(host, assetId, nodeId, port.key),
);
host.fields.set(`input:${port.key}`, row);
host.body.add(row.row);
}
addGap(host);
host.body.add(new Button('Delete node', { variant: 'danger', onClick: () => host.state.execute(new RemoveNodeCommand(assetId, nodeId)) }));
host.body.add(
new Button('Delete node', {
variant: 'danger',
onClick: () => host.state.execute(new RemoveNodeCommand(assetId, nodeId)),
}),
);
}
/** Pushes the node's current params into the rows. False when the node is gone (rebuild). */
@@ -60,7 +77,10 @@ export function refreshNodeSection(host: InspectorHost, assetId: string, nodeId:
if (!node || !def) return false;
for (const [key, row] of host.fields) {
const [scope, name] = key.split(':') as ['param' | 'input', string];
const fallback = scope === 'param' ? def.params.find((p) => p.key === name)?.default : inlineInputDef(def.inputs.find((p) => p.key === name)!).default;
const fallback =
scope === 'param'
? def.params.find((p) => p.key === name)?.default
: inlineInputDef(def.inputs.find((p) => p.key === name)!).default;
row.setValue(node.params[name] ?? fallback ?? '');
}
return true;
@@ -76,7 +96,12 @@ function resolveParamDef(project: Project, param: PropertyDef, asset: Asset): Pr
return { ...param, options: asset.variables.map((v) => ({ value: v.key, label: v.key })) };
}
if (param.key === 'asset') {
return { ...param, options: project.assets.filter((a) => isPlaceableType(a.type)).map((a) => ({ value: a.id, label: `${a.name} (${a.type})` })) };
return {
...param,
options: project.assets
.filter((a) => isPlaceableType(a.type))
.map((a) => ({ value: a.id, label: `${a.name} (${a.type})` })),
};
}
return param;
}