From b7d85c70bb6ffbbeae30fac35171a30a02e40a3d Mon Sep 17 00:00:00 2001
From: "melodic-automation-lanes[bot]"
<337595936+melodic-automation-lanes[bot]@users.noreply.github.com>
Date: Sun, 1 Nov 2026 05:57:39 +0000
Subject: [PATCH 1/4] Resolve issue #11843
Lane-Activity: work-resolve-issue/resolve-issue
---
.../work-resolve-issue-issue-11843-5b1e9c3d.md | 7 +++++++
plugins/planning/surface/index.html | 13 ++++++++-----
plugins/planning/surface/surface-checks.sh | 2 +-
plugins/planning/surface/tests/ui_journey.js | 4 ++++
4 files changed, 20 insertions(+), 6 deletions(-)
create mode 100644 .changes/planning/work-resolve-issue-issue-11843-5b1e9c3d.md
diff --git a/.changes/planning/work-resolve-issue-issue-11843-5b1e9c3d.md b/.changes/planning/work-resolve-issue-issue-11843-5b1e9c3d.md
new file mode 100644
index 00000000000..70e572377a7
--- /dev/null
+++ b/.changes/planning/work-resolve-issue-issue-11843-5b1e9c3d.md
@@ -0,0 +1,7 @@
+---
+bump: patch
+---
+
+### Fixed
+
+- The interview page's Notes panel keeps its send message (for example "Queued, Claude is busy") when a state push redraws the panel. Before, the message went blank as soon as the next push arrived, often before it could be read. "Queued, Claude is busy" drops off once Claude is no longer busy with the earlier wake. A failed send clears the message, and so does leaving the Notes panel.
diff --git a/plugins/planning/surface/index.html b/plugins/planning/surface/index.html
index 60b49e0b48f..28ac3b02fbf 100644
--- a/plugins/planning/surface/index.html
+++ b/plugins/planning/surface/index.html
@@ -621,7 +621,7 @@
"; // held in state, so a re-render on a state push keeps it
const ta = $("noteText"), nk = noteBox(ta, "send"); ta.nextElementSibling.append(nk.count); Kit.grow(ta);
const th = body.querySelector(".nthread"); if (th.lastElementChild) th.lastElementChild.scrollIntoView({block: "nearest"});
}
@@ -1880,7 +1881,7 @@
Interview
$("keysBody").innerHTML = KEYS.map(([k, t]) => "" + esc(k) + "" + esc(t) + "").join("");
$("keysDlg").showModal();
}
- function showFly(p){ S.fly = p; lsSet("fly", p); renderFly(); renderHeader(); }
+ function showFly(p){ if (p !== "notes") S.noteToast = ""; S.fly = p; lsSet("fly", p); renderFly(); renderHeader(); }
const openPanel = p => showFly(S.fly === p ? null : p);
function focusHead(){ const h = $("qhead"); if (h) { h.tabIndex = -1; h.focus(); } }
@@ -2298,8 +2299,10 @@
Interview
async function sendNote(){
const ta = $("noteText"); if (!ta) return;
const text = ta.value.trim();
- if (!text) { $("noteToast").textContent = "Write a note first."; return; }
- if (await sendFree("note", text)) { lsSet("noteDraft", null); if (S.fly === "notes") renderFly(); const t = $("noteToast"); if (t) t.textContent = "Sent. Claude's reply lands here." + queued(); }
+ const say = (msg, q) => { if (S.fly !== "notes") msg = ""; S.noteToast = msg; S.noteQueued = !!q; const t = $("noteToast"); if (t) t.textContent = noteToastText(); }; // a send that finishes after the panel closed holds nothing
+ if (!text) { say("Write a note first."); return; }
+ if (await sendFree("note", text)) { lsSet("noteDraft", null); if (S.fly === "notes") renderFly(); say("Sent. Claude's reply lands here.", queued()); }
+ else say(""); // a failed send must not leave the last send's toast standing
}
const carried = q => String(lsGet("draft:" + q.id, null) || "").trim(); // what a single Accept would send: the note typed on it
function openedRev(q){ // the revision the user last opened q at, with the decisions made since
diff --git a/plugins/planning/surface/surface-checks.sh b/plugins/planning/surface/surface-checks.sh
index 9c1b23f13c8..1c0ee505eab 100755
--- a/plugins/planning/surface/surface-checks.sh
+++ b/plugins/planning/surface/surface-checks.sh
@@ -25,7 +25,7 @@ bad() { echo "FAIL: $1"; fail=$((fail + 1)); }
# The browser groups of each part, and each group's check count for the SKIP line.
declare -A PARTS=([main]="ab e live" [page]="page" [ui-c]="c" [ui-c-listen]="c_listen" [ui-c-ledger]="c_ledger" [journey]="journey")
-declare -A CHECKS=([ab]=97 [c]=176 [c_listen]=21 [c_ledger]=10 [e]=22 [journey]=235 [live]=18 [page]=122)
+declare -A CHECKS=([ab]=97 [c]=176 [c_listen]=21 [c_ledger]=10 [e]=22 [journey]=236 [live]=18 [page]=122)
part=main
case "$#:${1:-}" in
0:) ;;
diff --git a/plugins/planning/surface/tests/ui_journey.js b/plugins/planning/surface/tests/ui_journey.js
index e616592f8be..08226e2341e 100644
--- a/plugins/planning/surface/tests/ui_journey.js
+++ b/plugins/planning/surface/tests/ui_journey.js
@@ -717,6 +717,10 @@ async page => { // the user journey in order on one page, no reload after phase
await page.evaluate(() => document.querySelector('[data-panel="notes"]').click()); await settle(300);
await page.fill("#noteText", "second note"); await page.click("[data-sendnote]");
ok("a save while a wake is in flight says queued, Claude is busy", await until(() => /Queued, Claude is busy/.test((document.getElementById("noteToast") || {}).textContent || ""), 4000), await text("#noteToast"));
+ const frame = await page.evaluate(() => window.__io.applied);
+ await page.request.get((await page.evaluate(() => location.origin)) + "/api/wait?after=0&timeout=1", {headers: {"X-Interview-Token": await token()}}); // delivers the second note, so the server pushes a state frame
+ const pushed = await page.waitForFunction(f => window.__io.applied !== f, frame, {timeout: 5000, polling: 50}).then(() => true).catch(() => false);
+ ok("the Notes send toast outlasts the next state push", pushed && /Queued, Claude is busy/.test(await text("#noteToast")), (pushed ? "" : "no frame; ") + await text("#noteToast"));
await page.click("#flyClose", {timeout: 2000}).catch(() => {});
}
if (PHASE === 17) { // after the wrap-up the user confirms the third restatement; the shell has written nothing since phase 16
From 4283c441460557cbd48806d7b4efd2bed69f9180 Mon Sep 17 00:00:00 2001
From: "melodic-automation-lanes[bot]"
<337595936+melodic-automation-lanes[bot]@users.noreply.github.com>
Date: Sun, 1 Nov 2026 06:37:00 +0000
Subject: [PATCH 2/4] chore: start the lane checks again on main's workflow
files
The token broker refused a lane run on the previous head as workflow-modified (.github/workflows/pr-run-activity-write.yml). This commit changes no file.
From 4071cfa4477a952387418b8c24e5992d0a3ed95c Mon Sep 17 00:00:00 2001
From: "melodic-automation-lanes[bot]"
<337595936+melodic-automation-lanes[bot]@users.noreply.github.com>
Date: Sun, 1 Nov 2026 07:02:36 +0000
Subject: [PATCH 3/4] Address review feedback on
work-resolve-issue/issue-11843-41275815373-1
Lane-Activity: pr-unblock/address-feedback
Lane-Thread: PRRT_kwDOTCGFQM6yYBsx
---
plugins/planning/surface/index.html | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/plugins/planning/surface/index.html b/plugins/planning/surface/index.html
index 28ac3b02fbf..b20140cb2ca 100644
--- a/plugins/planning/surface/index.html
+++ b/plugins/planning/surface/index.html
@@ -621,7 +621,7 @@
(log.length ? 'Log (' + log.length + ")" + rows(log) + "" : "");
if (un.size) { markSeen(); renderHeader(); renderRail(); }
}
- const noteToastText = () => S.noteToast + (S.noteQueued ? queued() : ""); // the busy note lasts only while the wake it reported is in flight
+ const noteToastText = () => S.noteToast + (S.noteQueued.some(s => !isHandled(s)) ? queued() : ""); // the busy note lasts only while the wake it reported is in flight: S.noteQueued holds the seqs that wake was handling when the note was sent
function renderNotes(body){
const nt = noteThread(), draft = lsGet("noteDraft", "");
S.noteSeen = Date.now(); lsSet("noteSeen", S.noteSeen);
@@ -2299,9 +2299,10 @@
Interview
async function sendNote(){
const ta = $("noteText"); if (!ta) return;
const text = ta.value.trim();
- const say = (msg, q) => { if (S.fly !== "notes") msg = ""; S.noteToast = msg; S.noteQueued = !!q; const t = $("noteToast"); if (t) t.textContent = noteToastText(); }; // a send that finishes after the panel closed holds nothing
+ const say = (msg, q) => { if (S.fly !== "notes") msg = ""; S.noteToast = msg; S.noteQueued = q || []; const t = $("noteToast"); if (t) t.textContent = noteToastText(); }; // a send that finishes after the panel closed holds nothing
if (!text) { say("Write a note first."); return; }
- if (await sendFree("note", text)) { lsSet("noteDraft", null); if (S.fly === "notes") renderFly(); say("Sent. Claude's reply lands here.", queued()); }
+ const inFlight = pending().filter(delivered).map(e => e.seq); // the wake in flight before this note, never the note itself
+ if (await sendFree("note", text)) { lsSet("noteDraft", null); if (S.fly === "notes") renderFly(); say("Sent. Claude's reply lands here.", queued() ? inFlight : []); }
else say(""); // a failed send must not leave the last send's toast standing
}
const carried = q => String(lsGet("draft:" + q.id, null) || "").trim(); // what a single Accept would send: the note typed on it
From 56c5a3f16990bf85ab9878e34678238e36f8dd18 Mon Sep 17 00:00:00 2001
From: "melodic-automation-lanes[bot]"
<337595936+melodic-automation-lanes[bot]@users.noreply.github.com>
Date: Sun, 1 Nov 2026 07:59:54 +0000
Subject: [PATCH 4/4] Address review feedback on
work-resolve-issue/issue-11843-41275815373-1
Lane-Activity: pr-unblock/address-feedback
Lane-Thread: PRRT_kwDOTCGFQM6yXweM
---
plugins/planning/surface/index.html | 4 ++--
plugins/planning/surface/tests/ui_journey.js | 5 +++++
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/plugins/planning/surface/index.html b/plugins/planning/surface/index.html
index b20140cb2ca..c893a20eb0f 100644
--- a/plugins/planning/surface/index.html
+++ b/plugins/planning/surface/index.html
@@ -1881,7 +1881,7 @@
Interview
$("keysBody").innerHTML = KEYS.map(([k, t]) => "" + esc(k) + "" + esc(t) + "").join("");
$("keysDlg").showModal();
}
- function showFly(p){ if (p !== "notes") S.noteToast = ""; S.fly = p; lsSet("fly", p); renderFly(); renderHeader(); }
+ function showFly(p){ if (p !== "notes") { S.noteToast = ""; S.noteQueued = []; } S.fly = p; lsSet("fly", p); renderFly(); renderHeader(); }
const openPanel = p => showFly(S.fly === p ? null : p);
function focusHead(){ const h = $("qhead"); if (h) { h.tabIndex = -1; h.focus(); } }
@@ -2299,7 +2299,7 @@
Interview
async function sendNote(){
const ta = $("noteText"); if (!ta) return;
const text = ta.value.trim();
- const say = (msg, q) => { if (S.fly !== "notes") msg = ""; S.noteToast = msg; S.noteQueued = q || []; const t = $("noteToast"); if (t) t.textContent = noteToastText(); }; // a send that finishes after the panel closed holds nothing
+ const say = (msg, q) => { if (S.fly !== "notes") { msg = ""; q = []; } S.noteToast = msg; S.noteQueued = q || []; const t = $("noteToast"); if (t) t.textContent = noteToastText(); }; // a send that finishes after the panel closed holds nothing
if (!text) { say("Write a note first."); return; }
const inFlight = pending().filter(delivered).map(e => e.seq); // the wake in flight before this note, never the note itself
if (await sendFree("note", text)) { lsSet("noteDraft", null); if (S.fly === "notes") renderFly(); say("Sent. Claude's reply lands here.", queued() ? inFlight : []); }
diff --git a/plugins/planning/surface/tests/ui_journey.js b/plugins/planning/surface/tests/ui_journey.js
index a1ec9dc6f72..e9b347f54fb 100644
--- a/plugins/planning/surface/tests/ui_journey.js
+++ b/plugins/planning/surface/tests/ui_journey.js
@@ -728,6 +728,11 @@ async page => { // the user journey in order on one page, no reload after phase
const pushed = await page.waitForFunction(f => window.__io.applied !== f, frame, {timeout: 5000, polling: 50}).then(() => true).catch(() => false);
ok("the Notes send toast outlasts the next state push", pushed && /Queued, Claude is busy/.test(await text("#noteToast")), (pushed ? "" : "no frame; ") + await text("#noteToast"));
await page.click("#flyClose", {timeout: 2000}).catch(() => {});
+ // Leaving Notes clears the send toast and its busy note together, so coming back while that wake is still in flight shows neither.
+ const closed = !(await page.$eval("#fly", el => el.classList.contains("open"))), busy = /^Claude is working on /.test(await text("#workLine"));
+ await page.evaluate(() => document.querySelector('[data-panel="notes"]').click()); await settle(300);
+ ok("reopening Notes while a wake is in flight shows no busy note on its own", closed && busy && await text("#noteToast") === "", (closed ? "" : "Notes did not close; ") + (busy ? "" : "no wake in flight; ") + JSON.stringify(await text("#noteToast")));
+ await page.click("#flyClose", {timeout: 2000}).catch(() => {});
}
if (PHASE === 17) { // after the wrap-up the user confirms the third restatement; the shell has written nothing since phase 16
await page.setViewportSize({width: 1400, height: 860}); await tap("#sumBtn", 300);