From 307f88be3e0d2095bdc13bd13c43185edcffba73 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 15 Nov 2026 11:35:48 -0600 Subject: [PATCH] fix(webui): sp12 shots, at 1200px wide and above the page keeps clear of the agent drawer so nothing sits under it; where this Console could change the organization's agents but Stop is not offered, the drawer says why (a copy, a protected agent, or not running); drawer sections drop the page section margin (#1873, lane sp12-agent-drawer) --- packages/webui/src/public/shell.css | 5 ++++- packages/webui/src/public/workspace.js | 4 ++++ packages/webui/tests/agent-drawer-browser.test.mjs | 6 +++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/webui/src/public/shell.css b/packages/webui/src/public/shell.css index fac063015..bca4f7427 100644 --- a/packages/webui/src/public/shell.css +++ b/packages/webui/src/public/shell.css @@ -741,7 +741,7 @@ html[data-organizations] .ws-drawer-f{display:grid;grid-template-columns:96px mi html[data-organizations] .ws-drawer-f dt{color:var(--muted);font-weight:600} html[data-organizations] .ws-drawer-f dd{margin:0;color:var(--strong);overflow-wrap:anywhere;min-width:0} html[data-organizations] .ws-drawer-f code,html[data-organizations] .ws-drawer .mono{font:12px var(--mono)} -html[data-organizations] .ws-drawer-sec{display:grid;gap:6px} +html[data-organizations] .ws-drawer-sec{display:grid;gap:6px;margin:0} html[data-organizations] .ws-drawer-sec h3{margin:0;font:700 11px/1.3 var(--font);letter-spacing:.06em;text-transform:uppercase;color:var(--muted)} html[data-organizations] .ws-drawer-list{list-style:none;margin:0;padding:0;display:grid;gap:4px;font-size:13px} html[data-organizations] .ws-drawer-list li{border:1px solid var(--line);border-radius:6px;padding:6px 9px;background:var(--canvas);overflow-wrap:anywhere} @@ -753,6 +753,9 @@ html[data-organizations] .ws-drawer-confirm{display:flex;flex-wrap:wrap;gap:8px; html[data-organizations] .ws-drawer .ws-btn.danger{color:var(--danger)} html[data-organizations] .ws-drawer-status{margin:0;flex-basis:100%;font-size:13px;color:var(--strong)} html[data-organizations] .ws-drawer-status:empty{display:none} +html[data-organizations] .ws-drawer-acts .note{flex-basis:100%} +/* Wide screens: the page keeps clear of the drawer, so nothing sits under it. */ +@media (min-width:1200px){html[data-organizations] body:has(.ws-drawer) .content{padding-right:calc(420px + 22px)}} @media (max-width:700px){ html[data-organizations] .ws-drawer{top:0;left:0;right:0;width:auto;border-left:0;box-shadow:none} } diff --git a/packages/webui/src/public/workspace.js b/packages/webui/src/public/workspace.js index 5d655fa02..5c208847b 100644 --- a/packages/webui/src/public/workspace.js +++ b/packages/webui/src/public/workspace.js @@ -1173,6 +1173,10 @@ if (chat && thread?.thread) acts.push(`Message ${esc(a.name)}`); else if (chat && a.level === 'org' && window.MosaicAgents?.isThread?.(def)) acts.push(`Message ${esc(a.name)} Start one from New thread in ${esc(orgName)}.`); if (change && a.level === 'org' && !a.keep && running(item) > 0) acts.push(stopActions(pick, a, orgName)); + // Why Stop is not offered, in words, where this Console could otherwise change the organization's agents. + else if (change) acts.push(`

${a.level !== 'org' ? 'A copy is not stopped from here.' + : a.keep ? `${esc(a.name)} is one of ${esc(orgName)}’s protected agents, so it is not stopped from here.` + : 'It is not running, so there is nothing to stop.'}

`); const st = stopState?.id === id ? stopState : null; const actions = chat || change ? `
${acts.join(' ')}

${esc(st?.text || '')}

` diff --git a/packages/webui/tests/agent-drawer-browser.test.mjs b/packages/webui/tests/agent-drawer-browser.test.mjs index b90eaddc4..4cff4dcf8 100644 --- a/packages/webui/tests/agent-drawer-browser.test.mjs +++ b/packages/webui/tests/agent-drawer-browser.test.mjs @@ -140,7 +140,9 @@ test('a row on #/agents opens the drawer: a region with a heading, the page stay // At the right of the page. const box = await b.evaluate(`(() => { const r = document.querySelector('${d}').getBoundingClientRect(); return [Math.round(r.left), Math.round(r.right), innerWidth]; })()`); assert.ok(box[0] > box[2] / 2, `drawer left edge ${box[0]} should be in the right half of ${box[2]}`); - assert.ok(box[1] >= box[2] - 20, `drawer right edge ${box[1]} should reach the right of ${box[2]}`); + assert.ok(box[1] >= box[2] - 20, `drawer right edge ${box[1]} should reach the right of ${box[2]}`); // At 1440 wide nothing on the page sits under it: the agents table ends left of the drawer. + const table = await b.evaluate(`Math.round(document.querySelector('[data-ws-agent-list]').getBoundingClientRect().right)`); + assert.ok(table <= box[0], `the agents table right edge ${table} should be left of the drawer at ${box[0]}`); }); test('the drawer shows each field from its read: name, organization, role and source, state, model, project, task, open tasks and decisions', async t => { @@ -297,6 +299,8 @@ test('Message opens the agent\'s thread in an Assistant pane; Stop asks first an await b.evaluate('location.hash = "#/agents?agent=acme%3Achief"'); await wait(open('acme:chief')); assert.equal(await has('[data-ws-drawer="acme:chief"] [data-drawer-stop]'), false); + // Where Stop is not offered, the drawer says why beside the actions. + assert.match(await text('[data-ws-drawer="acme:chief"] [data-drawer-actions] [data-drawer-nostop]'), /^chief is one of Acme Studio’s protected agents, so it is not stopped from here\.$/); }); test('no action is shown where the Console cannot write for the agent\'s organization', async t => {