diff --git a/.changes/planning/fix-interview-web-view-followups-3c7d91a2.md b/.changes/planning/fix-interview-web-view-followups-3c7d91a2.md
new file mode 100644
index 0000000000..177dbf8045
--- /dev/null
+++ b/.changes/planning/fix-interview-web-view-followups-3c7d91a2.md
@@ -0,0 +1,7 @@
+---
+bump: patch
+---
+
+### Fixed
+
+- When no session has listened for 30 seconds, the interview page's timer now updates the question chips and the receipt line together; before, the receipt line could keep "Sent to Claude" for up to two seconds after a chip read "No session is listening".
diff --git a/plugins/planning/surface/index.html b/plugins/planning/surface/index.html
index 74933e7f72..85c28b72f8 100644
--- a/plugins/planning/surface/index.html
+++ b/plugins/planning/surface/index.html
@@ -2113,7 +2113,7 @@
Interview
});
window.addEventListener("resize", () => grow($("note")));
let wasUnheard = false;
- setInterval(() => { if (!S.loaded) return; renderHeader(); if (unheard() !== wasUnheard) { wasUnheard = unheard(); renderRail(); } }, 2000);
+ setInterval(() => { if (!S.loaded) return; renderHeader(); const u = unheard(); if (u !== wasUnheard) { wasUnheard = u; renderRail(); renderHeader(); } }, 2000); // unheard() turns true as time passes, so the header is redrawn after the rail on the tick it flips
applySettings();
refresh().then(() => setOnline(true)).catch(() => { setOnline(false); restoreFinish(); }).finally(connect);
diff --git a/plugins/planning/surface/surface.test.sh b/plugins/planning/surface/surface.test.sh
index 1df7ff6f12..1f2c7c63c3 100755
--- a/plugins/planning/surface/surface.test.sh
+++ b/plugins/planning/surface/surface.test.sh
@@ -374,7 +374,7 @@ if command -v playwright-cli >/dev/null 2>&1; then
for n in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do grade "ui_journey.$n" "$tmp/uj$n.out"; done
for n in 1 2 3 4; do grade "ui_live.$n" "$tmp/ul$n.out"; done
else
- browser=472 journey=222
+ browser=473 journey=222
echo "SKIP: $browser browser checks not run, $journey of them the journey (playwright-cli not found)" # silent-skip-ok: browser checks need a local playwright-cli # discriminating-skip-ok: the API, watcher and hygiene checks above still grade this suite
skip=$((skip + browser))
fi
diff --git a/plugins/planning/surface/tests/ui_c.js b/plugins/planning/surface/tests/ui_c.js
index c6537b20b6..be0a366ba7 100644
--- a/plugins/planning/surface/tests/ui_c.js
+++ b/plugins/planning/surface/tests/ui_c.js
@@ -236,6 +236,13 @@ async page => {
await page.click("main.detail h3"); await page.keyboard.press("Control+Enter"); await page.waitForTimeout(800);
const ev4 = await last();
ok("an armed alternative survives a Reconfirm row shifting the key numbers: (b) stays armed once stale and saves alt b", /^\(b\)/.test(a0) && wasStale && /^\(b\)/.test(a1) && ev4.id === "P2" && ev4.kind === "alt" && ev4.alt === "b", a0 + " / " + a1 + " " + JSON.stringify(ev4));
+ // Reconfirm of a person-held deferral with a new name typed over the kept one stays person-held
+ await restale({kind: "defer", text: "someone else decides: Dana"}, {kind: "alt", alt: "b"});
+ await page.fill("#note", "Lee, the architect");
+ await page.click("main.detail h3"); await page.keyboard.press("a");
+ const r7 = (await armed()).trim(); await page.keyboard.press("Control+Enter"); await page.waitForTimeout(800);
+ const ev7 = await last();
+ ok("Reconfirm of a person-held deferral with a new name keeps the someone else decides prefix", /^Reconfirm/.test(r7) && ev7.id === "P2" && ev7.kind === "defer" && ev7.text === "someone else decides: Lee, the architect", r7 + " " + JSON.stringify(ev7));
// Reconfirm with a note typed over the kept one records the typed note
await restale({kind: "alt", alt: "b", text: "Only on weekends"}, {kind: "accept"});
await page.fill("#note", "Weekdays too");
@@ -500,7 +507,8 @@ async page => {
ok("a fresh answer, with no watcher silent for 30 s yet, reads Sent to Claude", /Sent to Claude/.test(await chip()), await chip());
const quiet = await page.waitForFunction(() => /No session is listening; type next in the terminal/.test(document.querySelector('.qbtn[data-q="D1"]').textContent), null, {timeout: 45000}).then(() => true).catch(() => false);
ok("with no watcher holding the lease, an unhandled answer reads as not delivered", quiet && !/Sent to Claude/.test(await chip()), await chip());
- ok("the receipt line says the same", /No session is listening/.test(await page.textContent("#claudeLine")), await page.textContent("#claudeLine"));
+ const line = await page.waitForFunction(() => /No session is listening/.test(document.getElementById("claudeLine").textContent), null, {timeout: 5000}).then(() => true).catch(() => false);
+ ok("the receipt line says the same", line, await page.textContent("#claudeLine"));
// The watcher claims the lease; the shell handles the event a few seconds later.
const r = await page.request.get(base + "api/wait?after=handled&timeout=1&watcher=w1", {headers: {"X-Interview-Token": await token()}});
const back = await page.waitForFunction(() => /Sent to Claude/.test(document.querySelector('.qbtn[data-q="D1"]').textContent), null, {timeout: 12000}).then(() => true).catch(() => false);