+
+ {children[0]}
+
+
{
+ const step = 2;
+ if (
+ (isHorizontal && e.key === "ArrowLeft") ||
+ (!isHorizontal && e.key === "ArrowUp")
+ ) {
+ e.preventDefault();
+ setSplit((s) => clamp(s - step, 5, 95));
+ } else if (
+ (isHorizontal && e.key === "ArrowRight") ||
+ (!isHorizontal && e.key === "ArrowDown")
+ ) {
+ e.preventDefault();
+ setSplit((s) => clamp(s + step, 5, 95));
+ }
+ }}
+ className="hover:bg-[var(--color-border-strong)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-[var(--color-focus-ring)]"
+ data-testid="split-pane-handle"
+ />
+
+ {children[1]}
+
+
+ );
+}
diff --git a/workbench-ui/src/design/components/TabBar/TabBar.test.tsx b/workbench-ui/src/design/components/TabBar/TabBar.test.tsx
new file mode 100644
index 00000000..2c5fed3b
--- /dev/null
+++ b/workbench-ui/src/design/components/TabBar/TabBar.test.tsx
@@ -0,0 +1,99 @@
+import { render, screen, fireEvent } from "@testing-library/react";
+import { useState } from "react";
+import { TabBar, type Tab } from "./TabBar";
+
+const TABS: Tab[] = [
+ { id: "surfaces", label: "Surfaces" },
+ { id: "grounding", label: "Grounding" },
+ { id: "verdicts", label: "Verdicts" },
+];
+
+function TestTabBar({ initialTab = "surfaces" }: { initialTab?: string }) {
+ const [active, setActive] = useState(initialTab);
+ return (
+
+ Content for {active}
+
+ );
+}
+
+describe("TabBar", () => {
+ it("renders all tabs", () => {
+ render(
);
+ expect(screen.getByRole("tab", { name: "Surfaces" })).toBeInTheDocument();
+ expect(screen.getByRole("tab", { name: "Grounding" })).toBeInTheDocument();
+ expect(screen.getByRole("tab", { name: "Verdicts" })).toBeInTheDocument();
+ });
+
+ it("marks the active tab with aria-selected", () => {
+ render(
);
+ expect(screen.getByRole("tab", { name: "Surfaces" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("tab", { name: "Grounding" })).toHaveAttribute("aria-selected", "false");
+ });
+
+ it("renders a tabpanel with correct aria-labelledby", () => {
+ render(
);
+ const panel = screen.getByRole("tabpanel");
+ expect(panel).toHaveAttribute("aria-labelledby", "tab-surfaces");
+ });
+
+ it("changes tab on click", () => {
+ render(
);
+ fireEvent.click(screen.getByRole("tab", { name: "Grounding" }));
+ expect(screen.getByRole("tab", { name: "Grounding" })).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByTestId("panel-grounding")).toBeInTheDocument();
+ });
+
+ it("navigates with ArrowRight", () => {
+ render(
);
+ const first = screen.getByRole("tab", { name: "Surfaces" });
+ fireEvent.keyDown(first, { key: "ArrowRight" });
+ expect(screen.getByRole("tab", { name: "Grounding" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("wraps around with ArrowRight from last", () => {
+ render(
);
+ const last = screen.getByRole("tab", { name: "Verdicts" });
+ fireEvent.keyDown(last, { key: "ArrowRight" });
+ expect(screen.getByRole("tab", { name: "Surfaces" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("navigates with ArrowLeft", () => {
+ render(
);
+ const tab = screen.getByRole("tab", { name: "Grounding" });
+ fireEvent.keyDown(tab, { key: "ArrowLeft" });
+ expect(screen.getByRole("tab", { name: "Surfaces" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("wraps around with ArrowLeft from first", () => {
+ render(
);
+ const first = screen.getByRole("tab", { name: "Surfaces" });
+ fireEvent.keyDown(first, { key: "ArrowLeft" });
+ expect(screen.getByRole("tab", { name: "Verdicts" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("Home jumps to first tab", () => {
+ render(
);
+ const tab = screen.getByRole("tab", { name: "Verdicts" });
+ fireEvent.keyDown(tab, { key: "Home" });
+ expect(screen.getByRole("tab", { name: "Surfaces" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("End jumps to last tab", () => {
+ render(
);
+ const first = screen.getByRole("tab", { name: "Surfaces" });
+ fireEvent.keyDown(first, { key: "End" });
+ expect(screen.getByRole("tab", { name: "Verdicts" })).toHaveAttribute("aria-selected", "true");
+ });
+
+ it("inactive tabs have tabIndex -1", () => {
+ render(
);
+ expect(screen.getByRole("tab", { name: "Grounding" })).toHaveAttribute("tabIndex", "-1");
+ expect(screen.getByRole("tab", { name: "Verdicts" })).toHaveAttribute("tabIndex", "-1");
+ });
+
+ it("has a tablist role container", () => {
+ render(
);
+ expect(screen.getByRole("tablist")).toBeInTheDocument();
+ });
+});
diff --git a/workbench-ui/src/design/components/TabBar/TabBar.tsx b/workbench-ui/src/design/components/TabBar/TabBar.tsx
new file mode 100644
index 00000000..bd14beaf
--- /dev/null
+++ b/workbench-ui/src/design/components/TabBar/TabBar.tsx
@@ -0,0 +1,115 @@
+import { useRef, useCallback, type ReactNode, type KeyboardEvent } from "react";
+
+export interface Tab {
+ id: string;
+ label: string;
+}
+
+export interface TabBarProps {
+ tabs: readonly Tab[];
+ activeTab: string;
+ onTabChange: (id: string) => void;
+ children: ReactNode;
+}
+
+export function TabBar({ tabs, activeTab, onTabChange, children }: TabBarProps) {
+ const tablistRef = useRef
(null);
+
+ const focusTab = useCallback(
+ (index: number) => {
+ const tablist = tablistRef.current;
+ if (!tablist) return;
+ const buttons = tablist.querySelectorAll('[role="tab"]');
+ buttons[index]?.focus();
+ onTabChange(tabs[index].id);
+ },
+ [onTabChange, tabs],
+ );
+
+ const onKeyDown = useCallback(
+ (e: KeyboardEvent) => {
+ const currentIndex = tabs.findIndex((t) => t.id === activeTab);
+ if (currentIndex === -1) return;
+
+ switch (e.key) {
+ case "ArrowRight": {
+ e.preventDefault();
+ focusTab((currentIndex + 1) % tabs.length);
+ break;
+ }
+ case "ArrowLeft": {
+ e.preventDefault();
+ focusTab((currentIndex - 1 + tabs.length) % tabs.length);
+ break;
+ }
+ case "Home": {
+ e.preventDefault();
+ focusTab(0);
+ break;
+ }
+ case "End": {
+ e.preventDefault();
+ focusTab(tabs.length - 1);
+ break;
+ }
+ }
+ },
+ [tabs, activeTab, focusTab],
+ );
+
+ return (
+
+
+ {tabs.map((tab) => {
+ const isActive = tab.id === activeTab;
+ return (
+
+ );
+ })}
+
+
+ {children}
+
+
+ );
+}
diff --git a/workbench-ui/src/design/components/Timestamp/Timestamp.test.tsx b/workbench-ui/src/design/components/Timestamp/Timestamp.test.tsx
new file mode 100644
index 00000000..5f916ef2
--- /dev/null
+++ b/workbench-ui/src/design/components/Timestamp/Timestamp.test.tsx
@@ -0,0 +1,55 @@
+import { render, screen } from "@testing-library/react";
+import { Timestamp } from "./Timestamp";
+
+const ISO = "2026-06-12T10:30:00Z";
+
+describe("Timestamp", () => {
+ it("renders a
+