From 963d2f5413492f2532462cce0606c86cd06a3073 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sun, 11 Oct 2026 22:07:39 -0400 Subject: [PATCH] fix: v0.9.3 plan usage instead of API-price cost; larger footer - Footer shows "5h N%" when Claude sends rate_limits (Pro/Max); the dollar figure is an API-price estimate, so it is kept only for API-key billing. Sessions tooltip follows suit. Closes #40 - Bottom row: 40px, larger hints, bold 1rem usage meters with wider bars; narrow windows drop hints instead of clipping the meters. Closes #41 - bats setup unsets inherited DevDesk env so tests pass inside DevDesk Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01WHRLCCdXYm41Qx5kNhehaP --- README.md | 2 +- debian/changelog | 15 +++++++++++++++ libexec/devdesk-statusline | 10 +++++++--- tests/attach.bats | 12 ++++++++++++ web/src/sessions.js | 3 ++- web/styles.css | 24 ++++++++++++++++++++---- 6 files changed, 57 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index be106c5..38a5b26 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ The tmux server runs in its own unit, so every Claude process lives in **that** - **Search past conversations** (⋯ menu, palette): full text over every Claude conversation on the host; **Resume** it in a new session or **Fork** it. - **Plan usage:** the 5-hour and weekly windows as meters in the footer (warning at 90 %); a **PR** chip on sessions whose branch has a pull request. - **Quiet phone at the desk:** while a tab shows a session, Remote Control holds back phone pushes for it (client-presence marker, expires ~2 min after you leave). -- **Context:** a Claude `statusLine` command (`devdesk-statusline`) snapshots each session's model, context-window use and cost to `/var/lib/devdesk/context/.json`. The Sessions panel shows it as a bar (cost in the tooltip), and Claude's own footer shows a one-line summary. +- **Context:** a Claude `statusLine` command (`devdesk-statusline`) snapshots each session's model, context-window use, plan usage and cost to `/var/lib/devdesk/context/.json`. The Sessions panel shows it as a bar and Claude's own footer shows a one-line summary. On a Pro/Max plan that summary shows 5-hour plan usage; the dollar figure Claude reports there is an API-price estimate, not a bill, so it appears only for API-key billing. - **Light mode:** the terminal switches palettes with the page. DevDesk starts Claude with its `dark-ansi` theme so Claude draws with the terminal's 16 colours, and the light palette maps bright white to ink; tmux and the status line use ANSI colours too. A session picks up Claude's theme the next time Claude starts in it (`/exit`, then ⏎ resumes the same conversation). - **Phone:** a key bar replaces the shortcut hints (Esc, Tab, ⇧Tab, ^C, ↑, ↓, ⏎, Claude/Shell window), and the panels open as a drawer. - **SSH:** `devdesk attach `, `devdesk new [project]`, `devdesk rename `, `devdesk end `, `devdesk ls`, `devdesk pick`, `devdesk repo `, `devdesk alerts`. diff --git a/debian/changelog b/debian/changelog index 57e4e3b..f15df2b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,18 @@ +devdesk (0.9.3) bookworm; urgency=medium + + * Claude's footer shows 5-hour plan use instead of a dollar figure on a + Pro/Max subscription (#40). Claude Code's cost is an API-price + estimate, not a bill; it is still shown for API-key billing. The + Sessions tooltip follows the same rule. + * The bottom row is larger and easier to read (#41): 40px tall, larger + hints, and 5h/week usage meters in bold monospace with wider bars. + Narrow windows drop the least-used hints instead of clipping the + meters. + * Tests clear inherited DevDesk variables, so they pass when run from + inside a DevDesk session. + + -- Jared Vititoe Sun, 11 Oct 2026 22:08:00 -0400 + devdesk (0.9.2) bookworm; urgency=medium * Ops Alerts → Ask Claude starts an investigation (#39): diff --git a/libexec/devdesk-statusline b/libexec/devdesk-statusline index fba5e82..cd55d7d 100755 --- a/libexec/devdesk-statusline +++ b/libexec/devdesk-statusline @@ -43,13 +43,17 @@ if [[ $session =~ ^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$ ]]; then fi fi -# Footer line: session · model · context % · cost. ANSI colour numbers, not RGB, -# so the browser's dark or light palette applies (devdesk#31). +# Footer line: session · model · context % · plan use. ANSI colour numbers, not +# RGB, so the browser's dark or light palette applies (devdesk#31). Claude sends +# rate_limits only to Pro/Max subscribers; there .cost is an API-price estimate, +# not a bill, so show the 5-hour plan use instead and keep $ for API-key billing. jq -rM --arg s "$session" ' def c($sgr; $t): "\u001b[\($sgr)m\($t)\u001b[0m"; def pct: if . == null then "—" else "\(. | floor)%" end; [ (if $s != "" then c("33"; "◈ \($s)") else empty end), c("36"; .model.display_name // "Claude"), c("37"; "ctx \(.context_window.used_percentage | pct)"), - c("37"; "$\((.cost.total_cost_usd // 0) * 100 | round / 100)") + (if .rate_limits.five_hour.used_percentage != null + then c("37"; "5h \(.rate_limits.five_hour.used_percentage | pct)") + else c("37"; "$\((.cost.total_cost_usd // 0) * 100 | round / 100)") end) ] | join(c("90"; " · "))' <<<"$input" 2>/dev/null || true diff --git a/tests/attach.bats b/tests/attach.bats index 38d0986..d856edd 100644 --- a/tests/attach.bats +++ b/tests/attach.bats @@ -5,6 +5,7 @@ REPO=$(cd "$(dirname "$BATS_TEST_FILENAME")/.." && pwd) setup() { TMP=$(mktemp -d /tmp/devdesk-test.XXXXXX) # short path: unix socket limit + unset DEVDESK_REMOTE_CONTROL CLAUDE_ARGS DEVDESK_SESSION TMUX TMUX_PANE # when run from inside DevDesk export DEVDESK_ENV=/dev/null export DEVDESK_PROJECTS_ROOT="$TMP/projects" export DEVDESK_STATE_DIR="$TMP/state" @@ -200,6 +201,17 @@ claude_args() { [ "$(jq -r '.sessions[] | select(.name=="alpha") | .context.model' <<<"$output")" = Opus ] } +@test "statusline footer shows plan use on a subscription and dollars only on API billing" { + sub='{"model":{"display_name":"Opus"},"context_window":{"used_percentage":10}, + "cost":{"total_cost_usd":1186.99},"rate_limits":{"five_hour":{"used_percentage":8.4}}}' + run bash -c "echo '$sub' | '$REPO/libexec/devdesk-statusline'" + [[ $output == *"5h 8%"* ]] + [[ $output != *'$'* ]] + api='{"model":{"display_name":"Opus"},"context_window":{"used_percentage":10},"cost":{"total_cost_usd":1.5}}' + run bash -c "echo '$api' | '$REPO/libexec/devdesk-statusline'" + [[ $output == *'$1.5'* ]] +} + @test "hooks ignore Claude running in some other tmux server" { start_server other="$TMP/other.sock" diff --git a/web/src/sessions.js b/web/src/sessions.js index b906b93..a0a0596 100644 --- a/web/src/sessions.js +++ b/web/src/sessions.js @@ -278,7 +278,8 @@ export class SessionsPanel { const title = [ s.dir, status.key === 'waiting' && s.status?.message, - s.context?.model && `${s.context.model}${s.context.cost_usd != null ? ` · $${s.context.cost_usd.toFixed(2)}` : ''}`, + // rate_limits means a Pro/Max plan, where cost_usd is only an API-price estimate + s.context?.model && `${s.context.model}${s.context.rate_limits ? '' : s.context.cost_usd != null ? ` · $${s.context.cost_usd.toFixed(2)}` : ''}`, ].filter(Boolean).join('\n'); const iconButton = (label, glyph, onclick, extra = '') => el('button', { class: `dd-icon-btn ${extra}`, type: 'button', title: label, 'aria-label': `${label} ${s.name}`, onclick }, glyph); diff --git a/web/styles.css b/web/styles.css index c4641f1..35df68c 100644 --- a/web/styles.css +++ b/web/styles.css @@ -2,7 +2,7 @@ everything visual comes from the TDS tokens defined in base.css :root. */ :root { - --dd-footer-height: 30px; + --dd-footer-height: 40px; --dd-sidebar-width: 320px; --dd-gap: 8px; } @@ -492,7 +492,19 @@ body.dd-sidebar-open .dd-sidebar { overflow: hidden; } -.dd-footer .lt-footer-key { margin-right: 4px; } +.dd-footer .lt-footer-key { margin-right: 5px; font-size: 0.86rem; } +/* devdesk#41: the bottom row was too small to read at a glance */ +.dd-footer .lt-footer-hint { font-size: 0.9rem; } +.dd-footer .lt-footer-sep { font-size: 0.9rem; } +/* Narrower windows keep the usage meters and drop the least-used hints. */ +@media (max-width: 1400px) { + .dd-footer .lt-footer-hints .lt-footer-hint:nth-child(n + 13), + .dd-footer .lt-footer-sep:nth-child(n + 12) { display: none; } +} +@media (max-width: 1150px) { + .dd-footer .lt-footer-hints .lt-footer-hint:nth-child(n + 9), + .dd-footer .lt-footer-sep:nth-child(n + 8) { display: none; } +} .dd-keybar { display: flex; gap: 4px; width: 100%; overflow-x: auto; scrollbar-width: none; } .dd-keybar[hidden] { display: none; } @@ -625,7 +637,7 @@ html[data-theme="light"] .lt-cmd-item.is-selected { background: var(--accent-cya background: none; color: var(--accent-cyan); font-family: var(--font-mono); - font-size: 0.64rem; + font-size: 0.8rem; cursor: pointer; } .dd-footer-help:hover { border-color: var(--accent-cyan); } @@ -1005,11 +1017,15 @@ html[data-theme="light"] .dd-files.is-narrow .dd-explorer { box-shadow: 8px 0 24 .dd-history-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: 8px; } .dd-history-actions .lt-input { width: 220px; font-size: 0.74rem; } -.dd-footer-usage { margin-left: auto; padding-left: 12px; } +.dd-footer-usage { margin-left: auto; padding-left: 16px; } .dd-footer-usage[hidden], .dd-usage-row[hidden] { display: none; } .dd-usage { display: inline-flex; align-items: center; gap: 10px; } +.dd-footer-usage { display: inline-flex; align-items: center; gap: 18px; } .dd-meter { display: inline-flex; align-items: center; gap: 5px; color: var(--text-secondary); font-size: 0.66rem; white-space: nowrap; } +.dd-footer-usage .dd-meter { gap: 7px; color: var(--text-primary); font-family: var(--font-mono); font-size: 1rem; font-weight: 600; } +.dd-footer-usage .dd-meter-label { font-weight: 400; } +.dd-footer-usage .dd-meter-bar { width: 80px; height: 8px; } .dd-meter-label { color: var(--text-muted); } .dd-meter-bar { width: 42px; height: 4px; background: var(--bg-tertiary); } .dd-meter-fill { display: block; height: 100%; background: var(--accent-cyan); }