Skip to content

Commit 28dff6b

Browse files
committed
fix(ui): hide inactive FleetView roster
1 parent 3ed8632 commit 28dff6b

2 files changed

Lines changed: 42 additions & 18 deletions

File tree

src/ui/fleet-list.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,15 +337,14 @@ export class FleetList {
337337
// ---- Rendering ----
338338

339339
private renderBar(width: number, theme: Theme): string[] {
340+
if (!this.active || this.viewerClose || !this.editorHasFocus()) return [];
340341
const agents = this.roster().slice(1) as AgentEntry[];
341342
if (agents.length === 0) return [];
342343
// Clamp locally so a render between a roster shrink and the next update()
343344
// (e.g. on terminal resize) never loses the selection marker.
344345
const sel = Math.min(this.selectedIndex, agents.length);
345346

346-
const hint = this.active
347-
? "↑↓ select · enter view · esc back"
348-
: "esc to interrupt · ← for agents · ↓ to manage";
347+
const hint = "↑↓ select · enter view · esc back";
349348
const lines: string[] = [];
350349
lines.push(truncateToWidth(" " + theme.fg("dim", hint), width));
351350
lines.push("");

test/fleet-list.test.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,17 @@ describe("FleetList navigation", () => {
138138
expect(h.render()).toEqual([]);
139139
});
140140

141+
it("keeps the roster hidden until selection is active", () => {
142+
const h = harness([makeRecord()]);
143+
expect(h.render()).toEqual([]);
144+
145+
h.press(DOWN);
146+
expect(h.render().some(l => l.includes("enter view"))).toBe(true);
147+
148+
h.press(ESC);
149+
expect(h.render()).toEqual([]);
150+
});
151+
141152
it("activates on ↓ at an empty prompt, consuming the key", () => {
142153
const h = harness([makeRecord()]);
143154
const res = h.press(DOWN);
@@ -155,6 +166,7 @@ describe("FleetList navigation", () => {
155166
const h = harness([makeRecord()]);
156167
h.setEditorText("hello");
157168
expect(h.press(DOWN)).toBeUndefined();
169+
expect(h.render()).toEqual([]);
158170
});
159171

160172
it("ignores key-release events so one tap moves exactly one row", () => {
@@ -190,22 +202,21 @@ describe("FleetList navigation", () => {
190202
const h = harness([makeRecord()]);
191203
h.press(DOWN); // activate, index 0
192204
expect(h.press(UP)).toEqual({ consume: true });
193-
// back to inactive hint
194-
expect(h.render().some(l => l.includes("← for agents"))).toBe(true);
205+
expect(h.render()).toEqual([]);
195206
});
196207

197208
it("Esc deactivates", () => {
198209
const h = harness([makeRecord()]);
199210
h.press(DOWN);
200211
expect(h.press(ESC)).toEqual({ consume: true });
201-
expect(h.render().some(l => l.includes("← for agents"))).toBe(true);
212+
expect(h.render()).toEqual([]);
202213
});
203214

204215
it("passes non-nav keys through and cancels navigation", () => {
205216
const h = harness([makeRecord()]);
206217
h.press(DOWN);
207218
expect(h.press(RIGHT)).toBeUndefined();
208-
expect(h.render().some(l => l.includes("← for agents"))).toBe(true);
219+
expect(h.render()).toEqual([]);
209220
});
210221

211222
it("ignores all input while disabled and hides the widget", () => {
@@ -271,11 +282,11 @@ describe("FleetList vs other focused components (#123)", () => {
271282
focusInHarness(h, realEditor());
272283
expect(h.press(DOWN)).toEqual({ consume: true }); // activate at the prompt
273284
focusInHarness(h, { kind: "selector" }); // a dialog takes focus
285+
expect(h.render()).toEqual([]);
274286
expect(h.press(DOWN)).toBeUndefined();
275287
expect(h.press(ENTER)).toBeUndefined();
276288
expect(h.press(ESC)).toBeUndefined();
277-
// and the list dropped back to its inactive hint
278-
expect(h.render().some(l => l.includes("← for agents"))).toBe(true);
289+
expect(h.render()).toEqual([]);
279290
});
280291

281292
it("still activates when the prompt editor has focus", () => {
@@ -294,9 +305,10 @@ describe("FleetList vs other focused components (#123)", () => {
294305
describe("FleetList rendering", () => {
295306
it("renders main + agent rows with markers, type, description and right-aligned stats", () => {
296307
const h = harness([makeRecord({ description: "Sleep then report 1" })]);
308+
h.press(DOWN);
297309
const lines = h.render(120);
298310
// hint + blank + main + one agent
299-
expect(lines[0]).toContain("← for agents");
311+
expect(lines[0]).toContain("enter view");
300312
expect(lines.find(l => l.includes("main"))).toContain("●"); // main selected by default
301313
const agentLine = lines.find(l => l.includes("Sleep then report 1"))!;
302314
expect(agentLine).toContain("○");
@@ -310,7 +322,9 @@ describe("FleetList rendering", () => {
310322
makeRecord({ id: "new", description: "newest", startedAt: 2000 }),
311323
makeRecord({ id: "old", description: "oldest", startedAt: 1000 }),
312324
];
313-
const lines = harness(agents).render();
325+
const h = harness(agents);
326+
h.press(DOWN);
327+
const lines = h.render();
314328
const oldIdx = lines.findIndex(l => l.includes("oldest"));
315329
const newIdx = lines.findIndex(l => l.includes("newest"));
316330
expect(oldIdx).toBeGreaterThanOrEqual(0);
@@ -322,7 +336,9 @@ describe("FleetList rendering", () => {
322336
makeRecord({ id: "live", description: "running one" }),
323337
makeRecord({ id: "pending", description: "queued one", status: "queued", session: undefined }),
324338
];
325-
const lines = harness(agents).render();
339+
const h = harness(agents);
340+
h.press(DOWN);
341+
const lines = h.render();
326342
expect(lines.some(l => l.includes("running one"))).toBe(true);
327343
expect(lines.some(l => l.includes("queued one"))).toBe(false);
328344
});
@@ -331,6 +347,7 @@ describe("FleetList rendering", () => {
331347
const agents = Array.from({ length: 8 }, (_, i) =>
332348
makeRecord({ id: `a${i}`, description: `report ${i}` }));
333349
const h = harness(agents);
350+
h.press(DOWN);
334351
const lines = h.render(120);
335352
// 8 agents, cap 5 visible → "↓ 3 more"
336353
expect(lines.some(l => l.includes("↓ 3 more"))).toBe(true);
@@ -366,7 +383,7 @@ describe("FleetList overlay lifecycle", () => {
366383
h.press(DOWN); // active, index 0 (main)
367384
h.press(ENTER);
368385
expect(h.overlayOpened()).toBe(false); // never opened an overlay
369-
expect(h.render().some(l => l.includes("← for agents"))).toBe(true);
386+
expect(h.render()).toEqual([]);
370387
});
371388

372389
it("keeps the cursor on the viewed agent after closing, even if the list reordered", async () => {
@@ -405,7 +422,7 @@ describe("FleetList overlay lifecycle", () => {
405422
expect(h.manager.steer).toHaveBeenCalledWith("live", "go left");
406423
});
407424

408-
it("does NOT auto-close when the viewed agent finishes (final output stays readable)", () => {
425+
it("does NOT auto-close when the viewed agent finishes (final output stays readable)", async () => {
409426
const agents = [makeRecord({ id: "live", description: "the one" })];
410427
const h = harness(agents);
411428
h.press(DOWN); // active (main)
@@ -415,14 +432,22 @@ describe("FleetList overlay lifecycle", () => {
415432
// The agent finishes, well past the linger window...
416433
agents[0] = makeRecord({ id: "live", description: "the one", status: "completed", completedAt: Date.now() - 60_000 });
417434
h.fleet.onAgentFinished("live");
418-
expect(h.overlayClosed()).toBe(false); // viewer stays open
419-
expect(h.render().some(l => l.includes("the one"))).toBe(true); // and stays listed while viewed
435+
expect(h.overlayClosed()).toBe(false); // viewer stays open
436+
expect(h.render()).toEqual([]); // roster stays hidden while it cannot be selected
437+
438+
await h.closeOverlay();
439+
expect(h.render()).toEqual([]);
420440
});
421441

422442
it("lingers a finished agent in the list, then drops it after the window", () => {
423443
const recent = makeRecord({ id: "r", description: "recent done", status: "completed", completedAt: Date.now() });
424-
expect(harness([recent]).render().some(l => l.includes("recent done"))).toBe(true);
444+
const recentHarness = harness([recent]);
445+
recentHarness.press(DOWN);
446+
expect(recentHarness.render().some(l => l.includes("recent done"))).toBe(true);
447+
425448
const old = makeRecord({ id: "o", description: "old done", status: "completed", completedAt: Date.now() - 60_000 });
426-
expect(harness([old]).render().some(l => l.includes("old done"))).toBe(false);
449+
const oldHarness = harness([old]);
450+
oldHarness.press(DOWN);
451+
expect(oldHarness.render().some(l => l.includes("old done"))).toBe(false);
427452
});
428453
});

0 commit comments

Comments
 (0)