4runr-os 2.9.50 → 2.9.52
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/app.rs
CHANGED
|
@@ -344,8 +344,6 @@ pub struct SetupPortalState {
|
|
|
344
344
|
pub detection_result: Option<DetectionResult>,
|
|
345
345
|
pub error: Option<String>,
|
|
346
346
|
pub list_state: ratatui::widgets::ListState,
|
|
347
|
-
/// Last option we drew; clear terminal only when this differs from selected_option (reduces flicker).
|
|
348
|
-
pub last_rendered_option: Option<GatewayOption>,
|
|
349
347
|
}
|
|
350
348
|
|
|
351
349
|
#[derive(Debug, Clone)]
|
|
@@ -377,7 +375,6 @@ impl Default for SetupPortalState {
|
|
|
377
375
|
detection_result: None,
|
|
378
376
|
error: None,
|
|
379
377
|
list_state,
|
|
380
|
-
last_rendered_option: None,
|
|
381
378
|
}
|
|
382
379
|
}
|
|
383
380
|
}
|
|
@@ -385,7 +382,6 @@ impl Default for SetupPortalState {
|
|
|
385
382
|
impl SetupPortalState {
|
|
386
383
|
pub fn cycle_option(&mut self, direction: i8) {
|
|
387
384
|
use GatewayOption::*;
|
|
388
|
-
let prev = self.selected_option.clone();
|
|
389
385
|
self.selected_option = match (&self.selected_option, direction) {
|
|
390
386
|
// Forward cycling (Down key, Scroll Down)
|
|
391
387
|
(LocalBundle, 1) => CloudServer,
|
|
@@ -405,9 +401,6 @@ impl SetupPortalState {
|
|
|
405
401
|
CustomUrl => 2,
|
|
406
402
|
};
|
|
407
403
|
self.list_state.select(Some(new_index));
|
|
408
|
-
|
|
409
|
-
eprintln!("[SETUP-PORTAL] cycle_option direction={} prev={:?} -> new={:?} list_index={}",
|
|
410
|
-
direction, prev, self.selected_option, new_index);
|
|
411
404
|
}
|
|
412
405
|
|
|
413
406
|
pub fn reset(&mut self) {
|
|
@@ -1819,7 +1812,6 @@ impl App {
|
|
|
1819
1812
|
// ============================================================
|
|
1820
1813
|
|
|
1821
1814
|
fn handle_setup_portal_input(&mut self, key: KeyEvent, _ws_client: Option<&WebSocketClient>) -> anyhow::Result<bool> {
|
|
1822
|
-
eprintln!("[SETUP-PORTAL] INPUT key={:?} (selected_option={:?})", key.code, self.state.setup_portal.selected_option);
|
|
1823
1815
|
// Guard: Don't process if already detecting
|
|
1824
1816
|
if self.state.setup_portal.detecting {
|
|
1825
1817
|
return Ok(false);
|
|
@@ -1844,13 +1836,11 @@ impl App {
|
|
|
1844
1836
|
|
|
1845
1837
|
// Up/Down - Navigate options (immediate render for responsiveness)
|
|
1846
1838
|
KeyCode::Up => {
|
|
1847
|
-
eprintln!("[SETUP-PORTAL] KEY Up -> cycle_option(-1)");
|
|
1848
1839
|
self.state.setup_portal.cycle_option(-1);
|
|
1849
1840
|
self.request_immediate_render("setup_nav_up");
|
|
1850
1841
|
}
|
|
1851
1842
|
|
|
1852
1843
|
KeyCode::Down => {
|
|
1853
|
-
eprintln!("[SETUP-PORTAL] KEY Down -> cycle_option(1)");
|
|
1854
1844
|
self.state.setup_portal.cycle_option(1);
|
|
1855
1845
|
self.request_immediate_render("setup_nav_down");
|
|
1856
1846
|
}
|
package/mk3-tui/src/main.rs
CHANGED
|
@@ -591,23 +591,13 @@ fn main() -> Result<()> {
|
|
|
591
591
|
let current_screen = app.state.navigation.current_screen().clone();
|
|
592
592
|
let is_portal_active = matches!(current_screen, crate::screens::Screen::ConnectionPortal | crate::screens::Screen::SetupPortal);
|
|
593
593
|
|
|
594
|
-
// Clear on screen switch
|
|
595
|
-
// (avoids flicker from clearing every frame while still fixing list/details mismatch)
|
|
594
|
+
// Clear only on screen switch (no clear on Setup Portal nav = no flicker)
|
|
596
595
|
let is_switching = match (&previous_screen, ¤t_screen) {
|
|
597
596
|
(Some(prev), current) => prev != current,
|
|
598
597
|
(None, _) => true,
|
|
599
598
|
};
|
|
600
599
|
|
|
601
|
-
|
|
602
|
-
crate::screens::Screen::SetupPortal => {
|
|
603
|
-
let last = app.state.setup_portal.last_rendered_option.as_ref();
|
|
604
|
-
let current = &app.state.setup_portal.selected_option;
|
|
605
|
-
last != Some(current)
|
|
606
|
-
}
|
|
607
|
-
_ => false,
|
|
608
|
-
};
|
|
609
|
-
|
|
610
|
-
if is_switching || setup_selection_changed {
|
|
600
|
+
if is_switching {
|
|
611
601
|
terminal.clear()?;
|
|
612
602
|
}
|
|
613
603
|
|
|
@@ -640,12 +630,10 @@ fn main() -> Result<()> {
|
|
|
640
630
|
if !app.state.setup_portal.detecting {
|
|
641
631
|
match mouse.kind {
|
|
642
632
|
MouseEventKind::ScrollUp => {
|
|
643
|
-
eprintln!("[SETUP-PORTAL] MOUSE ScrollUp -> cycle_option(-1)");
|
|
644
633
|
app.state.setup_portal.cycle_option(-1);
|
|
645
634
|
app.request_immediate_render("setup_portal_scroll_up");
|
|
646
635
|
}
|
|
647
636
|
MouseEventKind::ScrollDown => {
|
|
648
|
-
eprintln!("[SETUP-PORTAL] MOUSE ScrollDown -> cycle_option(1)");
|
|
649
637
|
app.state.setup_portal.cycle_option(1);
|
|
650
638
|
app.request_immediate_render("setup_portal_scroll_down");
|
|
651
639
|
}
|
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.52";
|
|
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,40 +18,22 @@ 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: &mut AppState) {
|
|
21
|
-
// Per-frame render ID
|
|
21
|
+
// Per-frame render ID (used for passing to content/details; debug logs removed to avoid terminal spam)
|
|
22
22
|
static mut RENDER_ID: u64 = 0;
|
|
23
23
|
let render_id = unsafe {
|
|
24
24
|
RENDER_ID += 1;
|
|
25
25
|
RENDER_ID
|
|
26
26
|
};
|
|
27
|
-
eprintln!("[SETUP-PORTAL] R#{} START selected_option={:?}", render_id, state.setup_portal.selected_option);
|
|
28
|
-
|
|
29
|
-
static mut RENDER_COUNT: u64 = 0;
|
|
30
|
-
unsafe {
|
|
31
|
-
RENDER_COUNT += 1;
|
|
32
|
-
if RENDER_COUNT % 10 == 1 {
|
|
33
|
-
eprintln!("[SETUP-PORTAL] Render #{}", RENDER_COUNT);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
27
|
|
|
37
28
|
let area = f.size();
|
|
38
29
|
|
|
39
|
-
unsafe {
|
|
40
|
-
if RENDER_COUNT % 10 == 1 {
|
|
41
|
-
eprintln!("[SETUP-PORTAL] Terminal size: {}x{}", area.width, area.height);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
30
|
// CRITICAL: Validate terminal size before rendering
|
|
46
31
|
// This prevents character corruption when terminal size is wrong on initial render
|
|
47
32
|
if area.width == 0 || area.height == 0 {
|
|
48
|
-
eprintln!("[SETUP-PORTAL] ⚠️ Terminal size invalid: {}x{} - skipping render", area.width, area.height);
|
|
49
33
|
return;
|
|
50
34
|
}
|
|
51
|
-
|
|
52
|
-
// Ensure minimum size for portal (85x25)
|
|
35
|
+
|
|
53
36
|
if area.width < 85 || area.height < 25 {
|
|
54
|
-
eprintln!("[SETUP-PORTAL] ⚠️ Terminal too small: {}x{} (required: 85x25)", area.width, area.height);
|
|
55
37
|
render_too_small(f, area);
|
|
56
38
|
return;
|
|
57
39
|
}
|
|
@@ -76,15 +58,7 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
|
76
58
|
width: portal_width,
|
|
77
59
|
height: portal_height,
|
|
78
60
|
};
|
|
79
|
-
|
|
80
|
-
// DEBUG: Log portal area calculations
|
|
81
|
-
unsafe {
|
|
82
|
-
if RENDER_COUNT % 10 == 1 {
|
|
83
|
-
eprintln!("[SETUP-PORTAL] Portal area: x={}, y={}, w={}, h={}",
|
|
84
|
-
portal_area.x, portal_area.y, portal_area.width, portal_area.height);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
61
|
+
|
|
88
62
|
// Split portal into sections
|
|
89
63
|
use ratatui::layout::{Constraint, Direction, Layout};
|
|
90
64
|
|
|
@@ -111,31 +85,7 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
|
111
85
|
.direction(Direction::Vertical)
|
|
112
86
|
.constraints(constraints)
|
|
113
87
|
.split(portal_area);
|
|
114
|
-
|
|
115
|
-
// DEBUG: Log layout chunks and selected option
|
|
116
|
-
unsafe {
|
|
117
|
-
if RENDER_COUNT % 10 == 1 {
|
|
118
|
-
eprintln!("[SETUP-PORTAL] Selected option: {:?}", state.setup_portal.selected_option);
|
|
119
|
-
eprintln!("[SETUP-PORTAL] Has error: {}", has_error);
|
|
120
|
-
eprintln!("[SETUP-PORTAL] Chunks count: {}", chunks.len());
|
|
121
|
-
for (i, chunk) in chunks.iter().enumerate() {
|
|
122
|
-
eprintln!("[SETUP-PORTAL] chunks[{}]: x={}, y={}, w={}, h={}",
|
|
123
|
-
i, chunk.x, chunk.y, chunk.width, chunk.height);
|
|
124
|
-
}
|
|
125
|
-
// Check for overlaps
|
|
126
|
-
for i in 0..chunks.len() {
|
|
127
|
-
for j in (i+1)..chunks.len() {
|
|
128
|
-
let a = &chunks[i];
|
|
129
|
-
let b = &chunks[j];
|
|
130
|
-
if a.x < b.x + b.width && a.x + a.width > b.x &&
|
|
131
|
-
a.y < b.y + b.height && a.y + a.height > b.y {
|
|
132
|
-
eprintln!("[SETUP-PORTAL] ⚠️ OVERLAP: chunks[{}] overlaps chunks[{}]!", i, j);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
88
|
+
|
|
139
89
|
render_header(f, chunks[0], state);
|
|
140
90
|
render_description(f, chunks[1]);
|
|
141
91
|
render_content(f, chunks[2], state, render_id);
|
|
@@ -146,9 +96,6 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
|
146
96
|
} else {
|
|
147
97
|
render_actions(f, chunks[3], state);
|
|
148
98
|
}
|
|
149
|
-
|
|
150
|
-
// So main loop only clears terminal when selection actually changed (reduces flicker).
|
|
151
|
-
state.setup_portal.last_rendered_option = Some(state.setup_portal.selected_option.clone());
|
|
152
99
|
}
|
|
153
100
|
|
|
154
101
|
fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
|
|
@@ -191,14 +138,6 @@ fn render_description(f: &mut Frame, area: Rect) {
|
|
|
191
138
|
}
|
|
192
139
|
|
|
193
140
|
fn render_content(f: &mut Frame, area: Rect, state: &mut AppState, render_id: u64) {
|
|
194
|
-
static mut LOG_COUNT: u64 = 0;
|
|
195
|
-
unsafe {
|
|
196
|
-
LOG_COUNT += 1;
|
|
197
|
-
if LOG_COUNT % 10 == 1 {
|
|
198
|
-
eprintln!("[SETUP-PORTAL] R#{} render_content area: {}x{}", render_id, area.width, area.height);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
141
|
let chunks = Layout::default()
|
|
203
142
|
.direction(Direction::Horizontal)
|
|
204
143
|
.constraints([
|
|
@@ -211,10 +150,7 @@ fn render_content(f: &mut Frame, area: Rect, state: &mut AppState, render_id: u6
|
|
|
211
150
|
render_option_details(f, chunks[1], state, render_id);
|
|
212
151
|
}
|
|
213
152
|
|
|
214
|
-
fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState,
|
|
215
|
-
let fill = Block::default().style(Style::default().bg(BG_PANEL));
|
|
216
|
-
f.render_widget(fill, area);
|
|
217
|
-
|
|
153
|
+
fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState, _render_id: u64) {
|
|
218
154
|
let options = vec![
|
|
219
155
|
("Local Bundle", GatewayOption::LocalBundle),
|
|
220
156
|
("4Runr Server", GatewayOption::CloudServer),
|
|
@@ -222,12 +158,10 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState, render_i
|
|
|
222
158
|
];
|
|
223
159
|
|
|
224
160
|
let selected_option = &state.setup_portal.selected_option;
|
|
225
|
-
let
|
|
161
|
+
let _selected_index = options.iter()
|
|
226
162
|
.position(|(_, opt)| opt == selected_option)
|
|
227
163
|
.unwrap_or(0);
|
|
228
164
|
|
|
229
|
-
eprintln!("[SETUP-PORTAL] R#{} LIST selected_index={} selected_option={:?}", render_id, selected_index, state.setup_portal.selected_option);
|
|
230
|
-
|
|
231
165
|
let items: Vec<ListItem> = options
|
|
232
166
|
.iter()
|
|
233
167
|
.map(|(name, option)| {
|
|
@@ -248,26 +182,26 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState, render_i
|
|
|
248
182
|
})
|
|
249
183
|
.collect();
|
|
250
184
|
|
|
185
|
+
// CRITICAL: Render block first, then fill inner, then list to ensure full paint
|
|
251
186
|
let block = Block::default()
|
|
252
187
|
.borders(Borders::ALL)
|
|
253
188
|
.title(" Gateway Options ")
|
|
254
189
|
.border_style(Style::default().fg(CYBER_CYAN))
|
|
255
190
|
.style(Style::default().bg(BG_PANEL));
|
|
256
191
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
192
|
+
let inner = block.inner(area);
|
|
193
|
+
f.render_widget(block, area);
|
|
194
|
+
|
|
195
|
+
// Fill inner area
|
|
196
|
+
let fill = Block::default().style(Style::default().bg(BG_PANEL));
|
|
197
|
+
f.render_widget(fill, inner);
|
|
262
198
|
|
|
263
|
-
|
|
264
|
-
|
|
199
|
+
// STATELESS: Render list without ListState so only our item styling (► + color) shows selection
|
|
200
|
+
let list = List::new(items).style(Style::default().bg(BG_PANEL));
|
|
201
|
+
f.render_widget(list, inner);
|
|
202
|
+
}
|
|
265
203
|
|
|
266
|
-
|
|
267
|
-
// Without this, Paragraph may not paint every cell; leftover shows previous option (corruption).
|
|
268
|
-
let fill = Block::default().style(Style::default().bg(BG_PANEL));
|
|
269
|
-
f.render_widget(fill, area);
|
|
270
|
-
|
|
204
|
+
fn render_option_details(f: &mut Frame, area: Rect, state: &AppState, _render_id: u64) {
|
|
271
205
|
let selected_option = &state.setup_portal.selected_option;
|
|
272
206
|
let detection_result = &state.setup_portal.detection_result;
|
|
273
207
|
|
|
@@ -353,31 +287,33 @@ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState, render_id:
|
|
|
353
287
|
),
|
|
354
288
|
};
|
|
355
289
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// Use Block to properly contain and clear the area
|
|
290
|
+
// CRITICAL: Render Block first (clears its area), then Paragraph separately to force full paint
|
|
359
291
|
let block = Block::default()
|
|
360
292
|
.title(title)
|
|
361
293
|
.borders(Borders::ALL)
|
|
362
294
|
.border_style(Style::default().fg(TEXT_DIM))
|
|
363
295
|
.style(Style::default().bg(BG_PANEL));
|
|
364
296
|
|
|
365
|
-
|
|
366
|
-
|
|
297
|
+
let inner = block.inner(area);
|
|
298
|
+
f.render_widget(block, area);
|
|
299
|
+
|
|
300
|
+
// Fill the inner area with background explicitly
|
|
301
|
+
let fill = Block::default().style(Style::default().bg(BG_PANEL));
|
|
302
|
+
f.render_widget(fill, inner);
|
|
303
|
+
|
|
304
|
+
// Now render paragraph (content only, no block)
|
|
367
305
|
let paragraph = Paragraph::new(content)
|
|
368
|
-
.block(block)
|
|
369
306
|
.wrap(Wrap { trim: true })
|
|
370
|
-
.style(Style::default().bg(BG_PANEL));
|
|
307
|
+
.style(Style::default().bg(BG_PANEL));
|
|
371
308
|
|
|
372
309
|
// Validate area before rendering
|
|
373
310
|
let terminal_size = f.size();
|
|
374
|
-
if
|
|
375
|
-
|
|
376
|
-
eprintln!("[SETUP-PORTAL] ⚠️ ERROR: Invalid area, skipping render");
|
|
311
|
+
if inner.width == 0 || inner.height == 0 ||
|
|
312
|
+
inner.x >= terminal_size.width || inner.y >= terminal_size.height {
|
|
377
313
|
return;
|
|
378
314
|
}
|
|
379
315
|
|
|
380
|
-
f.render_widget(paragraph,
|
|
316
|
+
f.render_widget(paragraph, inner);
|
|
381
317
|
}
|
|
382
318
|
|
|
383
319
|
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.52",
|
|
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.52: Setup Portal - block+fill+widget render (no flicker, no corruption). v2.9.51: No clear on nav, removed debug logs. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|