Tabs are open documents: Scene, Game and Plugins stay as fixed tabs, every other asset opens in its own tab (double click or Enter in the asset panel, on creation, from the list button), several assets of one kind can be open at once, a tab is labelled with its kind dimmed before the name, cut at 160 px and renamed with its asset, closes with its x or a middle click and goes away when its asset is deleted; a tab always shows its own document instead of following the selection, the picker in the tab head is gone, zoom and scroll are kept per document for the session and dropped on close, a double click on a call node stays in the same tab with breadcrumbs per tab; one editor per kind is switched between documents, the placement rules live in the pure module documents.ts, and the test hook showTab stays as a compatibility path; new e2e module 44 documents (#171)
game-maker / gate (push) Successful in 1m59s
game-maker / suite (push) Successful in 6m53s

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5 committed 2026-09-19 15:49:54 +02:00
1 parent 4f546c0e08
commit cc285af10a
33 files changed
+895 -225

No files matched your search

+20 -15
View File
@@ -1,5 +1,5 @@
import { edge, node, port } from '../lib/builders.mjs';
import { MAKE_OBJECT } from './lib/scaffold.mjs';
import { clickDocumentTab, MAKE_OBJECT } from './lib/scaffold.mjs';
/**
* A behavior used as a node inside another behavior's graph: the ports it grows from the other
@@ -321,31 +321,36 @@ export async function nestedBehavior(t) {
`current=${await evaluate('__gm.graph.current()')} trail=${await trail()}`,
);
// Opening another behavior any other way ends the trail: the asset panel, then the picker.
// Another behavior opened any other way gets a tab of its own (#171), without a trail; the trail
// stays with the tab it was made in, and comes back with it.
const enterDouble = async () => {
const call = await json(`__gm.graph.nodeAt('nb-call')`);
await t.doubleClick(call.x, call.y);
};
await enterDouble();
const followed = await json('__gm.documents()');
check(
'following a call stays in the same tab',
followed.active === 'asset:nb-double' && !followed.docs.includes('asset:nb-caller'),
JSON.stringify(followed),
);
const emptyAgain = await json(`__gm.assetRow('nb-empty')`);
await click(emptyAgain.x, emptyAgain.y);
await t.doubleClick(emptyAgain.x, emptyAgain.y);
check(
'choosing another behavior in the asset panel ends the trail',
(await evaluate('__gm.graph.current()')) === 'nb-empty' && (await crumbs()).length === 0,
`current=${await evaluate('__gm.graph.current()')} trail=${await trail()}`,
'another behavior opened from the asset panel comes up in its own tab, without a trail',
(await evaluate('__gm.graph.current()')) === 'nb-empty' &&
(await crumbs()).length === 0 &&
(await json('__gm.documents()')).docs.includes('asset:nb-double'),
`current=${await evaluate('__gm.graph.current()')} trail=${await trail()} docs=${JSON.stringify(await json('__gm.documents()'))}`,
);
await evaluate(`__gm.state.select({ kind: 'asset', assetId: 'nb-caller' })`);
await sleep(200);
await enterDouble();
const picker = await json('__gm.graph.pickerAt()');
await click(picker.x, picker.y);
const emptyItem = await locate('Empty', true);
await click(emptyItem.x, emptyItem.y);
await clickDocumentTab(t, 'asset:nb-double');
check(
'choosing another behavior in the picker ends the trail',
(await evaluate('__gm.graph.current()')) === 'nb-empty' && (await crumbs()).length === 0,
'back in the tab the call was followed in, its trail is there again',
(await evaluate('__gm.graph.current()')) === 'nb-double' && (await trail()) === 'Caller › Double*',
`current=${await evaluate('__gm.graph.current()')} trail=${await trail()}`,
);
await evaluate(`__gm.closeDocument('asset:nb-empty')`);
await evaluate(`__gm.closeDocument('asset:nb-double')`);
// 19l. Clean up: the three behaviors and the box are gone again.
await evaluate(`__gm.state.select(null)`);