An edge to a vanished port needs a handle, so the port stays as a ghost (#60)
game-maker / suite (push) Successful in 10m7s

Four ways erase a derived port and take no edge with them: deleting a state,
deleting the `State machine` literal, deleting `Behavior start`/`end`, deleting
the asset. The common cause is `RemoveNodeCommand` clearing only its own graph,
so the fix sits in `drawnDef` (graph/nested.ts) and touches no delete site.

A right-click on an edge goes through `view.portAt` only — there is no curve
hit-test. Without a port row such an edge is not merely invisible, it is
unreachable, so the ghost row is the handle Disconnect hangs on.

`resolveDef` stays untouched: it is the truth for interpreter, compiler,
ConnectCommand and dropTarget, so nothing runs or connects through a ghost
without a second guard.
This commit is contained in:
root committed 2026-09-14 11:50:36 +02:00
1 parent d5e67cd80c
commit a041322a0e
8 files changed
+372 -16

No files matched your search

+12 -4
View File
@@ -82,6 +82,11 @@ export function portColor(type: PortType): number {
}
}
function rowStyle(port: PortDef): TextStyleToken {
if (port.ghost) return 'warning';
return port.type === 'exec' ? 'body' : 'muted';
}
export interface PortHit {
port: PortDef;
isOutput: boolean;
@@ -336,7 +341,7 @@ export class NodeView extends Widget {
if (input) {
const text = add(
this.inputText(input),
input.type === 'exec' ? 'body' : 'muted',
rowStyle(input),
PAD + PORT_RADIUS + 4,
rowTop,
'left',
@@ -347,13 +352,13 @@ export class NodeView extends Widget {
if (output) {
const text = add(
output.label,
output.type === 'exec' ? 'body' : 'muted',
rowStyle(output),
this.w - PAD - PORT_RADIUS - 4,
rowTop,
'right',
NODE_WIDTH / 2 - 10,
);
if (output.type !== 'exec')
if (output.type !== 'exec' && !output.ghost)
this.valueSlots.set(output.key, { left: inputEnd + 6, right: text.x - 6, top: rowTop + 1 });
}
}
@@ -394,7 +399,10 @@ export class NodeView extends Widget {
this.ports.circle(x, y, r + 2).stroke({ width: 2, color: c.node });
this.ports.circle(x, y, r + 4).stroke({ width: 2, color: c.portTarget });
}
if (port.type === 'exec') {
if (port.ghost) {
// Hollow: a pin is drawn where a port used to be, and nothing stands behind it any more.
this.ports.circle(x, y, r).stroke({ width: 2, color: c.danger });
} else if (port.type === 'exec') {
// A pentagon pointing right: the Blueprint-style exec pin.
this.ports
.moveTo(x - r, y - r)