ai-agent-session-center 2.0.2 → 2.0.3

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.
Files changed (58) hide show
  1. package/README.md +484 -429
  2. package/docs/3D/ADAPTATION_GUIDE.md +592 -0
  3. package/docs/3D/index.html +754 -0
  4. package/docs/AGENT_TEAM_TASKS.md +716 -0
  5. package/docs/CYBERDROME_V2_SPEC.md +531 -0
  6. package/docs/ERROR_185_ANALYSIS.md +263 -0
  7. package/docs/PLATFORM_FEATURES_PROMPT.md +296 -0
  8. package/docs/SESSION_DETAIL_FEATURES.md +98 -0
  9. package/docs/_3d_multimedia_features.md +1080 -0
  10. package/docs/_frontend_features.md +1057 -0
  11. package/docs/_server_features.md +1077 -0
  12. package/docs/session-duplication-fixes.md +271 -0
  13. package/docs/session-terminal-linkage.md +412 -0
  14. package/package.json +63 -5
  15. package/public/apple-touch-icon.svg +21 -0
  16. package/public/css/dashboard.css +0 -161
  17. package/public/css/detail-panel.css +25 -0
  18. package/public/css/layout.css +18 -1
  19. package/public/css/modals.css +0 -26
  20. package/public/css/settings.css +0 -150
  21. package/public/css/terminal.css +34 -0
  22. package/public/favicon.svg +18 -0
  23. package/public/index.html +6 -26
  24. package/public/js/alarmManager.js +0 -21
  25. package/public/js/app.js +21 -7
  26. package/public/js/detailPanel.js +63 -64
  27. package/public/js/historyPanel.js +61 -55
  28. package/public/js/quickActions.js +132 -48
  29. package/public/js/sessionCard.js +5 -20
  30. package/public/js/sessionControls.js +8 -0
  31. package/public/js/settingsManager.js +0 -142
  32. package/server/apiRouter.js +60 -15
  33. package/server/apiRouter.ts +774 -0
  34. package/server/approvalDetector.ts +94 -0
  35. package/server/authManager.ts +144 -0
  36. package/server/autoIdleManager.ts +110 -0
  37. package/server/config.ts +121 -0
  38. package/server/constants.ts +150 -0
  39. package/server/db.ts +475 -0
  40. package/server/hookInstaller.d.ts +3 -0
  41. package/server/hookProcessor.ts +108 -0
  42. package/server/hookRouter.ts +18 -0
  43. package/server/hookStats.ts +116 -0
  44. package/server/index.js +15 -1
  45. package/server/index.ts +230 -0
  46. package/server/logger.ts +75 -0
  47. package/server/mqReader.ts +349 -0
  48. package/server/portManager.ts +55 -0
  49. package/server/processMonitor.ts +239 -0
  50. package/server/serverConfig.ts +29 -0
  51. package/server/sessionMatcher.js +17 -6
  52. package/server/sessionMatcher.ts +403 -0
  53. package/server/sessionStore.js +109 -3
  54. package/server/sessionStore.ts +1145 -0
  55. package/server/sshManager.js +167 -24
  56. package/server/sshManager.ts +671 -0
  57. package/server/teamManager.ts +289 -0
  58. package/server/wsManager.ts +200 -0
@@ -0,0 +1,1057 @@
1
+ # Frontend Features — AI Agent Session Center
2
+
3
+ > Sections 13–14, 16–21, 27–30. Generated from source files in `public/js/` and `public/css/`.
4
+
5
+ ---
6
+
7
+ ## 13. IndexedDB Client Persistence (`browserDb.js`)
8
+
9
+ ### Database Identity
10
+
11
+ | Property | Value |
12
+ |----------|-------|
13
+ | Database name | `claude-dashboard` |
14
+ | Schema version | `2` |
15
+ | API | `window.indexedDB` (browser-native) |
16
+ | Initialization | `openDB()` — idempotent singleton |
17
+
18
+ ### Object Stores (12 total)
19
+
20
+ | Store | Key Path | Auto-Increment | Purpose |
21
+ |-------|----------|----------------|---------|
22
+ | `sessions` | `id` | No | Session snapshots, one record per session |
23
+ | `prompts` | `id` | Yes | User prompt history entries |
24
+ | `responses` | `id` | Yes | Claude response excerpts |
25
+ | `toolCalls` | `id` | Yes | Tool invocation records |
26
+ | `events` | `id` | Yes | Raw hook event log |
27
+ | `notes` | `id` | Yes | Per-session user notes |
28
+ | `promptQueue` | `id` | Yes | Queued prompts awaiting dispatch |
29
+ | `alerts` | `id` | Yes | Duration alert rules |
30
+ | `sshProfiles` | `id` | Yes | SSH connection profiles |
31
+ | `settings` | `key` | No | Key-value settings store |
32
+ | `summaryPrompts` | `id` | Yes | AI summarization prompt templates |
33
+ | `teams` | `id` | No | Subagent team definitions |
34
+
35
+ ### Indexes
36
+
37
+ | Store | Index Name | Key Path | Notes |
38
+ |-------|-----------|----------|-------|
39
+ | `sessions` | `status` | `status` | Filter by status |
40
+ | `sessions` | `projectPath` | `projectPath` | Filter by project |
41
+ | `sessions` | `startedAt` | `startedAt` | Sort by time |
42
+ | `sessions` | `lastActivityAt` | `lastActivityAt` | Sort by activity |
43
+ | `sessions` | `archived` | `archived` | Archived flag (0/1) |
44
+ | `prompts` | `sessionId` | `sessionId` | Lookup by session |
45
+ | `prompts` | `sessionId_timestamp` | `[sessionId, timestamp]` | Compound for sorted lookup |
46
+ | `responses` | `sessionId` | `sessionId` | Lookup by session |
47
+ | `toolCalls` | `sessionId` | `sessionId` | Lookup by session |
48
+ | `toolCalls` | `toolName` | `toolName` | Filter by tool |
49
+ | `events` | `sessionId` | `sessionId` | Lookup by session |
50
+ | `promptQueue` | `sessionId_position` | `[sessionId, position]` | Ordered queue items |
51
+ | `notes` | `sessionId` | `sessionId` | Lookup notes per session |
52
+ | `summaryPrompts` | `isDefault` | `isDefault` | Find default template |
53
+
54
+ ### Batched Write Queue
55
+
56
+ Writes are coalesced per store to minimize IndexedDB transaction overhead:
57
+
58
+ | Parameter | Value |
59
+ |-----------|-------|
60
+ | Flush interval | `200 ms` |
61
+ | Max queue depth before immediate flush | `20 items` |
62
+
63
+ `putBatched(storeName, data)` schedules writes; `flushWriteQueue()` executes them in a single transaction. `flushAllWriteQueues()` is called on page unload.
64
+
65
+ ### Session Persistence (`persistSessionUpdate`)
66
+
67
+ When a `session_update` WebSocket message arrives, the following records are upserted/appended:
68
+
69
+ - **sessions** store: full session record (status, projectPath, title, model, label, teamId, queueCount, etc.)
70
+ - **prompts**: new entries deduplicated by `timestamp`
71
+ - **toolCalls**: new entries deduplicated by `timestamp`; maps `tool` → `toolName`, `input` → `toolInputSummary`
72
+ - **responses**: new entries deduplicated by `timestamp`; maps `text` → `textExcerpt`
73
+ - **events**: new entries deduplicated by `timestamp`
74
+
75
+ ### Query API (`searchSessions`)
76
+
77
+ Supported filter parameters with defaults:
78
+
79
+ | Parameter | Type | Default | Notes |
80
+ |-----------|------|---------|-------|
81
+ | `query` | string | — | Substring match on prompt text |
82
+ | `project` | string | — | Exact match on `projectPath` |
83
+ | `status` | string | — | Exact match on `status` |
84
+ | `dateFrom` | number (ms) | — | `startedAt >= dateFrom` |
85
+ | `dateTo` | number (ms) | — | `startedAt <= dateTo` |
86
+ | `archived` | boolean/`'all'` | excludes archived | `archived === 'all'` returns everything |
87
+ | `sortBy` | string | `'startedAt'` | Any session field |
88
+ | `sortDir` | `'asc'`/`'desc'` | `'desc'` | — |
89
+ | `page` | number | `1` | 1-based |
90
+ | `pageSize` | number | `50` | — |
91
+
92
+ Returns `{ sessions, total, page, pageSize }`.
93
+
94
+ ### Full-Text Search (`fullTextSearch`)
95
+
96
+ Searches `prompts` and `responses` stores with case-insensitive substring matching. Returns results with `<mark>`-wrapped snippets (context window: 60 chars before/after match). Supports `type` filter: `'all'`, `'prompts'`, `'responses'`.
97
+
98
+ ### Analytics Queries
99
+
100
+ | Function | Data source | Output |
101
+ |---------|-------------|--------|
102
+ | `getSummaryStats()` | sessions + toolCalls + prompts | total sessions, prompts, tools, active count, avg duration, most-used tool, busiest project |
103
+ | `getToolBreakdown()` | toolCalls | per-tool count + % of total |
104
+ | `getDurationTrends({ period })` | sessions with `endedAt` | avg duration + session count per bucket |
105
+ | `getActiveProjects()` | sessions + prompts + toolCalls | per-project: session count, total prompts, total tools, last activity |
106
+ | `getHeatmap()` | events | 7×24 grid of event counts (Monday-first, 0–6) |
107
+ | `getTimeline({ dateFrom, dateTo, granularity, project })` | sessions + prompts + toolCalls | per-period: session_count, prompt_count, tool_call_count |
108
+
109
+ Timeline granularity options: `'hour'`, `'day'` (default), `'week'`, `'month'`. Period key format: `YYYY-MM-DD` (day), `YYYY-MM-DD HH:00` (hour), `YYYY-Www` (week), `YYYY-MM` (month).
110
+
111
+ ### Prompt Queue CRUD
112
+
113
+ | Operation | Function |
114
+ |-----------|----------|
115
+ | List (ordered) | `getQueue(sessionId)` — sorted by `position` |
116
+ | Add | `addToQueue(sessionId, text)` — appends at max position + 1 |
117
+ | Remove first (dequeue) | `popQueue(sessionId)` |
118
+ | Reorder | `reorderQueue(sessionId, orderedIds)` — reassigns position by array index |
119
+ | Move items | `moveQueueItems(itemIds, targetSessionId)` |
120
+ | Move all | `moveAllQueue(sourceSessionId, targetSessionId)` |
121
+
122
+ ### Notes Helpers
123
+
124
+ - `getNotes(sessionId)` — sorted newest-first by `createdAt`
125
+ - `addNote(sessionId, text)` — sets `createdAt` and `updatedAt` to `Date.now()`
126
+
127
+ ### Settings Helpers
128
+
129
+ - `getSetting(key)` / `setSetting(key, value)` — single key-value
130
+ - `getAllSettings()` — returns plain object
131
+ - `setManySettings(obj)` — batch upsert
132
+
133
+ ### Default Seed Data
134
+
135
+ On first DB open (`settingsCount === 0`), the following defaults are inserted:
136
+
137
+ | Key | Default Value |
138
+ |-----|--------------|
139
+ | `theme` | `'command-center'` |
140
+ | `fontSize` | `'13'` |
141
+ | `modelUrl` | `'https://threejs.org/examples/models/gltf/Xbot.glb'` |
142
+ | `modelName` | `'Xbot'` |
143
+ | `soundEnabled` | `'true'` |
144
+ | `soundVolume` | `'0.5'` |
145
+ | `soundPack` | `'default'` |
146
+
147
+ Five summary prompt templates are seeded on first use: **Detailed Technical Summary** (default), **Quick Bullet Points**, **Changelog Entry**, **Handoff Notes**, **PR Description**.
148
+
149
+ ### Session ID Migration
150
+
151
+ `migrateSessionId(oldSessionId, newSessionId)` re-keys all child store records across 7 stores: `prompts`, `responses`, `toolCalls`, `events`, `notes`, `promptQueue`, `alerts`. Used when `claude --resume` creates a new session UUID.
152
+
153
+ ### Delete Cascade
154
+
155
+ `deleteSession(sessionId)` removes the session record and all related records from 7 child stores.
156
+
157
+ ---
158
+
159
+ ## 14. CSS Character System (`robotManager.js` + `public/css/characters/`)
160
+
161
+ ### Character Models (20 total)
162
+
163
+ | Model key | CSS class | Description |
164
+ |-----------|-----------|-------------|
165
+ | `robot` | `char-robot` | Classic robot with antenna, chest light, typing dots |
166
+ | `cat` | `char-cat` | Cat with ears, whiskers, tail |
167
+ | `alien` | `char-alien` | Dome-headed alien with 3 eyes and tentacles |
168
+ | `ghost` | `char-ghost` | Ghost with blush marks |
169
+ | `orb` | `char-orb` | Energy orb with 2 rings and particles |
170
+ | `dragon` | `char-dragon` | Dragon with horns, wings, fire breath |
171
+ | `penguin` | `char-penguin` | Penguin with beak and flippers |
172
+ | `octopus` | `char-octopus` | Octopus with 4 tentacles |
173
+ | `mushroom` | `char-mushroom` | Mushroom with spots and stem |
174
+ | `fox` | `char-fox` | Fox with ears, snout, tail |
175
+ | `unicorn` | `char-unicorn` | Unicorn with horn and mane |
176
+ | `jellyfish` | `char-jellyfish` | Jellyfish with 5 tentacles |
177
+ | `owl` | `char-owl` | Owl with tufts, pupils, wings |
178
+ | `bat` | `char-bat` | Bat with large wings and fangs |
179
+ | `cactus` | `char-cactus` | Cactus with flower, arms |
180
+ | `slime` | `char-slime` | Blob with shine |
181
+ | `pumpkin` | `char-pumpkin` | Pumpkin with grooves |
182
+ | `yeti` | `char-yeti` | Yeti with fur and belly |
183
+ | `crystal` | `char-crystal` | Faceted crystal with glowing core |
184
+ | `bee` | `char-bee` | Bee with stripes, wings, stinger |
185
+
186
+ ### Color Palette (8 colors, round-robin assignment)
187
+
188
+ | Index | Hex Value | Associated Status |
189
+ |-------|-----------|-----------------|
190
+ | 0 | `#00e5ff` | cyan — prompting |
191
+ | 1 | `#ff9100` | orange — working |
192
+ | 2 | `#00ff88` | green — idle |
193
+ | 3 | `#ff3355` | red — ended |
194
+ | 4 | `#aa66ff` | purple — input |
195
+ | 5 | `#ffdd00` | yellow — approval |
196
+ | 6 | `#ff66aa` | pink |
197
+ | 7 | `#66ffdd` | teal |
198
+
199
+ Colors are assigned sequentially on character creation and persisted to the server via `PUT /api/sessions/:id/accent-color`.
200
+
201
+ ### Status → CSS Animation Mapping
202
+
203
+ The `data-status` attribute on `.css-robot` drives all animations via CSS:
204
+
205
+ | Status | `data-status` | Visual Behaviour |
206
+ |--------|--------------|-----------------|
207
+ | Idle | `idle` | Float up/down (or breathe/sway/sparkle per effect setting) |
208
+ | Prompting | `prompting` | Eye-cycle movement, wave animation |
209
+ | Working | `working` | Running/bobbing animation |
210
+ | Waiting (approval/input) | `waiting` | Bounce animation; stops when `data-checked="true"` |
211
+ | Ended | `ended` | Death animation then fade |
212
+
213
+ ### Lifecycle
214
+
215
+ 1. `createRobot(sessionId, sessionCharModel, sessionColor)` — finds `.robot-viewport` inside the card, builds HTML from the character template, sets `--robot-color` CSS variable, stores in `Map<sessionId, robotData>`.
216
+ 2. `updateRobot(session)` — updates `data-status`, optionally switches character model, triggers emote animations (`robot.classList.add('robot-emote')` for 600 ms).
217
+ 3. `switchSessionCharacter(sessionId, modelName)` — replaces inner HTML with new template, sets `perSession: true` flag to skip global model switches.
218
+ 4. `switchAllCharacters(modelName)` — updates all characters without per-session overrides (called when global setting changes).
219
+ 5. `markChecked(sessionId)` — sets `data-checked="true"` to stop the waiting bounce.
220
+ 6. `removeRobot(sessionId)` — removes DOM element and map entry.
221
+
222
+ ### Per-Session Override
223
+
224
+ Each session card has a character model selector in the detail panel header (`#detail-char-model`). Choosing a model sets `perSession: true` on the robot data object and persists the choice to both IndexedDB (`sessions` store, `characterModel` field) and the server.
225
+
226
+ ---
227
+
228
+ ## 16. Session Cards (`sessionCard.js`)
229
+
230
+ ### Card DOM Structure
231
+
232
+ ```html
233
+ <div class="session-card" data-session-id="..." data-status="..." draggable="true">
234
+ <button class="close-btn">×</button>
235
+ <button class="pin-btn">▲</button>
236
+ <button class="summarize-card-btn">↓AI</button>
237
+ <button class="mute-btn">♫</button>
238
+ <button class="resume-card-btn hidden">▶ RESUME</button>
239
+ <div class="robot-viewport"><!-- CSS character injected here --></div>
240
+ <div class="card-info">
241
+ <div class="card-title"><!-- editable title --></div>
242
+ <div class="card-header">
243
+ <span class="project-name"></span>
244
+ <span class="card-label-badge"></span>
245
+ <span class="card-group-badge">+</span>
246
+ <span class="source-badge"></span>
247
+ <span class="status-badge"></span>
248
+ </div>
249
+ <div class="waiting-banner">NEEDS YOUR INPUT</div>
250
+ <div class="card-prompt"></div>
251
+ <div class="card-stats">
252
+ <span class="duration"></span>
253
+ <span class="tool-count"></span>
254
+ <span class="subagent-count"></span>
255
+ <span class="queue-count"></span>
256
+ </div>
257
+ <div class="tool-bars"><!-- top-5 tool bars --></div>
258
+ </div>
259
+ </div>
260
+ ```
261
+
262
+ ### Debounced DOM Updates
263
+
264
+ | Parameter | Value |
265
+ |-----------|-------|
266
+ | Debounce delay for subsequent updates | `100 ms` |
267
+ | Initial card creation | No debounce (immediate) |
268
+
269
+ `pendingCardUpdates` map tracks pending timers. Only the latest update within the window is applied.
270
+
271
+ ### Status Display
272
+
273
+ | Session Status | Badge Text | CSS class |
274
+ |---------------|-----------|-----------|
275
+ | `idle` | `IDLE` | `.idle` |
276
+ | `prompting` | `PROMPTING` | `.prompting` |
277
+ | `working` | `WORKING` | `.working` |
278
+ | `approval` | `APPROVAL NEEDED` | `.approval` |
279
+ | `input` | `WAITING FOR INPUT` | `.input` |
280
+ | `waiting` | `WAITING` | `.waiting` |
281
+ | `ended` | `DISCONNECTED` | `.disconnected` |
282
+
283
+ Active-status cards (`working`, `prompting`, `approval`, `input`) automatically float to the top of their grid section (ahead of unpinned cards) when their status transitions.
284
+
285
+ ### Pinned Sessions
286
+
287
+ - Persisted in `localStorage['pinned-sessions']` (JSON array of session IDs).
288
+ - `pinSession(sessionId)` / unpin toggle on pin button click.
289
+ - `reorderPinnedCards()` re-inserts pinned cards at the start of their containing grid.
290
+ - CSS class `pinned` applied to card; pin button gets class `active`.
291
+
292
+ ### Muted Sessions
293
+
294
+ - Persisted in `localStorage['muted-sessions']` (JSON array).
295
+ - Global mute via `toggleMuteAll()` — sets `globalMuted` flag; affects sound playback.
296
+ - Per-session mute toggle on the `♫` button: adds/removes `muted` class, updates icon to `M`.
297
+ - `isMuted(sessionId)` returns `true` if globally muted or session is individually muted.
298
+
299
+ ### Tool Bars
300
+
301
+ Top 5 tools rendered as horizontal fill bars:
302
+ - Bar width: `(count / maxCount) * 100%`
303
+ - Bars sorted by count descending
304
+ - Maximum bars shown: `5`
305
+
306
+ ### Toast Notifications
307
+
308
+ `showToast(title, message)`:
309
+ - Auto-dismiss after `5000 ms`
310
+ - Fade-out animation: `300 ms`
311
+ - Error/failed toasts (matching `/error|failed/i`) always shown regardless of `toastEnabled` setting
312
+ - Close button dismisses immediately
313
+
314
+ ### Label Badges
315
+
316
+ | Label Value | CSS modifier on card |
317
+ |-------------|---------------------|
318
+ | `HEAVY` | `.heavy-session` |
319
+ | `ONEOFF` | `.oneoff-session` |
320
+ | `IMPORTANT` | `.important-session` |
321
+
322
+ Label-specific decorative frames are applied via `data-frame` attribute if configured in label settings.
323
+
324
+ ### Source Badges
325
+
326
+ Displayed when session source is not `ssh`:
327
+
328
+ | Source key | Display text |
329
+ |-----------|-------------|
330
+ | `vscode` | VS Code |
331
+ | `jetbrains` | JetBrains |
332
+ | `iterm` | iTerm |
333
+ | `warp` | Warp |
334
+ | `kitty` | Kitty |
335
+ | `ghostty` | Ghostty |
336
+ | `alacritty` | Alacritty |
337
+ | `wezterm` | WezTerm |
338
+ | `hyper` | Hyper |
339
+ | `terminal` | Terminal |
340
+ | `tmux` | tmux |
341
+
342
+ ### Prompt Preview
343
+
344
+ Truncated at `120 characters` with `...` suffix. Shows `currentPrompt` or latest prompt history entry.
345
+
346
+ ### Drag-and-Drop Reordering
347
+
348
+ - Cards are `draggable="true"` (except display-only source cards).
349
+ - During drag, card gets class `dragging`.
350
+ - Drop target shows `drag-over-left` or `drag-over-right` class based on mouse X position relative to card midpoint.
351
+ - Dropping onto a group grid auto-assigns the session to that group.
352
+
353
+ ### Inline Title Rename
354
+
355
+ Clicking `.card-title` makes it `contentEditable`. Saving: `blur` or `Enter`. Cancelling: `Escape` restores original. Title saved to server via `PUT /api/sessions/:id/title` and persisted to IndexedDB.
356
+
357
+ ### Card-Level Summarize & Archive
358
+
359
+ The `↓AI` button on each card:
360
+ 1. Fetches session detail from IndexedDB.
361
+ 2. Builds prompt context (prompts, tool calls, responses).
362
+ 3. Loads default summary template from `summaryPrompts` store.
363
+ 4. POSTs to `POST /api/sessions/:id/summarize`.
364
+ 5. On success: marks session `archived: 1`, updates IndexedDB, shows toast.
365
+
366
+ ### Resume Button
367
+
368
+ Visible only when `status === 'ended'`. Calls `POST /api/sessions/:id/resume`. On success, opens detail panel Terminal tab.
369
+
370
+ ---
371
+
372
+ ## 17. Session Detail Panel (`detailPanel.js`)
373
+
374
+ ### Panel Structure
375
+
376
+ The panel slides in from the right, overlaid on top of the main grid. Triggered by clicking a session card.
377
+
378
+ **Header fields:**
379
+ - Project name (`#detail-project-name`)
380
+ - Status badge (`#detail-status-badge`) — same text mapping as card badges
381
+ - Model name (`#detail-model`)
382
+ - Duration (`#detail-duration`)
383
+ - Character mini-preview (`#detail-char-preview`) — live CSS character at current status
384
+ - Character model selector (`#detail-char-model`) — dropdown with all 20 models
385
+ - Session title input (`#detail-title`)
386
+ - Session label input (`#detail-label`) with datalist suggestions
387
+
388
+ ### Tabs (6 total)
389
+
390
+ | Tab `data-tab` | Content | Container ID |
391
+ |----------------|---------|-------------|
392
+ | `conversation` | Prompt history (newest first), previous sessions (collapsible) | `#detail-conversation` |
393
+ | `activity` | Merged tool calls + events + responses (newest first) | `#detail-activity-log` |
394
+ | `terminal` | xterm.js terminal embed | `#tab-terminal` |
395
+ | `notes` | Session notes with timestamps | `#tab-notes` |
396
+ | `queue` | Prompt queue with compose textarea | `#tab-queue` |
397
+ | `summary` | AI-generated summary text | `#tab-summary` |
398
+
399
+ Tab state persisted in `localStorage['active-tab']` and restored on page refresh.
400
+
401
+ ### Conversation Tab Features
402
+
403
+ - Each prompt entry: numbered `#N`, timestamp, COPY button
404
+ - COPY button writes prompt text to clipboard; temporarily shows `COPIED` for `1500 ms`
405
+ - Previous session sections (from `session.previousSessions[]`) rendered as collapsible accordions with headers showing session number, time range, and prompt count
406
+ - Toggle: click `.prev-session-header` to expand/collapse `.prev-session-section.collapsed`
407
+
408
+ ### Activity Tab
409
+
410
+ Entries merged from `session.events[]`, `session.toolLog[]`, `session.responseLog[]`, sorted newest-first:
411
+
412
+ | Entry kind | CSS class | Badge text |
413
+ |-----------|-----------|------------|
414
+ | Tool call | `.activity-tool` | Tool name |
415
+ | Response | `.activity-response` | `RESPONSE` |
416
+ | Event | `.activity-event` | Event type |
417
+
418
+ ### Terminal Tab
419
+
420
+ - Auto-attaches to `session.terminalId` when Terminal tab is selected.
421
+ - `refitTerminal()` called on tab switch to recalculate xterm dimensions.
422
+ - RECONNECT button (`#terminal-reconnect-btn`): visible when `session.terminalId || session.lastTerminalId || session.status === 'ended'`.
423
+ - If active terminal: sends `claude --resume <id> || claude --continue` via WebSocket.
424
+ - If no active terminal: calls `POST /api/sessions/:id/resume` to create new PTY.
425
+
426
+ ### Panel Resize
427
+
428
+ Drag handle (`#detail-resize-handle`) on the left edge:
429
+ - Min width: `320 px`
430
+ - Max width: `95 vw`
431
+ - Width persisted in `localStorage['detail-panel-width']`
432
+ - Adds `.resizing` class to panel during drag
433
+
434
+ ### Selection Persistence
435
+
436
+ - Selection saved to `localStorage['selected-session']` on select.
437
+ - `restoreSelection()` called after WebSocket snapshot arrives: restores both selected session and active tab.
438
+
439
+ ### History View Mode (`openSessionDetailFromHistory`)
440
+
441
+ Opens the detail panel from the History panel using IndexedDB data (not live session state). Renders all conversation types (user, tool, claude) and activity log. Does not show Queue or Notes tabs controls that require a live session.
442
+
443
+ ### Live Search Integration
444
+
445
+ `/` key or clicking the search bar (`#live-search`) filters visible cards:
446
+ - Cards not matching query get class `filtered` (hidden via CSS).
447
+ - Match criteria: `projectName`, `cardTitle`, `promptHistory[].text`, `responseLog[].text`.
448
+ - When a session is selected, matching entries in Conversation and Activity tabs get class `search-highlight` (CSS highlight), and the tab containing the first match is automatically activated; the matching entry is scrolled into view.
449
+
450
+ ---
451
+
452
+ ## 18. Session Controls (`sessionControls.js`)
453
+
454
+ All control buttons are in the detail panel header/footer area. Button IDs prefixed `ctrl-`.
455
+
456
+ ### Resume (`#ctrl-resume`)
457
+
458
+ - Visible only when `session.status === 'ended'`.
459
+ - Calls `POST /api/sessions/:id/resume`.
460
+ - On success: shows toast, switches to Terminal tab.
461
+ - Button text cycles: `RESUME` → `RESUMING...` → `RESUME`.
462
+
463
+ ### Kill (`#ctrl-kill`)
464
+
465
+ - Opens confirmation modal (`#kill-modal`).
466
+ - Modal message: `Kill session for "<projectName>"? This will terminate the Claude process (SIGTERM → SIGKILL).`
467
+ - Confirmed: `POST /api/sessions/:id/kill` with body `{ confirm: true }`.
468
+ - On success: deletes associated terminal via `DELETE /api/terminals/:terminalId`, deselects session, plays `kill` sound.
469
+ - Reports `data.pid` in success toast.
470
+
471
+ ### Archive (`#ctrl-archive`)
472
+
473
+ - Sets `status: 'ended'`, `archived: 1`, `endedAt: Date.now()` in IndexedDB.
474
+ - Calls `DELETE /api/sessions/:id` to remove from server.
475
+ - Deselects session, removes card, dispatches `card-dismissed` event.
476
+ - Toast: `ARCHIVED — Session moved to history`.
477
+
478
+ ### Permanent Delete (`#ctrl-delete`)
479
+
480
+ - Shows `confirm()` dialog: `Permanently delete session "<label>"? This cannot be undone.`
481
+ - Calls `DELETE /api/sessions/:id` + `db.del('sessions', id)`.
482
+ - No cascade delete via IndexedDB `deleteSession()` (only the top-level session record is explicitly deleted here).
483
+
484
+ ### Summarize (`#ctrl-summarize`)
485
+
486
+ Opens the summarize modal with a 5-template list. Workflow:
487
+
488
+ 1. User selects a template (default auto-selected).
489
+ 2. Clicks **RUN SUMMARIZE**.
490
+ 3. System assembles context: project info + prompts (ISO timestamps) + tool calls + responses.
491
+ 4. `POST /api/sessions/:id/summarize` with `{ context, promptTemplate }`.
492
+ 5. On success: updates Summary tab, auto-switches to Summary tab, marks session `archived: 1`.
493
+ 6. Button text: `SUMMARIZE` → `SUMMARIZING...` → `RE-SUMMARIZE`.
494
+
495
+ #### Summary Template CRUD
496
+
497
+ | Action | UI element |
498
+ |--------|------------|
499
+ | Select | Click template row (highlighted selected) |
500
+ | Set default | Star (★) button; clears previous default |
501
+ | Edit | Pencil (✎) button; fills form below list |
502
+ | Delete | × button; removes from IndexedDB |
503
+ | Create new | **+ CUSTOM PROMPT** button; shows name + textarea form |
504
+ | Save template | **SAVE AS TEMPLATE** button |
505
+ | Use once | **USE ONCE** button; runs without saving |
506
+
507
+ Template preview shows first `150 characters` of the prompt text.
508
+
509
+ ### Alert (`#ctrl-alert`)
510
+
511
+ Sets a duration alert for the selected session:
512
+ - Input: minutes (integer ≥ 1)
513
+ - Stored in `alerts` store: `{ sessionId, thresholdMs: minutes * 60000, triggerAt: now + thresholdMs }`
514
+ - Toast: `ALERT SET — Will alert after N minutes`
515
+
516
+ ### Notes (`#tab-notes`)
517
+
518
+ - **Save Note** button (`#save-note`): POSTs to `POST /api/db/sessions/:id/notes`.
519
+ - Notes list refreshed via `GET /api/db/sessions/:id/notes` on every panel open.
520
+ - Each note shows timestamp + DELETE button.
521
+ - DELETE: `DELETE /api/db/notes/:noteId` then refreshes list.
522
+
523
+ ### Title (`#detail-title`)
524
+
525
+ - Saves on `blur` or `Enter`.
526
+ - Syncs to server via `PUT /api/sessions/:id/title`.
527
+ - Persists to IndexedDB `sessions` store.
528
+ - Mirrors update to card `.card-title` in DOM.
529
+
530
+ ### Label (`#detail-label`)
531
+
532
+ - Text input with datalist suggestions from `localStorage['sessionLabels']` (up to 30 recent labels).
533
+ - Saves on `blur` or `Enter` via `PUT /api/sessions/:id/label`.
534
+ - Persists to IndexedDB.
535
+ - Mirrors update to `.card-label-badge` on card.
536
+ - Saved labels are stored MRU in `localStorage['sessionLabels']` (max 30).
537
+
538
+ #### Label Quick-Select Chips
539
+
540
+ Three built-in label chips always visible:
541
+
542
+ | Label | Icon | Color |
543
+ |-------|------|-------|
544
+ | `ONEOFF` | 🔥 | `#ff9100` |
545
+ | `HEAVY` | ★ | `#ff3355` |
546
+ | `IMPORTANT` | ⚠ | `#aa66ff` |
547
+
548
+ Plus up to 5 custom labels from `localStorage['sessionLabels']`. Clicking a chip toggles the label (click same chip again to clear). Active chip shows colored border + background.
549
+
550
+ ### Group Select (`#detail-group-select`)
551
+
552
+ Dropdown with all defined groups + `No group` + `+ New Group` option. `+ New Group` triggers `prompt()` for name, creates group, and immediately moves session card into it.
553
+
554
+ ---
555
+
556
+ ## 19. Quick Actions (`quickActions.js`)
557
+
558
+ ### Action Bar Buttons
559
+
560
+ | Element ID | Label | Behavior |
561
+ |-----------|-------|----------|
562
+ | `#qa-new-session` | + NEW SESSION | Opens full New Session modal |
563
+ | `#qa-quick-session` | QUICK SESSION | Opens Quick Session modal with no preset label |
564
+ | `#qa-oneoff` | ONEOFF | Opens Quick Session modal with label `ONEOFF` |
565
+ | `#qa-heavy` | HEAVY | Opens Quick Session modal with label `HEAVY` |
566
+ | `#qa-important` | IMPORTANT | Opens Quick Session modal with label `IMPORTANT` |
567
+ | `#qa-mute-all` | MUTE ALL | Toggles global mute; button text toggles |
568
+ | `#qa-archive-ended` | ARCHIVE ENDED | Archives all ended sessions |
569
+ | `#qa-new-group` | NEW GROUP | Creates a new session group |
570
+
571
+ ### New Session Modal (`#new-session-modal`)
572
+
573
+ Full SSH connection form:
574
+
575
+ | Field | Default | Notes |
576
+ |-------|---------|-------|
577
+ | Host | `window.location.hostname` | Pre-filled from browser URL |
578
+ | Port | `22` | — |
579
+ | Username | — | — |
580
+ | Auth method | `key` | Options: `key`, `password` |
581
+ | Private key | — | Loaded from `GET /api/ssh-keys` (lists `~/.ssh/` keys) |
582
+ | Password | — | Shown only when auth method = `password` |
583
+ | Working directory | — | Text input with history dropdown |
584
+ | Command preset | — | Options: `claude`, `codex`, `gemini`, `custom` |
585
+ | Custom command | — | Shown only when preset = `custom` |
586
+ | API key | — | Auto-filled from settings for chosen CLI |
587
+ | Terminal theme | `auto` (from settings) | — |
588
+ | Session title | — | Optional |
589
+ | Session label | — | Datalist with saved labels |
590
+
591
+ **Session mode buttons:**
592
+
593
+ | Mode | Behavior |
594
+ |------|----------|
595
+ | New Session | Default; starts a fresh shell |
596
+ | tmux-wrap | Wraps command in a new tmux window |
597
+ | tmux-attach | Lists existing tmux sessions; allows attach |
598
+
599
+ tmux session list shows: name, window count, attached/detached state, age (e.g., `5m ago`).
600
+
601
+ **Connect & Launch** button calls `POST /api/terminals`. On success: saves session config to `localStorage['lastSession']`, saves working directory to history, sets terminal theme, shows toast.
602
+
603
+ **Special label behaviors:**
604
+ - `HEAVY` or `IMPORTANT` sessions: auto-pinned via `pinSession()` after 500 ms.
605
+ - Toast messages differ per label: `HEAVY SESSION — High-priority session launched & pinned`, `ONEOFF SESSION — One-off session launched`.
606
+
607
+ ### Quick Session Modal (`#quick-session-modal`)
608
+
609
+ Abbreviated form that reuses `localStorage['lastSession']` connection config:
610
+
611
+ | Field | Notes |
612
+ |-------|-------|
613
+ | Label | Pre-filled from button (ONEOFF/HEAVY/IMPORTANT) or empty |
614
+ | Session title | Optional custom title |
615
+ | Working directory | Defaults to last-used working dir |
616
+
617
+ Requires saved session config (from previous New Session). Shows error toast if no config saved.
618
+
619
+ ### Working Directory History
620
+
621
+ - Stored in `localStorage['workdir-history']` (max `20` entries, MRU order).
622
+ - Dropdown toggle button shows history items, each with a delete (×) icon.
623
+ - Closed by clicking outside `.workdir-input-wrapper`.
624
+
625
+ ### Session Labels History
626
+
627
+ - Stored in `localStorage['sessionLabels']` (max `30` entries, MRU order).
628
+ - Label chips in Quick Session modal show saved labels with delete icons.
629
+ - Clicking a chip pre-fills the label input.
630
+
631
+ ### Mobile FAB (`#mobile-qa-fab`)
632
+
633
+ A floating action button visible on small screens. Tapping opens `#mobile-qa-panel` with all quick action items mirroring desktop buttons. Overlay (`#mobile-qa-overlay`) dismisses the panel on click.
634
+
635
+ ---
636
+
637
+ ## 20. Prompt Queue (`promptQueue.js`)
638
+
639
+ ### Per-Session Queue (in Detail Panel Queue Tab)
640
+
641
+ The queue tab (`#tab-queue`) shows prompts staged for the selected session.
642
+
643
+ **Queue item DOM:**
644
+ ```html
645
+ <div class="queue-item" draggable="true" data-queue-id="...">
646
+ <span class="queue-pos">1</span>
647
+ <div class="queue-text">prompt text</div>
648
+ <div class="queue-actions">
649
+ <button class="queue-send">SEND</button>
650
+ <button class="queue-expand">⤢</button>
651
+ <button class="queue-edit">EDIT</button>
652
+ <button class="queue-move">MOVE</button>
653
+ <button class="queue-delete">DEL</button>
654
+ </div>
655
+ </div>
656
+ ```
657
+
658
+ Count badge (`#terminal-queue-count`) shows `(N)` when queue is non-empty.
659
+
660
+ ### Item Actions
661
+
662
+ | Button | Behavior |
663
+ |--------|----------|
664
+ | SEND | Sends text to active terminal via WebSocket (`terminal_input`), deletes from IndexedDB, refreshes queue |
665
+ | EXPAND (⤢) | Opens full-screen edit modal (`#queue-expand-modal`) for multi-line editing |
666
+ | EDIT | Replaces `.queue-text` with inline `<textarea>`; SAVE / Enter/Escape |
667
+ | MOVE | Enters **Move Mode** (see below) |
668
+ | DEL | Removes item from IndexedDB, refreshes queue |
669
+
670
+ ### Drag-to-Reorder
671
+
672
+ Queue items are draggable within the list. On `dragend`, all visible item IDs are collected in DOM order and `reorderQueue(sessionId, orderedIds)` is called to persist positions.
673
+
674
+ **Drag-to-Terminal:** Queue items can be dragged and dropped onto `#terminal-container`. On drop, sends text to terminal and removes item from queue.
675
+
676
+ ### Add to Queue
677
+
678
+ `#queue-add-btn` reads `#queue-textarea`, trims text, calls `db.addToQueue(sessionId, text)`, refreshes queue, syncs count to server via WebSocket `update_queue_count`.
679
+
680
+ ### Auto-Send
681
+
682
+ When `autoSendQueue` setting is `'true'`, `tryAutoSend(sessionId, terminalId)` sends the first queue item to the terminal automatically when the terminal tab is focused with an active connection.
683
+
684
+ ### Keyboard Shortcut
685
+
686
+ `Ctrl+Enter` (or `Cmd+Enter`): sends the first queued prompt to the terminal and removes it from the queue.
687
+
688
+ ### Move Mode
689
+
690
+ `enterQueueMoveMode(itemIds, sourceSessionId)`:
691
+ - Deselects current session.
692
+ - Shows `#move-mode-banner` with text: `Click a session to move N prompt(s)`.
693
+ - Adds `.move-mode` class to `<body>`.
694
+ - Cards: source gets `.move-source`, all others get `.move-target`.
695
+ - Clicking a target card calls `completeQueueMove(targetSessionId)` → `db.moveQueueItems(itemIds, targetSessionId)`.
696
+ - Move mode cancelled via **CANCEL** button (`#move-mode-cancel`).
697
+ - **MOVE ALL** button (`#queue-move-all-btn`) moves all items at once.
698
+
699
+ ### Global Queue View (Queue Panel / separate route)
700
+
701
+ `renderQueueView()`:
702
+ - Loads all `promptQueue` store records.
703
+ - Groups by `sessionId`, displays in a table per session.
704
+ - Columns: `#`, `ID`, `Text`, `Position`, `Created`, Delete button.
705
+ - Stats bar: `N items across M sessions`.
706
+ - **EXPORT** button downloads all items as `prompt-queue-<timestamp>.json`.
707
+ - **REFRESH** button reloads from IndexedDB.
708
+
709
+ ---
710
+
711
+ ## 21. Session Groups (`sessionGroups.js`)
712
+
713
+ ### Default Groups (seeded on first launch)
714
+
715
+ | Name | Order |
716
+ |------|-------|
717
+ | Priority | 0 |
718
+ | Active | 1 |
719
+ | Background | 2 |
720
+ | Review | 3 |
721
+
722
+ Seeding is guarded by `localStorage['groups-seeded']` flag. Groups are persisted in `localStorage['session-groups']` as JSON array.
723
+
724
+ ### Group Record Schema
725
+
726
+ ```json
727
+ {
728
+ "id": "grp-<timestamp>",
729
+ "name": "Active",
730
+ "sessionIds": ["session-id-1", "session-id-2"],
731
+ "order": 1,
732
+ "colSpan": 6
733
+ }
734
+ ```
735
+
736
+ ### CSS Grid Layout (12-Column System)
737
+
738
+ Groups container (`#groups-container`) uses CSS Grid with 12 equal columns. Each group occupies `colSpan` columns (1–12, min `3`). The `gridColumn: 'span N'` style is applied to each group element.
739
+
740
+ ### Layout Presets (5 options)
741
+
742
+ | Preset key | Label | Column spans |
743
+ |------------|-------|-------------|
744
+ | `1-col` | 1 Column | `[12]` |
745
+ | `2-col` | 2 Columns | `[6, 6]` |
746
+ | `3-col` | 3 Columns | `[4, 4, 4]` |
747
+ | `1-3-2-3` | 1/3 + 2/3 | `[4, 8]` |
748
+ | `2-3-1-3` | 2/3 + 1/3 | `[8, 4]` |
749
+
750
+ Each preset button in the layout bar shows an SVG icon visualizing the column proportions. Active preset is highlighted. Saved to `localStorage['dashboard-layout']` as `{ preset, columns: 12 }`.
751
+
752
+ ### Group Resize Handles
753
+
754
+ Each group element has a `.group-resize-handle` div at the right edge. Mouse drag:
755
+ - `startColSpan` captured on mousedown.
756
+ - `deltaColSpan = Math.round(dx / (containerWidth / 12))`.
757
+ - New span clamped to `[3, 12]`.
758
+ - On mouseup: saves to `localStorage`, sets preset to `'custom'`.
759
+
760
+ ### Group CRUD
761
+
762
+ | Operation | Trigger |
763
+ |-----------|---------|
764
+ | Create | `#qa-new-group` button; `createGroup(name)` |
765
+ | Rename | Double-click `.group-name` → contentEditable |
766
+ | Delete | × button → `deleteGroup(groupId)` → cards moved to `#sessions-grid` |
767
+ | Reorder | Drag `.group-header` to another group |
768
+
769
+ Group drag uses MIME type `application/group-id` to distinguish from card drags. Drop shows `group-drop-left` / `group-drop-right` indicator.
770
+
771
+ ### Session Assignment
772
+
773
+ - **Drag card into group grid**: calls `addSessionToGroup(groupId, sessionId)`.
774
+ - **Drag card to `#sessions-grid`**: calls `removeSessionFromGroup(sessionId)`.
775
+ - **Group badge on card**: click → context dropdown with all groups + `Remove from group` + `+ New Group`.
776
+ - **Detail panel group select**: `#detail-group-select` dropdown.
777
+ - **Auto-assign**: new cards are placed in `localStorage['last-used-group']` automatically.
778
+
779
+ ### Group Badge on Cards
780
+
781
+ `updateCardGroupBadge(sessionId)`:
782
+ - Shows group name (truncated to 10 chars + `..` if longer) with class `.has-group`.
783
+ - Shows `+` with no group class if session is ungrouped.
784
+ - Click opens context dropdown.
785
+
786
+ ### Group Assign Toast
787
+
788
+ Shown for new sessions (max 3 simultaneous, no duplicates):
789
+ - Session title + group select dropdown + `+ New Group` option + `SKIP` button.
790
+ - Auto-dismisses after `15000 ms`.
791
+
792
+ ### Auto-Scroll During Drag
793
+
794
+ When dragging near the top/bottom of `#view-live`, the panel scrolls at up to `12 px/frame` (proportional to distance from edge zone of `60 px`).
795
+
796
+ ### Collapse/Expand
797
+
798
+ Clicking `.group-collapse` toggles `.collapsed` class on the group element and updates the icon `▼` / `▶`.
799
+
800
+ ---
801
+
802
+ ## 27. History Panel (`historyPanel.js`)
803
+
804
+ ### Data Source
805
+
806
+ History panel fetches from the **server-side SQLite database** (not IndexedDB) via REST API:
807
+ - `GET /api/db/projects` — distinct projects for filter dropdown
808
+ - `GET /api/db/sessions?...` — paginated session list
809
+ - `GET /api/db/sessions/:id` — full session detail
810
+ - `DELETE /api/db/sessions/:id` — permanent delete
811
+
812
+ ### Filter Controls
813
+
814
+ | Control | Type | Notes |
815
+ |---------|------|-------|
816
+ | Search input (`#search-input`) | Text | Debounced `300 ms`; filters on prompt text |
817
+ | Project filter (`#history-project-filter`) | Select | Populated from `GET /api/db/projects` |
818
+ | Status filter (`#history-status-filter`) | Select | Options include `archived` as special value |
819
+ | Date from (`#history-date-from`) | Date | Converted to ms timestamp; start of day |
820
+ | Date to (`#history-date-to`) | Date | Converted to ms timestamp; end of day (`23:59:59`) |
821
+ | Sort by (`#history-sort-by`) | Select | Maps: `date → started_at`, `duration → last_activity_at` |
822
+ | Sort direction (`#history-sort-dir`) | Toggle button | `DESC` / `ASC`; click to toggle |
823
+
824
+ Filter changes reset `currentPage` to `1`.
825
+
826
+ ### Session Row Columns
827
+
828
+ | Column | Field |
829
+ |--------|-------|
830
+ | Title | `title` (empty if none) |
831
+ | Project | `project_name` |
832
+ | Date | `started_at` formatted as `MMM D, YYYY HH:MM` (24h) |
833
+ | Duration | `ended_at - started_at` or `now - started_at` |
834
+ | Status | Color-coded badge |
835
+ | Prompts | `total_prompts` count |
836
+ | Tools | `total_tool_calls` count |
837
+ | Branch | `git_branch` (currently empty) |
838
+ | Delete | × button |
839
+
840
+ ### Pagination
841
+
842
+ - Page size: `50` records per page.
843
+ - Shows `Prev` / `Next` buttons and page numbers.
844
+ - Ellipsis (`...`) for gaps in page range (shows pages within ±2 of current).
845
+ - Page buttons disabled when at first/last page.
846
+
847
+ ### Row Click → Detail View
848
+
849
+ Clicking a row (not the delete button) calls `openHistoryDetail(sessionId)`:
850
+ 1. Fetches `GET /api/db/sessions/:id` for full data.
851
+ 2. Populates the same detail panel overlay used for live sessions.
852
+ 3. Conversation tab shows interleaved prompts + responses sorted by timestamp.
853
+ 4. Activity tab shows merged tool calls + events.
854
+
855
+ ### Delete
856
+
857
+ Calls `DELETE /api/db/sessions/:id`, fades row out over `300 ms`, removes from DOM. Re-queries if page becomes empty.
858
+
859
+ ---
860
+
861
+ ## 28. Analytics Panel (`analyticsPanel.js`)
862
+
863
+ ### Data Sources
864
+
865
+ All analytics loaded in parallel via `Promise.all`:
866
+
867
+ | API endpoint | Section |
868
+ |-------------|---------|
869
+ | `GET /api/db/analytics/summary` | Summary stats |
870
+ | `GET /api/db/analytics/tools` | Tool usage |
871
+ | `GET /api/db/analytics/projects` | Active projects |
872
+ | `GET /api/db/analytics/heatmap` | Activity heatmap |
873
+
874
+ Duration trends section currently receives an empty array (server endpoint not yet implemented); shows "No duration data" placeholder.
875
+
876
+ ### Section 1: Summary Stats Cards
877
+
878
+ Six summary stat cards rendered into `#analytics-summary`:
879
+
880
+ | Label | Field | Detail |
881
+ |-------|-------|--------|
882
+ | Total Sessions | `total_sessions` | "all time" |
883
+ | Total Prompts | `total_prompts` | "all time" |
884
+ | Total Tool Calls | `total_tool_calls` | "all time" |
885
+ | Avg Duration | `avg_duration` (ms) | "per session" |
886
+ | Most Used Tool | `most_used_tool.tool_name` | count calls |
887
+ | Busiest Project | `busiest_project.name` | N sessions |
888
+
889
+ ### Section 2: Tool Usage Chart (horizontal bar chart, SVG)
890
+
891
+ Container: `#tool-usage-chart`. Custom SVG chart:
892
+
893
+ | Property | Value |
894
+ |----------|-------|
895
+ | Max tools shown | `15` |
896
+ | Bar height | `20 px` |
897
+ | Gap between bars | `4 px` |
898
+ | Label column width | `120 px` |
899
+ | Value column width | `90 px` |
900
+ | Bar color | `#00e5ff` (cyan), opacity `0.85` → `1.0` on hover |
901
+ | Value display | `count (percentage%)` |
902
+
903
+ Hover tooltip shows: `ToolName: N (X%)`.
904
+
905
+ ### Section 3: Duration Trends (line chart, SVG)
906
+
907
+ Container: `#duration-trends-chart`. Line chart with area fill:
908
+
909
+ | Property | Value |
910
+ |----------|-------|
911
+ | Chart height | `250 px` |
912
+ | Padding left | `55 px` |
913
+ | Padding bottom | `30 px` |
914
+ | Y-axis ticks | 5 (every 25% of max) |
915
+ | Y-axis labels | Formatted as `Xh Ym` / `Xm Ys` / `Xs` |
916
+ | X-axis label density | Every `max(1, floor(N/10))` buckets |
917
+ | Line color | `#00e5ff` |
918
+ | Area opacity | `0.1` |
919
+ | Dot radius | `3 px` |
920
+ | Date label format | `MMM D` (from `YYYY-MM-DD` buckets) |
921
+
922
+ Hover on dots shows: `Period: Duration`.
923
+
924
+ ### Section 4: Active Projects (horizontal bar chart, SVG)
925
+
926
+ Container: `#active-projects-chart`:
927
+
928
+ | Property | Value |
929
+ |----------|-------|
930
+ | Bar height | `22 px` |
931
+ | Label column width | `130 px` |
932
+ | Value column width | `160 px` |
933
+ | Sort order | `session_count` descending |
934
+ | Bar color | `#00e5ff`, opacity `0.85` → `1.0` on hover |
935
+ | Value format | `N sessions | MMM D` (last active date) |
936
+
937
+ Hover tooltip shows: `ProjectName: N sessions, M prompts, P tools`.
938
+
939
+ ### Section 5: Daily Activity Heatmap (CSS Grid)
940
+
941
+ Container: `#daily-heatmap-chart`. 7 rows (Mon–Sun) × 24 columns (hours):
942
+
943
+ | Property | Value |
944
+ |----------|-------|
945
+ | Cell size | `14 × 14 px` |
946
+ | Cell gap | `2 px` |
947
+ | Day label column | `40 px` |
948
+ | Color min (no activity) | `#12122a` |
949
+ | Color max (peak activity) | `#00ff88` |
950
+ | Day order | Monday-first (`0=Mon`, `6=Sun`) |
951
+ | Hour labels | `0`–`23` across top row |
952
+
953
+ Color interpolation: linear RGB blend between min and max by `value / maxValue`. Hover shows: `DayName HH:00 - N events`.
954
+
955
+ ---
956
+
957
+ ## 29. Timeline Panel (`timelinePanel.js`)
958
+
959
+ ### Data Source
960
+
961
+ Timeline uses **IndexedDB** via `getTimeline()` from `browserDb.js` (not server API). Project filter dropdown is populated from server (`GET /api/db/projects`).
962
+
963
+ ### Controls
964
+
965
+ | Control ID | Type | Default | Notes |
966
+ |-----------|------|---------|-------|
967
+ | `#timeline-granularity` | Select | `'day'` | Options: `hour`, `day`, `week`, `month` |
968
+ | `#timeline-project-filter` | Select | All projects | — |
969
+ | `#timeline-date-from` | Date | 30 days ago | Set on `init()` |
970
+ | `#timeline-date-to` | Date | Today | Set on `init()` |
971
+
972
+ All control changes trigger `loadTimeline()`.
973
+
974
+ ### Chart: Grouped Bar Chart (SVG)
975
+
976
+ Container: `#timeline-chart`. Three bars per time bucket (grouped, not stacked):
977
+
978
+ | Series | Color |
979
+ |--------|-------|
980
+ | Sessions | `#00e5ff` (cyan) |
981
+ | Prompts | `#00ff88` (green) |
982
+ | Tool Calls | `#ff9800` (orange) |
983
+
984
+ | Property | Value |
985
+ |----------|-------|
986
+ | SVG height | `300 px` (320 px for hourly) |
987
+ | Padding left | `50 px` |
988
+ | Padding bottom | `50 px` (70 px for hourly) |
989
+ | Y-axis ticks | 5 (0%, 25%, 50%, 75%, 100% of max) |
990
+ | Y-axis labels | `formatNumber()` values |
991
+ | Bar min width | `2 px` |
992
+ | Bar gap within group | `1 px` |
993
+ | Bar opacity | `0.85` → `1.0` on hover |
994
+ | Corner radius | `2 px` |
995
+
996
+ X-axis label density:
997
+ - Hourly: max `12` labels
998
+ - Weekly: max `12` labels
999
+ - Daily: max `15` labels
1000
+ - Rotation: applied when `groupCount > 10` or granularity = `hour`; labels rotated `-40°`
1001
+
1002
+ X-axis labels are formatted by `formatTimeLabel()`:
1003
+ | Granularity | Format example |
1004
+ |------------|---------------|
1005
+ | `hour` | `Feb 10 14:00` |
1006
+ | `day` | `Feb 10` |
1007
+ | `week` | `Feb 10` (week start date) |
1008
+ | `month` | `Feb` |
1009
+
1010
+ Legend: three colored squares at bottom (`Sessions`, `Prompts`, `Tool Calls`), spaced `100 px` apart.
1011
+
1012
+ Hover on any bar shows tooltip: `Series: value\nSessions: X | Prompts: Y | Tools: Z`.
1013
+
1014
+ ---
1015
+
1016
+ ## 30. Keyboard Shortcuts (`keyboardShortcuts.js`)
1017
+
1018
+ ### All Shortcuts
1019
+
1020
+ | Key | Context | Action |
1021
+ |-----|---------|--------|
1022
+ | `/` | Any (not in input) | Focus `#live-search` |
1023
+ | `?` | Any (not in input) | Toggle shortcuts help modal (`#shortcuts-modal`) |
1024
+ | `S` / `s` | Any (not in input) | Toggle settings modal (`#settings-modal`) |
1025
+ | `K` / `k` | Session selected | Click `#ctrl-kill` (open kill confirmation modal) |
1026
+ | `A` / `a` | Session selected | Click `#ctrl-archive` |
1027
+ | `T` / `t` | Any (not in input) | Show `#new-session-modal` |
1028
+ | `M` / `m` | Any (not in input) | Click `#qa-mute-all` (toggle global mute) |
1029
+ | `Escape` | Terminal tab active | Send escape character `\x1b` to SSH terminal (capture phase) |
1030
+ | `Ctrl+Enter` / `Cmd+Enter` | Any | Send first queued prompt to terminal; remove from queue |
1031
+
1032
+ ### Implementation Notes
1033
+
1034
+ - All shortcuts skip when `e.target` is `INPUT`, `TEXTAREA`, `SELECT`, or `contentEditable`.
1035
+ - xterm textarea (`e.target.classList.contains('xterm-helper-textarea')`) is always bypassed.
1036
+ - Modifier keys (`ctrlKey`, `metaKey`, `altKey`) suppress most shortcuts (except `Ctrl+Enter`/`Cmd+Enter`).
1037
+ - Escape runs in **capture phase** (third `addEventListener` argument `true`) to intercept before xterm can handle it, ensuring single-Escape reliably sends `\x1b` to the remote shell.
1038
+
1039
+ ### localStorage Keys Reference
1040
+
1041
+ All keys used across frontend modules:
1042
+
1043
+ | Key | Module | Contents |
1044
+ |-----|--------|----------|
1045
+ | `muted-sessions` | sessionCard | JSON array of muted session IDs |
1046
+ | `pinned-sessions` | sessionCard | JSON array of pinned session IDs |
1047
+ | `session-groups` | sessionGroups | JSON array of group objects |
1048
+ | `sessionLabels` | sessionControls/quickActions | JSON array of recent labels (max 30) |
1049
+ | `lastSession` | quickActions | JSON object with SSH connection config |
1050
+ | `selected-session` | detailPanel | Session ID string |
1051
+ | `active-tab` | detailPanel | Tab name string |
1052
+ | `detail-panel-width` | detailPanel | CSS width string (e.g., `420px`) |
1053
+ | `dashboard-layout` | sessionGroups | `{ preset, columns: 12 }` |
1054
+ | `groups-seeded` | sessionGroups | `'1'` flag |
1055
+ | `workdir-history` | quickActions | JSON array of working dirs (max 20) |
1056
+ | `last-used-group` | sessionGroups | Group ID string |
1057
+ | `debug` | utils | `'true'` to enable debug logging |