4runr-os 2.9.45 → 2.9.47
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
|
}
|
|
@@ -390,7 +394,17 @@ impl SetupPortalState {
|
|
|
390
394
|
(CustomUrl, -1) => CloudServer,
|
|
391
395
|
_ => self.selected_option.clone(),
|
|
392
396
|
};
|
|
393
|
-
|
|
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);
|
|
394
408
|
}
|
|
395
409
|
|
|
396
410
|
pub fn reset(&mut self) {
|
|
@@ -917,7 +931,7 @@ impl App {
|
|
|
917
931
|
}
|
|
918
932
|
}
|
|
919
933
|
|
|
920
|
-
pub fn render(&self, f: &mut Frame) {
|
|
934
|
+
pub fn render(&mut self, f: &mut Frame) {
|
|
921
935
|
// Use new navigation system - render based on current screen
|
|
922
936
|
let current = self.state.navigation.current_screen();
|
|
923
937
|
|
|
@@ -970,7 +984,7 @@ impl App {
|
|
|
970
984
|
// Render Setup Portal as standalone screen (not overlay)
|
|
971
985
|
// This eliminates double-rendering and improves performance
|
|
972
986
|
use crate::ui::setup_portal;
|
|
973
|
-
setup_portal::render(f, &self.state);
|
|
987
|
+
setup_portal::render(f, &mut self.state);
|
|
974
988
|
}
|
|
975
989
|
Screen::Confirmation { message, action } => {
|
|
976
990
|
// TODO: Implement confirmation popup
|
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.47";
|
|
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,7 +17,7 @@ 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) {
|
|
20
|
+
pub fn render(f: &mut Frame, state: &mut AppState) {
|
|
21
21
|
// DEBUG: Every frame log the selection state used for this draw (no throttle)
|
|
22
22
|
eprintln!("[SETUP-PORTAL] FRAME selected_option={:?}", state.setup_portal.selected_option);
|
|
23
23
|
|
|
@@ -182,7 +182,7 @@ fn render_description(f: &mut Frame, area: Rect) {
|
|
|
182
182
|
f.render_widget(text, inner);
|
|
183
183
|
}
|
|
184
184
|
|
|
185
|
-
fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
185
|
+
fn render_content(f: &mut Frame, area: Rect, state: &mut AppState) {
|
|
186
186
|
// DEBUG: Log content area
|
|
187
187
|
static mut LOG_COUNT: u64 = 0;
|
|
188
188
|
unsafe {
|
|
@@ -216,7 +216,7 @@ fn render_content(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
216
216
|
render_option_details(f, chunks[1], state);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
219
|
+
fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState) {
|
|
220
220
|
// DEBUG: Log options list rendering
|
|
221
221
|
static mut LOG_COUNT: u64 = 0;
|
|
222
222
|
unsafe {
|
|
@@ -238,13 +238,14 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
238
238
|
("Custom URL", GatewayOption::CustomUrl),
|
|
239
239
|
];
|
|
240
240
|
|
|
241
|
-
// Find selected index
|
|
241
|
+
// Find selected index from canonical source of truth (selected_option)
|
|
242
242
|
let selected_index = options.iter()
|
|
243
243
|
.position(|(_, opt)| opt == &state.setup_portal.selected_option)
|
|
244
244
|
.unwrap_or(0);
|
|
245
245
|
|
|
246
|
-
//
|
|
247
|
-
|
|
246
|
+
// CRITICAL: Force ListState to match selected_option every frame.
|
|
247
|
+
// This prevents drift (e.g. from widget internals or multiple render paths).
|
|
248
|
+
state.setup_portal.list_state.select(Some(selected_index));
|
|
248
249
|
|
|
249
250
|
unsafe {
|
|
250
251
|
if LOG_COUNT % 10 == 1 {
|
|
@@ -274,10 +275,6 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
274
275
|
})
|
|
275
276
|
.collect();
|
|
276
277
|
|
|
277
|
-
// Create ListState with selected index
|
|
278
|
-
let mut list_state = ListState::default();
|
|
279
|
-
list_state.select(Some(selected_index));
|
|
280
|
-
|
|
281
278
|
// Use Block to properly contain and clear the area
|
|
282
279
|
let block = Block::default()
|
|
283
280
|
.borders(Borders::ALL)
|
|
@@ -289,7 +286,8 @@ fn render_options_list(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
289
286
|
.block(block)
|
|
290
287
|
.highlight_style(Style::default().fg(CYBER_CYAN).bold().bg(BG_PANEL));
|
|
291
288
|
|
|
292
|
-
|
|
289
|
+
// CRITICAL: Pass the mutable reference to the persisted ListState
|
|
290
|
+
f.render_stateful_widget(list, area, &mut state.setup_portal.list_state);
|
|
293
291
|
}
|
|
294
292
|
|
|
295
293
|
fn render_option_details(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.47",
|
|
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.47: Setup Portal - sync ListState from selected_option every frame so highlight never drifts. v2.9.46: Persisted ListState. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|