4runr-os 2.9.46 → 2.9.48

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.
@@ -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.46";
147
+ const PACKAGE_VERSION: &str = "2.9.48";
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)),
@@ -2,7 +2,7 @@
2
2
  /// Wizard for setting up Gateway connection options
3
3
 
4
4
  use ratatui::prelude::*;
5
- use ratatui::widgets::{Block, Borders, Paragraph, Wrap, Clear, List, ListItem, ListState};
5
+ use ratatui::widgets::{Block, Borders, Paragraph, Wrap, Clear, List, ListItem};
6
6
  use crate::app::{AppState, GatewayOption};
7
7
 
8
8
  // === 4RUNR BRAND COLORS (matching layout.rs) ===
@@ -18,8 +18,13 @@ 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
- // 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);
21
+ // Per-frame render ID so we can correlate all logs from the same draw
22
+ static mut RENDER_ID: u64 = 0;
23
+ let render_id = unsafe {
24
+ RENDER_ID += 1;
25
+ RENDER_ID
26
+ };
27
+ eprintln!("[SETUP-PORTAL] R#{} START selected_option={:?}", render_id, state.setup_portal.selected_option);
23
28
 
24
29
  static mut RENDER_COUNT: u64 = 0;
25
30
  unsafe {
@@ -133,7 +138,7 @@ pub fn render(f: &mut Frame, state: &mut AppState) {
133
138
 
134
139
  render_header(f, chunks[0], state);
135
140
  render_description(f, chunks[1]);
136
- render_content(f, chunks[2], state);
141
+ render_content(f, chunks[2], state, render_id);
137
142
 
138
143
  if has_error {
139
144
  render_error_box(f, chunks[3], state);
@@ -182,132 +187,79 @@ fn render_description(f: &mut Frame, area: Rect) {
182
187
  f.render_widget(text, inner);
183
188
  }
184
189
 
185
- fn render_content(f: &mut Frame, area: Rect, state: &mut AppState) {
186
- // DEBUG: Log content area
190
+ fn render_content(f: &mut Frame, area: Rect, state: &mut AppState, render_id: u64) {
187
191
  static mut LOG_COUNT: u64 = 0;
188
192
  unsafe {
189
193
  LOG_COUNT += 1;
190
194
  if LOG_COUNT % 10 == 1 {
191
- eprintln!("[SETUP-PORTAL] render_content - area: x={}, y={}, w={}, h={}",
192
- area.x, area.y, area.width, area.height);
195
+ eprintln!("[SETUP-PORTAL] R#{} render_content area: {}x{}", render_id, area.width, area.height);
193
196
  }
194
197
  }
195
-
196
- // Split content into two columns: Options (left) and Details (right)
198
+
197
199
  let chunks = Layout::default()
198
200
  .direction(Direction::Horizontal)
199
201
  .constraints([
200
- Constraint::Percentage(40), // Options list
201
- Constraint::Percentage(60), // Details panel
202
+ Constraint::Percentage(40),
203
+ Constraint::Percentage(60),
202
204
  ])
203
205
  .split(area);
204
-
205
- // DEBUG: Log content chunks
206
- unsafe {
207
- if LOG_COUNT % 10 == 1 {
208
- eprintln!("[SETUP-PORTAL] Content chunks[0] (options): x={}, y={}, w={}, h={}",
209
- chunks[0].x, chunks[0].y, chunks[0].width, chunks[0].height);
210
- eprintln!("[SETUP-PORTAL] Content chunks[1] (details): x={}, y={}, w={}, h={}",
211
- chunks[1].x, chunks[1].y, chunks[1].width, chunks[1].height);
212
- }
213
- }
214
-
215
- render_options_list(f, chunks[0], state);
216
- render_option_details(f, chunks[1], state);
206
+
207
+ render_options_list(f, chunks[0], state, render_id);
208
+ render_option_details(f, chunks[1], state, render_id);
217
209
  }
218
210
 
219
- fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState) {
220
- // DEBUG: Log options list rendering
221
- static mut LOG_COUNT: u64 = 0;
222
- unsafe {
223
- LOG_COUNT += 1;
224
- if LOG_COUNT % 10 == 1 {
225
- eprintln!("[SETUP-PORTAL] render_options_list - area: x={}, y={}, w={}, h={}",
226
- area.x, area.y, area.width, area.height);
227
- }
228
- }
229
-
230
- // CRITICAL: Fill entire area with background so every cell is overwritten.
231
- // Without this, List only paints rows with items; leftover rows show previous frame (duplication).
211
+ fn render_options_list(f: &mut Frame, area: Rect, state: &mut AppState, render_id: u64) {
232
212
  let fill = Block::default().style(Style::default().bg(BG_PANEL));
233
213
  f.render_widget(fill, area);
234
-
214
+
235
215
  let options = vec![
236
216
  ("Local Bundle", GatewayOption::LocalBundle),
237
217
  ("4Runr Server", GatewayOption::CloudServer),
238
218
  ("Custom URL", GatewayOption::CustomUrl),
239
219
  ];
240
-
241
- // Find selected index
220
+
221
+ let selected_option = &state.setup_portal.selected_option;
242
222
  let selected_index = options.iter()
243
- .position(|(_, opt)| opt == &state.setup_portal.selected_option)
223
+ .position(|(_, opt)| opt == selected_option)
244
224
  .unwrap_or(0);
245
225
 
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);
226
+ eprintln!("[SETUP-PORTAL] R#{} LIST selected_index={} selected_option={:?}", render_id, selected_index, state.setup_portal.selected_option);
248
227
 
249
- unsafe {
250
- if LOG_COUNT % 10 == 1 {
251
- eprintln!("[SETUP-PORTAL] Options count: {}", options.len());
252
- }
253
- }
254
-
255
228
  let items: Vec<ListItem> = options
256
229
  .iter()
257
230
  .map(|(name, option)| {
258
- let is_selected = &state.setup_portal.selected_option == option;
231
+ let is_selected = selected_option == option;
259
232
  let style = if is_selected {
260
233
  Style::default().fg(CYBER_CYAN).bold()
261
234
  } else {
262
235
  Style::default().fg(TEXT_MUTED)
263
236
  };
264
-
265
237
  let icon = match option {
266
238
  GatewayOption::LocalBundle => "[■]",
267
239
  GatewayOption::CloudServer => "[≡]",
268
240
  GatewayOption::CustomUrl => "[◆]",
269
241
  };
270
-
271
242
  let prefix = if is_selected { "► " } else { " " };
272
243
  let text = format!("{}{} {}", prefix, icon, name);
273
244
  ListItem::new(text).style(style)
274
245
  })
275
246
  .collect();
276
-
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);
282
-
283
- // Use Block to properly contain and clear the area
247
+
284
248
  let block = Block::default()
285
249
  .borders(Borders::ALL)
286
250
  .title(" Gateway Options ")
287
251
  .border_style(Style::default().fg(CYBER_CYAN))
288
252
  .style(Style::default().bg(BG_PANEL));
289
-
290
- let list = List::new(items)
291
- .block(block)
292
- .highlight_style(Style::default().fg(CYBER_CYAN).bold().bg(BG_PANEL));
293
-
294
- // CRITICAL: Pass the mutable reference to the persisted ListState
295
- f.render_stateful_widget(list, area, &mut state.setup_portal.list_state);
253
+
254
+ // STATELESS: Render list without ListState so only our item styling (► + color) shows selection.
255
+ // This removes any second source of highlight that could drift from selected_option.
256
+ let list = List::new(items).block(block);
257
+ f.render_widget(list, area);
296
258
  }
297
259
 
298
- fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
299
- // DEBUG: Every frame - state used for details panel
300
- eprintln!("[SETUP-PORTAL] DETAILS selected_option={:?}", state.setup_portal.selected_option);
260
+ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState, render_id: u64) {
261
+ eprintln!("[SETUP-PORTAL] R#{} DETAILS selected_option={:?}", render_id, state.setup_portal.selected_option);
301
262
 
302
- static mut LOG_COUNT: u64 = 0;
303
- unsafe {
304
- LOG_COUNT += 1;
305
- if LOG_COUNT % 10 == 1 {
306
- eprintln!("[SETUP-PORTAL] render_option_details - area: x={}, y={}, w={}, h={}",
307
- area.x, area.y, area.width, area.height);
308
- }
309
- }
310
-
311
263
  // CRITICAL: Fill entire area with background so every cell is overwritten.
312
264
  // Without this, Paragraph may not paint every cell; leftover shows previous option (corruption).
313
265
  let fill = Block::default().style(Style::default().bg(BG_PANEL));
@@ -397,19 +349,9 @@ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
397
349
  ]
398
350
  ),
399
351
  };
400
-
401
- // DEBUG: Log what content is being rendered
402
- unsafe {
403
- if LOG_COUNT % 10 == 1 {
404
- eprintln!("[SETUP-PORTAL] Title: '{}'", title);
405
- eprintln!("[SETUP-PORTAL] Content lines: {}", content.len());
406
- for (i, line) in content.iter().take(3).enumerate() {
407
- let preview = format!("{:?}", line).chars().take(50).collect::<String>();
408
- eprintln!("[SETUP-PORTAL] content[{}]: {}", i, preview);
409
- }
410
- }
411
- }
412
-
352
+
353
+ eprintln!("[SETUP-PORTAL] R#{} DETAILS title='{}' lines={}", render_id, title.trim(), content.len());
354
+
413
355
  // Use Block to properly contain and clear the area
414
356
  let block = Block::default()
415
357
  .title(title)
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.46",
3
+ "version": "2.9.48",
4
4
  "type": "module",
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",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.48: Setup Portal - stateless list (single source of truth), per-frame R# debug. v2.9.47: ListState sync. ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",