4runr-os 2.9.16 → 2.9.17

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.
@@ -721,12 +721,15 @@ impl App {
721
721
  }
722
722
  }
723
723
  "connect portal" | "portal" => {
724
- self.push_overlay(Screen::ConnectionPortal);
724
+ // Navigate to portal as base screen (standalone, not overlay)
725
+ self.state.navigation.navigate_to_base(Screen::ConnectionPortal);
725
726
  self.state.connection_portal.reset();
726
727
  self.state.logs.push_back("[NAV] Opening Connection Portal...".into());
728
+ self.request_render("open_connection_portal");
727
729
  }
728
730
  "setup" | "setup gateway" | "setup portal" => {
729
- self.push_overlay(Screen::SetupPortal);
731
+ // Navigate to portal as base screen (standalone, not overlay)
732
+ self.state.navigation.navigate_to_base(Screen::SetupPortal);
730
733
  self.state.setup_portal.reset();
731
734
 
732
735
  // Auto-detect Gateway options when portal opens
@@ -1651,9 +1654,10 @@ impl App {
1651
1654
  }
1652
1655
 
1653
1656
  match key.code {
1654
- // ESC - Close portal
1657
+ // ESC - Close portal and return to Main
1655
1658
  KeyCode::Esc => {
1656
- self.pop_overlay();
1659
+ // Navigate back to Main (portals are now base screens, not overlays)
1660
+ self.state.navigation.navigate_to_base(Screen::Main);
1657
1661
  self.add_log("[NAV] Connection Portal closed".to_string());
1658
1662
  self.request_render("portal_close");
1659
1663
  }
@@ -1770,15 +1774,16 @@ impl App {
1770
1774
  match key.code {
1771
1775
  // ESC - Cancel and close portal
1772
1776
  KeyCode::Esc => {
1773
- self.pop_overlay();
1777
+ // Navigate back to Main (portals are now base screens, not overlays)
1778
+ self.state.navigation.navigate_to_base(Screen::Main);
1774
1779
  self.add_log("[NAV] Setup Portal closed".to_string());
1775
1780
  self.request_render("setup_portal_close");
1776
1781
  }
1777
1782
 
1778
1783
  // F2 - Go directly to Connection Portal
1779
1784
  KeyCode::F(2) => {
1780
- self.pop_overlay();
1781
- self.push_overlay(Screen::ConnectionPortal);
1785
+ // Navigate to portal as base screen (standalone, not overlay)
1786
+ self.state.navigation.navigate_to_base(Screen::ConnectionPortal);
1782
1787
  self.add_log("[NAV] Opening Connection Portal...".to_string());
1783
1788
  self.request_render("open_connection_portal");
1784
1789
  }
@@ -1814,9 +1819,8 @@ impl App {
1814
1819
  self.state.connection_portal.error = None;
1815
1820
  self.state.connection_portal.connection_success = false;
1816
1821
 
1817
- // Navigate to Connection Portal
1818
- self.pop_overlay();
1819
- self.push_overlay(Screen::ConnectionPortal);
1822
+ // Navigate to Connection Portal as base screen (standalone, not overlay)
1823
+ self.state.navigation.navigate_to_base(Screen::ConnectionPortal);
1820
1824
 
1821
1825
  self.add_log(format!("[SETUP] Selected {} - Opening Connection Portal",
1822
1826
  self.state.setup_portal.selected_option.as_str()));
@@ -70,6 +70,9 @@ fn main() -> Result<()> {
70
70
  let start_time = Instant::now();
71
71
  let mut last_tick = Instant::now();
72
72
 
73
+ // Track previous screen to detect portal navigation
74
+ let mut previous_screen: Option<crate::screens::Screen> = None;
75
+
73
76
  // Force initial render
74
77
  app.request_render("initial");
75
78
 
@@ -378,8 +381,9 @@ fn main() -> Result<()> {
378
381
  // Bundle is running! Proceed to Connection Portal
379
382
  app.add_log(format!("✓ [{}] Local Bundle detected and running", short_id));
380
383
  app.state.connection_portal.gateway_url = "http://localhost:3001".to_string();
381
- app.pop_overlay();
382
- app.push_overlay(crate::screens::Screen::ConnectionPortal);
384
+ // Navigate to portal as base screen (standalone, not overlay)
385
+ app.state.navigation.navigate_to_base(crate::screens::Screen::ConnectionPortal);
386
+ app.request_render("setup_detect_success");
383
387
  } else if exists {
384
388
  // Bundle exists but not running
385
389
  app.add_log(format!("⚠ [{}] Local Bundle found but not running", short_id));
@@ -513,10 +517,31 @@ fn main() -> Result<()> {
513
517
  // Check if render is scheduled and throttle limit has passed
514
518
  // OPTIMIZATION: Removed input debounce check - using immediate render for typing
515
519
  if app.should_render() {
520
+ // Check if we're switching to a portal - clear terminal buffer for clean transition
521
+ // Clone current screen before mutable borrow
522
+ let current_screen = app.state.navigation.current_screen().clone();
523
+ let is_switching_to_portal = match (&previous_screen, &current_screen) {
524
+ (Some(prev), current) if prev != current => {
525
+ matches!(current, crate::screens::Screen::ConnectionPortal | crate::screens::Screen::SetupPortal)
526
+ }
527
+ (None, current) => {
528
+ matches!(current, crate::screens::Screen::ConnectionPortal | crate::screens::Screen::SetupPortal)
529
+ }
530
+ _ => false,
531
+ };
532
+
533
+ if is_switching_to_portal {
534
+ // Clear terminal buffer when switching to portal for clean standalone rendering
535
+ terminal.clear()?;
536
+ }
537
+
516
538
  let render_start = Instant::now();
517
539
  terminal.draw(|f| app.render(f))?;
518
540
  let render_duration = render_start.elapsed().as_millis() as u64;
519
541
  app.record_render(render_duration);
542
+
543
+ // Update previous screen tracking
544
+ previous_screen = Some(current_screen);
520
545
  }
521
546
 
522
547
  // ┌─────────────────────────────────────────────────────────┐
@@ -33,7 +33,7 @@ impl Screen {
33
33
  pub fn is_overlay(&self) -> bool {
34
34
  matches!(
35
35
  self,
36
- Screen::AgentBuilder | Screen::RunManager | Screen::Settings | Screen::AgentList | Screen::ConnectionPortal | Screen::SetupPortal
36
+ Screen::AgentBuilder | Screen::RunManager | Screen::Settings | Screen::AgentList
37
37
  )
38
38
  }
39
39
 
@@ -45,9 +45,9 @@ impl Screen {
45
45
  )
46
46
  }
47
47
 
48
- /// Returns true if this screen is a base screen
48
+ /// Returns true if this screen is a base screen (full-screen standalone)
49
49
  pub fn is_base(&self) -> bool {
50
- matches!(self, Screen::Boot | Screen::Main)
50
+ matches!(self, Screen::Boot | Screen::Main | Screen::ConnectionPortal | Screen::SetupPortal)
51
51
  }
52
52
 
53
53
  /// Returns the screen name for display
@@ -17,20 +17,19 @@ const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
17
17
  const BG_PANEL: Color = Color::Rgb(18, 18, 25);
18
18
  const ERROR_RED: Color = Color::Rgb(255, 69, 69);
19
19
 
20
- /// Main render function
20
+ /// Main render function - Full-screen standalone portal
21
21
  pub fn render(f: &mut Frame, state: &AppState) {
22
22
  let area = f.size();
23
23
 
24
- // CRITICAL FIX: Clear the ENTIRE screen first to prevent artifacts
24
+ // CRITICAL: Clear the ENTIRE screen first - this is a standalone screen, not an overlay
25
25
  f.render_widget(Clear, area);
26
26
 
27
- // Create a dimmed overlay over the entire screen (modal backdrop)
28
- // Using very dark gray instead of pure black for better dimming effect
29
- let overlay = Block::default()
30
- .style(Style::default().bg(Color::Rgb(10, 10, 15)));
31
- f.render_widget(overlay, area);
27
+ // Render full-screen background (no dimming - this is the only screen)
28
+ let bg = Block::default()
29
+ .style(Style::default().bg(BG_PANEL));
30
+ f.render_widget(bg, area);
32
31
 
33
- // Calculate centered portal dimensions
32
+ // Calculate centered portal dimensions (smaller dialog on full-screen background)
34
33
  let portal_width = area.width.min(80).max(60);
35
34
  let portal_height = area.height.min(28).max(22);
36
35
 
@@ -44,7 +43,7 @@ pub fn render(f: &mut Frame, state: &AppState) {
44
43
  height: portal_height,
45
44
  };
46
45
 
47
- // Clear the portal area again for clean rendering
46
+ // Clear the portal area for clean rendering
48
47
  f.render_widget(Clear, portal_area);
49
48
 
50
49
  // Render portal background
@@ -143,8 +143,8 @@ fn render_header(f: &mut Frame, area: Rect, state: &AppState) {
143
143
  };
144
144
 
145
145
  // Line 1: Brand + version + mode + uptime - Bug 3 fix: Use "4Runr." with dot (matches brand logo)
146
- // Use npm package version (2.9.16) - matches package.json
147
- const PACKAGE_VERSION: &str = "2.9.16";
146
+ // Use npm package version (2.9.17) - matches package.json
147
+ const PACKAGE_VERSION: &str = "2.9.17";
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)),
@@ -16,18 +16,17 @@ const TEXT_MUTED: Color = Color::Rgb(60, 60, 80);
16
16
  const BG_PANEL: Color = Color::Rgb(18, 18, 25);
17
17
  const ERROR_RED: Color = Color::Rgb(255, 69, 69);
18
18
 
19
- /// Render the Setup Portal screen (full-screen overlay)
19
+ /// Render the Setup Portal screen - Full-screen standalone portal
20
20
  pub fn render(f: &mut Frame, state: &AppState) {
21
21
  let area = f.size();
22
22
 
23
- // CRITICAL FIX: Clear the ENTIRE screen first to prevent artifacts
23
+ // CRITICAL: Clear the ENTIRE screen first - this is a standalone screen, not an overlay
24
24
  f.render_widget(Clear, area);
25
25
 
26
- // Create a dimmed overlay over the entire screen (modal backdrop)
27
- // Using very dark gray instead of pure black for better dimming effect
28
- let overlay = Block::default()
29
- .style(Style::default().bg(Color::Rgb(10, 10, 15)));
30
- f.render_widget(overlay, area);
26
+ // Render full-screen background (no dimming - this is the only screen)
27
+ let bg = Block::default()
28
+ .style(Style::default().bg(BG_PANEL));
29
+ f.render_widget(bg, area);
31
30
 
32
31
  // Center the portal dialog - larger for better visibility
33
32
  let portal_width = area.width.min(110).max(85);
@@ -42,7 +41,7 @@ pub fn render(f: &mut Frame, state: &AppState) {
42
41
  height: portal_height,
43
42
  };
44
43
 
45
- // Clear the portal area again for clean rendering
44
+ // Clear the portal area for clean rendering
46
45
  f.render_widget(Clear, portal_area);
47
46
 
48
47
  // Split portal into sections
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.16",
3
+ "version": "2.9.17",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.16: Portal complete isolation (pauses base screen state updates). v2.9.14: Portal standalone rendering. v2.9.13: Instant typing (0ms latency), 120 FPS animations. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.17: Portals are now standalone base screens (not overlays) - complete isolation, no background OS, terminal cleared on switch. v2.9.16: Portal complete isolation (pauses base screen state updates). v2.9.14: Portal standalone rendering. ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",