chore: bump version to 0.1557
Lifecycle log window: draggable, resizable, wider (900px), hidden on mobile. Fixed display toggle (block vs empty string). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@@ -35,7 +35,7 @@ When user says "COM":
|
||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version CLAUDE.md`)
|
||||
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||
|
||||
**Version**: 0.1556 (must match `package.json` for npm publish)
|
||||
**Version**: 0.1557 (must match `package.json` for npm publish)
|
||||
|
||||
## Project Overview
|
||||
|
||||
@@ -120,6 +120,7 @@ journalctl --user -u claudeman-web -f
|
||||
| `src/bash-tool-parser.ts` | Parses Claude's bash tool invocations from output |
|
||||
| `src/transcript-watcher.ts` | Watches Claude's transcript files for changes |
|
||||
| `src/hooks-config.ts` | Manages `.claude/settings.local.json` hook configuration |
|
||||
| `src/session-lifecycle-log.ts` | Append-only JSONL audit log at `~/.claudeman/session-lifecycle.jsonl` |
|
||||
| `src/image-watcher.ts` | Watches for image file creation (screenshots, etc.) |
|
||||
| `src/file-stream-manager.ts` | Manages `tail -f` processes for live log viewing |
|
||||
| `src/plan-orchestrator.ts` | Multi-agent plan generation with research and planning phases |
|
||||
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 142 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claudeman",
|
||||
"version": "0.1556",
|
||||
"version": "0.1557",
|
||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||
"type": "module",
|
||||
"main": "dist/index.js",
|
||||
|
||||
@@ -22,6 +22,7 @@ const PROJECT_ROOT = join(__dirname, '..');
|
||||
const PUBLIC_DIR = join(PROJECT_ROOT, 'src', 'web', 'public');
|
||||
const PORT = 3199;
|
||||
const VIEWPORT = { width: 1280, height: 720 };
|
||||
const DEVICE_SCALE_FACTOR = 1;
|
||||
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
|
||||
@@ -117,10 +118,10 @@ function makeSession(id, name, mode, status, extra = {}) {
|
||||
niceValue: 10,
|
||||
color: 'default',
|
||||
flickerFilterEnabled: false,
|
||||
cliVersion: '2.1.14',
|
||||
cliModel: 'Opus 4.5',
|
||||
cliVersion: '2.1.47',
|
||||
cliModel: 'Opus 4.6',
|
||||
cliAccountType: 'Claude Max',
|
||||
cliLatestVersion: '2.1.14',
|
||||
cliLatestVersion: '2.1.47',
|
||||
messageCount: mode === 'claude' ? 15 : 0,
|
||||
isWorking: status === 'busy',
|
||||
lastPromptTime: Date.now() - 30000,
|
||||
@@ -298,7 +299,7 @@ function buildInitPayload(sessions, subagents = []) {
|
||||
}
|
||||
|
||||
return {
|
||||
version: '0.1540',
|
||||
version: '0.1556',
|
||||
sessions,
|
||||
scheduledRuns: [],
|
||||
respawnStatus,
|
||||
@@ -333,8 +334,8 @@ const DIM = '\x1b[2m';
|
||||
// Claude Code init banner (matching the overview screenshot)
|
||||
const TERMINAL_INIT = [
|
||||
'',
|
||||
` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.14`,
|
||||
` ${RED}████${RST} ${GRY}Opus 4.5${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`,
|
||||
` ${RED}████${RST} ${BOLD}Claude Code${RST} v2.1.47`,
|
||||
` ${RED}████${RST} ${GRY}Opus 4.6${RST} ${DIM}·${RST} ${GRY}Claude Max${RST}`,
|
||||
` ${RED}██${RST} ${RED}██${RST} ${GRY}~/claudeman-cases/testcase${RST}`,
|
||||
'',
|
||||
'',
|
||||
@@ -343,7 +344,7 @@ const TERMINAL_INIT = [
|
||||
'',
|
||||
`${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
|
||||
` ${BOLD}0 tokens${RST}`,
|
||||
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
|
||||
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
|
||||
].join('\r\n');
|
||||
|
||||
// Claude working output (for multi-session-dashboard / subagent-spawn)
|
||||
@@ -380,7 +381,7 @@ const TERMINAL_WORKING = [
|
||||
`────────────────────────────────────────────────────────────────────────────────────────────────────────────────`,
|
||||
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
|
||||
` ${BOLD}28163 tokens${RST}`,
|
||||
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
|
||||
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
|
||||
].join('\r\n');
|
||||
|
||||
// Ralph terminal content (matching ralph-tracker screenshot)
|
||||
@@ -412,7 +413,7 @@ const TERMINAL_RALPH = [
|
||||
`────────────────────────────────────────────────────────────────────────────────────────────────────────────────`,
|
||||
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
|
||||
` ${BOLD}37267 tokens${RST}`,
|
||||
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
|
||||
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
|
||||
].join('\r\n');
|
||||
|
||||
// Subagent window content (tool call activity)
|
||||
@@ -470,7 +471,7 @@ const TERMINAL_SUBAGENT = [
|
||||
'',
|
||||
'',
|
||||
` ${YEL}»»${RST} ${BOLD}bypass permissions on${RST} ${GRY}(shift+tab to cycle)${RST}`,
|
||||
` ${GRY}current: 2.1.14${RST} ${DIM}·${RST} ${GRY}latest: 2.1.14${RST}`,
|
||||
` ${GRY}current: 2.1.47${RST} ${DIM}·${RST} ${GRY}latest: 2.1.47${RST}`,
|
||||
].join('\r\n');
|
||||
|
||||
// ─── Route Interceptors ──────────────────────────────────────────────────────
|
||||
@@ -589,7 +590,8 @@ async function setupRoutes(page, initPayload, terminalContent) {
|
||||
*/
|
||||
async function injectState(page, initPayload, terminalContent, activeSessionId) {
|
||||
// Wait for app to be ready
|
||||
await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 10000 });
|
||||
await page.waitForFunction(() => window.app && window.app.terminal, { timeout: 15000 });
|
||||
await sleep(1000);
|
||||
|
||||
// Cancel the SSE fallback timer and inject state directly
|
||||
await page.evaluate((payload) => {
|
||||
@@ -603,7 +605,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
|
||||
app.handleInit(payload);
|
||||
}, initPayload);
|
||||
|
||||
await sleep(500);
|
||||
await sleep(1500);
|
||||
|
||||
// Select the target session (this triggers terminal fetch via our mocked route)
|
||||
if (activeSessionId) {
|
||||
@@ -611,7 +613,7 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
|
||||
window.app.activeSessionId = null; // Force re-select
|
||||
window.app.selectSession(sid);
|
||||
}, activeSessionId);
|
||||
await sleep(800);
|
||||
await sleep(2000);
|
||||
}
|
||||
|
||||
// Write terminal content directly as backup (in case fetch didn't work)
|
||||
@@ -630,11 +632,25 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
|
||||
}
|
||||
}, terminalContent);
|
||||
|
||||
await sleep(1000);
|
||||
|
||||
// Clean up UI elements that look wrong in screenshots
|
||||
await page.evaluate(() => {
|
||||
// Hide connection indicator (SSE is blocked so it shows "Reconnecting...")
|
||||
// Kill SSE reconnection entirely and hide the connection indicator
|
||||
const app2 = window.app;
|
||||
if (app2) {
|
||||
// Stop reconnection timers
|
||||
if (app2.sseReconnectTimeout) clearTimeout(app2.sseReconnectTimeout);
|
||||
if (app2.eventSource) { app2.eventSource.close(); app2.eventSource = null; }
|
||||
app2._connectionStatus = 'connected';
|
||||
// Monkey-patch so it never re-shows
|
||||
app2._updateConnectionIndicator = () => {};
|
||||
app2.connectSSE = () => {};
|
||||
app2.setConnectionStatus = () => {};
|
||||
}
|
||||
// Remove the indicator element from the DOM entirely
|
||||
const indicator = document.getElementById('connectionIndicator');
|
||||
if (indicator) indicator.style.display = 'none';
|
||||
if (indicator) indicator.remove();
|
||||
|
||||
// Hide respawn banner (shows "idle" state which isn't needed in screenshots)
|
||||
const respawnBanner = document.getElementById('respawnBanner');
|
||||
@@ -676,7 +692,8 @@ async function injectState(page, initPayload, terminalContent, activeSessionId)
|
||||
}
|
||||
});
|
||||
|
||||
await sleep(300);
|
||||
// Final settle — let xterm.js WebGL renderer, fonts, and layout stabilize
|
||||
await sleep(2000);
|
||||
}
|
||||
|
||||
// ─── Screenshot Scenarios ────────────────────────────────────────────────────
|
||||
@@ -701,7 +718,7 @@ async function captureOverview(page) {
|
||||
app._renderMuxSessionsImmediate();
|
||||
}
|
||||
}, MUX_SESSIONS);
|
||||
await sleep(500);
|
||||
await sleep(1500);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(PROJECT_ROOT, 'docs', 'images', 'claude-overview.png'),
|
||||
@@ -733,9 +750,9 @@ async function captureSubagentSpawn(page) {
|
||||
app.openSubagentWindow('agent-001');
|
||||
app.openSubagentWindow('agent-002');
|
||||
}, SUBAGENT_ACTIVITY);
|
||||
await sleep(800);
|
||||
await sleep(2000);
|
||||
|
||||
// Position windows nicely (matching existing screenshot layout)
|
||||
// Position windows nicely for 1280x720 viewport
|
||||
await page.evaluate(() => {
|
||||
const app = window.app;
|
||||
if (!app) return;
|
||||
@@ -755,7 +772,7 @@ async function captureSubagentSpawn(page) {
|
||||
w2.style.height = '320px';
|
||||
}
|
||||
});
|
||||
await sleep(500);
|
||||
await sleep(1500);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(PROJECT_ROOT, 'docs', 'images', 'subagent-spawn.png'),
|
||||
@@ -789,7 +806,7 @@ async function captureRalphTracker(page) {
|
||||
app._renderRalphStatePanelImmediate();
|
||||
}
|
||||
}, RALPH_SESSION_ID);
|
||||
await sleep(500);
|
||||
await sleep(1500);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(PROJECT_ROOT, 'docs', 'images', 'ralph-tracker-8tasks-44percent.png'),
|
||||
@@ -834,7 +851,7 @@ async function captureMultiSessionMonitor(page) {
|
||||
app._renderMuxSessionsImmediate();
|
||||
}
|
||||
}, MUX_SESSIONS);
|
||||
await sleep(500);
|
||||
await sleep(1500);
|
||||
|
||||
await page.screenshot({
|
||||
path: join(PROJECT_ROOT, 'docs', 'screenshots', 'multi-session-monitor.png'),
|
||||
@@ -874,9 +891,9 @@ async function main() {
|
||||
captureMultiSessionDashboard,
|
||||
captureMultiSessionMonitor,
|
||||
]) {
|
||||
const context = await browser.newContext({ viewport: VIEWPORT });
|
||||
const context = await browser.newContext({ viewport: VIEWPORT, deviceScaleFactor: DEVICE_SCALE_FACTOR });
|
||||
const page = await context.newPage();
|
||||
page.setDefaultTimeout(15000);
|
||||
page.setDefaultTimeout(30000);
|
||||
|
||||
try {
|
||||
await scenario(page);
|
||||
|
||||
@@ -9909,12 +9909,51 @@ class ClaudemanApp {
|
||||
// ========== Session Lifecycle Log ==========
|
||||
|
||||
openLifecycleLog() {
|
||||
document.getElementById('lifecycleModal').classList.add('active');
|
||||
const win = document.getElementById('lifecycleWindow');
|
||||
win.style.display = 'block';
|
||||
// Reset transform so it appears centered initially
|
||||
if (!win._dragInitialized) {
|
||||
win.style.left = '50%';
|
||||
win.style.transform = 'translateX(-50%)';
|
||||
this._initLifecycleDrag(win);
|
||||
win._dragInitialized = true;
|
||||
}
|
||||
this.loadLifecycleLog();
|
||||
}
|
||||
|
||||
closeLifecycleLog() {
|
||||
document.getElementById('lifecycleModal').classList.remove('active');
|
||||
document.getElementById('lifecycleWindow').style.display = 'none';
|
||||
}
|
||||
|
||||
_initLifecycleDrag(win) {
|
||||
const header = document.getElementById('lifecycleWindowHeader');
|
||||
let isDragging = false;
|
||||
let startX, startY, startLeft, startTop;
|
||||
|
||||
header.addEventListener('mousedown', (e) => {
|
||||
if (e.target.tagName === 'SELECT' || e.target.tagName === 'INPUT' || e.target.tagName === 'BUTTON') return;
|
||||
isDragging = true;
|
||||
// Clear transform so left/top work in absolute pixels
|
||||
const rect = win.getBoundingClientRect();
|
||||
win.style.transform = 'none';
|
||||
win.style.left = rect.left + 'px';
|
||||
win.style.top = rect.top + 'px';
|
||||
startX = e.clientX;
|
||||
startY = e.clientY;
|
||||
startLeft = rect.left;
|
||||
startTop = rect.top;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
win.style.left = (startLeft + e.clientX - startX) + 'px';
|
||||
win.style.top = (startTop + e.clientY - startY) + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
});
|
||||
}
|
||||
|
||||
async loadLifecycleLog() {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
<meta name="google" content="notranslate">
|
||||
<title>Claudeman</title>
|
||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='100%25' stop-color='%233b82f6'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='6' fill='%230a0a0a'/%3E%3Cpath d='M18 4L8 18h6l-2 10 10-14h-6z' fill='url(%23g)'/%3E%3C/svg%3E">
|
||||
<link rel="stylesheet" href="styles.css?v=0.1536">
|
||||
<link rel="stylesheet" href="mobile.css?v=0.1536" media="(max-width: 1023px)">
|
||||
<link rel="stylesheet" href="styles.css?v=0.1556">
|
||||
<link rel="stylesheet" href="mobile.css?v=0.1556" media="(max-width: 1023px)">
|
||||
<!-- xterm.css loaded async — terminal won't display until xterm.js runs anyway -->
|
||||
<link rel="preload" href="vendor/xterm.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
|
||||
<noscript><link rel="stylesheet" href="vendor/xterm.css"></noscript>
|
||||
@@ -84,7 +84,7 @@
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
|
||||
<span class="notification-badge" id="notifBadge" style="display:none;">0</span>
|
||||
</button>
|
||||
<button class="btn-icon-header" onclick="app.openLifecycleLog()" title="Session Lifecycle Log" aria-label="Open session lifecycle log" style="font-size:14px;opacity:0.7">📋</button>
|
||||
<button class="btn-icon-header btn-lifecycle-log" onclick="app.openLifecycleLog()" title="Session Lifecycle Log" aria-label="Open session lifecycle log"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg></button>
|
||||
<button class="btn-icon-header btn-settings" onclick="app.openAppSettings()" title="App Settings" aria-label="Open app settings"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg></button>
|
||||
<div class="header-tokens" id="headerTokens" title="Total tokens across all sessions">0 tokens</div>
|
||||
</div>
|
||||
@@ -1501,17 +1501,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Session Lifecycle Audit Log Modal -->
|
||||
<div class="modal" id="lifecycleModal">
|
||||
<div class="modal-backdrop" onclick="app.closeLifecycleLog()"></div>
|
||||
<div class="modal-content modal-lg">
|
||||
<div class="modal-header">
|
||||
<h3>Session Lifecycle Log</h3>
|
||||
<button class="modal-close" onclick="app.closeLifecycleLog()" aria-label="Close lifecycle log">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="max-height:70vh;overflow:auto;padding:8px">
|
||||
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
|
||||
<select id="lifecycleFilterEvent" style="background:#1a1a2e;color:#e0e0e0;border:1px solid #333;border-radius:4px;padding:4px 8px;font-size:12px">
|
||||
<!-- Session Lifecycle Audit Log (floating window) -->
|
||||
<div id="lifecycleWindow" style="display:none;position:fixed;z-index:2000;top:60px;left:50%;transform:translateX(-50%);width:900px;max-width:95vw;min-width:400px;min-height:250px;height:520px;background:#111;border:1px solid #333;border-radius:8px;box-shadow:0 8px 32px rgba(0,0,0,0.6);resize:both;overflow:hidden">
|
||||
<div id="lifecycleWindowHeader" style="display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background:#1a1a2e;border-bottom:1px solid #333;cursor:move;user-select:none;border-radius:8px 8px 0 0">
|
||||
<span style="color:#e0e0e0;font-size:13px;font-weight:600">Session Lifecycle Log</span>
|
||||
<div style="display:flex;gap:8px;align-items:center">
|
||||
<div style="display:flex;gap:6px;flex-wrap:nowrap">
|
||||
<select id="lifecycleFilterEvent" style="background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px">
|
||||
<option value="">All Events</option>
|
||||
<option value="created">Created</option>
|
||||
<option value="started">Started</option>
|
||||
@@ -1523,24 +1519,27 @@
|
||||
<option value="server_started">Server Started</option>
|
||||
<option value="server_stopped">Server Stopped</option>
|
||||
</select>
|
||||
<input id="lifecycleFilterSession" type="text" placeholder="Filter by session ID..." style="background:#1a1a2e;color:#e0e0e0;border:1px solid #333;border-radius:4px;padding:4px 8px;font-size:12px;flex:1;min-width:120px">
|
||||
<button class="btn btn-sm btn-secondary" onclick="app.loadLifecycleLog()" style="font-size:12px;padding:4px 10px">Refresh</button>
|
||||
<input id="lifecycleFilterSession" type="text" placeholder="Session ID..." style="background:#0d0d0d;color:#e0e0e0;border:1px solid #444;border-radius:4px;padding:2px 6px;font-size:11px;width:120px">
|
||||
<button onclick="app.loadLifecycleLog()" style="background:#333;color:#ccc;border:1px solid #444;border-radius:4px;padding:2px 8px;font-size:11px;cursor:pointer">Refresh</button>
|
||||
</div>
|
||||
<table id="lifecycleTable" style="width:100%;border-collapse:collapse;font-size:12px;font-family:monospace">
|
||||
<thead>
|
||||
<tr style="color:#888;text-align:left;border-bottom:1px solid #333">
|
||||
<th style="padding:4px 8px">Time</th>
|
||||
<th style="padding:4px 8px">Event</th>
|
||||
<th style="padding:4px 8px">Session</th>
|
||||
<th style="padding:4px 8px">Reason</th>
|
||||
<th style="padding:4px 8px">Extra</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="lifecycleTableBody"></tbody>
|
||||
</table>
|
||||
<div id="lifecycleEmpty" style="color:#666;text-align:center;padding:20px;display:none">No lifecycle entries found</div>
|
||||
<button onclick="app.closeLifecycleLog()" style="background:none;border:none;color:#888;font-size:18px;cursor:pointer;padding:0 4px;line-height:1" aria-label="Close">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="overflow:auto;height:calc(100% - 36px);padding:4px 0">
|
||||
<table id="lifecycleTable" style="width:100%;border-collapse:collapse;font-size:12px;font-family:monospace">
|
||||
<thead>
|
||||
<tr style="color:#888;text-align:left;border-bottom:1px solid #333;position:sticky;top:0;background:#111">
|
||||
<th style="padding:4px 8px">Time</th>
|
||||
<th style="padding:4px 8px">Event</th>
|
||||
<th style="padding:4px 8px">Session</th>
|
||||
<th style="padding:4px 8px">Reason</th>
|
||||
<th style="padding:4px 8px">Extra</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="lifecycleTableBody"></tbody>
|
||||
</table>
|
||||
<div id="lifecycleEmpty" style="color:#666;text-align:center;padding:20px;display:none">No lifecycle entries found</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SVG Overlay for Subagent Connection Lines -->
|
||||
@@ -1548,6 +1547,6 @@
|
||||
<!-- Lines drawn dynamically -->
|
||||
</svg>
|
||||
|
||||
<script defer src="app.js?v=0.1536"></script>
|
||||
<script defer src="app.js?v=0.1556"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -308,8 +308,9 @@ html.mobile-init .file-browser-panel {
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Hide header settings gear on mobile - moved to toolbar */
|
||||
.btn-icon-header.btn-settings {
|
||||
/* Hide header settings gear and lifecycle log on mobile - settings moved to toolbar */
|
||||
.btn-icon-header.btn-settings,
|
||||
.btn-icon-header.btn-lifecycle-log {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||