From 7d6124527ab6160c94b2c7b7abd6ef7b6cbdb2c5 Mon Sep 17 00:00:00 2001 From: Andrew Anderson Date: Mon, 5 Oct 2026 12:33:35 -0400 Subject: [PATCH 1/4] =?UTF-8?q?=E2=9C=A8=20dashboard:=20link=20agents=20na?= =?UTF-8?q?v=20and=20surface=20up=20next?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andrew Anderson --- .../dashboard/agent_collapsed_tiles_test.go | 20 ++++++ src/pkg/dashboard/static/index.html | 67 ++++++++++++++++++- 2 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/pkg/dashboard/agent_collapsed_tiles_test.go b/src/pkg/dashboard/agent_collapsed_tiles_test.go index 85c1be73a2..dd7586091a 100644 --- a/src/pkg/dashboard/agent_collapsed_tiles_test.go +++ b/src/pkg/dashboard/agent_collapsed_tiles_test.go @@ -40,6 +40,26 @@ func TestAgentsCollapsedSummaryRendersPerAgentTiles(t *testing.T) { } } +func TestAgentsSidebarLinkAndNavbarUpNextContracts(t *testing.T) { + html := indexHTML(t) + for _, want := range []string{ + `data-section="agents-section" data-action="ocNavigate" data-arg0="agents-section">🤖Agents`, + `id="agent-navbar-upnext"`, + `class="agent-navbar-upnext"`, + `function renderAgentNavbarUpNext(agents, nowMs)`, + `sortAgentsForCollapsedTiles(agents, now).filter(a => agentNavbarHasSchedule(a, now)).slice(0, 3)`, + `function openAgentsUpNextPanel()`, + `data-action="openAgentsUpNextPanel"`, + `renderAgentNavbarUpNext(agents, Date.now())`, + `refreshAgentNavbarUpNextCountdowns(now);`, + `@media (max-width: 1180px) { .agent-navbar-upnext { display: none !important; } }`, + } { + if !strings.Contains(html, want) { + t.Fatalf("agents sidebar/navbar up-next contract missing %q", want) + } + } +} + func TestAgentsCollapsedTileSortOrder(t *testing.T) { node, err := exec.LookPath("node") if err != nil { diff --git a/src/pkg/dashboard/static/index.html b/src/pkg/dashboard/static/index.html index 5bb8055b2a..bcdc6bd219 100644 --- a/src/pkg/dashboard/static/index.html +++ b/src/pkg/dashboard/static/index.html @@ -3311,7 +3311,21 @@ .agent-collapsed-tile.stopped .agent-tile-dot, .agent-collapsed-tile.needs-login .agent-tile-dot, .repo-collapsed-tile.error .agent-tile-dot { background: var(--red); box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 40%, transparent); } .repo-collapsed-tile.blocked .agent-tile-dot { background: var(--yellow); box-shadow: 0 0 0 1px color-mix(in srgb, var(--yellow) 40%, transparent); } .agent-collapsed-tile.disabled .agent-tile-dot, .repo-collapsed-tile.paused .agent-tile-dot, .repo-collapsed-tile.idle .agent-tile-dot { background: var(--muted); box-shadow: none; } - @media (prefers-reduced-motion: reduce) { .agent-collapsed-tile.active .agent-tile-dot { animation: none; } } + .agent-navbar-upnext { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; max-width: min(480px, 34vw); overflow: hidden; } + .agent-navbar-upnext[hidden] { display: none; } + .agent-navbar-tile { flex: 0 1 150px; min-width: 0; height: 32px; border: 1px solid var(--line-subtle); border-radius: var(--r-pill); background: color-mix(in srgb, var(--surface-2) 64%, transparent); color: var(--text); cursor: pointer; display: inline-grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: 1fr 1fr; align-items: center; column-gap: var(--sp-2); padding: var(--sp-1) var(--sp-3); font: inherit; font-size: var(--fs-2xs); line-height: 1.05; text-align: left; } + .agent-navbar-tile:hover, .agent-navbar-tile:focus-visible { border-color: var(--border); background: var(--surface-2); outline: none; } + .agent-navbar-tile.up-next { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent); } + .agent-navbar-tile.active { border-color: color-mix(in srgb, var(--green) 70%, var(--line-subtle)); } + .agent-navbar-tile.stopped, .agent-navbar-tile.needs-login { border-color: color-mix(in srgb, var(--red) 45%, var(--line-subtle)); } + .agent-navbar-tile .agent-tile-dot { grid-row: 1 / 3; } + .agent-navbar-tile .agent-tile-name { grid-column: 2; } + .agent-navbar-tile .agent-tile-next { grid-column: 2 / 4; color: var(--muted); } + .agent-navbar-tile .agent-up-next { grid-column: 3; } + .agent-navbar-tile.active .agent-tile-dot { animation: green-pulse 2.4s ease-in-out infinite; } + .agent-navbar-tile.stopped .agent-tile-dot, .agent-navbar-tile.needs-login .agent-tile-dot { background: var(--red); box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 40%, transparent); } + @media (max-width: 1180px) { .agent-navbar-upnext { display: none !important; } } + @media (prefers-reduced-motion: reduce) { .agent-collapsed-tile.active .agent-tile-dot, .agent-navbar-tile.active .agent-tile-dot { animation: none; } } .mini-summary-strip .sparkline { display: inline-flex; margin: 0; } .mini-summary-strip .sparkline-line, .mini-spark .sparkline-line { stroke-dasharray: 160; stroke-dashoffset: 160; animation: mini-spark-draw 650ms ease-out forwards; } .mini-gauge-track { stroke: var(--line-subtle); } @@ -4219,6 +4233,7 @@

🐝 Getting Started🪙Tokens 💵Cost 🔎Review Queue + 🤖Agents 📋Audit Log 🔍Diagnostics @@ -4291,6 +4306,7 @@

🐝 Getting Started +
@@ -5729,8 +5745,9 @@

What sh } function refreshAgentsCollapsedCountdowns(nowMs) { const strip = document.querySelector('.agents-collapsed-tiles'); - if (!strip) return; const now = Number.isFinite(nowMs) ? nowMs : Date.now(); + refreshAgentNavbarUpNextCountdowns(now); + if (!strip) return; const tiles = Array.from(strip.querySelectorAll('.agent-collapsed-tile')); tiles.forEach(tile => { const rawNext = String(tile.getAttribute('data-agent-next-kick-ms') || '').trim(); @@ -5761,6 +5778,50 @@

What sh if (window._agentsCollapsedCountdownInterval) return; window._agentsCollapsedCountdownInterval = setInterval(refreshAgentsCollapsedCountdowns, AGENTS_COLLAPSED_COUNTDOWN_TICK_MS); } + function agentNavbarHasSchedule(a, nowMs) { + return !!a && !agentIsDisabled(a) && (a.busy === 'working' || Number.isFinite(agentCollapsedNextKickMs(a, nowMs)) || a.continuous === true); + } + function agentNavbarTileHtml(a, nowMs, isUpNext) { + const name = (a && (a.displayName || a.name)) || 'agent'; + const disabled = agentIsDisabled(a); + const isDown = a && a.state !== 'running'; + const active = !!a && !disabled && a.busy === 'working'; + const auth = agentCollapsedAuthInfo(a || {}, isDown); + const nextMs = agentCollapsedNextKickMs(a, nowMs); + const countdown = agentCollapsedNextLabel(a, nowMs); + const classes = ['agent-navbar-tile', active ? 'active' : '', isDown && !disabled ? 'stopped' : '', auth.needsLogin ? 'needs-login' : '', isUpNext ? 'up-next' : ''].filter(Boolean).join(' '); + const title = `${name} +next kick: ${agentCollapsedTimestampDetail(nextMs, a && (a.nextKickAt || a.nextKick))}`; + const upNext = isUpNext ? 'up next' : ''; + return ``; + } + function renderAgentNavbarUpNext(agents, nowMs) { + const wrap = document.getElementById('agent-navbar-upnext'); + if (!wrap) return; + const now = Number.isFinite(nowMs) ? nowMs : Date.now(); + const list = sortAgentsForCollapsedTiles(agents, now).filter(a => agentNavbarHasSchedule(a, now)).slice(0, 3); + wrap.hidden = list.length === 0; + wrap.innerHTML = list.length ? list.map((a, i) => agentNavbarTileHtml(a, now, i === 0)).join('') : ''; + if (list.length) ensureAgentsCollapsedCountdownTicker(); + } + function refreshAgentNavbarUpNextCountdowns(nowMs) { + const wrap = document.getElementById('agent-navbar-upnext'); + if (!wrap || wrap.hidden) return; + const now = Number.isFinite(nowMs) ? nowMs : Date.now(); + Array.from(wrap.querySelectorAll('.agent-navbar-tile')).forEach(tile => { + const rawNext = String(tile.getAttribute('data-agent-next-kick-ms') || '').trim(); + const nextMs = rawNext ? Number(rawNext) : NaN; + const countdown = tile.getAttribute('data-agent-active') === '1' ? 'now' : formatKickCountdown(Number.isFinite(nextMs) ? nextMs - now : null); + const el = tile.querySelector('.agent-tile-countdown'); + if (el && el.textContent !== countdown) el.textContent = countdown; + }); + } + function openAgentsUpNextPanel() { + navPeekRestore('agents-section', { instant: true }); + const expanded = navPeekExpand('agents-section'); + if (!expanded && isSectionCollapsed('agents-section')) toggleSection('agents-section', { collapsed: false, persist: false, navPeek: true }); + ocAfterNavLayoutSettles(['agents-section'], function() { ocScrollSectionIntoView('agents-section'); }); + } function agentsCollapsedTilesHtml(agents, nowMs) { const list = sortAgentsForCollapsedTiles(agents, nowMs); if (!list.length) return ''; @@ -9798,6 +9859,7 @@

What sh })() : (Array.isArray(agents) ? agents : []); refreshSectionCardShell('agents-section'); setSectionSummary('agents-section', agentsSectionSummary(agents)); + renderAgentNavbarUpNext(agents, Date.now()); const el = document.getElementById('agents'); // Preserve custom prompt input values across re-renders const savedInputs = {}; @@ -9807,6 +9869,7 @@

What sh }); const { sorted: sortedAgents } = _sortAgentsBySidebar(agents); if (sortedAgents.length === 0) { + renderAgentNavbarUpNext([], Date.now()); setSectionSummary('agents-section', agentsSectionSummary([])); setIfChanged(el, emptyStateCard('🤖', 'Add your first agent', 'Agents are the workers that scan repos, review changes, keep CI healthy, and write docs. Start from a quick template or build one from scratch. ' + From 140d3b3a6e7f86038361d5ce54c9a29f469cf40e Mon Sep 17 00:00:00 2001 From: clubanderson <407614+clubanderson@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:34:28 +0000 Subject: [PATCH 2/4] changelog: fragment for #10632 Generated by changelog-fragment-guard from the PR title (#5675). Edit the wording freely, or replace this file with the no-changelog label if the change is not user-visible. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Signed-off-by: clubanderson <407614+clubanderson@users.noreply.github.com> --- changelog.d/added-10632.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/added-10632.md diff --git a/changelog.d/added-10632.md b/changelog.d/added-10632.md new file mode 100644 index 0000000000..16042d339d --- /dev/null +++ b/changelog.d/added-10632.md @@ -0,0 +1 @@ +- dashboard: Agents sidebar link + up-next agent tiles in top navbar (#10632) From 1c0bd3e043e1b6f77c8424f08d8ebb18d43d1c3e Mon Sep 17 00:00:00 2001 From: Andrew Anderson Date: Mon, 5 Oct 2026 12:46:15 -0400 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=90=9B=20dashboard:=20fix=20local=20v?= =?UTF-8?q?alidation=20blockers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andrew Anderson --- src/pkg/dashboard/static/index.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pkg/dashboard/static/index.html b/src/pkg/dashboard/static/index.html index bcdc6bd219..15323d9a56 100644 --- a/src/pkg/dashboard/static/index.html +++ b/src/pkg/dashboard/static/index.html @@ -3325,7 +3325,8 @@ .agent-navbar-tile.active .agent-tile-dot { animation: green-pulse 2.4s ease-in-out infinite; } .agent-navbar-tile.stopped .agent-tile-dot, .agent-navbar-tile.needs-login .agent-tile-dot { background: var(--red); box-shadow: 0 0 0 1px color-mix(in srgb, var(--red) 40%, transparent); } @media (max-width: 1180px) { .agent-navbar-upnext { display: none !important; } } - @media (prefers-reduced-motion: reduce) { .agent-collapsed-tile.active .agent-tile-dot, .agent-navbar-tile.active .agent-tile-dot { animation: none; } } + @media (prefers-reduced-motion: reduce) { .agent-collapsed-tile.active .agent-tile-dot { animation: none; } } + @media (prefers-reduced-motion: reduce) { .agent-navbar-tile.active .agent-tile-dot { animation: none; } } .mini-summary-strip .sparkline { display: inline-flex; margin: 0; } .mini-summary-strip .sparkline-line, .mini-spark .sparkline-line { stroke-dasharray: 160; stroke-dashoffset: 160; animation: mini-spark-draw 650ms ease-out forwards; } .mini-gauge-track { stroke: var(--line-subtle); } From f23d1001a7eeb397b9052135cbb9307ca60adc2a Mon Sep 17 00:00:00 2001 From: Andrew Anderson Date: Mon, 5 Oct 2026 13:02:03 -0400 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=90=9B=20dashboard:=20satisfy=20style?= =?UTF-8?q?=20ratchet=20for=20navbar=20tiles?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Andrew Anderson --- src/pkg/dashboard/static/index.html | 4 ++-- src/pkg/dashboard/webstatic/style_ratchet_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pkg/dashboard/static/index.html b/src/pkg/dashboard/static/index.html index 15323d9a56..c9b3f7bd2b 100644 --- a/src/pkg/dashboard/static/index.html +++ b/src/pkg/dashboard/static/index.html @@ -3313,12 +3313,12 @@ .agent-collapsed-tile.disabled .agent-tile-dot, .repo-collapsed-tile.paused .agent-tile-dot, .repo-collapsed-tile.idle .agent-tile-dot { background: var(--muted); box-shadow: none; } .agent-navbar-upnext { display: inline-flex; align-items: center; gap: var(--sp-2); min-width: 0; max-width: min(480px, 34vw); overflow: hidden; } .agent-navbar-upnext[hidden] { display: none; } - .agent-navbar-tile { flex: 0 1 150px; min-width: 0; height: 32px; border: 1px solid var(--line-subtle); border-radius: var(--r-pill); background: color-mix(in srgb, var(--surface-2) 64%, transparent); color: var(--text); cursor: pointer; display: inline-grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: 1fr 1fr; align-items: center; column-gap: var(--sp-2); padding: var(--sp-1) var(--sp-3); font: inherit; font-size: var(--fs-2xs); line-height: 1.05; text-align: left; } + .agent-navbar-tile { flex: 0 1 150px; min-width: 0; height: 32px; border: 1px solid var(--line-subtle); border-radius: var(--r-pill); background: color-mix(in srgb, var(--surface-2) 64%, transparent); color: var(--text); cursor: pointer; display: inline-grid; grid-template-columns: auto minmax(0, 1fr) auto; grid-template-rows: 1fr 1fr; align-items: center; column-gap: var(--sp-2); box-sizing: border-box; font: inherit; font-size: var(--fs-2xs); line-height: 1.05; text-align: left; } .agent-navbar-tile:hover, .agent-navbar-tile:focus-visible { border-color: var(--border); background: var(--surface-2); outline: none; } .agent-navbar-tile.up-next { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 55%, transparent); } .agent-navbar-tile.active { border-color: color-mix(in srgb, var(--green) 70%, var(--line-subtle)); } .agent-navbar-tile.stopped, .agent-navbar-tile.needs-login { border-color: color-mix(in srgb, var(--red) 45%, var(--line-subtle)); } - .agent-navbar-tile .agent-tile-dot { grid-row: 1 / 3; } + .agent-navbar-tile .agent-tile-dot { grid-row: 1 / 3; margin-left: var(--sp-2); } .agent-navbar-tile .agent-tile-name { grid-column: 2; } .agent-navbar-tile .agent-tile-next { grid-column: 2 / 4; color: var(--muted); } .agent-navbar-tile .agent-up-next { grid-column: 3; } diff --git a/src/pkg/dashboard/webstatic/style_ratchet_test.go b/src/pkg/dashboard/webstatic/style_ratchet_test.go index 04237ebc69..2c5b7697d5 100644 --- a/src/pkg/dashboard/webstatic/style_ratchet_test.go +++ b/src/pkg/dashboard/webstatic/style_ratchet_test.go @@ -28,7 +28,7 @@ var styleRatchetBaselines = map[string]styleRatchetCounts{ "operator static/index.html": { inlineStyles: 1588, rawColors: 143, - rawFontSizes: 666, + rawFontSizes: 666, rawPadding: 229, rawBorderRadii: 176}, "contributor landing": {