diff --git a/docs/web-ui.md b/docs/web-ui.md index a3036da0..8f086622 100644 --- a/docs/web-ui.md +++ b/docs/web-ui.md @@ -160,15 +160,9 @@ the previous process's socket release resolves itself. ### Container row A full-height **square agent icon** on the left (the agent's -`/icon`, an `` absolutely positioned inside a wrapper div so -its load state can never reflow the row), and the card body on -the right with three stacked lines -(`assets/app.js::renderContainers`). The `` points straight -at `/icon`; if it actually fails to load (container stopped -or mid-transient, web server not answering) the `error` handler -falls it back to the dimmed hyperhive mark (`/favicon.svg`) -instead of an empty box — a real load-failure fallback, not a -guess from container state. +`/icon`, painted as a background-image div so its load state can +never reflow the row), and the card body on the right with three +stacked lines (`assets/app.js::renderContainers`): - Line 1: agent name (link → new tab), m1nd/ag3nt chip, status badges — `⊘ rate limited` (red, while the harness is parked diff --git a/hive-c0re/assets/app.js b/hive-c0re/assets/app.js index 42309267..e23d222d 100644 --- a/hive-c0re/assets/app.js +++ b/hive-c0re/assets/app.js @@ -578,27 +578,15 @@ const pending = transientsState.get(c.name)?.kind || null; const li = el('li', { class: 'container-row' + (pending ? ' pending' : '') }); - // Full-height square agent icon, left of the card body. The - // icon is an absolutely positioned inside a wrapper div: - // the div is the flex child and sizes itself via aspect-ratio + - // stretch, the is out of flow so its load state — pending, - // loaded or broken — can never contribute intrinsic size or - // reflow the row. (issue #177) - // - // The icon points straight at the agent's `/icon`. We don't - // guess whether the agent is reachable from the container row — - // we just let the try, and if it actually fails to load - // (agent stopped, restarting, rebuilding — web server not - // answering) the error handler falls it back to the dimmed - // hyperhive mark (`/favicon.svg`, served by the dashboard - // itself, always reachable). (issues #195, #202) - const iconImg = el('img', { class: 'container-icon-img', src: `${url}icon`, alt: '' }); - const icon = el('div', { class: 'container-icon' }, iconImg); - iconImg.addEventListener('error', () => { - if (iconImg.dataset.fallback) return; // guard: don't loop if the favicon itself 404s - iconImg.dataset.fallback = '1'; - icon.classList.add('icon-unreachable'); - iconImg.src = '/favicon.svg'; + // Full-height square agent icon, left of the card body. A + // background-image on a div (not ) contributes no intrinsic + // size, so loading or failing it can't shift the row layout, and + // a failed load (stopped container, web server down) just shows + // the placeholder fill — no broken-image glyph, no collapse. + // (issue #177) + const icon = el('div', { + class: 'container-icon', + style: `background-image:url("${url}icon")`, }); // Card body: the three stacked content lines, right of the icon. const body = el('div', { class: 'card-body' }); diff --git a/hive-c0re/assets/dashboard.css b/hive-c0re/assets/dashboard.css index 5b3e8a86..0e05cc9e 100644 --- a/hive-c0re/assets/dashboard.css +++ b/hive-c0re/assets/dashboard.css @@ -89,29 +89,14 @@ a:hover { gap: 0.7em; } .container-row:not(.tombstone) > .container-icon { - position: relative; - overflow: hidden; flex: none; align-self: stretch; aspect-ratio: 1; border-radius: 6px; background-color: rgba(17, 17, 27, 0.6); -} -/* The icon image fills the square wrapper and is taken out of flow - (absolute) so its load state — pending, loaded, broken — can never - contribute intrinsic size or reflow the row. (issue #177) */ -.container-row:not(.tombstone) > .container-icon > .container-icon-img { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: contain; -} -/* When the fails to load it falls back to the dimmed hyperhive - mark, standing in for the unreachable agent icon (issues #195, #202). */ -.container-row:not(.tombstone) > .container-icon.icon-unreachable { - filter: grayscale(1); - opacity: 0.4; + background-size: contain; + background-position: center; + background-repeat: no-repeat; } .container-row .card-body { flex: 1;