diff --git a/services/minecraft/src/debug/web/app.js b/services/minecraft/src/debug/web/app.js index 3232614ac..6cb92b71c 100644 --- a/services/minecraft/src/debug/web/app.js +++ b/services/minecraft/src/debug/web/app.js @@ -895,9 +895,95 @@ class TimelinePanel { // Application // ============================================================================= +// ============================================================================= +// Layout Manager (Resizing & Maximizing) +// ============================================================================= + +class LayoutManager { + constructor() { + this.root = document.documentElement + this.activeSplitter = null + this.startPos = 0 + this.startSize = 0 + } + + init() { + this.setupSplitters() + this.setupMaximizeButtons() + } + + setupSplitters() { + const splitters = [ + { id: 'splitter-v1', var: '--col-left', type: 'v' }, + { id: 'splitter-h1', var: '--row-1', type: 'h' }, + { id: 'splitter-h2', var: '--row-2', type: 'h' }, + { id: 'splitter-h3', var: '--row-3', type: 'h' }, + ] + + splitters.forEach((config) => { + const el = document.getElementById(config.id) + if (!el) + return + + el.addEventListener('mousedown', (e) => { + this.activeSplitter = { el, ...config } + this.startPos = config.type === 'v' ? e.clientX : e.clientY + + const style = getComputedStyle(this.root) + this.startSize = Number.parseInt(style.getPropertyValue(config.var), 10) + + el.classList.add('dragging') + document.body.style.cursor = config.type === 'v' ? 'col-resize' : 'row-resize' + document.body.style.userSelect = 'none' // Prevent text selection + }) + }) + + document.addEventListener('mousemove', e => this.handleDrag(e)) + document.addEventListener('mouseup', () => this.handleDragEnd()) + } + + handleDrag(e) { + if (!this.activeSplitter) + return + + const currentPos = this.activeSplitter.type === 'v' ? e.clientX : e.clientY + const delta = currentPos - this.startPos + const newSize = Math.max(50, this.startSize + delta) // Min 50px + + this.root.style.setProperty(this.activeSplitter.var, `${newSize}px`) + } + + handleDragEnd() { + if (!this.activeSplitter) + return + + this.activeSplitter.el.classList.remove('dragging') + this.activeSplitter = null + document.body.style.cursor = '' + document.body.style.userSelect = '' + } + + setupMaximizeButtons() { + document.querySelectorAll('.maximize-btn').forEach((btn) => { + btn.addEventListener('click', (e) => { + e.stopPropagation() // Prevent bubbling + const panel = btn.closest('.panel') + if (!panel) + return + + panel.classList.toggle('maximized') + const isMaximized = panel.classList.contains('maximized') + btn.textContent = isMaximized ? '✕' : '⤢' + btn.title = isMaximized ? 'Restore' : 'Maximize' + }) + }) + } +} + class DebugApp { constructor() { this.client = new DebugClient() + this.layoutManager = new LayoutManager() this.queuePanel = new QueuePanel(this.client) this.reflexPanel = new ReflexPanel(this.client) this.blackboardPanel = new BlackboardPanel(this.client) @@ -919,6 +1005,9 @@ class DebugApp { } init() { + // Initialize layout + this.layoutManager.init() + // Initialize all panels Object.values(this.panels).forEach(panel => panel.init()) diff --git a/services/minecraft/src/debug/web/index.html b/services/minecraft/src/debug/web/index.html index d9b288634..bda72f5df 100644 --- a/services/minecraft/src/debug/web/index.html +++ b/services/minecraft/src/debug/web/index.html @@ -47,7 +47,10 @@

Queue & Processing

- 0 +
+ 0 + +
@@ -63,7 +66,10 @@

Reflex State

- idle +
+ idle + +
@@ -93,7 +99,10 @@

Blackboard

- +
+ + +
{}
@@ -101,6 +110,9 @@
+ +
+
@@ -118,6 +130,7 @@ Auto-scroll +
@@ -125,8 +138,11 @@
+ +
+ -
+

Event Timeline

@@ -137,6 +153,7 @@ +
@@ -153,23 +170,33 @@
+ +
+ -
+

LLM Traces

- 0 +
+ 0 + +
+ +
+ -
+

Saliency Heatmap

Slot: 0 +
diff --git a/services/minecraft/src/debug/web/styles.css b/services/minecraft/src/debug/web/styles.css index c4d6c81ae..705d7ecc9 100644 --- a/services/minecraft/src/debug/web/styles.css +++ b/services/minecraft/src/debug/web/styles.css @@ -17,6 +17,12 @@ --accent-error: #f85149; --font-mono: 'Consolas', 'Monaco', 'Courier New', monospace; --font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + + /* Resizable Grid Defaults */ + --col-left: 350px; + --row-1: 300px; + --row-2: 200px; + --row-3: 200px; } /* Base Styles */ @@ -138,9 +144,11 @@ button:hover, /* Main Grid Layout */ #main-grid { display: grid; - grid-template-columns: 350px 1fr; - grid-template-rows: minmax(300px, 1fr) minmax(200px, 0.6fr) minmax(200px, 0.4fr); - gap: 8px; + /* Columns: Left Panel | Splitter | Right Content */ + grid-template-columns: var(--col-left) 8px 1fr; + /* Rows: Logs | Splitter | Timeline | Splitter | LLM | Splitter | Saliency */ + grid-template-rows: var(--row-1) 8px var(--row-2) 8px var(--row-3) 8px 1fr; + gap: 0; padding: 8px; height: calc(100vh - 48px); overflow: hidden; @@ -150,33 +158,109 @@ button:hover, display: flex; flex-direction: column; gap: 8px; + grid-column: 1 / 2; + grid-row: 1 / -1; + /* Span full height */ + overflow: hidden; + padding-right: 4px; + /* Padding for visual separation */ +} + +/* Splitters */ +.splitter-v { + grid-row: 1 / -1; + grid-column: 2 / 3; + cursor: col-resize; + background-color: transparent; + width: 8px; + z-index: 10; + position: relative; +} + +.splitter-v::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: 3px; + width: 2px; + background-color: var(--border-color); + transition: background-color 0.2s; +} + +.splitter-v:hover::after, +.splitter-v.dragging::after { + background-color: var(--accent-info); +} + +.splitter-h { + grid-column: 3 / 4; + cursor: row-resize; + background-color: transparent; + height: 8px; + z-index: 10; + position: relative; +} + +.splitter-h::after { + content: ''; + position: absolute; + left: 0; + right: 0; + top: 3px; + height: 2px; + background-color: var(--border-color); + transition: background-color 0.2s; +} + +.splitter-h:hover::after, +.splitter-h.dragging::after { + background-color: var(--accent-info); +} + +/* Grid Sections */ +#logs-section { + grid-column: 3 / 4; grid-row: 1 / 2; -} - -#queue-section { - flex: 0 0 auto; - max-height: 40%; - min-height: 180px; -} - -#blackboard-section { - flex: 1; min-height: 0; } -#logs-section { - grid-row: 1 / 2; +#splitter-v1 { grid-column: 2 / 3; + grid-row: 1 / -1; +} + +#splitter-h1 { + grid-column: 3 / 4; + grid-row: 2 / 3; +} + +#timeline-section { + grid-column: 3 / 4; + grid-row: 3 / 4; + min-height: 0; +} + +#splitter-h2 { + grid-column: 3 / 4; + grid-row: 4 / 5; } #llm-section { - grid-row: 2 / 3; - grid-column: 1 / 3; + grid-column: 3 / 4; + grid-row: 5 / 6; + min-height: 0; +} + +#splitter-h3 { + grid-column: 3 / 4; + grid-row: 6 / 7; } #saliency-section { - grid-row: 3 / 4; - grid-column: 1 / 3; + grid-column: 3 / 4; + grid-row: 7 / 8; + min-height: 0; } /* Panel Styles */ @@ -232,6 +316,24 @@ button:hover, border-radius: 3px; } +.maximize-btn { + font-size: 14px; + margin-left: 0.5rem; +} + +.maximized { + position: fixed !important; + top: 48px !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + z-index: 1000 !important; + margin: 0 !important; + width: auto !important; + height: auto !important; + border-radius: 0 !important; +} + .panel-controls input[type="search"] { width: 200px; } @@ -557,23 +659,8 @@ button:hover, Timeline Panel Styles ============================================================================= */ -#timeline-section { - grid-row: 2 / 3; - grid-column: 1 / 3; -} - -#llm-section { - grid-row: 3 / 4; -} - -#saliency-section { - grid-row: 4 / 5; -} - /* Adjust grid for timeline */ -#main-grid { - grid-template-rows: minmax(250px, 1fr) minmax(180px, 0.5fr) minmax(180px, 0.4fr) minmax(150px, 0.3fr); -} +/* REMOVED: Managed in main-grid definition now */ .timeline-container { flex: 1;