diff --git a/apps/studio/src/App.tsx b/apps/studio/src/App.tsx index e5271375..f92cca3e 100644 --- a/apps/studio/src/App.tsx +++ b/apps/studio/src/App.tsx @@ -70,6 +70,7 @@ import { type StandingRouteState, type StandingRouteUsage, } from "@/data/standing-routes"; +import { executionProfileRowIdentity } from "@/lib/execution-profile-row-identity"; import { cn } from "@/lib/utils"; import { parseWorkspaceRoute, @@ -5790,6 +5791,7 @@ function AgentOperationsView() {
{runtime?.kind ?? "runtime"} · {model?.model ?? "model"} + {executionProfileRowIdentity(profile)} {capability?.serviceCapability ?? "UNVERIFIED"} · {capability?.diagnostic ?? "not checked"}
@@ -6889,7 +6891,7 @@ function RealCandidatePreflightView() { {currentStages.map((stage, index) => (
- + {stage.status === "PASS" ? index + 1 : }
diff --git a/apps/studio/src/index.css b/apps/studio/src/index.css index 21281c5f..837e171f 100644 --- a/apps/studio/src/index.css +++ b/apps/studio/src/index.css @@ -5506,22 +5506,24 @@ footer span:first-child { .preflight-disposition-facts span, .preflight-disposition-facts strong { display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } .preflight-disposition-facts span { color: #685c51; font-size: 12px; text-transform: uppercase; + overflow: visible; + white-space: normal; } .preflight-disposition-facts strong { margin-top: 5px; + overflow: hidden; color: #c28f62; font-family: inherit; font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; } .preflight-disposition > code { @@ -6006,7 +6008,7 @@ footer span:first-child { padding-bottom: 0; } -.preflight-stage > span { +.preflight-stage-index { display: grid; width: 22px; height: 22px; @@ -6018,7 +6020,7 @@ footer span:first-child { font-size: 12px; } -.preflight-stage > span.is-blocked { +.preflight-stage-index.is-blocked { border-color: rgba(213, 161, 110, 0.18); color: #d5a16e; } diff --git a/apps/studio/src/lib/execution-profile-row-identity.test.ts b/apps/studio/src/lib/execution-profile-row-identity.test.ts new file mode 100644 index 00000000..4b7dba18 --- /dev/null +++ b/apps/studio/src/lib/execution-profile-row-identity.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; + +import { executionProfileRowIdentity } from "./execution-profile-row-identity.js"; + +const terraCodex = { + executionProfileId: `sha256:${"a".repeat(64)}`, + profileKey: "rule-evidence-codex-app-server", + revision: 1006, +} as const; + +const terraOntology = { + executionProfileId: `sha256:${"b".repeat(64)}`, + profileKey: "ontology-codex-app-server", + revision: 1002, +} as const; + +describe("execution profile row identity", () => { + it("shows the picker rN plus profileKey and a short id", () => { + expect(executionProfileRowIdentity(terraCodex)).toBe( + "r1006 · rule-evidence-codex-app-server · aaaaaaaaaa", + ); + }); + + it("distinguishes same-runtime same-model rows the picker would otherwise collapse", () => { + const left = executionProfileRowIdentity(terraCodex); + const right = executionProfileRowIdentity(terraOntology); + expect(left).not.toBe(right); + expect(left).toContain("r1006"); + expect(right).toContain("r1002"); + expect(left).toContain("rule-evidence-codex-app-server"); + expect(right).toContain("ontology-codex-app-server"); + expect(left).toContain("aaaaaaaaaa"); + expect(right).toContain("bbbbbbbbbb"); + }); + + it("keeps later revisions of the same profileKey distinct", () => { + expect(executionProfileRowIdentity({ + ...terraCodex, + executionProfileId: `sha256:${"c".repeat(64)}`, + revision: 2006, + })).toBe("r2006 · rule-evidence-codex-app-server · cccccccccc"); + }); + + it("does not turn the capability list into a run or campaign control", () => { + const label = executionProfileRowIdentity(terraCodex).toLowerCase(); + expect(label).not.toContain("preflight"); + expect(label).not.toContain("run"); + expect(label).not.toContain("campaign"); + expect(label).not.toContain("spend"); + }); +}); diff --git a/apps/studio/src/lib/execution-profile-row-identity.ts b/apps/studio/src/lib/execution-profile-row-identity.ts new file mode 100644 index 00000000..fddc46df --- /dev/null +++ b/apps/studio/src/lib/execution-profile-row-identity.ts @@ -0,0 +1,20 @@ +const HASH_PREFIX = "sha256:"; +const SHORT_ID_LENGTH = 10; + +export type ExecutionProfileRowIdentityInput = Readonly<{ + executionProfileId: string; + profileKey: string; + revision: number; +}>; + +function shortExecutionProfileId(executionProfileId: string): string { + return executionProfileId.startsWith(HASH_PREFIX) + ? executionProfileId.slice(HASH_PREFIX.length, HASH_PREFIX.length + SHORT_ID_LENGTH) + : executionProfileId.slice(0, SHORT_ID_LENGTH); +} + +export function executionProfileRowIdentity( + profile: ExecutionProfileRowIdentityInput, +): string { + return `r${profile.revision} · ${profile.profileKey} · ${shortExecutionProfileId(profile.executionProfileId)}`; +} diff --git a/apps/studio/src/lib/preflight-disposition-facts.test.ts b/apps/studio/src/lib/preflight-disposition-facts.test.ts new file mode 100644 index 00000000..50b60f41 --- /dev/null +++ b/apps/studio/src/lib/preflight-disposition-facts.test.ts @@ -0,0 +1,24 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const studioRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); + +describe("preflight disposition fact labels", () => { + it("does not ellipsize POST-FEE UPPER BOUND", () => { + const css = readFileSync(join(studioRoot, "index.css"), "utf8"); + const start = css.indexOf(".preflight-disposition-facts span {"); + expect(start).toBeGreaterThan(-1); + const block = css.slice(start, css.indexOf("}", start) + 1); + expect(block).toContain("white-space: normal"); + expect(block).not.toContain("ellipsis"); + expect(block).not.toContain("nowrap"); + }); + + it("keeps the Post-fee upper bound copy on the current-snapshot row", () => { + const app = readFileSync(join(studioRoot, "App.tsx"), "utf8"); + expect(app).toContain("Post-fee upper bound"); + expect(app).toContain("className=\"preflight-disposition-facts\""); + }); +}); diff --git a/apps/studio/src/lib/preflight-stage-index.test.ts b/apps/studio/src/lib/preflight-stage-index.test.ts new file mode 100644 index 00000000..94847a64 --- /dev/null +++ b/apps/studio/src/lib/preflight-stage-index.test.ts @@ -0,0 +1,21 @@ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const studioRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); + +describe("preflight stage index circle", () => { + it("scopes the 22px circle to the step index, not every child span", () => { + const css = readFileSync(join(studioRoot, "index.css"), "utf8"); + expect(css).toContain(".preflight-stage-index {"); + expect(css).toContain("width: 22px;"); + expect(css).not.toMatch(/\.preflight-stage\s*>\s*span\s*\{/); + }); + + it("marks the step index with preflight-stage-index so Badge stays a pill", () => { + const app = readFileSync(join(studioRoot, "App.tsx"), "utf8"); + expect(app).toContain('className={stage.status === "PASS" ? "preflight-stage-index" : "preflight-stage-index is-blocked"}'); + expect(app).toContain(""); + }); +});