Compare commits

...
Author SHA1 Message Date
iris
5eacc99146 fix(dashboard): ctx·Nk card badge showed 0k — use context_tokens()
/api/dashboard-state reported ctx_tokens from raw ctx_usage.input_tokens,
which is only the *uncached* input. With prompt caching the bulk of the
prompt is cache-read, so input_tokens is ~0 and every SW4RM card showed
'ctx·0k'. Use ctx_usage.context_tokens() (input + cache-read + cache-creation
= the real window size) to match the agent page (ships the full ctx_usage)
and turn.rs's cache-TTL check. Doc comment updated.
2026-06-21 21:56:18 +02:00
iris
84077289f1 fix(dashboard): call.js renderQuestions referenced tabs.js-only helper
renderQuestions (call.js) called snapshotOpenDetails()/restoreOpenDetails()
which are closure-locals in tabs.js (built on MANAGED_SECTION_IDS) and not in
call.js's module scope. On a /dashboard.html (Y3R C4LL tab) refresh this threw
'ReferenceError: snapshotOpenDetails is not defined' and aborted refreshState
entirely. Give call.js its own section-scoped snapshot/restore pair operating
on the questions-section root (the only section renderQuestions manages), so
open <details> state still survives an SSE re-render without reaching into
tabs.js internals.
2026-06-21 21:56:18 +02:00
2 changed files with 32 additions and 5 deletions

View file

@ -615,6 +615,27 @@ function buildQuestionLi(q) {
return li;
}
// Snapshot / restore open `<details>` state across a re-render, scoped to
// one section root. renderQuestions only manages `#questions-section`, so it
// keeps its own section-local pair rather than reaching into tabs.js's
// `MANAGED_SECTION_IDS`-based helpers (which aren't in this module's scope —
// referencing them threw `ReferenceError: snapshotOpenDetails is not defined`
// and aborted refreshState). Sections that should survive a refresh carry a
// stable `data-restore-key`.
function snapshotOpenDetails(root) {
const open = new Set();
for (const d of root.querySelectorAll('details[data-restore-key]')) {
if (d.open) open.add(d.dataset.restoreKey);
}
return open;
}
function restoreOpenDetails(root, open) {
if (!open.size) return;
for (const d of root.querySelectorAll('details[data-restore-key]')) {
if (open.has(d.dataset.restoreKey)) d.open = true;
}
}
export function renderQuestions() {
const root = $('questions-section');
// #questions-section only lives on /dashboard.html (Y3R C4LL tab);
@ -625,7 +646,7 @@ export function renderQuestions() {
// any expanded sections. The keyed-cache approach reuses question
// <li> nodes (preserving textarea/checkbox state) and only rebuilds
// cache-miss rows, so we no longer wipe the DOM at the start.
const openDetails = snapshotOpenDetails();
const openDetails = snapshotOpenDetails(root);
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
const allPending = questionsState.pending;
@ -750,7 +771,7 @@ export function renderQuestions() {
const histEl = root.querySelector('.q-history');
if (histEl) histEl.open = true;
}
restoreOpenDetails(openDetails);
restoreOpenDetails(root, openDetails);
}
// Format a remaining-seconds count as the `⏳ …` TTL chip text on a

View file

@ -664,8 +664,9 @@ struct DashboardState {
/// `None` when no status is set.
#[serde(skip_serializing_if = "Option::is_none")]
status_set_at: Option<i64>,
/// Input token count from the most recent completed turn (`ctx_usage.input_tokens`).
/// `None` until the first turn finishes.
/// Full context-window size from the most recent completed turn
/// (`ctx_usage.context_tokens()` = input + cache-read + cache-creation).
/// `None` until the first turn finishes. Drives the `ctx·Nk` card badge.
#[serde(skip_serializing_if = "Option::is_none")]
ctx_tokens: Option<u64>,
/// Effective context-window budget for the current model. Same
@ -714,7 +715,12 @@ async fn api_dashboard_state(State(state): State<AppState>) -> axum::Json<Dashbo
.bus
.api_context_window()
.unwrap_or_else(|| crate::events::context_window_tokens(&model));
let ctx_tokens = state.bus.last_ctx_usage().map(|u| u.input_tokens);
// Full context-window size = input + cache-read + cache-creation. Using
// raw `input_tokens` here reported only the *uncached* sliver, which is
// ~0 once prompt caching kicks in — so every card showed `ctx·0k`. Match
// the agent page (which ships the whole `ctx_usage` and sums it) and the
// cache-TTL logic in turn.rs, both of which use `context_tokens()`.
let ctx_tokens = state.bus.last_ctx_usage().map(|u| u.context_tokens());
axum::Json(DashboardState {
status_text,
status_set_at,