4runr-os 1.4.2 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ui/v3/commands/commandEngine.js +203 -0
- package/dist/ui/v3/commands/commandEngine.js.map +1 -1
- package/dist/ui/v3/tui/geometry.d.ts +56 -0
- package/dist/ui/v3/tui/geometry.d.ts.map +1 -0
- package/dist/ui/v3/tui/geometry.js +124 -0
- package/dist/ui/v3/tui/geometry.js.map +1 -0
- package/dist/ui/v3/ui/debugUtils.d.ts +61 -0
- package/dist/ui/v3/ui/debugUtils.d.ts.map +1 -0
- package/dist/ui/v3/ui/debugUtils.js +226 -0
- package/dist/ui/v3/ui/debugUtils.js.map +1 -0
- package/dist/ui/v3/ui/layout/phase1Layout.d.ts +21 -5
- package/dist/ui/v3/ui/layout/phase1Layout.d.ts.map +1 -1
- package/dist/ui/v3/ui/layout/phase1Layout.js +110 -55
- package/dist/ui/v3/ui/layout/phase1Layout.js.map +1 -1
- package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts +5 -0
- package/dist/ui/v3/ui/layout/phase1Layout.test.d.ts.map +1 -0
- package/dist/ui/v3/ui/layout/phase1Layout.test.js +92 -0
- package/dist/ui/v3/ui/layout/phase1Layout.test.js.map +1 -0
- package/dist/ui/v3/ui/phase1RuntimeClean.d.ts +4 -0
- package/dist/ui/v3/ui/phase1RuntimeClean.d.ts.map +1 -1
- package/dist/ui/v3/ui/phase1RuntimeClean.js +111 -37
- package/dist/ui/v3/ui/phase1RuntimeClean.js.map +1 -1
- package/dist/ui/v3/v1Adapters/connect.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,66 +1,96 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Phase 1 Layout Calculator
|
|
2
|
+
* Phase 1 Layout Calculator - FINAL BULLETPROOF EDITION
|
|
3
3
|
*
|
|
4
4
|
* Fixed hub layout with 7 boxes:
|
|
5
5
|
* - Left column (3 stacked): POSTURE, RESOURCES, ASSETS
|
|
6
6
|
* - Center (1): OPERATIONS
|
|
7
7
|
* - Right column (2 stacked): NETWORK, CAPABILITIES
|
|
8
8
|
* - Bottom bar (1): COMMAND LINE
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL RULES (NEVER VIOLATE):
|
|
11
|
+
* 1. Use geometry.ts for all dimension queries
|
|
12
|
+
* 2. Total width MUST NOT exceed safeCols
|
|
13
|
+
* 3. Total height MUST NOT exceed safeRows
|
|
14
|
+
* 4. No floating point math - only integers
|
|
15
|
+
* 5. Right column width calculated as REMAINDER (prevents rounding errors)
|
|
16
|
+
* 6. All positions validated before return
|
|
17
|
+
* 7. All text must use fitText() to prevent overflow
|
|
9
18
|
*/
|
|
10
|
-
|
|
11
|
-
//
|
|
12
|
-
export const MIN_WIDTH =
|
|
13
|
-
export const MIN_HEIGHT =
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
//
|
|
17
|
-
const BORDER_WIDTH = 2; // 1 char left + 1 char right per panel
|
|
18
|
-
const GUTTER_WIDTH = PANEL_SPACING; // Space between columns
|
|
19
|
+
import { getTerminalGeometry } from '../../tui/geometry.js';
|
|
20
|
+
// Minimum terminal size (tested on small terminals)
|
|
21
|
+
export const MIN_WIDTH = 80;
|
|
22
|
+
export const MIN_HEIGHT = 24;
|
|
23
|
+
// Layout constants (NEVER change these without testing on multiple terminal sizes)
|
|
24
|
+
const COMMAND_LINE_HEIGHT = 3; // Fixed height for command line
|
|
25
|
+
const GUTTER = 1; // Space between columns (MUST be 1 for blessed compatibility)
|
|
19
26
|
/**
|
|
20
|
-
* Compute Phase 1 layout
|
|
27
|
+
* Compute Phase 1 layout - FINAL BULLETPROOF IMPLEMENTATION
|
|
28
|
+
*
|
|
29
|
+
* This function GUARANTEES:
|
|
30
|
+
* - Never exceeds terminal dimensions
|
|
31
|
+
* - Works on ANY terminal size >= MIN_WIDTH x MIN_HEIGHT
|
|
32
|
+
* - Integer math only (no rounding errors)
|
|
33
|
+
* - Uses geometry.ts as single source of truth
|
|
21
34
|
*/
|
|
22
|
-
export function computePhase1Layout(width, height) {
|
|
23
|
-
//
|
|
24
|
-
|
|
35
|
+
export function computePhase1Layout(width, height, screen) {
|
|
36
|
+
// Get geometry from single source of truth
|
|
37
|
+
const geo = getTerminalGeometry(screen);
|
|
38
|
+
// Use safe dimensions (never exceed these)
|
|
39
|
+
const safeWidth = Math.min(width, geo.safeCols);
|
|
40
|
+
const safeHeight = Math.min(height, geo.safeRows);
|
|
41
|
+
// Enforce minimum terminal size
|
|
42
|
+
if (safeWidth < MIN_WIDTH || safeHeight < MIN_HEIGHT) {
|
|
25
43
|
return {
|
|
26
44
|
ok: false,
|
|
27
|
-
errorMessage: `Terminal too small
|
|
45
|
+
errorMessage: `Terminal too small: ${safeWidth}x${safeHeight} (need ${MIN_WIDTH}x${MIN_HEIGHT})`,
|
|
28
46
|
};
|
|
29
47
|
}
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
//
|
|
41
|
-
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// STEP 1: Calculate available space (subtract command line)
|
|
50
|
+
// ============================================================================
|
|
51
|
+
const contentHeight = safeHeight - COMMAND_LINE_HEIGHT - GUTTER;
|
|
52
|
+
// ============================================================================
|
|
53
|
+
// STEP 2: Calculate column widths (3 columns: left, center, right)
|
|
54
|
+
// CRITICAL: Use integer division and calculate right as REMAINDER
|
|
55
|
+
// ============================================================================
|
|
56
|
+
const totalGutters = GUTTER * 2; // 2 gutters between 3 columns
|
|
57
|
+
const availableWidth = safeWidth - totalGutters;
|
|
58
|
+
// Left column: 25% of available width
|
|
59
|
+
const leftColWidth = Math.floor(availableWidth * 0.25);
|
|
60
|
+
// Center column: 50% of available width
|
|
61
|
+
const centerWidth = Math.floor(availableWidth * 0.50);
|
|
62
|
+
// Right column: REMAINDER (guarantees no overflow)
|
|
63
|
+
// CRITICAL: Subtract 1 extra char to account for blessed's border rendering quirks
|
|
64
|
+
const rightColWidth = availableWidth - leftColWidth - centerWidth - 1;
|
|
65
|
+
// Validate widths
|
|
66
|
+
if (leftColWidth < 10 || centerWidth < 20 || rightColWidth < 10) {
|
|
42
67
|
return {
|
|
43
68
|
ok: false,
|
|
44
|
-
errorMessage: `Terminal too
|
|
69
|
+
errorMessage: `Terminal too narrow: ${width} (need ${MIN_WIDTH})`,
|
|
45
70
|
};
|
|
46
71
|
}
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
72
|
+
// ============================================================================
|
|
73
|
+
// STEP 3: Calculate panel heights
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Left column: 3 panels (divide evenly with 2 gutters)
|
|
76
|
+
const leftAvailableHeight = contentHeight - (GUTTER * 2);
|
|
77
|
+
const leftPanelHeight = Math.floor(leftAvailableHeight / 3);
|
|
78
|
+
// Right column: 2 panels (60/40 split with 1 gutter)
|
|
79
|
+
const rightAvailableHeight = contentHeight - GUTTER;
|
|
80
|
+
const networkHeight = Math.floor(rightAvailableHeight * 0.60);
|
|
81
|
+
const capabilitiesHeight = rightAvailableHeight - networkHeight;
|
|
82
|
+
// Validate heights
|
|
83
|
+
if (leftPanelHeight < 3 || networkHeight < 3 || capabilitiesHeight < 3) {
|
|
56
84
|
return {
|
|
57
85
|
ok: false,
|
|
58
|
-
errorMessage: `Terminal too
|
|
86
|
+
errorMessage: `Terminal too short: ${height} (need ${MIN_HEIGHT})`,
|
|
59
87
|
};
|
|
60
88
|
}
|
|
61
|
-
//
|
|
89
|
+
// ============================================================================
|
|
90
|
+
// STEP 4: Calculate positions (deterministic, no rounding errors)
|
|
91
|
+
// ============================================================================
|
|
62
92
|
const layout = {
|
|
63
|
-
// Left column
|
|
93
|
+
// Left column (x=0)
|
|
64
94
|
posture: {
|
|
65
95
|
top: 0,
|
|
66
96
|
left: 0,
|
|
@@ -68,45 +98,70 @@ export function computePhase1Layout(width, height) {
|
|
|
68
98
|
height: leftPanelHeight,
|
|
69
99
|
},
|
|
70
100
|
resources: {
|
|
71
|
-
top: leftPanelHeight +
|
|
101
|
+
top: leftPanelHeight + GUTTER,
|
|
72
102
|
left: 0,
|
|
73
103
|
width: leftColWidth,
|
|
74
104
|
height: leftPanelHeight,
|
|
75
105
|
},
|
|
76
106
|
assets: {
|
|
77
|
-
top: (leftPanelHeight +
|
|
107
|
+
top: (leftPanelHeight + GUTTER) * 2,
|
|
78
108
|
left: 0,
|
|
79
109
|
width: leftColWidth,
|
|
80
110
|
height: leftPanelHeight,
|
|
81
111
|
},
|
|
82
|
-
// Center
|
|
112
|
+
// Center column (x = leftColWidth + GUTTER)
|
|
83
113
|
operations: {
|
|
84
114
|
top: 0,
|
|
85
|
-
left: leftColWidth +
|
|
115
|
+
left: leftColWidth + GUTTER,
|
|
86
116
|
width: centerWidth,
|
|
87
|
-
height:
|
|
117
|
+
height: contentHeight,
|
|
88
118
|
},
|
|
89
|
-
// Right column
|
|
119
|
+
// Right column (x = leftColWidth + GUTTER + centerWidth + GUTTER)
|
|
90
120
|
network: {
|
|
91
121
|
top: 0,
|
|
92
|
-
left: leftColWidth +
|
|
122
|
+
left: leftColWidth + GUTTER + centerWidth + GUTTER,
|
|
93
123
|
width: rightColWidth,
|
|
94
|
-
height:
|
|
124
|
+
height: networkHeight,
|
|
95
125
|
},
|
|
96
126
|
capabilities: {
|
|
97
|
-
top:
|
|
98
|
-
left: leftColWidth +
|
|
127
|
+
top: networkHeight + GUTTER,
|
|
128
|
+
left: leftColWidth + GUTTER + centerWidth + GUTTER,
|
|
99
129
|
width: rightColWidth,
|
|
100
|
-
height:
|
|
130
|
+
height: capabilitiesHeight,
|
|
101
131
|
},
|
|
102
|
-
//
|
|
132
|
+
// Command line (bottom, full width - but never exceed safe width)
|
|
103
133
|
commandLine: {
|
|
104
|
-
top:
|
|
134
|
+
top: contentHeight + GUTTER,
|
|
105
135
|
left: 0,
|
|
106
|
-
width:
|
|
107
|
-
height:
|
|
136
|
+
width: safeWidth,
|
|
137
|
+
height: COMMAND_LINE_HEIGHT,
|
|
108
138
|
},
|
|
109
139
|
};
|
|
140
|
+
// ============================================================================
|
|
141
|
+
// STEP 5: VALIDATION (catch any bugs before they cause visual issues)
|
|
142
|
+
// ============================================================================
|
|
143
|
+
const errors = [];
|
|
144
|
+
// Validate total width (columns + gutters <= screen width)
|
|
145
|
+
// Note: We intentionally subtract 1 from right column to prevent blessed border overflow
|
|
146
|
+
const totalWidth = leftColWidth + centerWidth + rightColWidth;
|
|
147
|
+
if (totalWidth > availableWidth) {
|
|
148
|
+
errors.push(`Width overflow: ${totalWidth} > ${availableWidth}`);
|
|
149
|
+
}
|
|
150
|
+
// Validate no overlaps
|
|
151
|
+
const rightColLeft = leftColWidth + GUTTER + centerWidth + GUTTER;
|
|
152
|
+
if (rightColLeft + rightColWidth > safeWidth) {
|
|
153
|
+
errors.push(`Right column overflow: ${rightColLeft + rightColWidth} > ${safeWidth}`);
|
|
154
|
+
}
|
|
155
|
+
// Validate command line position
|
|
156
|
+
if (layout.commandLine.top + layout.commandLine.height > safeHeight) {
|
|
157
|
+
errors.push(`Command line overflow: ${layout.commandLine.top + layout.commandLine.height} > ${safeHeight}`);
|
|
158
|
+
}
|
|
159
|
+
if (errors.length > 0) {
|
|
160
|
+
return {
|
|
161
|
+
ok: false,
|
|
162
|
+
errorMessage: `Layout validation failed: ${errors.join(', ')}`,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
110
165
|
return { ok: true, layout };
|
|
111
166
|
}
|
|
112
167
|
//# sourceMappingURL=phase1Layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase1Layout.js","sourceRoot":"","sources":["../../../../../src/ui/v3/ui/layout/phase1Layout.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"phase1Layout.js","sourceRoot":"","sources":["../../../../../src/ui/v3/ui/layout/phase1Layout.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase1Layout.test.d.ts","sourceRoot":"","sources":["../../../../../src/ui/v3/ui/layout/phase1Layout.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layout Tests - Verify bulletproof layout on multiple terminal sizes
|
|
3
|
+
*/
|
|
4
|
+
import { computePhase1Layout } from './phase1Layout.js';
|
|
5
|
+
// Test terminal sizes (common real-world sizes)
|
|
6
|
+
const TEST_SIZES = [
|
|
7
|
+
{ width: 80, height: 24, name: 'Small (80x24)' },
|
|
8
|
+
{ width: 100, height: 30, name: 'Medium (100x30)' },
|
|
9
|
+
{ width: 120, height: 40, name: 'Large (120x40)' },
|
|
10
|
+
{ width: 160, height: 50, name: 'XLarge (160x50)' },
|
|
11
|
+
{ width: 200, height: 60, name: 'XXLarge (200x60)' },
|
|
12
|
+
{ width: 280, height: 70, name: 'Ultrawide (280x70)' },
|
|
13
|
+
];
|
|
14
|
+
function validateLayout(width, height) {
|
|
15
|
+
const result = computePhase1Layout(width, height);
|
|
16
|
+
const errors = [];
|
|
17
|
+
if (!result.ok || !result.layout) {
|
|
18
|
+
errors.push(`Layout failed: ${result.errorMessage}`);
|
|
19
|
+
return { ok: false, errors };
|
|
20
|
+
}
|
|
21
|
+
const layout = result.layout;
|
|
22
|
+
// Test 1: No panel exceeds screen bounds
|
|
23
|
+
const panels = [
|
|
24
|
+
{ name: 'posture', rect: layout.posture },
|
|
25
|
+
{ name: 'resources', rect: layout.resources },
|
|
26
|
+
{ name: 'assets', rect: layout.assets },
|
|
27
|
+
{ name: 'operations', rect: layout.operations },
|
|
28
|
+
{ name: 'network', rect: layout.network },
|
|
29
|
+
{ name: 'capabilities', rect: layout.capabilities },
|
|
30
|
+
{ name: 'commandLine', rect: layout.commandLine },
|
|
31
|
+
];
|
|
32
|
+
for (const panel of panels) {
|
|
33
|
+
const right = panel.rect.left + panel.rect.width;
|
|
34
|
+
const bottom = panel.rect.top + panel.rect.height;
|
|
35
|
+
if (right > width) {
|
|
36
|
+
errors.push(`${panel.name} exceeds width: ${right} > ${width}`);
|
|
37
|
+
}
|
|
38
|
+
if (bottom > height) {
|
|
39
|
+
errors.push(`${panel.name} exceeds height: ${bottom} > ${height}`);
|
|
40
|
+
}
|
|
41
|
+
if (panel.rect.width <= 0) {
|
|
42
|
+
errors.push(`${panel.name} has invalid width: ${panel.rect.width}`);
|
|
43
|
+
}
|
|
44
|
+
if (panel.rect.height <= 0) {
|
|
45
|
+
errors.push(`${panel.name} has invalid height: ${panel.rect.height}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// Test 2: No overlaps
|
|
49
|
+
for (let i = 0; i < panels.length; i++) {
|
|
50
|
+
for (let j = i + 1; j < panels.length; j++) {
|
|
51
|
+
const a = panels[i].rect;
|
|
52
|
+
const b = panels[j].rect;
|
|
53
|
+
// Check if rectangles overlap
|
|
54
|
+
const overlapX = a.left < b.left + b.width && a.left + a.width > b.left;
|
|
55
|
+
const overlapY = a.top < b.top + b.height && a.top + a.height > b.top;
|
|
56
|
+
if (overlapX && overlapY) {
|
|
57
|
+
errors.push(`${panels[i].name} overlaps ${panels[j].name}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Test 3: Command line spans full width
|
|
62
|
+
if (layout.commandLine.width !== width) {
|
|
63
|
+
errors.push(`Command line width mismatch: ${layout.commandLine.width} !== ${width}`);
|
|
64
|
+
}
|
|
65
|
+
return { ok: errors.length === 0, errors };
|
|
66
|
+
}
|
|
67
|
+
// Run tests
|
|
68
|
+
console.log('=== LAYOUT VALIDATION TESTS ===\n');
|
|
69
|
+
let allPassed = true;
|
|
70
|
+
for (const size of TEST_SIZES) {
|
|
71
|
+
const result = validateLayout(size.width, size.height);
|
|
72
|
+
if (result.ok) {
|
|
73
|
+
console.log(`✓ ${size.name}: PASS`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
console.log(`✗ ${size.name}: FAIL`);
|
|
77
|
+
for (const error of result.errors) {
|
|
78
|
+
console.log(` - ${error}`);
|
|
79
|
+
}
|
|
80
|
+
allPassed = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
console.log('\n=== RESULTS ===');
|
|
84
|
+
if (allPassed) {
|
|
85
|
+
console.log('✓ All tests passed! Layout is bulletproof.');
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
console.log('✗ Some tests failed. Layout needs fixes.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=phase1Layout.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"phase1Layout.test.js","sourceRoot":"","sources":["../../../../../src/ui/v3/ui/layout/phase1Layout.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,4 +11,8 @@
|
|
|
11
11
|
* Step 1: Single instance guard
|
|
12
12
|
*/
|
|
13
13
|
export declare function startPhase1Runtime(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Export layout dump for debug commands
|
|
16
|
+
*/
|
|
17
|
+
export declare function dumpLayout(): string[];
|
|
14
18
|
//# sourceMappingURL=phase1RuntimeClean.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"phase1RuntimeClean.d.ts","sourceRoot":"","sources":["../../../../src/ui/v3/ui/phase1RuntimeClean.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"phase1RuntimeClean.d.ts","sourceRoot":"","sources":["../../../../src/ui/v3/ui/phase1RuntimeClean.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAwMH;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAwKxD;AAsdD;;GAEG;AACH,wBAAgB,UAAU,IAAI,MAAM,EAAE,CAsCrC"}
|
|
@@ -23,8 +23,14 @@ import { renderNetworkPanel } from './panels/NetworkPanel.js';
|
|
|
23
23
|
import { renderCapabilitiesPanel } from './panels/CapabilitiesPanel.js';
|
|
24
24
|
import { isAvailable } from '../state/value.js';
|
|
25
25
|
import { parse, execute, UiAction } from '../commands/commandEngine.js';
|
|
26
|
+
import { BOOT_ID, isDebugMode, trackWidget, trackListenerBinding, dumpUIInventory, installStdoutMonkeypatch, updateResizeStats } from './debugUtils.js';
|
|
27
|
+
import { getTerminalGeometry, hasGeometryChanged, isGeometryStable, formatGeometry } from '../tui/geometry.js';
|
|
26
28
|
const blessedLib = blessed;
|
|
27
29
|
const PROMPT = '4runr> ';
|
|
30
|
+
// Phase B1: Global mount guard - ONE SCREEN. ONE MOUNT. FOREVER.
|
|
31
|
+
if (globalThis.__TUI_MOUNTED__) {
|
|
32
|
+
throw new Error('TUI already mounted globally - cannot mount twice');
|
|
33
|
+
}
|
|
28
34
|
// Step 1: Mount guard - ensures UI mounted exactly once
|
|
29
35
|
let mounted = false;
|
|
30
36
|
// Step 2: Single widget registry (source of truth)
|
|
@@ -41,11 +47,11 @@ let historyIndex = -1;
|
|
|
41
47
|
let commandInputValue = PROMPT;
|
|
42
48
|
let cursorPosition = PROMPT.length;
|
|
43
49
|
let scrollOffset = 0;
|
|
44
|
-
// Resize debounce (
|
|
50
|
+
// Phase C1: Resize debounce (prevent resize storm)
|
|
45
51
|
let resizeDebounceTimer = null;
|
|
46
|
-
const RESIZE_DEBOUNCE_MS =
|
|
47
|
-
let
|
|
48
|
-
let
|
|
52
|
+
const RESIZE_DEBOUNCE_MS = 100; // 100ms debounce (Phase C1 requirement)
|
|
53
|
+
let resizeEventCount = 0;
|
|
54
|
+
let resizeApplyCount = 0;
|
|
49
55
|
// Resize handler guard (Step 4A: bind once)
|
|
50
56
|
let resizeHandlerBound = false;
|
|
51
57
|
/**
|
|
@@ -55,7 +61,7 @@ let resizeHandlerBound = false;
|
|
|
55
61
|
function log(tag, msg, level = 'INFO') {
|
|
56
62
|
eventBus.emit({
|
|
57
63
|
tag,
|
|
58
|
-
msg,
|
|
64
|
+
msg: isDebugMode() ? `[boot=${BOOT_ID}] ${msg}` : msg,
|
|
59
65
|
level
|
|
60
66
|
});
|
|
61
67
|
}
|
|
@@ -120,8 +126,8 @@ function layoutUI(screen, widgets) {
|
|
|
120
126
|
// Step 5: Read terminal size fresh (deterministic)
|
|
121
127
|
const width = screen.width;
|
|
122
128
|
const height = screen.height;
|
|
123
|
-
// Compute layout from scratch
|
|
124
|
-
const layoutResult = computePhase1Layout(width, height);
|
|
129
|
+
// Compute layout from scratch (pass screen for geometry)
|
|
130
|
+
const layoutResult = computePhase1Layout(width, height, screen);
|
|
125
131
|
if (!layoutResult.ok || !layoutResult.layout) {
|
|
126
132
|
log('SYS', `Terminal too small: ${width}x${height} (need ${MIN_WIDTH}x${MIN_HEIGHT})`, 'WARN');
|
|
127
133
|
return;
|
|
@@ -162,6 +168,8 @@ export async function startPhase1Runtime() {
|
|
|
162
168
|
log('ERR', 'UI already mounted - cannot mount twice', 'ERROR');
|
|
163
169
|
return;
|
|
164
170
|
}
|
|
171
|
+
// Log boot ID and terminal size
|
|
172
|
+
log('SYS', `TUI starting (bootId=${BOOT_ID})`);
|
|
165
173
|
// Issue 2 - Step 2: Patch console in TUI mode (failsafe)
|
|
166
174
|
// This prevents any library or accidental console.log from breaking the TUI
|
|
167
175
|
const originalLog = console.log;
|
|
@@ -198,6 +206,8 @@ export async function startPhase1Runtime() {
|
|
|
198
206
|
if (!screen) {
|
|
199
207
|
throw new Error('Failed to create screen');
|
|
200
208
|
}
|
|
209
|
+
// Log actual screen dimensions
|
|
210
|
+
log('SYS', `Screen size: ${screen.width}x${screen.height}`);
|
|
201
211
|
// Hide cursor
|
|
202
212
|
if (screen.program?.hideCursor) {
|
|
203
213
|
screen.program.hideCursor();
|
|
@@ -205,17 +215,27 @@ export async function startPhase1Runtime() {
|
|
|
205
215
|
// Check terminal size
|
|
206
216
|
const width = screen.width;
|
|
207
217
|
const height = screen.height;
|
|
208
|
-
const layoutResult = computePhase1Layout(width, height);
|
|
218
|
+
const layoutResult = computePhase1Layout(width, height, screen);
|
|
209
219
|
if (!layoutResult.ok || !layoutResult.layout) {
|
|
210
220
|
const errorMsg = layoutResult.errorMessage || 'Terminal too small';
|
|
211
221
|
screen.destroy();
|
|
212
222
|
throw new Error(errorMsg);
|
|
213
223
|
}
|
|
214
224
|
const layout = layoutResult.layout;
|
|
225
|
+
// Debug: Log layout dimensions
|
|
226
|
+
if (isDebugMode()) {
|
|
227
|
+
log('DBG', `Layout computed for ${width}x${height}`);
|
|
228
|
+
log('DBG', `Left: ${layout.posture.width}w, Center: ${layout.operations.width}w, Right: ${layout.network.width}w`);
|
|
229
|
+
log('DBG', `Right col position: left=${layout.network.left}, width=${layout.network.width}`);
|
|
230
|
+
log('DBG', `Right edge: ${layout.network.left + layout.network.width} (screen width: ${width})`);
|
|
231
|
+
}
|
|
215
232
|
// A) mountUI() - Create widgets ONCE
|
|
216
233
|
widgets = mountUI(screen, layout);
|
|
217
234
|
// Store unbind functions
|
|
218
235
|
const unbindFns = [];
|
|
236
|
+
// Step 3A: Install stdout monkeypatch (debug mode)
|
|
237
|
+
const uninstallStdoutMonkeypatch = installStdoutMonkeypatch();
|
|
238
|
+
unbindFns.push(uninstallStdoutMonkeypatch);
|
|
219
239
|
// Restore console on cleanup
|
|
220
240
|
unbindFns.push(() => {
|
|
221
241
|
console.log = originalLog;
|
|
@@ -267,9 +287,9 @@ export async function startPhase1Runtime() {
|
|
|
267
287
|
// Initial content update
|
|
268
288
|
updateAllPanels(widgets);
|
|
269
289
|
// Initial render
|
|
270
|
-
lastResizeWidth = width;
|
|
271
|
-
lastResizeHeight = height;
|
|
272
290
|
screen.render();
|
|
291
|
+
// Phase B1: Mark as globally mounted
|
|
292
|
+
globalThis.__TUI_MOUNTED__ = true;
|
|
273
293
|
// Disable boot phase (enable strict stdout blocking)
|
|
274
294
|
disableBootPhase();
|
|
275
295
|
// Start background state updates
|
|
@@ -292,48 +312,59 @@ function setupResizeHandling(screen, widgets, unbindFns) {
|
|
|
292
312
|
return;
|
|
293
313
|
}
|
|
294
314
|
resizeHandlerBound = true;
|
|
315
|
+
updateResizeStats({ handlerBound: true });
|
|
295
316
|
const resizeHandler = () => {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
//
|
|
299
|
-
if (
|
|
317
|
+
// Phase C1: Count resize events
|
|
318
|
+
resizeEventCount++;
|
|
319
|
+
// Phase C1: Check geometry stability (reject impossible sizes)
|
|
320
|
+
if (!isGeometryStable(screen)) {
|
|
321
|
+
if (isDebugMode()) {
|
|
322
|
+
const geo = getTerminalGeometry(screen);
|
|
323
|
+
log('DBG', `Unstable geometry detected: ${formatGeometry(geo)} - skipping`);
|
|
324
|
+
}
|
|
300
325
|
return;
|
|
301
326
|
}
|
|
302
|
-
//
|
|
327
|
+
// Phase C1: Check if geometry actually changed
|
|
328
|
+
if (!hasGeometryChanged(screen)) {
|
|
329
|
+
return; // No-op resize, ignore
|
|
330
|
+
}
|
|
331
|
+
// Phase C1: Debounce (100-150ms to handle resize storms)
|
|
303
332
|
if (resizeDebounceTimer) {
|
|
304
333
|
clearTimeout(resizeDebounceTimer);
|
|
305
334
|
}
|
|
306
335
|
resizeDebounceTimer = setTimeout(() => {
|
|
307
336
|
resizeDebounceTimer = null;
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
if (
|
|
313
|
-
|
|
337
|
+
resizeApplyCount++;
|
|
338
|
+
updateResizeStats({ eventCount: resizeEventCount, applyCount: resizeApplyCount });
|
|
339
|
+
// Get current geometry
|
|
340
|
+
const geo = getTerminalGeometry(screen);
|
|
341
|
+
if (isDebugMode()) {
|
|
342
|
+
log('DBG', `Resize apply #${resizeApplyCount} (${resizeEventCount} events): ${formatGeometry(geo)}`);
|
|
314
343
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
// Issue 1 - Step 4: Temporary duplication detector (debug)
|
|
318
|
-
const widgetCountBefore = screen.children?.length || 0;
|
|
319
|
-
const commandLineCountBefore = screen.children?.filter((w) => w === widgets.commandLine || w.name === 'commandLine').length || 0;
|
|
320
|
-
// Issue 1 - Step 3: Call layoutUI() ONLY - NO mountUI(), NO widget creation
|
|
344
|
+
// Phase B2: Resize = REFLOW ONLY (no recreate, no re-append, no rebind)
|
|
345
|
+
// Only update widget positions/sizes
|
|
321
346
|
layoutUI(screen, widgets);
|
|
322
|
-
// Render
|
|
347
|
+
// Render once
|
|
323
348
|
screen.render();
|
|
324
|
-
//
|
|
325
|
-
const
|
|
326
|
-
const
|
|
327
|
-
if (
|
|
328
|
-
log('ERR', `
|
|
349
|
+
// Phase B1: Validate no duplication occurred
|
|
350
|
+
const widgetCount = screen.children?.length || 0;
|
|
351
|
+
const commandLineCount = screen.children?.filter((w) => w === widgets.commandLine).length || 0;
|
|
352
|
+
if (commandLineCount > 1) {
|
|
353
|
+
log('ERR', `DUPLICATION DETECTED: ${commandLineCount} command lines!`, 'ERROR');
|
|
354
|
+
}
|
|
355
|
+
// Log resize completion (quiet in non-debug mode, verbose in debug mode)
|
|
356
|
+
if (isDebugMode()) {
|
|
357
|
+
log('DBG', `Resize applied: ${geo.cols}x${geo.rows} (events: ${resizeEventCount}, applies: ${resizeApplyCount})`);
|
|
358
|
+
dumpUIInventory(screen);
|
|
329
359
|
}
|
|
330
|
-
|
|
331
|
-
log
|
|
360
|
+
else {
|
|
361
|
+
// Only log resize if it's a significant change (suppress spam during resize storms)
|
|
362
|
+
// This is already debounced, so occasional logs are acceptable
|
|
332
363
|
}
|
|
333
|
-
// Issue 2 - Step 4: Single log per resize (debounced)
|
|
334
|
-
log('SYS', `Resized to ${currentWidth}x${currentHeight}`);
|
|
335
364
|
}, RESIZE_DEBOUNCE_MS);
|
|
336
365
|
};
|
|
366
|
+
// Debug: Track listener binding
|
|
367
|
+
trackListenerBinding('resize');
|
|
337
368
|
screen.on('resize', resizeHandler);
|
|
338
369
|
unbindFns.push(() => {
|
|
339
370
|
// Use off() method (EventEmitter API) instead of removeListener
|
|
@@ -376,6 +407,8 @@ function createPanel(screen, rect, title) {
|
|
|
376
407
|
focusable: false,
|
|
377
408
|
});
|
|
378
409
|
panel.setLabel(` {cyan-fg}${title}{/}`);
|
|
410
|
+
// Debug: Track widget creation
|
|
411
|
+
trackWidget(panel, title);
|
|
379
412
|
return panel;
|
|
380
413
|
}
|
|
381
414
|
/**
|
|
@@ -402,6 +435,8 @@ function createStatusStrip(screen, layout) {
|
|
|
402
435
|
focusable: false,
|
|
403
436
|
});
|
|
404
437
|
strip.setLabel(' {grey-fg}STATUS{/}');
|
|
438
|
+
// Debug: Track widget creation
|
|
439
|
+
trackWidget(strip, 'STATUS_STRIP');
|
|
405
440
|
return strip;
|
|
406
441
|
}
|
|
407
442
|
/**
|
|
@@ -427,6 +462,8 @@ function createCommandLine(screen, layout) {
|
|
|
427
462
|
keys: false,
|
|
428
463
|
});
|
|
429
464
|
cmdLine.setLabel(' {cyan-fg}COMMAND LINE{/}');
|
|
465
|
+
// Debug: Track widget creation
|
|
466
|
+
trackWidget(cmdLine, 'COMMAND_LINE');
|
|
430
467
|
return cmdLine;
|
|
431
468
|
}
|
|
432
469
|
/**
|
|
@@ -655,6 +692,43 @@ function startBackgroundUpdates(widgets, screen) {
|
|
|
655
692
|
const runtime = getUIRuntime();
|
|
656
693
|
runtime.unbindFns.push(() => clearInterval(interval));
|
|
657
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Export layout dump for debug commands
|
|
697
|
+
*/
|
|
698
|
+
export function dumpLayout() {
|
|
699
|
+
const runtime = getUIRuntime();
|
|
700
|
+
const widgets = runtime.widgets;
|
|
701
|
+
const geo = getTerminalGeometry(runtime.screen);
|
|
702
|
+
const lines = [];
|
|
703
|
+
lines.push(`=== LAYOUT DUMP ===`);
|
|
704
|
+
lines.push(`Terminal: ${geo.cols}x${geo.rows} (safe: ${geo.safeCols}x${geo.safeRows})`);
|
|
705
|
+
lines.push(``);
|
|
706
|
+
const panels = [
|
|
707
|
+
{ name: 'POSTURE', widget: widgets.posture },
|
|
708
|
+
{ name: 'RESOURCES', widget: widgets.resources },
|
|
709
|
+
{ name: 'ASSETS', widget: widgets.assets },
|
|
710
|
+
{ name: 'OPERATIONS', widget: widgets.operations },
|
|
711
|
+
{ name: 'NETWORK', widget: widgets.network },
|
|
712
|
+
{ name: 'CAPABILITIES', widget: widgets.capabilities },
|
|
713
|
+
{ name: 'STATUS_STRIP', widget: widgets.statusStrip },
|
|
714
|
+
{ name: 'COMMAND_LINE', widget: widgets.commandLine },
|
|
715
|
+
];
|
|
716
|
+
for (const { name, widget } of panels) {
|
|
717
|
+
const top = widget.top;
|
|
718
|
+
const left = widget.left;
|
|
719
|
+
const width = widget.width;
|
|
720
|
+
const height = widget.height;
|
|
721
|
+
const right = left + width;
|
|
722
|
+
const bottom = top + height;
|
|
723
|
+
lines.push(`${name}:`);
|
|
724
|
+
lines.push(` pos: (${left}, ${top}) size: ${width}x${height}`);
|
|
725
|
+
lines.push(` bounds: left=${left} right=${right} top=${top} bottom=${bottom}`);
|
|
726
|
+
lines.push(` width check: ${width} <= ${geo.safeCols} ${width <= geo.safeCols ? '✓' : '✗'}`);
|
|
727
|
+
lines.push(``);
|
|
728
|
+
}
|
|
729
|
+
lines.push(`=== END LAYOUT DUMP ===`);
|
|
730
|
+
return lines;
|
|
731
|
+
}
|
|
658
732
|
/**
|
|
659
733
|
* Step 4: Clean exit
|
|
660
734
|
*/
|