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:
rootandClaude Opus 5 committed 2026-09-12 19:35:40 +02:00
1 parent e7d82bd1a9
commit c47fd5e4dc
25 files changed
+690 -42

No files matched your search

+21 -3
View File
@@ -2,7 +2,8 @@ import type { Application } from 'pixi.js';
import { findAsset, findObjectHolder } from '../core/model/Project';
import type { ProjectStore } from '../core/store/ProjectStore';
import type { TextureStore } from '../core/store/TextureStore';
import { Button, HStack, Splitter, Theme, ui, VStack, type Widget } from '../ui';
import { Button, clamp, HStack, Panel, Splitter, Theme, ui, VStack, type Widget } from '../ui';
import { ConsolePanel } from './console/ConsolePanel';
import { GameViewport } from './viewport/GameViewport';
import { SceneViewport } from './viewport/SceneViewport';
import { attachAutosave } from './autosave';
@@ -52,6 +53,9 @@ const MIN_LEFT_WIDTH = 160;
const MAX_LEFT_WIDTH = 500;
const MIN_RIGHT_WIDTH = 200;
const MAX_RIGHT_WIDTH = 600;
// The floor is the console's own header: dragged all the way shut it stays visible to drag open.
const MIN_CONSOLE_HEIGHT = Panel.HEADER_HEIGHT;
const MAX_CONSOLE_HEIGHT = 600;
/**
* Composes toolbar, panels, viewports and graph editor into the one application that edits and
@@ -72,6 +76,7 @@ export class Editor implements PlayHost {
readonly hierarchy: HierarchyPanel;
readonly assets: AssetPanel;
readonly inspector: Inspector;
readonly console = new ConsolePanel();
tab: CentreTab = 'scene';
/** Frame rate, refreshed twice a second; null until the first measurement. */
fps: number | null = null;
@@ -81,6 +86,7 @@ export class Editor implements PlayHost {
private readonly leftColumn: VStack;
private readonly leftSplitter = new Splitter('x');
private readonly rightSplitter = new Splitter('x');
readonly consoleSplitter = new Splitter('y');
private readonly workspace: HStack;
private readonly centre: VStack;
private readonly tabButtons: Record<CentreTab, Button>;
@@ -88,8 +94,9 @@ export class Editor implements PlayHost {
private readonly toolbar: Toolbar;
private fpsFrames = 0;
private fpsElapsed = 0;
private leftWidth = 240;
private rightWidth = 300;
private leftWidth = 250;
private rightWidth = 350;
private consoleHeight = 300;
constructor(
app: Application,
@@ -169,6 +176,16 @@ export class Editor implements PlayHost {
this.panes[tab].visible = tab === 'scene';
}
this.tabButtons.scene.setToggled(true);
// The console sits below whichever tab is showing, so it is a sibling of the panes, not of
// the workspace: switching tabs must not move it.
this.centre.add(this.consoleSplitter);
this.centre.add(this.console);
this.console.minHeight = this.consoleHeight;
this.consoleSplitter.onDrag = (delta) => {
this.consoleHeight = clamp(this.consoleHeight - delta, MIN_CONSOLE_HEIGHT, MAX_CONSOLE_HEIGHT);
this.console.minHeight = this.consoleHeight;
this.centre.relayout();
};
this.leftColumn.minWidth = this.leftWidth;
this.inspector.minWidth = this.rightWidth;
@@ -263,6 +280,7 @@ export class Editor implements PlayHost {
this.measureFps(dt);
this.play.tick(dt);
this.editorRun.tick(dt);
this.console.refresh();
if (this.tab === 'animation') this.animationEditor.tick(dt);
}