From fd29653d4e6f874765d281edf94095d4c8d95bc7 Mon Sep 17 00:00:00 2001 From: yetone Date: Thu, 8 Oct 2026 12:53:16 +0800 Subject: [PATCH] context: a window opened on the Context tab loads it at once, not at the refresh's first tick five seconds on context.js comes after app.js, and app.js shows ?view=usage and reads the state before context.js is there: when /api/state answered first, both paths called window.loadContext while it was undefined, the TypeError went to the status line, and the tab stayed empty until the 5s refresh (found as context-window's webkit de 1100px flake, 2 of 6 runs). The calls are optional now, and context.js loads the tab itself when the page opened on it, as plugins.js and library.js do. The new test holds context.js until the state has answered: it fails on both engines without the fix and passes with it; context-window passes 22/22, the case that flaked 10/10. --- internal/gui/assets/app.js | 4 ++-- internal/gui/assets/context.js | 3 +++ internal/gui/tests/context-window.test.cjs | 28 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/internal/gui/assets/app.js b/internal/gui/assets/app.js index 8483e05f..277bae4c 100644 --- a/internal/gui/assets/app.js +++ b/internal/gui/assets/app.js @@ -12443,7 +12443,7 @@ async function loadUsage(asked) { if (asked) loadQuotas(true); if (usageTab === "sessions") return loadSessions(); if (usageTab === "requests") return loadLedger(); - if (usageTab === "context") return window.loadContext(); + if (usageTab === "context") return window.loadContext?.(); renderUsageLoading(); if (!asked) loadQuotas(); const p = period, read = ++usageRead; @@ -20205,7 +20205,7 @@ function refreshUsage(now = false) { // the newest page takes the requests as they come; an older one stays put if (ledger && (want || !ledOffset)) await loadLedger(true); } else if (usageTab === "context") { - await window.loadContext(); + await window.loadContext?.(); } else { // the reader asking reads the allowances afresh, a Claude account's by // running Claude Code's own /usage (the backend runs it at most once in 30s) diff --git a/internal/gui/assets/context.js b/internal/gui/assets/context.js index 5c5f9dc7..df38a1d4 100644 --- a/internal/gui/assets/context.js +++ b/internal/gui/assets/context.js @@ -616,4 +616,7 @@ } window.loadContext = loadContext; window.renderContext = renderContext; + // opened on the Context tab (?view=usage): app.js showed it, and may have + // read the state, before this file was here to load it + if (view === "usage" && usageTab === "context") loadContext().catch((e) => status(e.message, "err")); })(); diff --git a/internal/gui/tests/context-window.test.cjs b/internal/gui/tests/context-window.test.cjs index 60dac1d9..cf42a008 100644 --- a/internal/gui/tests/context-window.test.cjs +++ b/internal/gui/tests/context-window.test.cjs @@ -180,3 +180,31 @@ for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", } } } + +// context.js comes after app.js, which shows the page and reads the state +// before it is there: the state answered first, the tab still loads at once +// rather than at the refresh's first tick, five seconds on +for (const engine of (process.env.BROWSER ? [process.env.BROWSER] : ["chromium", "webkit"])) { + test(`${engine}: the Context tab loads when context.js arrives after the state`, async (t) => { + const browser = await (engine === "webkit" ? webkit.launch() : chromium.launch({ channel: "chromium" })); + t.after(() => browser.close()); + const page = await (await browser.newContext({ viewport: { width: 1100, height: 900 } })).newPage(); + const errors = [], asked = []; + page.on("pageerror", (e) => errors.push(e.message)); + const base = serve("en", asked); + let answered; + const stateDone = new Promise((res) => { answered = res; }); + await page.route("**/*", async (r) => { + const p = new URL(r.request().url()).pathname; + if (p === "/context.js") await stateDone.then(() => new Promise((res) => setTimeout(res, 150))); + await base(r); + if (p === "/api/state") answered(); + }); + await page.addInitScript(() => { try { localStorage.clear(); localStorage.setItem("magpie.usageTab", "context"); } catch {} }); + await page.goto("http://magpie.test/?view=usage"); + await page.locator(".ctx-agent", { hasText: "Codex" }).waitFor({ timeout: 2500 }); + assert.equal(asked[0], "7"); + assert.doesNotMatch(await page.locator("#status").innerText(), /loadContext|not a function/); + assert.deepEqual(errors, []); + }); +}