@jhizzard/termdeck 0.3.1 → 0.3.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": "@jhizzard/termdeck",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Browser-based terminal multiplexer with metadata overlays, panel flashback memory recall, and AI-aware session management",
5
5
  "bin": {
6
6
  "termdeck": "./packages/cli/src/index.js"
@@ -2403,29 +2403,43 @@
2403
2403
  document.getElementById('healthBadgeLabel').textContent = 'Health: offline';
2404
2404
  }
2405
2405
 
2406
+ // Tier 2/3 checks only shown when the user has configured those tiers.
2407
+ // Without DATABASE_URL, mnestra/rumen/database checks are irrelevant noise.
2408
+ const TIER1_CHECKS = new Set(['project_paths', 'shell_sanity']);
2409
+ const TIER23_CHECKS = new Set(['mnestra_reachable', 'mnestra_has_memories', 'rumen_recent', 'database_url']);
2410
+
2411
+ function filterChecksByTier(checks) {
2412
+ const hasDb = checks.some(c => c.name === 'database_url' && c.passed);
2413
+ if (hasDb) return checks; // full stack configured — show everything
2414
+ // No DATABASE_URL: only show Tier 1 checks
2415
+ return checks.filter(c => TIER1_CHECKS.has(c.name));
2416
+ }
2417
+
2406
2418
  function renderHealthBadge(data) {
2407
2419
  const badge = document.getElementById('healthBadge');
2408
2420
  if (!badge) return;
2409
2421
  badge.style.display = '';
2410
2422
 
2411
- const checks = data.checks || [];
2423
+ const allChecks = data.checks || [];
2424
+ const checks = filterChecksByTier(allChecks);
2412
2425
  const total = checks.length;
2413
- const passed = checks.filter(c => c.ok).length;
2426
+ const passed = checks.filter(c => c.passed).length;
2414
2427
  const allOk = passed === total && total > 0;
2428
+ const tierLabel = total < allChecks.length ? 'Tier 1' : 'Stack';
2415
2429
 
2416
2430
  if (allOk) {
2417
2431
  badge.className = 'health-badge hb-green';
2418
- document.getElementById('healthBadgeLabel').textContent = 'Stack: OK';
2432
+ document.getElementById('healthBadgeLabel').textContent = `${tierLabel}: OK`;
2419
2433
  } else if (total === 0) {
2420
2434
  badge.className = 'health-badge hb-amber';
2421
2435
  document.getElementById('healthBadgeLabel').textContent = 'Stack: ?';
2422
2436
  } else {
2423
2437
  badge.className = 'health-badge hb-red';
2424
- document.getElementById('healthBadgeLabel').textContent = `Stack: ${passed}/${total}`;
2438
+ document.getElementById('healthBadgeLabel').textContent = `${tierLabel}: ${passed}/${total}`;
2425
2439
  }
2426
2440
 
2427
- // Update dropdown content
2428
- renderHealthDropdown(data);
2441
+ // Update dropdown content — pass filtered checks
2442
+ renderHealthDropdown({ ...data, checks });
2429
2443
  }
2430
2444
 
2431
2445
  function renderHealthDropdown(data) {
@@ -2439,16 +2453,16 @@
2439
2453
 
2440
2454
  let html = '';
2441
2455
  for (const check of checks) {
2442
- const icon = check.ok ? '✓' : '✗';
2443
- const cls = check.ok ? 'hd-ok' : 'hd-fail';
2456
+ const icon = check.passed ? '✓' : '✗';
2457
+ const cls = check.passed ? 'hd-ok' : 'hd-fail';
2444
2458
  const name = check.name || 'Unknown';
2445
2459
  const detail = check.detail || '';
2446
- const remediation = check.ok ? '' : (check.remediation ? `<div class="hd-remediation">${escapeHtml(check.remediation)}</div>` : '');
2460
+ const remediation = check.passed ? '' : (check.remediation ? `<div class="hd-remediation">${escapeHtml(check.remediation)}</div>` : '');
2447
2461
  html += `<div class="hd-check ${cls}">
2448
2462
  <span class="hd-icon">${icon}</span>
2449
2463
  <span class="hd-name">${escapeHtml(name)}</span>
2450
2464
  <span class="hd-dots"></span>
2451
- <span class="hd-status">${check.ok ? 'OK' : 'FAIL'}</span>
2465
+ <span class="hd-status">${check.passed ? 'OK' : 'FAIL'}</span>
2452
2466
  <span class="hd-detail">${escapeHtml(detail)}</span>
2453
2467
  ${remediation}
2454
2468
  </div>`;
@@ -484,14 +484,14 @@ function createServer(config) {
484
484
  // GET /api/transcripts/search - FTS across all sessions
485
485
  // (Must be registered before :sessionId to avoid route collision)
486
486
  app.get('/api/transcripts/search', async (req, res) => {
487
- if (!transcriptWriter) return res.json([]);
487
+ if (!transcriptWriter) return res.json({ results: [] });
488
488
  const q = req.query.q;
489
489
  if (!q) return res.status(400).json({ error: 'Missing q parameter' });
490
490
  const since = req.query.since || null;
491
491
  const limit = Math.min(Math.max(parseInt(req.query.limit) || 50, 1), 200);
492
492
  try {
493
493
  const results = await transcriptWriter.search(q, { since, limit });
494
- res.json(results);
494
+ res.json({ results });
495
495
  } catch (err) {
496
496
  console.error('[transcript] search endpoint error:', err.message);
497
497
  res.status(500).json({ error: 'Transcript search failed' });
@@ -499,13 +499,24 @@ function createServer(config) {
499
499
  });
500
500
 
501
501
  // GET /api/transcripts/recent - time-windowed crash recovery
502
+ // Returns { sessions: [ { session_id, chunks: [...] }, ... ] }
502
503
  app.get('/api/transcripts/recent', async (req, res) => {
503
- if (!transcriptWriter) return res.json([]);
504
+ if (!transcriptWriter) return res.json({ sessions: [] });
504
505
  const minutes = Math.min(Math.max(parseInt(req.query.minutes) || 60, 1), 1440);
505
506
  const limit = Math.min(Math.max(parseInt(req.query.limit) || 500, 1), 2000);
506
507
  try {
507
- const results = await transcriptWriter.getRecent(minutes, limit);
508
- res.json(results);
508
+ const rows = await transcriptWriter.getRecent(minutes, limit);
509
+ // Group by session_id for client consumption
510
+ const grouped = new Map();
511
+ for (const row of rows) {
512
+ if (!grouped.has(row.session_id)) grouped.set(row.session_id, []);
513
+ grouped.get(row.session_id).push(row);
514
+ }
515
+ const sessions = [];
516
+ for (const [session_id, chunks] of grouped) {
517
+ sessions.push({ session_id, chunks });
518
+ }
519
+ res.json({ sessions });
509
520
  } catch (err) {
510
521
  console.error('[transcript] recent endpoint error:', err.message);
511
522
  res.status(500).json({ error: 'Transcript recent query failed' });
@@ -513,13 +524,16 @@ function createServer(config) {
513
524
  });
514
525
 
515
526
  // GET /api/transcripts/:sessionId - ordered chunks for a session
527
+ // Returns { content: string } (joined transcript text)
516
528
  app.get('/api/transcripts/:sessionId', async (req, res) => {
517
- if (!transcriptWriter) return res.json([]);
529
+ if (!transcriptWriter) return res.json({ content: '', lines: [] });
518
530
  const limit = req.query.limit ? Math.min(Math.max(parseInt(req.query.limit), 1), 5000) : undefined;
519
531
  const since = req.query.since || undefined;
520
532
  try {
521
533
  const chunks = await transcriptWriter.getSessionTranscript(req.params.sessionId, { limit, since });
522
- res.json(chunks);
534
+ const lines = chunks.map(c => c.content);
535
+ const content = lines.join('');
536
+ res.json({ content, lines, chunks });
523
537
  } catch (err) {
524
538
  console.error('[transcript] session transcript endpoint error:', err.message);
525
539
  res.status(500).json({ error: 'Transcript retrieval failed' });
@@ -165,8 +165,8 @@ class RAGIntegration {
165
165
  // Success — reset any accumulated 404 count for this table
166
166
  this._resetCircuit(table);
167
167
  } catch (err) {
168
- // Will be retried by sync loop
169
168
  console.error('[mnestra] Push failed:', err.message);
169
+ throw err; // Propagate to caller so sync loop knows this event failed
170
170
  }
171
171
  }
172
172
 
@@ -28,6 +28,7 @@ class TranscriptWriter {
28
28
  this._databaseUrl = databaseUrl;
29
29
  this._batchSize = options.batchSize || 50;
30
30
  this._flushIntervalMs = options.flushIntervalMs || 2000;
31
+ this._maxBufferSize = options.maxBufferSize || 10000;
31
32
  this._enabled = options.enabled !== false;
32
33
 
33
34
  // Per-session monotonic chunk counters
@@ -91,6 +92,11 @@ class TranscriptWriter {
91
92
  const idx = this._counters.get(sessionId) || 0;
92
93
  this._counters.set(sessionId, idx + 1);
93
94
 
95
+ // Cap buffer to prevent unbounded growth during sustained DB failures
96
+ if (this._buffer.length >= this._maxBufferSize) {
97
+ this._buffer.splice(0, this._buffer.length - this._maxBufferSize + 1);
98
+ }
99
+
94
100
  this._buffer.push({
95
101
  sessionId,
96
102
  content: stripped,