Let a Start input or a variable name the state machine it carries

A sub-behavior that gets its machine through a Start input had no
state ports on Is in state, Set state and the other state nodes.
Their ports came only from an inline pick or a State machine literal,
so a machine handed in from outside left them empty.

- A statemachine port of a Start node can now name a machine
  (DeclaredPort.machine). The inspector shows the pick next to the
  port name. SetPortMachineCommand sets it with undo.
- machineIdOf follows an edge from such a port to that machine, so
  the state nodes it feeds offer its states.
- A Run behavior node has the machine picked in advance on that input.
- A different pick there is allowed, but the node warns, because the
  called graph asks for the states of its own machine.
- A statemachine variable of a Variables node can name a machine
  the same way, picked next to the variable's row.
- The inspector signature of a node now includes the picked machine.
  Before, the pick changed the ports but the dropdown kept its text
  until the node was selected again.
- A local machine handed in still starts in no state every call.
  That rule from #229 is unchanged.

Tests:
- tests/core/start-machine.test.ts (7): ports with and without a
  machine and undo, save and load, the preset value moving a shared
  machine through the call, the warning, a port of another kind,
  the inspector signature, a Variables node.
- Counter checks: without the new branch in machineIdOf 2 tests
  fail, without the machine in the signature 1 test fails.
- Gate on melektaus-atx: 239 files, 3599 tests, CHECK EXIT 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-04 18:15:56 +02:00
1 parent 3f12a77aea
commit d80d5cce26
8 files changed
+284 -10

No files matched your search

+42
View File
@@ -279,6 +279,48 @@ export class SetPortWriteCommand implements Command {
}
}
/**
* Says which machine a `statemachine` port carries – an input of a start node, or a variable a
* Variables node reads; empty says none. Edges on the states of the one before stay where they are
* and hang there, as when a machine loses a state.
*/
export class SetPortMachineCommand implements Command {
readonly label: string;
private was: string | undefined;
constructor(
private readonly assetId: string,
private readonly nodeId: string,
private readonly key: string,
private readonly machine: string,
) {
this.label = `Set machine of ${key}`;
}
execute(project: Project): void {
const node = requireNode(project, this.assetId, this.nodeId);
if (!isEntryTerminal(node.type) && !declaresVariablePorts(node.type))
throw new CommandError('Only a start node or a Variables node carries a machine');
const port = (node.ports ?? []).find((p) => p.key === this.key);
if (!port) throw new CommandError(`Port "${this.key}" not found`);
if (port.kind !== 'statemachine') throw new CommandError(`"${this.key}" is not a state machine`);
this.was = port.machine;
this.mark(node, this.machine);
}
undo(project: Project): void {
this.mark(requireNode(project, this.assetId, this.nodeId), this.was ?? '');
}
private mark(node: GraphNode, machine: string): void {
node.ports = (node.ports ?? []).map((port) => {
if (port.key !== this.key) return port;
const { machine: _, ...plain } = port;
return machine ? { ...plain, machine } : plain;
});
}
}
/** Rewrites the edges and inline values pointing at the port too, in every graph that has any. */
export class RenamePortCommand extends RenameCommand {
constructor(