fix(beehive): align harness inventory visuals

Signed-off-by: Logan Johnson <loganj@squareup.com>
This commit is contained in:
Logan Johnson
2026-09-21 11:54:14 -04:00
parent 7ffb9a6973
commit caa7eca39e
2 changed files with 24 additions and 14 deletions

View File

@@ -4,7 +4,7 @@ import { HarnessInventoryController, type HarnessInventorySnapshot } from './har
export const palette = {
surface: '#0d100e', text: '#dce1d8', muted: '#7d867c', divider: '#4a5149',
focus: '#ffd34e', selected: '#dce1d8', selectedText: '#10130f', dialog: '#9ca49b', scrim: '#070907',
focus: '#ffd34e', healthy: '#75d795', failure: '#ff8179', selected: '#dce1d8', selectedText: '#10130f', dialog: '#9ca49b', scrim: '#070907',
} as const;
const wideFooter = '←→ move Enter open Tab panes Esc return ? help q quit';
@@ -123,7 +123,7 @@ export class OpenTuiShell {
onMouseDown: () => this.activateSelectedRow() });
this.listPane.add(this.listTitle); this.detailPane.add(this.detailTitle); this.detailPane.add(this.detailText); this.detailPane.add(this.detailAction);
for (let slot = 0; slot < 32; slot++) {
const row = new TextRenderable(renderer, { position: 'absolute', left: 2, top: slot * 2 + 3, height: 2, fg: palette.text, content: '', visible: false,
const row = new TextRenderable(renderer, { position: 'absolute', left: 2, top: slot + 3, height: 1, fg: palette.text, content: '', visible: false,
onMouseDown: () => this.clickListSlot(slot) });
this.listPane.add(row); this.listRows.push(row);
}
@@ -241,8 +241,9 @@ export class OpenTuiShell {
this.listTitle.visible = shown; this.detailTitle.visible = shown; this.detailText.visible = shown; this.detailAction.visible = false;
for (const row of this.listRows) row.visible = false;
if (!shown) { this.detailText.content = ''; return; }
this.listTitle.content = 'LOCAL RUNTIMES';
const bodyHeight = Math.max(1, this.renderer.height - 5);
const capacity = Math.max(1, Math.min(this.listRows.length, Math.floor((bodyHeight - 4) / 2)));
const capacity = Math.max(1, Math.min(this.listRows.length, bodyHeight - 4));
this.state.setListCapacity(capacity);
for (let slot = 0; slot < capacity; slot++) {
const descriptor = this.state.harnessRows[this.state.listOffset + slot];
@@ -250,11 +251,15 @@ export class OpenTuiShell {
if (!descriptor) continue;
const selected = descriptor.id === this.state.harnessSelection;
const harness = descriptor.kind === 'harness' ? this.inventorySnapshot.harnesses.find(value => `harness:${value.id}` === descriptor.id) : undefined;
const status = harness ? harness.state === 'available' ? 'Available' : harness.state === 'not-installed' ? 'Not installed' : harness.state === 'cli-missing' ? 'CLI missing' : 'Incompatible' : this.inventorySnapshot.phase === 'refreshing' ? 'Refreshing…' : 'Refresh harnesses';
renderable.content = descriptor.kind === 'command' ? `↻ ${status}\n Re-read and save local facts` : `${harness?.label ?? descriptor.id} · ${status}\n ${harness?.reason ?? 'Reason unavailable'}`;
renderable.width = Math.max(1, (this.state.splitPane ? listWidth(this.renderer.width) : this.renderer.width) - 4);
const status = harness ? harness.state === 'available' ? '● READY' : harness.state === 'not-installed' ? 'UNAVAILABLE' : harness.state === 'cli-missing' ? 'CLI MISSING' : 'INCOMPATIBLE' : this.inventorySnapshot.phase === 'refreshing' ? 'REFRESHING…' : '';
const icon = descriptor.kind === 'command' ? '↻' : harness?.state === 'available' ? '◆' : '◇';
const label = descriptor.kind === 'command' ? 'Refresh harnesses' : harness?.label ?? descriptor.id;
const width = Math.max(1, (this.state.splitPane ? listWidth(this.renderer.width) : this.renderer.width) - 4);
const gap = Math.max(1, width - icon.length - label.length - status.length - 2);
renderable.content = `${icon} ${label}${' '.repeat(gap)}${status}`;
renderable.width = width;
renderable.bg = selected ? palette.selected : palette.surface;
renderable.fg = selected ? palette.selectedText : palette.text;
renderable.fg = selected ? palette.selectedText : harness?.state === 'available' ? palette.healthy : palette.text;
renderable.visible = true;
}
const selected = this.state.selectedHarnessRow;
@@ -262,10 +267,15 @@ export class OpenTuiShell {
const harness = this.inventorySnapshot.harnesses.find(value => `harness:${value.id}` === selected.id);
if (harness) {
const saved = this.inventorySnapshot.savedConfigurations[harness.id] ?? 0;
this.detailText.content = `${harness.label}\n\nState: ${harness.state}\nReason: ${harness.reason}\nVersion: ${harness.version ?? 'Not established'}\nExecutable: ${harness.executable ? 'Detected' : 'Not established'}\nUnderlying CLI: ${harness.cli ? 'Detected' : 'Not established'}\nProvider integrations: ${harness.providers.join(', ') || 'None'}\nSaved configurations: ${saved}\n\nThis inventory is local to this computer.`;
} else this.detailText.content = 'This harness is no longer in the current inventory.';
const state = harness.state === 'available' ? '● READY' : harness.state === 'not-installed' ? 'UNAVAILABLE' : harness.state === 'cli-missing' ? 'CLI MISSING' : 'INCOMPATIBLE';
const location = harness.executable ?? 'Not found';
this.detailTitle.content = harness.label.toUpperCase();
this.detailText.content = `State ${state}\nVersion ${harness.version ?? 'Version unavailable'}\nLocation ${location}${harness.state === 'available' ? '' : `\n\nReason ${harness.reason}\n\nInstall or repair this runtime, then refresh harnesses.`}${saved ? `\n\nSaved configurations ${saved}` : ''}`;
} else { this.detailTitle.content = 'RUNTIME UNAVAILABLE'; this.detailText.content = 'This harness is no longer in the current inventory.'; }
} else {
this.detailText.content = `Refresh harnesses\n\nRe-read bounded local executable and version evidence, then save the complete harness inventory.\n\nStatus: ${this.inventorySnapshot.message}\n\nNo owner sign-in, relay, Host, agent, provider, browser, or credential operation is used.`;
this.detailTitle.content = 'REFRESH HARNESSES';
this.detailText.content = `Check this Host again for supported harnesses and installed versions.\n\n${this.inventorySnapshot.message}\n\n────────────────────────────────────────────────────────\nAVAILABLE ACTIONS`;
this.detailAction.top = 10;
this.detailAction.content = this.inventorySnapshot.phase === 'refreshing' ? ' Refresh in progress ' : ' Refresh harnesses ';
this.detailAction.visible = this.state.focus === 'detail' && this.visible('detail');
}

View File

@@ -36,8 +36,8 @@ test('memory renderer draws the exact wide empty shell and focus perimeter', asy
assert.equal(shell.state.headerIndex, 1); assert.equal(shell.state.activeSection, 0);
ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce(); frame = ui.captureCharFrame();
assert.equal(shell.state.activeSection, 2); assert.equal(shell.state.focus, 'list'); assert.ok(frame.includes('│'));
assert.ok(frame.includes('Fixture Harness · Available'));
assert.ok(frame.includes('State: available'));
assert.ok(frame.includes('Fixture Harness')); assert.ok(frame.includes('● READY'));
assert.ok(frame.includes('State ● READY'));
const spans = JSON.stringify(ui.captureSpans());
assert.ok(spans.includes('"0":255,"1":211,"2":78'), `focus color missing from ${spans.slice(0, 300)}`);
} finally { shell.close(); }
@@ -117,7 +117,7 @@ test('memory renderer binds refresh to its command row and applies real async re
complete([{ id: 'codex', label: 'Codex', state: 'incompatible', providers: [], reason: 'Codex ACP 1.10.0 or newer required; version unknown/outdated' }]);
await new Promise(resolve => setTimeout(resolve, 0)); await ui.renderOnce();
const frame = ui.captureCharFrame();
assert.ok(frame.includes('Codex · Incompatible')); assert.ok(frame.includes('Refresh harnesses'));
assert.ok(frame.includes('Codex')); assert.ok(frame.includes('INCOMPATIBLE')); assert.ok(frame.includes('Refresh harnesses'));
assert.equal(shell.state.harnessSelection, 'command:refresh', 'surviving command identity and its completed operation Details remain selected');
} finally { shell.close(); }
});
@@ -140,7 +140,7 @@ test('renderer shows refresh failure, permits recovery, and fences completion af
assert.ok(ui.captureCharFrame().includes('Harness refresh failed'));
assert.doesNotMatch(ui.captureCharFrame(), /private\/diagnostic/);
ui.mockInput.pressEnter(); await new Promise(resolve => setTimeout(resolve, 0)); await ui.renderOnce();
assert.ok(ui.captureCharFrame().includes('Buzz Agent · Available'));
assert.ok(ui.captureCharFrame().includes('Buzz Agent')); assert.ok(ui.captureCharFrame().includes('● READY'));
ui.mockInput.pressArrow('down'); ui.mockInput.pressEnter(); ui.mockInput.pressEnter(); await ui.renderOnce();
ui.mockInput.pressArrow('up'); ui.mockInput.pressArrow('up'); ui.mockInput.pressArrow('right'); ui.mockInput.pressEnter(); await ui.renderOnce();
assert.equal(shell.state.activeSection, 3);