+
{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("");
+ });
+});