Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion console/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
</header>
<main class="content">
<section id="config" class="config-wrap"></section>
<section id="fleet-detail" class="fleet-detail-wrap" hidden>
<div id="fd-header"></div>
<section id="roster" class="roster-wrap"></section>
</section>
<section id="config-editor" class="cfg-editor-wrap" hidden>
<div class="cfg-editor-head">
<span class="cfg-label" id="cfg-editor-title">edit fleets.toml</span>
Expand Down Expand Up @@ -226,7 +230,6 @@
</form>
</div>
</section>
<section id="roster" class="roster-wrap"></section>
</main>
<script type="module" src="/src/main.ts"></script>
</body>
Expand Down
40 changes: 40 additions & 0 deletions console/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
renderRoster,
renderIdentity,
renderFleetConfig,
renderFleetDetailHeader,
renderRemote,
filterByMembers,
deploymentKey,
Expand Down Expand Up @@ -46,6 +47,8 @@ let agentConsole: AgentConsole | null = null;
const roster = document.getElementById("roster");
const identityEl = document.getElementById("identity");
const configEl = document.getElementById("config");
const fleetDetailEl = document.getElementById("fleet-detail");
const fdHeaderEl = document.getElementById("fd-header");
const remoteEl = document.getElementById("remote");
const editorSection = document.getElementById("config-editor");
const editorMount = document.getElementById("cfg-editor-mount");
Expand Down Expand Up @@ -267,6 +270,17 @@ async function refreshRemote(): Promise<void> {
}
}

// The console's top-level drill-down (ADR #83 Part A): Fleets ↔ Fleet detail.
// `activeFleet === null` shows the Fleets screen (the `#config` list); a
// selected fleet shows Fleet detail (breadcrumb header + the members roster)
// instead. This is screen 7.2 vs 7.3 — slice 2's scope; the further drill into
// an Agent console (7.4) is slice 3.
function updateScreen(): void {
if (configEl) configEl.hidden = activeFleet !== null;
if (fleetDetailEl) fleetDetailEl.hidden = activeFleet === null;
if (activeFleet && fdHeaderEl) renderFleetDetailHeader(fdHeaderEl, activeFleet);
}

// Switch the active fleet by identity (name): re-point every read at its cluster
// (and thus its bound credential), filter the roster to its members, and refresh
// immediately — so "switch fleet" == "switch managing account + roster" the ADR
Expand All @@ -282,6 +296,22 @@ function selectFleet(name: string): void {
if (clusterLabel) clusterLabel.textContent = `${activeFleet} · ${activeCluster}`;
note("info", `config: switched to fleet "${activeFleet}" (cluster "${activeCluster}")`);
if (configEl) renderFleetConfig(configEl, fleetConfig, activeFleet);
updateScreen();
void refreshIdentity();
void tick();
}

// The Fleet detail screen's "← Fleets" breadcrumb: back to the Fleets list,
// unfiltered roster (whole default cluster) until another fleet is picked.
function deselectFleet(): void {
if (!activeFleet) return;
activeFleet = null;
activeCluster = DEFAULT_CLUSTER;
activeMembers = [];
if (clusterLabel) clusterLabel.textContent = activeCluster;
note("info", "config: back to Fleets");
if (configEl) renderFleetConfig(configEl, fleetConfig, activeFleet);
updateScreen();
void refreshIdentity();
void tick();
}
Expand Down Expand Up @@ -395,6 +425,15 @@ if (configEl) {
});
}

// The Fleet detail header: only "← Fleets" is wired this slice — `+ Add
// instance` / `⚙` render disabled (slices 5/6 wire them).
if (fleetDetailEl) {
fleetDetailEl.addEventListener("click", (ev) => {
const target = ev.target as HTMLElement;
if (target.closest('[data-action="back-to-fleets"]')) deselectFleet();
});
}

// The remote panel: "Edit config" opens remote.toml in the editor; "Activate"
// dials the /acp endpoint; "Disconnect" tears it down. Status then updates via
// the `remote-status` event, with a refresh as a fallback.
Expand Down Expand Up @@ -754,6 +793,7 @@ async function boot(): Promise<void> {
// Compose tab: author the template/overlay/skills library + preview the
// composed bundle (agent-deployment ADR, slice 1). Self-contained; no polling.
initComposeTab();
updateScreen();
void refreshConfig();
void refreshIdentity();
void refreshRemote();
Expand Down
21 changes: 21 additions & 0 deletions console/src/render.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
rosterHtml,
identityHtml,
fleetConfigHtml,
fleetDetailHeaderHtml,
remoteHtml,
agentListHtml,
agentConsoleHeaderHtml,
Expand Down Expand Up @@ -284,6 +285,26 @@ describe("fleetConfigHtml", () => {
});
});

describe("fleetDetailHeaderHtml", () => {
it("renders the breadcrumb back to Fleets and the fleet name", () => {
const html = fleetDetailHeaderHtml("oab-prod-orca");
expect(html).toContain('data-action="back-to-fleets"');
expect(html).toContain("oab-prod-orca");
});

it("renders the deploy and debug-drawer entry points disabled (slice 2 scope)", () => {
const html = fleetDetailHeaderHtml("oab-prod-orca");
expect(html).toContain('data-action="add-instance" disabled');
expect(html).toContain('data-action="fleet-debug" disabled');
});

it("escapes the fleet name", () => {
const html = fleetDetailHeaderHtml("<x>");
expect(html).toContain("&lt;x&gt;");
expect(html).not.toContain("<x>");
});
});

describe("remoteHtml", () => {
it("shows the endpoint and an Activate button when configured + disconnected", () => {
const html = remoteHtml(FIXTURE_REMOTE_CONFIG);
Expand Down
20 changes: 20 additions & 0 deletions console/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,26 @@ export function renderFleetConfig(
el.innerHTML = fleetConfigHtml(cfg, activeFleet);
}

// ---- Fleet detail screen header (ADR #83 slice 2) -----------------------------
// The breadcrumb + action row shown above the roster once a fleet is selected —
// "← Fleets" returns to the Fleets screen (Part A's drill-down). `[+ Add
// instance]` and `[⚙]` are the slice 5/6 entry points (deploy, Debug drawer);
// stubbed disabled here per the ADR's slice-2 scope ("No wiring yet").
export function fleetDetailHeaderHtml(fleetName: string): string {
return `<div class="fd-head">
<button class="fd-back" type="button" data-action="back-to-fleets">&larr; Fleets</button>
<span class="fd-sep">/</span>
<span class="fd-name">${escapeHtml(fleetName)}</span>
<span class="fd-spacer"></span>
<button class="fd-btn" type="button" data-action="add-instance" disabled title="coming soon">+ Add instance</button>
<button class="fd-btn fd-gear" type="button" data-action="fleet-debug" disabled title="coming soon">⚙</button>
</div>`;
}

export function renderFleetDetailHeader(el: HTMLElement, fleetName: string): void {
el.innerHTML = fleetDetailHeaderHtml(fleetName);
}

// ---- Remote reverse-MCP connection panel (Part B) ---------------------------
// The "activate the remote connection" surface: the /acp endpoint, live status,
// an explicit Activate/Disconnect button, and Edit config. Since #69 the editor
Expand Down
52 changes: 52 additions & 0 deletions console/src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,58 @@ button.act:disabled {
border-color: var(--s-starting);
}

/* ---- Fleet detail screen (ADR #83 slice 2: 7.3) ---- */
.fleet-detail-wrap {
margin: 0 0 16px;
}
.fd-head {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border: 1px solid var(--border);
border-bottom: none;
border-radius: 6px 6px 0 0;
background: var(--panel);
}
.fd-back,
.fd-btn {
cursor: pointer;
border: 1px solid var(--border);
border-radius: 5px;
background: var(--bg);
color: var(--muted);
font: inherit;
font-size: 12px;
padding: 3px 10px;
}
.fd-back:hover {
color: var(--text);
border-color: var(--s-starting);
}
.fd-btn:disabled {
opacity: 0.5;
cursor: default;
}
.fd-gear {
padding: 3px 8px;
}
.fd-sep {
color: var(--muted);
}
.fd-name {
font-weight: 600;
font-size: 14px;
}
.fd-spacer {
flex: 1 1 auto;
}
.fleet-detail-wrap .roster-wrap {
border: 1px solid var(--border);
border-radius: 0 0 6px 6px;
background: var(--panel);
}

/* ---- fleets.toml editor (slice C) ---- */
.cfg-editor-wrap {
margin: 0 0 12px;
Expand Down
Loading