ai-or-die 0.1.44 → 0.1.45

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.
@@ -471,6 +471,42 @@ jobs:
471
471
  playwright-report/
472
472
  retention-days: 14
473
473
 
474
+ test-browser-mobile-journeys:
475
+ runs-on: ${{ matrix.os }}
476
+ needs: test
477
+ timeout-minutes: 12
478
+ strategy:
479
+ fail-fast: false
480
+ matrix:
481
+ os: [ubuntu-latest, windows-latest]
482
+ steps:
483
+ - uses: actions/checkout@v4
484
+ - uses: actions/setup-node@v4
485
+ with:
486
+ node-version: '22'
487
+ cache: 'npm'
488
+ - run: npm ci
489
+ - name: Cache Playwright browsers
490
+ uses: actions/cache@v4
491
+ with:
492
+ path: |
493
+ ~/.cache/ms-playwright
494
+ ~/AppData/Local/ms-playwright
495
+ key: playwright-${{ runner.os }}-${{ hashFiles('package-lock.json') }}
496
+ - name: Install Playwright browsers
497
+ run: npx playwright install chromium --with-deps
498
+ - name: Run mobile journey tests
499
+ run: npx playwright test --config e2e/playwright.config.js --project mobile-journeys
500
+ - name: Upload Playwright report
501
+ uses: actions/upload-artifact@v4
502
+ if: ${{ !cancelled() }}
503
+ with:
504
+ name: playwright-mobile-journeys-${{ matrix.os }}
505
+ path: |
506
+ e2e/test-results/
507
+ playwright-report/
508
+ retention-days: 14
509
+
474
510
  build-binary:
475
511
  runs-on: ${{ matrix.os }}
476
512
  timeout-minutes: 12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-or-die",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
5
5
  "main": "src/server.js",
6
6
  "bin": {
package/src/public/app.js CHANGED
@@ -674,11 +674,16 @@ class ClaudeCodeWebInterface {
674
674
  }
675
675
 
676
676
  _setupExtraKeys() {
677
- if (!this.isMobile || !window.visualViewport || typeof ExtraKeys === 'undefined') return;
677
+ if (!this.isMobile || typeof ExtraKeys === 'undefined') return;
678
678
 
679
679
  this.extraKeys = new ExtraKeys({ app: this });
680
680
  this._keyboardOpen = false;
681
681
 
682
+ // Browsers without visualViewport (Firefox Android, Samsung Internet):
683
+ // Initialize extra-keys but skip viewport-based keyboard detection.
684
+ // The extra-keys bar is still usable via manual show/hide.
685
+ if (!window.visualViewport) return;
686
+
682
687
  // Thrashing detection: if >3 resize events in 500ms, fall back to fixed threshold
683
688
  const resizeTimestamps = [];
684
689
  let useFallbackThreshold = false;
@@ -1477,6 +1482,9 @@ class ClaudeCodeWebInterface {
1477
1482
  if (this._reconnecting) return;
1478
1483
  this._reconnecting = true;
1479
1484
  this.disconnect();
1485
+ // Reset overlay flag so session_joined can show start prompt if terminal
1486
+ // exited during the disconnect (fixes stuck blank screen after reconnect)
1487
+ this._overlayExplicitlyHidden = false;
1480
1488
  // Reset flow control state so stale pause signals aren't sent on new connection
1481
1489
  this._outputPaused = false;
1482
1490
  this._pendingCallbacks = 0;
@@ -1494,7 +1502,10 @@ class ClaudeCodeWebInterface {
1494
1502
  }
1495
1503
  setTimeout(() => {
1496
1504
  try {
1497
- this.connect()
1505
+ const connectTimeout = new Promise((_, reject) =>
1506
+ setTimeout(() => reject(new Error('Connection timeout')), 10000)
1507
+ );
1508
+ Promise.race([this.connect(), connectTimeout])
1498
1509
  .catch(err => console.error('Reconnection failed:', err))
1499
1510
  .finally(() => { this._reconnecting = false; });
1500
1511
  } catch (err) {
@@ -3694,6 +3705,11 @@ class ClaudeCodeWebInterface {
3694
3705
  this.pendingJoinResolve = resolve;
3695
3706
  this.pendingJoinSessionId = sessionId;
3696
3707
 
3708
+ // Reset overlay flag before joining a new session so that
3709
+ // session_joined can correctly show/hide the overlay based on
3710
+ // the NEW session's state, not a stale flag from the previous tab.
3711
+ this._overlayExplicitlyHidden = false;
3712
+
3697
3713
  // Send the join request
3698
3714
  this.send({ type: 'join_session', sessionId });
3699
3715
 
@@ -4012,7 +4028,7 @@ class ClaudeCodeWebInterface {
4012
4028
  // Container is already visible by default
4013
4029
 
4014
4030
  // Check if mobile screen
4015
- const isMobile = window.innerWidth <= 768;
4031
+ const isMobile = window.innerWidth <= 820;
4016
4032
  const isSmallMobile = window.innerWidth <= 480;
4017
4033
 
4018
4034
  // Format tokens (K/M notation)
@@ -4152,7 +4168,7 @@ class ClaudeCodeWebInterface {
4152
4168
  if (totalTokens > 0) {
4153
4169
  const opusPercent = (opusTokens / totalTokens) * 100;
4154
4170
  const sonnetPercent = (sonnetTokens / totalTokens) * 100;
4155
- const isMobile = window.innerWidth <= 768;
4171
+ const isMobile = window.innerWidth <= 820;
4156
4172
 
4157
4173
  // Use short names on mobile, full names on desktop
4158
4174
  const opusName = isMobile ? 'O' : 'Opus';
@@ -4175,7 +4191,7 @@ class ClaudeCodeWebInterface {
4175
4191
  }
4176
4192
  } else {
4177
4193
  // No active session or expired session - show zeros
4178
- const isMobile = window.innerWidth <= 768;
4194
+ const isMobile = window.innerWidth <= 820;
4179
4195
 
4180
4196
  document.getElementById('usageTitle').textContent = '0h 0m';
4181
4197
  document.getElementById('usageTokens').textContent = isMobile ? '0%' : '0';
@@ -78,8 +78,8 @@
78
78
 
79
79
  @media (orientation: landscape) {
80
80
  .extra-key {
81
- min-height: 36px;
82
- min-width: 36px;
81
+ min-height: 44px;
82
+ min-width: 44px;
83
83
  font-size: var(--text-sm);
84
84
  }
85
85
  }
@@ -433,6 +433,8 @@
433
433
  .overflow-tab-close {
434
434
  width: 20px;
435
435
  height: 20px;
436
+ min-width: 44px;
437
+ min-height: 44px;
436
438
  display: flex;
437
439
  align-items: center;
438
440
  justify-content: center;
@@ -328,7 +328,7 @@
328
328
  padding: 0;
329
329
  border-width: 0;
330
330
  overflow: hidden;
331
- transition: opacity 0.2s ease, height 0.2s ease;
331
+ transition: opacity 0.2s ease, height 0.2s ease, padding 0.2s ease, border-width 0.2s ease;
332
332
  }
333
333
  body.keyboard-open .session-tabs-bar {
334
334
  height: 0;
@@ -336,7 +336,8 @@
336
336
  overflow: hidden;
337
337
  padding: 0;
338
338
  border-width: 0;
339
- transition: height 0.2s ease;
339
+ opacity: 0;
340
+ transition: opacity 0.2s ease, height 0.2s ease, padding 0.2s ease, border-width 0.2s ease;
340
341
  }
341
342
  body.keyboard-open #app {
342
343
  padding-bottom: 0 !important;
@@ -479,7 +479,7 @@ class SessionTabManager {
479
479
  }
480
480
 
481
481
  updateTabOverflow() {
482
- const isMobile = window.innerWidth <= 768;
482
+ const isMobile = window.innerWidth <= 820;
483
483
  const overflowWrapper = document.getElementById('tabOverflowWrapper');
484
484
  const overflowCount = document.querySelector('.tab-overflow-count');
485
485
 
@@ -647,7 +647,7 @@ class SessionTabManager {
647
647
  });
648
648
 
649
649
  // Reorder tabs based on the initial timestamps (mobile only)
650
- if (window.innerWidth <= 768) {
650
+ if (window.innerWidth <= 820) {
651
651
  this.reorderTabsByLastAccessed();
652
652
  }
653
653
 
@@ -808,7 +808,7 @@ class SessionTabManager {
808
808
  this.updateTabHistory(sessionId);
809
809
  }
810
810
 
811
- if (window.innerWidth <= 768) {
811
+ if (window.innerWidth <= 820) {
812
812
  const tabIndex = this.getOrderedTabIds().indexOf(sessionId);
813
813
  if (tabIndex >= 2) this.reorderTabsByLastAccessed();
814
814
  }
package/src/server.js CHANGED
@@ -126,6 +126,7 @@ class ClaudeCodeWebServer {
126
126
  for (const [id, session] of this.claudeSessions) {
127
127
  if (!session.active && session.connections.size === 0 && new Date(session.lastActivity || session.created).getTime() < sevenDaysAgo) {
128
128
  this.claudeSessions.delete(id);
129
+ this.activityBroadcastTimestamps.delete(id);
129
130
  this.sessionStore.markDirty();
130
131
  }
131
132
  }
@@ -1795,6 +1796,7 @@ class ClaudeCodeWebServer {
1795
1796
  currentSession.agent = null;
1796
1797
  this.sessionStore.markDirty();
1797
1798
  }
1799
+ this.activityBroadcastTimestamps.delete(sessionId);
1798
1800
  this.broadcastToSession(sessionId, { type: 'error', message: error.message });
1799
1801
  this.broadcastSessionActivity(sessionId, 'session_error');
1800
1802
  },