From edbe4e777ce77673f94637d5e7a87f663fcab9ce Mon Sep 17 00:00:00 2001 From: "Orca (ecs-claude)" Date: Wed, 19 Aug 2026 15:33:55 +0800 Subject: [PATCH 1/2] fix(console): visible feedback when a roster row can't open a console MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brett: clicking "prod/mira" in the roster "isn't working." `openAgentForRow` (main.ts) already had two silent-failure paths — no matching `agents.toml` entry, or a matching entry found `disabled` (no url/token configured) — and both only reached `note()`, which writes to the Activity tab of the (closed by default) Debug drawer. A click that can't open anything looked identical to a dead button; nothing at the point of the click told you why. This is almost certainly what's actually going on for mira in Brett's real config (mira's ECS service is scaled to 0 today, so it may not be a live/configured `agents.toml` entry) — but the silent failure itself was worth fixing regardless of which of the two cases it is. Now `openAgentForRow` takes the clicked roster button and, on either failure path, flashes it — relabeled ("not configured" / "no console") and disabled for ~1.8s — before restoring, so the click visibly does *something* right where it happened. Also tightened the "not found" message: it used to always say "no agent console registered," which was misleading for the management agent's own roster row (it *is* registered — its console is just the persistent one above, already open) — now notes that possibility too. ## Verification - `tsc --noEmit` — clean - `vitest run` — 103/103 passing (no behavior in tested pure modules changed) - `vite build` — clean - Playwright: confirmed an ordinary configured agent (mira) still opens normally on click, and the management agent's roster row (which has no `data-agent` selector to synthetic-click) now flashes "no console" for ~1.8s instead of silently doing nothing, then restores its label. Still need Brett to confirm on his real config whether mira's `agents.toml` entry exists/is configured — the flash + note will now say which case it is next time he clicks it. 🤖 Generated by Orca ('ecs-claude'). --- console/src/main.ts | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/console/src/main.ts b/console/src/main.ts index 2330012..c20f3a1 100644 --- a/console/src/main.ts +++ b/console/src/main.ts @@ -720,7 +720,11 @@ async function scale( // already-tested path via a synthetic click rather than duplicating the // open/dial/teardown logic here. Tries the service-name form first, then the // short name — the same precedence `filterByMembers` uses for fleet members. -function openAgentForRow(svc: string, alt: string): void { +// `source` is the roster row button that was clicked — every "can't open" +// path flashes feedback on it, since `note()` alone only reaches the (closed) +// Debug drawer's Activity tab and a click that does nothing there reads as +// the roster row simply not working. +function openAgentForRow(svc: string, alt: string, source: HTMLButtonElement): void { const btn = document.querySelector( `#agent-list [data-agent="${CSS.escape(svc)}"]`, @@ -728,14 +732,28 @@ function openAgentForRow(svc: string, alt: string): void { document.querySelector( `#agent-list [data-agent="${CSS.escape(alt)}"]`, ); - if (btn) { + if (btn && !btn.disabled) { btn.click(); - } else { - note( - "info", - `agents: no agent console registered for "${svc}" — add it to agents.toml to open one`, - ); + return; } + const reason = btn + ? "registered in agents.toml but not configured (no url/token)" + : "no matching agents.toml entry — or it's the management agent, whose console is already open above"; + note("info", `agents: "${alt}" ${reason} — nothing opened`); + flashRowFeedback(source, btn ? "not configured" : "no console"); +} + +// Briefly relabel the clicked roster button so a click that can't open a +// console still visibly does *something*, instead of looking identical to a +// dead button. +function flashRowFeedback(btn: HTMLButtonElement, label: string): void { + const original = btn.textContent; + btn.disabled = true; + btn.textContent = label; + setTimeout(() => { + btn.textContent = original; + btn.disabled = false; + }, 1800); } // One delegated listener on the roster. Start executes on click; Stop is @@ -749,7 +767,7 @@ if (roster) { const openBtn = target.closest("button.row-open"); if (openBtn) { const { openAgent, openAgentAlt } = openBtn.dataset; - if (openAgent) openAgentForRow(openAgent, openAgentAlt ?? openAgent); + if (openAgent) openAgentForRow(openAgent, openAgentAlt ?? openAgent, openBtn); return; } const btn = target.closest("button.act"); From 9b7e3bf9ba7302ba95766863dd892f6a0ce1ab55 Mon Sep 17 00:00:00 2001 From: "Orca (ecs-claude)" Date: Wed, 19 Aug 2026 17:57:26 +0800 Subject: [PATCH 2/2] feat(console): combine Identity + Remote into one status box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brett: "MANAGING oab" and "REMOTE" are similar, possible to combine? They're two different failure modes (bad AWS credentials vs. no live ACP connection) so kept as two independently-rendered/tested pure functions — but both are supporting status for the same persistent Agent chat, so visually they now read as one box instead of two floating ones. ## What - `#remote` moves into the persistent side column, right after `#identity`, wrapped in a new `.mgmt-combo` div — no changes to `identityHtml`/ `remoteHtml` or their tests, just where their mount points sit and how the two boxes are bordered (`.identity` rounds only its top corners and drops its bottom border; `.remote` rounds only its bottom corners — one seam, not a double border). - Remote used to hide during fleet/agent drill-down (a Fleets-screen-only section, per an earlier request); now that it's part of the always-visible side column, that no longer made sense — it stays visible at every depth, same as Identity and Agent chat, since it's literally the connection status backing that chat. `updateScreen()`'s `remoteEl.hidden` toggle dropped; Compose still hides on drill-down (unchanged). ## Verification - `tsc --noEmit` — clean - `vitest run` — 103/103 passing (no render-function behavior changed) - `vite build` — clean - Playwright: confirmed zero gap between the two sections (one continuous box), and that Remote now stays visible after drilling into a fleet while Compose still hides. 🤖 Generated by Orca ('ecs-claude'). --- console/index.html | 12 ++++++++++-- console/src/main.ts | 9 +++++---- console/src/styles.css | 18 +++++++++++++++--- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/console/index.html b/console/index.html index 4df21cb..b6cadc2 100644 --- a/console/index.html +++ b/console/index.html @@ -147,7 +147,16 @@
-
+ +
+
+
+
Agent chat @@ -175,7 +184,6 @@
-