shell-mirror 1.5.131 → 1.5.138

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.131",
3
+ "version": "1.5.138",
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": {
@@ -0,0 +1,7 @@
1
+ # Clean URL rewrites for marketing pages
2
+ RewriteEngine On
3
+ RewriteCond %{REQUEST_FILENAME} !-f
4
+ RewriteCond %{REQUEST_FILENAME} !-d
5
+ RewriteRule ^how-it-works$ /how-it-works.html [L]
6
+ RewriteRule ^privacy$ /privacy.html [L]
7
+ RewriteRule ^contact$ /contact.html [L]
@@ -3,7 +3,8 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Shell Mirror Dashboard</title>
6
+ <link rel="icon" type="image/png" href="/images/favicon.png">
7
+ <title>>shell-mirror Dashboard</title>
7
8
 
8
9
  <!-- Google Analytics 4 -->
9
10
  <script>
@@ -59,7 +60,7 @@
59
60
  <div class="header-content">
60
61
  <div class="logo">
61
62
  <a href="/" style="text-decoration: none; color: inherit;">
62
- <h1>Shell Mirror</h1>
63
+ <h1>>shell-mirror</h1>
63
64
  </a>
64
65
  </div>
65
66
  <div class="header-right">
@@ -98,7 +99,7 @@
98
99
 
99
100
  <!-- Version Footer -->
100
101
  <footer id="version-footer" style="background: var(--bg-secondary, #141519); color: var(--text-secondary, #8a8f98); text-align: center; padding: 8px 0; font-size: 0.75rem; position: fixed; bottom: 0; left: 0; right: 0; z-index: 1000; border-top: 1px solid var(--border, #2a2b30);">
101
- <p id="dashboard-version-info" style="margin: 0;">Shell Mirror • Loading...</p>
102
+ <p id="dashboard-version-info" style="margin: 0;">>shell-mirror • Loading...</p>
102
103
  </footer>
103
104
 
104
105
  <script src="dashboard.js"></script>
@@ -56,6 +56,10 @@ class ShellMirrorDashboard {
56
56
  if (authStatus.isAuthenticated) {
57
57
  this.isAuthenticated = true;
58
58
  this.user = authStatus.user;
59
+ // Track successful sign-in arrival (key event for funnel)
60
+ if (typeof sendGAEvent === 'function') {
61
+ sendGAEvent('sign_in_complete', { method: 'google' });
62
+ }
59
63
  await this.loadDashboardData();
60
64
  this.renderAuthenticatedDashboard();
61
65
  this.updateLastRefreshTime();
@@ -3,6 +3,7 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" type="image/png" href="/images/favicon.png">
6
7
  <title>Terminal Mirror</title>
7
8
 
8
9
  <!-- Google Analytics 4 -->
@@ -549,36 +550,36 @@
549
550
  <!-- Floating Buttons - Mobile CLI Shortcuts -->
550
551
  <div id="floating-buttons" class="floating-buttons">
551
552
  <!-- Toggle button (collapse/expand) -->
552
- <button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts">
553
+ <button class="fab-toggle" id="fabToggle" aria-label="Toggle keyboard shortcuts" tabindex="-1">
553
554
  <span class="fab-toggle-icon">⌨</span>
554
555
  </button>
555
556
 
556
557
  <!-- Left scroll indicator -->
557
- <button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left">
558
+ <button class="fab-scroll fab-scroll-left hidden" id="fabScrollLeft" aria-label="Scroll left" tabindex="-1">
558
559
  <span>‹</span>
559
560
  </button>
560
561
 
561
562
  <!-- Scrollable button strip -->
562
563
  <div class="fab-strip" id="fabStrip">
563
564
  <!-- Control group (scroll left to reveal) -->
564
- <button class="fab-btn" data-keys="ctrl+l" title="Clear screen">^L</button>
565
- <button class="fab-btn" data-keys="ctrl+d" title="EOF/Exit">^D</button>
566
- <button class="fab-btn" data-keys="ctrl+c" title="Interrupt">^C</button>
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>
567
568
 
568
569
  <!-- Primary group (default visible - AI CLI essentials) -->
569
- <button class="fab-btn fab-primary" data-keys="shift+tab" title="Mode: Normal → Auto → Plan">⇧Tab</button>
570
- <button class="fab-btn" data-keys="tab" title="Autocomplete / Thinking">Tab</button>
571
- <button class="fab-btn" data-keys="up" data-repeat="true" title="History up">↑</button>
572
- <button class="fab-btn" data-keys="down" data-repeat="true" title="History down">↓</button>
573
- <button class="fab-btn" data-keys="escape" title="Cancel / Rewind (2x)">Esc</button>
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>
574
575
 
575
576
  <!-- Navigation group (scroll right to reveal) -->
576
- <button class="fab-btn" data-keys="left" title="Cursor left">←</button>
577
- <button class="fab-btn" data-keys="right" title="Cursor right">→</button>
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>
578
579
  </div>
579
580
 
580
581
  <!-- Right scroll indicator -->
581
- <button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right">
582
+ <button class="fab-scroll fab-scroll-right" id="fabScrollRight" aria-label="Scroll right" tabindex="-1">
582
583
  <span>›</span>
583
584
  </button>
584
585
  </div>
@@ -1464,30 +1464,42 @@ function initFloatingButtons() {
1464
1464
 
1465
1465
  console.log('[CLIENT] ⌨️ Initializing floating buttons for mobile CLI shortcuts');
1466
1466
 
1467
- // Toggle collapse/expand
1468
- toggle.addEventListener('click', () => {
1467
+ // Toggle collapse/expand - preserve focus
1468
+ const handleToggle = (e) => {
1469
+ e.preventDefault();
1469
1470
  container.classList.toggle('collapsed');
1470
1471
  localStorage.setItem('fab-collapsed', container.classList.contains('collapsed'));
1471
- });
1472
+ setTimeout(() => term.focus(), 0);
1473
+ };
1474
+ toggle.addEventListener('touchstart', handleToggle, { passive: false });
1475
+ toggle.addEventListener('click', handleToggle);
1472
1476
 
1473
1477
  // Restore saved collapsed state
1474
1478
  if (localStorage.getItem('fab-collapsed') === 'true') {
1475
1479
  container.classList.add('collapsed');
1476
1480
  }
1477
1481
 
1478
- // Scroll indicator click handlers
1482
+ // Scroll indicator click handlers - preserve focus
1479
1483
  const scrollAmount = 150; // pixels per click
1480
1484
 
1481
1485
  if (scrollLeft) {
1482
- scrollLeft.addEventListener('click', () => {
1486
+ const handleScrollLeft = (e) => {
1487
+ e.preventDefault();
1483
1488
  strip.scrollBy({ left: -scrollAmount, behavior: 'smooth' });
1484
- });
1489
+ setTimeout(() => term.focus(), 0);
1490
+ };
1491
+ scrollLeft.addEventListener('touchstart', handleScrollLeft, { passive: false });
1492
+ scrollLeft.addEventListener('click', handleScrollLeft);
1485
1493
  }
1486
1494
 
1487
1495
  if (scrollRight) {
1488
- scrollRight.addEventListener('click', () => {
1496
+ const handleScrollRight = (e) => {
1497
+ e.preventDefault();
1489
1498
  strip.scrollBy({ left: scrollAmount, behavior: 'smooth' });
1490
- });
1499
+ setTimeout(() => term.focus(), 0);
1500
+ };
1501
+ scrollRight.addEventListener('touchstart', handleScrollRight, { passive: false });
1502
+ scrollRight.addEventListener('click', handleScrollRight);
1491
1503
  }
1492
1504
 
1493
1505
  // Update scroll indicator visibility based on scroll position
@@ -1504,12 +1516,24 @@ function initFloatingButtons() {
1504
1516
  strip.addEventListener('scroll', updateScrollIndicators);
1505
1517
 
1506
1518
  // Button click handlers - send key sequences to terminal
1507
- document.querySelectorAll('.fab-btn').forEach(btn => {
1519
+ // Use touchstart with preventDefault to prevent focus loss on mobile
1520
+ document.querySelectorAll('.fab-btn:not([data-repeat])').forEach(btn => {
1521
+ // Touch handler (mobile) - prevent focus loss
1522
+ btn.addEventListener('touchstart', (e) => {
1523
+ e.preventDefault(); // Prevents focus shift and keyboard dismiss
1524
+ const keys = btn.dataset.keys;
1525
+ if (keys && fabKeyMap[keys]) {
1526
+ sendFabKey(fabKeyMap[keys]);
1527
+ }
1528
+ // Re-focus terminal after a tiny delay to ensure keyboard stays up
1529
+ setTimeout(() => term.focus(), 0);
1530
+ }, { passive: false });
1531
+
1532
+ // Click handler (desktop fallback)
1508
1533
  btn.addEventListener('click', (e) => {
1509
1534
  e.preventDefault();
1510
1535
  const keys = btn.dataset.keys;
1511
1536
  if (keys && fabKeyMap[keys]) {
1512
- // Send the key sequence through the terminal input handler
1513
1537
  sendFabKey(fabKeyMap[keys]);
1514
1538
  term.focus();
1515
1539
  }
@@ -1557,7 +1581,6 @@ function initFabLongPress() {
1557
1581
 
1558
1582
  // First key immediately
1559
1583
  sendFabKey(fabKeyMap[keys]);
1560
- term.focus();
1561
1584
 
1562
1585
  // Then repeat after delay
1563
1586
  timeout = setTimeout(() => {
@@ -1572,13 +1595,15 @@ function initFabLongPress() {
1572
1595
  if (interval) clearInterval(interval);
1573
1596
  timeout = null;
1574
1597
  interval = null;
1598
+ // Re-focus terminal when touch ends
1599
+ setTimeout(() => term.focus(), 0);
1575
1600
  };
1576
1601
 
1577
- // Touch events for mobile
1602
+ // Touch events for mobile - prevent focus loss
1578
1603
  btn.addEventListener('touchstart', (e) => {
1579
- e.preventDefault();
1604
+ e.preventDefault(); // Prevents focus shift and keyboard dismiss
1580
1605
  startRepeat();
1581
- });
1606
+ }, { passive: false });
1582
1607
  btn.addEventListener('touchend', stopRepeat);
1583
1608
  btn.addEventListener('touchcancel', stopRepeat);
1584
1609