Console panel: capture console.*, timestamped lines, resizable (#50)
A log layer (src/editor/console/log.ts) keeps a 500-line ring buffer and
wraps console.log/info/debug/warn/error, still calling the original. Notices
and behaviour runtime errors go straight to the layer instead of through
console, so editor tests stay quiet and errors can render red. world.log is
untouched: it is per-run structured state that runtime tests and e2e read
directly, not a text stream.
The panel sits under the visible tab, resizable with Splitter('y'), with a
CONSOLE header and a clear button. Each line carries its wall-clock time in
its own dimmed column, so locate() still finds the message text.
The taller centre area drops the graph fit zoom to 0.60, which broke six e2e
checks that assumed one screen pixel is one graph unit and that a fixed
offset from the canvas centre is empty. They now ask __gm.emptySpot(),
__gm.graph.emptySpot() and __gm.graph.zoom() instead of guessing.
locate/locateAll added the unscaled Pixi text width to the global position,
so click points in the scaled graph canvas landed too far right; they use
getBounds() now.
check.sh: 831 tests. Full e2e: ALL PASSED - 671 checks. Smokes 24/19/20.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e7d82bd1a9
commit
c47fd5e4dc
25 files changed
+690
-42
No files matched your search
+40
-2
@@ -24,8 +24,10 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
const walk = (node: Container): void => {
|
||||
if (!node.visible) return;
|
||||
if (node instanceof Text && node.text === text) {
|
||||
const p = node.getGlobalPosition();
|
||||
hits.push({ x: p.x + node.width / 2, y: p.y + node.height / 2 });
|
||||
// Global bounds, not `width`/`height`: those are unscaled, and inside the graph canvas
|
||||
// (which is scaled by the zoom) that put the point right of the text it was meant to hit.
|
||||
const box = node.getBounds();
|
||||
hits.push({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
|
||||
return;
|
||||
}
|
||||
for (const child of node.children) walk(child);
|
||||
@@ -180,6 +182,30 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
labelsDropped,
|
||||
shownLabel,
|
||||
viewportCenter: () => centreOf(viewport),
|
||||
/** Its drawn box – a step that wants an empty spot must stay inside what the console left over. */
|
||||
viewportRect: () => {
|
||||
const p = viewport.getGlobalPosition();
|
||||
return { x: p.x, y: p.y, w: viewport.w, h: viewport.h };
|
||||
},
|
||||
/**
|
||||
* A point in the viewport that no object covers – steps click there to deselect. A fixed
|
||||
* offset from the centre lands on an object as soon as the viewport changes size.
|
||||
*/
|
||||
emptySpot: () => {
|
||||
const p = viewport.getGlobalPosition();
|
||||
const scene = state.activeScene;
|
||||
const taken = scene
|
||||
? objectsOf(scene.id)
|
||||
.map((object) => viewport.objectRect(object.id))
|
||||
.filter((rect): rect is NonNullable<typeof rect> => rect !== null)
|
||||
: [];
|
||||
const clear = (x: number, y: number) =>
|
||||
!taken.some((r) => x >= r.x - 12 && x <= r.x + r.w + 12 && y >= r.y - 12 && y <= r.y + r.h + 12);
|
||||
for (let y = p.y + viewport.h - 24; y > p.y + 24; y -= 24) {
|
||||
for (let x = p.x + 24; x < p.x + viewport.w - 24; x += 24) if (clear(x, y)) return { x, y };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
viewportInfo: () => viewport.debugInfo(),
|
||||
/** Where a dragged asset would land in the Scene tab, and why not when it may not. */
|
||||
dropPreview: () => viewport.dropInfo(),
|
||||
@@ -216,6 +242,8 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
/** 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(),
|
||||
selected: () => state.selectedNodeIds,
|
||||
dropTarget: () => graphEditor.canvas.pendingDropTarget(),
|
||||
danglingEdge: () => graphEditor.canvas.danglingEdgeVisible(),
|
||||
@@ -228,6 +256,7 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
pickerWidth: () => graphEditor.picker.w,
|
||||
pickerAt: () => centreOf(graphEditor.picker),
|
||||
snapping: () => graphEditor.canvas.snapEnabled,
|
||||
zoom: () => graphEditor.canvas.zoomLevel,
|
||||
crumbs: () => graphEditor.breadcrumb.crumbsDrawn(),
|
||||
},
|
||||
stateMachine: {
|
||||
@@ -240,6 +269,15 @@ export function installDebugHook(app: Application, state: EditorState, editor: E
|
||||
center: () => centreOf(editor.stateMachineEditor.canvas),
|
||||
snapping: () => editor.stateMachineEditor.canvas.snapEnabled,
|
||||
},
|
||||
/** The console panel: its lines as kept, its drawn height, and the divider above it. */
|
||||
console: {
|
||||
lines: () => editor.console.debugInfo().lines,
|
||||
height: () => editor.console.h,
|
||||
clear: () => editor.console.clear(),
|
||||
scroll: () => editor.console.scrollInfo(),
|
||||
centre: () => centreOf(editor.console),
|
||||
splitterAt: () => centreOf(editor.consoleSplitter),
|
||||
},
|
||||
/** The open image picker: what it offers, what is picked, and where a tile sits. */
|
||||
imagePicker: () => currentImagePicker()?.debugInfo() ?? null,
|
||||
imagePickerTile: (file: string) => currentImagePicker()?.tileGlobal(file) ?? null,
|
||||
|
||||
Reference in new issue
Block a user