fix: place processing controls within the Console navigation box

This commit is contained in:
2026-10-21 22:10:19 -05:00
parent 5cd32f4c1d
commit da43348062
6 changed files with 117 additions and 21 deletions
@@ -410,3 +410,65 @@ no document scroll at 1440x1000; the existing 1440x420 long-page checks remain.
KS59 reverts processing.css to 0f8235277's bytes and must fail that same shell
browser test. Total: 74 planned mutants. No test or mutant has run on this fix.
Webby must statically review this delta before Sage opens the full no-carry gate.
### Correction after Webby B1 (webby-0104, 2026-10-22 03:00Z)
The cause stated in the mos-1248/sage-2363 note above is withdrawn.
processing.css loads after shell.css and already overrides the frame minimum.
With the tree fixed and inspector hidden, the remaining floor is the Board's
main content. The grid min-content row preserves that floor, so 5cd32f4c1
is not an accepted layout fix. Its static review is CHANGES at Webby's
REVIEW-layout.md, commit e2e7c60e5. No run established a new layout measurement.
Next step requires a granted fixture probe for main/control heights and an
owner layout ruling; no further product change or gate is authorized yet.
Existing shell assertions remain unchanged.
### Static nav placement: mos-1249/mos-1250, sage-2383/2385/2386, webby-0106/0108
Fast-forward on 5cd32f4c1; the rejected grid is removed. The unchanged labeled
region is now the first child of nav.tree, before its Console h2. The banner
stays at its original body location with every id, role, label and shipped
string unchanged. No processing.js change and no runtime reparenting.
At >=760 the tree is the same fixed box, with the same width breakpoints and
ordinary overflow:auto. The controls are non-shrinking and non-sticky; they
scroll with overflowing nav links (sage-2386 withdraws persistent visibility).
Below760 the region takes a full flex line before Console/Projects. Its mobile
location changes from before .frame to first in the static nav; the banner
therefore precedes it in a halted mobile view. Banner behavior itself is unchanged.
Specificity (0,3,0) resets .btn margin/order and .small margin/basis, leaving
the shell rules unchanged. No content is shortened and no overflow is hidden.
Correct height arithmetic (Webby B1): page=max(innerHeight,86+C), where C is
main content height. Controls consume space within the existing nav box, not
another body row. The old failing layout was max(innerHeight,86+barHeight+C).
The halted banner remains a body row; Sage tracks that separately.
Candidate-test design change at processing-ui.test.mjs:67 under mos-1250.
Old assertion:
```js
assert.ok(html.indexOf('id="processing-controls"') < html.indexOf('<div class="frame">'));
```
New assertion text:
```js
const controlsAt = html.indexOf('id="processing-controls"');
const mainAt = html.indexOf('<main id="main"'), inspectorAt = html.indexOf('<aside class="inspector"');
assert.ok(controlsAt >= 0 && controlsAt < mainAt, 'controls are outside and before #main');
assert.ok(!(inspectorAt < controlsAt && controlsAt < html.indexOf('</aside>', inspectorAt)),
'controls are outside aside.inspector');
```
Reason: the bar moves into the frame's first column under mos-1249 while
remaining outside route-specific #main and aside.inspector. This is a change
to our unlanded candidate assertion, not a landed assertion.
All existing shell assertions stay unchanged. Added bar-inside-tree assertion
and diagnostic height output. New processing browser coverage checks 400px
Stop order=0, visibility, focus and position above Projects; also on-load
visibility/focus at1440x1000,1440x420,800x420. Debug logs report controls/tree
heights at those desktop sizes and main height; Sage judges controls over half
the 334px nav at1440x420 before the window. No run yet.
Mutant delta: KS59 now reverts the complete layout (HTML and CSS together);
KS60 hides the controls; KS61 moves them into #main; KS62 drops the (0,3,0)
button reset. Total77. Every new site has its named killing test.
+4 -4
View File
@@ -11,15 +11,15 @@
<button type="button" class="cmdk" id="cmdk" aria-haspopup="dialog" aria-controls="cmdk-dialog"><svg class="i" aria-hidden="true"><use href="/icons.svg#i-search"/></svg><span>Go to a view, decision or task</span><kbd>Ctrl K</kbd></button>
<p class="fresh" id="fresh"><span id="fresh-board"></span><span id="fresh-bus"></span></p>
<div class="cmd-right"><label>Palette<select id="palette"></select></label><label>Appearance<select id="mode"><option value="system">system</option><option value="light">light</option><option value="dim">dim</option><option value="dark">dark</option></select></label><button class="btn" id="refresh" type="button">Refresh</button><button class="btn" id="pause" type="button" aria-pressed="false">Pause</button><form id="sign-out" method="post" action="/auth/logout" hidden><button class="btn" type="submit">Sign out</button></form></div></header>
<div id="processing-controls" class="processing-controls" role="region" aria-label="Instance processing">
<div id="processing-banner" class="processing-banner" role="alert" hidden></div>
<div class="frame">
<nav class="tree" aria-label="Console"><div id="processing-controls" class="processing-controls" role="region" aria-label="Instance processing">
<strong>Processing</strong><p id="processing-state" role="status">Reading processing state.</p>
<button id="processing-halt" type="button" class="btn" disabled>Stop processing</button>
<button id="processing-resume" type="button" class="btn" disabled hidden>Resume processing</button>
<p id="processing-unavailable" class="processing-note small muted">Processing controls are unavailable here. Use mosaic halt or mosaic resume from the instance CLI.</p>
<p id="processing-receipt" class="processing-receipt small" aria-live="polite"></p></div>
<div id="processing-banner" class="processing-banner" role="alert" hidden></div>
<div class="frame">
<nav class="tree" aria-label="Console"><h2 class="tree-title">Console</h2><ul class="s1-sections" id="sections"></ul><div id="board-tree"><h2 class="tree-title">Projects</h2><div id="projects"><p class="muted">Loading projects…</p></div><p class="small muted">Select a project to filter sessions. Waiting on you always shows all projects.</p></div></nav>
<h2 class="tree-title">Console</h2><ul class="s1-sections" id="sections"></ul><div id="board-tree"><h2 class="tree-title">Projects</h2><div id="projects"><p class="muted">Loading projects…</p></div><p class="small muted">Select a project to filter sessions. Waiting on you always shows all projects.</p></div></nav>
<main id="main" class="content" tabindex="-1"><div id="signed-out" class="banner ref" role="alert" tabindex="-1" hidden></div><div id="bus-down" class="banner err" role="alert" hidden></div><div class="page-head" id="board-head"><div><h1>Control board</h1><p id="status" class="small muted" role="status">Loading board…</p></div><div class="filters"><label><input id="show-machine" type="checkbox">Show machine sessions</label></div></div>
<section id="held-section" aria-labelledby="held-title"><h2 id="held-title">Held roles <span id="held-count" class="count">–</span></h2><p class="small muted">The stack's seats: each role a run holds on the bus, from the bus <code>agents</code> read. Machine sessions are under Show machine sessions.</p><div id="held"><div class="skel-rows" aria-busy="true"><span class="sr-only">Reading the held roles.</span><span class="skel"></span><span class="skel"></span></div></div></section>
<div id="error" class="banner err" role="alert" hidden></div>
+8 -16
View File
@@ -5,26 +5,18 @@
.processing-controls button:focus-visible { outline:3px solid currentColor; outline-offset:3px; }
.processing-banner { padding:.75rem 1rem; font-weight:600; border-bottom:2px solid currentColor; }
.processing-controls [hidden], .processing-banner[hidden] { display:none; }
/* Keep the controls in normal flow. On desktop the frame fills the remaining
viewport rather than adding a full viewport below the controls. Navigation
retains the shell's command-bar/footer bounds independently of this bar. */
/* The desktop controls share the nav's existing viewport box. They scroll with
its contents: no sticky overlay may cover a focused navigation link. */
.tree .processing-controls .btn { margin:0; order:0; }
.tree .processing-controls .small { margin:0; flex-basis:100%; }
@media (min-width:760px) {
body {
--processing-nav:240px;
display:grid;
grid-template-columns:minmax(0,1fr);
grid-template-rows:auto auto auto minmax(min-content,1fr) auto;
min-height:100vh;
}
.cmdbar { grid-row:1; }
.processing-controls { grid-row:2; }
.processing-banner { grid-row:3; }
footer.foot { grid-row:5; }
.processing-controls, .processing-banner { margin-left:var(--processing-nav); }
.frame { grid-row:4; min-height:0; }
body { --processing-nav:240px; }
.processing-banner { margin-left:var(--processing-nav); }
.tree > .processing-controls { flex:none; padding:.7rem .4rem; }
.frame > .content { grid-column:2; }
.frame > .tree { position:fixed; width:var(--processing-nav); }
}
@media (max-width:759px) { .tree > .processing-controls { flex-basis:100%; } }
@media (min-width:760px) and (max-width:959px) { body { --processing-nav:200px; } }
@media (min-width:1900px) { body { --processing-nav:280px; } }
@media (min-width:2560px) { body { --processing-nav:320px; } }
@@ -93,3 +93,30 @@ for (const [name,path,kind] of [
assert.equal(await b.evaluate('window.processingRequests.length'), count, 'no processing GET or POST after shared sign-out');
if(kind==='signin') assert.equal(await b.evaluate(`!!document.querySelector('#signed-out a[href^="/auth/login"]')`), true);
});
test('processing browser: static nav controls keep Stop visible, ordered and keyboard reachable', async t => {
const { b } = await setup(t);
for (const [width, height] of [[400, 1000], [1440, 1000], [1440, 420], [800, 420]]) {
await b.viewport(width, height);
await b.evaluate('scrollTo(0,0);document.querySelector(".tree").scrollTop=0;true');
const layout = await b.evaluate(`(() => {
const controls = document.querySelector('#processing-controls'), stop = document.querySelector('#processing-halt');
const c = controls.getBoundingClientRect(), n = document.querySelector('.tree').getBoundingClientRect();
const s = stop.getBoundingClientRect(), bar = document.querySelector('.cmdbar').getBoundingClientRect();
return { width: innerWidth, height: innerHeight, controls: c.height, tree: n.height,
main: document.querySelector('#main').getBoundingClientRect().height,
top: s.top, bottom: s.bottom, barBottom: bar.bottom,
projectsTop: document.querySelector('#projects').getBoundingClientRect().top,
order: getComputedStyle(stop).order, visible: stop.getClientRects().length > 0 &&
getComputedStyle(stop).visibility === 'visible' && getComputedStyle(stop).display !== 'none' };
})()`);
console.log('ROW191_LAYOUT', JSON.stringify(layout));
assert.ok(layout.visible && layout.top >= layout.barBottom && layout.bottom <= height,
'Stop is visible below the command bar and inside the viewport on load');
assert.equal(layout.order, '0', 'nav button rules do not reorder Stop');
if (width === 400) assert.ok(layout.top < layout.projectsTop, 'Stop precedes Projects on mobile');
await b.evaluate('document.querySelector("#processing-halt").focus();true');
assert.equal(await b.evaluate('document.activeElement.id'), 'processing-halt');
}
});
+5 -1
View File
@@ -64,7 +64,11 @@ test('unknown writes are not retried or displayed as success and partial stops r
});
test('the persistent shell loads the control outside route-specific content with accessible status and native buttons', () => {
const html = readFileSync(new URL('../src/public/index.html', import.meta.url), 'utf8');
assert.ok(html.indexOf('id="processing-controls"') < html.indexOf('<div class="frame">'));
const controlsAt = html.indexOf('id="processing-controls"');
const mainAt = html.indexOf('<main id="main"'), inspectorAt = html.indexOf('<aside class="inspector"');
assert.ok(controlsAt >= 0 && controlsAt < mainAt, 'controls are outside and before #main');
assert.ok(!(inspectorAt < controlsAt && controlsAt < html.indexOf('</aside>', inspectorAt)),
'controls are outside aside.inspector');
assert.match(html, /src="\/processing.js"/); assert.match(html, /href="\/processing.css"/);
assert.match(html, /id="processing-receipt"[^>]*aria-live="polite"/);
assert.match(html, /<button[^>]*id="processing-halt"[^>]*type="button"/);
+11
View File
@@ -386,6 +386,17 @@ test('shell in a browser: System default, seeded states, 400px, focus and the Ct
scroll: document.documentElement.scrollHeight };
})())`));
assert.ok(processingBar.visible && processingBar.height > 0, 'the processing bar is visible on the short Board');
const treeRect = await b.evaluate(`(() => {
const r = document.querySelector('.tree').getBoundingClientRect();
return { top: r.top, bottom: r.bottom };
})()`);
assert.ok(processingBar.top >= treeRect.top && processingBar.bottom <= treeRect.bottom,
'the processing bar sits inside the existing tree box');
console.log('ROW191_LAYOUT', JSON.stringify(await b.evaluate(`(() => {
const height = s => document.querySelector(s).getBoundingClientRect().height;
return { width: innerWidth, height: innerHeight, controls: height('#processing-controls'),
tree: height('.tree'), main: height('#main') };
})()`)));
assert.ok(processingBar.top >= bar[1] && processingBar.bottom <= processingBar.viewport,
'the processing bar sits inside the viewport below the command bar');
assert.equal(processingBar.scroll, processingBar.viewport, 'the visible processing bar adds no short-page scroll');