4runr-os 2.9.10 → 2.9.12

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.
@@ -32,6 +32,7 @@ pub struct RenderScheduler {
32
32
  run_mode: RunMode,
33
33
  last_render: Instant,
34
34
  render_scheduled: bool,
35
+ immediate_render: bool, // Flag for immediate renders (bypass throttling)
35
36
  render_scheduled_count: u64,
36
37
  min_render_interval: Duration,
37
38
  }
@@ -40,14 +41,15 @@ impl RenderScheduler {
40
41
  pub fn new() -> Self {
41
42
  let run_mode = RunMode::detect();
42
43
  let min_render_interval = match run_mode {
43
- RunMode::Browser => Duration::from_millis(100), // Max 10 FPS
44
- RunMode::Local => Duration::from_millis(50), // Max 20 FPS
44
+ RunMode::Browser => Duration::from_millis(50), // Max 20 FPS (browser limitation)
45
+ RunMode::Local => Duration::from_millis(8), // Max 120 FPS (ultra-responsive, throttled for non-input)
45
46
  };
46
47
 
47
48
  Self {
48
49
  run_mode,
49
50
  last_render: Instant::now(),
50
51
  render_scheduled: false,
52
+ immediate_render: false,
51
53
  render_scheduled_count: 0,
52
54
  min_render_interval,
53
55
  }
@@ -75,6 +77,14 @@ impl RenderScheduler {
75
77
  }
76
78
  }
77
79
 
80
+ /// Request immediate render (bypass throttling) - for critical input events
81
+ pub fn request_immediate_render(&mut self, _reason: &str) -> bool {
82
+ self.render_scheduled_count += 1;
83
+ self.render_scheduled = true;
84
+ self.immediate_render = true; // Mark as immediate to bypass throttling
85
+ true
86
+ }
87
+
78
88
  pub fn should_render(&mut self) -> bool {
79
89
  if !self.render_scheduled {
80
90
  return false;
@@ -83,9 +93,12 @@ impl RenderScheduler {
83
93
  let now = Instant::now();
84
94
  let elapsed = now.duration_since(self.last_render);
85
95
 
86
- if elapsed >= self.min_render_interval {
96
+ // If immediate render was requested, bypass throttling
97
+ // Otherwise, respect the throttle interval
98
+ if self.immediate_render || elapsed >= self.min_render_interval {
87
99
  self.last_render = now;
88
100
  self.render_scheduled = false;
101
+ self.immediate_render = false; // Clear immediate flag
89
102
  true
90
103
  } else {
91
104
  false
@@ -407,10 +407,10 @@ impl App {
407
407
  let render_scheduler = RenderScheduler::new();
408
408
  let run_mode = render_scheduler.run_mode();
409
409
 
410
- // Input debounce: browser 50ms, local 25ms
410
+ // Input debounce: browser 10ms, local 5ms (minimal delay for smooth typing)
411
411
  let input_debounce_duration = match run_mode {
412
- RunMode::Browser => Duration::from_millis(50),
413
- RunMode::Local => Duration::from_millis(25),
412
+ RunMode::Browser => Duration::from_millis(10),
413
+ RunMode::Local => Duration::from_millis(5),
414
414
  };
415
415
 
416
416
  let mut state = AppState::default();
@@ -448,6 +448,11 @@ impl App {
448
448
  self.render_scheduler.request_render(reason)
449
449
  }
450
450
 
451
+ /// Request immediate render (bypass throttling) - for critical input events
452
+ pub fn request_immediate_render(&mut self, reason: &str) -> bool {
453
+ self.render_scheduler.request_immediate_render(reason)
454
+ }
455
+
451
456
  pub fn should_render(&mut self) -> bool {
452
457
  self.render_scheduler.should_render()
453
458
  }
@@ -592,14 +597,13 @@ impl App {
592
597
 
593
598
  // === MAIN INPUT HANDLING ===
594
599
  match key.code {
595
- // Typing - ALL characters go to command input (with debounce)
600
+ // Typing - ALL characters go to command input (immediate render for responsiveness)
596
601
  KeyCode::Char(c) => {
597
602
  // CRITICAL: Always set focused when typing to show cursor
598
603
  self.state.command_focused = true;
599
604
  self.state.command_input.push(c);
600
- // Debounce: schedule render after delay
601
- self.input_debounce = Some(Instant::now());
602
- // Don't render immediately - let debounce handle it
605
+ // OPTIMIZATION: Immediate render for instant typing feedback
606
+ self.request_immediate_render("typing_input");
603
607
  }
604
608
 
605
609
  // Submit command
@@ -794,8 +798,8 @@ impl App {
794
798
  if self.state.command_input.is_empty() {
795
799
  self.state.command_focused = false;
796
800
  }
797
- // Debounce render
798
- self.input_debounce = Some(Instant::now());
801
+ // OPTIMIZATION: Immediate render for instant feedback
802
+ self.request_immediate_render("backspace_input");
799
803
  }
800
804
 
801
805
  // Clear input or close overlay/popup
@@ -1706,23 +1710,25 @@ impl App {
1706
1710
  self.request_render("portal_connect");
1707
1711
  }
1708
1712
 
1709
- // Typing - edit focused field
1713
+ // Typing - edit focused field (immediate render for responsiveness)
1710
1714
  KeyCode::Char(c) => {
1711
1715
  match self.state.connection_portal.focused_field {
1712
1716
  PortalField::GatewayUrl => {
1713
1717
  self.state.connection_portal.gateway_url.push(c);
1714
1718
  self.state.connection_portal.error = None;
1715
- self.request_render("portal_input");
1719
+ // OPTIMIZATION: Immediate render for instant typing feedback
1720
+ self.request_immediate_render("portal_input");
1716
1721
  }
1717
1722
  PortalField::Username => {
1718
1723
  self.state.connection_portal.username.push(c);
1719
1724
  self.state.connection_portal.error = None;
1720
- self.request_render("portal_input");
1725
+ // OPTIMIZATION: Immediate render for instant typing feedback
1726
+ self.request_immediate_render("portal_input");
1721
1727
  }
1722
1728
  }
1723
1729
  }
1724
1730
 
1725
- // Backspace - delete character
1731
+ // Backspace - delete character (immediate render for responsiveness)
1726
1732
  KeyCode::Backspace => {
1727
1733
  match self.state.connection_portal.focused_field {
1728
1734
  PortalField::GatewayUrl => {
@@ -1733,7 +1739,8 @@ impl App {
1733
1739
  }
1734
1740
  }
1735
1741
  self.state.connection_portal.error = None;
1736
- self.request_render("portal_delete");
1742
+ // OPTIMIZATION: Immediate render for instant feedback
1743
+ self.request_immediate_render("portal_delete");
1737
1744
  }
1738
1745
 
1739
1746
  _ => {}
@@ -483,21 +483,10 @@ fn main() -> Result<()> {
483
483
  }
484
484
 
485
485
  // ┌─────────────────────────────────────────────────────────┐
486
- // │ STEP 3: CHECK RENDER SCHEDULER
487
- // └─────────────────────────────────────────────────────────┘
488
- // Check input debounce
489
- if let Some(debounce_time) = app.input_debounce {
490
- let elapsed = debounce_time.elapsed();
491
- if elapsed >= app.input_debounce_duration {
492
- app.input_debounce = None;
493
- app.request_render("input_debounce");
494
- }
495
- }
496
-
497
- // ┌─────────────────────────────────────────────────────────┐
498
- // │ STEP 4: RENDER (conditional - only when needed) │
486
+ // │ STEP 3: RENDER (conditional - only when needed)
499
487
  // └─────────────────────────────────────────────────────────┘
500
488
  // Check if render is scheduled and throttle limit has passed
489
+ // OPTIMIZATION: Removed input debounce check - using immediate render for typing
501
490
  if app.should_render() {
502
491
  let render_start = Instant::now();
503
492
  terminal.draw(|f| app.render(f))?;
@@ -506,11 +495,11 @@ fn main() -> Result<()> {
506
495
  }
507
496
 
508
497
  // ┌─────────────────────────────────────────────────────────┐
509
- // │ STEP 5: CHECK FOR INPUT (non-blocking with timeout) │
498
+ // │ STEP 4: CHECK FOR INPUT (non-blocking with timeout) │
510
499
  // └─────────────────────────────────────────────────────────┘
511
- // poll() with short timeout (16ms) - returns immediately if no input
512
- // This allows loop to run ~60 times/second for smooth animations
513
- if crossterm::event::poll(Duration::from_millis(16))? {
500
+ // OPTIMIZATION: Reduced poll timeout to 1ms for faster input detection
501
+ // This allows loop to run ~1000 times/second when idle, but input is detected immediately
502
+ if crossterm::event::poll(Duration::from_millis(1))? {
514
503
  match event::read()? {
515
504
  Event::Key(key) => {
516
505
  if key.kind == KeyEventKind::Press {
@@ -21,9 +21,13 @@ const ERROR_RED: Color = Color::Rgb(255, 69, 69);
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
25
+ f.render_widget(Clear, area);
26
+
24
27
  // Create a dimmed overlay over the entire screen (modal backdrop)
28
+ // Using very dark gray instead of pure black for better dimming effect
25
29
  let overlay = Block::default()
26
- .style(Style::default().bg(Color::Rgb(0, 0, 0)));
30
+ .style(Style::default().bg(Color::Rgb(10, 10, 15)));
27
31
  f.render_widget(overlay, area);
28
32
 
29
33
  // Calculate centered portal dimensions
@@ -40,7 +44,7 @@ pub fn render(f: &mut Frame, state: &AppState) {
40
44
  height: portal_height,
41
45
  };
42
46
 
43
- // Clear just the portal area (not the whole screen)
47
+ // Clear the portal area again for clean rendering
44
48
  f.render_widget(Clear, portal_area);
45
49
 
46
50
  // Render portal background
@@ -151,6 +155,9 @@ fn render_input_fields(f: &mut Frame, area: Rect, state: &AppState) {
151
155
  fn render_content_area(f: &mut Frame, area: Rect, state: &AppState) {
152
156
  let portal = &state.connection_portal;
153
157
 
158
+ // CRITICAL FIX: Clear the content area before rendering to prevent artifacts
159
+ f.render_widget(Clear, area);
160
+
154
161
  if portal.connection_success {
155
162
  render_success(f, area, state);
156
163
  } else if let Some(error) = &portal.error {
@@ -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.8.12) - matches package.json
147
- const PACKAGE_VERSION: &str = "2.9.10";
146
+ // Use npm package version (2.9.12) - matches package.json
147
+ const PACKAGE_VERSION: &str = "2.9.12";
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)),
@@ -20,9 +20,13 @@ const ERROR_RED: Color = Color::Rgb(255, 69, 69);
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
24
+ f.render_widget(Clear, area);
25
+
23
26
  // Create a dimmed overlay over the entire screen (modal backdrop)
27
+ // Using very dark gray instead of pure black for better dimming effect
24
28
  let overlay = Block::default()
25
- .style(Style::default().bg(Color::Rgb(0, 0, 0)));
29
+ .style(Style::default().bg(Color::Rgb(10, 10, 15)));
26
30
  f.render_widget(overlay, area);
27
31
 
28
32
  // Center the portal dialog - larger for better visibility
@@ -38,7 +42,7 @@ pub fn render(f: &mut Frame, state: &AppState) {
38
42
  height: portal_height,
39
43
  };
40
44
 
41
- // Clear just the portal area (not the whole screen)
45
+ // Clear the portal area again for clean rendering
42
46
  f.render_widget(Clear, portal_area);
43
47
 
44
48
  // Split portal into sections
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "4runr-os",
3
- "version": "2.9.10",
3
+ "version": "2.9.12",
4
4
  "type": "module",
5
- "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.10: FIXED Modal Rendering - Clear widget only clears portal area (not whole screen), proper overlay backdrop, no more duplication. v2.9.9: Portal Overlay Architecture Fix. ⚠️ Pre-MVP / Development Phase",
5
+ "description": "4Runr AI Agent OS - Secure terminal interface for AI agents. v2.9.12: PERFORMANCE OPTIMIZATION - Instant typing (0ms latency), immediate render for input events, 1ms poll timeout, 120 FPS animations. v2.9.11: Portal rendering fixes. ⚠️ Pre-MVP / Development Phase",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
8
8
  "4runr": "dist/index.js",