shell-mirror 1.5.139 → 1.5.140

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shell-mirror",
3
- "version": "1.5.139",
3
+ "version": "1.5.140",
4
4
  "description": "Access your Mac shell from any device securely. Perfect for mobile coding with Claude Code CLI, Gemini CLI, and any shell tool.",
5
5
  "main": "server.js",
6
6
  "bin": {
@@ -2,7 +2,7 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-content">
6
6
  <link rel="icon" type="image/png" href="/images/favicon.png">
7
7
  <title>Terminal Mirror</title>
8
8
 
@@ -56,25 +56,33 @@
56
56
  <script src="https://cdn.jsdelivr.net/npm/xterm@4.15.0/lib/xterm.js"></script>
57
57
  <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script>
58
58
  <style>
59
- body, html {
60
- margin: 0;
61
- padding: 0;
62
- height: 100%;
63
- overflow: hidden;
64
- background-color: #1e1e1e;
65
- color: #ccc;
59
+ body, html {
60
+ margin: 0;
61
+ padding: 0;
62
+ height: 100%;
63
+ overflow: hidden;
64
+ background-color: #1e1e1e;
65
+ color: #ccc;
66
66
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
67
- }
68
- #terminal-container {
69
- display: none;
70
- height: 100%;
71
- width: 100%;
67
+ box-sizing: border-box;
68
+ /* JS sets --keyboard-inset on <html> via visualViewport to shrink
69
+ the body when the on-screen keyboard opens. The flex column inside
70
+ shrinks with it so the FAB rides above the keyboard. */
71
+ padding-bottom: var(--keyboard-inset, 0px);
72
+ transition: padding-bottom 0.15s ease-out;
73
+ }
74
+ #terminal-container {
75
+ display: none;
76
+ height: 100%;
77
+ width: 100%;
72
78
  background-color: #000000;
73
79
  }
74
-
80
+
75
81
  #terminal-container.show {
76
82
  display: flex;
77
83
  flex-direction: column;
84
+ min-height: 0;
85
+ overflow: hidden;
78
86
  }
79
87
 
80
88
  /* Session Header - Unified Design */
@@ -281,9 +289,11 @@
281
289
  #terminal {
282
290
  padding: 8px; /* Mac Terminal.app padding */
283
291
  background-color: #000000;
284
- height: calc(100% - 16px - 40px); /* Subtract session header height */
285
- width: calc(100% - 16px);
286
- flex: 1;
292
+ flex: 1 1 auto;
293
+ min-height: 0; /* critical: lets xterm shrink as the column shortens */
294
+ width: 100%;
295
+ box-sizing: border-box;
296
+ overflow: hidden;
287
297
  }
288
298
  #connect-container { padding: 2em; text-align: center; }
289
299
  #agent-id-input { font-size: 1.2em; padding: 8px; width: 400px; margin-bottom: 1em; }
@@ -376,14 +386,18 @@
376
386
  Floating Buttons - Mobile CLI Shortcuts
377
387
  ======================================== */
378
388
  .floating-buttons {
379
- position: fixed;
380
- bottom: 20px;
381
- left: 50%;
382
- transform: translateX(-50%);
389
+ /* In-flow flex child of #terminal-container. Layout (not the
390
+ compositor) decides where the strip sits, which avoids iOS Safari's
391
+ position:fixed + backdrop-filter freeze during keyboard transitions. */
392
+ position: relative;
393
+ align-self: center;
394
+ margin: 0 auto 8px;
395
+ padding-bottom: env(safe-area-inset-bottom, 0);
383
396
  z-index: 1000;
384
397
  display: flex;
385
398
  align-items: center;
386
399
  gap: 0;
400
+ flex-shrink: 0;
387
401
  }
388
402
 
389
403
  /* Toggle button (collapse/expand) */
@@ -400,6 +414,8 @@
400
414
  cursor: pointer;
401
415
  flex-shrink: 0;
402
416
  margin-right: 4px;
417
+ touch-action: manipulation;
418
+ -webkit-tap-highlight-color: transparent;
403
419
  }
404
420
 
405
421
  .fab-toggle:active {
@@ -422,6 +438,8 @@
422
438
  display: flex;
423
439
  align-items: center;
424
440
  justify-content: center;
441
+ touch-action: manipulation;
442
+ -webkit-tap-highlight-color: transparent;
425
443
  }
426
444
 
427
445
  .fab-scroll:active { opacity: 1; background: #22232a; }
@@ -486,6 +504,8 @@
486
504
  white-space: nowrap;
487
505
  cursor: pointer;
488
506
  transition: background 0.1s;
507
+ touch-action: manipulation;
508
+ -webkit-tap-highlight-color: transparent;
489
509
  }
490
510
 
491
511
  .fab-btn:active { background: #22232a; }
@@ -545,43 +565,43 @@
545
565
  </div>
546
566
  </div>
547
567
  <div id="terminal"></div>
548
- </div>
549
568
 
550
- <!-- Floating Buttons - Mobile CLI Shortcuts -->
551
- <div id="floating-buttons" class="floating-buttons">
552
- <!-- Toggle button (collapse/expand) -->
553
- <button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts" tabindex="-1">
554
- <span class="fab-toggle-icon">⌨</span>
555
- </button>
556
-
557
- <!-- Left scroll indicator -->
558
- <button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left" tabindex="-1">
559
- <span>‹</span>
560
- </button>
561
-
562
- <!-- Scrollable button strip -->
563
- <div class="fab-strip" id="fabStrip">
564
- <!-- Control group (scroll left to reveal) -->
565
- <button class="fab-btn" data-keys="ctrl+l" title="Clear screen" tabindex="-1">^L</button>
566
- <button class="fab-btn" data-keys="ctrl+d" title="EOF/Exit" tabindex="-1">^D</button>
567
- <button class="fab-btn" data-keys="ctrl+c" title="Interrupt" tabindex="-1">^C</button>
568
-
569
- <!-- Primary group (default visible - AI CLI essentials) -->
570
- <button class="fab-btn fab-primary" data-keys="shift+tab" title="Mode: Normal → Auto → Plan" tabindex="-1">⇧Tab</button>
571
- <button class="fab-btn" data-keys="tab" title="Autocomplete / Thinking" tabindex="-1">Tab</button>
572
- <button class="fab-btn" data-keys="up" data-repeat="true" title="History up" tabindex="-1">↑</button>
573
- <button class="fab-btn" data-keys="down" data-repeat="true" title="History down" tabindex="-1">↓</button>
574
- <button class="fab-btn" data-keys="escape" title="Cancel / Rewind (2x)" tabindex="-1">Esc</button>
575
-
576
- <!-- Navigation group (scroll right to reveal) -->
577
- <button class="fab-btn" data-keys="left" title="Cursor left" tabindex="-1">←</button>
578
- <button class="fab-btn" data-keys="right" title="Cursor right" tabindex="-1">→</button>
579
- </div>
580
-
581
- <!-- Right scroll indicator -->
582
- <button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right" tabindex="-1">
583
- <span>›</span>
584
- </button>
569
+ <!-- Floating Buttons - Mobile CLI Shortcuts (flex child so it reserves layout space) -->
570
+ <div id="floating-buttons" class="floating-buttons">
571
+ <!-- Toggle button (collapse/expand) -->
572
+ <button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts" tabindex="-1">
573
+ <span class="fab-toggle-icon">⌨</span>
574
+ </button>
575
+
576
+ <!-- Left scroll indicator -->
577
+ <button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left" tabindex="-1">
578
+ <span>‹</span>
579
+ </button>
580
+
581
+ <!-- Scrollable button strip -->
582
+ <div class="fab-strip" id="fabStrip">
583
+ <!-- Control group (scroll left to reveal) -->
584
+ <button class="fab-btn" data-keys="ctrl+l" title="Clear screen" tabindex="-1">^L</button>
585
+ <button class="fab-btn" data-keys="ctrl+d" title="EOF/Exit" tabindex="-1">^D</button>
586
+ <button class="fab-btn" data-keys="ctrl+c" title="Interrupt" tabindex="-1">^C</button>
587
+
588
+ <!-- Primary group (default visible - AI CLI essentials) -->
589
+ <button class="fab-btn fab-primary" data-keys="shift+tab" title="Mode: Normal → Auto → Plan" tabindex="-1">⇧Tab</button>
590
+ <button class="fab-btn" data-keys="tab" title="Autocomplete / Thinking" tabindex="-1">Tab</button>
591
+ <button class="fab-btn" data-keys="up" data-repeat="true" title="History up" tabindex="-1">↑</button>
592
+ <button class="fab-btn" data-keys="down" data-repeat="true" title="History down" tabindex="-1">↓</button>
593
+ <button class="fab-btn" data-keys="escape" title="Cancel / Rewind (2x)" tabindex="-1">Esc</button>
594
+
595
+ <!-- Navigation group (scroll right to reveal) -->
596
+ <button class="fab-btn" data-keys="left" title="Cursor left" tabindex="-1">←</button>
597
+ <button class="fab-btn" data-keys="right" title="Cursor right" tabindex="-1">→</button>
598
+ </div>
599
+
600
+ <!-- Right scroll indicator -->
601
+ <button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right" tabindex="-1">
602
+ <span>›</span>
603
+ </button>
604
+ </div>
585
605
  </div>
586
606
 
587
607
  <!-- Help Modal (Dark Kraken Style) -->
@@ -996,9 +996,7 @@ function setupDataChannel() {
996
996
  }
997
997
  });
998
998
 
999
- window.addEventListener('resize', () => {
1000
- fitAddon.fit();
1001
- });
999
+ window.addEventListener('resize', scheduleFit);
1002
1000
 
1003
1001
  term.onResize(({ cols, rows }) => {
1004
1002
  if (dataChannel && dataChannel.readyState === 'open') {
@@ -1543,6 +1541,11 @@ function initFloatingButtons() {
1543
1541
  // Long-press repeat for arrow keys
1544
1542
  initFabLongPress();
1545
1543
 
1544
+ // Drive body padding-bottom from the visualViewport keyboard inset so the
1545
+ // flex column shrinks when the iOS keyboard opens. The FAB is a flex child
1546
+ // and rides up with the column — no fixed-positioning, no transform.
1547
+ initKeyboardInsetTracking();
1548
+
1546
1549
  // Scroll to show primary buttons (⇧Tab visible on left)
1547
1550
  setTimeout(() => {
1548
1551
  const modeBtn = strip.querySelector('[data-keys="shift+tab"]');
@@ -1555,6 +1558,43 @@ function initFloatingButtons() {
1555
1558
  console.log('[CLIENT] ⌨️ Floating buttons initialized');
1556
1559
  }
1557
1560
 
1561
+ // rAF-throttled fit so window.resize, visualViewport, and orientationchange
1562
+ // don't trigger duplicate measurement passes within the same frame.
1563
+ let fitPending = false;
1564
+ function scheduleFit() {
1565
+ if (fitPending) return;
1566
+ fitPending = true;
1567
+ requestAnimationFrame(() => {
1568
+ fitPending = false;
1569
+ try { fitAddon.fit(); } catch (_) { /* container may be 0×0 mid-bootstrap */ }
1570
+ });
1571
+ }
1572
+
1573
+ // Track on-screen keyboard size via visualViewport, expose it as
1574
+ // --keyboard-inset on <html> so body { padding-bottom } shrinks the flex column.
1575
+ function initKeyboardInsetTracking() {
1576
+ const vv = window.visualViewport;
1577
+ if (!vv) return; // older browsers — body stays full-height (acceptable on desktop)
1578
+
1579
+ let rafPending = false;
1580
+ const update = () => {
1581
+ rafPending = false;
1582
+ const inset = Math.max(0, window.innerHeight - (vv.height + vv.offsetTop));
1583
+ document.documentElement.style.setProperty('--keyboard-inset', `${inset}px`);
1584
+ scheduleFit();
1585
+ };
1586
+ const schedule = () => {
1587
+ if (rafPending) return;
1588
+ rafPending = true;
1589
+ requestAnimationFrame(update);
1590
+ };
1591
+
1592
+ vv.addEventListener('resize', schedule);
1593
+ vv.addEventListener('scroll', schedule); // QuickType bar slide-in fires scroll on some iOS builds
1594
+ window.addEventListener('orientationchange', schedule);
1595
+ update(); // initial state
1596
+ }
1597
+
1558
1598
  // Send key sequence through the appropriate connection
1559
1599
  function sendFabKey(keySequence) {
1560
1600
  // Use the same logic as term.onData - send through WebRTC or direct WS