From 5f8a308d620d61da21d5b72af95bfff3ad49c8f3 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 11 Sep 2026 12:54:07 +0200 Subject: [PATCH] Drop the collider and delete-node buttons from the inspector Adding a collider now lives only in the Scene objects context menu, deleting a node on the Delete key and the node context menu. The e2e checks that pinned the inspector button now drive the hierarchy menu instead, including that its entry is disabled on template content and on a placed container without raising a notice. Co-Authored-By: Claude Opus 5 --- README.md | 6 +-- src/editor/inspector/nodeSection.ts | 13 +----- src/editor/inspector/objectSection.ts | 13 +----- .../done/inspector-buttons-entfernen.md | 7 ++++ todo-notes/inspector-buttons-entfernen.md | 2 - tools/e2e/06-graph.mjs | 2 + tools/e2e/09-containers.mjs | 41 +++++++++++++++---- 7 files changed, 48 insertions(+), 36 deletions(-) create mode 100644 todo-notes/done/inspector-buttons-entfernen.md delete mode 100644 todo-notes/inspector-buttons-entfernen.md diff --git a/README.md b/README.md index 92330035..0b4ed385 100644 --- a/README.md +++ b/README.md @@ -351,8 +351,8 @@ inspector below X/Y/Z, undoable, with ↺ back to 0. It belongs to the placed ob A **collider** is an asset (`shape` box/circle, `width`/`height` or `radius`) placed like any other object – typically as a child of the sprite it belongs to, so its position is the offset -from that *body* and it moves with it. Right-click an object in **Scene objects** (or press -**+ Add collider** in its inspector) to get one with one click: a box collider sized like the +from that *body* and it moves with it. Right-click an object in **Scene objects** → *Add +collider* to get one with one click: a box collider sized like the sprite, parented at its centre. Colliders can be reused across objects, overridden per instance and stacked (hitbox + hurtbox on one sprite; colliders of the same body never collide with each other). They are editor symbols: green outlines in edit mode, invisible in play mode. @@ -644,7 +644,7 @@ npm run check # typecheck + lint + format:check + tests, what CI would ru # code is lost (tail's is reported), so a red run reads as green npm run build # writes dist/ and regenerates index.html export GM_REMOTE=user@host # optional: run the browser elsewhere; see below -node tools/seed-e2e.mjs && node tools/e2e.mjs # 535 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 537 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 – a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result diff --git a/src/editor/inspector/nodeSection.ts b/src/editor/inspector/nodeSection.ts index 8ed9398e..395e722a 100644 --- a/src/editor/inspector/nodeSection.ts +++ b/src/editor/inspector/nodeSection.ts @@ -1,4 +1,3 @@ -import { RemoveNodeCommand } from '../../core/commands/graphCommands'; import { callProblem, declaresVariablePorts, @@ -13,9 +12,9 @@ import type { Asset } from '../../core/model/Asset'; import { behaviorLookup } from '../../core/model/behaviorPrograms'; import { findAssetOf, type Project } from '../../core/model/Project'; import type { PropertyDef } from '../../core/properties/PropertySchema'; -import { Button, Label } from '../../ui'; +import { Label } from '../../ui'; import { createFieldRow, type FieldHandlers } from './fieldFactory'; -import { addGap, addNote, addTitle, type InspectorHost } from './host'; +import { addNote, addTitle, type InspectorHost } from './host'; import { buildPortSection, buildVariablePortSection } from './portSection'; import { listOpener, previewingHandlers } from './previewHandlers'; import { currentNode, nodeParamValue } from './valueSpecs'; @@ -81,14 +80,6 @@ export function buildNodeSection(host: InspectorHost, assetId: string, nodeId: s 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)), - }), - ); } /** Pushes the node's current params into the rows. False when the node is gone (rebuild). */ diff --git a/src/editor/inspector/objectSection.ts b/src/editor/inspector/objectSection.ts index ed82310d..66552990 100644 --- a/src/editor/inspector/objectSection.ts +++ b/src/editor/inspector/objectSection.ts @@ -38,7 +38,7 @@ import { resolveSceneObjectProps, } from '../../core/properties/resolve'; import { Button, TextField } from '../../ui'; -import { addCollider, canAddCollider, createContainer, removeObjects } from '../actions'; +import { createContainer, removeObjects } from '../actions'; import { aspectActions, sizeCommand } from './aspectAction'; import { defSignature } from './assetSection'; import { createFieldRow, type FieldRow, labelledRow, type RowActions } from './fieldFactory'; @@ -230,17 +230,6 @@ export function buildObjectSection(host: InspectorHost, holderId: string, object markObjectRows(host, object, asset, target); if (asset.type === 'container') contentSection(host, holderId, object, asset.objects.length); - - const stored = findObjectHolder(project, holderId); - if (stored && canAddCollider(project, stored, object)) { - addGap(host); - host.body.add( - new Button('+ Add collider', { - variant: 'ghost', - onClick: () => addCollider(host.state, holderId, objectId), - }), - ); - } } /** diff --git a/todo-notes/done/inspector-buttons-entfernen.md b/todo-notes/done/inspector-buttons-entfernen.md new file mode 100644 index 00000000..6ef94310 --- /dev/null +++ b/todo-notes/done/inspector-buttons-entfernen.md @@ -0,0 +1,7 @@ +- Scene: Add collider button aus insepctor entfernen +- Graph: Delete node button aus inspector entfernen + +Umgesetzt: beide Aktionen bleiben erreichbar, nur nicht mehr im Inspector. +- Collider: Rechtsklick auf das Objekt in **Scene objects** → *Add collider*. Das ist jetzt der + einzige Weg; seine Sperre (Template-Inhalt, platzierter Container) prüft e2e 09 über das Menü. +- Node: `Delete` oder Rechtsklick auf den Node → *Delete node*. diff --git a/todo-notes/inspector-buttons-entfernen.md b/todo-notes/inspector-buttons-entfernen.md deleted file mode 100644 index 3813c40b..00000000 --- a/todo-notes/inspector-buttons-entfernen.md +++ /dev/null @@ -1,2 +0,0 @@ -- Scene: Add collider button aus insepctor entfernen -- Graph: Delete node button aus inspector entfernen diff --git a/tools/e2e/06-graph.mjs b/tools/e2e/06-graph.mjs index 8a54bfab..87899a31 100644 --- a/tools/e2e/06-graph.mjs +++ b/tools/e2e/06-graph.mjs @@ -233,6 +233,8 @@ export async function graph(t) { // 13e. Delete removes the selected node together with its edge; undo brings both back. await select({ kind: 'node', assetId: 'mover', nodeId: newNode.id }); + await sleep(200); + check('the node inspector has no delete button of its own', !(await t.has('Delete node'))); await press('Delete'); await sleep(150); const deleted = await graphCounts(); diff --git a/tools/e2e/09-containers.mjs b/tools/e2e/09-containers.mjs index fe69e765..25e3ebda 100644 --- a/tools/e2e/09-containers.mjs +++ b/tools/e2e/09-containers.mjs @@ -296,12 +296,33 @@ export async function containers(t) { // 16i. Structure is the template's business: resolved content cannot be removed, re-parented, // given a collider or turned into a container of its own. - await selectObject(redA); - check('the inspector offers no collider for resolved content', !(await has('+ Add collider'))); - await selectObject(instanceA.id); + // The hierarchy menu is the only way to a collider, and a disabled entry must not even reach + // the command: the command refuses too, but with a notice. + const colliderFromMenu = async (id) => { + await selectObject(id); + const row = await json(`__gm.hierarchyRow(${JSON.stringify(id)})`); + const before = (await objects()).length; + const noticeNow = await notice(); + await click(row.x, row.y, 'right'); + await sleep(150); + await clickText('Add collider'); + await sleep(200); + await press('Escape'); + await sleep(100); + return { added: (await objects()).length - before, notice: await noticeNow() }; + }; + const refused = JSON.stringify({ added: 0, notice: '' }); + const fromContent = await colliderFromMenu(redA); check( - 'nor for the placed container itself, which the command refuses as the hierarchy menu does', - !(await has('+ Add collider')), + 'the menu offers no collider for resolved content', + JSON.stringify(fromContent) === refused, + JSON.stringify(fromContent), + ); + const fromInstance = await colliderFromMenu(instanceA.id); + check( + 'nor for the placed container itself', + JSON.stringify(fromInstance) === refused, + JSON.stringify(fromInstance), ); const plainId = await json(`(() => { const p = __gm.state.project; @@ -311,11 +332,15 @@ export async function containers(t) { return sprite ? sprite.id : null; })()`); await selectObject(plainId); + check('the inspector has no collider button of its own', !(await has('+ Add collider'))); + const fromPlain = await colliderFromMenu(plainId); check( - 'while a plain sprite still gets it', - Boolean(plainId) && (await has('+ Add collider')), - JSON.stringify({ plainId }), + 'while a plain sprite still gets one from the menu', + fromPlain.added === 1, + JSON.stringify(fromPlain), ); + await undo(); + await sleep(200); await selectObject(redA); let noticeAfter = await notice(); await press('Delete');