fix(runner): align devtools tab icons

This commit is contained in:
Dotta committed 2026-08-20 12:17:33 -05:00
1 parent 04b1aa2ac9
commit a6b2ad79b7
4 files changed
+42 -16

No files matched your search

@@ -722,6 +722,15 @@ test.describe("Capability clean-room chat", () => {
const tabs = panel.getByRole("tab");
await expect(tabs.first()).toHaveText(/Evidence/);
await expect(tabs.nth(1)).toHaveText(/Timeline/);
await expect(panel.locator(".pit-devtools-tabs .pit-icon")).toHaveCount(8);
const centerOffsets = await tabs.evaluateAll((elements) => elements.map((element) => {
const icon = element.querySelector(".pit-tab-glyph")?.getBoundingClientRect();
const label = element.querySelector(".pit-tab-glyph + span")?.getBoundingClientRect();
return icon === undefined || label === undefined
? Number.POSITIVE_INFINITY
: Math.abs((icon.top + icon.height / 2) - (label.top + label.height / 2));
}));
expect(Math.max(...centerOffsets)).toBeLessThanOrEqual(1);
await expect(tabs.first()).toHaveAttribute("aria-selected", "true");
await expect(panel.locator("#evidence-turn-selector")).toBeVisible();
await panel.getByRole("tab", { name: /Timeline/ }).click();
@@ -1,7 +1,7 @@
import { useEffect, useMemo, useState } from "react";
import type { CapabilityDevtoolsSnapshot } from "../../../src/devtools";
import { Icon } from "./Icons";
import { Icon, type IconName } from "./Icons";
export type CapabilityDevtoolsTab = "evidence" | "timeline" | "state" | "diff" | "documents" | "protocol" | "runtime" | "authority";
type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
@@ -55,15 +55,15 @@ function download(snapshot: CapabilityDevtoolsSnapshot) {
URL.revokeObjectURL(url);
}
const TABS: ReadonlyArray<{ id: CapabilityDevtoolsTab; glyph: string; label: string }> = [
{ id: "evidence", glyph: "◎", label: "Evidence" },
{ id: "timeline", glyph: "◫", label: "Timeline" },
{ id: "state", glyph: "{}", label: "State" },
{ id: "diff", glyph: "±", label: "Diff" },
{ id: "documents", glyph: "▤", label: "Documents" },
{ id: "protocol", glyph: "⇄", label: "Protocol" },
{ id: "runtime", glyph: ">_", label: "Runtime" },
{ id: "authority", glyph: "◇", label: "Authority" },
const TABS: ReadonlyArray<{ id: CapabilityDevtoolsTab; icon: IconName; label: string }> = [
{ id: "evidence", icon: "evidence", label: "Evidence" },
{ id: "timeline", icon: "timeline", label: "Timeline" },
{ id: "state", icon: "state", label: "State" },
{ id: "diff", icon: "diff", label: "Diff" },
{ id: "documents", icon: "documents", label: "Documents" },
{ id: "protocol", icon: "protocol", label: "Protocol" },
{ id: "runtime", icon: "terminal", label: "Runtime" },
{ id: "authority", icon: "authority", label: "Authority" },
];
function documentsOf(state: Json): Array<{ id: string; key: string; title: string; revisions: Array<{ id: string; revision: number; body: string; changeSummary: string | null; createdAt: string }> }> {
@@ -124,8 +124,8 @@ export function DevtoolsInspector({ snapshot, onFork, tab, onTabChange }: { snap
<button className="pit-button" type="button" onClick={() => onFork(revision)}><Icon name="branch" /> Fork r{revision}</button>
</div>
<div className="pit-devtools-tabs" role="tablist" aria-label="Developer tools">
{TABS.map(({ id, glyph, label }) => (
<button key={id} type="button" role="tab" aria-selected={tab === id} className="pit-tab" onClick={() => onTabChange(id)}><span className="pit-tab-glyph" aria-hidden="true">{glyph}</span>{label}</button>
{TABS.map(({ id, icon, label }) => (
<button key={id} type="button" role="tab" aria-selected={tab === id} className="pit-tab" onClick={() => onTabChange(id)}><span className="pit-tab-glyph"><Icon name={icon} /></span><span>{label}</span></button>
))}
</div>
{tab === "timeline" ? (
@@ -1,19 +1,26 @@
export type IconName =
| "activity" | "branch" | "close" | "download" | "evidence" | "pause"
| "play" | "reset" | "spark" | "stop" | "terminal";
| "activity" | "authority" | "branch" | "close" | "diff" | "documents"
| "download" | "evidence" | "pause" | "play" | "protocol" | "reset"
| "spark" | "state" | "stop" | "terminal" | "timeline";
const PATHS: Record<IconName, string> = {
activity: "M3 12h3l2-6 4 12 3-9 2 5h4",
authority: "M12 3l7 4v5c0 4.6-2.8 7.6-7 9-4.2-1.4-7-4.4-7-9V7zM9 12l2 2 4-4",
branch: "M6 3v12a3 3 0 0 0 3 3h6M15 18l-3-3m3 3-3 3M6 7h6a3 3 0 0 0 3-3V3",
close: "M5 5l14 14M19 5 5 19",
diff: "M8 5v14M4 9h8M17 7v10M14 12h6",
documents: "M6 3h9l3 3v15H6zM15 3v4h4M9 11h6M9 15h6",
download: "M12 3v12m-5-5 5 5 5-5M5 21h14",
evidence: "M4 5h16v14H4zM8 9h8M8 13h5",
pause: "M8 5v14M16 5v14",
play: "M8 5v14l11-7z",
protocol: "M4 8h14m-3-3 3 3-3 3M20 16H6m3-3-3 3 3 3",
reset: "M5 8a8 8 0 1 1-1 7M5 8V3m0 5h5",
spark: "M12 2l1.7 5.3L19 9l-5.3 1.7L12 16l-1.7-5.3L5 9l5.3-1.7zM19 16l.8 2.2L22 19l-2.2.8L19 22l-.8-2.2L16 19l2.2-.8z",
state: "M9 4H6a2 2 0 0 0-2 2v3M15 4h3a2 2 0 0 1 2 2v3M9 20H6a2 2 0 0 1-2-2v-3M15 20h3a2 2 0 0 0 2-2v-3M9 9h6v6H9z",
stop: "M7 7h10v10H7z",
terminal: "M5 7l4 5-4 5m7 0h7",
timeline: "M5 5v14M5 8h5M5 13h9M5 18h13",
};
export function Icon({ name }: { name: IconName }) {
@@ -1824,8 +1824,13 @@ textarea {
}
.pit-tab {
display: inline-flex;
align-items: center;
justify-content: center;
flex: 0 0 auto;
min-height: var(--pit-touch-target);
gap: var(--pit-space-1);
line-height: 1;
gap: var(--pit-space-2);
border: 0;
border-bottom: var(--pit-space-1) solid transparent;
border-radius: 0;
@@ -1840,10 +1845,15 @@ textarea {
border-bottom-color: var(--pit-accent);
background: var(--pit-accent-surface);
color: var(--pit-foreground);
font-weight: 600;
}
.pit-tab-glyph {
font-family: var(--pit-font-mono);
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--pit-space-4);
height: var(--pit-space-4);
color: var(--pit-live);
}