tabminal 3.0.4 → 3.0.5

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": "tabminal",
3
- "version": "3.0.4",
3
+ "version": "3.0.5",
4
4
  "description": "Tab(ter)minal, a Cloud-Native terminal and ACP agent workspace for desktop, tablet, and phone.",
5
5
  "type": "module",
6
6
  "bin": {
package/public/app.js CHANGED
@@ -10614,12 +10614,65 @@ function renderServerControls() {
10614
10614
 
10615
10615
  // #region Notification Manager
10616
10616
  const notificationManager = new NotificationManager();
10617
+ const APP_NOTIFICATION_QUIET_MS = 30_000;
10618
+ const APP_NOTIFICATION_IDLE_MS = 3 * 60 * 1000;
10619
+ let appNotificationQuietUntil = Date.now() + APP_NOTIFICATION_QUIET_MS;
10620
+ let lastAppInteractionAt = Date.now();
10621
+
10622
+ function noteAppInteraction() {
10623
+ lastAppInteractionAt = Date.now();
10624
+ }
10625
+
10626
+ function enterAppNotificationQuietPeriod(duration = APP_NOTIFICATION_QUIET_MS) {
10627
+ appNotificationQuietUntil = Math.max(
10628
+ appNotificationQuietUntil,
10629
+ Date.now() + duration
10630
+ );
10631
+ }
10632
+
10633
+ function shouldNotifyConnectionStatus() {
10634
+ if (Date.now() < appNotificationQuietUntil) {
10635
+ return false;
10636
+ }
10637
+ if (document.visibilityState !== 'visible') {
10638
+ return false;
10639
+ }
10640
+ if (typeof document.hasFocus === 'function' && !document.hasFocus()) {
10641
+ return false;
10642
+ }
10643
+ if ((Date.now() - lastAppInteractionAt) > APP_NOTIFICATION_IDLE_MS) {
10644
+ return false;
10645
+ }
10646
+ return true;
10647
+ }
10648
+
10649
+ document.addEventListener('pointerdown', noteAppInteraction, {
10650
+ capture: true,
10651
+ passive: true
10652
+ });
10653
+ document.addEventListener('touchstart', noteAppInteraction, {
10654
+ capture: true,
10655
+ passive: true
10656
+ });
10657
+ document.addEventListener('keydown', noteAppInteraction, {
10658
+ capture: true
10659
+ });
10660
+ window.addEventListener('focus', () => {
10661
+ noteAppInteraction();
10662
+ enterAppNotificationQuietPeriod();
10663
+ });
10664
+ window.addEventListener('pageshow', () => {
10665
+ noteAppInteraction();
10666
+ enterAppNotificationQuietPeriod();
10667
+ });
10617
10668
 
10618
10669
  document.addEventListener('click', () => {
10619
10670
  notificationManager.requestPermission();
10620
10671
  }, { once: true, capture: true });
10621
10672
  document.addEventListener('visibilitychange', () => {
10622
10673
  if (document.visibilityState === 'visible') {
10674
+ noteAppInteraction();
10675
+ enterAppNotificationQuietPeriod();
10623
10676
  clearVisibleAttentionState();
10624
10677
  }
10625
10678
  });
@@ -10661,18 +10714,20 @@ function setStatus(server, status) {
10661
10714
  statusMemory.set(server.id, status);
10662
10715
  server.connectionStatus = status;
10663
10716
  renderServerControls();
10664
-
10665
- const activeServer = getActiveServer();
10666
- if (!activeServer || activeServer.id !== server.id) return;
10667
10717
  const hostName = getDisplayHost(server);
10668
10718
  const target = hostName || 'host';
10719
+ const shouldNotify = shouldNotifyConnectionStatus();
10669
10720
 
10670
- if (status === 'reconnecting') {
10721
+ if (status === 'reconnecting' && shouldNotify) {
10671
10722
  alert(`Lost connection to ${target}. Reconnecting...`, {
10672
10723
  type: 'warning',
10673
10724
  title: 'Connection'
10674
10725
  });
10675
- } else if (status === 'connected' && prevStatus === 'reconnecting') {
10726
+ } else if (
10727
+ status === 'connected'
10728
+ && prevStatus === 'reconnecting'
10729
+ && shouldNotify
10730
+ ) {
10676
10731
  alert(`Connection to ${target} restored.`, {
10677
10732
  type: 'success',
10678
10733
  title: 'Connection'
@@ -10682,7 +10737,7 @@ function setStatus(server, status) {
10682
10737
  type: 'error',
10683
10738
  title: 'Connection'
10684
10739
  });
10685
- } else if (status === 'connected' && !prevStatus) {
10740
+ } else if (status === 'connected' && !prevStatus && shouldNotify) {
10686
10741
  alert(`Connected to ${target}.`, {
10687
10742
  type: 'success',
10688
10743
  title: 'Connection'
package/public/index.html CHANGED
@@ -14,15 +14,33 @@
14
14
  <link rel="preconnect" href="https://cdn.jsdelivr.net">
15
15
  <script>
16
16
  const COMPACT_WORKSPACE_MAX_WIDTH = 767;
17
- const COMPACT_WORKSPACE_MAX_SHORT_HEIGHT = 500;
18
17
  const COMPACT_WORKSPACE_MAX_LANDSCAPE_WIDTH = 950;
18
+ const COMPACT_WORKSPACE_ENTER_MAX_SHORT_HEIGHT = 500;
19
+ const COMPACT_WORKSPACE_EXIT_MAX_SHORT_HEIGHT = 540;
20
+ const SIDEBAR_LAYOUT_PAUSE_MS = 360;
21
+ let layoutLoopPausedUntil = 0;
19
22
 
20
- function shouldUseCompactWorkspaceMode(width, height) {
21
- return width <= COMPACT_WORKSPACE_MAX_WIDTH
22
- || (
23
- width <= COMPACT_WORKSPACE_MAX_LANDSCAPE_WIDTH
24
- && height <= COMPACT_WORKSPACE_MAX_SHORT_HEIGHT
25
- );
23
+ function shouldUseCompactWorkspaceMode(
24
+ width,
25
+ height,
26
+ previousCompactWorkspaceMode = false
27
+ ) {
28
+ if (width <= COMPACT_WORKSPACE_MAX_WIDTH) {
29
+ return true;
30
+ }
31
+
32
+ const isShortLandscapeCandidate = (
33
+ width > height
34
+ && width <= COMPACT_WORKSPACE_MAX_LANDSCAPE_WIDTH
35
+ );
36
+ if (!isShortLandscapeCandidate) {
37
+ return false;
38
+ }
39
+
40
+ const shortHeightThreshold = previousCompactWorkspaceMode
41
+ ? COMPACT_WORKSPACE_EXIT_MAX_SHORT_HEIGHT
42
+ : COMPACT_WORKSPACE_ENTER_MAX_SHORT_HEIGHT;
43
+ return height <= shortHeightThreshold;
26
44
  }
27
45
 
28
46
  // Mobile Layout Manager
@@ -46,12 +64,13 @@
46
64
  document.documentElement.style.setProperty('--app-top', `${offsetTop}px`);
47
65
  document.documentElement.style.setProperty('--app-left', `${offsetLeft}px`);
48
66
 
67
+ const previousCompactWorkspaceMode =
68
+ window.__tabminalCompactWorkspaceMode;
49
69
  const compactWorkspaceMode = shouldUseCompactWorkspaceMode(
50
70
  width,
51
- height
71
+ height,
72
+ previousCompactWorkspaceMode
52
73
  );
53
- const previousCompactWorkspaceMode =
54
- window.__tabminalCompactWorkspaceMode;
55
74
  window.__tabminalCompactWorkspaceMode = compactWorkspaceMode;
56
75
 
57
76
  if (document.body) {
@@ -94,6 +113,16 @@
94
113
  }
95
114
  }
96
115
 
116
+ function pauseLayoutLoop(duration = SIDEBAR_LAYOUT_PAUSE_MS) {
117
+ const now = performance.now();
118
+ layoutLoopPausedUntil = Math.max(
119
+ layoutLoopPausedUntil,
120
+ now + duration
121
+ );
122
+ }
123
+
124
+ window.__tabminalPauseLayoutLoop = pauseLayoutLoop;
125
+
97
126
  if (window.visualViewport) {
98
127
  window.visualViewport.addEventListener('resize', updateLayout);
99
128
  window.visualViewport.addEventListener('scroll', updateLayout);
@@ -103,13 +132,34 @@
103
132
 
104
133
  // High-performance Layout Loop (60fps)
105
134
  function layoutLoop() {
106
- updateLayout();
135
+ if (performance.now() >= layoutLoopPausedUntil) {
136
+ updateLayout();
137
+ }
107
138
  requestAnimationFrame(layoutLoop);
108
139
  }
109
140
  layoutLoop();
110
141
 
111
142
  // Initial triggers
112
143
  document.addEventListener('DOMContentLoaded', updateLayout);
144
+ document.addEventListener('DOMContentLoaded', () => {
145
+ const sidebar = document.getElementById('sidebar');
146
+ if (!sidebar) {
147
+ return;
148
+ }
149
+ let sidebarOpen = sidebar.classList.contains('open');
150
+ const observer = new MutationObserver(() => {
151
+ const isOpen = sidebar.classList.contains('open');
152
+ if (isOpen === sidebarOpen) {
153
+ return;
154
+ }
155
+ sidebarOpen = isOpen;
156
+ pauseLayoutLoop();
157
+ });
158
+ observer.observe(sidebar, {
159
+ attributes: true,
160
+ attributeFilter: ['class']
161
+ });
162
+ });
113
163
  window.addEventListener('load', updateLayout);
114
164
 
115
165
  // Enhanced Touch Interceptor with Boundary Check
package/public/styles.css CHANGED
@@ -2241,7 +2241,7 @@ kbd {
2241
2241
  min-width: 0;
2242
2242
  }
2243
2243
 
2244
- @media (max-width: 767px) {
2244
+ @media (max-width: 1408px) {
2245
2245
  .agent-panel-commands {
2246
2246
  display: none !important;
2247
2247
  }