repl: route newTab through setActiveTab so toolbar buttons sync

newTab() used to mutate state.activeTabId directly, skipping
setActiveTab's auto-pause hook AND the toolbar button-state sync.
Opening a new tab during a running eval left the send button
disabled on the brand-new (idle) tab — fox would land on a tab he
couldn't type into.

Now newTab pushes the tab onto state.tabs, then awaits setActiveTab
which handles both the outgoing-tab auto-pause (if applicable) and
the incoming-tab button sync.

Verified end-to-end headless: with COOP/COEP enabled, switching
from a c-tier eval to a new tab now correctly snapshots the source
tab's state, leaves the send button enabled on tab 2, and on
switch-back the resumed eval re-fires from the snapshot.
This commit is contained in:
russell@unturf.com 2026-06-15 11:32:22 -04:00
parent 0b711abd61
commit ca27d03c60
No known key found for this signature in database
3 changed files with 54 additions and 15 deletions

View file

@ -146,13 +146,15 @@ function relock() {
}
// ─── Tabs ───────────────────────────────────────────────────────────
function newTab(name) {
async function newTab(name) {
const id = state.nextTabId++;
name = name || `session ${id}`;
state.tabs.push({ id, name, tier: "c", transcript: [], inputDraft: "" });
state.activeTabId = id;
renderAll();
saveSoon();
// Route through setActiveTab so the outgoing tab's auto-pause
// fires and the toolbar buttons sync to the freshly-created
// (idle) tab. Without this, opening a new tab during an eval
// leaves the send button disabled and the eval orphaned.
await setActiveTab(id);
}
function closeTab(id) {
@ -206,8 +208,19 @@ async function setActiveTab(id) {
}
state.activeTabId = id;
renderAll();
saveSoon();
// Sync the global toolbar button states to the incoming tab —
// send/cancel/pause are not tab-scoped DOM elements, so a
// switch-away during one tab's eval would otherwise leave the
// newly-active (idle) tab with send disabled. The active tab
// is idle iff nothing in state.pending matches its id.
const incoming = activeTab();
if (incoming) {
const evalRunning = isEvalInFlight(incoming);
sendBtn.disabled = evalRunning;
cancelBtn.disabled = !evalRunning;
pauseBtn.disabled = !evalRunning || !pauseFlagView;
}
saveSoon();
if (incoming && incoming.autoPause) {
await autoResumeTab(incoming);
}

View file

@ -146,13 +146,15 @@ function relock() {
}
// ─── Tabs ───────────────────────────────────────────────────────────
function newTab(name) {
async function newTab(name) {
const id = state.nextTabId++;
name = name || `session ${id}`;
state.tabs.push({ id, name, tier: "c", transcript: [], inputDraft: "" });
state.activeTabId = id;
renderAll();
saveSoon();
// Route through setActiveTab so the outgoing tab's auto-pause
// fires and the toolbar buttons sync to the freshly-created
// (idle) tab. Without this, opening a new tab during an eval
// leaves the send button disabled and the eval orphaned.
await setActiveTab(id);
}
function closeTab(id) {
@ -206,8 +208,19 @@ async function setActiveTab(id) {
}
state.activeTabId = id;
renderAll();
saveSoon();
// Sync the global toolbar button states to the incoming tab —
// send/cancel/pause are not tab-scoped DOM elements, so a
// switch-away during one tab's eval would otherwise leave the
// newly-active (idle) tab with send disabled. The active tab
// is idle iff nothing in state.pending matches its id.
const incoming = activeTab();
if (incoming) {
const evalRunning = isEvalInFlight(incoming);
sendBtn.disabled = evalRunning;
cancelBtn.disabled = !evalRunning;
pauseBtn.disabled = !evalRunning || !pauseFlagView;
}
saveSoon();
if (incoming && incoming.autoPause) {
await autoResumeTab(incoming);
}

View file

@ -146,13 +146,15 @@ function relock() {
}
// ─── Tabs ───────────────────────────────────────────────────────────
function newTab(name) {
async function newTab(name) {
const id = state.nextTabId++;
name = name || `session ${id}`;
state.tabs.push({ id, name, tier: "c", transcript: [], inputDraft: "" });
state.activeTabId = id;
renderAll();
saveSoon();
// Route through setActiveTab so the outgoing tab's auto-pause
// fires and the toolbar buttons sync to the freshly-created
// (idle) tab. Without this, opening a new tab during an eval
// leaves the send button disabled and the eval orphaned.
await setActiveTab(id);
}
function closeTab(id) {
@ -206,8 +208,19 @@ async function setActiveTab(id) {
}
state.activeTabId = id;
renderAll();
saveSoon();
// Sync the global toolbar button states to the incoming tab —
// send/cancel/pause are not tab-scoped DOM elements, so a
// switch-away during one tab's eval would otherwise leave the
// newly-active (idle) tab with send disabled. The active tab
// is idle iff nothing in state.pending matches its id.
const incoming = activeTab();
if (incoming) {
const evalRunning = isEvalInFlight(incoming);
sendBtn.disabled = evalRunning;
cancelBtn.disabled = !evalRunning;
pauseBtn.disabled = !evalRunning || !pauseFlagView;
}
saveSoon();
if (incoming && incoming.autoPause) {
await autoResumeTab(incoming);
}