Title a collapsed Variables or Properties node by what it does

Collapsed, the node reads "Set 2 variables", "Get width" or "No properties";
expanded it is always "Variables" / "Properties". The Properties node shows
its object type (and behavior) as a line under the title instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Opus 5 committed 2026-09-11 19:53:43 +02:00
1 parent 58f19a3451
commit 0376ae175d
11 files changed
+182 -69

No files matched your search

+52 -7
View File
@@ -186,7 +186,53 @@ export async function variableNodes(t) {
await sleep(300);
check('and undo points it back', (await portsOf('vp-get'))[0].key === 'vx');
// 23f. A Pack keeps declaring its own fields, but a variable that already carries the shape can
// 23f. Collapsed, the ports are out of sight, so the title says what the node does – and only then.
const titleOf = (id) => json(`__gm.graph.nodeLabel(${JSON.stringify(id)})`);
const toggle = async (id) => {
const chevron = await json(`__gm.graph.chevronAt(${JSON.stringify(id)})`);
await click(chevron.x, chevron.y);
await sleep(250);
};
/** `locate` matches exactly, so a title cut short by a too-narrow node is not found. */
const drawnWhole = async (text) => {
const canvas = await json('__gm.graph.canvasRect()');
const hits = await json(`__gm.locateAll(${JSON.stringify(text)})`);
return hits.some((p) => p.x > canvas.x && p.x < canvas.x + canvas.w);
};
await toggle('vp-set');
await toggle('vp-get');
const shut = { set: await titleOf('vp-set'), get: await titleOf('vp-get') };
check(
'collapsed, a node writing two variables says so, and one only reading two says that',
shut.set === 'Set 2 variables' &&
shut.get === 'Get 2 variables' &&
(await drawnWhole('Set 2 variables')) &&
(await drawnWhole('Get 2 variables')),
JSON.stringify(shut),
);
await shot('02b-collapsed-titles');
const narrow = (await json(`__gm.graph.nodeRect('vp-get')`)).w;
await clickLastSet();
const switched = await titleOf('vp-get');
check(
'switching one to Set while collapsed retitles it and resizes it to the new title',
switched === 'Set vy' &&
(await drawnWhole('Set vy')) &&
(await json(`__gm.graph.nodeRect('vp-get')`)).w < narrow,
JSON.stringify({ switched, narrow }),
);
await undo();
await sleep(300);
await toggle('vp-set');
await toggle('vp-get');
const open = [await titleOf('vp-set'), await titleOf('vp-get')];
check(
'expanded again, both are plain Variables nodes',
open.join() === 'Variables,Variables',
JSON.stringify(open),
);
// 23g. A Pack keeps declaring its own fields, but a variable that already carries the shape can
// hand it over. A color field arrives as a number port – the graph has no colors.
await mutate(`
const a = __gm.state.project.assets.find((x) => x.id === 'vp-logic');
@@ -232,7 +278,7 @@ export async function variableNodes(t) {
await sleep(300);
check('one undo takes all of them back', (await portsOf('vp-pack')).length === 0);
// 23g. A list of structs declares the shape of its items, which is the bundle a Pack builds for it.
// 23h. A list of structs declares the shape of its items, which is the bundle a Pack builds for it.
await click(from.x, from.y);
await sleep(200);
await clickLast('shapes (item)');
@@ -246,7 +292,7 @@ export async function variableNodes(t) {
await undo();
await sleep(300);
// 23h. A behavior's own inputs are not fields of a bundle, so the helper stays out of that section.
// 23i. A behavior's own inputs are not fields of a bundle, so the helper stays out of that section.
await mutate(`
__gm.state.project.assets.find((x) => x.id === 'vp-logic').graph.nodes.push(
${JSON.stringify(node('vp-start', 'BehaviorStart', 560, 600))},
@@ -262,7 +308,7 @@ export async function variableNodes(t) {
JSON.stringify(helper),
);
// 23i. From the palette the node comes empty; picking a variable titles it, and switched to Set
// 23j. From the palette the node comes empty; picking a variable keeps its title, and switched to Set
// it runs `vz = vz + 1` once a frame – its own output feeding its input via Add.
const nodeIds = async () => (await asset('vp-logic')).graph.nodes.map((n) => n.id);
const idsBefore = await nodeIds();
@@ -278,7 +324,6 @@ export async function variableNodes(t) {
const added = (await asset('vp-logic')).graph.nodes.find((n) => !idsBefore.includes(n.id));
check('the palette offers the Variables node', added?.type === 'Variables', JSON.stringify(added));
const variableId = added?.id ?? '';
const titleOf = (id) => json(`__gm.graph.nodeLabel(${JSON.stringify(id)})`);
const untitled = await titleOf(variableId);
const bare = !(await portAt(variableId, 'exec', false));
await clickLast('+ Variable');
@@ -286,8 +331,8 @@ export async function variableNodes(t) {
await sleep(300);
await clickLastSet();
check(
'it starts without pins, and picking its variable makes that the title',
untitled === 'Variables' && bare && (await titleOf(variableId)) === 'vz',
'it starts without pins, and picking its variable leaves the title Variables',
untitled === 'Variables' && bare && (await titleOf(variableId)) === 'Variables',
JSON.stringify({ untitled, bare, titled: await titleOf(variableId) }),
);
check('switched to Set it grows its flow pins', Boolean(await portAt(variableId, 'exec', false)));
+24 -11
View File
@@ -36,6 +36,7 @@ export async function propertyNodes(t) {
(p) => `${p.key}:${p.kind}${p.write ? ':set' : ''}`,
);
const title = (nodeId) => json(`__gm.graph.nodeLabel(${JSON.stringify(nodeId)})`);
const settings = async (nodeId) => (await json(`__gm.graph.nodeSettings(${JSON.stringify(nodeId)})`)) ?? [];
const select = async (nodeId) => {
await evaluate(
`__gm.state.select({ kind: 'node', assetId: 'pp-logic', nodeId: ${JSON.stringify(nodeId)}, nodeIds: [${JSON.stringify(nodeId)}] })`,
@@ -111,14 +112,22 @@ export async function propertyNodes(t) {
JSON.stringify({ width: box?.width, color: box?.color, seen }),
);
// 24c. Named after its type; the type is chosen above the list it decides.
// 24c. Titled Properties; the type stands on the node as a setting row, chosen above the list it decides.
await select('pp-set');
const typeRow = await json(`__gm.inspectorField('Type', true)`);
const header = await locate('PROPERTIES', true);
check(
'the node is named after its type, chosen above its properties',
(await title('pp-set')) === 'Sprite properties' && typeRow && typeRow.y < header.y,
JSON.stringify({ title: await title('pp-set'), type: typeRow?.y, header: header.y }),
'the node is titled Properties and shows its type, chosen above its properties',
(await title('pp-set')) === 'Properties' &&
(await settings('pp-set')).join() === 'Type: Sprite' &&
typeRow &&
typeRow.y < header.y,
JSON.stringify({
title: await title('pp-set'),
rows: await settings('pp-set'),
type: typeRow?.y,
header: header.y,
}),
);
check(
'the node shows a typed-in color as a color',
@@ -165,9 +174,11 @@ export async function propertyNodes(t) {
await choose('Type', 'Camera');
const camera = await offered();
check(
'switching to Camera renames the node and offers the fields of a camera',
(await title('pp-set')) === 'Camera properties' && camera.includes('zoom') && !camera.includes('texture'),
JSON.stringify({ title: await title('pp-set'), offered: camera }),
'switching to Camera shows it on the node and offers the fields of a camera',
(await settings('pp-set')).join() === 'Type: Camera' &&
camera.includes('zoom') &&
!camera.includes('texture'),
JSON.stringify({ rows: await settings('pp-set'), offered: camera }),
);
check(
'a port the new type does not have stays, marked',
@@ -177,7 +188,7 @@ export async function propertyNodes(t) {
await shot('03-camera');
await undo();
await sleep(300);
check('undo switches back', (await title('pp-set')) === 'Sprite properties', await title('pp-set'));
check('undo switches back', (await settings('pp-set')).join() === 'Type: Sprite', await settings('pp-set'));
// 24g. Any names its ports by hand, and the kind is picked when adding one. A name that happens
// to be a property's stays of the kind picked.
@@ -218,9 +229,11 @@ export async function propertyNodes(t) {
await choose('Behavior', 'Reshape (behavior)');
const vars = await offered();
check(
'picking the behavior offers its variables and names the node after it',
vars.includes('seen') && vars.includes('enabled') && (await title('pp-beh')) === 'Reshape properties',
JSON.stringify({ title: await title('pp-beh'), offered: vars }),
'picking the behavior offers its variables and shows it on the node',
vars.includes('seen') &&
vars.includes('enabled') &&
(await settings('pp-beh')).join() === 'Type: Behavior,Behavior: Reshape',
JSON.stringify({ rows: await settings('pp-beh'), offered: vars }),
);
await addProperty('seen');
check('a variable is picked as a port', (await portsOf('pp-beh')).join() === 'seen:number');