4runr-os 2.9.44 → 2.9.46
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
|
@@ -343,6 +343,7 @@ pub struct SetupPortalState {
|
|
|
343
343
|
pub detecting: bool,
|
|
344
344
|
pub detection_result: Option<DetectionResult>,
|
|
345
345
|
pub error: Option<String>,
|
|
346
|
+
pub list_state: ratatui::widgets::ListState, // CRITICAL: Must persist across frames
|
|
346
347
|
}
|
|
347
348
|
|
|
348
349
|
#[derive(Debug, Clone)]
|
|
@@ -366,11 +367,14 @@ pub struct CloudStatus {
|
|
|
366
367
|
|
|
367
368
|
impl Default for SetupPortalState {
|
|
368
369
|
fn default() -> Self {
|
|
370
|
+
let mut list_state = ratatui::widgets::ListState::default();
|
|
371
|
+
list_state.select(Some(0)); // Start with first option selected
|
|
369
372
|
Self {
|
|
370
373
|
selected_option: GatewayOption::LocalBundle,
|
|
371
374
|
detecting: false,
|
|
372
375
|
detection_result: None,
|
|
373
376
|
error: None,
|
|
377
|
+
list_state,
|
|
374
378
|
}
|
|
375
379
|
}
|
|
376
380
|
}
|
|
@@ -378,6 +382,7 @@ impl Default for SetupPortalState {
|
|
|
378
382
|
impl SetupPortalState {
|
|
379
383
|
pub fn cycle_option(&mut self, direction: i8) {
|
|
380
384
|
use GatewayOption::*;
|
|
385
|
+
let prev = self.selected_option.clone();
|
|
381
386
|
self.selected_option = match (&self.selected_option, direction) {
|
|
382
387
|
// Forward cycling (Down key, Scroll Down)
|
|
383
388
|
(LocalBundle, 1) => CloudServer,
|
|
@@ -389,6 +394,17 @@ impl SetupPortalState {
|
|
|
389
394
|
(CustomUrl, -1) => CloudServer,
|
|
390
395
|
_ => self.selected_option.clone(),
|
|
391
396
|
};
|
|
397
|
+
|
|
398
|
+
// CRITICAL: Update the persisted ListState index
|
|
399
|
+
let new_index = match self.selected_option {
|
|
400
|
+
LocalBundle => 0,
|
|
401
|
+
CloudServer => 1,
|
|
402
|
+
CustomUrl => 2,
|
|
403
|
+
};
|
|
404
|
+
self.list_state.select(Some(new_index));
|
|
405
|
+
|
|
406
|
+
eprintln!("[SETUP-PORTAL] cycle_option direction={} prev={:?} -> new={:?} list_index={}",
|
|
407
|
+
direction, prev, self.selected_option, new_index);
|
|
392
408
|
}
|
|
393
409
|
|
|
394
410
|
pub fn reset(&mut self) {
|
|
@@ -915,7 +931,7 @@ impl App {
|
|
|
915
931
|
}
|
|
916
932
|
}
|
|
917
933
|
|
|
918
|
-
pub fn render(&self, f: &mut Frame) {
|
|
934
|
+
pub fn render(&mut self, f: &mut Frame) {
|
|
919
935
|
// Use new navigation system - render based on current screen
|
|
920
936
|
let current = self.state.navigation.current_screen();
|
|
921
937
|
|
|
@@ -968,7 +984,7 @@ impl App {
|
|
|
968
984
|
// Render Setup Portal as standalone screen (not overlay)
|
|
969
985
|
// This eliminates double-rendering and improves performance
|
|
970
986
|
use crate::ui::setup_portal;
|
|
971
|
-
setup_portal::render(f, &self.state);
|
|
987
|
+
setup_portal::render(f, &mut self.state);
|
|
972
988
|
}
|
|
973
989
|
Screen::Confirmation { message, action } => {
|
|
974
990
|
// TODO: Implement confirmation popup
|
|
@@ -1799,12 +1815,13 @@ impl App {
|
|
|
1799
1815
|
// SETUP PORTAL INPUT HANDLING
|
|
1800
1816
|
// ============================================================
|
|
1801
1817
|
|
|
1802
|
-
fn handle_setup_portal_input(&mut self, key: KeyEvent,
|
|
1818
|
+
fn handle_setup_portal_input(&mut self, key: KeyEvent, _ws_client: Option<&WebSocketClient>) -> anyhow::Result<bool> {
|
|
1819
|
+
eprintln!("[SETUP-PORTAL] INPUT key={:?} (selected_option={:?})", key.code, self.state.setup_portal.selected_option);
|
|
1803
1820
|
// Guard: Don't process if already detecting
|
|
1804
1821
|
if self.state.setup_portal.detecting {
|
|
1805
1822
|
return Ok(false);
|
|
1806
1823
|
}
|
|
1807
|
-
|
|
1824
|
+
|
|
1808
1825
|
match key.code {
|
|
1809
1826
|
// ESC - Cancel and close portal
|
|
1810
1827
|
KeyCode::Esc => {
|
|
@@ -1824,11 +1841,13 @@ impl App {
|
|
|
1824
1841
|
|
|
1825
1842
|
// Up/Down - Navigate options (immediate render for responsiveness)
|
|
1826
1843
|
KeyCode::Up => {
|
|
1844
|
+
eprintln!("[SETUP-PORTAL] KEY Up -> cycle_option(-1)");
|
|
1827
1845
|
self.state.setup_portal.cycle_option(-1);
|
|
1828
1846
|
self.request_immediate_render("setup_nav_up");
|
|
1829
1847
|
}
|
|
1830
1848
|
|
|
1831
1849
|
KeyCode::Down => {
|
|
1850
|
+
eprintln!("[SETUP-PORTAL] KEY Down -> cycle_option(1)");
|
|
1832
1851
|
self.state.setup_portal.cycle_option(1);
|
|
1833
1852
|
self.request_immediate_render("setup_nav_down");
|
|
1834
1853
|
}
|
package/mk3-tui/src/main.rs
CHANGED
|
@@ -632,12 +632,12 @@ fn main() -> Result<()> {
|
|
|
632
632
|
if !app.state.setup_portal.detecting {
|
|
633
633
|
match mouse.kind {
|
|
634
634
|
MouseEventKind::ScrollUp => {
|
|
635
|
-
|
|
635
|
+
eprintln!("[SETUP-PORTAL] MOUSE ScrollUp -> cycle_option(-1)");
|
|
636
636
|
app.state.setup_portal.cycle_option(-1);
|
|
637
637
|
app.request_immediate_render("setup_portal_scroll_up");
|
|
638
638
|
}
|
|
639
639
|
MouseEventKind::ScrollDown => {
|
|
640
|
-
|
|
640
|
+
eprintln!("[SETUP-PORTAL] MOUSE ScrollDown -> cycle_option(1)");
|
|
641
641
|
app.state.setup_portal.cycle_option(1);
|
|
642
642
|
app.request_immediate_render("setup_portal_scroll_down");
|
|
643
643
|
}
|
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.46";
|
|
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)),
|
|
@@ -17,19 +17,20 @@ const BG_PANEL: Color = Color::Rgb(18, 18, 25);
|
|
|
17
17
|
const ERROR_RED: Color = Color::Rgb(255, 69, 69);
|
|
18
18
|
|
|
19
19
|
/// Render the Setup Portal screen - Full-screen standalone portal
|
|
20
|
-
pub fn render(f: &mut Frame, state: &AppState) {
|
|
21
|
-
// DEBUG:
|
|
20
|
+
pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
21
|
+
// DEBUG: Every frame log the selection state used for this draw (no throttle)
|
|
22
|
+
eprintln!("[SETUP-PORTAL] FRAME selected_option={:?}", state.setup_portal.selected_option);
|
|
23
|
+
|
|
22
24
|
static mut RENDER_COUNT: u64 = 0;
|
|
23
25
|
unsafe {
|
|
24
26
|
RENDER_COUNT += 1;
|
|
25
|
-
if RENDER_COUNT % 10 == 1 {
|
|
27
|
+
if RENDER_COUNT % 10 == 1 {
|
|
26
28
|
eprintln!("[SETUP-PORTAL] Render #{}", RENDER_COUNT);
|
|
27
29
|
}
|
|
28
30
|
}
|
|
29
|
-
|
|
31
|
+
|
|
30
32
|
let area = f.size();
|
|
31
|
-
|
|
32
|
-
// DEBUG: Log terminal size
|
|
33
|
+
|
|
33
34
|
unsafe {
|
|
34
35
|
if RENDER_COUNT % 10 == 1 {
|
|
35
36
|
eprintln!("[SETUP-PORTAL] Terminal size: {}x{}", area.width, area.height);
|
|
@@ -181,7 +182,7 @@ fn render_description(f: &mut Frame, area: Rect) {
|
|
|
181
182
|
f.render_widget(text, inner);
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
185
|
+
fn render_content(f: &mut Frame, area: Rect, state: &mut AppState) {
|
|
185
186
|
// DEBUG: Log content area
|
|
186
187
|
static mut LOG_COUNT: u64 = 0;
|
|
187
188
|
unsafe {
|
|
@@ -215,7 +216,7 @@ fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
215
216
|
render_option_details(f, chunks[1], state);
|
|
216
217
|
}
|
|
217
218
|
|
|
218
|
-
fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
219
|
+
fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState) {
|
|
219
220
|
// DEBUG: Log options list rendering
|
|
220
221
|
static mut LOG_COUNT: u64 = 0;
|
|
221
222
|
unsafe {
|
|
@@ -241,12 +242,12 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
241
242
|
let selected_index = options.iter()
|
|
242
243
|
.position(|(_, opt)| opt == &state.setup_portal.selected_option)
|
|
243
244
|
.unwrap_or(0);
|
|
244
|
-
|
|
245
|
-
// DEBUG:
|
|
245
|
+
|
|
246
|
+
// DEBUG: Every frame - exact state used for list (so we can match what's drawn)
|
|
247
|
+
eprintln!("[SETUP-PORTAL] LIST selected_index={} selected_option={:?}", selected_index, state.setup_portal.selected_option);
|
|
248
|
+
|
|
246
249
|
unsafe {
|
|
247
250
|
if LOG_COUNT % 10 == 1 {
|
|
248
|
-
eprintln!("[SETUP-PORTAL] Selected index: {}, option: {:?}",
|
|
249
|
-
selected_index, state.setup_portal.selected_option);
|
|
250
251
|
eprintln!("[SETUP-PORTAL] Options count: {}", options.len());
|
|
251
252
|
}
|
|
252
253
|
}
|
|
@@ -273,9 +274,11 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
273
274
|
})
|
|
274
275
|
.collect();
|
|
275
276
|
|
|
276
|
-
//
|
|
277
|
-
|
|
278
|
-
list_state.
|
|
277
|
+
// CRITICAL: Use the PERSISTED ListState from app state (not a new one each frame)
|
|
278
|
+
// This is required for Ratatui's stateful widget to maintain selection across frames
|
|
279
|
+
let list_state_selected = state.setup_portal.list_state.selected();
|
|
280
|
+
eprintln!("[SETUP-PORTAL] LIST About to render: list_state.selected()={:?} selected_index={} selected_option={:?}",
|
|
281
|
+
list_state_selected, selected_index, state.setup_portal.selected_option);
|
|
279
282
|
|
|
280
283
|
// Use Block to properly contain and clear the area
|
|
281
284
|
let block = Block::default()
|
|
@@ -288,18 +291,20 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
288
291
|
.block(block)
|
|
289
292
|
.highlight_style(Style::default().fg(CYBER_CYAN).bold().bg(BG_PANEL));
|
|
290
293
|
|
|
291
|
-
|
|
294
|
+
// CRITICAL: Pass the mutable reference to the persisted ListState
|
|
295
|
+
f.render_stateful_widget(list, area, &mut state.setup_portal.list_state);
|
|
292
296
|
}
|
|
293
297
|
|
|
294
298
|
fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
295
|
-
// DEBUG:
|
|
299
|
+
// DEBUG: Every frame - state used for details panel
|
|
300
|
+
eprintln!("[SETUP-PORTAL] DETAILS selected_option={:?}", state.setup_portal.selected_option);
|
|
301
|
+
|
|
296
302
|
static mut LOG_COUNT: u64 = 0;
|
|
297
303
|
unsafe {
|
|
298
304
|
LOG_COUNT += 1;
|
|
299
305
|
if LOG_COUNT % 10 == 1 {
|
|
300
|
-
eprintln!("[SETUP-PORTAL] render_option_details - area: x={}, y={}, w={}, h={}",
|
|
306
|
+
eprintln!("[SETUP-PORTAL] render_option_details - area: x={}, y={}, w={}, h={}",
|
|
301
307
|
area.x, area.y, area.width, area.height);
|
|
302
|
-
eprintln!("[SETUP-PORTAL] Selected option: {:?}", state.setup_portal.selected_option);
|
|
303
308
|
}
|
|
304
309
|
}
|
|
305
310
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-os",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.46",
|
|
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.46: Setup Portal navigation fix - persisted ListState so selection matches visual. v2.9.45: Debug logging. v2.9.44: Fill-background fix. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|