codedash-app 3.0.0 → 3.0.2

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": "codedash-app",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "description": "Termius-style browser dashboard for Claude Code sessions. View, search, resume, and delete sessions with a dark-themed UI.",
5
5
  "bin": {
6
6
  "codedash": "./bin/cli.js"
@@ -209,19 +209,46 @@ async function pollActiveSessions() {
209
209
  activeSessions[a.sessionId] = a;
210
210
  }
211
211
  });
212
- // Update badges without full re-render
212
+ // Update badges + animated border wrappers
213
213
  document.querySelectorAll('.card').forEach(function(card) {
214
214
  var id = card.getAttribute('data-id');
215
+
216
+ // Remove old badge
215
217
  var existing = card.querySelector('.live-badge');
216
218
  if (existing) existing.remove();
219
+
220
+ // Remove old wrapper if session no longer active
221
+ var parent = card.parentElement;
222
+ if (parent && parent.classList.contains('card-live-wrap') && !activeSessions[id]) {
223
+ parent.replaceWith(card);
224
+ card.style.border = '';
225
+ return;
226
+ }
227
+
217
228
  if (activeSessions[id]) {
218
229
  var a = activeSessions[id];
230
+
231
+ // Add badge
219
232
  var badge = document.createElement('span');
220
233
  badge.className = 'live-badge live-' + a.status;
221
234
  badge.textContent = a.status === 'waiting' ? 'WAITING' : 'LIVE';
222
235
  badge.title = 'PID ' + a.pid + ' | CPU ' + a.cpu.toFixed(1) + '% | ' + a.memoryMB + 'MB';
223
236
  var top = card.querySelector('.card-top');
224
237
  if (top) top.insertBefore(badge, top.firstChild);
238
+
239
+ // Add animated border wrapper if not already wrapped
240
+ if (!parent || !parent.classList.contains('card-live-wrap')) {
241
+ var wrap = document.createElement('div');
242
+ wrap.className = 'card-live-wrap' + (a.status === 'waiting' ? ' live-waiting' : '');
243
+ wrap.style.setProperty('--live-color', a.status === 'waiting'
244
+ ? 'rgba(251, 191, 36, 0.5)'
245
+ : 'rgba(74, 222, 128, 0.7)');
246
+ var borderDiv = document.createElement('div');
247
+ borderDiv.className = 'live-border';
248
+ card.parentNode.insertBefore(wrap, card);
249
+ wrap.appendChild(borderDiv);
250
+ wrap.appendChild(card);
251
+ }
225
252
  }
226
253
  });
227
254
  } catch {}
@@ -1599,22 +1626,15 @@ function focusSession(sessionId) {
1599
1626
  var a = activeSessions[sessionId];
1600
1627
  if (!a) { showToast('Session not active'); return; }
1601
1628
 
1602
- // Use osascript via the launch API to focus the terminal window
1603
- var terminal = localStorage.getItem('codedash-terminal') || '';
1604
- fetch('/api/launch', {
1629
+ fetch('/api/focus', {
1605
1630
  method: 'POST',
1606
1631
  headers: { 'Content-Type': 'application/json' },
1607
- body: JSON.stringify({
1608
- sessionId: sessionId,
1609
- tool: a.kind === 'codex' ? 'codex' : 'claude',
1610
- flags: ['focus'],
1611
- project: a.cwd || '',
1612
- terminal: terminal,
1613
- })
1614
- }).then(function() {
1615
- showToast('Focused terminal');
1632
+ body: JSON.stringify({ pid: a.pid })
1633
+ }).then(function(r) { return r.json(); }).then(function(data) {
1634
+ if (data.ok) showToast('Switched to terminal (PID ' + a.pid + ')');
1635
+ else showToast('Could not focus — try clicking the terminal manually');
1616
1636
  }).catch(function() {
1617
- showToast('Could not focus terminal');
1637
+ showToast('Focus failed');
1618
1638
  });
1619
1639
  }
1620
1640
 
@@ -1437,50 +1437,41 @@ body {
1437
1437
  50% { opacity: 0.3; transform: scale(0.6); }
1438
1438
  }
1439
1439
 
1440
- /* Animated border for live sessions */
1441
- .card:has(.live-active),
1442
- .card:has(.live-waiting) {
1443
- border-color: transparent;
1440
+ /* Animated border wrapper for live sessions */
1441
+ .card-live-wrap {
1444
1442
  position: relative;
1445
- overflow: hidden;
1443
+ border-radius: 12px;
1444
+ padding: 2px;
1446
1445
  }
1447
1446
 
1448
- .card:has(.live-active)::before,
1449
- .card:has(.live-waiting)::before {
1450
- content: '';
1447
+ .card-live-wrap > .card {
1448
+ border: none;
1449
+ position: relative;
1450
+ z-index: 1;
1451
+ }
1452
+
1453
+ .card-live-wrap .live-border {
1451
1454
  position: absolute;
1452
- inset: -2px;
1455
+ inset: 0;
1453
1456
  border-radius: 12px;
1454
- z-index: -1;
1457
+ z-index: 0;
1455
1458
  background: conic-gradient(
1456
1459
  from var(--border-angle, 0deg),
1457
- transparent 40%,
1458
- var(--live-color) 50%,
1459
- transparent 60%
1460
+ transparent 35%,
1461
+ var(--live-color, rgba(74, 222, 128, 0.6)) 50%,
1462
+ transparent 65%
1460
1463
  );
1461
1464
  animation: border-spin 3s linear infinite;
1462
1465
  }
1463
1466
 
1464
- .card:has(.live-active)::after,
1465
- .card:has(.live-waiting)::after {
1466
- content: '';
1467
- position: absolute;
1468
- inset: 1px;
1469
- border-radius: 9px;
1470
- background: var(--bg-card);
1471
- z-index: -1;
1472
- }
1473
-
1474
- .card:has(.live-active) {
1475
- --live-color: rgba(74, 222, 128, 0.6);
1476
- }
1477
-
1478
- .card:has(.live-waiting) {
1479
- --live-color: rgba(251, 191, 36, 0.4);
1480
- }
1481
-
1482
- .card:has(.live-waiting)::before {
1467
+ .card-live-wrap.live-waiting .live-border {
1483
1468
  animation: none;
1469
+ background: conic-gradient(
1470
+ from 90deg,
1471
+ transparent 35%,
1472
+ var(--live-color, rgba(251, 191, 36, 0.4)) 50%,
1473
+ transparent 65%
1474
+ );
1484
1475
  }
1485
1476
 
1486
1477
  @keyframes border-spin {
@@ -1493,11 +1484,6 @@ body {
1493
1484
  inherits: false;
1494
1485
  }
1495
1486
 
1496
- [data-theme="light"] .card:has(.live-active)::after,
1497
- [data-theme="light"] .card:has(.live-waiting)::after {
1498
- background: #ffffff;
1499
- }
1500
-
1501
1487
  /* ── Card expand preview ────────────────────────────────────── */
1502
1488
 
1503
1489
  .card-expand-btn {
package/src/server.js CHANGED
@@ -4,7 +4,7 @@ const https = require('https');
4
4
  const { URL } = require('url');
5
5
  const { exec } = require('child_process');
6
6
  const { loadSessions, loadSessionDetail, deleteSession, getGitCommits, exportSessionMarkdown, getSessionPreview, searchFullText, getActiveSessions, getSessionReplay, getCostAnalytics } = require('./data');
7
- const { detectTerminals, openInTerminal } = require('./terminals');
7
+ const { detectTerminals, openInTerminal, focusTerminalByPid } = require('./terminals');
8
8
  const { getHTML } = require('./html');
9
9
 
10
10
  function startServer(port, openBrowser = true) {
@@ -110,6 +110,19 @@ function startServer(port, openBrowser = true) {
110
110
  json(res, active);
111
111
  }
112
112
 
113
+ // ── Focus terminal ──────────────────────
114
+ else if (req.method === 'POST' && pathname === '/api/focus') {
115
+ readBody(req, body => {
116
+ try {
117
+ const { pid } = JSON.parse(body);
118
+ const ok = focusTerminalByPid(pid);
119
+ json(res, { ok });
120
+ } catch (e) {
121
+ json(res, { ok: false, error: e.message }, 400);
122
+ }
123
+ });
124
+ }
125
+
113
126
  // ── Session preview ─────────────────────
114
127
  else if (req.method === 'GET' && pathname.startsWith('/api/preview/')) {
115
128
  const sessionId = pathname.split('/').pop();
package/src/terminals.js CHANGED
@@ -157,4 +157,53 @@ function openInTerminal(sessionId, tool, flags, projectDir, terminalId) {
157
157
  }
158
158
  }
159
159
 
160
- module.exports = { detectTerminals, openInTerminal };
160
+ // ── Focus existing terminal by PID ──────────────────────────
161
+
162
+ function focusTerminalByPid(pid) {
163
+ const platform = process.platform;
164
+
165
+ if (platform === 'darwin') {
166
+ // Find which terminal app owns this PID's TTY, then activate it
167
+ try {
168
+ // Get TTY of the process
169
+ const ttyOut = execSync(`ps -p ${pid} -o tty= 2>/dev/null`, { encoding: 'utf8' }).trim();
170
+ if (!ttyOut) throw new Error('no tty');
171
+
172
+ // Try iTerm2 first — activate and select session by tty
173
+ try {
174
+ const script = `
175
+ tell application "iTerm"
176
+ activate
177
+ repeat with w in windows
178
+ repeat with t in tabs of w
179
+ repeat with s in sessions of t
180
+ if tty of s contains "${ttyOut}" then
181
+ select t
182
+ return
183
+ end if
184
+ end repeat
185
+ end repeat
186
+ end repeat
187
+ end tell
188
+ `;
189
+ execSync(`osascript -e '${script.replace(/'/g, "'\\''")}'`, { stdio: 'pipe', timeout: 3000 });
190
+ return true;
191
+ } catch {}
192
+
193
+ // Fallback: just activate iTerm2 or Terminal.app
194
+ try {
195
+ execSync(`osascript -e 'tell application "iTerm" to activate'`, { stdio: 'pipe' });
196
+ return true;
197
+ } catch {}
198
+ try {
199
+ execSync(`osascript -e 'tell application "Terminal" to activate'`, { stdio: 'pipe' });
200
+ return true;
201
+ } catch {}
202
+ } catch {}
203
+ }
204
+
205
+ // Linux/other: not much we can do without window manager integration
206
+ return false;
207
+ }
208
+
209
+ module.exports = { detectTerminals, openInTerminal, focusTerminalByPid };