Split the debug hook into one file per topic (#215)

debugHook.ts had grown to 1097 lines of unrelated test hooks. They
now live in seven files under src/editor/debug/, grouped by topic.
debugHook.ts only assembles them and is down to 86 lines.

- The shape of window.__gm is unchanged for tools/. Measured in the
  browser before and after: 120 top-level keys and 313 leaves with
  their arity, byte-identical JSON.
- The Text import is explicit in the helpers. Without it the name
  fell back to the DOM type and instanceof would never have matched.

Findings:
- The comment for vectorArt sat above pixelArt. It is back in place.
- An orphaned duplicate comment on assetRow is gone.

Tests:
- ./check.sh: 145 files, 2192 tests, CHECK EXIT 0 (unchanged)
- e2e 1-16,37,39-43,47,48: ALL PASSED, 734 checks
- The shape probe turns red against a deliberately misnested hook.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-23 10:20:06 +02:00
1 parent b830f9c747
commit ed29b2f3dc
9 files changed
+1114 -1031

No files matched your search

+143
View File
@@ -0,0 +1,143 @@
import { NameTransitionCommand } from '../../core/commands/graphCommands';
import { currentNodePalette } from '../graph/NodePalette';
import { rectOf } from '../textProbe';
import { centreOf, snapButton, type HookContext } from './hookHelpers';
/** The node graphs: behaviors, state machines, particles and shaders. */
export function graphHooks({ state, editor }: HookContext) {
const { graphEditor } = editor;
return {
graph: {
current: () => graphEditor.currentAssetId,
portAt: (nodeId: string, port: string, isOutput: boolean) =>
graphEditor.canvas.portGlobal(nodeId, port, isOutput),
nodeAt: (nodeId: string) => graphEditor.canvas.nodeGlobal(nodeId),
chevronAt: (nodeId: string) => graphEditor.canvas.chevronGlobal(nodeId),
nodeLabel: (nodeId: string) => graphEditor.canvas.nodeLabel(nodeId),
nodeProblem: (nodeId: string) => graphEditor.canvas.nodeProblem(nodeId),
nodeSettings: (nodeId: string) => graphEditor.canvas.nodeSettings(nodeId),
/** Each input row as drawn: its name, and the inline value beside it while unconnected. */
nodeInputs: (nodeId: string) => graphEditor.canvas.nodeInputs(nodeId),
/** Each output row as drawn: while playing, the value it last carried in front of its name. */
nodeOutputs: (nodeId: string) => graphEditor.canvas.nodeOutputs(nodeId),
/** Beads in flight on exec edges as drawn, in graph coordinates; `progress` 0 = source, 1 = target. */
beads: () => graphEditor.canvas.beadsDrawn(),
/** Drawn rectangles on screen: what "fit" has to keep inside `canvasRect`. */
nodeRect: (nodeId: string) => graphEditor.canvas.nodeRect(nodeId),
canvasRect: () => graphEditor.canvas.canvasRect(),
/** Bare canvas below every node – where a step right-clicks to open the palette. */
emptySpot: () => graphEditor.canvas.emptySpotGlobal(),
/**
* What the open palette lists, in order. No colour check can see a ranking: the rows all
* look alike, only their order carries the answer (#43).
*/
paletteRows: () => currentNodePalette()?.rowsDrawn() ?? null,
selected: () => state.selectedNodeIds,
/** Groups and comments as drawn – text and both colours included, since no pixel says them. */
annotations: () => graphEditor.canvas.annotationsDrawn(),
/** Where one is grabbed: its title bar, or with `corner` the handle that resizes it. */
annotationAt: (id: string, corner = false) => graphEditor.canvas.annotationGlobal(id, corner),
/** The box as drawn on screen – how a check sees whether a fit brought it into view. */
annotationRect: (id: string) => graphEditor.canvas.annotationRect(id),
dropTarget: () => graphEditor.canvas.pendingDropTarget(),
danglingEdge: () => graphEditor.canvas.danglingEdgeVisible(),
edgeDrawn: (edgeId: string) => graphEditor.canvas.edgeDrawn(edgeId),
/** The colour it was stroked in: `danger` is an edge whose two ends stopped fitting (#207). */
edgeColor: (edgeId: string) => graphEditor.canvas.edgeColor(edgeId),
/** The types the node's ports are drawn with, after a shared group had its say (#207). */
portTypes: (nodeId: string) => graphEditor.canvas.portTypes(nodeId),
foldMark: (edgeId: string, isOutput: boolean) => graphEditor.canvas.foldMarkGlobal(edgeId, isOutput),
foldMarks: () => graphEditor.canvas.foldMarksDrawn(),
// The canvas, not the whole tab: a centre measured through the header sits above the
// drawing area.
center: () => centreOf(graphEditor.canvas),
snapping: () => graphEditor.canvas.snapEnabled,
snapButton: () => snapButton(graphEditor.snap),
zoom: () => graphEditor.canvas.zoomLevel,
crumbs: () => graphEditor.breadcrumb.crumbsDrawn(),
},
stateMachine: {
current: () => editor.stateMachineEditor.currentAssetId,
nodeAt: (nodeId: string) => editor.stateMachineEditor.canvas.nodeGlobal(nodeId),
nodeLabel: (nodeId: string) => editor.stateMachineEditor.canvas.nodeLabel(nodeId),
nodeRect: (nodeId: string) => editor.stateMachineEditor.canvas.nodeRect(nodeId),
canvasRect: () => editor.stateMachineEditor.canvas.canvasRect(),
center: () => centreOf(editor.stateMachineEditor.canvas),
snapping: () => editor.stateMachineEditor.canvas.snapEnabled,
snapButton: () => snapButton(editor.stateMachineEditor.snap),
/** Where a state's hidden pin sits; it cannot be grabbed – `portGrabbable` is the check. */
portAt: (nodeId: string, port: string, isOutput: boolean) =>
editor.stateMachineEditor.canvas.portGlobal(nodeId, port, isOutput),
portGrabbable: (nodeId: string, port: string, isOutput: boolean) =>
editor.stateMachineEditor.canvas.portGrabbable(nodeId, port, isOutput),
edgeDrawn: (edgeId: string) => editor.stateMachineEditor.canvas.edgeDrawn(edgeId),
transitionLabel: (edgeId: string) => editor.stateMachineEditor.canvas.transitionLabelDrawn(edgeId),
transitionLabelAt: (edgeId: string) => editor.stateMachineEditor.canvas.transitionLabelAt(edgeId),
hint: () => editor.stateMachineEditor.canvas.shownHint(),
/** Names a transition the way the inspector row does, for steps that need one already named. */
nameTransition: (edgeId: string, name: string) =>
state.execute(new NameTransitionCommand(editor.stateMachineEditor.currentAssetId, edgeId, name)),
dropTarget: () => editor.stateMachineEditor.canvas.pendingDropTarget(),
danglingEdge: () => editor.stateMachineEditor.canvas.danglingEdgeVisible(),
emptySpot: () => editor.stateMachineEditor.canvas.emptySpotGlobal(),
selected: () => state.selectedNodeIds,
/** The start ring and the live marks as drawn – what no colour check could tell apart. */
startRing: (nodeId: string) => editor.stateMachineEditor.canvas.startRingDrawn(nodeId),
stateMarks: () => editor.stateMachineEditor.canvas.stateMarksDrawn(),
},
particles: {
current: () => editor.particlesEditor.currentAssetId,
info: () => editor.particlesEditor.shownInfo,
snapButton: () => snapButton(editor.particlesEditor.snap),
nodeAt: (nodeId: string) => editor.particlesEditor.canvas.nodeGlobal(nodeId),
nodeLabel: (nodeId: string) => editor.particlesEditor.canvas.nodeLabel(nodeId),
nodeRect: (nodeId: string) => editor.particlesEditor.canvas.nodeRect(nodeId),
nodeProblem: (nodeId: string) => editor.particlesEditor.canvas.nodeProblem(nodeId),
canvasRect: () => editor.particlesEditor.canvas.canvasRect(),
emptySpot: () => editor.particlesEditor.canvas.emptySpotGlobal(),
center: () => centreOf(editor.particlesEditor.canvas),
/** The panel beside the graph: how many particles it draws and which second it stands at. */
preview: () => ({
count: editor.particlesEditor.preview.count,
time: Math.round(editor.particlesEditor.preview.time * 100) / 100,
holding: editor.particlesEditor.preview.holding,
}),
previewRect: () => rectOf(editor.particlesEditor.preview),
/** Hold button, time readout and slider as they are shown, not as the preview holds them. */
controls: () => editor.particlesEditor.controls,
splitterAt: () => centreOf(editor.particlesEditor.splitter),
sliderAt: () => centreOf(editor.particlesEditor.slider),
/** Jumps the preview to a second – exact, because a particle is a function of its age. */
hold: (at: number | null) => editor.particlesEditor.preview.hold(at),
},
shader: {
current: () => editor.shaderEditor.currentAssetId,
info: () => editor.shaderEditor.shownInfo,
nodeAt: (nodeId: string) => editor.shaderEditor.canvas.nodeGlobal(nodeId),
nodeLabel: (nodeId: string) => editor.shaderEditor.canvas.nodeLabel(nodeId),
nodeRect: (nodeId: string) => editor.shaderEditor.canvas.nodeRect(nodeId),
nodeProblem: (nodeId: string) => editor.shaderEditor.canvas.nodeProblem(nodeId),
nodeSettings: (nodeId: string) => editor.shaderEditor.canvas.nodeSettings(nodeId),
canvasRect: () => editor.shaderEditor.canvas.canvasRect(),
emptySpot: () => editor.shaderEditor.canvas.emptySpotGlobal(),
center: () => centreOf(editor.shaderEditor.canvas),
snapping: () => editor.shaderEditor.canvas.snapEnabled,
snapButton: () => snapButton(editor.shaderEditor.snap),
/** The panel beside the graph (#168): what it shows, its clock and the sliders' values. */
preview: () => ({
...editor.shaderEditor.controls,
holding: editor.shaderEditor.preview.holding,
seconds: Math.round(editor.shaderEditor.preview.time * 100) / 100,
}),
previewRect: () => rectOf(editor.shaderEditor.preview),
/** Where the picture sits inside the panel, in screen pixels. */
pictureRect: () => editor.shaderEditor.preview.pictureRect,
sliderAt: (key: string) => {
const slider = editor.shaderEditor.sliderOf(key);
return slider ? rectOf(slider) : null;
},
splitterAt: () => centreOf(editor.shaderEditor.splitter),
hold: (at: number | null) => editor.shaderEditor.hold(at),
},
};
}