4runr-os 2.9.47 → 2.9.49
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/main.rs
CHANGED
|
@@ -591,15 +591,16 @@ 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 terminal
|
|
595
|
-
//
|
|
594
|
+
// Clear terminal on screen switch OR if Setup Portal is active
|
|
595
|
+
// Setup Portal needs per-frame clear to prevent terminal buffering artifacts
|
|
596
596
|
let is_switching = match (&previous_screen, ¤t_screen) {
|
|
597
597
|
(Some(prev), current) => prev != current,
|
|
598
598
|
(None, _) => true, // First render
|
|
599
|
-
_ => false,
|
|
600
599
|
};
|
|
601
600
|
|
|
602
|
-
|
|
601
|
+
let force_clear_for_setup = matches!(current_screen, crate::screens::Screen::SetupPortal);
|
|
602
|
+
|
|
603
|
+
if is_switching || force_clear_for_setup {
|
|
603
604
|
terminal.clear()?;
|
|
604
605
|
}
|
|
605
606
|
|
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.49";
|
|
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
|
|
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
|
-
//
|
|
22
|
-
|
|
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,127 +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
|
|
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),
|
|
201
|
-
Constraint::Percentage(60),
|
|
202
|
+
Constraint::Percentage(40),
|
|
203
|
+
Constraint::Percentage(60),
|
|
202
204
|
])
|
|
203
205
|
.split(area);
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
220
|
+
|
|
221
|
+
let selected_option = &state.setup_portal.selected_option;
|
|
242
222
|
let selected_index = options.iter()
|
|
243
|
-
.position(|(_, opt)| opt ==
|
|
223
|
+
.position(|(_, opt)| opt == selected_option)
|
|
244
224
|
.unwrap_or(0);
|
|
245
225
|
|
|
246
|
-
|
|
247
|
-
// This prevents drift (e.g. from widget internals or multiple render paths).
|
|
248
|
-
state.setup_portal.list_state.select(Some(selected_index));
|
|
226
|
+
eprintln!("[SETUP-PORTAL] R#{} LIST selected_index={} selected_option={:?}", render_id, selected_index, state.setup_portal.selected_option);
|
|
249
227
|
|
|
250
|
-
unsafe {
|
|
251
|
-
if LOG_COUNT % 10 == 1 {
|
|
252
|
-
eprintln!("[SETUP-PORTAL] Options count: {}", options.len());
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
228
|
let items: Vec<ListItem> = options
|
|
257
229
|
.iter()
|
|
258
230
|
.map(|(name, option)| {
|
|
259
|
-
let is_selected =
|
|
231
|
+
let is_selected = selected_option == option;
|
|
260
232
|
let style = if is_selected {
|
|
261
233
|
Style::default().fg(CYBER_CYAN).bold()
|
|
262
234
|
} else {
|
|
263
235
|
Style::default().fg(TEXT_MUTED)
|
|
264
236
|
};
|
|
265
|
-
|
|
266
237
|
let icon = match option {
|
|
267
238
|
GatewayOption::LocalBundle => "[■]",
|
|
268
239
|
GatewayOption::CloudServer => "[≡]",
|
|
269
240
|
GatewayOption::CustomUrl => "[◆]",
|
|
270
241
|
};
|
|
271
|
-
|
|
272
242
|
let prefix = if is_selected { "► " } else { " " };
|
|
273
243
|
let text = format!("{}{} {}", prefix, icon, name);
|
|
274
244
|
ListItem::new(text).style(style)
|
|
275
245
|
})
|
|
276
246
|
.collect();
|
|
277
|
-
|
|
278
|
-
// Use Block to properly contain and clear the area
|
|
247
|
+
|
|
279
248
|
let block = Block::default()
|
|
280
249
|
.borders(Borders::ALL)
|
|
281
250
|
.title(" Gateway Options ")
|
|
282
251
|
.border_style(Style::default().fg(CYBER_CYAN))
|
|
283
252
|
.style(Style::default().bg(BG_PANEL));
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// CRITICAL: Pass the mutable reference to the persisted ListState
|
|
290
|
-
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);
|
|
291
258
|
}
|
|
292
259
|
|
|
293
|
-
fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
294
|
-
|
|
295
|
-
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);
|
|
296
262
|
|
|
297
|
-
static mut LOG_COUNT: u64 = 0;
|
|
298
|
-
unsafe {
|
|
299
|
-
LOG_COUNT += 1;
|
|
300
|
-
if LOG_COUNT % 10 == 1 {
|
|
301
|
-
eprintln!("[SETUP-PORTAL] render_option_details - area: x={}, y={}, w={}, h={}",
|
|
302
|
-
area.x, area.y, area.width, area.height);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
263
|
// CRITICAL: Fill entire area with background so every cell is overwritten.
|
|
307
264
|
// Without this, Paragraph may not paint every cell; leftover shows previous option (corruption).
|
|
308
265
|
let fill = Block::default().style(Style::default().bg(BG_PANEL));
|
|
@@ -392,19 +349,9 @@ fn render_option_details(f: &mut Frame, area: Rect, state: &AppState) {
|
|
|
392
349
|
]
|
|
393
350
|
),
|
|
394
351
|
};
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if LOG_COUNT % 10 == 1 {
|
|
399
|
-
eprintln!("[SETUP-PORTAL] Title: '{}'", title);
|
|
400
|
-
eprintln!("[SETUP-PORTAL] Content lines: {}", content.len());
|
|
401
|
-
for (i, line) in content.iter().take(3).enumerate() {
|
|
402
|
-
let preview = format!("{:?}", line).chars().take(50).collect::<String>();
|
|
403
|
-
eprintln!("[SETUP-PORTAL] content[{}]: {}", i, preview);
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
352
|
+
|
|
353
|
+
eprintln!("[SETUP-PORTAL] R#{} DETAILS title='{}' lines={}", render_id, title.trim(), content.len());
|
|
354
|
+
|
|
408
355
|
// Use Block to properly contain and clear the area
|
|
409
356
|
let block = Block::default()
|
|
410
357
|
.title(title)
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "4runr-os",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.49",
|
|
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.49: Setup Portal - per-frame terminal clear to fix list/details mismatch (terminal buffering). v2.9.48: Stateless list, R# debug. ⚠️ Pre-MVP / Development Phase",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"4runr": "dist/index.js",
|