4runr-os 2.9.40 → 2.9.41
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.
|
Binary file
|
package/mk3-tui/src/ui/layout.rs
CHANGED
|
@@ -144,7 +144,7 @@ fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
144
144
|
|
|
145
145
|
// Line 1: Brand + version + mode + uptime - Bug 3 fix: Use "4Runr." with dot (matches brand logo)
|
|
146
146
|
// Use npm package version (2.9.24) - matches package.json
|
|
147
|
-
const PACKAGE_VERSION: &str = "2.9.
|
|
147
|
+
const PACKAGE_VERSION: &str = "2.9.41";
|
|
148
148
|
let brand_line = Line::from(vec![
|
|
149
149
|
Span::styled("4Runr.", Style::default().fg(BRAND_PURPLE).add_modifier(Modifier::BOLD)),
|
|
150
150
|
Span::styled(" AI AGENT OS", Style::default().fg(BRAND_VIOLET)),
|
|
@@ -18,18 +18,34 @@ const ERROR_RED: Color = Color::Rgb(255, 69, 69);
|
|
|
18
18
|
|
|
19
19
|
/// Render the Setup Portal screen - Full-screen standalone portal
|
|
20
20
|
pub fn render(f: &mut Frame, state: &AppState) {
|
|
21
|
+
// DEBUG: Track render calls
|
|
22
|
+
static mut RENDER_COUNT: u64 = 0;
|
|
23
|
+
unsafe {
|
|
24
|
+
RENDER_COUNT += 1;
|
|
25
|
+
if RENDER_COUNT % 10 == 1 { // Log every 10th render to avoid spam
|
|
26
|
+
eprintln!("[SETUP-PORTAL] Render #{}", RENDER_COUNT);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
21
30
|
let area = f.size();
|
|
22
31
|
|
|
32
|
+
// DEBUG: Log terminal size
|
|
33
|
+
unsafe {
|
|
34
|
+
if RENDER_COUNT % 10 == 1 {
|
|
35
|
+
eprintln!("[SETUP-PORTAL] Terminal size: {}x{}", area.width, area.height);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
23
39
|
// CRITICAL: Validate terminal size before rendering
|
|
24
40
|
// This prevents character corruption when terminal size is wrong on initial render
|
|
25
41
|
if area.width == 0 || area.height == 0 {
|
|
26
|
-
|
|
42
|
+
eprintln!("[SETUP-PORTAL] ⚠️ Terminal size invalid: {}x{} - skipping render", area.width, area.height);
|
|
27
43
|
return;
|
|
28
44
|
}
|
|
29
45
|
|
|
30
46
|
// Ensure minimum size for portal (85x25)
|
|
31
47
|
if area.width < 85 || area.height < 25 {
|
|
32
|
-
|
|
48
|
+
eprintln!("[SETUP-PORTAL] ⚠️ Terminal too small: {}x{} (required: 85x25)", area.width, area.height);
|
|
33
49
|
render_too_small(f, area);
|
|
34
50
|
return;
|
|
35
51
|
}
|
|
@@ -55,6 +71,14 @@ pub fn render(f: &mut Frame, state: &AppState) {
|
|
|
55
71
|
height: portal_height,
|
|
56
72
|
};
|
|
57
73
|
|
|
74
|
+
// DEBUG: Log portal area calculations
|
|
75
|
+
unsafe {
|
|
76
|
+
if RENDER_COUNT % 10 == 1 {
|
|
77
|
+
eprintln!("[SETUP-PORTAL] Portal area: x={}, y={}, w={}, h={}",
|
|
78
|
+
portal_area.x, portal_area.y, portal_area.width, portal_area.height);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
58
82
|
// Split portal into sections
|
|
59
83
|
use ratatui::layout::{Constraint, Direction, Layout};
|
|
60
84
|
|
|
@@ -82,6 +106,30 @@ pub fn render(f: &mut Frame, state: &AppState) {
|
|
|
82
106
|
.constraints(constraints)
|
|
83
107
|
.split(portal_area);
|
|
84
108
|
|
|
109
|
+
// DEBUG: Log layout chunks and selected option
|
|
110
|
+
unsafe {
|
|
111
|
+
if RENDER_COUNT % 10 == 1 {
|
|
112
|
+
eprintln!("[SETUP-PORTAL] Selected option: {:?}", state.setup_portal.selected_option);
|
|
113
|
+
eprintln!("[SETUP-PORTAL] Has error: {}", has_error);
|
|
114
|
+
eprintln!("[SETUP-PORTAL] Chunks count: {}", chunks.len());
|
|
115
|
+
for (i, chunk) in chunks.iter().enumerate() {
|
|
116
|
+
eprintln!("[SETUP-PORTAL] chunks[{}]: x={}, y={}, w={}, h={}",
|
|
117
|
+
i, chunk.x, chunk.y, chunk.width, chunk.height);
|
|
118
|
+
}
|
|
119
|
+
// Check for overlaps
|
|
120
|
+
for i in 0..chunks.len() {
|
|
121
|
+
for j in (i+1)..chunks.len() {
|
|
122
|
+
let a = &chunks[i];
|
|
123
|
+
let b = &chunks[j];
|
|
124
|
+
if a.x < b.x + b.width && a.x + a.width > b.x &&
|
|
125
|
+
a.y < b.y + b.height && a.y + a.height > b.y {
|
|
126
|
+
eprintln!("[SETUP-PORTAL] ⚠️ OVERLAP: chunks[{}] overlaps chunks[{}]!", i, j);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
85
133
|
render_header(f, chunks[0], state);
|
|
86
134
|
render_description(f, chunks[1]);
|
|
87
135
|
render_content(f, chunks[2], state);
|
|
@@ -134,6 +182,16 @@ fn render_description(f: &mut Frame, area: Rect) {
|
|
|
134
182
|
}
|
|
135
183
|
|
|
136
184
|
fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
185
|
+
// DEBUG: Log content area
|
|
186
|
+
static mut LOG_COUNT: u64 = 0;
|
|
187
|
+
unsafe {
|
|
188
|
+
LOG_COUNT += 1;
|
|
189
|
+
if LOG_COUNT % 10 == 1 {
|
|
190
|
+
eprintln!("[SETUP-PORTAL] render_content - area: x={}, y={}, w={}, h={}",
|
|
191
|
+
area.x, area.y, area.width, area.height);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
137
195
|
// Split content into two columns: Options (left) and Details (right)
|
|
138
196
|
let chunks = Layout::default()
|
|
139
197
|
.direction(Direction::Horizontal)
|
|
@@ -143,11 +201,31 @@ fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
143
201
|
])
|
|
144
202
|
.split(area);
|
|
145
203
|
|
|
204
|
+
// DEBUG: Log content chunks
|
|
205
|
+
unsafe {
|
|
206
|
+
if LOG_COUNT % 10 == 1 {
|
|
207
|
+
eprintln!("[SETUP-PORTAL] Content chunks[0] (options): x={}, y={}, w={}, h={}",
|
|
208
|
+
chunks[0].x, chunks[0].y, chunks[0].width, chunks[0].height);
|
|
209
|
+
eprintln!("[SETUP-PORTAL] Content chunks[1] (details): x={}, y={}, w={}, h={}",
|
|
210
|
+
chunks[1].x, chunks[1].y, chunks[1].width, chunks[1].height);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
146
214
|
render_options_list(f, chunks[0], state);
|
|
147
215
|
render_option_details(f, chunks[1], state);
|
|
148
216
|
}
|
|
149
217
|
|
|
150
218
|
fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
219
|
+
// DEBUG: Log options list rendering
|
|
220
|
+
static mut LOG_COUNT: u64 = 0;
|
|
221
|
+
unsafe {
|
|
222
|
+
LOG_COUNT += 1;
|
|
223
|
+
if LOG_COUNT % 10 == 1 {
|
|
224
|
+
eprintln!("[SETUP-PORTAL] render_options_list - area: x={}, y={}, w={}, h={}",
|
|
225
|
+
area.x, area.y, area.width, area.height);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
151
229
|
// CRITICAL: Clear the list area first to prevent duplication
|
|
152
230
|
f.render_widget(Clear, area);
|
|
153
231
|
|
|
@@ -162,6 +240,15 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
162
240
|
.position(|(_, opt)| opt == &state.setup_portal.selected_option)
|
|
163
241
|
.unwrap_or(0);
|
|
164
242
|
|
|
243
|
+
// DEBUG: Log selected index
|
|
244
|
+
unsafe {
|
|
245
|
+
if LOG_COUNT % 10 == 1 {
|
|
246
|
+
eprintln!("[SETUP-PORTAL] Selected index: {}, option: {:?}",
|
|
247
|
+
selected_index, state.setup_portal.selected_option);
|
|
248
|
+
eprintln!("[SETUP-PORTAL] Options count: {}", options.len());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
165
252
|
let items: Vec<ListItem> = options
|
|
166
253
|
.iter()
|
|
167
254
|
.map(|(name, option)| {
|
|
@@ -203,6 +290,17 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
203
290
|
}
|
|
204
291
|
|
|
205
292
|
fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
293
|
+
// DEBUG: Log details rendering
|
|
294
|
+
static mut LOG_COUNT: u64 = 0;
|
|
295
|
+
unsafe {
|
|
296
|
+
LOG_COUNT += 1;
|
|
297
|
+
if LOG_COUNT % 10 == 1 {
|
|
298
|
+
eprintln!("[SETUP-PORTAL] render_option_details - area: x={}, y={}, w={}, h={}",
|
|
299
|
+
area.x, area.y, area.width, area.height);
|
|
300
|
+
eprintln!("[SETUP-PORTAL] Selected option: {:?}", state.setup_portal.selected_option);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
206
304
|
// CRITICAL: Clear the details area first to prevent content overlap/corruption
|
|
207
305
|
f.render_widget(Clear, area);
|
|
208
306
|
|
|
@@ -291,6 +389,18 @@ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
291
389
|
),
|
|
292
390
|
};
|
|
293
391
|
|
|
392
|
+
// DEBUG: Log what content is being rendered
|
|
393
|
+
unsafe {
|
|
394
|
+
if LOG_COUNT % 10 == 1 {
|
|
395
|
+
eprintln!("[SETUP-PORTAL] Title: '{}'", title);
|
|
396
|
+
eprintln!("[SETUP-PORTAL] Content lines: {}", content.len());
|
|
397
|
+
for (i, line) in content.iter().take(3).enumerate() {
|
|
398
|
+
let preview = format!("{:?}", line).chars().take(50).collect::<String>();
|
|
399
|
+
eprintln!("[SETUP-PORTAL] content[{}]: {}", i, preview);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
294
404
|
// Use Block to properly contain and clear the area
|
|
295
405
|
let block = Block::default()
|
|
296
406
|
.title(title)
|
|
@@ -300,13 +410,56 @@ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
300
410
|
|
|
301
411
|
// CRITICAL: Render block first to clear and establish boundaries
|
|
302
412
|
let inner = block.inner(area);
|
|
413
|
+
|
|
414
|
+
// CRITICAL: Validate inner area to prevent index out of bounds panic
|
|
415
|
+
// Ratatui panics if area coordinates exceed terminal buffer
|
|
416
|
+
let terminal_size = f.size();
|
|
417
|
+
let safe_inner = Rect {
|
|
418
|
+
x: inner.x.min(terminal_size.width.saturating_sub(1)),
|
|
419
|
+
y: inner.y.min(terminal_size.height.saturating_sub(1)),
|
|
420
|
+
width: inner.width.min(terminal_size.width.saturating_sub(inner.x)),
|
|
421
|
+
height: inner.height.min(terminal_size.height.saturating_sub(inner.y)),
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
// DEBUG: Log block inner area
|
|
425
|
+
unsafe {
|
|
426
|
+
if LOG_COUNT % 10 == 1 {
|
|
427
|
+
eprintln!("[SETUP-PORTAL] Block outer: x={}, y={}, w={}, h={}",
|
|
428
|
+
area.x, area.y, area.width, area.height);
|
|
429
|
+
eprintln!("[SETUP-PORTAL] Block inner (original): x={}, y={}, w={}, h={}",
|
|
430
|
+
inner.x, inner.y, inner.width, inner.height);
|
|
431
|
+
eprintln!("[SETUP-PORTAL] Terminal size: {}x{}", terminal_size.width, terminal_size.height);
|
|
432
|
+
eprintln!("[SETUP-PORTAL] Block inner (safe): x={}, y={}, w={}, h={}",
|
|
433
|
+
safe_inner.x, safe_inner.y, safe_inner.width, safe_inner.height);
|
|
434
|
+
|
|
435
|
+
// Warn if we had to clamp
|
|
436
|
+
if inner.x != safe_inner.x || inner.y != safe_inner.y ||
|
|
437
|
+
inner.width != safe_inner.width || inner.height != safe_inner.height {
|
|
438
|
+
eprintln!("[SETUP-PORTAL] ⚠️ WARNING: Inner area was clamped to fit terminal bounds!");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Validate area before rendering block
|
|
444
|
+
if area.width == 0 || area.height == 0 ||
|
|
445
|
+
area.x >= terminal_size.width || area.y >= terminal_size.height {
|
|
446
|
+
eprintln!("[SETUP-PORTAL] ⚠️ ERROR: Invalid block area, skipping render");
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
|
|
303
450
|
f.render_widget(block, area);
|
|
304
451
|
|
|
305
|
-
//
|
|
452
|
+
// Validate safe_inner before rendering paragraph
|
|
453
|
+
if safe_inner.width == 0 || safe_inner.height == 0 {
|
|
454
|
+
eprintln!("[SETUP-PORTAL] ⚠️ ERROR: Invalid inner area (zero size), skipping paragraph render");
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Then render paragraph content only within the safe inner area
|
|
306
459
|
let paragraph = Paragraph::new(content)
|
|
307
460
|
.wrap(Wrap { trim: true });
|
|
308
461
|
|
|
309
|
-
f.render_widget(paragraph,
|
|
462
|
+
f.render_widget(paragraph, safe_inner);
|
|
310
463
|
}
|
|
311
464
|
|
|
312
465
|
fn render_error_box(f: &mut Frame, area: Rect, state: &AppState) {
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-os",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.41",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.
|
|
5
|
+
"description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.41: Fixed Setup Portal index out of bounds panic - added bounds validation and clamping for safe area rendering. Added comprehensive debug logging for Setup Portal rendering diagnostics. v2.9.40: Fixed Setup Portal rendering. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|