fix(tui): agent-detail runtime field writes the NAME as the value (a4c9548a)

S19 follow-on founder ruling (bounds the marks ruling): where there is room,
write the runtime NAME as the value; an icon may accompany decoratively but never
substitutes. The agent-detail runtime field rendered mark-only (the ROUND-3/4
mark-as-value behavior); it now renders the name (honest placeholder when
unserved) with the mark trailing decoratively. Topology cards stay mark-only by
space (layout.ts/hatchet.ts untouched) — bounded, not reversed.

RED-first: new pin agent-detail-runtime-name.test.ts (2 cases) RED at 5de3b994,
GREEN after the fix. Two shipped-floor assertions in topology-view.test.ts that
pinned the reversed behavior ("the mark IS the value" / "spelled runtime is
dead") updated truthfully citing a4c9548a; every mark-styling-survival assertion
preserved. Full tui suite 214/214, tsc 0, build clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
v-openrig-buildandClaude Opus 4.8 committed 2026-08-05 02:07:44 +00:00
1 parent 5de3b994fc
commit 5a70e2005f
3 files changed
+60 -15

No files matched your search

+10 -4
View File
@@ -215,14 +215,20 @@ function contentLines(state: ViewState, snap: FleetSnapshot, contentWidth: numbe
if (!found) return [{ text: `agent "${leaf.name}" not in the current snapshot` }];
const { agent, rig, pod } = found;
const specInLibrary = !!agent.spec && !!findSpec(snap, agent.spec);
// ROUND-3: the runtime MARK lives on the detail page (sparingly).
// ROUND-4 (guard finding 2): the mark's OWN token segments ride the seg
// channel so its colors/background survive stylization, and NO spelled
// runtime word renders beside it (spelled runtime is dead everywhere).
// a4c9548a — S19 FOLLOW-ON FOUNDER RULING (binding, bounds the S19 marks
// ruling): where there is ROOM, WRITE THE NAME — an icon NEVER replaces text
// as the value. The detail page has room, so the runtime NAME is the VALUE
// (honest placeholder when unserved); the mark's OWN token segments still ride
// the seg channel (ROUND-4: colors/background survive stylization) but only
// ACCOMPANY the name decoratively, never substitute. Topology cards remain
// mark-only by space (layout.ts/hatchet.ts) — the ruling is bounded, not reversed.
const rtName = agent.runtime ?? "— (not served)";
const rtSegs = [
{ text: " " },
{ text: "runtime:", token: "dim" as const },
{ text: " ".repeat(LABEL_W - "runtime:".length + 1) },
{ text: rtName },
{ text: " " },
...runtimeMarkSegs(agent.runtime),
];
const runtimeLine: ContentLine = { text: rtSegs.map((g) => g.text).join(""), segs: rtSegs };
@@ -0,0 +1,35 @@
// a4c9548a — S19 FOLLOW-ON FOUNDER RULING (binding, bounds the S19 marks ruling):
// icons are DECORATIVE or for no-room contexts; where there is ROOM, WRITE THE NAME —
// an icon NEVER replaces text as the value. The agent-DETAIL page has room, so its
// runtime field must render the runtime NAME as text (claude-code / codex / ...); a
// mark MAY accompany decoratively but never substitutes for the value. Topology cards
// (space-constrained) KEEP their marks — the ruling is BOUNDED, not reversed (see
// topology-view / navigator-reskin suites, untouched by this fix). (qitem a4c9548a)
import { describe, expect, it } from "vitest";
import { demoSnapshot } from "../src/demo-data.js";
import { renderScreen } from "../src/render.js";
import { createViewState } from "../src/state.js";
function renderAgentDetail(name: string): string {
const snap = demoSnapshot();
const view = createViewState({ instanceId: "t", getSnapshot: () => snap });
view.dispatch({
type: "drill",
resource: "agent",
name,
target: { host: "vm-host", rig: "openrig-build", pod: "dev50" },
});
return renderScreen(view.get(), snap, { cols: 140, rows: 34 }).lines.join("\n");
}
describe("agent-detail runtime field renders the NAME as the value (founder rule a4c9548a)", () => {
it("a claude-code agent's runtime field shows `claude-code` as TEXT — the name is the value, not only the mark", () => {
const out = renderAgentDetail("dev50.driver");
expect(out).toMatch(/runtime:\s+claude-code/);
});
it("a codex agent's runtime field shows `codex` as TEXT", () => {
const out = renderAgentDetail("dev50.guard");
expect(out).toMatch(/runtime:\s+codex/);
});
});
+15 -11
View File
@@ -548,8 +548,12 @@ describe("ROUND-3 LOCKED SET (orch locked-scope GO; pins 02259adb/29a10b62)", ()
// detail page shows the mark as the runtime field — spelled runtime is dead
s.dispatch({ type: "drill", resource: "agent", name: "dev.driver", target: { host: "vm-host", rig: FIXTURE_RIG_NAME, pod: "dev" } });
const detail = renderScreen(s.get(), snap, { cols: 150, rows: 40 }).lines.join("\n");
expect(detail).toMatch(/runtime:\s+></); // the mark IS the runtime value
expect(detail).not.toMatch(/>< claude-code/); // no spelled runtime beside the mark
// a4c9548a (S19 follow-on founder ruling, bounds the marks ruling): on the detail
// page there is room, so the runtime NAME is the VALUE; the mark only accompanies
// decoratively, never substitutes. (Supersedes the earlier "the mark IS the value /
// spelled runtime is dead" pin — topology cards above stay mark-only, bounded not reversed.)
expect(detail).toMatch(/runtime:\s+claude-code/); // the NAME is the runtime value
expect(detail).toMatch(/claude-code\s+></); // the clawd mark still accompanies decoratively
});
it("the clawd eyes are the picks-v4 INWARD SQUINTY pair `><` (founder amendment 14afeb74, supersedes the round-4 quadrant geometry)", async () => {
@@ -563,7 +567,7 @@ describe("ROUND-3 LOCKED SET (orch locked-scope GO; pins 02259adb/29a10b62)", ()
expect(markText(runtimeMarkSegs("claude-code"))).toBe("><"); // shipped claude mark = the refined face
});
it("agent-detail runtime marks keep their OWN styling in compiled output (guard round-4 finding 2)", () => {
it("agent-detail runtime NAME is the value AND the mark keeps its OWN styling in compiled output (a4c9548a + guard round-4 finding 2)", () => {
const node = (id: string, name: string, runtime: string) => ({
id, type: "rigNode", parentId: "pod-D",
data: { logicalId: name, podNamespace: "d", runtime, model: null, status: "running",
@@ -599,18 +603,18 @@ describe("ROUND-3 LOCKED SET (orch locked-scope GO; pins 02259adb/29a10b62)", ()
const cl = drillDetail("d.cl");
expect(cl.styled).toMatch(/38;2;24;24;24;48;2;173;103;85m[^\x1b]*>/);
expect(cl.styled).toMatch(/38;2;24;24;24;48;2;173;103;85m[^\x1b]*</); // both inward eyes carry the eye-on-terracotta SGR
expect(cl.plain).not.toMatch(/claude/); // spelled runtime is dead
expect(cl.plain).toMatch(/runtime:\s+claude-code/); // a4c9548a: the NAME is the value (the mark's SGR above proves it still accompanies decoratively)
// terminal: the dark-cell background survives to the compiled detail line
const tty = drillDetail("d.tty");
expect(tty.styled).toMatch(/48;2;12;10;9m?[^\x1b]*>/);
expect(tty.plain.slice(31)).not.toMatch(/terminal/);
// codex: ASCII >_ text with the picks-v4 CHEVRON-ONLY blue hint — the `>`
// carries the OFFICIAL sampled #6867aa (38;2;104;103;170) on detail; the
// `_` stays light ink; no ❯, no outline, no spelled runtime
expect(tty.plain.slice(31)).toMatch(/runtime:\s+terminal/); // a4c9548a: the NAME is the value; the >_ mark still trails (SGR above)
// codex: the NAME `codex` is the value (a4c9548a), with the picks-v4 CHEVRON-ONLY
// blue hint accompanying — the `>` carries the OFFICIAL sampled #6867aa
// (38;2;104;103;170) on detail; the `_` stays light ink; no ❯, no outline.
const cx = drillDetail("d.cx");
expect(cx.plain.slice(31).trimEnd()).toMatch(/runtime:\s+>_$/);
expect(cx.styled).toMatch(/38;2;104;103;170m[^\x1b]*>/); // chevron pick (picks v4 item a)
expect(cx.plain).not.toMatch(/❯|codex/);
expect(cx.plain.slice(31)).toMatch(/runtime:\s+codex/); // a4c9548a: the NAME is the value
expect(cx.styled).toMatch(/38;2;104;103;170m[^\x1b]*>/); // chevron pick (picks v4 item a) still accompanies
expect(cx.plain).not.toMatch(/❯/); // no ❯ outline (the codex name renders as the value, not an icon substitute)
});
it("rig glyph is ▦ and pod glyph is ≡ (founder picks of record)", () => {