feat(cockpit): quick wins — sync chip, cost budget guard, digest, recent commits (W3)
Four small, high-delight features that turn passive capture into glanceable signals.
- Sync chip on project cards: ahead/behind vs upstream + last-commit time, folded
into the existing concurrent per-repo metadata pass (git rev-list --count
--left-right @{u}...HEAD + git log -1 --format=%ct; no upstream → undefined, no route).
- Cost budget guard: COST_BUDGET_USD env (0 = off); a per-session one-shot latch
(Session.budgetNotified, cost is monotonic so never re-armed) fires a single push
on threshold crossing in manager.handleStatusLine; the already-broadcast telemetry
frame carries the warn (tg-cost-warn styling derived from costUsd>=budget via
/config/ui — no new ServerMessage). web-push title added to sw-push.js.
- "While you were away" digest: GET /digest?since= → {finished, needsInput, stuck,
totalCostUsd, sessions[]} aggregate over manager.list(); FE banner on reconnect.
- Recent commits per project: src/http/git-log.ts (NUL-delimited git log → CommitInfo[]),
GET /projects/log?path= (isValidGitDir), textContent-inert render in project detail.
All git via execFile (no shell) + validated cwd; new routes read-only; commit
messages rendered via textContent. Verified: typecheck + build:web clean, 1904 pass
at --test-timeout=30000 (two default-5s failures are slow-sandbox real-subprocess
timeout flakes — the known ring-buffer test + a new real-git-clone sync test — not
logic regressions).
This commit is contained in:
@@ -141,6 +141,9 @@ export class TabApp {
|
||||
// B4: mirrors the server ALLOW_AUTO_MODE gate (from /config/ui); when false the
|
||||
// high-risk 'auto' permission mode is hidden/refused (SEC-M5).
|
||||
private allowAutoMode = false
|
||||
// W3(b): the server COST_BUDGET_USD (from /config/ui); 0 = disabled. The per-tab
|
||||
// gauge warn-styles the cost chip once costUsd >= this budget.
|
||||
private costBudgetUsd = 0
|
||||
private pushHost!: HTMLElement // A1: 🔔 host, mounted once, re-parented per rebuild
|
||||
private timelinePanel!: HTMLElement // A4: shared timeline panel (one mounted at a time)
|
||||
private timelineOpen = false
|
||||
@@ -275,6 +278,11 @@ export class TabApp {
|
||||
) {
|
||||
this.allowAutoMode = (data as UiConfig).allowAutoMode
|
||||
}
|
||||
// W3(b): read the cost budget (optional, present only when > 0 server-side).
|
||||
const budget = (data as Record<string, unknown>)?.['costBudgetUsd']
|
||||
if (typeof budget === 'number' && Number.isFinite(budget) && budget > 0) {
|
||||
this.costBudgetUsd = budget
|
||||
}
|
||||
} catch {
|
||||
// best-effort — leave allowAutoMode false (auto hidden) on any failure
|
||||
}
|
||||
@@ -1441,7 +1449,7 @@ export class TabApp {
|
||||
queue.textContent = n > 0 ? `⧗${n}` : ''
|
||||
}
|
||||
const gauge = el.querySelector<HTMLElement>('.tab-gauge')
|
||||
if (gauge) renderTelemetryGauge(gauge, entry.session.telemetry, STATUSLINE_TTL_MS) // B2
|
||||
if (gauge) renderTelemetryGauge(gauge, entry.session.telemetry, STATUSLINE_TTL_MS, this.costBudgetUsd) // B2 + W3(b)
|
||||
}
|
||||
|
||||
/** Full rebuild — ONLY for structural changes (add/close/reorder/rename). */
|
||||
|
||||
Reference in New Issue
Block a user