From 9a86b8519b60a65155a4a7e76065c00feac620ab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:15:31 -0500
Subject: [PATCH 01/92] feat(ui): add connection status component
---
.../ui/src/components/connection-status.tsx | 176 ++++++++++++++++++
1 file changed, 176 insertions(+)
create mode 100644 packages/ui/src/components/connection-status.tsx
diff --git a/packages/ui/src/components/connection-status.tsx b/packages/ui/src/components/connection-status.tsx
new file mode 100644
index 0000000..b11fb04
--- /dev/null
+++ b/packages/ui/src/components/connection-status.tsx
@@ -0,0 +1,176 @@
+import {
+ CheckCircle2,
+ CircleDashed,
+ CircleOff,
+ ShieldCheck,
+ ShieldQuestion,
+ TriangleAlert,
+} from "lucide-react";
+import type { ReactNode } from "react";
+
+import { cn } from "@opencoven/ui/lib/utils";
+
+type ConnectionState =
+ | "connected"
+ | "pending"
+ | "degraded"
+ | "disconnected"
+ | "unavailable";
+
+type AuthorityLevel =
+ | "read-only"
+ | "proposal"
+ | "mutating"
+ | "local-authority";
+
+type ConnectionStatusProps = {
+ name: string;
+ kind: "SDK" | "CLI" | "Daemon" | "Runtime" | "Project";
+ state: ConnectionState;
+ authority: AuthorityLevel;
+ detail?: string;
+ version?: string;
+ meta?: string;
+ action?: ReactNode;
+ className?: string;
+};
+
+const stateDetails: Record<
+ ConnectionState,
+ { label: string; icon: typeof CheckCircle2; className: string }
+> = {
+ connected: {
+ label: "Connected",
+ icon: CheckCircle2,
+ className: "text-success",
+ },
+ pending: {
+ label: "Connecting",
+ icon: CircleDashed,
+ className: "text-information",
+ },
+ degraded: {
+ label: "Degraded",
+ icon: TriangleAlert,
+ className: "text-warning",
+ },
+ disconnected: {
+ label: "Disconnected",
+ icon: CircleOff,
+ className: "text-muted-foreground",
+ },
+ unavailable: {
+ label: "Unavailable",
+ icon: TriangleAlert,
+ className: "text-destructive",
+ },
+};
+
+const authorityDetails: Record<
+ AuthorityLevel,
+ { label: string; icon: typeof ShieldCheck; className: string }
+> = {
+ "read-only": {
+ label: "Read only",
+ icon: ShieldCheck,
+ className: "text-information",
+ },
+ proposal: {
+ label: "Proposal only",
+ icon: ShieldQuestion,
+ className: "text-warning",
+ },
+ mutating: {
+ label: "Mutation capable",
+ icon: ShieldQuestion,
+ className: "text-destructive",
+ },
+ "local-authority": {
+ label: "Local authority",
+ icon: ShieldCheck,
+ className: "text-success",
+ },
+};
+
+function ConnectionStatus({
+ name,
+ kind,
+ state,
+ authority,
+ detail,
+ version,
+ meta,
+ action,
+ className,
+}: ConnectionStatusProps) {
+ const stateDetail = stateDetails[state];
+ const authorityDetail = authorityDetails[authority];
+ const StateIcon = stateDetail.icon;
+ const AuthorityIcon = authorityDetail.icon;
+
+ return (
+
+
+
+
+ {kind}
+
+
+ {name}
+
+
+ {version ? (
+
+ {version}
+
+ ) : null}
+
+
+ {detail ? (
+ {detail}
+ ) : null}
+
+
+
+
+ {stateDetail.label}
+
+
+
+ {authorityDetail.label}
+
+ {meta ? (
+
+ {meta}
+
+ ) : null}
+ {action ?
{action} : null}
+
+
+ );
+}
+
+export {
+ ConnectionStatus,
+ type AuthorityLevel,
+ type ConnectionState,
+ type ConnectionStatusProps,
+};
From cb17221f5cbfcae79f4c787a40afb6b696b31124 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:15:42 -0500
Subject: [PATCH 02/92] feat(ui): add command receipt component
---
.../ui/src/components/command-receipt.tsx | 126 ++++++++++++++++++
1 file changed, 126 insertions(+)
create mode 100644 packages/ui/src/components/command-receipt.tsx
diff --git a/packages/ui/src/components/command-receipt.tsx b/packages/ui/src/components/command-receipt.tsx
new file mode 100644
index 0000000..298bc3c
--- /dev/null
+++ b/packages/ui/src/components/command-receipt.tsx
@@ -0,0 +1,126 @@
+import {
+ Ban,
+ CheckCircle2,
+ CircleDashed,
+ Clock3,
+ TerminalSquare,
+ TriangleAlert,
+} from "lucide-react";
+
+import { cn } from "@opencoven/ui/lib/utils";
+
+type InvocationChannel = "cli" | "sdk" | "daemon" | "runtime";
+type InvocationStatus = "running" | "success" | "failed" | "blocked";
+
+type CommandReceiptProps = {
+ channel: InvocationChannel;
+ command: string;
+ status: InvocationStatus;
+ summary?: string;
+ duration?: string;
+ exitCode?: number;
+ timestamp?: string;
+ className?: string;
+};
+
+const statusDetails: Record<
+ InvocationStatus,
+ { label: string; icon: typeof CheckCircle2; className: string }
+> = {
+ running: {
+ label: "Running",
+ icon: CircleDashed,
+ className: "text-information",
+ },
+ success: {
+ label: "Complete",
+ icon: CheckCircle2,
+ className: "text-success",
+ },
+ failed: {
+ label: "Failed",
+ icon: TriangleAlert,
+ className: "text-destructive",
+ },
+ blocked: {
+ label: "Blocked",
+ icon: Ban,
+ className: "text-warning",
+ },
+};
+
+function CommandReceipt({
+ channel,
+ command,
+ status,
+ summary,
+ duration,
+ exitCode,
+ timestamp,
+ className,
+}: CommandReceiptProps) {
+ const statusDetail = statusDetails[status];
+ const StatusIcon = statusDetail.icon;
+
+ return (
+
+
+
+
+
+
+
+
+ {channel}
+
+
+
+ {statusDetail.label}
+
+
+
+ {command}
+
+ {summary ? (
+
+ {summary}
+
+ ) : null}
+
+
+
+ {duration || exitCode !== undefined || timestamp ? (
+
+ {duration ? (
+
+
+ {duration}
+
+ ) : null}
+ {exitCode !== undefined ? exit {exitCode} : null}
+ {timestamp ? {timestamp} : null}
+
+ ) : null}
+
+ );
+}
+
+export {
+ CommandReceipt,
+ type CommandReceiptProps,
+ type InvocationChannel,
+ type InvocationStatus,
+};
From e8b8bb04b43a33c437f6bb173eb7f3c24c9efb2f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:16:01 -0500
Subject: [PATCH 03/92] feat(ui): add developer surface block
---
packages/ui/src/blocks/developer-surface.tsx | 169 +++++++++++++++++++
1 file changed, 169 insertions(+)
create mode 100644 packages/ui/src/blocks/developer-surface.tsx
diff --git a/packages/ui/src/blocks/developer-surface.tsx b/packages/ui/src/blocks/developer-surface.tsx
new file mode 100644
index 0000000..a9b1517
--- /dev/null
+++ b/packages/ui/src/blocks/developer-surface.tsx
@@ -0,0 +1,169 @@
+import { Code2, GitBranch, Layers3 } from "lucide-react";
+import type { ReactNode } from "react";
+
+import {
+ CommandReceipt,
+ type CommandReceiptProps,
+} from "@opencoven/ui/components/command-receipt";
+import {
+ ConnectionStatus,
+ type ConnectionStatusProps,
+} from "@opencoven/ui/components/connection-status";
+import { cn } from "@opencoven/ui/lib/utils";
+
+type DeveloperSurfaceProps = {
+ project: string;
+ branch?: string;
+ title?: string;
+ description?: string;
+ connections: ConnectionStatusProps[];
+ activity?: CommandReceiptProps[];
+ actions?: ReactNode;
+ aside?: ReactNode;
+ className?: string;
+};
+
+function DeveloperSurface({
+ project,
+ branch,
+ title = "Development surface",
+ description = "Project context, integration health, and execution evidence in one reusable surface.",
+ connections,
+ activity = [],
+ actions,
+ aside,
+ className,
+}: DeveloperSurfaceProps) {
+ return (
+
+
+
+
+
+ Developer surface
+
+
+ {title}
+
+
+ {description}
+
+
+ {actions ? {actions}
: null}
+
+
+
+
+
+
+
+ {project}
+
+ {branch ? (
+
+
+ {branch}
+
+ ) : null}
+
+
+
+
+
+
+ Integrations
+
+
+ Connected development context
+
+
+
+ {connections.length} source{connections.length === 1 ? "" : "s"}
+
+
+
+ {connections.map((connection) => (
+
+ ))}
+
+
+
+
+
+
+
+ Evidence
+
+
+ Recent invocations
+
+
+
+ {activity.length} receipt{activity.length === 1 ? "" : "s"}
+
+
+ {activity.length > 0 ? (
+
+ {activity.map((receipt, index) => (
+
+ ))}
+
+ ) : (
+
+ No invocation receipts yet. Keep empty states explicit rather than
+ fabricating execution history.
+
+ )}
+
+
+
+
+ {aside ?? (
+
+
+
+ Authority rule
+
+
+ Presentation does not imply permission. Consumers must source
+ authority from Coven, Threads, Psyche, or another canonical
+ producer and render that state explicitly.
+
+
+
+
+ Adapter boundary
+
+
+ Feed this block normalized view models from the SDK, CLI, daemon,
+ runtime registry, or application state. The component performs no
+ discovery or mutation on its own.
+
+
+
+ )}
+
+
+
+ );
+}
+
+export { DeveloperSurface, type DeveloperSurfaceProps };
From ba7d239ffbe28cfdb11d223b94221a15fa332bc3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:16:14 -0500
Subject: [PATCH 04/92] feat(ui): export developer surface components
---
packages/ui/src/index.ts | 3 +++
1 file changed, 3 insertions(+)
diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts
index fc3053d..e11b8d1 100644
--- a/packages/ui/src/index.ts
+++ b/packages/ui/src/index.ts
@@ -1,4 +1,5 @@
export * from "./blocks/composer";
+export * from "./blocks/developer-surface";
export * from "./blocks/run-rail";
export * from "./blocks/session-header";
export * from "./blocks/transcript-turn";
@@ -6,7 +7,9 @@ export * from "./blocks/transcript-turn";
export * from "./components/activity-item";
export * from "./components/attachment-chip";
export * from "./components/budget-pill";
+export * from "./components/command-receipt";
export * from "./components/completion-palette";
+export * from "./components/connection-status";
export * from "./components/context-meter";
export * from "./components/empty-state";
export * from "./components/error-state";
From c6e9be8b55e634a601493a25f02f8c7cb2e64d38 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:16:30 -0500
Subject: [PATCH 05/92] test(ui): cover developer surface semantics
---
packages/ui/tests/developer-surface.test.tsx | 83 ++++++++++++++++++++
1 file changed, 83 insertions(+)
create mode 100644 packages/ui/tests/developer-surface.test.tsx
diff --git a/packages/ui/tests/developer-surface.test.tsx b/packages/ui/tests/developer-surface.test.tsx
new file mode 100644
index 0000000..7bd326d
--- /dev/null
+++ b/packages/ui/tests/developer-surface.test.tsx
@@ -0,0 +1,83 @@
+import { render, screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { axe } from "vitest-axe";
+
+import {
+ CommandReceipt,
+ ConnectionStatus,
+ DeveloperSurface,
+} from "@opencoven/ui";
+
+describe("developer surface components", () => {
+ it("renders connection state and authority as text, not color alone", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Connected")).toBeInTheDocument();
+ expect(screen.getByText("Read only")).toBeInTheDocument();
+ expect(screen.getByText("Cave client")).toBeInTheDocument();
+ });
+
+ it("keeps invocation evidence explicit", () => {
+ render(
+ ,
+ );
+
+ expect(screen.getByText("Complete")).toBeInTheDocument();
+ expect(screen.getByText("coven doctor")).toBeInTheDocument();
+ expect(screen.getByText("exit 0")).toBeInTheDocument();
+ });
+
+ it("assembles integrations and receipts without performing authority work", async () => {
+ const { container } = render(
+ ,
+ );
+
+ expect(screen.getByText("OpenCoven/ui")).toBeInTheDocument();
+ expect(screen.getByText("2 sources")).toBeInTheDocument();
+ expect(screen.getByText("1 receipt")).toBeInTheDocument();
+ expect(screen.getByText("Authority rule")).toBeInTheDocument();
+ expect(await axe(container)).toHaveNoViolations();
+ });
+});
From d038f9ae1478e989d92b5b8a6827f067c4d21fa5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:16:57 -0500
Subject: [PATCH 06/92] feat(registry): add developer surface items
---
registry/developer/registry.fragment.json | 62 +++++++++++++++++++++++
1 file changed, 62 insertions(+)
create mode 100644 registry/developer/registry.fragment.json
diff --git a/registry/developer/registry.fragment.json b/registry/developer/registry.fragment.json
new file mode 100644
index 0000000..4d5ed1f
--- /dev/null
+++ b/registry/developer/registry.fragment.json
@@ -0,0 +1,62 @@
+{
+ "$schema": "https://ui.shadcn.com/schema/registry.json",
+ "items": [
+ {
+ "name": "connection-status",
+ "type": "registry:component",
+ "title": "Connection status",
+ "description": "Integration health and authority state for SDK, CLI, daemon, runtime, and project sources.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": ["cn", "coven-theme"],
+ "files": [
+ {
+ "path": "../../packages/ui/src/components/connection-status.tsx",
+ "type": "registry:component",
+ "target": "@components/connection-status.tsx"
+ }
+ ],
+ "meta": {
+ "states": ["connected", "pending", "degraded", "disconnected", "unavailable"],
+ "authority": ["read-only", "proposal", "mutating", "local-authority"]
+ }
+ },
+ {
+ "name": "command-receipt",
+ "type": "registry:component",
+ "title": "Command receipt",
+ "description": "Explicit CLI, SDK, daemon, or runtime invocation evidence with status and timing.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": ["cn", "coven-theme"],
+ "files": [
+ {
+ "path": "../../packages/ui/src/components/command-receipt.tsx",
+ "type": "registry:component",
+ "target": "@components/command-receipt.tsx"
+ }
+ ],
+ "meta": {
+ "channels": ["cli", "sdk", "daemon", "runtime"],
+ "states": ["running", "success", "failed", "blocked"]
+ }
+ },
+ {
+ "name": "developer-surface",
+ "type": "registry:block",
+ "title": "Developer surface",
+ "description": "Project context, integration health, authority cues, and invocation receipts in one reusable development cockpit.",
+ "dependencies": ["lucide-react"],
+ "registryDependencies": ["connection-status", "command-receipt", "cn"],
+ "files": [
+ {
+ "path": "../../packages/ui/src/blocks/developer-surface.tsx",
+ "type": "registry:block",
+ "target": "@components/blocks/developer-surface.tsx"
+ }
+ ],
+ "meta": {
+ "integrationSources": ["sdk", "cli", "daemon", "runtime", "project"],
+ "authority": "presentation-only"
+ }
+ }
+ ]
+}
From 1a970ec05415dfa52a8356a81e8e01e4f97ff900 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:17:04 -0500
Subject: [PATCH 07/92] feat(registry): compose developer surface fragment
---
scripts/compose-registry.mjs | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/compose-registry.mjs b/scripts/compose-registry.mjs
index 0e658d7..d5a5ee1 100644
--- a/scripts/compose-registry.mjs
+++ b/scripts/compose-registry.mjs
@@ -7,6 +7,7 @@ const fragments = [
"registry/styles/registry.fragment.json",
"registry/components/registry.fragment.json",
"registry/blocks/registry.fragment.json",
+ "registry/developer/registry.fragment.json",
];
const rawItems = [];
From 756ae8fdad10ae47357ccf5d10fe46b27b6f3082 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:17:29 -0500
Subject: [PATCH 08/92] feat(specimens): add developer surface showcase
---
apps/specimens/src/developer-showcase.tsx | 194 ++++++++++++++++++++++
1 file changed, 194 insertions(+)
create mode 100644 apps/specimens/src/developer-showcase.tsx
diff --git a/apps/specimens/src/developer-showcase.tsx b/apps/specimens/src/developer-showcase.tsx
new file mode 100644
index 0000000..211e76c
--- /dev/null
+++ b/apps/specimens/src/developer-showcase.tsx
@@ -0,0 +1,194 @@
+import { Button, DeveloperSurface } from "@opencoven/ui";
+import { ArrowLeft, BookOpen, Boxes, TerminalSquare } from "lucide-react";
+
+function DeveloperShowcase() {
+ return (
+
+
+
+
+
+
+
+ Shared development UI
+
+
+ One visual language for the work around the work.
+
+
+ Reusable components for project context, SDK state, CLI execution,
+ daemon authority, runtime health, and verifiable receipts. The UI
+ presents canonical state; it never invents authority.
+
+
+
+ {[
+ ["CLI", "coven"],
+ ["SDK", "read-only"],
+ ["Authority", "daemon"],
+ ].map(([label, value]) => (
+
+
+ {label}
+
+ {value}
+
+ ))}
+
+
+
+
+
+
+
+ Open CLI
+
+
+ Inspect project
+
+ >
+ }
+ connections={[
+ {
+ name: "Coven daemon",
+ kind: "Daemon",
+ state: "connected",
+ authority: "local-authority",
+ version: "v0.1",
+ detail:
+ "Session and runtime authority from the same-user local daemon contract.",
+ meta: "coven.daemon.v1",
+ },
+ {
+ name: "OpenCoven SDK",
+ kind: "SDK",
+ state: "degraded",
+ authority: "read-only",
+ version: "0.1 exp",
+ detail:
+ "Experimental read-only coordination surface; mutation remains deliberately unavailable.",
+ meta: "Cave + Coven reads",
+ },
+ {
+ name: "Coven CLI",
+ kind: "CLI",
+ state: "connected",
+ authority: "proposal",
+ version: "@opencoven/cli",
+ detail:
+ "Canonical user CLI. Execution is still revalidated by the daemon authority boundary.",
+ meta: "coven doctor · run · sessions",
+ },
+ {
+ name: "coven-code runtime",
+ kind: "Runtime",
+ state: "connected",
+ authority: "proposal",
+ version: "0.7",
+ detail:
+ "Registered coding runtime driven through the Coven execution substrate.",
+ meta: "stream-json",
+ },
+ ]}
+ activity={[
+ {
+ channel: "cli",
+ command: "coven doctor",
+ status: "success",
+ summary: "Local runtime and harness readiness verified.",
+ duration: "0.8s",
+ exitCode: 0,
+ timestamp: "now",
+ },
+ {
+ channel: "sdk",
+ command: "cave.health({ timeoutMs: 5000 })",
+ status: "success",
+ summary: "Read-only Cave health returned through a caller-controlled transport.",
+ duration: "42ms",
+ timestamp: "now",
+ },
+ {
+ channel: "daemon",
+ command: "session.create · project=OpenCoven/ui",
+ status: "blocked",
+ summary:
+ "Mutation is intentionally shown as blocked until the canonical authority grants it.",
+ timestamp: "now",
+ },
+ ]}
+ aside={
+
+
+
+ Integration contract
+
+ Normalize, then render.
+
+ SDK and CLI adapters map external responses into small view
+ models. UI code does not perform discovery, credential lookup,
+ transport negotiation, or daemon mutation.
+
+
+
+
+ Public install
+
+
+ pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/developer-surface.json
+
+
+
+
+ Canonical CLI
+
+
+ npm install -g @opencoven/cli
+
+
+
+ }
+ />
+
+
+
+ );
+}
+
+export { DeveloperShowcase };
From 24bf7bd23eea9fadb47fba5cb55697d631cf75ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:17:36 -0500
Subject: [PATCH 09/92] feat(specimens): route developer showcase
---
apps/specimens/src/main.tsx | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/apps/specimens/src/main.tsx b/apps/specimens/src/main.tsx
index 15f8d77..9f8902f 100644
--- a/apps/specimens/src/main.tsx
+++ b/apps/specimens/src/main.tsx
@@ -4,6 +4,7 @@ import { createRoot } from "react-dom/client";
import "@opencoven/ui/globals.css";
import "./specimens.css";
import { App } from "./app";
+import { DeveloperShowcase } from "./developer-showcase";
const root = document.getElementById("root");
@@ -11,8 +12,10 @@ if (!root) {
throw new Error("Specimen root is missing");
}
+const normalizedPath = window.location.pathname.replace(/\/+$/, "") || "/";
+
createRoot(root).render(
-
+ {normalizedPath === "/developer" ? : }
,
);
From 7764b62e0884945a277773582b0deec2922c4c80 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:17:53 -0500
Subject: [PATCH 10/92] docs(ui): define SDK and CLI adapter boundary
---
docs/developer-surface.md | 88 +++++++++++++++++++++++++++++++++++++++
1 file changed, 88 insertions(+)
create mode 100644 docs/developer-surface.md
diff --git a/docs/developer-surface.md b/docs/developer-surface.md
new file mode 100644
index 0000000..82f0fd1
--- /dev/null
+++ b/docs/developer-surface.md
@@ -0,0 +1,88 @@
+# Developer surface integration contract
+
+OpenCoven UI exposes presentation primitives for development tooling. It does not own runtime discovery, credentials, authority, orchestration, session mutation, or execution.
+
+## Canonical ownership
+
+| Concern | Canonical producer | UI responsibility |
+| --- | --- | --- |
+| Cave/Coven read models | `@opencoven/sdk` packages | Normalize returned state into view models and render it |
+| User-facing local CLI | `@opencoven/cli` (`coven`) | Render commands, status, and receipts supplied by the host |
+| Session/runtime authority | Coven daemon (`coven.daemon.v1`) | Display authority state explicitly; never infer permission |
+| Orchestration authority | Psyche | Display task/lease/approval/receipt state supplied by the client |
+| Protected mutation | Threads + Coven | Present pending/proposed/committed state without performing the mutation |
+| Production application behavior | Cave | Consume these components where appropriate; Cave remains product authority |
+
+## Current SDK status
+
+The TypeScript SDK is experimental. Its first public release is intentionally read-only: discovery, compatibility, pairing, health, and canonical reads are in scope; message sending, streaming, attachments, task handoffs, GitHub mutation, and offline mutation queues are deferred.
+
+Do not make a UI control look executable merely because an SDK type exists. Use `ConnectionStatus` authority labels and blocked/proposal states until a canonical producer supplies explicit authority.
+
+## Current CLI status
+
+The canonical user CLI is `@opencoven/cli`, invoked as `coven`.
+
+```bash
+npm install -g @opencoven/cli
+coven doctor
+```
+
+The SDK repository also contains a private experimental `@opencoven/dev-cli`. That workspace is for repository development and must not be advertised as the public OpenCoven CLI.
+
+## Adapter pattern
+
+Keep integration code outside the UI package:
+
+```ts
+import type {
+ CommandReceiptProps,
+ ConnectionStatusProps,
+} from '@opencoven/ui';
+
+export function toCaveConnection(result: CaveHealth): ConnectionStatusProps {
+ return {
+ name: 'Cave client',
+ kind: 'SDK',
+ state: result.ok ? 'connected' : 'degraded',
+ authority: 'read-only',
+ version: result.protocolVersion,
+ meta: result.instanceId,
+ };
+}
+
+export function toCliReceipt(result: CovenCommandResult): CommandReceiptProps {
+ return {
+ channel: 'cli',
+ command: result.command,
+ status: result.exitCode === 0 ? 'success' : 'failed',
+ exitCode: result.exitCode,
+ duration: result.duration,
+ };
+}
+```
+
+Then render the normalized values:
+
+```tsx
+import { DeveloperSurface } from '@opencoven/ui';
+
+
+```
+
+The UI package performs no import-time I/O and should not add a dependency on `@opencoven/sdk` or `@opencoven/cli`. This prevents dependency cycles, keeps package consumption lightweight, and preserves the security boundary: adapters retrieve and verify state; components present it.
+
+## Registry installation
+
+```bash
+pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/connection-status.json
+pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/command-receipt.json
+pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/developer-surface.json
+```
+
+The assembled reference is available at `/developer` in the specimen app.
From 69b1e52699f00e5267514c9f8bacc98afe68bf2b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:18:16 -0500
Subject: [PATCH 11/92] fix(specimens): use supported button density
---
apps/specimens/src/developer-showcase.tsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/apps/specimens/src/developer-showcase.tsx b/apps/specimens/src/developer-showcase.tsx
index 211e76c..60998ae 100644
--- a/apps/specimens/src/developer-showcase.tsx
+++ b/apps/specimens/src/developer-showcase.tsx
@@ -75,11 +75,11 @@ function DeveloperShowcase() {
description="A normalized, presentation-only view over real SDK, CLI, daemon, runtime, and repository signals."
actions={
<>
-
+
Open CLI
-
+
Inspect project
>
From 790b1206429621a0a4966256e7245720b818583e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:18:39 -0500
Subject: [PATCH 12/92] fix(deploy): build registry before specimen app
---
vercel.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vercel.json b/vercel.json
index 3dac685..07b8f6c 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,7 +1,7 @@
{
"framework": "vite",
"installCommand": "pnpm install --frozen-lockfile",
- "buildCommand": "pnpm build",
+ "buildCommand": "pnpm registry:build && pnpm build",
"outputDirectory": "apps/specimens/dist",
"rewrites": [
{
From 268688b709ac48cc6544b27bb59b6811f28c8737 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:19:00 -0500
Subject: [PATCH 13/92] test(ui): align accessibility assertion style
---
packages/ui/tests/developer-surface.test.tsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/packages/ui/tests/developer-surface.test.tsx b/packages/ui/tests/developer-surface.test.tsx
index 7bd326d..49a4cc0 100644
--- a/packages/ui/tests/developer-surface.test.tsx
+++ b/packages/ui/tests/developer-surface.test.tsx
@@ -78,6 +78,8 @@ describe("developer surface components", () => {
expect(screen.getByText("2 sources")).toBeInTheDocument();
expect(screen.getByText("1 receipt")).toBeInTheDocument();
expect(screen.getByText("Authority rule")).toBeInTheDocument();
- expect(await axe(container)).toHaveNoViolations();
+
+ const results = await axe(container);
+ expect(results.violations).toHaveLength(0);
});
});
From d92d556c51405931b23c0a395f0101b1ef3a5bc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:20:35 -0500
Subject: [PATCH 14/92] refactor(specimens): remove nonfunctional showcase
actions
---
apps/specimens/src/developer-showcase.tsx | 34 +++++++++++------------
1 file changed, 16 insertions(+), 18 deletions(-)
diff --git a/apps/specimens/src/developer-showcase.tsx b/apps/specimens/src/developer-showcase.tsx
index 60998ae..6d9d07b 100644
--- a/apps/specimens/src/developer-showcase.tsx
+++ b/apps/specimens/src/developer-showcase.tsx
@@ -1,5 +1,5 @@
-import { Button, DeveloperSurface } from "@opencoven/ui";
-import { ArrowLeft, BookOpen, Boxes, TerminalSquare } from "lucide-react";
+import { DeveloperSurface } from "@opencoven/ui";
+import { ArrowLeft, BookOpen, Boxes } from "lucide-react";
function DeveloperShowcase() {
return (
@@ -17,7 +17,10 @@ function DeveloperShowcase() {
Developer surface
-
+
{label}
- {value}
+
+ {value}
+
))}
@@ -73,17 +78,6 @@ function DeveloperShowcase() {
branch="feat/developer-surface-system"
title="OpenCoven development context"
description="A normalized, presentation-only view over real SDK, CLI, daemon, runtime, and repository signals."
- actions={
- <>
-
-
- Open CLI
-
-
- Inspect project
-
- >
- }
connections={[
{
name: "Coven daemon",
@@ -140,7 +134,8 @@ function DeveloperShowcase() {
channel: "sdk",
command: "cave.health({ timeoutMs: 5000 })",
status: "success",
- summary: "Read-only Cave health returned through a caller-controlled transport.",
+ summary:
+ "Read-only Cave health returned through a caller-controlled transport.",
duration: "42ms",
timestamp: "now",
},
@@ -159,7 +154,9 @@ function DeveloperShowcase() {
Integration contract
- Normalize, then render.
+
+ Normalize, then render.
+
SDK and CLI adapters map external responses into small view
models. UI code does not perform discovery, credential lookup,
@@ -171,7 +168,8 @@ function DeveloperShowcase() {
Public install
- pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/developer-surface.json
+ pnpm dlx shadcn@latest add
+ https://ui.opencoven.ai/r/developer-surface.json
From 85ece827a62fa06dec9bea82fe39762d8a2ba2d3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:20:53 -0500
Subject: [PATCH 15/92] test(contracts): enforce developer integration boundary
---
scripts/verify-contracts.mjs | 40 +++++++++++++++++++++++++++++++++---
1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/scripts/verify-contracts.mjs b/scripts/verify-contracts.mjs
index 9c305b5..a91fed8 100644
--- a/scripts/verify-contracts.mjs
+++ b/scripts/verify-contracts.mjs
@@ -10,6 +10,9 @@ const [
tokens,
specimenCss,
specimenApp,
+ specimenMain,
+ developerSurface,
+ developerDocs,
button,
tooltip,
menu,
@@ -19,6 +22,9 @@ const [
read("packages/ui/src/styles/globals.css"),
read("apps/specimens/src/specimens.css"),
read("apps/specimens/src/app.tsx"),
+ read("apps/specimens/src/main.tsx"),
+ read("packages/ui/src/blocks/developer-surface.tsx"),
+ read("docs/developer-surface.md"),
read("packages/ui/src/components/ui/button.tsx"),
read("packages/ui/src/components/ui/tooltip.tsx"),
read("packages/ui/src/components/ui/dropdown-menu.tsx"),
@@ -43,6 +49,17 @@ const assertions = [
name.startsWith("react-aria"),
),
],
+ [
+ "developer UI does not depend on SDK or CLI runtimes",
+ ![...Object.keys(manifest.dependencies), ...Object.keys(manifest.peerDependencies)].some(
+ (name) =>
+ name === "@opencoven/sdk" ||
+ name === "@opencoven/sdk-core" ||
+ name === "@opencoven/cave-client" ||
+ name === "@opencoven/coven-client" ||
+ name === "@opencoven/cli",
+ ),
+ ],
[
"tool classes are canonical",
["read", "write", "exec", "net"].every(
@@ -108,9 +125,8 @@ const assertions = [
[
"responsive rail becomes compact navigation",
specimenCss.includes("@media (max-width: 68rem)") &&
- specimenCss.includes(
- ".specimen-rail__context,\n .specimen-rail__package",
- ),
+ specimenCss.includes(".specimen-rail__context") &&
+ specimenCss.includes(".specimen-rail__package"),
],
[
"specimen chrome avoids decorative gradients",
@@ -120,6 +136,24 @@ const assertions = [
"filled action variants are explicit",
button.includes("primary:") && button.includes("presence:"),
],
+ [
+ "developer surface remains presentation only",
+ developerSurface.includes("Presentation does not imply permission") &&
+ developerSurface.includes("performs no discovery or mutation on its own") &&
+ !developerSurface.includes("@opencoven/sdk") &&
+ !developerSurface.includes("@opencoven/cli"),
+ ],
+ [
+ "developer route is explicit",
+ specimenMain.includes('normalizedPath === "/developer"') &&
+ specimenMain.includes(" "),
+ ],
+ [
+ "developer docs distinguish public CLI and experimental SDK",
+ developerDocs.includes("@opencoven/cli") &&
+ developerDocs.includes("private experimental `@opencoven/dev-cli`") &&
+ developerDocs.includes("first public release is intentionally read-only"),
+ ],
];
const failed = assertions.filter(([, passed]) => !passed);
From 8745c288234fb3d4bed6d4e9791a7a359a23868a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:23:05 -0500
Subject: [PATCH 16/92] test(registry): install developer surface in clean
consumer
---
scripts/test-registry-consumer.mjs | 41 +++++++++++++++++-------------
1 file changed, 24 insertions(+), 17 deletions(-)
diff --git a/scripts/test-registry-consumer.mjs b/scripts/test-registry-consumer.mjs
index 3bec70b..88337e3 100644
--- a/scripts/test-registry-consumer.mjs
+++ b/scripts/test-registry-consumer.mjs
@@ -145,28 +145,35 @@ function run(command, args) {
}
try {
- const itemUrl = `http://127.0.0.1:${address.port}/composer.json`;
- await run("pnpm", [
- "exec",
- "shadcn",
- "add",
- itemUrl,
- "--cwd",
- consumer,
- "--yes",
- ]);
+ const items = ["composer", "developer-surface"];
+ for (const item of items) {
+ await run("pnpm", [
+ "exec",
+ "shadcn",
+ "add",
+ `http://127.0.0.1:${address.port}/${item}.json`,
+ "--cwd",
+ consumer,
+ "--yes",
+ ]);
+ }
+
await run("pnpm", ["--dir", consumer, "install", "--ignore-workspace"]);
await run("pnpm", ["--dir", consumer, "exec", "tsc", "--noEmit"]);
- const composer = await readFile(
- path.join(consumer, "src", "components", "blocks", "composer.tsx"),
- "utf8",
- );
- if (composer.includes("@opencoven/ui")) {
- throw new Error("Installed source retained package-internal aliases");
+ for (const [relativePath, label] of [
+ ["src/components/blocks/composer.tsx", "composer"],
+ ["src/components/blocks/developer-surface.tsx", "developer surface"],
+ ]) {
+ const source = await readFile(path.join(consumer, relativePath), "utf8");
+ if (source.includes("@opencoven/ui")) {
+ throw new Error(`${label} retained package-internal aliases`);
+ }
}
- console.log(`Clean consumer installed and type-checked ${itemUrl}.`);
+ console.log(
+ `Clean consumer installed and type-checked ${items.join(", ")} from the generated registry.`,
+ );
} finally {
await new Promise((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
From 16fd15fc48ca7d3e95dc7b11c254e81cfdadc0a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:23:22 -0500
Subject: [PATCH 17/92] test(specimens): capture developer surface receipts
---
scripts/developer-visual-review.mjs | 119 ++++++++++++++++++++++++++++
1 file changed, 119 insertions(+)
create mode 100644 scripts/developer-visual-review.mjs
diff --git a/scripts/developer-visual-review.mjs b/scripts/developer-visual-review.mjs
new file mode 100644
index 0000000..2352741
--- /dev/null
+++ b/scripts/developer-visual-review.mjs
@@ -0,0 +1,119 @@
+import { mkdir, writeFile } from "node:fs/promises";
+import { spawn } from "node:child_process";
+import path from "node:path";
+
+const chromePath = process.env.CHROME_PATH;
+const baseUrl = process.env.BASE_URL ?? "http://127.0.0.1:4173";
+const outputDir = path.resolve(
+ process.env.VISUAL_OUTPUT_DIR ?? "artifacts/visual-review",
+);
+
+if (!chromePath) {
+ throw new Error("CHROME_PATH is required");
+}
+
+await mkdir(outputDir, { recursive: true });
+
+function runChrome(args) {
+ return new Promise((resolve, reject) => {
+ const child = spawn(chromePath, args, {
+ stdio: ["ignore", "pipe", "pipe"],
+ });
+ const stdout = [];
+ const stderr = [];
+ child.stdout.on("data", (chunk) => stdout.push(String(chunk)));
+ child.stderr.on("data", (chunk) => stderr.push(String(chunk)));
+ child.on("error", reject);
+ child.on("exit", (code) => {
+ if (code !== 0) {
+ reject(
+ new Error(
+ `Chrome exited ${code}: ${stderr.join("").trim() || "no stderr"}`,
+ ),
+ );
+ return;
+ }
+ resolve({ stdout: stdout.join(""), stderr: stderr.join("") });
+ });
+ });
+}
+
+const commonArgs = [
+ "--headless=new",
+ "--no-sandbox",
+ "--disable-dev-shm-usage",
+ "--disable-gpu",
+ "--hide-scrollbars",
+ "--force-prefers-reduced-motion",
+ "--virtual-time-budget=1500",
+];
+const developerUrl = new URL("/developer", baseUrl).href;
+
+const dom = await runChrome([
+ ...commonArgs,
+ "--dump-dom",
+ developerUrl,
+]);
+
+for (const requiredText of [
+ "OpenCoven development context",
+ "Coven daemon",
+ "OpenCoven SDK",
+ "Coven CLI",
+ "coven-code runtime",
+ "Read only",
+ "Local authority",
+ "Recent invocations",
+]) {
+ if (!dom.stdout.includes(requiredText)) {
+ throw new Error(`Developer surface is missing rendered text: ${requiredText}`);
+ }
+}
+
+if (dom.stdout.includes("Open CLI") || dom.stdout.includes("Inspect project")) {
+ throw new Error(
+ "Developer showcase rendered a control without a real host action contract",
+ );
+}
+
+const scenarios = [
+ { name: "developer-dark-desktop", width: 1440, height: 1000 },
+ { name: "developer-dark-mobile", width: 390, height: 844 },
+];
+
+for (const scenario of scenarios) {
+ const screenshot = path.join(outputDir, `${scenario.name}.png`);
+ await runChrome([
+ ...commonArgs,
+ `--window-size=${scenario.width},${scenario.height}`,
+ `--screenshot=${screenshot}`,
+ developerUrl,
+ ]);
+}
+
+await writeFile(
+ path.join(outputDir, "developer-review.json"),
+ `${JSON.stringify(
+ {
+ route: "/developer",
+ result: "PASS",
+ requiredText: [
+ "OpenCoven development context",
+ "Coven daemon",
+ "OpenCoven SDK",
+ "Coven CLI",
+ "coven-code runtime",
+ "Read only",
+ "Local authority",
+ "Recent invocations",
+ ],
+ scenarios,
+ },
+ null,
+ 2,
+ )}\n`,
+);
+
+console.log(
+ `Developer visual review passed with ${scenarios.length} viewport receipts.`,
+);
From eaa83d3f79dceee9cec0ce461d0e1f85373ecd3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Fri, 28 Aug 2026 23:23:33 -0500
Subject: [PATCH 18/92] test(specimens): review developer surface viewports
---
.github/workflows/visual-review.yml | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/visual-review.yml b/.github/workflows/visual-review.yml
index 0b7b3ea..5204537 100644
--- a/.github/workflows/visual-review.yml
+++ b/.github/workflows/visual-review.yml
@@ -7,6 +7,7 @@ on:
- "apps/specimens/**"
- "packages/ui/**"
- "scripts/visual-review.mjs"
+ - "scripts/developer-visual-review.mjs"
workflow_dispatch:
permissions:
@@ -40,8 +41,8 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- - name: Build package and specimen app
- run: pnpm build
+ - name: Build registry, package, and specimen app
+ run: pnpm registry:build && pnpm build
- name: Set up Chrome for Testing
id: chrome
@@ -85,11 +86,16 @@ jobs:
set +e
node scripts/visual-review.mjs
- review_status=$?
+ browser_status=$?
+ node scripts/developer-visual-review.mjs
+ developer_status=$?
set -e
cp "$preview_log" artifacts/visual-review/preview.log
- exit "$review_status"
+
+ if [[ "$browser_status" -ne 0 || "$developer_status" -ne 0 ]]; then
+ exit 1
+ fi
- name: Upload visual-review artifact
if: always()
From 6e0df638cfa4921bb1635978e039673b671868ec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:15:57 -0500
Subject: [PATCH 19/92] chore(ci): remove brittle self-modifying visual
workflow
---
.../refine-visual-clipping-check.yml | 151 ------------------
1 file changed, 151 deletions(-)
delete mode 100644 .github/workflows/refine-visual-clipping-check.yml
diff --git a/.github/workflows/refine-visual-clipping-check.yml b/.github/workflows/refine-visual-clipping-check.yml
deleted file mode 100644
index 26546a4..0000000
--- a/.github/workflows/refine-visual-clipping-check.yml
+++ /dev/null
@@ -1,151 +0,0 @@
-name: Refine visual clipping check
-
-on:
- push:
- branches:
- - test/specimen-visual-review
-
-permissions:
- contents: write
-
-jobs:
- patch:
- if: github.actor != 'github-actions[bot]'
- runs-on: ubuntu-latest
- timeout-minutes: 10
- steps:
- - uses: actions/checkout@v4
- - name: Refine intentional-scroll assertions
- run: |
- python - <<'PY'
- from pathlib import Path
-
- def replace_once(path: str, before: str, after: str) -> None:
- target = Path(path)
- source = target.read_text()
- if before not in source:
- raise SystemExit(f"Anchor not found in {path}")
- target.write_text(source.replace(before, after, 1))
-
- replace_once(
- "scripts/visual-review.mjs",
- ''' const clippedSurfaceSelectors = [
- '[data-slot="session-header"]',
- '.assembled-lab [data-slot="tabs"]',
- '.assembled-lab [data-slot="transcript-turn"]',
- '.assembled-lab [data-slot="composer"]',
- ];''',
- ''' const clippedSurfaceSelectors = [
- '[data-slot="session-header"]',
- '.assembled-lab [data-slot="transcript-turn"]',
- '.assembled-lab [data-slot="composer"]',
- ];''',
- )
-
- replace_once(
- "scripts/visual-review.mjs",
- ''' const internallyClipped = clippedSurfaceSelectors.flatMap((selector) =>
- [...document.querySelectorAll(selector)]
- .map((element) => ({
- selector,
- overflow: Math.max(0, element.scrollWidth - element.clientWidth),
- }))
- .filter(({ overflow }) => overflow > 1),
- );
-
- const isVisible = (element) => {''',
- ''' const internallyClipped = clippedSurfaceSelectors.flatMap((selector) =>
- [...document.querySelectorAll(selector)]
- .map((element) => ({
- selector,
- overflow: Math.max(0, element.scrollWidth - element.clientWidth),
- }))
- .filter(({ overflow }) => overflow > 1),
- );
- const tabNavigation = lab?.querySelector(".assembled-lab__nav");
- const firstTab = tabNavigation?.querySelector('[role="tab"]');
- const tabNavigationStyle = tabNavigation
- ? getComputedStyle(tabNavigation)
- : null;
- const tabNavigationRect = tabNavigation?.getBoundingClientRect();
- const firstTabRect = firstTab?.getBoundingClientRect();
- const tabNavigationReady =
- !lab ||
- Boolean(
- tabNavigation &&
- firstTab &&
- tabNavigationStyle &&
- tabNavigationRect &&
- firstTabRect &&
- ["auto", "scroll"].includes(tabNavigationStyle.overflowX) &&
- firstTabRect.left >= tabNavigationRect.left - 1 &&
- firstTabRect.right <= tabNavigationRect.right + 1,
- );
-
- const isVisible = (element) => {''',
- )
-
- replace_once(
- "scripts/visual-review.mjs",
- ''' density: root.dataset.density,
- internallyClipped,
- };''',
- ''' density: root.dataset.density,
- internallyClipped,
- tabNavigationReady,
- };''',
- )
-
- replace_once(
- "scripts/visual-review.mjs",
- ''' if (layout.internallyClipped.length > 0) {
- failures.push(
- `internally clipped surfaces: ${layout.internallyClipped
- .map(({ selector, overflow }) => `${selector} (${overflow}px)`)
- .join(", ")}`,
- );
- }
- if (layout.scheme !== scenario.scheme) {''',
- ''' if (layout.internallyClipped.length > 0) {
- failures.push(
- `internally clipped surfaces: ${layout.internallyClipped
- .map(({ selector, overflow }) => `${selector} (${overflow}px)`)
- .join(", ")}`,
- );
- }
- if (scenario.expected === "lab" && !layout.tabNavigationReady) {
- failures.push(
- "assembled tabs are not exposed through a usable horizontal scroller",
- );
- }
- if (layout.scheme !== scenario.scheme) {''',
- )
-
- replace_once(
- "handoffs/visual-review.md",
- "- the page has no horizontal overflow and key assembled surfaces have no hidden internal clipping;",
- "- the page has no horizontal overflow, key assembled surfaces have no hidden clipping, and the assembled tab strip exposes an intentional horizontal scroller;",
- )
- replace_once(
- "handoffs/visual-review.md",
- "kept key assembled surfaces free of hidden internal clipping, and preserved its\nstructural contracts.",
- "kept key assembled surfaces free of hidden clipping, exposed a usable assembled-tab scroller, and preserved its structural contracts.",
- )
- PY
- - uses: pnpm/action-setup@v4
- with:
- version: 10.17.1
- - uses: actions/setup-node@v4
- with:
- node-version: 24
- cache: pnpm
- - run: pnpm install --frozen-lockfile
- - run: pnpm exec prettier --write scripts/visual-review.mjs handoffs/visual-review.md
- - run: node --check scripts/visual-review.mjs
- - name: Commit refined assertions
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add scripts/visual-review.mjs handoffs/visual-review.md
- git commit -m "test(specimens): distinguish scrolling from clipping"
- git push origin HEAD:test/specimen-visual-review
From 11559518f65a83f0c7bbe703a4275b8d3c1ede4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:19:21 -0500
Subject: [PATCH 20/92] fix(specimens): carry shell regression guards into
developer surface
---
apps/specimens/src/specimens-fixes.css | 37 ++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
create mode 100644 apps/specimens/src/specimens-fixes.css
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
new file mode 100644
index 0000000..7623b03
--- /dev/null
+++ b/apps/specimens/src/specimens-fixes.css
@@ -0,0 +1,37 @@
+/* Focused regression guards layered after the specimen shell styles. */
+
+body:not(:has(#group-composer))
+ .specimen-rail
+ a[href="#group-composer"],
+body:not(:has(#group-run-rail))
+ .specimen-rail
+ a[href="#group-run-rail"],
+body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
+ display: none;
+}
+
+@media (max-width: 24.375rem) {
+ .assembled-lab__nav {
+ overflow-x: hidden;
+ }
+
+ .assembled-lab__tabs {
+ display: grid;
+ width: 100%;
+ min-width: 0;
+ grid-template-columns: repeat(5, minmax(0, 1fr));
+ }
+
+ .assembled-lab__tabs [role="tab"] {
+ min-width: 0;
+ padding-inline: 0.35rem;
+ font-size: 0.625rem;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .specimen-card:hover,
+ .specimen-card:focus-within {
+ translate: none;
+ }
+}
From 1a1a2ff11b80a3832abedb407886f00491ade608 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:19:28 -0500
Subject: [PATCH 21/92] fix(specimens): preserve browser shortcuts across
developer surfaces
---
apps/specimens/src/main.tsx | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/apps/specimens/src/main.tsx b/apps/specimens/src/main.tsx
index 9f8902f..1ca7f6f 100644
--- a/apps/specimens/src/main.tsx
+++ b/apps/specimens/src/main.tsx
@@ -3,6 +3,7 @@ import { createRoot } from "react-dom/client";
import "@opencoven/ui/globals.css";
import "./specimens.css";
+import "./specimens-fixes.css";
import { App } from "./app";
import { DeveloperShowcase } from "./developer-showcase";
@@ -14,6 +15,18 @@ if (!root) {
const normalizedPath = window.location.pathname.replace(/\/+$/, "") || "/";
+if (normalizedPath !== "/") {
+ window.addEventListener(
+ "keydown",
+ (event) => {
+ if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
+ event.stopImmediatePropagation();
+ }
+ },
+ { capture: true },
+ );
+}
+
createRoot(root).render(
{normalizedPath === "/developer" ? : }
From fa5ad222f80b71b3c45e9760e5dfca342c6a1c72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:20:42 -0500
Subject: [PATCH 22/92] style(specimens): format regression guards
---
apps/specimens/src/specimens-fixes.css | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index 7623b03..e44c473 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -1,11 +1,7 @@
/* Focused regression guards layered after the specimen shell styles. */
-body:not(:has(#group-composer))
- .specimen-rail
- a[href="#group-composer"],
-body:not(:has(#group-run-rail))
- .specimen-rail
- a[href="#group-run-rail"],
+body:not(:has(#group-composer)) .specimen-rail a[href="#group-composer"],
+body:not(:has(#group-run-rail)) .specimen-rail a[href="#group-run-rail"],
body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
display: none;
}
From 48dba4e8cf9e875d488b013754f35105a84a843e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:21:21 -0500
Subject: [PATCH 23/92] chore(ci): add one-shot developer surface formatter
---
.../format-developer-surface-once.yml | 56 +++++++++++++++++++
1 file changed, 56 insertions(+)
create mode 100644 .github/workflows/format-developer-surface-once.yml
diff --git a/.github/workflows/format-developer-surface-once.yml b/.github/workflows/format-developer-surface-once.yml
new file mode 100644
index 0000000..4f88a90
--- /dev/null
+++ b/.github/workflows/format-developer-surface-once.yml
@@ -0,0 +1,56 @@
+name: Format developer surface once
+
+on:
+ push:
+ branches:
+ - feat/developer-surface-system
+
+permissions:
+ contents: write
+
+jobs:
+ format:
+ if: github.actor != 'github-actions[bot]'
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v4
+ - uses: pnpm/action-setup@v4
+ with:
+ version: 10.17.1
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 24
+ cache: pnpm
+ - run: pnpm install --frozen-lockfile
+ - name: Format changed developer-surface files
+ run: |
+ pnpm exec prettier --write \
+ apps/specimens/src/developer-showcase.tsx \
+ apps/specimens/src/main.tsx \
+ apps/specimens/src/specimens-fixes.css \
+ docs/developer-surface.md \
+ packages/ui/src/blocks/developer-surface.tsx \
+ packages/ui/src/components/connection-status.tsx \
+ registry/developer/registry.fragment.json \
+ scripts/developer-visual-review.mjs \
+ scripts/verify-contracts.mjs
+ - name: Commit formatting if needed
+ run: |
+ git config user.name "github-actions[bot]"
+ git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
+ git add \
+ apps/specimens/src/developer-showcase.tsx \
+ apps/specimens/src/main.tsx \
+ apps/specimens/src/specimens-fixes.css \
+ docs/developer-surface.md \
+ packages/ui/src/blocks/developer-surface.tsx \
+ packages/ui/src/components/connection-status.tsx \
+ registry/developer/registry.fragment.json \
+ scripts/developer-visual-review.mjs \
+ scripts/verify-contracts.mjs
+ if git diff --cached --quiet; then
+ exit 0
+ fi
+ git commit -m "style(ui): format developer surface"
+ git push origin HEAD:feat/developer-surface-system
From b2433633387bc197364829edb090c4c094e96a5f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
<41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 30 Aug 2026 12:21:35 +0000
Subject: [PATCH 24/92] style(ui): format developer surface
---
apps/specimens/src/developer-showcase.tsx | 4 +-
docs/developer-surface.md | 37 +++++++++----------
packages/ui/src/blocks/developer-surface.tsx | 15 +++++---
.../ui/src/components/connection-status.tsx | 12 +-----
registry/developer/registry.fragment.json | 8 +++-
scripts/developer-visual-review.mjs | 10 ++---
scripts/verify-contracts.mjs | 9 ++++-
7 files changed, 48 insertions(+), 47 deletions(-)
diff --git a/apps/specimens/src/developer-showcase.tsx b/apps/specimens/src/developer-showcase.tsx
index 6d9d07b..bd6be1e 100644
--- a/apps/specimens/src/developer-showcase.tsx
+++ b/apps/specimens/src/developer-showcase.tsx
@@ -159,8 +159,8 @@ function DeveloperShowcase() {
SDK and CLI adapters map external responses into small view
- models. UI code does not perform discovery, credential lookup,
- transport negotiation, or daemon mutation.
+ models. UI code does not perform discovery, credential
+ lookup, transport negotiation, or daemon mutation.
diff --git a/docs/developer-surface.md b/docs/developer-surface.md
index 82f0fd1..fd86819 100644
--- a/docs/developer-surface.md
+++ b/docs/developer-surface.md
@@ -4,14 +4,14 @@ OpenCoven UI exposes presentation primitives for development tooling. It does no
## Canonical ownership
-| Concern | Canonical producer | UI responsibility |
-| --- | --- | --- |
-| Cave/Coven read models | `@opencoven/sdk` packages | Normalize returned state into view models and render it |
-| User-facing local CLI | `@opencoven/cli` (`coven`) | Render commands, status, and receipts supplied by the host |
-| Session/runtime authority | Coven daemon (`coven.daemon.v1`) | Display authority state explicitly; never infer permission |
-| Orchestration authority | Psyche | Display task/lease/approval/receipt state supplied by the client |
-| Protected mutation | Threads + Coven | Present pending/proposed/committed state without performing the mutation |
-| Production application behavior | Cave | Consume these components where appropriate; Cave remains product authority |
+| Concern | Canonical producer | UI responsibility |
+| ------------------------------- | -------------------------------- | -------------------------------------------------------------------------- |
+| Cave/Coven read models | `@opencoven/sdk` packages | Normalize returned state into view models and render it |
+| User-facing local CLI | `@opencoven/cli` (`coven`) | Render commands, status, and receipts supplied by the host |
+| Session/runtime authority | Coven daemon (`coven.daemon.v1`) | Display authority state explicitly; never infer permission |
+| Orchestration authority | Psyche | Display task/lease/approval/receipt state supplied by the client |
+| Protected mutation | Threads + Coven | Present pending/proposed/committed state without performing the mutation |
+| Production application behavior | Cave | Consume these components where appropriate; Cave remains product authority |
## Current SDK status
@@ -35,17 +35,14 @@ The SDK repository also contains a private experimental `@opencoven/dev-cli`. Th
Keep integration code outside the UI package:
```ts
-import type {
- CommandReceiptProps,
- ConnectionStatusProps,
-} from '@opencoven/ui';
+import type { CommandReceiptProps, ConnectionStatusProps } from "@opencoven/ui";
export function toCaveConnection(result: CaveHealth): ConnectionStatusProps {
return {
- name: 'Cave client',
- kind: 'SDK',
- state: result.ok ? 'connected' : 'degraded',
- authority: 'read-only',
+ name: "Cave client",
+ kind: "SDK",
+ state: result.ok ? "connected" : "degraded",
+ authority: "read-only",
version: result.protocolVersion,
meta: result.instanceId,
};
@@ -53,9 +50,9 @@ export function toCaveConnection(result: CaveHealth): ConnectionStatusProps {
export function toCliReceipt(result: CovenCommandResult): CommandReceiptProps {
return {
- channel: 'cli',
+ channel: "cli",
command: result.command,
- status: result.exitCode === 0 ? 'success' : 'failed',
+ status: result.exitCode === 0 ? "success" : "failed",
exitCode: result.exitCode,
duration: result.duration,
};
@@ -65,14 +62,14 @@ export function toCliReceipt(result: CovenCommandResult): CommandReceiptProps {
Then render the normalized values:
```tsx
-import { DeveloperSurface } from '@opencoven/ui';
+import { DeveloperSurface } from "@opencoven/ui";
+/>;
```
The UI package performs no import-time I/O and should not add a dependency on `@opencoven/sdk` or `@opencoven/cli`. This prevents dependency cycles, keeps package consumption lightweight, and preserves the security boundary: adapters retrieve and verify state; components present it.
diff --git a/packages/ui/src/blocks/developer-surface.tsx b/packages/ui/src/blocks/developer-surface.tsx
index a9b1517..d88de68 100644
--- a/packages/ui/src/blocks/developer-surface.tsx
+++ b/packages/ui/src/blocks/developer-surface.tsx
@@ -100,7 +100,10 @@ function DeveloperSurface({
-
+
@@ -128,8 +131,8 @@ function DeveloperSurface({
) : (
- No invocation receipts yet. Keep empty states explicit rather than
- fabricating execution history.
+ No invocation receipts yet. Keep empty states explicit rather
+ than fabricating execution history.
)}
@@ -153,9 +156,9 @@ function DeveloperSurface({
Adapter boundary
- Feed this block normalized view models from the SDK, CLI, daemon,
- runtime registry, or application state. The component performs no
- discovery or mutation on its own.
+ Feed this block normalized view models from the SDK, CLI,
+ daemon, runtime registry, or application state. The component
+ performs no discovery or mutation on its own.
diff --git a/packages/ui/src/components/connection-status.tsx b/packages/ui/src/components/connection-status.tsx
index b11fb04..f4b98d6 100644
--- a/packages/ui/src/components/connection-status.tsx
+++ b/packages/ui/src/components/connection-status.tsx
@@ -11,17 +11,9 @@ import type { ReactNode } from "react";
import { cn } from "@opencoven/ui/lib/utils";
type ConnectionState =
- | "connected"
- | "pending"
- | "degraded"
- | "disconnected"
- | "unavailable";
+ "connected" | "pending" | "degraded" | "disconnected" | "unavailable";
-type AuthorityLevel =
- | "read-only"
- | "proposal"
- | "mutating"
- | "local-authority";
+type AuthorityLevel = "read-only" | "proposal" | "mutating" | "local-authority";
type ConnectionStatusProps = {
name: string;
diff --git a/registry/developer/registry.fragment.json b/registry/developer/registry.fragment.json
index 4d5ed1f..4db8893 100644
--- a/registry/developer/registry.fragment.json
+++ b/registry/developer/registry.fragment.json
@@ -16,7 +16,13 @@
}
],
"meta": {
- "states": ["connected", "pending", "degraded", "disconnected", "unavailable"],
+ "states": [
+ "connected",
+ "pending",
+ "degraded",
+ "disconnected",
+ "unavailable"
+ ],
"authority": ["read-only", "proposal", "mutating", "local-authority"]
}
},
diff --git a/scripts/developer-visual-review.mjs b/scripts/developer-visual-review.mjs
index 2352741..18a34f5 100644
--- a/scripts/developer-visual-review.mjs
+++ b/scripts/developer-visual-review.mjs
@@ -49,11 +49,7 @@ const commonArgs = [
];
const developerUrl = new URL("/developer", baseUrl).href;
-const dom = await runChrome([
- ...commonArgs,
- "--dump-dom",
- developerUrl,
-]);
+const dom = await runChrome([...commonArgs, "--dump-dom", developerUrl]);
for (const requiredText of [
"OpenCoven development context",
@@ -66,7 +62,9 @@ for (const requiredText of [
"Recent invocations",
]) {
if (!dom.stdout.includes(requiredText)) {
- throw new Error(`Developer surface is missing rendered text: ${requiredText}`);
+ throw new Error(
+ `Developer surface is missing rendered text: ${requiredText}`,
+ );
}
}
diff --git a/scripts/verify-contracts.mjs b/scripts/verify-contracts.mjs
index a91fed8..51ffa2f 100644
--- a/scripts/verify-contracts.mjs
+++ b/scripts/verify-contracts.mjs
@@ -51,7 +51,10 @@ const assertions = [
],
[
"developer UI does not depend on SDK or CLI runtimes",
- ![...Object.keys(manifest.dependencies), ...Object.keys(manifest.peerDependencies)].some(
+ ![
+ ...Object.keys(manifest.dependencies),
+ ...Object.keys(manifest.peerDependencies),
+ ].some(
(name) =>
name === "@opencoven/sdk" ||
name === "@opencoven/sdk-core" ||
@@ -139,7 +142,9 @@ const assertions = [
[
"developer surface remains presentation only",
developerSurface.includes("Presentation does not imply permission") &&
- developerSurface.includes("performs no discovery or mutation on its own") &&
+ developerSurface.includes(
+ "performs no discovery or mutation on its own",
+ ) &&
!developerSurface.includes("@opencoven/sdk") &&
!developerSurface.includes("@opencoven/cli"),
],
From 79ece5b3bef3ffb8e6818740a0fd59805d02fd4a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:22:38 -0500
Subject: [PATCH 25/92] chore(ci): remove one-shot developer formatter
---
.../format-developer-surface-once.yml | 56 -------------------
1 file changed, 56 deletions(-)
delete mode 100644 .github/workflows/format-developer-surface-once.yml
diff --git a/.github/workflows/format-developer-surface-once.yml b/.github/workflows/format-developer-surface-once.yml
deleted file mode 100644
index 4f88a90..0000000
--- a/.github/workflows/format-developer-surface-once.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Format developer surface once
-
-on:
- push:
- branches:
- - feat/developer-surface-system
-
-permissions:
- contents: write
-
-jobs:
- format:
- if: github.actor != 'github-actions[bot]'
- runs-on: ubuntu-latest
- timeout-minutes: 10
- steps:
- - uses: actions/checkout@v4
- - uses: pnpm/action-setup@v4
- with:
- version: 10.17.1
- - uses: actions/setup-node@v4
- with:
- node-version: 24
- cache: pnpm
- - run: pnpm install --frozen-lockfile
- - name: Format changed developer-surface files
- run: |
- pnpm exec prettier --write \
- apps/specimens/src/developer-showcase.tsx \
- apps/specimens/src/main.tsx \
- apps/specimens/src/specimens-fixes.css \
- docs/developer-surface.md \
- packages/ui/src/blocks/developer-surface.tsx \
- packages/ui/src/components/connection-status.tsx \
- registry/developer/registry.fragment.json \
- scripts/developer-visual-review.mjs \
- scripts/verify-contracts.mjs
- - name: Commit formatting if needed
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add \
- apps/specimens/src/developer-showcase.tsx \
- apps/specimens/src/main.tsx \
- apps/specimens/src/specimens-fixes.css \
- docs/developer-surface.md \
- packages/ui/src/blocks/developer-surface.tsx \
- packages/ui/src/components/connection-status.tsx \
- registry/developer/registry.fragment.json \
- scripts/developer-visual-review.mjs \
- scripts/verify-contracts.mjs
- if git diff --cached --quiet; then
- exit 0
- fi
- git commit -m "style(ui): format developer surface"
- git push origin HEAD:feat/developer-surface-system
From 682abded6adb0711fe54eb2ccd3d0165ff6e0722 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:23:49 -0500
Subject: [PATCH 26/92] fix(specimens): constrain assembled tabs at mobile
width
---
apps/specimens/src/specimens-fixes.css | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index e44c473..169ab9a 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -7,21 +7,32 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
}
@media (max-width: 24.375rem) {
+ .assembled-lab > [data-slot="tabs"] {
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ }
+
.assembled-lab__nav {
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
overflow-x: hidden;
}
.assembled-lab__tabs {
display: grid;
- width: 100%;
+ width: 100% !important;
min-width: 0;
+ max-width: 100%;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.assembled-lab__tabs [role="tab"] {
min-width: 0;
- padding-inline: 0.35rem;
+ padding-inline: 0.25rem;
font-size: 0.625rem;
+ white-space: normal;
}
}
From 44f084a9b8bf55832d5d25b74f4d86e13e5a817b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:27:57 -0500
Subject: [PATCH 27/92] fix(specimens): constrain assembled tab panels on
mobile
---
apps/specimens/src/specimens-fixes.css | 18 ++++++++++++++----
1 file changed, 14 insertions(+), 4 deletions(-)
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index 169ab9a..f262bc3 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -7,16 +7,21 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
}
@media (max-width: 24.375rem) {
- .assembled-lab > [data-slot="tabs"] {
+ .assembled-lab > [data-slot="tabs"],
+ .assembled-lab [data-slot="tabs-content"],
+ .assembled-lab__nav,
+ .assembled-lab__stage {
width: 100%;
min-width: 0;
max-width: 100%;
}
+ .assembled-lab [data-slot="tabs-content"],
+ .assembled-lab__stage {
+ overflow-x: hidden;
+ }
+
.assembled-lab__nav {
- width: 100%;
- min-width: 0;
- max-width: 100%;
overflow-x: hidden;
}
@@ -34,6 +39,11 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
font-size: 0.625rem;
white-space: normal;
}
+
+ .assembled-lab__stage > * {
+ min-width: 0;
+ max-width: 100%;
+ }
}
@media (prefers-reduced-motion: reduce) {
From 36d298ea7fdbf11f83b57b4224975147d60612c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:30:25 -0500
Subject: [PATCH 28/92] fix(specimens): make mobile assembled sizing border-box
---
apps/specimens/src/specimens-fixes.css | 2 ++
1 file changed, 2 insertions(+)
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index f262bc3..28376e3 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -11,6 +11,7 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
.assembled-lab [data-slot="tabs-content"],
.assembled-lab__nav,
.assembled-lab__stage {
+ box-sizing: border-box;
width: 100%;
min-width: 0;
max-width: 100%;
@@ -27,6 +28,7 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
.assembled-lab__tabs {
display: grid;
+ box-sizing: border-box;
width: 100% !important;
min-width: 0;
max-width: 100%;
From eec571d7c86e3758468901619fd0b195edd4d7ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:39:35 -0500
Subject: [PATCH 29/92] fix(tabs): honor Base UI orientation contract
---
packages/ui/src/components/ui/tabs.tsx | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/packages/ui/src/components/ui/tabs.tsx b/packages/ui/src/components/ui/tabs.tsx
index c4ec275..0cd59e5 100644
--- a/packages/ui/src/components/ui/tabs.tsx
+++ b/packages/ui/src/components/ui/tabs.tsx
@@ -14,8 +14,10 @@ function Tabs({
);
From 62c70100172222c9a68e86db37205be7fa3ae022 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:39:52 -0500
Subject: [PATCH 30/92] fix(registry): publish corrected tabs orientation
---
public/r/tabs.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/public/r/tabs.json b/public/r/tabs.json
index 9acc68c..cf9404c 100644
--- a/public/r/tabs.json
+++ b/public/r/tabs.json
@@ -14,7 +14,7 @@
"files": [
{
"path": "packages/ui/src/components/ui/tabs.tsx",
- "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Tabs({\n className,\n orientation = \"horizontal\",\n ...props\n}: TabsPrimitive.Root.Props) {\n return (\n \n );\n}\n\nconst tabsListVariants = cva(\n \"group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-[3px] text-muted-foreground group-data-vertical/tabs:flex-col\",\n {\n variants: {\n variant: {\n default: \"bg-muted\",\n line: \"gap-1 rounded-none bg-transparent\",\n },\n density: {\n default: \"min-h-8\",\n compact: \"min-h-7\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n density: \"default\",\n },\n },\n);\n\nfunction TabsList({\n className,\n variant = \"default\",\n density = \"default\",\n ...props\n}: TabsPrimitive.List.Props & VariantProps) {\n return (\n \n );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n return (\n \n );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n return (\n \n );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };\n",
+ "content": "\"use client\";\n\nimport { Tabs as TabsPrimitive } from \"@base-ui/react/tabs\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nfunction Tabs({\n className,\n orientation = \"horizontal\",\n ...props\n}: TabsPrimitive.Root.Props) {\n return (\n \n );\n}\n\nconst tabsListVariants = cva(\n \"group/tabs-list inline-flex w-fit items-center justify-center rounded-md p-[3px] text-muted-foreground data-[orientation=vertical]:flex-col\",\n {\n variants: {\n variant: {\n default: \"bg-muted\",\n line: \"gap-1 rounded-none bg-transparent\",\n },\n density: {\n default: \"min-h-8\",\n compact: \"min-h-7\",\n },\n },\n defaultVariants: {\n variant: \"default\",\n density: \"default\",\n },\n },\n);\n\nfunction TabsList({\n className,\n variant = \"default\",\n density = \"default\",\n ...props\n}: TabsPrimitive.List.Props & VariantProps) {\n return (\n \n );\n}\n\nfunction TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {\n return (\n \n );\n}\n\nfunction TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {\n return (\n \n );\n}\n\nexport { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants };\n",
"type": "registry:ui",
"target": "@ui/tabs.tsx"
}
From 6d1560cdce778b31a3d628577cd9c25355425681 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 07:40:00 -0500
Subject: [PATCH 31/92] test(tabs): lock Base UI orientation semantics
---
packages/ui/tests/tabs.test.tsx | 46 +++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
create mode 100644 packages/ui/tests/tabs.test.tsx
diff --git a/packages/ui/tests/tabs.test.tsx b/packages/ui/tests/tabs.test.tsx
new file mode 100644
index 0000000..738e1aa
--- /dev/null
+++ b/packages/ui/tests/tabs.test.tsx
@@ -0,0 +1,46 @@
+import { render, screen } from "@testing-library/react";
+
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from "@opencoven/ui/components/ui/tabs";
+
+describe("Tabs orientation", () => {
+ it("stacks horizontal tab lists above their active panel", () => {
+ const { container } = render(
+
+
+ Overview
+ Details
+
+ Overview panel
+ Details panel
+ ,
+ );
+
+ const root = container.querySelector('[data-slot="tabs"]');
+ expect(root).toHaveAttribute("data-orientation", "horizontal");
+ expect(root).toHaveClass("flex-col");
+ expect(screen.getByText("Overview panel")).toBeVisible();
+ expect(screen.queryByText("Details panel")).not.toBeInTheDocument();
+ });
+
+ it("lets Base UI expose vertical orientation directly on the list", () => {
+ const { container } = render(
+
+
+ Overview
+
+ Overview panel
+ ,
+ );
+
+ const root = container.querySelector('[data-slot="tabs"]');
+ const list = container.querySelector('[data-slot="tabs-list"]');
+ expect(root).toHaveAttribute("data-orientation", "vertical");
+ expect(root).not.toHaveClass("flex-col");
+ expect(list).toHaveAttribute("data-orientation", "vertical");
+ });
+});
From b83c80a4ac5eedb4b644b0385e8bd56ba713fee5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 08:30:02 -0500
Subject: [PATCH 32/92] test(specimens): add mobile quality receipts to
developer stack
---
.github/workflows/visual-review.yml | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/visual-review.yml b/.github/workflows/visual-review.yml
index 5204537..2d0b9d5 100644
--- a/.github/workflows/visual-review.yml
+++ b/.github/workflows/visual-review.yml
@@ -8,6 +8,7 @@ on:
- "packages/ui/**"
- "scripts/visual-review.mjs"
- "scripts/developer-visual-review.mjs"
+ - "scripts/mobile-quality-review.mjs"
workflow_dispatch:
permissions:
@@ -56,7 +57,7 @@ jobs:
run: |
set -euo pipefail
- mkdir -p artifacts/visual-review
+ mkdir -p artifacts/visual-review artifacts/mobile-quality
preview_log="$RUNNER_TEMP/specimens-preview.log"
pnpm --filter @opencoven/specimens preview \
@@ -89,11 +90,13 @@ jobs:
browser_status=$?
node scripts/developer-visual-review.mjs
developer_status=$?
+ node scripts/mobile-quality-review.mjs
+ mobile_status=$?
set -e
cp "$preview_log" artifacts/visual-review/preview.log
- if [[ "$browser_status" -ne 0 || "$developer_status" -ne 0 ]]; then
+ if [[ "$browser_status" -ne 0 || "$developer_status" -ne 0 || "$mobile_status" -ne 0 ]]; then
exit 1
fi
@@ -105,3 +108,12 @@ jobs:
path: artifacts/visual-review
if-no-files-found: error
retention-days: 14
+
+ - name: Upload mobile-quality artifact
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: specimen-mobile-quality-${{ github.event.pull_request.number || github.run_number }}
+ path: artifacts/mobile-quality
+ if-no-files-found: error
+ retention-days: 14
From 2e8996173048fc13a2d8acd640db98f2dca8e2f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 08:30:34 -0500
Subject: [PATCH 33/92] test(specimens): add mobile quality browser gate
---
scripts/mobile-quality-review.mjs | 214 ++++++++++++++++++++++++++++++
1 file changed, 214 insertions(+)
create mode 100644 scripts/mobile-quality-review.mjs
diff --git a/scripts/mobile-quality-review.mjs b/scripts/mobile-quality-review.mjs
new file mode 100644
index 0000000..102c64d
--- /dev/null
+++ b/scripts/mobile-quality-review.mjs
@@ -0,0 +1,214 @@
+import { spawn } from "node:child_process";
+import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
+import { tmpdir } from "node:os";
+import path from "node:path";
+
+const chromePath = process.env.CHROME_PATH;
+const baseUrl = process.env.BASE_URL ?? "http://127.0.0.1:4173";
+const outputDir = path.resolve(
+ process.env.MOBILE_OUTPUT_DIR ?? "artifacts/mobile-quality",
+);
+const port = Number(process.env.MOBILE_CHROME_PORT ?? 9233);
+
+if (!chromePath) throw new Error("CHROME_PATH is required");
+
+const cases = [
+ { name: "mobile-320-dark-cozy", width: 320, scheme: "dark", density: "default" },
+ { name: "mobile-375-light-compact", width: 375, scheme: "light", density: "compact" },
+ { name: "mobile-390-dark-cozy", width: 390, scheme: "dark", density: "default" },
+ { name: "mobile-430-light-cozy", width: 430, scheme: "light", density: "default" },
+ { name: "mobile-390-dark-rtl", width: 390, scheme: "dark", density: "compact", rtl: true },
+ { name: "mobile-390-dark-text-200", width: 390, scheme: "dark", density: "default", textScale: 2 },
+];
+
+const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+const profile = await mkdtemp(path.join(tmpdir(), "opencoven-mobile-quality-"));
+await mkdir(outputDir, { recursive: true });
+
+const chrome = spawn(chromePath, [
+ "--headless=new",
+ "--no-sandbox",
+ "--disable-dev-shm-usage",
+ "--disable-gpu",
+ "--hide-scrollbars",
+ `--remote-debugging-port=${port}`,
+ `--user-data-dir=${profile}`,
+ "about:blank",
+]);
+
+let socket;
+try {
+ let target;
+ for (let attempt = 0; attempt < 100; attempt += 1) {
+ try {
+ const targets = await fetch(`http://127.0.0.1:${port}/json/list`).then((response) => response.json());
+ target = targets.find((entry) => entry.type === "page");
+ if (target?.webSocketDebuggerUrl) break;
+ } catch {}
+ await sleep(100);
+ }
+ if (!target?.webSocketDebuggerUrl) throw new Error("Chrome debugging target unavailable");
+
+ socket = new WebSocket(target.webSocketDebuggerUrl);
+ await new Promise((resolve, reject) => {
+ socket.addEventListener("open", resolve, { once: true });
+ socket.addEventListener("error", reject, { once: true });
+ });
+
+ let id = 0;
+ const pending = new Map();
+ socket.addEventListener("message", (event) => {
+ const message = JSON.parse(String(event.data));
+ if (!message.id) return;
+ const waiter = pending.get(message.id);
+ if (!waiter) return;
+ pending.delete(message.id);
+ if (message.error) waiter.reject(new Error(message.error.message));
+ else waiter.resolve(message.result ?? {});
+ });
+
+ const send = (method, params = {}) =>
+ new Promise((resolve, reject) => {
+ const requestId = ++id;
+ pending.set(requestId, { resolve, reject });
+ socket.send(JSON.stringify({ id: requestId, method, params }));
+ });
+
+ const evaluate = async (expression) => {
+ const result = await send("Runtime.evaluate", {
+ expression,
+ awaitPromise: true,
+ returnByValue: true,
+ });
+ if (result.exceptionDetails) {
+ throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text);
+ }
+ return result.result?.value;
+ };
+
+ await send("Page.enable");
+ await send("Runtime.enable");
+ await send("Emulation.setEmulatedMedia", {
+ features: [{ name: "prefers-reduced-motion", value: "reduce" }],
+ });
+
+ const results = [];
+ for (const scenario of cases) {
+ await send("Emulation.setDeviceMetricsOverride", {
+ width: scenario.width,
+ height: 900,
+ deviceScaleFactor: 1,
+ mobile: true,
+ screenWidth: scenario.width,
+ screenHeight: 900,
+ });
+
+ await send("Page.navigate", { url: new URL("/", baseUrl).href });
+ await sleep(250);
+ await evaluate(`(() => {
+ localStorage.setItem("coven-ui:scheme", ${JSON.stringify(scenario.scheme)});
+ localStorage.setItem("coven-ui:density", ${JSON.stringify(scenario.density)});
+ return true;
+ })()`);
+ await send("Page.navigate", { url: new URL("/", baseUrl).href });
+ await sleep(500);
+
+ const measurement = await evaluate(`(async () => {
+ await document.fonts.ready;
+ document.documentElement.dir = ${JSON.stringify(scenario.rtl ? "rtl" : "ltr")};
+ const existingScale = document.querySelector("#mobile-quality-text-scale");
+ existingScale?.remove();
+ ${scenario.textScale ? `const scale = document.createElement("style"); scale.id = "mobile-quality-text-scale"; scale.textContent = "html { font-size: ${scenario.textScale * 100}% !important; }"; document.head.append(scale);` : ""}
+ await new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+
+ const root = document.documentElement;
+ const cards = [...document.querySelectorAll(".specimen-card")];
+ const stages = [...document.querySelectorAll(".specimen-stage")];
+ const cardTabRoots = cards.map((card) => card.querySelector(':scope > [data-slot="tabs"]')).filter(Boolean);
+ const cardLists = cardTabRoots.map((tabs) => tabs.querySelector(':scope > [data-slot="tabs-list"]')).filter(Boolean);
+ const activePanels = cardTabRoots.map((tabs) => tabs.querySelector(':scope > [data-slot="tabs-content"]')).filter(Boolean);
+ const transcript = document.querySelector("#transcript-turn [data-slot=\"transcript-turn\"]");
+ const session = document.querySelector("#session-header [data-slot=\"session-header\"]");
+ const sessionTitle = session?.querySelector("strong");
+ const clipped = (element) => element ? Math.max(0, element.scrollWidth - element.clientWidth) : 0;
+ const rect = (element) => element?.getBoundingClientRect();
+
+ return {
+ viewport: root.clientWidth,
+ documentOverflow: Math.max(0, root.scrollWidth - root.clientWidth),
+ cardCount: cards.length,
+ maxCardOverflow: Math.max(0, ...cards.map(clipped)),
+ maxStageOverflow: Math.max(0, ...stages.map(clipped)),
+ maxTabRootOverflow: Math.max(0, ...cardTabRoots.map(clipped)),
+ minTabHeight: Math.min(...cardLists.flatMap((list) => [...list.querySelectorAll('[role="tab"]')].map((tab) => rect(tab).height))),
+ stackedTabs: cardLists.every((list, index) => {
+ const listRect = rect(list);
+ const panelRect = rect(activePanels[index]);
+ return listRect && panelRect && panelRect.top >= listRect.bottom - 1;
+ }),
+ fullWidthTabs: cardLists.every((list, index) => {
+ const listRect = rect(list);
+ const rootRect = rect(cardTabRoots[index]);
+ return listRect && rootRect && Math.abs(listRect.width - rootRect.width) <= 1;
+ }),
+ transcriptOverflow: clipped(transcript),
+ sessionOverflow: clipped(session),
+ sessionTitleEllipsized: sessionTitle ? getComputedStyle(sessionTitle).textOverflow === "ellipsis" : null,
+ direction: root.dir,
+ reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches,
+ };
+ })()`);
+
+ const failures = [];
+ if (measurement.cardCount !== 16) failures.push(`expected 16 cards, got ${measurement.cardCount}`);
+ if (measurement.documentOverflow > 1) failures.push(`document overflow ${measurement.documentOverflow}px`);
+ if (measurement.maxCardOverflow > 1) failures.push(`card overflow ${measurement.maxCardOverflow}px`);
+ if (measurement.maxStageOverflow > 1) failures.push(`stage overflow ${measurement.maxStageOverflow}px`);
+ if (measurement.maxTabRootOverflow > 1) failures.push(`tab-root overflow ${measurement.maxTabRootOverflow}px`);
+ if (measurement.minTabHeight < 44) failures.push(`tab target ${measurement.minTabHeight}px < 44px`);
+ if (!measurement.stackedTabs) failures.push("card tabs are not stacked above their active panels");
+ if (!measurement.fullWidthTabs) failures.push("card tab lists do not consume the mobile content width");
+ if (measurement.transcriptOverflow > 1) failures.push(`transcript overflow ${measurement.transcriptOverflow}px`);
+ if (measurement.sessionOverflow > 1) failures.push(`session header overflow ${measurement.sessionOverflow}px`);
+ if (measurement.sessionTitleEllipsized) failures.push("session title is ellipsized on mobile");
+ if (!measurement.reducedMotion) failures.push("reduced-motion media query was not active");
+ if (scenario.rtl && measurement.direction !== "rtl") failures.push("RTL direction was not applied");
+
+ const image = await send("Page.captureScreenshot", {
+ format: "png",
+ fromSurface: true,
+ captureBeyondViewport: false,
+ });
+ const screenshot = `${scenario.name}.png`;
+ await writeFile(path.join(outputDir, screenshot), Buffer.from(image.data, "base64"));
+ results.push({ ...scenario, measurement, failures, screenshot });
+ }
+
+ const summary = {
+ generatedAt: new Date().toISOString(),
+ passed: results.every((entry) => entry.failures.length === 0),
+ results,
+ };
+ await writeFile(path.join(outputDir, "summary.json"), `${JSON.stringify(summary, null, 2)}\n`);
+ await writeFile(
+ path.join(outputDir, "README.md"),
+ [
+ "# OpenCoven UI mobile quality review",
+ "",
+ `Result: **${summary.passed ? "PASS" : "FAIL"}**`,
+ "",
+ "| Scenario | Width | Document overflow | Stage overflow | Min tab target | Result |",
+ "|---|---:|---:|---:|---:|---|",
+ ...results.map((entry) => `| ${entry.name} | ${entry.width}px | ${entry.measurement.documentOverflow}px | ${entry.measurement.maxStageOverflow}px | ${entry.measurement.minTabHeight}px | ${entry.failures.length ? entry.failures.join("; ") : "PASS"} |`),
+ "",
+ ].join("\n"),
+ );
+
+ if (!summary.passed) {
+ throw new Error(results.flatMap((entry) => entry.failures.map((failure) => `${entry.name}: ${failure}`)).join("\n"));
+ }
+} finally {
+ socket?.close();
+ chrome.kill();
+ await rm(profile, { recursive: true, force: true });
+}
From 5f0aa1190196b7784466fb9f6201570f00cba5e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 08:30:53 -0500
Subject: [PATCH 34/92] sync(specimens): inherit mobile card composition
---
apps/specimens/src/specimens-fixes.css | 90 ++++++++++++++++++++++++--
1 file changed, 85 insertions(+), 5 deletions(-)
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index 28376e3..cf90e9b 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -6,6 +6,86 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
display: none;
}
+@media (max-width: 48rem) {
+ .specimen-card > [data-slot="tabs"] {
+ width: 100%;
+ min-width: 0;
+ gap: 0;
+ }
+
+ .specimen-card > [data-slot="tabs"] > [data-slot="tabs-list"] {
+ display: grid;
+ box-sizing: border-box;
+ width: 100% !important;
+ min-width: 0;
+ max-width: 100%;
+ min-height: 2.75rem;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0;
+ margin: 0 !important;
+ border-block-end: 1px solid var(--border);
+ padding: 0 0.75rem;
+ }
+
+ .specimen-card
+ > [data-slot="tabs"]
+ > [data-slot="tabs-list"]
+ > [data-slot="tabs-trigger"] {
+ min-width: 0;
+ min-height: 2.75rem;
+ padding: 0.625rem 0.5rem;
+ }
+
+ .specimen-card > [data-slot="tabs"] > [data-slot="tabs-content"] {
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-card__header {
+ min-height: 0;
+ padding: 1rem;
+ }
+
+ .specimen-card__title {
+ margin-block-start: 0.875rem;
+ }
+
+ .specimen-card__description {
+ margin-block-start: 0.375rem;
+ line-height: 1.5;
+ }
+
+ .specimen-stage {
+ width: 100%;
+ min-width: 0;
+ min-height: 0;
+ max-width: 100%;
+ align-content: start;
+ justify-items: stretch;
+ border-block-start: 0;
+ padding: 1rem;
+ }
+
+ .specimen-stage > * {
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-documentation {
+ width: 100%;
+ min-width: 0;
+ min-height: 0;
+ max-width: 100%;
+ padding: 1rem;
+ }
+
+ .specimen-command {
+ max-width: 100%;
+ }
+}
+
@media (max-width: 24.375rem) {
.assembled-lab > [data-slot="tabs"],
.assembled-lab [data-slot="tabs-content"],
@@ -17,11 +97,6 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
max-width: 100%;
}
- .assembled-lab [data-slot="tabs-content"],
- .assembled-lab__stage {
- overflow-x: hidden;
- }
-
.assembled-lab__nav {
overflow-x: hidden;
}
@@ -42,6 +117,11 @@ body:not(:has(#group-blocks)) .specimen-rail a[href="#group-blocks"] {
white-space: normal;
}
+ .assembled-lab__stage {
+ min-height: 0;
+ align-content: start;
+ }
+
.assembled-lab__stage > * {
min-width: 0;
max-width: 100%;
From 9e1f857f19d7dbfd052e5e4455d817c0f0d52634 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Val=20Alexander=20=F0=9F=91=91?=
Date: Sun, 30 Aug 2026 08:31:07 -0500
Subject: [PATCH 35/92] sync(blocks): inherit mobile transcript contract
---
packages/ui/src/blocks/transcript-turn.tsx | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/packages/ui/src/blocks/transcript-turn.tsx b/packages/ui/src/blocks/transcript-turn.tsx
index 48fc401..5ad93dc 100644
--- a/packages/ui/src/blocks/transcript-turn.tsx
+++ b/packages/ui/src/blocks/transcript-turn.tsx
@@ -29,11 +29,11 @@ function TranscriptTurn({
-