claude-mission-control 1.18.0 → 1.20.0
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/lib/collector.js +14 -3
- package/lib/transcripts.js +15 -1
- package/package.json +1 -1
- package/public/index.html +302 -248
package/lib/collector.js
CHANGED
|
@@ -7,7 +7,7 @@ const { readQuota } = require('./quota');
|
|
|
7
7
|
const { fetchOauthUsage } = require('./usage');
|
|
8
8
|
const { readRegistry } = require('./registry');
|
|
9
9
|
const { refreshHistory, activityBuckets, weekHourHeat } = require('./history');
|
|
10
|
-
const { scanAllTranscripts, scanFile, groupByProject, sessionTitle, combinedUsage, combinedDays, dailyCostSeries, subagentSummary, usageBreakdown, dayKey, reasonText, contextSummary, toolSummary } = require('./transcripts');
|
|
10
|
+
const { scanAllTranscripts, scanFile, groupByProject, sessionTitle, sessionKind, combinedUsage, combinedDays, dailyCostSeries, subagentSummary, usageBreakdown, dayKey, reasonText, contextSummary, toolSummary } = require('./transcripts');
|
|
11
11
|
const { collectGitStatus } = require('./gitstatus');
|
|
12
12
|
const { friendlyName } = require('./names');
|
|
13
13
|
const { isIgnored } = require('./ignore');
|
|
@@ -277,6 +277,7 @@ class Collector {
|
|
|
277
277
|
const liveByProject = new Set(
|
|
278
278
|
this.raw.live.map((s) => worktreeRoot(s.cwd).root.toLowerCase())
|
|
279
279
|
);
|
|
280
|
+
const liveIds = new Set(this.raw.live.map((s) => s.sessionId));
|
|
280
281
|
|
|
281
282
|
const projects = [];
|
|
282
283
|
for (const p of this.projectPaths()) {
|
|
@@ -287,6 +288,15 @@ class Collector {
|
|
|
287
288
|
const git = this.raw.git.get(p) || null;
|
|
288
289
|
|
|
289
290
|
const sessionMetas = group ? group.sessions : [];
|
|
291
|
+
// Automated and empty sessions still count toward spend and activity;
|
|
292
|
+
// they just don't take up rows in the card.
|
|
293
|
+
const shown = [];
|
|
294
|
+
const hidden = { automated: 0, empty: 0 };
|
|
295
|
+
for (const m of sessionMetas) {
|
|
296
|
+
const kind = sessionKind(m, liveIds.has(m.sessionId));
|
|
297
|
+
if (kind) hidden[kind]++;
|
|
298
|
+
else shown.push(m);
|
|
299
|
+
}
|
|
290
300
|
const lastActivityAt = Math.max(
|
|
291
301
|
sessionMetas[0] ? sessionMetas[0].lastActivityAt : 0,
|
|
292
302
|
hist ? hist.lastTimestamp : 0,
|
|
@@ -309,7 +319,8 @@ class Collector {
|
|
|
309
319
|
Date.now() - m.lastActivityAt < SPEND_WINDOW_MS ? sum + estimateCost(combinedUsage(m), pricing) : sum,
|
|
310
320
|
0
|
|
311
321
|
),
|
|
312
|
-
|
|
322
|
+
hidden,
|
|
323
|
+
sessions: shown.slice(0, SESSIONS_PER_PROJECT).map((m) => {
|
|
313
324
|
const { title, source } = sessionTitle(m, sessionNames);
|
|
314
325
|
return {
|
|
315
326
|
sessionId: m.sessionId,
|
|
@@ -508,7 +519,6 @@ class Collector {
|
|
|
508
519
|
const forecast = this.limitForecast();
|
|
509
520
|
|
|
510
521
|
// Background jobs, live or finished, with any worktree work at risk.
|
|
511
|
-
const liveIds = new Set(this.raw.live.map((s) => s.sessionId));
|
|
512
522
|
const jobWt = this.raw.jobWt || new Map();
|
|
513
523
|
const jobsOut = jobs.map((j) => ({
|
|
514
524
|
jobId: j.jobId,
|
|
@@ -543,6 +553,7 @@ class Collector {
|
|
|
543
553
|
sessionId: m.sessionId,
|
|
544
554
|
title,
|
|
545
555
|
titleSource: source,
|
|
556
|
+
kind: sessionKind(m, this.raw.live.some((s) => s.sessionId === m.sessionId)),
|
|
546
557
|
estCost: estimateCost(combinedUsage(m), pricing),
|
|
547
558
|
assumed: costReport(combinedUsage(m), pricing).assumedModels.length > 0,
|
|
548
559
|
awaySummary: m.awaySummary,
|
package/lib/transcripts.js
CHANGED
|
@@ -128,6 +128,7 @@ async function scanFile(abs, sessionIdFromName) {
|
|
|
128
128
|
awaySummaryAt: null,
|
|
129
129
|
aiTitle: null,
|
|
130
130
|
waitingReason: null,
|
|
131
|
+
entrypoint: null,
|
|
131
132
|
};
|
|
132
133
|
|
|
133
134
|
// Head: first ~40 lines; the first line can be queue-operation without cwd.
|
|
@@ -148,6 +149,9 @@ async function scanFile(abs, sessionIdFromName) {
|
|
|
148
149
|
const t = Date.parse(rec.timestamp);
|
|
149
150
|
if (!Number.isNaN(t)) meta.startedAt = t;
|
|
150
151
|
}
|
|
152
|
+
if (meta.entrypoint === null && rec.type === 'user' && typeof rec.entrypoint === 'string') {
|
|
153
|
+
meta.entrypoint = rec.entrypoint;
|
|
154
|
+
}
|
|
151
155
|
if (meta.firstUserPrompt === null) {
|
|
152
156
|
const text = extractUserText(rec);
|
|
153
157
|
if (text) meta.firstUserPrompt = text.slice(0, 120);
|
|
@@ -573,6 +577,16 @@ function combinedUsage(meta) {
|
|
|
573
577
|
return mergeUsage(mergeUsage({}, meta.usage), meta.subagentUsage);
|
|
574
578
|
}
|
|
575
579
|
|
|
580
|
+
// Sessions nobody needs in a project list. 'automated': a one-shot claude -p
|
|
581
|
+
// run (recap and summarizer tools, plugin probes). 'empty': opened and closed
|
|
582
|
+
// without ever calling the model — only /clear, /exit and the like. A live
|
|
583
|
+
// session is never empty; it may just not have had its first reply yet.
|
|
584
|
+
function sessionKind(meta, isLive = false) {
|
|
585
|
+
if (meta.entrypoint === 'sdk-cli') return 'automated';
|
|
586
|
+
if (!isLive && !Object.keys(combinedUsage(meta)).length) return 'empty';
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
|
|
576
590
|
// Cost breakdown for the transcript viewer, from the meta the collector
|
|
577
591
|
// already holds — never re-reads the file. `context` and `tools` are
|
|
578
592
|
// filled by the scanner once the context-pressure batch lands.
|
|
@@ -671,7 +685,7 @@ function groupByProject(sessions) {
|
|
|
671
685
|
}
|
|
672
686
|
|
|
673
687
|
module.exports = {
|
|
674
|
-
scanAllTranscripts, scanFile, groupByProject, sessionTitle, combinedUsage, mergeUsage,
|
|
688
|
+
scanAllTranscripts, scanFile, groupByProject, sessionTitle, sessionKind, combinedUsage, mergeUsage,
|
|
675
689
|
scanLine, mergeDays, combinedDays, dailyCostSeries, subagentSummary, usageBreakdown, dayKey, contextSummary, toolSummary,
|
|
676
690
|
waitingReason, reasonText, shouldReadMeta, TOOL_RING,
|
|
677
691
|
};
|
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -31,23 +31,25 @@
|
|
|
31
31
|
src: url('/fonts/Oswald-Variable.woff2') format('woff2');
|
|
32
32
|
font-weight: 200 700; font-style: normal; font-display: swap;
|
|
33
33
|
}
|
|
34
|
-
/* Departures board: blue-black enamel,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Light theme is the same
|
|
34
|
+
/* Departures board: blue-black enamel, amber is the loudest color (needs
|
|
35
|
+
you), green is a small "boarding" lamp, red means broken. Prose is the
|
|
36
|
+
system sans, data is JetBrains Mono, and the split-flap status lamp is
|
|
37
|
+
the one place Oswald caps survive. Light theme is the same board on paper. */
|
|
38
38
|
:root {
|
|
39
39
|
--disp-font: 'Oswald', 'Arial Narrow', sans-serif;
|
|
40
|
-
--
|
|
41
|
-
--
|
|
42
|
-
--
|
|
40
|
+
--ui-font: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
|
|
41
|
+
--mono-font: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
|
|
42
|
+
--bg: #f1f0eb;
|
|
43
|
+
--surface: #fbfbf8;
|
|
44
|
+
--surface2: #e6e5df;
|
|
43
45
|
--ink: #1b2022;
|
|
44
46
|
--muted: #596264;
|
|
45
|
-
--faint: #
|
|
46
|
-
--line: #
|
|
47
|
+
--faint: #8a9193;
|
|
48
|
+
--line: #dddcd4;
|
|
47
49
|
--accent: #1d7a44;
|
|
48
50
|
--warn: #a66a00;
|
|
49
51
|
--bad: #bd3524;
|
|
50
|
-
--chip-bg: #
|
|
52
|
+
--chip-bg: #ecebe5;
|
|
51
53
|
--warn-bg: rgba(166, 106, 0, 0.10);
|
|
52
54
|
--accent-bg: rgba(29, 122, 68, 0.08);
|
|
53
55
|
--glow: none;
|
|
@@ -82,6 +84,7 @@
|
|
|
82
84
|
so it overrides both light and dark palettes. */
|
|
83
85
|
:root[data-theme="phosphor"] {
|
|
84
86
|
--disp-font: 'JetBrains Mono', ui-monospace, monospace;
|
|
87
|
+
--ui-font: 'JetBrains Mono', ui-monospace, monospace;
|
|
85
88
|
--bg: #050a06;
|
|
86
89
|
--surface: #0a120c;
|
|
87
90
|
--surface2: #122015;
|
|
@@ -106,6 +109,7 @@
|
|
|
106
109
|
and "needs you" is the white-hot bright intensity. Red still means broken. */
|
|
107
110
|
:root[data-theme="amber"] {
|
|
108
111
|
--disp-font: 'JetBrains Mono', ui-monospace, monospace;
|
|
112
|
+
--ui-font: 'JetBrains Mono', ui-monospace, monospace;
|
|
109
113
|
--bg: #0a0602;
|
|
110
114
|
--surface: #140d04;
|
|
111
115
|
--surface2: #1f1508;
|
|
@@ -179,10 +183,12 @@
|
|
|
179
183
|
body {
|
|
180
184
|
background: var(--bg);
|
|
181
185
|
color: var(--ink);
|
|
182
|
-
font:
|
|
186
|
+
font: 13.5px/1.5 var(--ui-font);
|
|
183
187
|
padding-bottom: 48px;
|
|
188
|
+
-webkit-font-smoothing: antialiased;
|
|
184
189
|
}
|
|
185
|
-
|
|
190
|
+
/* Numbers, costs, branches and commands stay monospaced; prose does not. */
|
|
191
|
+
.mono, code, pre, kbd, .badge, .num { font-family: var(--mono-font); font-variant-numeric: tabular-nums; }
|
|
186
192
|
::selection { background: var(--warn); color: var(--amber-ink); }
|
|
187
193
|
|
|
188
194
|
/* Display face: condensed caps, the board's letter-painted voice. */
|
|
@@ -206,99 +212,100 @@
|
|
|
206
212
|
.flap.flip { animation: flapflip 0.3s ease-out; transform-origin: 50% 50%; }
|
|
207
213
|
@media (prefers-reduced-motion: reduce) { .flap.flip { animation: none; } }
|
|
208
214
|
|
|
209
|
-
/* ---------- header
|
|
215
|
+
/* ---------- header ---------- */
|
|
210
216
|
header {
|
|
211
|
-
display: flex; align-items: center; gap:
|
|
217
|
+
display: flex; align-items: center; gap: 14px;
|
|
212
218
|
flex-wrap: wrap; row-gap: 10px;
|
|
213
|
-
padding:
|
|
214
|
-
border-bottom:
|
|
219
|
+
padding: 10px 24px;
|
|
220
|
+
border-bottom: 1px solid var(--line);
|
|
215
221
|
background: var(--surface);
|
|
216
222
|
}
|
|
217
223
|
header h1 {
|
|
218
|
-
font-
|
|
219
|
-
font-size: 19px; font-weight: 600; letter-spacing: 0.10em;
|
|
220
|
-
text-transform: uppercase; white-space: nowrap;
|
|
221
|
-
}
|
|
222
|
-
header h1 .tilde { color: var(--warn); }
|
|
223
|
-
#clock {
|
|
224
|
-
font-size: 14px; font-weight: 700; color: var(--ink);
|
|
225
|
-
letter-spacing: 0.08em; font-variant-numeric: tabular-nums;
|
|
224
|
+
font-size: 15px; font-weight: 650; letter-spacing: -0.01em; white-space: nowrap;
|
|
226
225
|
}
|
|
226
|
+
#clock { display: none; }
|
|
227
227
|
#conn {
|
|
228
|
-
width:
|
|
228
|
+
width: 8px; height: 8px; border-radius: 50%;
|
|
229
229
|
background: var(--faint); flex: none;
|
|
230
230
|
}
|
|
231
231
|
#conn.on { background: var(--accent); box-shadow: var(--glow) var(--accent); }
|
|
232
232
|
#conn.retry { background: var(--warn); }
|
|
233
233
|
.spacer { flex: 1; }
|
|
234
234
|
|
|
235
|
-
#quota { display: flex; gap:
|
|
235
|
+
#quota { display: flex; gap: 20px; align-items: center; font-size: 12.5px; color: var(--muted); }
|
|
236
236
|
#quota.stale { opacity: 0.45; }
|
|
237
|
-
.
|
|
237
|
+
#quota .q-spend { display: inline-flex; align-items: center; gap: 8px; white-space: nowrap; }
|
|
238
|
+
#quota .q-spend .num { color: var(--ink); }
|
|
239
|
+
.meter { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
|
|
240
|
+
.meter .m-pct { color: var(--ink); font-family: var(--mono-font); font-size: 12px; min-width: 3ch; }
|
|
238
241
|
.meter .bar {
|
|
239
|
-
width:
|
|
242
|
+
width: 64px; height: 6px; border-radius: 3px;
|
|
240
243
|
background: var(--surface2); overflow: hidden;
|
|
241
244
|
}
|
|
242
245
|
.meter .fill { height: 100%; border-radius: 3px; background: var(--accent); }
|
|
243
246
|
.meter .fill.hot { background: var(--warn); }
|
|
244
247
|
.meter .fill.crit { background: var(--bad); }
|
|
245
|
-
.meter .eta { color: var(--warn); white-space: nowrap; }
|
|
248
|
+
.meter .eta { color: var(--warn); white-space: nowrap; font-family: var(--mono-font); font-size: 12px; }
|
|
246
249
|
|
|
247
|
-
/*
|
|
250
|
+
/* Section headings: plain sentence case, the count as a quiet number. */
|
|
251
|
+
.sec-head {
|
|
252
|
+
display: flex; align-items: baseline; gap: 8px;
|
|
253
|
+
font-size: 14px; font-weight: 600; color: var(--ink);
|
|
254
|
+
margin-bottom: 10px;
|
|
255
|
+
}
|
|
256
|
+
.sec-head .n { color: var(--faint); font-weight: 500; font-family: var(--mono-font); font-size: 12.5px; }
|
|
257
|
+
|
|
258
|
+
/* ---------- live rail ---------- */
|
|
248
259
|
#live {
|
|
249
260
|
position: sticky; top: 0; z-index: 5;
|
|
250
261
|
background: var(--bg);
|
|
251
|
-
padding:
|
|
252
|
-
border-bottom:
|
|
262
|
+
padding: 18px 24px 16px;
|
|
263
|
+
border-bottom: 1px solid var(--line);
|
|
253
264
|
}
|
|
254
|
-
#live .
|
|
255
|
-
font-family: var(--disp-font);
|
|
256
|
-
font-size: 12px; text-transform: uppercase; letter-spacing: 0.22em;
|
|
257
|
-
color: var(--faint); margin-bottom: 10px;
|
|
258
|
-
}
|
|
259
|
-
#live .rows { display: flex; flex-direction: column; gap: 5px; }
|
|
265
|
+
#live .rows { display: flex; flex-direction: column; gap: 6px; }
|
|
260
266
|
.dep-row {
|
|
261
267
|
display: grid;
|
|
262
|
-
grid-template-columns:
|
|
263
|
-
gap: 2px
|
|
268
|
+
grid-template-columns: minmax(140px, 220px) 1fr max-content;
|
|
269
|
+
gap: 2px 18px; align-items: center;
|
|
264
270
|
background: var(--surface);
|
|
265
|
-
border
|
|
266
|
-
|
|
271
|
+
border: 1px solid var(--line);
|
|
272
|
+
border-radius: 8px;
|
|
273
|
+
padding: 10px 14px 10px 16px;
|
|
267
274
|
}
|
|
268
|
-
.dep-row.waiting { background: var(--warn-bg);
|
|
269
|
-
.dep-time {
|
|
275
|
+
.dep-row.waiting { background: var(--warn-bg); border-color: var(--warn); }
|
|
276
|
+
.dep-time { display: none; }
|
|
270
277
|
.dep-proj {
|
|
271
|
-
font-size:
|
|
278
|
+
font-size: 15px; font-weight: 600; line-height: 1.25; color: var(--ink);
|
|
272
279
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
273
280
|
min-width: 0;
|
|
274
281
|
}
|
|
275
|
-
.dep-proj .badge { vertical-align:
|
|
282
|
+
.dep-proj .badge { vertical-align: 1px; font-weight: 400; }
|
|
276
283
|
.dep-mid { min-width: 0; }
|
|
277
284
|
.dep-title {
|
|
278
|
-
font-size:
|
|
285
|
+
font-size: 13.5px; color: var(--ink); cursor: pointer;
|
|
279
286
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
280
287
|
}
|
|
281
|
-
.dep-title:hover { color: var(--
|
|
288
|
+
.dep-title:hover { color: var(--accent); }
|
|
282
289
|
.dep-task {
|
|
283
|
-
margin-top: 2px; font-size:
|
|
290
|
+
margin-top: 2px; font-size: 12px; color: var(--muted);
|
|
284
291
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
285
292
|
}
|
|
286
293
|
.dep-task .prompt-ch { color: var(--accent); margin-right: 6px; }
|
|
287
294
|
.dep-task .count { color: var(--faint); margin-left: 6px; }
|
|
288
295
|
.dep-reason {
|
|
289
|
-
margin-top: 2px; font-size:
|
|
296
|
+
margin-top: 2px; font-size: 12.5px; color: var(--ink);
|
|
290
297
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
291
298
|
}
|
|
292
299
|
.dep-reason .glyph { color: var(--warn); margin-right: 6px; font-weight: 600; }
|
|
293
|
-
.dep-right { display: flex; align-items: center; gap:
|
|
294
|
-
.dep-elapsed { color: var(--faint); font-size: 12px; }
|
|
300
|
+
.dep-right { display: flex; align-items: center; gap: 14px; justify-self: end; }
|
|
301
|
+
.dep-elapsed { color: var(--faint); font-size: 12px; font-family: var(--mono-font); }
|
|
295
302
|
.dep-elapsed.overdue { color: var(--warn); font-weight: 600; }
|
|
296
303
|
.unpriced { color: var(--warn); font-weight: 700; margin-left: 2px; cursor: help; }
|
|
297
|
-
.dep-cost { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.
|
|
304
|
+
.dep-cost { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.2; min-width: 52px; font-family: var(--mono-font); }
|
|
298
305
|
.dep-cost-main { color: var(--muted); font-size: 12px; }
|
|
299
306
|
.dep-burn { color: var(--faint); font-size: 10.5px; }
|
|
300
|
-
.ctx { display: inline-flex; flex-direction: column; gap:
|
|
301
|
-
.ctx-label { font-size: 10.5px; color: var(--faint); text-align: right; white-space: nowrap; }
|
|
307
|
+
.ctx { display: inline-flex; flex-direction: column; gap: 3px; min-width: 64px; }
|
|
308
|
+
.ctx-label { font-size: 10.5px; color: var(--faint); text-align: right; white-space: nowrap; font-family: var(--mono-font); }
|
|
302
309
|
.ctx-bar { height: 3px; background: var(--surface2); border-radius: 2px; overflow: hidden; }
|
|
303
310
|
.ctx-fill { height: 100%; background: var(--accent); }
|
|
304
311
|
.ctx.warn .ctx-fill { background: var(--warn); }
|
|
@@ -307,7 +314,11 @@
|
|
|
307
314
|
.ctx.bad .ctx-label { color: var(--bad); }
|
|
308
315
|
.mc-meta .failed { color: var(--warn); }
|
|
309
316
|
.mc-meta.overdue { color: var(--warn); font-weight: 600; }
|
|
310
|
-
|
|
317
|
+
/* The status lamp is the one split-flap left: the loudest thing on screen. */
|
|
318
|
+
.st {
|
|
319
|
+
font-family: var(--disp-font); text-transform: uppercase; font-weight: 500;
|
|
320
|
+
font-size: 11.5px; letter-spacing: 0.12em; padding: 3px 10px 2px;
|
|
321
|
+
}
|
|
311
322
|
.st.st-busy { color: var(--accent); }
|
|
312
323
|
.st.st-waiting {
|
|
313
324
|
background: var(--warn); color: var(--amber-ink); font-weight: 600;
|
|
@@ -315,105 +326,141 @@
|
|
|
315
326
|
}
|
|
316
327
|
.st.st-waiting::after { background: rgba(0, 0, 0, 0.22); }
|
|
317
328
|
.st.st-quiet { color: var(--warn); }
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
.
|
|
323
|
-
|
|
329
|
+
.live-none { color: var(--muted); font-size: 13px; }
|
|
330
|
+
.live-none code { color: var(--ink); }
|
|
331
|
+
|
|
332
|
+
/* Row actions (rename / open / copy) wait for hover so titles get the room. */
|
|
333
|
+
.acts { display: inline-flex; align-items: center; gap: 2px; flex: none; }
|
|
334
|
+
@media (hover: hover) {
|
|
335
|
+
.acts { opacity: 0; transition: opacity 0.12s; }
|
|
336
|
+
li:hover > .acts, li:focus-within > .acts,
|
|
337
|
+
.dep-row:hover .acts, .dep-row:focus-within .acts,
|
|
338
|
+
.pin-row:hover .acts, .pin-row:focus-within .acts,
|
|
339
|
+
.digest-item:hover .acts, .digest-item:focus-within .acts { opacity: 1; }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/* In the narrow project cards the actions float over the row's right end. */
|
|
343
|
+
@media (hover: hover) {
|
|
344
|
+
.sessions li { position: relative; }
|
|
345
|
+
.sessions li > .acts {
|
|
346
|
+
position: absolute; right: 0; top: 50%; transform: translateY(-50%);
|
|
347
|
+
padding-left: 28px;
|
|
348
|
+
background: linear-gradient(to right, transparent, var(--surface) 24px);
|
|
349
|
+
pointer-events: none;
|
|
350
|
+
}
|
|
351
|
+
.sessions li:hover > .acts, .sessions li:focus-within > .acts { pointer-events: auto; }
|
|
352
|
+
}
|
|
353
|
+
.hidden-note {
|
|
354
|
+
align-self: flex-start; margin: 6px 0 4px; padding: 2px 0;
|
|
355
|
+
border: none; background: none; cursor: pointer;
|
|
356
|
+
font: 12px var(--ui-font); color: var(--faint);
|
|
357
|
+
}
|
|
358
|
+
.hidden-note:hover { color: var(--accent); }
|
|
359
|
+
.hidden-note:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
|
|
360
|
+
.sessions + .hidden-note { border-top: 1px solid var(--line); align-self: stretch; text-align: left; padding-top: 8px; margin-top: 0; }
|
|
361
|
+
.d-noise { margin-top: 10px; }
|
|
362
|
+
.d-noise > summary { cursor: pointer; color: var(--faint); font-size: 12.5px; padding: 4px 0; }
|
|
363
|
+
.d-noise > summary:hover { color: var(--ink); }
|
|
364
|
+
.card .new-btn { font-size: 16px; line-height: 1; padding: 2px 7px; color: var(--faint); }
|
|
365
|
+
.card .new-btn:hover { color: var(--accent); }
|
|
324
366
|
|
|
325
367
|
/* ---------- project grid ---------- */
|
|
326
|
-
main { padding:
|
|
368
|
+
main { padding: 22px 24px; }
|
|
327
369
|
#grid {
|
|
328
370
|
display: grid; gap: 14px;
|
|
329
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
371
|
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
330
372
|
}
|
|
331
373
|
.card {
|
|
332
374
|
background: var(--surface);
|
|
333
|
-
border
|
|
334
|
-
border-
|
|
335
|
-
padding:
|
|
375
|
+
border: 1px solid var(--line);
|
|
376
|
+
border-radius: 10px;
|
|
377
|
+
padding: 14px 16px 8px;
|
|
336
378
|
display: flex; flex-direction: column;
|
|
379
|
+
min-width: 0;
|
|
337
380
|
}
|
|
338
381
|
.card .head { display: flex; align-items: center; gap: 8px; }
|
|
339
382
|
.card .name {
|
|
340
|
-
font-
|
|
341
|
-
text-
|
|
342
|
-
font-weight: 500; font-size: 15px;
|
|
383
|
+
font-weight: 600; font-size: 15px; line-height: 1.3;
|
|
384
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
|
|
343
385
|
}
|
|
344
|
-
.card .
|
|
345
|
-
.card .
|
|
346
|
-
.card .
|
|
386
|
+
.card .name:hover { color: var(--accent); }
|
|
387
|
+
.card .live-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--accent); flex: none; box-shadow: var(--glow) var(--accent); }
|
|
388
|
+
.card .spark { margin-left: auto; opacity: 0.8; flex: none; }
|
|
389
|
+
.card .meta { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; margin-top: 6px; font-size: 11.5px; }
|
|
347
390
|
.badge {
|
|
348
|
-
padding:
|
|
349
|
-
font-
|
|
391
|
+
padding: 1px 6px; border-radius: 4px; background: var(--chip-bg); color: var(--muted);
|
|
392
|
+
font-size: 11px; white-space: nowrap;
|
|
350
393
|
}
|
|
351
394
|
.badge.dirty { color: var(--warn); }
|
|
352
395
|
.badge.ahead { color: var(--accent); }
|
|
353
396
|
.badge.err { color: var(--bad); }
|
|
354
397
|
.badge.bg { color: var(--accent); letter-spacing: 0.06em; }
|
|
355
398
|
.badge.missing { color: var(--bad); }
|
|
399
|
+
.badge.changes { cursor: pointer; }
|
|
356
400
|
.note-mark { color: var(--warn); cursor: default; font-size: 12px; flex: none; }
|
|
357
401
|
#note-editor { margin: 10px 0 4px; display: flex; flex-direction: column; gap: 6px; }
|
|
358
402
|
#note-editor textarea {
|
|
359
403
|
width: 100%; min-height: 72px; resize: vertical; box-sizing: border-box;
|
|
360
|
-
font: 12.5px
|
|
361
|
-
background: var(--surface); border: 1px solid var(--line); border-radius:
|
|
404
|
+
font: 12.5px var(--mono-font); color: var(--ink);
|
|
405
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 6px; padding: 8px 10px;
|
|
362
406
|
}
|
|
363
407
|
#note-editor .hint { color: var(--faint); font-size: 11px; }
|
|
364
|
-
.card .when { color: var(--faint); font-size:
|
|
408
|
+
.card .when { color: var(--faint); font-size: 11.5px; margin-left: auto; }
|
|
365
409
|
.sessions { list-style: none; padding: 0; margin: 10px 0 0; border-top: 1px solid var(--line); }
|
|
366
410
|
.sessions li {
|
|
367
411
|
display: flex; align-items: center; gap: 8px;
|
|
368
412
|
padding: 7px 0; border-bottom: 1px solid var(--line);
|
|
369
|
-
font-size:
|
|
413
|
+
font-size: 13px; min-height: 34px;
|
|
370
414
|
}
|
|
371
415
|
.sessions li:last-child { border-bottom: none; }
|
|
372
|
-
.sessions .s-title {
|
|
416
|
+
.sessions .s-title, #pinned .s-title {
|
|
373
417
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
374
|
-
color: var(--ink); flex: 1;
|
|
418
|
+
color: var(--ink); flex: 1; min-width: 0;
|
|
375
419
|
}
|
|
376
|
-
.sessions .s-title .wt { color: var(--accent); font-size:
|
|
377
|
-
.
|
|
420
|
+
.sessions .s-title .wt { color: var(--accent); font-size: 11.5px; margin-right: 5px; }
|
|
421
|
+
.s-when { color: var(--faint); font-size: 11.5px; flex: none; white-space: nowrap; }
|
|
378
422
|
.copy {
|
|
379
|
-
flex: none; border: none; background: none; color: var(--
|
|
380
|
-
font-size:
|
|
423
|
+
flex: none; border: none; background: none; color: var(--muted);
|
|
424
|
+
font-size: 12px; cursor: pointer; padding: 3px 7px; border-radius: 5px;
|
|
381
425
|
font-family: inherit;
|
|
382
426
|
}
|
|
383
427
|
.copy:hover { background: var(--surface2); color: var(--ink); }
|
|
384
428
|
.copy:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
385
429
|
.open-btn { color: var(--accent); }
|
|
386
|
-
#open-pref-wrap { font-size:
|
|
430
|
+
#open-pref-wrap { font-size: 12.5px; color: var(--muted); display: flex; align-items: center; gap: 6px; white-space: nowrap; }
|
|
431
|
+
#open-pref-wrap[hidden] { display: none; }
|
|
387
432
|
#open-pref {
|
|
388
433
|
font: inherit; color: var(--ink);
|
|
389
434
|
background: var(--surface2); border: 1px solid var(--line);
|
|
390
|
-
border-radius:
|
|
435
|
+
border-radius: 6px; padding: 3px 6px;
|
|
391
436
|
}
|
|
392
437
|
#search {
|
|
393
438
|
font: inherit; color: var(--ink);
|
|
394
|
-
background: var(--
|
|
395
|
-
border-radius:
|
|
439
|
+
background: var(--bg); border: 1px solid var(--line);
|
|
440
|
+
border-radius: 8px; padding: 6px 12px; width: 260px; margin-left: 10px;
|
|
396
441
|
}
|
|
397
|
-
#search:focus { outline: 2px solid var(--accent); outline-offset:
|
|
442
|
+
#search:focus { outline: 2px solid var(--accent); outline-offset: -1px; }
|
|
398
443
|
#search::placeholder { color: var(--faint); }
|
|
399
|
-
/* A full set of meters needs
|
|
400
|
-
@media (max-width:
|
|
444
|
+
/* A full set of meters needs room; tighten before the header wraps. */
|
|
445
|
+
@media (max-width: 1500px) {
|
|
401
446
|
header { gap: 12px; }
|
|
402
|
-
#search { width:
|
|
447
|
+
#search { width: 180px; }
|
|
403
448
|
#open-pref-wrap .open-label { display: none; }
|
|
404
|
-
#quota { gap:
|
|
449
|
+
#quota { gap: 14px; }
|
|
405
450
|
.meter .bar { width: 44px; }
|
|
451
|
+
#wk-chart { display: none; }
|
|
406
452
|
}
|
|
407
|
-
|
|
453
|
+
.hd-btn {
|
|
408
454
|
border: none; background: none; color: var(--muted); cursor: pointer;
|
|
409
|
-
font-size: 15px; padding:
|
|
455
|
+
font-size: 15px; line-height: 1; padding: 6px 8px; border-radius: 6px;
|
|
410
456
|
}
|
|
411
|
-
|
|
457
|
+
.hd-btn:hover { background: var(--surface2); color: var(--ink); }
|
|
458
|
+
.hd-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
412
459
|
#bell-btn { position: relative; }
|
|
413
460
|
#bell-badge {
|
|
414
|
-
position: absolute; top: -
|
|
461
|
+
position: absolute; top: -2px; right: -2px;
|
|
415
462
|
background: var(--warn); color: var(--amber-ink);
|
|
416
|
-
font: 700 9.5px
|
|
463
|
+
font: 700 9.5px var(--mono-font);
|
|
417
464
|
border-radius: 8px; padding: 1px 4px; min-width: 10px; text-align: center;
|
|
418
465
|
}
|
|
419
466
|
|
|
@@ -429,11 +476,11 @@
|
|
|
429
476
|
.set-row input[type="text"] {
|
|
430
477
|
font: inherit; color: var(--ink);
|
|
431
478
|
background: var(--surface2); border: 1px solid var(--line);
|
|
432
|
-
border-radius:
|
|
479
|
+
border-radius: 6px; padding: 4px 8px; width: 170px;
|
|
433
480
|
}
|
|
434
481
|
.set-row select {
|
|
435
482
|
font: inherit; color: var(--ink); background: var(--surface2);
|
|
436
|
-
border: 1px solid var(--line); border-radius:
|
|
483
|
+
border: 1px solid var(--line); border-radius: 6px; padding: 3px 6px;
|
|
437
484
|
}
|
|
438
485
|
.set-row input[type="checkbox"] { accent-color: var(--accent); width: 15px; height: 15px; }
|
|
439
486
|
|
|
@@ -445,7 +492,7 @@
|
|
|
445
492
|
.rename-input {
|
|
446
493
|
font: inherit; font-size: 12.5px; color: var(--ink);
|
|
447
494
|
background: var(--surface2); border: 1px solid var(--accent);
|
|
448
|
-
border-radius:
|
|
495
|
+
border-radius: 6px; padding: 2px 6px; flex: 1; min-width: 0; width: 100%;
|
|
449
496
|
box-sizing: border-box;
|
|
450
497
|
}
|
|
451
498
|
.rename-input:focus { outline: none; }
|
|
@@ -457,8 +504,7 @@
|
|
|
457
504
|
border-radius: 3px; padding: 8px 12px; font-size: 13px;
|
|
458
505
|
white-space: pre-wrap; overflow-wrap: break-word;
|
|
459
506
|
}
|
|
460
|
-
.t-user .t-who { color: var(--accent); font-size:
|
|
461
|
-
letter-spacing: 0.08em; display: block; margin-bottom: 3px; }
|
|
507
|
+
.t-user .t-who { color: var(--accent); font-size: 11.5px; font-weight: 600; display: block; margin-bottom: 3px; }
|
|
462
508
|
.t-tool {
|
|
463
509
|
font-family: inherit; font-size: 11.5px;
|
|
464
510
|
color: var(--muted); padding: 1px 0 1px 10px;
|
|
@@ -476,16 +522,11 @@
|
|
|
476
522
|
.none-yet { color: var(--faint); font-size: 12px; padding: 10px 0 4px; }
|
|
477
523
|
|
|
478
524
|
/* ---------- pinned strip ---------- */
|
|
479
|
-
#pinned { margin-bottom:
|
|
480
|
-
#pinned .pin-label {
|
|
481
|
-
font-family: var(--disp-font);
|
|
482
|
-
font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.22em;
|
|
483
|
-
color: var(--faint); margin-bottom: 6px;
|
|
484
|
-
}
|
|
525
|
+
#pinned { margin-bottom: 22px; }
|
|
485
526
|
#pinned .pin-row {
|
|
486
|
-
display: flex; align-items:
|
|
487
|
-
background: var(--surface); border-radius:
|
|
488
|
-
padding:
|
|
527
|
+
display: flex; align-items: center; gap: 10px;
|
|
528
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
|
|
529
|
+
padding: 8px 14px; margin-bottom: 6px; min-height: 38px;
|
|
489
530
|
}
|
|
490
531
|
#pinned .pin-row .star { color: var(--warn); flex: none; }
|
|
491
532
|
|
|
@@ -509,78 +550,71 @@
|
|
|
509
550
|
|
|
510
551
|
/* ---------- git attention strip ---------- */
|
|
511
552
|
#git-attn {
|
|
512
|
-
display: flex; align-items:
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
padding:
|
|
516
|
-
font-size:
|
|
553
|
+
display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px;
|
|
554
|
+
border: 1px solid var(--line); border-left: 3px solid var(--warn); border-radius: 8px;
|
|
555
|
+
background: var(--surface);
|
|
556
|
+
padding: 10px 16px; margin-bottom: 22px;
|
|
557
|
+
font-size: 13px;
|
|
517
558
|
}
|
|
518
|
-
#git-attn .ga-label { color: var(--
|
|
519
|
-
#git-attn .ga-item { color: var(--
|
|
520
|
-
#git-attn .ga-item .badge { margin-left:
|
|
559
|
+
#git-attn .ga-label { color: var(--ink); font-weight: 600; white-space: nowrap; }
|
|
560
|
+
#git-attn .ga-item { color: var(--muted); white-space: nowrap; }
|
|
561
|
+
#git-attn .ga-item .badge { margin-left: 5px; }
|
|
521
562
|
|
|
522
563
|
/* ---------- digest ---------- */
|
|
523
|
-
#digest { margin-bottom:
|
|
524
|
-
#digest summary {
|
|
564
|
+
#digest { margin-bottom: 28px; }
|
|
565
|
+
#digest summary, #dormant summary {
|
|
525
566
|
cursor: pointer; user-select: none; list-style: none;
|
|
526
567
|
display: flex; align-items: center; gap: 12px;
|
|
527
568
|
}
|
|
528
|
-
#digest summary::-webkit-details-marker { display: none; }
|
|
529
|
-
#digest-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
569
|
+
#digest summary::-webkit-details-marker, #dormant summary::-webkit-details-marker { display: none; }
|
|
570
|
+
#digest summary .sec-head, #dormant summary .sec-head { margin-bottom: 0; }
|
|
571
|
+
.sec-head .caret {
|
|
572
|
+
display: inline-block; width: 10px; color: var(--faint); font-size: 10px;
|
|
573
|
+
transition: transform 0.12s;
|
|
533
574
|
}
|
|
534
|
-
|
|
535
|
-
#digest
|
|
536
|
-
#digest-range { display: none; gap: 2px; }
|
|
575
|
+
details[open] > summary .caret { transform: rotate(90deg); }
|
|
576
|
+
#digest-range { display: none; padding: 2px; gap: 2px; background: var(--surface); border: 1px solid var(--line); border-radius: 7px; }
|
|
537
577
|
#digest[open] #digest-range { display: inline-flex; }
|
|
538
578
|
#digest-range button {
|
|
539
|
-
font:
|
|
540
|
-
background: none; border:
|
|
541
|
-
padding:
|
|
579
|
+
font: 12px var(--ui-font); color: var(--muted);
|
|
580
|
+
background: none; border: none; border-radius: 5px; cursor: pointer;
|
|
581
|
+
padding: 3px 10px;
|
|
542
582
|
}
|
|
543
|
-
#digest-range button:
|
|
544
|
-
#digest-range button:last-child { border-radius: 0 2px 2px 0; }
|
|
583
|
+
#digest-range button:hover { color: var(--ink); }
|
|
545
584
|
#digest-range button.sel { background: var(--surface2); color: var(--ink); }
|
|
546
|
-
#digest-body { margin-top:
|
|
585
|
+
#digest-body { margin-top: 12px; }
|
|
547
586
|
.digest-day {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
letter-spacing: 0.22em; margin: 16px 0 6px;
|
|
587
|
+
color: var(--muted); font-size: 12.5px; font-weight: 500;
|
|
588
|
+
margin: 16px 0 8px;
|
|
551
589
|
}
|
|
590
|
+
.digest-day:first-child { margin-top: 0; }
|
|
552
591
|
.digest-item {
|
|
553
|
-
background: var(--surface); border-radius:
|
|
554
|
-
padding:
|
|
592
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 8px;
|
|
593
|
+
padding: 10px 14px; margin-bottom: 6px;
|
|
555
594
|
}
|
|
556
|
-
.digest-item .di-top { display: flex; align-items:
|
|
595
|
+
.digest-item .di-top { display: flex; align-items: center; gap: 10px; min-width: 0; }
|
|
557
596
|
.digest-item .di-proj {
|
|
558
|
-
font-
|
|
559
|
-
text-
|
|
560
|
-
font-weight: 500; font-size: 14.5px;
|
|
597
|
+
font-weight: 600; font-size: 13.5px; flex: none;
|
|
598
|
+
max-width: 30%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
561
599
|
}
|
|
562
|
-
.digest-item .di-title { color: var(--muted); font-size:
|
|
563
|
-
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width:
|
|
564
|
-
.digest-item .di-when { color: var(--faint); font-size:
|
|
600
|
+
.digest-item .di-title { color: var(--muted); font-size: 13.5px;
|
|
601
|
+
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0; }
|
|
602
|
+
.digest-item .di-when { color: var(--faint); font-size: 11.5px; font-family: var(--mono-font); white-space: nowrap; flex: none; }
|
|
565
603
|
.digest-item .di-summary {
|
|
566
|
-
margin-top: 6px; font-size:
|
|
604
|
+
margin-top: 6px; font-size: 13px; color: var(--ink); line-height: 1.55; max-width: 110ch;
|
|
567
605
|
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
|
568
606
|
cursor: pointer;
|
|
569
607
|
}
|
|
570
608
|
.digest-item .di-summary.expanded { display: block; }
|
|
571
|
-
.digest-item .di-tasks { margin-top: 5px; font-size:
|
|
609
|
+
.digest-item .di-tasks { margin-top: 5px; font-size: 12px; color: var(--faint); }
|
|
572
610
|
.digest-item .di-tasks .done { color: var(--accent); }
|
|
573
|
-
.digest-empty { color: var(--faint); font-size:
|
|
611
|
+
.digest-empty { color: var(--faint); font-size: 13px; padding: 6px 0; }
|
|
574
612
|
|
|
575
613
|
/* ---------- dormant ---------- */
|
|
576
|
-
#dormant { margin-top:
|
|
577
|
-
#dormant summary {
|
|
578
|
-
font-family: var(--disp-font);
|
|
579
|
-
cursor: pointer; color: var(--faint); font-size: 12px;
|
|
580
|
-
text-transform: uppercase; letter-spacing: 0.22em; user-select: none;
|
|
581
|
-
}
|
|
614
|
+
#dormant { margin-top: 32px; }
|
|
615
|
+
#grid + #dormant summary .sec-head { color: var(--muted); }
|
|
582
616
|
#dormant .cards { margin-top: 12px; display: grid; gap: 14px;
|
|
583
|
-
grid-template-columns: repeat(auto-fill, minmax(
|
|
617
|
+
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); }
|
|
584
618
|
#dormant .card { opacity: 0.75; }
|
|
585
619
|
|
|
586
620
|
/* ---------- mission control ---------- */
|
|
@@ -592,13 +626,10 @@
|
|
|
592
626
|
#mc[hidden] { display: none !important; }
|
|
593
627
|
.mc-head {
|
|
594
628
|
display: flex; align-items: center; gap: 14px;
|
|
595
|
-
padding: 12px
|
|
629
|
+
padding: 12px 24px; border-bottom: 1px solid var(--line);
|
|
596
630
|
background: var(--surface); flex: none;
|
|
597
631
|
}
|
|
598
|
-
.mc-title {
|
|
599
|
-
font-family: var(--disp-font);
|
|
600
|
-
font-size: 16px; font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase;
|
|
601
|
-
}
|
|
632
|
+
.mc-title { font-size: 15px; font-weight: 650; }
|
|
602
633
|
.mc-sub { color: var(--faint); font-size: 12px; }
|
|
603
634
|
#mc-grid {
|
|
604
635
|
flex: 1; overflow-y: auto;
|
|
@@ -609,7 +640,7 @@
|
|
|
609
640
|
#mc-grid .mc-empty { color: var(--faint); font-size: 13px; padding: 24px 0; }
|
|
610
641
|
.mc-pane {
|
|
611
642
|
display: flex; flex-direction: column;
|
|
612
|
-
background: var(--surface); border-radius:
|
|
643
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 10px; overflow: hidden;
|
|
613
644
|
height: min(46vh, 480px); min-width: 0;
|
|
614
645
|
}
|
|
615
646
|
.mc-pane.waiting { box-shadow: inset 0 2px 0 var(--warn); }
|
|
@@ -621,9 +652,7 @@
|
|
|
621
652
|
.mc-pane-head .mc-proj { min-width: 0; flex: 0 1 auto; }
|
|
622
653
|
.mc-stats { flex: 1 0 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; min-width: 0; }
|
|
623
654
|
.mc-pane-head .mc-proj {
|
|
624
|
-
font-
|
|
625
|
-
text-transform: uppercase; letter-spacing: 0.05em;
|
|
626
|
-
font-weight: 500; font-size: 15px; cursor: pointer;
|
|
655
|
+
font-weight: 600; font-size: 14.5px; cursor: pointer;
|
|
627
656
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
628
657
|
}
|
|
629
658
|
.mc-pane-head .mc-proj:hover { color: var(--warn); }
|
|
@@ -649,8 +678,8 @@
|
|
|
649
678
|
display: grid; gap: 10px; padding: 14px 20px 0;
|
|
650
679
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
651
680
|
}
|
|
652
|
-
.hv-tile { background: var(--surface); border-radius:
|
|
653
|
-
.hv-tile .k { color: var(--
|
|
681
|
+
.hv-tile { background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 9px 12px; min-width: 0; }
|
|
682
|
+
.hv-tile .k { color: var(--muted); font-size: 12px; font-weight: 500; }
|
|
654
683
|
.hv-tile .v { font-family: var(--disp-font); font-size: 26px; font-weight: 500; line-height: 1.15; }
|
|
655
684
|
.hv-tile .s { color: var(--faint); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
656
685
|
.hv-tile.warn .v, .hv-tile .eta { color: var(--warn); }
|
|
@@ -661,7 +690,7 @@
|
|
|
661
690
|
}
|
|
662
691
|
.hv-empty { color: var(--faint); font-size: 13px; padding: 24px 20px; }
|
|
663
692
|
.hv-card {
|
|
664
|
-
background: var(--surface); border-radius:
|
|
693
|
+
background: var(--surface); border: 1px solid var(--line); border-radius: 10px; min-width: 0;
|
|
665
694
|
border-top: 3px solid var(--hue); display: flex; flex-direction: column;
|
|
666
695
|
}
|
|
667
696
|
.hv-card.waiting { border-top-color: var(--warn); box-shadow: inset 0 0 0 1px var(--warn-bg); }
|
|
@@ -683,14 +712,14 @@
|
|
|
683
712
|
.hv-money .big { font-family: var(--disp-font); font-size: 38px; font-weight: 500; line-height: 1; }
|
|
684
713
|
.hv-money .rate { color: var(--muted); font-size: 12px; }
|
|
685
714
|
.hv-state {
|
|
686
|
-
margin: 0 14px; padding: 7px 10px; border-radius:
|
|
715
|
+
margin: 0 14px; padding: 7px 10px; border-radius: 6px; background: var(--surface2);
|
|
687
716
|
display: flex; gap: 10px; align-items: center; min-width: 0; font-size: 12px;
|
|
688
717
|
}
|
|
689
718
|
.hv-state .txt { color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
690
719
|
.hv-card.waiting .hv-state { background: var(--warn-bg); }
|
|
691
720
|
.hv-card.waiting .hv-state .txt { color: var(--ink); }
|
|
692
721
|
.hv-sec { padding: 12px 14px 0; }
|
|
693
|
-
.hv-label { color: var(--
|
|
722
|
+
.hv-label { color: var(--muted); font-size: 12px; font-weight: 500; display: flex; justify-content: space-between; gap: 8px; }
|
|
694
723
|
.hv-call { margin-top: 5px; font-size: 13px; display: flex; gap: 6px; min-width: 0; }
|
|
695
724
|
.hv-call .verb { color: var(--muted); flex: none; }
|
|
696
725
|
.hv-call .tgt { color: var(--ink); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
|
|
@@ -702,7 +731,7 @@
|
|
|
702
731
|
.hv-task { margin-top: 6px; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
703
732
|
.hv-task .prompt-ch { color: var(--accent); margin-right: 6px; }
|
|
704
733
|
.hv-bars { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 14px 14px 0; }
|
|
705
|
-
.hv-bar .row { display: flex; justify-content: space-between; color: var(--
|
|
734
|
+
.hv-bar .row { display: flex; justify-content: space-between; color: var(--muted); font-size: 12px; }
|
|
706
735
|
.hv-bar .row b { color: var(--muted); font-weight: 500; letter-spacing: 0; }
|
|
707
736
|
.hv-bar .track { height: 4px; background: var(--surface2); margin-top: 5px; border-radius: 2px; overflow: hidden; }
|
|
708
737
|
.hv-bar .fill { display: block; height: 100%; background: var(--hue); }
|
|
@@ -712,14 +741,14 @@
|
|
|
712
741
|
display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
|
|
713
742
|
padding: 12px 14px 12px; margin-top: 12px; border-top: 1px solid var(--line);
|
|
714
743
|
}
|
|
715
|
-
.hv-foot .k { color: var(--
|
|
744
|
+
.hv-foot .k { color: var(--muted); font-size: 12px; }
|
|
716
745
|
.hv-foot .v { font-size: 13px; margin-top: 2px; }
|
|
717
746
|
.hv-foot .v.bad { color: var(--bad); }
|
|
718
747
|
.hv-note { color: var(--faint); font-size: 11px; padding: 0 20px 24px; }
|
|
719
748
|
.fm-sum { color: var(--muted); font-size: 12px; margin: 4px 0 14px; }
|
|
720
|
-
.fm-award { margin: 0 0 18px; padding: 12px 14px; background: var(--surface); border-radius:
|
|
749
|
+
.fm-award { margin: 0 0 18px; padding: 12px 14px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; }
|
|
721
750
|
.fm-head { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
|
|
722
|
-
.fm-title { font-
|
|
751
|
+
.fm-title { font-size: 16px; font-weight: 650; }
|
|
723
752
|
.fm-blurb { color: var(--faint); font-size: 11.5px; flex: 1; min-width: 0; }
|
|
724
753
|
.fm-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; border-top: 1px solid var(--line); min-width: 0; }
|
|
725
754
|
.fm-head + .fm-row { border-top: none; margin-top: 6px; }
|
|
@@ -742,7 +771,7 @@
|
|
|
742
771
|
|
|
743
772
|
.mc-agents .agents { max-height: 160px; overflow-y: auto; }
|
|
744
773
|
.agents { padding: 4px 12px 6px; border-bottom: 1px solid var(--line); font-size: 11.5px; flex: none; }
|
|
745
|
-
.agents .team { color: var(--
|
|
774
|
+
.agents .team { color: var(--muted); font-size: 12px; font-weight: 500; margin-bottom: 2px; }
|
|
746
775
|
.agents .agent { display: flex; align-items: center; gap: 7px; line-height: 1.6; }
|
|
747
776
|
.agents .swatch { width: 7px; height: 7px; border-radius: 50%; flex: none; }
|
|
748
777
|
.agents .aname { color: var(--ink); }
|
|
@@ -771,10 +800,7 @@
|
|
|
771
800
|
display: flex; align-items: baseline; gap: 10px;
|
|
772
801
|
padding: 16px 22px 12px; border-bottom: 1px solid var(--line);
|
|
773
802
|
}
|
|
774
|
-
#drawer .d-head h2 {
|
|
775
|
-
font-family: var(--disp-font);
|
|
776
|
-
font-size: 17px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
|
|
777
|
-
}
|
|
803
|
+
#drawer .d-head h2 { font-size: 17px; font-weight: 650; }
|
|
778
804
|
#drawer .d-head .d-path {
|
|
779
805
|
color: var(--faint); font-size: 11px;
|
|
780
806
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1;
|
|
@@ -782,15 +808,15 @@
|
|
|
782
808
|
#drawer .d-actions { display: flex; gap: 2px; align-items: center; flex: none; }
|
|
783
809
|
#drawer .d-close {
|
|
784
810
|
border: none; background: none; color: var(--muted); cursor: pointer;
|
|
785
|
-
font-size: 18px; padding: 2px 8px; border-radius:
|
|
811
|
+
font-size: 18px; padding: 2px 8px; border-radius: 6px;
|
|
786
812
|
}
|
|
787
813
|
#drawer .d-close:hover { background: var(--surface2); color: var(--ink); }
|
|
788
814
|
#drawer .d-body { overflow-y: auto; padding: 6px 22px 40px; }
|
|
789
815
|
.d-section { margin-top: 18px; }
|
|
790
816
|
.d-section > summary {
|
|
791
817
|
cursor: pointer; user-select: none; list-style: none;
|
|
792
|
-
font-size:
|
|
793
|
-
color: var(--
|
|
818
|
+
font-size: 13px; font-weight: 600;
|
|
819
|
+
color: var(--muted); padding: 4px 0;
|
|
794
820
|
}
|
|
795
821
|
.d-section > summary::before { content: '▸ '; }
|
|
796
822
|
.d-section[open] > summary::before { content: '▾ '; }
|
|
@@ -804,7 +830,11 @@
|
|
|
804
830
|
font-size: 12px; color: var(--accent); margin-bottom: 6px;
|
|
805
831
|
}
|
|
806
832
|
.d-sessions li { display: flex; align-items: center; gap: 8px;
|
|
807
|
-
padding:
|
|
833
|
+
padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 13px; min-height: 36px; }
|
|
834
|
+
.d-sessions .s-title {
|
|
835
|
+
flex: 1; min-width: 0; overflow: hidden;
|
|
836
|
+
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
|
|
837
|
+
}
|
|
808
838
|
.d-sessions li:last-child { border-bottom: none; }
|
|
809
839
|
.d-sessions { list-style: none; padding: 0; margin: 6px 0 0; }
|
|
810
840
|
|
|
@@ -820,7 +850,7 @@
|
|
|
820
850
|
background: var(--surface2); padding: 1px 4px; border-radius: 4px;
|
|
821
851
|
}
|
|
822
852
|
.md pre {
|
|
823
|
-
background: var(--surface2); border-radius:
|
|
853
|
+
background: var(--surface2); border-radius: 6px; padding: 10px 12px;
|
|
824
854
|
overflow-x: auto; margin: 8px 0;
|
|
825
855
|
}
|
|
826
856
|
.md pre code { background: none; padding: 0; }
|
|
@@ -833,7 +863,7 @@
|
|
|
833
863
|
/* ---------- session changes panel ---------- */
|
|
834
864
|
.badge.changes { color: var(--accent); cursor: pointer; }
|
|
835
865
|
#changes-panel { margin-top: 10px; }
|
|
836
|
-
.d-section-title { font-size:
|
|
866
|
+
.d-section-title { font-size: 13px; font-weight: 600; color: var(--muted); padding: 4px 0; }
|
|
837
867
|
.chg-file { border: 1px solid var(--line); border-radius: 3px; margin-top: 8px; background: var(--surface); }
|
|
838
868
|
.chg-head { display: flex; align-items: center; gap: 10px; padding: 8px 12px; cursor: pointer; font-size: 12.5px; }
|
|
839
869
|
.chg-head .chg-path { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ink); }
|
|
@@ -927,13 +957,13 @@
|
|
|
927
957
|
}
|
|
928
958
|
#welcome h2 { font-size: 14px; margin-bottom: 8px; }
|
|
929
959
|
#welcome h2 .tilde { color: var(--accent); }
|
|
930
|
-
#welcome code { background: var(--surface2); padding: 1px 5px; border-radius:
|
|
960
|
+
#welcome code { background: var(--surface2); padding: 1px 5px; border-radius: 6px; }
|
|
931
961
|
#welcome p { color: var(--muted); font-size: 12.5px; }
|
|
932
962
|
|
|
933
963
|
#toast {
|
|
934
964
|
position: fixed; bottom: 22px; left: 50%; transform: translateX(-50%);
|
|
935
965
|
background: var(--ink); color: var(--bg);
|
|
936
|
-
padding: 7px 14px; border-radius:
|
|
966
|
+
padding: 7px 14px; border-radius: 6px; font-size: 12.5px;
|
|
937
967
|
opacity: 0; transition: opacity 0.18s; pointer-events: none;
|
|
938
968
|
}
|
|
939
969
|
#toast.show { opacity: 1; }
|
|
@@ -941,19 +971,22 @@
|
|
|
941
971
|
/* ---------- phones: everything in one column, nothing scrolls sideways ---------- */
|
|
942
972
|
@media (max-width: 700px) {
|
|
943
973
|
header { padding: 10px 16px; gap: 8px 10px; }
|
|
944
|
-
header h1 { font-size:
|
|
945
|
-
#search { flex: 1 1
|
|
974
|
+
header h1 { font-size: 15px; margin-right: auto; }
|
|
975
|
+
#search { order: 9; flex: 1 1 100%; width: auto; min-width: 0; margin-left: 0; }
|
|
946
976
|
header .spacer, #open-pref-wrap { display: none; } /* opening terminals is a desk thing; still in settings */
|
|
947
977
|
#quota { order: 10; flex: 1 1 100%; flex-wrap: wrap; gap: 8px 14px; }
|
|
948
978
|
.meter .bar { width: 52px; }
|
|
949
979
|
#live { padding: 12px 16px; }
|
|
950
980
|
main { padding: 14px 16px; }
|
|
951
|
-
.dep-row { grid-template-columns:
|
|
952
|
-
.dep-proj { font-size:
|
|
981
|
+
.dep-row { grid-template-columns: 1fr; padding: 10px 12px; }
|
|
982
|
+
.dep-proj { font-size: 15px; }
|
|
953
983
|
.dep-mid, .dep-right { grid-column: 1 / -1; }
|
|
954
984
|
.dep-right { justify-self: stretch; flex-wrap: wrap; gap: 6px 10px; }
|
|
955
985
|
.dep-title, .dep-reason { white-space: normal; overflow-wrap: anywhere; }
|
|
956
986
|
#pinned .pin-row { flex-wrap: wrap; padding: 7px 12px; }
|
|
987
|
+
.digest-item .di-top { flex-wrap: wrap; row-gap: 2px; }
|
|
988
|
+
.digest-item .di-proj { max-width: none; flex: 1 1 auto; }
|
|
989
|
+
.digest-item .di-title { order: 9; flex: 1 1 100%; }
|
|
957
990
|
#git-attn .ga-item { white-space: normal; }
|
|
958
991
|
#drawer { width: 100vw; border-left: none; }
|
|
959
992
|
#drawer .d-head { flex-wrap: wrap; padding: 12px 16px 10px; }
|
|
@@ -980,8 +1013,8 @@
|
|
|
980
1013
|
<body>
|
|
981
1014
|
<header>
|
|
982
1015
|
<div id="conn" title="connection"></div>
|
|
983
|
-
<h1>Claude
|
|
984
|
-
<input id="search" type="search" placeholder="Search
|
|
1016
|
+
<h1>Claude Mission Control</h1>
|
|
1017
|
+
<input id="search" type="search" placeholder="Search sessions" aria-label="Search sessions" autocomplete="off">
|
|
985
1018
|
<div class="spacer"></div>
|
|
986
1019
|
<label id="open-pref-wrap"><span class="open-label">Open in</span>
|
|
987
1020
|
<select id="open-pref" aria-label="Open sessions in">
|
|
@@ -990,10 +1023,10 @@
|
|
|
990
1023
|
</label>
|
|
991
1024
|
<div id="quota"></div>
|
|
992
1025
|
<span id="clock" class="flap" title="board time"></span>
|
|
993
|
-
<button id="mc-btn" title="Mission Control
|
|
994
|
-
<button id="hive-btn" title="Hive: every live session as a character card"
|
|
995
|
-
<button id="bell-btn" title="Catch
|
|
996
|
-
<button id="gear" title="Settings"
|
|
1026
|
+
<button id="mc-btn" class="hd-btn" title="Mission Control: every live session side by side" aria-label="Mission Control"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="2.5" width="5" height="5" rx="1"/><rect x="9" y="2.5" width="5" height="5" rx="1"/><rect x="2" y="9.5" width="5" height="5" rx="1"/><rect x="9" y="9.5" width="5" height="5" rx="1"/></svg></button>
|
|
1027
|
+
<button id="hive-btn" class="hd-btn" title="Hive: every live session as a character card" aria-label="Hive"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 1.8 13.4 4.9v6.2L8 14.2 2.6 11.1V4.9Z"/></svg></button>
|
|
1028
|
+
<button id="bell-btn" class="hd-btn" title="Catch up on what happened while you were away" aria-label="Catch up"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 11V7a4 4 0 0 1 8 0v4l1.2 1.5H2.8Z"/><path d="M6.5 14a1.6 1.6 0 0 0 3 0"/></svg><span id="bell-badge" hidden></span></button>
|
|
1029
|
+
<button id="gear" class="hd-btn" title="Settings" aria-label="Settings"><svg width="16" height="16" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="8" cy="8" r="2.2"/><path d="M8 1.5v2M8 12.5v2M1.5 8h2M12.5 8h2M3.4 3.4l1.4 1.4M11.2 11.2l1.4 1.4M3.4 12.6l1.4-1.4M11.2 4.8l1.4-1.4"/></svg></button>
|
|
997
1030
|
</header>
|
|
998
1031
|
|
|
999
1032
|
<section id="live"></section>
|
|
@@ -1002,13 +1035,14 @@
|
|
|
1002
1035
|
<div id="git-attn" hidden></div>
|
|
1003
1036
|
<details id="digest" open>
|
|
1004
1037
|
<summary>
|
|
1005
|
-
<span id="digest-label">
|
|
1006
|
-
<span id="digest-range" role="group" aria-label="
|
|
1007
|
-
<button data-days="1">
|
|
1038
|
+
<span class="sec-head"><span class="caret">▶</span><span id="digest-label">Recent sessions</span><span class="n" id="digest-n"></span></span>
|
|
1039
|
+
<span id="digest-range" role="group" aria-label="How far back">
|
|
1040
|
+
<button data-days="1">Today</button><button data-days="3">3 days</button><button data-days="7">Week</button>
|
|
1008
1041
|
</span>
|
|
1009
1042
|
</summary>
|
|
1010
1043
|
<div id="digest-body"></div>
|
|
1011
1044
|
</details>
|
|
1045
|
+
<div class="sec-head" id="grid-head" hidden>Projects <span class="n"></span></div>
|
|
1012
1046
|
<div id="grid"></div>
|
|
1013
1047
|
<details id="dormant" hidden>
|
|
1014
1048
|
<summary></summary>
|
|
@@ -1155,7 +1189,7 @@ function meter(label, pct, title, extra = '') {
|
|
|
1155
1189
|
return `<div class="meter" title="${esc(title || '')}">
|
|
1156
1190
|
<span>${esc(label)}</span>
|
|
1157
1191
|
<span class="bar"><span class="fill ${cls}" style="width:${Math.min(100, pct)}%"></span></span>
|
|
1158
|
-
<span>${Math.round(pct)}%</span>${extra}
|
|
1192
|
+
<span class="m-pct">${Math.round(pct)}%</span>${extra}
|
|
1159
1193
|
</div>`;
|
|
1160
1194
|
}
|
|
1161
1195
|
|
|
@@ -1218,6 +1252,11 @@ function modelShort(m) {
|
|
|
1218
1252
|
return m.replace(/^claude-/, '').replace(/-\d{8}$/, '');
|
|
1219
1253
|
}
|
|
1220
1254
|
|
|
1255
|
+
// Tooltip for a clickable session title; the model rides along instead of a chip.
|
|
1256
|
+
function titleTip(m, action = 'Click to read the transcript') {
|
|
1257
|
+
return m ? `${modelShort(m)}. ${action}` : action;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1221
1260
|
function modelChip(m) {
|
|
1222
1261
|
return m ? `<span class="badge" title="${esc(m)}">${esc(modelShort(m))}</span>` : '';
|
|
1223
1262
|
}
|
|
@@ -1265,7 +1304,7 @@ function noteMark(sess) {
|
|
|
1265
1304
|
|
|
1266
1305
|
function openBtn(sessionId) {
|
|
1267
1306
|
if (liveIds.has(sessionId)) return '';
|
|
1268
|
-
return `<button class="copy open-btn" data-open="${esc(sessionId)}" title="Open this session">open
|
|
1307
|
+
return `<button class="copy open-btn" data-open="${esc(sessionId)}" title="Open this session">open</button>`;
|
|
1269
1308
|
}
|
|
1270
1309
|
|
|
1271
1310
|
// "Δ n" — how many files Claude edited in this session. Opens the changes panel.
|
|
@@ -1325,16 +1364,14 @@ function liveCard(s) {
|
|
|
1325
1364
|
? ` <span class="badge bg" title="${BG_TIP}">BG</span>`
|
|
1326
1365
|
: s.isWorktree ? ' <span class="badge">worktree</span>' : '';
|
|
1327
1366
|
return `<div class="dep-row ${s.status === 'waiting' ? 'waiting' : ''}">
|
|
1328
|
-
<span class="dep-
|
|
1329
|
-
<span class="dep-proj disp">${esc(s.projectName)}${kindBadge}</span>
|
|
1367
|
+
<span class="dep-proj" title="Started at ${esc(dep)}">${esc(s.projectName)}${kindBadge}</span>
|
|
1330
1368
|
<div class="dep-mid">
|
|
1331
|
-
<div class="dep-title" data-session="${esc(s.sessionId)}" title="Click to watch the transcript live">${esc(s.title || '')}</div>
|
|
1369
|
+
<div class="dep-title" data-session="${esc(s.sessionId)}" title="${esc(titleTip(s.model, 'Click to watch the transcript live'))}">${esc(s.title || '')}</div>
|
|
1332
1370
|
${task}
|
|
1333
1371
|
${reason}
|
|
1334
1372
|
${bgDetail}
|
|
1335
1373
|
</div>
|
|
1336
1374
|
<div class="dep-right">
|
|
1337
|
-
${modelChip(s.model)}
|
|
1338
1375
|
${s.estCost != null ? `<span class="dep-cost">${costChip(s.estCost, { cls: 'dep-cost-main', assumed: s.assumed })}${
|
|
1339
1376
|
s.burnRate ? `<span class="dep-burn" title="Spend per hour over the last 30 minutes">$${s.burnRate}/h</span>` : ''}</span>` : ''}
|
|
1340
1377
|
${contextMeter(s.context)}
|
|
@@ -1342,39 +1379,47 @@ function liveCard(s) {
|
|
|
1342
1379
|
? `<span class="dep-elapsed overdue" title="No reply for ${s.waitingMin} minutes — session started ${elapsed(s.startedAt)} ago">${waitLabel(s.waitingMin)}</span>`
|
|
1343
1380
|
: `<span class="dep-elapsed">${elapsed(s.startedAt)}</span>`}
|
|
1344
1381
|
${statusFlap(s)}
|
|
1345
|
-
|
|
1346
|
-
${copyBtn(s.resumeCommand)}
|
|
1382
|
+
<span class="acts">${renameBtn(s.sessionId, s.title)}${copyBtn(s.resumeCommand)}</span>
|
|
1347
1383
|
</div>
|
|
1348
1384
|
</div>`;
|
|
1349
1385
|
}
|
|
1350
1386
|
|
|
1387
|
+
// "3 automated runs, 1 empty session" — the rows a card or drawer folds away.
|
|
1388
|
+
function hiddenLabel(h) {
|
|
1389
|
+
const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
|
|
1390
|
+
return [h.automated && plural(h.automated, 'automated run'), h.empty && plural(h.empty, 'empty session')]
|
|
1391
|
+
.filter(Boolean).join(', ');
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1351
1394
|
function projectCard(p) {
|
|
1352
1395
|
const sessions = p.sessions.length
|
|
1353
1396
|
? `<ul class="sessions">${p.sessions.map((sess) => `
|
|
1354
1397
|
<li>
|
|
1355
|
-
<span class="s-title" data-session="${esc(sess.sessionId)}" title="
|
|
1398
|
+
<span class="s-title" data-session="${esc(sess.sessionId)}" title="${esc(titleTip(sess.model))}">${
|
|
1356
1399
|
sess.worktree ? `<span class="wt mono">⎇ ${esc(sess.worktree)}</span>` : ''}${esc(sess.title)}</span>${noteMark(sess)}
|
|
1357
|
-
${modelChip(sess.model)}
|
|
1358
1400
|
${changesChip(sess)}
|
|
1359
1401
|
<span class="s-when">${relTime(sess.lastActivityAt)}</span>
|
|
1360
|
-
|
|
1361
|
-
${openBtn(sess.sessionId)}
|
|
1362
|
-
${copyBtn(sess.resumeCommand)}
|
|
1402
|
+
<span class="acts">${renameBtn(sess.sessionId, sess.title)}${openBtn(sess.sessionId)}${copyBtn(sess.resumeCommand)}</span>
|
|
1363
1403
|
</li>`).join('')}</ul>`
|
|
1364
|
-
: '
|
|
1404
|
+
: '';
|
|
1405
|
+
const hiddenTxt = p.hidden ? hiddenLabel(p.hidden) : '';
|
|
1406
|
+
const hiddenNote = hiddenTxt
|
|
1407
|
+
? `<button class="hidden-note" data-detail="${esc(p.path)}" data-detail-name="${esc(p.name)}" title="One-shot claude -p runs and sessions closed without a prompt. Open the project to see them.">${esc(hiddenTxt)} hidden</button>`
|
|
1408
|
+
: '';
|
|
1409
|
+
const empty = !p.sessions.length && !hiddenTxt ? '<div class="none-yet">No sessions recorded.</div>' : '';
|
|
1365
1410
|
return `<div class="card">
|
|
1366
1411
|
<div class="head">
|
|
1367
1412
|
${p.isLive ? '<span class="live-dot" title="session running"></span>' : ''}
|
|
1368
1413
|
<span class="name" data-detail="${esc(p.path)}" style="cursor:pointer" title="Click for project details">${esc(p.name)}</span>
|
|
1369
|
-
<button class="copy" data-new="${esc(p.path)}" title="Start a new Claude session here (
|
|
1414
|
+
<button class="copy new-btn" data-new="${esc(p.path)}" title="Start a new Claude session here" aria-label="New session in ${esc(p.name)}">+</button>
|
|
1370
1415
|
${sparkSVG(p.activity.counts)}
|
|
1371
1416
|
</div>
|
|
1372
1417
|
<div class="meta">
|
|
1373
1418
|
${gitBadges(p.git, p.missing)}
|
|
1374
|
-
${costChip(p.spend7d, { cls: 'badge', after: '
|
|
1419
|
+
${costChip(p.spend7d, { cls: 'badge', after: ' in 7d' })}
|
|
1375
1420
|
<span class="when" title="${esc(p.path)}">${relTime(p.lastActivityAt)}</span>
|
|
1376
1421
|
</div>
|
|
1377
|
-
${sessions}
|
|
1422
|
+
${sessions}${empty}${hiddenNote}
|
|
1378
1423
|
</div>`;
|
|
1379
1424
|
}
|
|
1380
1425
|
|
|
@@ -1383,16 +1428,14 @@ function renderPinned(state) {
|
|
|
1383
1428
|
const pins = state.pinned || [];
|
|
1384
1429
|
el.hidden = !pins.length;
|
|
1385
1430
|
if (!pins.length) { patch(el, ''); return; }
|
|
1386
|
-
patch(el, `<div class="
|
|
1431
|
+
patch(el, `<div class="sec-head">Pinned <span class="n">${pins.length}</span></div>` + pins.map((p) => `
|
|
1387
1432
|
<div class="pin-row">
|
|
1388
1433
|
<span class="star">★</span>
|
|
1389
|
-
<span class="s-title" data-session="${esc(p.sessionId)}" title="
|
|
1390
|
-
${modelChip(p.model)}
|
|
1434
|
+
<span class="s-title" data-session="${esc(p.sessionId)}" title="${esc(titleTip(p.model))}">${esc(p.title)}</span>${noteMark(p)}
|
|
1391
1435
|
${changesChip(p)}
|
|
1392
1436
|
<span class="s-when">${esc(p.projectName)}</span>
|
|
1393
1437
|
<span class="s-when">${relTime(p.lastActivityAt)}</span>
|
|
1394
|
-
|
|
1395
|
-
<button class="copy" data-pin="${esc(p.sessionId)}" title="Unpin">unpin</button>
|
|
1438
|
+
<span class="acts">${renameBtn(p.sessionId, p.title)}<button class="copy" data-pin="${esc(p.sessionId)}" title="Unpin">unpin</button></span>
|
|
1396
1439
|
</div>`).join(''));
|
|
1397
1440
|
}
|
|
1398
1441
|
|
|
@@ -1420,7 +1463,7 @@ function renderGitAttention(state) {
|
|
|
1420
1463
|
const plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
|
|
1421
1464
|
el.hidden = false;
|
|
1422
1465
|
patch(el,
|
|
1423
|
-
`<span class="ga-label">Unpushed work
|
|
1466
|
+
`<span class="ga-label">Unpushed work in ${[items.length && plural(items.length, 'repo'), atRisk.length && plural(atRisk.length, 'worktree')].filter(Boolean).join(', ')}</span>` +
|
|
1424
1467
|
items.map((it) => `<span class="ga-item">${esc(it.name)}${
|
|
1425
1468
|
it.changes ? `<span class="badge dirty" title="${it.changes} uncommitted/untracked files">●${it.changes}</span>` : ''}${
|
|
1426
1469
|
it.ahead ? `<span class="badge ahead" title="${it.ahead} commits not pushed">↑${it.ahead}</span>` : ''}</span>`).join('') +
|
|
@@ -1469,14 +1512,13 @@ function digestItem(it) {
|
|
|
1469
1512
|
return `<div class="digest-item">
|
|
1470
1513
|
<div class="di-top">
|
|
1471
1514
|
<span class="di-proj">${esc(it.project)}</span>
|
|
1472
|
-
<span class="di-title" data-session="${esc(it.sessionId)}" title="
|
|
1515
|
+
<span class="di-title" data-session="${esc(it.sessionId)}" title="${esc(titleTip(it.model))}">${it.worktree ? `<span class="wt mono">⎇</span> ` : ''}${esc(it.title)}</span>${noteMark(it)}
|
|
1473
1516
|
${job ? `<span class="badge bg" title="${BG_TIP}">BG</span>` : ''}
|
|
1474
1517
|
${running ? '<span class="flap disp st st-busy">running</span>' : ''}
|
|
1475
|
-
${modelChip(it.model)}
|
|
1476
1518
|
${changesChip(it)}
|
|
1477
1519
|
${costChip(it.estCost, { cls: 'di-when', assumed: it.assumed })}
|
|
1478
1520
|
<span class="di-when">${time}</span>
|
|
1479
|
-
|
|
1521
|
+
<span class="acts">${openBtn(it.sessionId)}</span>
|
|
1480
1522
|
</div>
|
|
1481
1523
|
${summary ? `<div class="di-summary" title="Click to expand">${esc(summary)}</div>` : ''}
|
|
1482
1524
|
${total ? `<div class="di-tasks"><span class="done">${t.completed}</span>/${total} tasks done${
|
|
@@ -1488,12 +1530,13 @@ function digestItem(it) {
|
|
|
1488
1530
|
function renderDigest(state) {
|
|
1489
1531
|
const items = digestEntries(state);
|
|
1490
1532
|
const label = $('#digest-label');
|
|
1491
|
-
label.textContent =
|
|
1533
|
+
label.textContent = 'Recent sessions';
|
|
1534
|
+
$('#digest-n').textContent = items.length || '';
|
|
1492
1535
|
for (const b of document.querySelectorAll('#digest-range button')) {
|
|
1493
1536
|
b.classList.toggle('sel', Number(b.dataset.days) === digestDays);
|
|
1494
1537
|
}
|
|
1495
1538
|
if (!items.length) {
|
|
1496
|
-
patch($('#digest-body'), `<div class="digest-empty">
|
|
1539
|
+
patch($('#digest-body'), `<div class="digest-empty">No sessions in the last ${
|
|
1497
1540
|
digestDays === 1 ? 'day' : digestDays + ' days'}.</div>`);
|
|
1498
1541
|
return;
|
|
1499
1542
|
}
|
|
@@ -1536,17 +1579,17 @@ function render(state) {
|
|
|
1536
1579
|
: '';
|
|
1537
1580
|
const b = state.budget;
|
|
1538
1581
|
const budgetMeter = b
|
|
1539
|
-
? meter('
|
|
1582
|
+
? meter('Budget', (b.spent / b.limit) * 100, `≈$${b.spent.toFixed(0)} of your $${b.limit} weekly budget (7-day estimate)`)
|
|
1540
1583
|
: '';
|
|
1541
1584
|
const spendChip = planChip + (fmtCost(spend)
|
|
1542
|
-
?
|
|
1585
|
+
? `<span class="q-spend">${weeklyChart(state.weeklyCost)}${costChip(spend, { cls: 'num' })}<span>in 7 days</span></span>`
|
|
1543
1586
|
: '') + budgetMeter;
|
|
1544
1587
|
if (q) {
|
|
1545
1588
|
qEl.className = q.stale ? 'stale' : '';
|
|
1546
1589
|
patch(qEl, spendChip +
|
|
1547
|
-
(q.utilization != null ? forecastMeter('
|
|
1548
|
-
(q.weeklyUtilization != null ? forecastMeter('
|
|
1549
|
-
(q.costUsed != null && q.costLimit ? `<span title="
|
|
1590
|
+
(q.utilization != null ? forecastMeter('Session', q.utilization, q.resetsAt, state.forecast && state.forecast.session) : '') +
|
|
1591
|
+
(q.weeklyUtilization != null ? forecastMeter('Week', q.weeklyUtilization, q.weeklyResetsAt, state.forecast && state.forecast.week) : '') +
|
|
1592
|
+
(q.costUsed != null && q.costLimit ? `<span title="Extra usage spent this month, of your limit">Extra <span class="num" style="color:var(--ink)">$${q.costUsed.toFixed(0)}</span> of $${q.costLimit.toFixed(0)}</span>` : ''));
|
|
1550
1593
|
} else {
|
|
1551
1594
|
qEl.className = '';
|
|
1552
1595
|
patch(qEl, spendChip);
|
|
@@ -1555,7 +1598,7 @@ function render(state) {
|
|
|
1555
1598
|
// live rail
|
|
1556
1599
|
const live = state.liveSessions || [];
|
|
1557
1600
|
patch($('#live'),
|
|
1558
|
-
`<div class="
|
|
1601
|
+
`<div class="sec-head">Running now${live.length ? ` <span class="n">${live.length}</span>` : ''}</div>` +
|
|
1559
1602
|
(live.length
|
|
1560
1603
|
? `<div class="rows">${live.map(liveCard).join('')}</div>`
|
|
1561
1604
|
: `<div class="live-none">No sessions running. Start one with <code class="mono">claude</code> in any project.</div>`));
|
|
@@ -1571,7 +1614,8 @@ function render(state) {
|
|
|
1571
1614
|
const now = Date.now();
|
|
1572
1615
|
const active = [], dormant = [];
|
|
1573
1616
|
for (const p of state.projects || []) {
|
|
1574
|
-
const
|
|
1617
|
+
const noiseOnly = !p.sessions.length && p.hidden && (p.hidden.automated || p.hidden.empty);
|
|
1618
|
+
const isDormant = !p.isLive && (noiseOnly || !p.lastActivityAt || now - p.lastActivityAt > DORMANT_MS);
|
|
1575
1619
|
(isDormant ? dormant : active).push(p);
|
|
1576
1620
|
}
|
|
1577
1621
|
patch($('#grid'), active.length || dormant.length
|
|
@@ -1583,7 +1627,10 @@ function render(state) {
|
|
|
1583
1627
|
</div>`);
|
|
1584
1628
|
const d = $('#dormant');
|
|
1585
1629
|
d.hidden = dormant.length === 0;
|
|
1586
|
-
$('summary', d)
|
|
1630
|
+
patch($('summary', d), `<span class="sec-head"><span class="caret">▶</span>Dormant projects <span class="n">${dormant.length}</span></span>`);
|
|
1631
|
+
const gh = $('#grid-head');
|
|
1632
|
+
gh.hidden = !active.length;
|
|
1633
|
+
$('.n', gh).textContent = active.length;
|
|
1587
1634
|
patch($('.cards', d), dormant.map(projectCard).join(''));
|
|
1588
1635
|
|
|
1589
1636
|
// tab title = attention signal
|
|
@@ -1618,6 +1665,7 @@ async function loadOpenPref() {
|
|
|
1618
1665
|
const opts = [`<option value="terminal">${esc(c.resolvedTerminal.label)}</option>`];
|
|
1619
1666
|
if (c.claudeApp) opts.push('<option value="app">Claude app</option>');
|
|
1620
1667
|
prefEl.innerHTML = opts.join('');
|
|
1668
|
+
$('#open-pref-wrap').hidden = opts.length < 2;
|
|
1621
1669
|
const stored = localStorage.getItem('openTarget') || 'terminal';
|
|
1622
1670
|
prefEl.value = stored === 'app' && !c.claudeApp ? 'terminal' : stored;
|
|
1623
1671
|
} catch {
|
|
@@ -1827,18 +1875,24 @@ function toolsetHtml(t, projectPath) {
|
|
|
1827
1875
|
function renderDetail(d, name) {
|
|
1828
1876
|
$('#d-title').textContent = name;
|
|
1829
1877
|
$('#d-path').textContent = d.path;
|
|
1830
|
-
const
|
|
1831
|
-
? `<ul class="d-sessions">${d.sessions.map((s) => `
|
|
1878
|
+
const sessionRow = (s) => `
|
|
1832
1879
|
<li>
|
|
1833
|
-
<span class="s-title" data-session="${esc(s.sessionId)}" title="
|
|
1880
|
+
<span class="s-title" data-session="${esc(s.sessionId)}" title="${esc(titleTip(s.model))}">${
|
|
1834
1881
|
s.worktree ? `<span class="wt mono">⎇ ${esc(s.worktree)}</span> ` : ''}${esc(s.title)}</span>${noteMark(s)}
|
|
1835
1882
|
${changesChip(s)}
|
|
1836
1883
|
${costChip(s.estCost, { assumed: s.assumed })}
|
|
1837
1884
|
<span class="s-when">${relTime(s.lastActivityAt)}</span>
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1885
|
+
<span class="acts">${renameBtn(s.sessionId, s.title)}${openBtn(s.sessionId)}${copyBtn(s.resumeCommand)}</span>
|
|
1886
|
+
</li>`;
|
|
1887
|
+
const real = d.sessions.filter((s) => !s.kind);
|
|
1888
|
+
const noise = d.sessions.filter((s) => s.kind);
|
|
1889
|
+
const noiseTxt = hiddenLabel({
|
|
1890
|
+
automated: noise.filter((s) => s.kind === 'automated').length,
|
|
1891
|
+
empty: noise.filter((s) => s.kind === 'empty').length,
|
|
1892
|
+
});
|
|
1893
|
+
const sessions = d.sessions.length
|
|
1894
|
+
? (real.length ? `<ul class="d-sessions">${real.map(sessionRow).join('')}</ul>` : '') +
|
|
1895
|
+
(noise.length ? `<details class="d-noise"><summary>${esc(noiseTxt)}</summary><ul class="d-sessions">${noise.map(sessionRow).join('')}</ul></details>` : '')
|
|
1842
1896
|
: null;
|
|
1843
1897
|
const caps = [...d.skills, ...d.agents.map((a) => ({ ...a, name: `agent: ${a.name}` })),
|
|
1844
1898
|
...d.commands.map((c) => ({ ...c, name: `/${c.name}` }))];
|
|
@@ -1875,7 +1929,7 @@ function renderDetail(d, name) {
|
|
|
1875
1929
|
const tsOff = ts ? KINDS.reduce((n, k) => n + (ts[k] || []).filter((r) => !r.enabled).length, 0) : 0;
|
|
1876
1930
|
$('#d-body').innerHTML =
|
|
1877
1931
|
muteBtn +
|
|
1878
|
-
dSection(`Sessions — ${d.sessions.length}`, sessions, 'No sessions recorded.') +
|
|
1932
|
+
dSection(`Sessions — ${d.sessions.filter((s) => !s.kind).length}`, sessions, 'No sessions recorded.') +
|
|
1879
1933
|
dSection(`What changed — last ${(d.commits || []).length} commits`, commitRows ? commitSummaryLine(d.commitSummary) + `<ul class="d-sessions">${commitRows}</ul>` : null, 'No git history here.') +
|
|
1880
1934
|
dSection('CLAUDE.md', d.claudeMd.length ? dFiles(d.claudeMd, 'name') : null, 'No CLAUDE.md — sessions here start without project instructions.') +
|
|
1881
1935
|
dSection(`Memory — ${d.memory.length} file${d.memory.length === 1 ? '' : 's'}`, d.memory.length ? dFiles(d.memory, 'name') : null, 'No memory yet.') +
|
|
@@ -3125,7 +3179,7 @@ document.addEventListener('click', async (e) => {
|
|
|
3125
3179
|
|
|
3126
3180
|
document.addEventListener('click', (e) => {
|
|
3127
3181
|
const el = e.target.closest('[data-detail]');
|
|
3128
|
-
if (el) openDetail(el.dataset.detail, el.textContent);
|
|
3182
|
+
if (el) openDetail(el.dataset.detail, el.dataset.detailName || el.textContent);
|
|
3129
3183
|
});
|
|
3130
3184
|
$('#d-close').addEventListener('click', closeDetail);
|
|
3131
3185
|
$('#drawer-backdrop').addEventListener('click', closeDetail);
|