Files
devdesk/web/src/context.js
T
jaredandClaude Opus 5.5 527cb1fda2
build-deb / Lint, test, package (push) Successful in 2m0s
build-deb / Notify on failure (push) Skipped
feat: free-form sessions, rename/end from the panel, live Claude Context (0.2.0)
- Sessions no longer need a project folder: any name works and starts in
  DEVDESK_DEFAULT_DIR unless it matches a project; `devdesk new NAME [PROJECT]`.
- Rename (✎) and end/forget (×) sessions from the Sessions panel, plus
  `devdesk rename`. The browser runs them as `devdesk attach --new|--rename|--end`
  through ttyd. Renames keep the running Claude and its recorded conversation:
  the hook, the run-wrapper and the statusline ask tmux for the current name.
- Claude Context panel is live: devdesk-statusline snapshots model,
  context-window use, cost and edits per session, which --list reports;
  Claude's footer gets a one-line summary. The panel now sits under Sessions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 03:03:09 -04:00

52 lines
2.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Claude Context panel: the current session's model, context-window use, cost
// and edits, from the snapshot devdesk-statusline writes on every Claude update.
import { ago } from './sessions.js';
function compact(tokens) {
if (tokens == null) return '—';
if (tokens >= 1e6) return `${+(tokens / 1e6).toFixed(tokens % 1e6 ? 1 : 0)}M`;
if (tokens >= 1e3) return `${Math.round(tokens / 1e3)}k`;
return String(tokens);
}
export function renderContext(root, session, context) {
const set = (field, text) => {
const node = root.querySelector(`[data-field="${field}"]`);
if (node) node.textContent = text;
};
const bar = root.querySelector('.lt-progress');
const fill = root.querySelector('.lt-progress-bar');
const note = root.querySelector('.dd-context-note');
if (!context) {
root.dataset.state = 'empty';
fill.style.width = '0%';
bar.setAttribute('aria-valuenow', '0');
for (const f of ['used', 'model', 'cost', 'lines', 'updated']) set(f, '—');
note.textContent = session
? 'No data yet: it appears once Claude has redrawn its status line in this session.'
: 'Open a session to see its context.';
return;
}
const pct = context.used_pct;
root.dataset.state = 'live';
fill.style.width = `${Math.min(100, pct ?? 0)}%`;
bar.setAttribute('aria-valuenow', String(Math.round(pct ?? 0)));
bar.classList.remove('lt-progress--cyan', 'lt-progress--red');
if (pct == null || pct < 70) bar.classList.add('lt-progress--cyan');
else if (pct >= 90) bar.classList.add('lt-progress--red');
set('used', pct == null
? `— / ${compact(context.window)}`
: `${compact(context.used_tokens)} / ${compact(context.window)} (${pct.toFixed(pct < 10 ? 1 : 0)}%)`);
set('model', [context.model, context.effort].filter(Boolean).join(' · ') || '—');
set('cost', context.cost_usd == null ? '—' : `$${context.cost_usd.toFixed(2)}`);
set('lines', `+${context.lines_added ?? 0} −${context.lines_removed ?? 0}`);
set('updated', context.updated ? `${ago(context.updated)} ago`.replace('now ago', 'just now') : '—');
note.textContent = pct != null && pct >= 80
? 'Context is filling up; consider /compact or a new conversation.'
: '';
}