- Five focused compositions. One shared language. A place to try the
- pieces before making them your own.
+import { ComposerDemo, RunRailDemo } from "./block-demos";
+import { CodeSnippet, HighlightedCode } from "./code-snippet";
+
+const scenes = [
+ {
+ id: "composer",
+ title: "Composer",
+ subtitle: "From intent to action",
+ icon: PenLine,
+ source: "composer",
+ tone: "presence",
+ },
+ {
+ id: "run-rail",
+ title: "Run rail",
+ subtitle: "Every step, accounted for",
+ icon: Activity,
+ source: "run-rail",
+ tone: "information",
+ },
+ {
+ id: "messages",
+ title: "Messages",
+ subtitle: "A familiar voice, with evidence",
+ icon: MessageSquare,
+ source: "transcript-turn",
+ tone: "presence",
+ },
+ {
+ id: "context",
+ title: "Context",
+ subtitle: "The right files, within reach",
+ icon: FileCode2,
+ source: "resource-row",
+ tone: "success",
+ },
+ {
+ id: "actions",
+ title: "Actions",
+ subtitle: "Small controls, clear intent",
+ icon: Wand2,
+ source: "completion-palette",
+ tone: "warning",
+ },
+ {
+ id: "cards",
+ title: "Cards",
+ subtitle: "Outcomes you can open",
+ icon: Layers3,
+ source: "session-header",
+ tone: "information",
+ },
+] as const;
+
+function Lab({ density }: { density: "default" | "compact" }) {
+ const [activeIndex, setActiveIndex] = useState(0);
+ const [actionStatus, setActionStatus] = useState(
+ "Choose an action to try its local preview.",
+ );
+ const [attached, setAttached] = useState(false);
+ const [prompt, setPrompt] = useState("Review this change.");
+ const pointerStart = useRef<{ x: number; y: number } | null>(null);
+ const scene = scenes[activeIndex]!;
+ const SceneIcon = scene.icon;
+ const navigate = (offset: number) =>
+ setActiveIndex((index) => (index + offset + scenes.length) % scenes.length);
+
+ const views = {
+ composer: ,
+ "run-rail": ,
+ messages: (
+
+
+
+
Linked to this session
+ 3 resources
+
+
+
+
+
+
+
+ Context stays explicit. File operations keep their own color and
+ label.
-
-
-
-
-
-
- {["Composer", "Messages", "Context", "Actions", "Cards"].map(
- (name) => (
-
- {name}
-
- ),
- )}
-
-
-
-
-
-
- The component source, registry item, and specimen share one
- implementation boundary. Ready when you are.
-
-
-
-
-
-
-
-
-
- Model selection, linked context, and send readiness stay
- visible without interrupting the writing flow.
-
-
-
-
- The same primitives carry a different familiar identity
- without changing their accessibility contract.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Give your next step a little context.
-
-
- setNotice("Demo: changed files attached as context.")
- }
- >
- Attach changed files
-
-
- setNotice(
- "Demo: clarify the intent, keep the scope, and verify the result.",
- )
- }
+
+ ),
+ actions: (
+
+
+ {
+ setAttached((value) => !value);
+ setActionStatus(
+ attached
+ ? "Sample context removed."
+ : "Sample changes.diff attached.",
+ );
+ }}
+ >
+
+
+ {attached ? "Remove context" : "Attach context"}
+ A sample diff, ready to reference
+
+
+ {
+ setPrompt(
+ "Review the changed files for behavior regressions. Suggest the smallest fix and explain the tradeoff.",
+ );
+ setActionStatus(
+ "Prompt expanded. Your scope stays focused on the changed files.",
+ );
+ }}
+ >
+
+
+ Clarify the prompt
+ Make the next step more specific
+
+
+
+
+ Your draft
+ {prompt}
+ {attached ? (
+ changes.diff · 8.1 KB
+ ) : null}
+
+
+ {actionStatus}
+
+
+ ),
+ cards: (
+
+ {[
+ {
+ kind: "Pull request",
+ title: "Recover attachment ingestion",
+ meta: "12 checks passed",
+ detail:
+ "The sample change keeps attachment metadata intact across retries.",
+ },
+ {
+ kind: "Proposal",
+ title: "A smaller composer",
+ meta: "Ready for your review",
+ detail:
+ "Keep the draft visible, group supporting controls, and retain one primary send action.",
+ },
+ {
+ kind: "Attachment",
+ title: "Component preview",
+ meta: "384 KB · added by Cody",
+ detail:
+ "A sample image artifact associated with the current review.",
+ },
+ {
+ kind: "Handoff",
+ title: "Deployment notes",
+ meta: "Source, registry, package",
+ detail:
+ "Build the package and publish the same component source through the registry.",
+ },
+ ].map((card) => (
+
+
+
+
- Enhance prompt
-
-
-
- {notice || "Local interaction examples. No files are accessed."}
-
-
-
-
-
- {[
- [
- "Pull request",
- "Recover attachment ingestion",
- "Checks 12 / 12",
- ],
- ["Proposal", "Merge with confidence", "Awaiting your review"],
- [
- "Attachment",
- "Components-preview.png",
- "384 KB · added by Cody",
- ],
- ["Handoff", "Deployment ledger", "7 sections"],
- ].map(([kind, title, meta]) => (
-
-
- {kind}
- {title}
- {meta}
-
-
- Example {kind?.toLowerCase()} surface composed from public
- modules.
-
-
-
- setNotice(
- `${kind}: ${title}. This is sample content in the component lab.`,
- )
- }
- >
- Inspect example
-
-
-
- ))}
-
{notice}
-
-
-
+ {card.kind}
+
+
{card.title}
+
{card.meta}
+
+ Read details
+
+
+
{card.detail}
+
+
+ ))}
-
- Example data only. These components connect to your own application
- logic.
-
-
+ ),
+ };
+
+ return (
+
+
+
+
+
+
+
+
{scene.title}
+
{scene.subtitle}
+
+
+
+
+ {scene.title}, scene
+ {String(activeIndex + 1).padStart(2, "0")} / 06
+
+
navigate(-1)}
+ >
+
+
+
navigate(1)}
+ >
+
+
+
+
+ {
+ const index = scenes.findIndex((item) => item.id === value);
+ if (index !== -1) setActiveIndex(index);
+ }}
+ className="lab-carousel"
+ >
+ {
+ pointerStart.current = null;
+ if (
+ event.pointerType !== "touch" ||
+ (event.target instanceof Element &&
+ event.target.closest(
+ "button, input, textarea, a, summary, pre, code, [role=tab]",
+ ))
+ )
+ return;
+ pointerStart.current = { x: event.clientX, y: event.clientY };
+ }}
+ onPointerCancel={() => {
+ pointerStart.current = null;
+ }}
+ onPointerUp={(event) => {
+ const start = pointerStart.current;
+ pointerStart.current = null;
+ if (!start || window.getSelection()?.toString()) return;
+ const dx = event.clientX - start.x;
+ const dy = event.clientY - start.y;
+ if (Math.abs(dx) > 60 && Math.abs(dx) > Math.abs(dy) * 1.5)
+ navigate(dx < 0 ? 1 : -1);
+ }}
+ >
+ {scenes.map((item) => (
+
+
+
+ ))}
+
+
+
+ {scenes.map((item) => (
+
+
+ {item.title}
+
+ ))}
+
+
+
+
+
);
}
+
+export { Lab };
diff --git a/apps/specimens/src/main.tsx b/apps/specimens/src/main.tsx
index 90b76dd..dbd0dfe 100644
--- a/apps/specimens/src/main.tsx
+++ b/apps/specimens/src/main.tsx
@@ -4,8 +4,23 @@ import { createRoot } from "react-dom/client";
import "@opencoven/ui/globals.css";
import "./specimens.css";
import "./specimens-fixes.css";
+import "./lab.css";
import { App } from "./app";
+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 },
+ );
+}
+
const root = document.getElementById("root");
if (!root) {
diff --git a/apps/specimens/src/specimens-fixes.css b/apps/specimens/src/specimens-fixes.css
index 2116a3f..378f841 100644
--- a/apps/specimens/src/specimens-fixes.css
+++ b/apps/specimens/src/specimens-fixes.css
@@ -1,41 +1,216 @@
-/* Focused regression guards layered after the specimen shell styles. */
-
-/* Keep the supported viewport floor stable when users enlarge root text. */
-html {
- min-width: 320px;
+/* Responsive guards for the catalog and its source panels. */
+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;
}
-/* Nested source previews must scroll themselves rather than widen the page. */
-.docs-main,
-.component-stage,
-.showcase-conversation,
-.lab-stage,
-.home-component,
-.doc-preview,
-.doc-preview [data-slot="tabs"],
-.doc-preview [data-slot="tabs-content"] {
- min-width: 0;
- max-width: 100%;
-}
+@media (max-width: 68rem) {
+ .specimen-topbar__inner {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .surface-switcher {
+ margin-inline-start: auto;
+ }
-.component-stage > *,
-.home-component-preview > *,
-.lab-stage > * {
- min-width: 0;
- max-width: 100%;
+ .specimen-topbar__actions {
+ display: flex;
+ flex: 1 0 100%;
+ flex-wrap: wrap;
+ }
+
+ .specimen-search {
+ width: auto;
+ max-width: none;
+ min-width: 7rem;
+ flex: 1 1 10rem;
+ }
}
@media (max-width: 48rem) {
- .component-stage [data-slot="session-header"],
- .lab-surface [data-slot="session-header"] {
- flex-wrap: wrap;
+ html {
+ scroll-padding-top: 1rem;
}
- .component-stage [data-slot="resource-row"],
- .lab-stage [data-slot="resource-row"] {
+
+ .specimen-topbar {
+ position: static;
+ }
+
+ .specimen-shell,
+ .specimen-main__inner,
+ .specimen-rail,
+ .specimen-card {
+ box-sizing: border-box;
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-hero,
+ .specimen-hero__copy,
+ .specimen-stats,
+ .specimen-stats div,
+ .specimen-rail > * {
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-stats :where(dt, dd),
+ .specimen-rail__context,
+ .specimen-rail__package {
overflow-wrap: anywhere;
}
- .code-block pre {
+
+ .specimen-rail__nav {
+ display: grid;
+ width: 100%;
+ min-width: 0;
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 5.5rem), 1fr));
+ }
+
+ .specimen-rail__nav a {
+ min-width: 0;
+ justify-content: center;
+ gap: min(0.375rem, 8px);
+ padding-inline: 4px;
+ }
+
+ .specimen-rail__nav small {
+ display: none;
+ }
+
+ .specimen-card > [data-slot="tabs"] {
+ width: 100%;
+ min-width: 0;
max-width: 100%;
+ }
+
+ .specimen-card > [data-slot="tabs"] > [data-slot="tabs-list"] {
+ display: grid;
+ box-sizing: border-box;
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ min-height: 2.75rem;
+ grid-template-columns: repeat(2, minmax(5.25rem, 1fr));
+ gap: 0;
overflow-x: auto;
+ border-block-end: 1px solid var(--border);
+ padding: 0 0.75rem;
+ scrollbar-width: thin;
+ }
+
+ .specimen-code-tabs [role="tab"] {
+ min-width: 0;
+ padding: 0.625rem 0.5rem;
+ }
+
+ .specimen-code-tabs [role="tab"][data-active]::after {
+ inset-block-end: 0;
+ }
+
+ .specimen-code-tabs [role="tab"]:focus-visible {
+ outline-offset: -3px;
+ }
+
+ .specimen-card__header {
+ min-height: 0;
+ padding: 16px;
+ overflow-wrap: anywhere;
+ }
+
+ .specimen-preview {
+ margin-inline: 16px;
+ }
+
+ .specimen-stage {
+ min-width: 0;
+ min-height: 10rem;
+ justify-items: stretch;
+ padding: 16px;
+ }
+
+ .specimen-stage [data-slot="composer"] {
+ padding: min(var(--density-panel), 24px);
+ }
+
+ .specimen-stage
+ :is([data-slot="button"], [data-slot="dropdown-menu-trigger"]) {
+ height: auto;
+ min-height: var(--density-control);
+ white-space: normal;
+ overflow-wrap: anywhere;
+ }
+
+ .specimen-stage > * {
+ box-sizing: border-box;
+ width: 100%;
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-stage *,
+ .specimen-documentation * {
+ min-width: 0;
+ max-width: 100%;
+ }
+
+ .specimen-stage :where(p, span, strong, small, code),
+ .specimen-documentation :where(p, span, strong, small, code) {
+ overflow-wrap: anywhere;
+ }
+
+ .specimen-documentation {
+ min-width: 0;
+ max-width: 100%;
+ padding: 0.75rem 1rem 1rem;
+ }
+}
+
+@media (max-width: 20rem) {
+ .specimen-topbar__inner {
+ gap: 0.375rem;
+ padding-block: 0.375rem;
+ }
+
+ .specimen-main__inner {
+ padding-block-start: 0.75rem;
+ }
+
+ .specimen-hero {
+ gap: 0.75rem;
+ margin-block-end: 1.25rem;
+ padding-block-end: 0.75rem;
+ }
+
+ .specimen-hero h1 {
+ font-size: 2rem;
+ }
+
+ .catalog-group__header {
+ gap: 0.375rem;
+ }
+
+ .catalog-group__count {
+ display: none;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .specimen-card:hover,
+ .specimen-card:focus-within {
+ translate: none;
+ }
+}
+
+@media (forced-colors: active) {
+ .specimen-brand__mark {
+ color: CanvasText;
+ }
+
+ .specimen-brand__mark > span {
+ forced-color-adjust: none;
}
}
diff --git a/apps/specimens/src/specimens.css b/apps/specimens/src/specimens.css
index 7dd3ec5..b057834 100644
--- a/apps/specimens/src/specimens.css
+++ b/apps/specimens/src/specimens.css
@@ -1,1470 +1,1262 @@
-:root {
- --background: #f7f6f9;
- --foreground: #242329;
- --card: #ffffff;
- --card-foreground: var(--foreground);
- --muted-foreground: #716c7b;
- --presence: #75629e;
- --presence-foreground: var(--card);
- --border: color-mix(in srgb, var(--foreground) 12%, var(--background));
- --font-numeric: var(--font-ui);
- --site-width: 1240px;
-}
-html.dark {
- --background: #111113;
- --foreground: #efedf2;
- --card: #19191c;
- --card-foreground: var(--foreground);
- --muted-foreground: #99979f;
- --presence: #baabe6;
- --presence-foreground: var(--background);
- --border: color-mix(in srgb, var(--foreground) 12%, var(--background));
-}
html {
min-width: 320px;
- scroll-padding-top: 100px;
+ --specimen-topbar-height: 3.75rem;
+ scroll-padding-top: calc(var(--specimen-topbar-height) + 1rem);
}
+
body {
min-height: 100vh;
+ overflow-x: clip;
}
-.site {
- font-size: 14px;
- line-height: 1.5;
-}
-.site a {
- text-decoration: none;
-}
-.site button:not(:disabled),
-.site select {
- cursor: pointer;
-}
-.site :where(a, button, input, select, textarea):focus-visible {
- outline: 2px solid var(--presence);
- outline-offset: 4px;
-}
-.site p {
- text-wrap: pretty;
-}
-.site h1,
-.site h2,
-.site h3 {
- text-wrap: balance;
-}
-.site em {
- font-weight: 400;
-}
+
.skip-link {
position: fixed;
- top: 12px;
- left: 12px;
+ max-width: calc(100% - 1.5rem);
+ overflow-wrap: anywhere;
+ inset-block-start: 0.75rem;
+ inset-inline-start: 0.75rem;
z-index: 100;
- transform: translateY(-180%);
+ translate: 0 -200%;
+ border-radius: var(--radius-2);
background: var(--foreground);
color: var(--background);
- padding: 12px 20px;
- border-radius: 6px;
+ padding: 0.625rem 0.875rem;
+ font-size: 0.8125rem;
+ font-weight: 700;
}
+
.skip-link:focus {
- transform: translateY(0);
+ translate: 0;
}
-.site-header {
+
+.specimen-topbar {
position: sticky;
- top: 0;
- z-index: 40;
- background: var(--background);
- border-bottom: 1px solid var(--border);
-}
-.site-header-inner {
- max-width: 1440px;
- min-height: 76px;
- margin-inline: auto;
- padding-inline: 40px;
+ inset-block-start: 0;
+ z-index: 50;
+ border-block-end: 1px solid var(--border);
+ background: color-mix(in srgb, var(--background) 92%, transparent);
+ backdrop-filter: blur(18px);
+}
+
+.specimen-topbar__inner {
display: flex;
+ flex-wrap: wrap;
+ min-height: 3.75rem;
+ max-width: 96rem;
align-items: center;
- gap: 52px;
+ gap: 0.75rem;
+ margin-inline: auto;
+ padding-inline: clamp(1rem, 3vw, 1.5rem);
}
-.site-brand {
+
+.specimen-brand {
display: inline-flex;
align-items: center;
- gap: 8px;
+ gap: var(--oc-space-2);
+ min-width: max-content;
color: var(--foreground);
- flex-shrink: 0;
+ text-decoration: none;
}
-.site-brand > svg {
- width: 34px;
- height: 34px;
- color: var(--presence);
- stroke-width: 2;
+
+.specimen-brand__mark {
+ display: grid;
+ width: 2rem;
+ height: 2rem;
+ flex: none;
+ place-items: center;
+ color: var(--foreground);
}
-.site-brand > span {
- font-size: 25px;
- font-weight: 650;
- letter-spacing: -1.3px;
+
+.specimen-brand__mark > span {
+ width: 1.875rem;
+ height: 1.875rem;
+ background: currentColor;
+ mask: url("/opencoven-mark.svg") center / contain no-repeat;
}
-.brand-ui {
+
+.specimen-brand > span:last-child {
+ display: grid;
+ font-size: 0.875rem;
+ font-weight: 750;
+ letter-spacing: -0.015em;
+ line-height: 1.1;
+ font-family: var(--font-display);
+}
+
+.specimen-brand small {
+ margin-block-start: 0.2rem;
color: var(--muted-foreground);
- font-size: 17px;
- letter-spacing: -0.7px;
- margin-left: 4px;
- font-weight: 450;
+ font-family: var(--font-numeric);
+ font-size: 0.625rem;
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
-.primary-nav {
- display: flex;
+
+.surface-switcher,
+.density-control {
+ display: inline-flex;
align-items: center;
- gap: 28px;
+ gap: 0.125rem;
+ padding: 0.1875rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-2);
+ background: var(--card);
}
-.primary-nav a {
+
+.surface-switcher a,
+.density-control button {
+ border: 0;
+ border-radius: calc(var(--radius-2) - 2px);
+ background: transparent;
color: var(--muted-foreground);
- display: inline-flex;
- align-items: center;
- gap: 7px;
+ padding: 0.375rem 0.75rem;
+ font-size: 0.75rem;
+ font-weight: 700;
+ line-height: 1.25rem;
+ text-decoration: none;
+ white-space: nowrap;
}
-.primary-nav a:hover,
-.primary-nav a[aria-current],
-.site-footer a:hover {
+
+.surface-switcher a:hover,
+.density-control button:hover {
+ background: var(--muted);
color: var(--foreground);
}
-.nav-dot,
-.release-dot {
- width: 5px;
- height: 5px;
- border-radius: 50%;
- background: var(--presence);
+
+.surface-switcher a[aria-current="page"],
+.density-control button[aria-pressed="true"] {
+ background: var(--foreground);
+ color: var(--background);
+ box-shadow: var(--elevation-1);
}
-.header-tools {
+
+.specimen-topbar__actions {
display: flex;
+ flex: 1 1 34rem;
+ flex-wrap: wrap;
+ min-width: 0;
align-items: center;
- gap: 14px;
- margin-left: auto;
+ justify-content: flex-end;
+ gap: 0.5rem;
+}
+
+.specimen-search {
+ width: min(20rem, 34vw);
+ max-width: 20rem;
+ flex: 1 1 16rem;
+}
+
+.scheme-control {
+ flex: none;
+ white-space: nowrap;
}
-.icon-link {
- display: inline-flex;
- align-items: center;
- color: var(--muted-foreground);
- padding: 6px;
+
+.scheme-control svg {
+ width: 0.875rem;
+ height: 0.875rem;
}
-.icon-link svg {
- width: 18px;
- height: 18px;
+
+.specimen-shell {
+ display: grid;
+ max-width: 96rem;
+ min-height: calc(100vh - var(--specimen-topbar-height));
+ grid-template-columns: 13.5rem minmax(0, 1fr);
+ margin-inline: auto;
}
-.site-search {
- position: relative;
- display: flex;
- align-items: center;
- gap: 9px;
- width: 212px;
- height: 34px;
- border: 1px solid var(--border);
- border-radius: 6px;
- background: var(--card);
- color: var(--muted-foreground);
- padding-inline: 10px;
+
+.specimen-rail {
+ position: sticky;
+ inset-block-start: var(--specimen-topbar-height);
+ align-self: start;
+ height: calc(100vh - var(--specimen-topbar-height));
+ overflow-y: auto;
+ border-inline-end: 1px solid var(--border);
+ background: color-mix(in srgb, var(--card) 72%, var(--background));
+ padding: 1.5rem 0.875rem;
+ scrollbar-width: thin;
}
-.site-search > svg {
- width: 14px;
- height: 14px;
- flex-shrink: 0;
+
+.specimen-rail__context {
+ padding-inline: 0.625rem;
}
-.site-search input {
- min-width: 0;
- width: 100%;
- background: transparent;
- border: none;
- color: var(--foreground);
- outline: none;
- font-size: 14px;
+
+.specimen-rail__context h2 {
+ margin: 0.4rem 0 0;
+ font-size: 1rem;
+ font-weight: 750;
+ letter-spacing: -0.015em;
}
-.site-search input::placeholder {
+
+.specimen-rail__context p:last-child {
+ margin: 0.625rem 0 0;
color: var(--muted-foreground);
+ font-size: 0.75rem;
+ line-height: 1.55;
}
-.site-search:focus-within {
- outline: 2px solid var(--presence);
- outline-offset: 2px;
-}
-.site-search input:focus-visible {
- outline: none;
-}
-.site-search kbd {
- white-space: nowrap;
- font: inherit;
- font-size: 14px;
-}
-.search-results {
- position: absolute;
- right: 0;
- top: 44px;
- width: 320px;
- max-height: 440px;
- overflow-y: auto;
- padding: 8px;
- border: 1px solid var(--border);
- border-radius: 10px;
- background: var(--card);
- color: var(--foreground);
- box-shadow: var(--elevation-2);
+
+.specimen-kicker {
+ margin: 0;
+ color: var(--presence);
+ font-size: 0.625rem;
+ font-weight: 750;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
}
-.search-heading {
- display: block;
- color: var(--muted-foreground);
- padding: 8px;
+
+.specimen-rail__nav {
+ display: grid;
+ gap: 0.25rem;
+ margin-block-start: 1.25rem;
}
-.search-result {
+
+.specimen-rail__nav a {
display: flex;
- justify-content: space-between;
align-items: center;
- padding: 10px;
- border-radius: 5px;
+ justify-content: space-between;
+ gap: 0.75rem;
+ border-radius: var(--radius-2);
+ color: var(--muted-foreground);
+ padding: 0.5rem 0.625rem;
+ font-size: 0.8125rem;
+ font-weight: 650;
+ text-decoration: none;
}
-.search-result:hover,
-.search-result:focus-visible {
+
+.specimen-rail__nav a:hover {
background: var(--muted);
+ color: var(--foreground);
}
-.search-result svg {
- width: 14px;
- height: 14px;
-}
-.search-results > p {
- padding: 12px;
-}
-.mobile-nav-button {
- display: none;
+
+.specimen-rail__nav small {
+ color: color-mix(in srgb, var(--muted-foreground) 76%, transparent);
+ font-size: 0.625rem;
}
-.home-main {
- max-width: var(--site-width);
- margin-inline: auto;
- padding-inline: 40px;
+
+.specimen-rail__package {
+ margin-block-start: 1.5rem;
+ border-block-start: 1px solid var(--border);
+ padding: 1rem 0.625rem 0;
}
-.home-hero {
- display: flex;
- flex-direction: column;
- align-items: center;
- text-align: center;
- padding-block: 78px 60px;
+
+.specimen-rail__package code {
+ display: block;
+ overflow: hidden;
+ margin-block-start: 0.5rem;
+ color: var(--foreground);
+ font-size: 0.75rem;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-.release-note {
- display: inline-flex;
- align-items: center;
- gap: 10px;
+
+.specimen-rail__package p {
+ margin: 0.625rem 0 0;
color: var(--muted-foreground);
- font-size: 14px;
+ font-size: 0.7rem;
+ line-height: 1.5;
}
-.release-note svg {
- width: 14px;
- height: 14px;
+
+.specimen-main {
+ min-width: 0;
}
-.release-divider {
- height: 13px;
- width: 1px;
- background: var(--border);
+
+.specimen-main__inner {
+ width: min(100%, 76rem);
+ margin-inline: auto;
+ padding: var(--oc-space-8) var(--oc-space-8) var(--oc-space-16);
}
-.home-hero h1 {
- font-size: clamp(56px, 7.6vw, 96px);
- font-weight: 450;
- letter-spacing: -5px;
- line-height: 1.07;
- margin: 30px 0 24px;
+
+.specimen-hero {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(15rem, 20rem);
+ align-items: center;
+ gap: 1.5rem;
+ margin-block-end: clamp(2rem, 4vw, 3rem);
+ border-block-end: 1px solid var(--border);
+ padding-block-end: clamp(1.5rem, 3vw, 2rem);
+}
+
+.specimen-hero__copy {
+ max-width: 52rem;
+}
+
+.specimen-hero h1 {
+ max-width: 19ch;
+ overflow-wrap: anywhere;
+ margin: 0.5rem 0 0;
+ font-family: var(--font-display);
+ font-size: var(--oc-type-4);
+ font-weight: 700;
+ letter-spacing: var(--oc-tracking-heading);
+ line-height: var(--oc-leading-heading);
+ color: var(--foreground);
+ text-wrap: balance;
}
-.home-hero h1 em {
+
+.specimen-hero h1 em {
color: var(--presence);
- letter-spacing: -4px;
-}
-.hero-description {
- color: var(--muted-foreground);
- font-size: 17px;
- line-height: 1.7;
- margin: 0;
- max-width: 620px;
+ font-style: normal;
}
-.hero-actions {
- display: flex;
- align-items: center;
- gap: 12px;
- padding-top: 29px;
+
+.specimen-stats div:nth-child(2) dd {
+ color: var(--information);
}
-.hero-cta {
- height: 42px;
- padding-inline: 20px;
- border-radius: 7px;
- font-size: 14px;
+
+.specimen-stats div:nth-child(3) dd {
+ color: var(--success);
}
-.hero-install {
- padding-top: 22px;
- max-width: 100%;
-}
-.hero-foundations {
- display: flex;
- gap: 22px;
- align-items: center;
+
+.specimen-hero__copy > p:last-child {
+ max-width: var(--oc-reading-measure);
+ margin: var(--oc-space-4) 0 0;
color: var(--muted-foreground);
- padding-top: 18px;
- font-size: 14px;
-}
-.hero-foundations span {
- display: inline-flex;
- align-items: center;
- gap: 5px;
+ font-size: var(--oc-type--1);
+ line-height: var(--oc-leading-reading);
}
-.hero-foundations svg {
- width: 13px;
- height: 13px;
- color: var(--presence);
+
+.specimen-stats {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 0.5rem;
+ margin: 0;
}
-.code-block {
- border: 1px solid var(--border);
- border-radius: 8px;
- background: var(--card);
- color: var(--foreground);
- overflow: hidden;
- max-width: 100%;
+
+.specimen-stats div {
min-width: 0;
- text-align: left;
+ border-inline-start: 1px solid var(--border);
+ padding-inline-start: 0.875rem;
}
-.code-block__bar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 12px;
- border-bottom: 1px solid var(--border);
+
+.specimen-stats dt {
color: var(--muted-foreground);
+ font-size: 0.625rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.specimen-stats dd {
+ margin: 0.25rem 0 0;
+ color: var(--foreground);
+ font-size: 1.25rem;
+ font-weight: 650;
}
-.code-block__bar > span {
- display: flex;
- align-items: center;
- gap: 8px;
- font-size: 14px;
+
+.catalog {
+ display: grid;
+ gap: clamp(2.75rem, 5vw, 4.5rem);
}
-.code-block__bar svg {
- width: 14px;
- height: 14px;
+
+.catalog-group {
+ --section-color: var(--presence);
+ scroll-margin-block-start: 0;
}
-.code-block pre {
- margin: 0;
- padding: 20px;
- overflow-x: auto;
- font-size: 14px;
- line-height: 1.7;
- tab-size: 2;
+
+#group-run-rail {
+ --section-color: var(--information);
}
-.code-block code {
- white-space: pre;
+
+#group-blocks {
+ --section-color: var(--success);
}
-.code-block--compact {
+
+.catalog-group__header {
display: flex;
- align-items: center;
- flex-direction: row-reverse;
- background: transparent;
- border-color: transparent;
-}
-.code-block--compact .code-block__bar {
- padding: 0;
- border: none;
+ align-items: end;
+ justify-content: space-between;
+ gap: 2rem;
+ margin-block-end: 0.875rem;
}
-.code-block--compact .code-block__bar > span {
- display: none;
+
+.catalog-group__eyebrow {
+ margin: 0;
+ color: var(--section-color);
+ font-size: 0.625rem;
+ font-weight: 750;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+}
+
+.catalog-group__header h2 {
+ max-width: 34rem;
+ margin: 0.35rem 0 0;
+ font-size: clamp(1.25rem, 2vw, 1.6rem);
+ font-weight: 700;
+ letter-spacing: -0.025em;
+ line-height: 1.2;
+ color: var(--section-color);
+ text-wrap: balance;
}
-.code-block--compact pre {
- padding: 4px 8px;
+
+.catalog-group__summary {
+ max-width: 52ch;
+ margin: 0.35rem 0 0;
color: var(--muted-foreground);
- font-size: 14px;
-}
-.copy-feedback {
- display: block;
- padding: 8px 12px;
- color: var(--presence);
+ font-size: 0.75rem;
+ line-height: 1.5;
}
-.section-overline {
- display: flex;
- justify-content: space-between;
- align-items: center;
+
+.catalog-group__count {
+ flex: none;
color: var(--muted-foreground);
- font-size: 14px;
- padding-bottom: 15px;
+ font-size: 0.6875rem;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
}
-.section-overline > span:last-child {
- display: flex;
- gap: 8px;
- align-items: center;
-}
-.section-overline svg {
- width: 14px;
- height: 14px;
+
+.specimen-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr);
+ gap: var(--oc-space-8);
}
-.agent-showcase {
+
+.specimen-card {
+ display: flex;
+ flex-direction: column;
+ min-width: 0;
+ overflow: hidden;
+ scroll-margin-block-start: 0;
border: 1px solid var(--border);
- border-radius: 12px;
+ border-radius: var(--radius-4);
background: var(--card);
- overflow: hidden;
- box-shadow: 0 12px 48px color-mix(in srgb, var(--background) 80%, transparent);
+ color: var(--card-foreground);
+ box-shadow: var(--elevation-1);
+ transition:
+ border-color var(--motion-standard) var(--motion-ease),
+ box-shadow var(--motion-standard) var(--motion-ease),
+ translate var(--motion-standard) var(--motion-ease);
+}
+
+.specimen-card:hover,
+.specimen-card:focus-within {
+ border-color: color-mix(in srgb, var(--section-color) 48%, var(--border));
+ box-shadow: var(--elevation-2);
}
-.showcase-title {
+
+.specimen-card__header {
+ min-height: 8.25rem;
+ padding: var(--oc-space-6) var(--oc-space-6) var(--oc-space-4);
+}
+
+.specimen-card__meta {
display: flex;
- justify-content: space-between;
+ flex-wrap: wrap;
align-items: center;
- padding: 17px 24px;
- border-bottom: 1px solid var(--border);
+ justify-content: space-between;
+ gap: 1rem;
}
-.showcase-title > span:first-child {
+
+.specimen-card__kind {
display: inline-flex;
align-items: center;
- gap: 9px;
- font-weight: 500;
-}
-.coven-glyph {
- width: 20px;
- height: 20px;
- color: var(--presence);
+ gap: 0.45rem;
+ color: var(--section-color);
+ font-size: 0.625rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.specimen-card__kind svg {
+ width: 0.875rem;
+ height: 0.875rem;
+}
+
+.specimen-card__title {
+ margin: 0.75rem 0 0;
+ color: color-mix(in srgb, var(--section-color) 55%, var(--foreground));
+ font-family: var(--font-display);
+ font-size: var(--oc-type-1);
+ font-weight: 650;
+ letter-spacing: var(--oc-tracking-heading);
}
-.showcase-workspace {
- display: grid;
- grid-template-columns: minmax(0, 1fr) 270px;
+
+.specimen-card__description {
+ max-width: 46ch;
+ margin: var(--oc-space-2) 0 0;
+ color: var(--muted-foreground);
+ font-size: var(--oc-type--1);
+ line-height: var(--oc-leading-reading);
}
-.showcase-conversation {
+
+.specimen-preview {
+ position: relative;
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ min-width: 0;
+ margin: 0 var(--oc-space-4) var(--oc-space-4);
+ overflow: hidden;
+ border: 1px solid color-mix(in srgb, var(--section-color) 24%, var(--border));
+ border-radius: var(--radius-2);
+ background: var(--oc-surface-2);
+ box-shadow: inset 0 1px 0
+ color-mix(in srgb, var(--section-color) 18%, transparent);
+}
+
+.specimen-preview__tabs {
+ flex: 1;
min-width: 0;
- padding: 24px 28px 12px;
+ gap: 0;
}
-.showcase-context {
- display: flex;
- align-items: center;
- gap: 7px;
- color: var(--muted-foreground);
- font-size: 14px;
- padding-bottom: 28px;
+
+.specimen-view-tabs {
+ width: 100%;
+ gap: var(--oc-space-1);
+ border-block-end: 1px solid var(--border);
+ border-radius: 0;
+ background: var(--card);
+ padding: var(--oc-space-2);
+ justify-content: flex-start;
+ overflow-x: auto;
+ scrollbar-width: thin;
}
-.showcase-context svg {
- width: 14px;
- height: 14px;
+
+.specimen-view-tabs [role="tab"] {
+ flex: 0 0 auto;
+ min-width: 0;
+ min-height: 2.75rem;
+ gap: var(--oc-space-2);
+ padding-inline: var(--oc-space-4);
+ font-size: 0.8125rem;
+}
+
+.specimen-view-tabs [role="tab"][data-active] {
+ border-color: var(--border);
+ background: var(--oc-surface-2);
+ color: var(--presence);
+ box-shadow: inset 0 -2px 0 var(--presence);
}
-.showcase-context span {
- margin-left: auto;
+
+.specimen-view-tabs svg {
+ width: 0.875rem;
+ height: 0.875rem;
}
-.showcase-plan {
- padding-block: 16px 24px;
+
+.specimen-view-tabs [role="tab"]:focus-visible {
+ outline-offset: -3px;
+}
+
+.specimen-preview__canvas {
+ position: relative;
+ display: grid;
+ flex: 1;
+ min-width: 0;
}
-.showcase-composer {
+
+.specimen-live-panel {
+ display: grid;
min-width: 0;
}
-.showcase-footnote {
+
+.specimen-source-overlay {
+ position: absolute;
+ inset: 0;
display: flex;
- align-items: center;
- justify-content: space-between;
- color: var(--muted-foreground);
- font-size: 14px;
- padding-top: 8px;
+ box-sizing: border-box;
+ min-width: 0;
+ min-height: 0;
+ padding: 0.75rem;
+ background: var(--card);
}
-.showcase-evidence {
- border-left: 1px solid var(--border);
+
+.specimen-source-overlay .specimen-code-snippet {
display: flex;
+ flex: 1;
flex-direction: column;
- padding: 28px 24px;
- background: color-mix(in srgb, var(--card) 55%, var(--background));
+ min-width: 0;
+ min-height: 0;
}
-.eyebrow {
- color: var(--muted-foreground);
- font-size: 14px;
- letter-spacing: 0.01em;
-}
-.showcase-evidence h3 {
- font-family: var(--font-editorial);
- font-size: 26px;
- font-weight: 400;
- line-height: 1.35;
- margin: 16px 0 30px;
-}
-.evidence-ownership {
- font-size: 14px;
- line-height: 1.7;
- color: var(--muted-foreground);
- padding-bottom: 24px;
+
+.specimen-source-overlay .specimen-code-snippet__header {
+ flex: none;
}
-.evidence-files {
- padding-top: 24px;
+
+.specimen-source-overlay .specimen-command {
+ flex: 1;
+ min-height: 0;
+ max-height: none;
+ overscroll-behavior: contain;
}
-.evidence-files > span {
- display: block;
- color: var(--muted-foreground);
- padding-bottom: 12px;
+
+.specimen-source-overlay .code-copy-error {
+ flex: none;
}
-.evidence-note {
- margin-top: auto;
- padding-top: 32px;
- display: flex;
- align-items: center;
- gap: 12px;
+
+.specimen-stage {
+ display: grid;
+ flex: 1;
+ min-height: 14rem;
+ align-content: center;
+ justify-items: center;
+ padding: var(--oc-space-6);
+}
+
+.specimen-stage > * {
+ min-width: 0;
+ max-width: min(100%, 48rem);
+}
+
+.composer-demo {
+ width: 100%;
+ min-width: 0;
+}
+
+.demo-status {
+ margin: 0.75rem 0 0;
color: var(--muted-foreground);
+ font-size: 0.6875rem;
+ line-height: 1.5;
}
-.evidence-note svg {
- width: 25px;
- height: 25px;
- color: var(--presence);
+
+.specimen-stage > .composer-demo,
+.specimen-stage > [data-slot="card"] {
+ width: 100%;
+}
+
+.specimen-stage > [data-slot="composer"],
+.specimen-stage > [data-slot="session-header"] {
+ width: 100%;
+}
+
+.specimen-stage > [data-slot="run-rail"] {
+ width: min(100%, 48rem);
}
-.evidence-note p {
- font-size: 14px;
+
+.specimen-stage > [data-slot="transcript-turn"] {
+ width: min(100%, 42rem);
+}
+
+.specimen-documentation {
+ display: grid;
+ min-height: 7.5rem;
+ align-content: start;
+ gap: var(--oc-space-3);
+ padding: var(--oc-space-3) var(--oc-space-4) var(--oc-space-4);
+ font-size: 0.8125rem;
line-height: 1.6;
}
-.showcase-caption {
- display: flex;
- justify-content: space-between;
- padding-top: 16px;
- font-size: 14px;
- color: var(--muted-foreground);
+
+.specimen-states {
+ display: grid;
+ gap: var(--oc-space-2);
+ border-block-start: 1px solid var(--border);
+ padding: var(--oc-space-4);
+ font-size: 0.6875rem;
}
-.showcase-caption a,
-.section-heading > a {
+
+.specimen-states > span {
display: inline-flex;
align-items: center;
- gap: 8px;
-}
-.showcase-caption svg,
-.section-heading > a svg {
- width: 16px;
- height: 16px;
+ gap: var(--oc-space-2);
+ color: var(--muted-foreground);
+ font-weight: 600;
}
-.home-library {
- padding-top: 106px;
+
+.specimen-states svg {
+ width: 0.875rem;
+ height: 0.875rem;
}
-.section-heading {
+.specimen-states ul {
display: flex;
- justify-content: space-between;
- align-items: flex-end;
- padding-bottom: 28px;
-}
-.section-heading h2 {
- font-size: 36px;
- letter-spacing: -1.3px;
- font-weight: 450;
- margin: 12px 0;
+ flex-wrap: wrap;
+ gap: var(--oc-space-2);
+ margin: 0;
+ padding: 0;
+ list-style: none;
}
-.section-heading p:last-child {
- color: var(--muted-foreground);
+.specimen-states li {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-1);
+ background: var(--oc-surface-2);
+ color: var(--oc-text-muted);
+ padding: var(--oc-space-1) var(--oc-space-2);
+}
+
+.specimen-card > [data-slot="tabs"] {
+ flex: none;
+ gap: 0;
+ border-block-start: 1px solid var(--border);
+ background: color-mix(in srgb, var(--background) 70%, var(--card));
+}
+
+.specimen-code-tabs {
margin: 0;
+ padding: 0.375rem 0.75rem;
+}
+
+@media (min-width: 64rem) {
+ .specimen-card__header {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1.25fr);
+ align-items: center;
+ column-gap: var(--oc-space-8);
+ row-gap: var(--oc-space-4);
+ padding: var(--oc-space-6);
+ }
+
+ .specimen-card__meta {
+ grid-column: 1 / -1;
+ }
+
+ .specimen-card__title,
+ .specimen-card__description {
+ margin: 0;
+ }
+
+ .specimen-card__description {
+ max-width: 60ch;
+ justify-self: end;
+ }
+
+ .specimen-preview {
+ margin-inline: var(--oc-space-6);
+ margin-block-end: var(--oc-space-6);
+ }
+
+ .specimen-stage {
+ padding: var(--oc-space-8);
+ }
+
+ .specimen-stage > [data-slot="run-rail"] {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: var(--oc-space-6);
+ }
+
+ .specimen-stage > [data-slot="run-rail"] > div {
+ min-width: 0;
+ }
+
+ .specimen-stage > [data-slot="run-rail"] > div:first-child {
+ grid-column: 1 / -1;
+ }
+
+ .specimen-card > [data-slot="tabs"] {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ align-items: start;
+ gap: var(--oc-space-6);
+ padding-inline: var(--oc-space-6);
+ }
+
+ .specimen-code-tabs {
+ margin-block-start: var(--oc-space-4);
+ padding-inline: 0;
+ }
+
+ .specimen-documentation {
+ min-height: 0;
+ padding: var(--oc-space-4) 0;
+ }
+
+ .specimen-states {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: var(--oc-space-4);
+ padding-inline: var(--oc-space-6);
+ }
}
-.section-heading > a {
- flex-shrink: 0;
- font-size: 14px;
- padding-bottom: 4px;
+
+.specimen-code-tabs [role="tab"] {
+ min-height: 2.75rem;
+ font-size: 0.6875rem;
}
-.home-component-grid {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 16px;
+
+.specimen-code-tabs [role="tab"][data-active] {
+ color: var(--section-color);
}
-.home-component {
- border: 1px solid var(--border);
- border-radius: 9px;
- background: var(--card);
- min-width: 0;
- overflow: hidden;
+
+.specimen-code-tabs [role="tab"][data-active]::after {
+ background: var(--section-color);
}
-.home-component-preview {
- display: flex;
- min-height: 195px;
- justify-content: center;
- align-items: center;
- padding: 20px;
- overflow: auto;
+
+.specimen-documentation p {
+ margin: 0;
}
-.home-component-preview [data-slot="plan-row"] {
- font-size: 14px;
+
+.specimen-code-snippet {
+ display: grid;
+ gap: 0.4rem;
}
-.home-component > a {
+
+.specimen-code-snippet__header {
display: flex;
align-items: center;
justify-content: space-between;
- border-top: 1px solid var(--border);
- padding: 15px 20px;
- font-size: 14px;
-}
-.home-component > a:hover {
- color: var(--presence);
- background: var(--muted);
+ gap: 0.75rem;
}
-.home-component > a svg {
- width: 16px;
- height: 16px;
+
+.specimen-code-snippet__header span {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.4rem;
color: var(--muted-foreground);
+ font-size: 0.6rem;
+ font-weight: 750;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.specimen-code-snippet__header svg {
+ width: 0.75rem;
+ height: 0.75rem;
+}
+
+.specimen-code-snippet__header button {
+ min-height: 2.75rem;
+ font-size: 0.625rem;
+}
+
+.code-copy-error {
+ color: var(--destructive);
+ font-size: 0.75rem;
+}
+
+.specimen-command {
+ display: block;
+ max-height: 22rem;
+ overflow: auto;
+ scrollbar-width: thin;
+ margin: 0;
+ overflow-wrap: anywhere;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-2);
+ background: var(--background);
+ color: var(--foreground);
+ padding: 0.75rem;
+ font-size: 0.7rem;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ word-break: break-word;
}
-.principles {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 45px;
- padding-block: 80px;
-}
-.principles svg {
- width: 22px;
- height: 22px;
- color: var(--presence);
-}
-.principles h3 {
- font-size: 16px;
- font-weight: 500;
- margin: 16px 0 10px;
-}
-.principles p {
- font-size: 14px;
- color: var(--muted-foreground);
- line-height: 1.7;
- margin: 0;
-}
-.home-closing {
- border-top: 1px solid var(--border);
- text-align: center;
- padding-block: 70px 90px;
-}
-.home-closing h2 {
- font-size: 55px;
- line-height: 1.14;
- letter-spacing: -2px;
- font-weight: 450;
- margin: 22px 0 32px;
-}
-.home-closing em {
- color: var(--presence);
-}
-.site-footer {
- max-width: 1440px;
- padding: 28px 40px;
- margin-inline: auto;
- display: flex;
- align-items: center;
- gap: 24px;
- border-top: 1px solid var(--border);
-}
-.site-footer .site-brand > span {
- font-size: 21px;
-}
-.site-footer .site-brand > svg {
- width: 27px;
- height: 27px;
-}
-.site-footer > p {
- color: var(--muted-foreground);
- font-size: 14px;
-}
-.site-footer nav {
- display: flex;
- align-items: center;
- gap: 24px;
- margin-left: auto;
- color: var(--muted-foreground);
- font-size: 14px;
-}
-.site-footer nav a {
- display: inline-flex;
- gap: 5px;
- align-items: center;
-}
-.site-footer nav svg {
- width: 14px;
- height: 14px;
-}
-.docs-layout {
- display: grid;
- grid-template-columns: 220px minmax(0, 1fr) 170px;
- max-width: 1440px;
- margin-inline: auto;
- padding-inline: 40px;
- gap: 48px;
- min-height: 80vh;
-}
-.docs-sidebar {
- position: sticky;
- top: 76px;
- align-self: start;
- height: calc(100vh - 76px);
- overflow-y: auto;
- padding-block: 32px;
- padding-right: 16px;
- scrollbar-width: thin;
- scrollbar-color: var(--border) transparent;
-}
-.sidebar-group {
- display: flex;
- flex-direction: column;
- padding-bottom: 24px;
-}
-.sidebar-group h2 {
- font-size: 14px;
- font-weight: 600;
- padding-inline: 10px;
- margin: 0 0 8px;
-}
-.sidebar-group a {
- padding: 7px 10px;
- color: var(--muted-foreground);
- border-radius: 5px;
- display: flex;
- align-items: center;
- justify-content: space-between;
-}
-.sidebar-group a:hover {
+
+.hljs {
color: var(--foreground);
+ font-family: var(--font-numeric);
}
-.sidebar-group a[aria-current] {
+
+.hljs-keyword,
+.hljs-built_in,
+.hljs-selector-tag,
+.hljs-meta {
color: var(--presence);
- background: color-mix(in srgb, var(--presence) 9%, transparent);
-}
-.sidebar-group a span {
- font-size: 14px;
-}
-.sidebar-lab {
- display: flex;
- align-items: center;
- gap: 10px;
- border: 1px solid var(--border);
- border-radius: 8px;
- padding: 14px;
-}
-.sidebar-lab > svg {
- width: 16px;
- height: 16px;
- flex-shrink: 0;
-}
-.sidebar-lab small {
- display: block;
- color: var(--muted-foreground);
- font-size: 14px;
- margin-top: 4px;
-}
-.docs-main {
- min-width: 0;
- padding-block: 36px 80px;
-}
-.breadcrumbs {
- display: flex;
- flex-wrap: wrap;
- gap: 10px;
- color: var(--muted-foreground);
- font-size: 14px;
- margin: 0 0 32px;
-}
-.breadcrumbs a:hover {
- color: var(--foreground);
-}
-.docs-heading {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
-}
-.docs-heading h1 {
- font-size: clamp(36px, 4.1vw, 52px);
- font-weight: 450;
- letter-spacing: -2px;
- line-height: 1.13;
- margin: 20px 0 16px;
-}
-.docs-heading > p {
- color: var(--muted-foreground);
- font-size: 16px;
- line-height: 1.65;
- margin: 0;
- max-width: 640px;
-}
-.source-link {
- display: inline-flex;
- align-items: center;
- gap: 5px;
- color: var(--muted-foreground);
- font-size: 14px;
-}
-.docs-heading .source-link {
- margin-top: 18px;
}
-.source-link svg {
- width: 14px;
- height: 14px;
-}
-.doc-preview {
- border: 1px solid var(--border);
- border-radius: 10px;
- margin-top: 28px;
- overflow: hidden;
-}
-.preview-toolbar {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 16px;
- border-bottom: 1px solid var(--border);
-}
-.density-select {
- display: flex;
- align-items: center;
- gap: 7px;
+
+.hljs-selector-class,
+.hljs-selector-pseudo,
+.hljs-selector-id,
+.hljs-attribute {
+ color: var(--information);
+}
+
+.hljs-string,
+.hljs-regexp,
+.hljs-template-tag {
+ color: var(--success);
+}
+
+.hljs-title,
+.hljs-name,
+.hljs-attr,
+.hljs-property,
+.hljs-type {
+ color: var(--information);
+}
+
+.hljs-number,
+.hljs-literal,
+.hljs-variable,
+.hljs-template-variable,
+.hljs-params {
+ color: var(--warning);
+}
+
+.hljs-comment,
+.hljs-punctuation {
color: var(--muted-foreground);
- font-size: 14px;
}
-.density-select svg {
- width: 14px;
- height: 14px;
-}
-.density-select select {
- background: var(--background);
- color: var(--foreground);
- border-radius: 5px;
+
+p > .hljs {
border: 1px solid var(--border);
- padding: 4px;
- font: inherit;
-}
-.component-stage {
- min-height: 280px;
- display: flex;
- align-items: center;
- justify-content: center;
- padding: 40px;
- min-width: 0;
-}
-.component-stage > * {
- max-width: 100%;
-}
-.preview-caption {
- display: flex;
- justify-content: space-between;
- padding: 10px 16px;
- color: var(--muted-foreground);
- font-size: 14px;
- background: var(--card);
- border-top: 1px solid var(--border);
-}
-.doc-preview .code-block {
- border: none;
- border-radius: 0;
-}
-.doc-preview .code-block pre {
- max-height: 560px;
-}
-.doc-section {
- padding-top: 40px;
- min-width: 0;
-}
-.doc-section h2 {
- font-size: 24px;
- font-weight: 500;
- letter-spacing: -0.7px;
- margin: 0 0 16px;
-}
-.doc-section p {
- color: var(--muted-foreground);
- font-size: 15px;
- line-height: 1.75;
- margin: 0 0 20px;
-}
-.doc-section p a {
- color: var(--foreground);
- text-decoration: underline;
- text-underline-offset: 3px;
-}
-.doc-section p code {
- font-size: 14px;
- color: var(--presence);
+ border-radius: var(--radius-1);
+ background: var(--muted);
+ padding: 0.1em 0.3em;
+ color: var(--information);
+ font-size: 0.8em;
overflow-wrap: anywhere;
}
-.doc-section > .code-block {
- margin-bottom: 24px;
-}
-.state-list {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 8px;
- padding-block: 12px;
-}
-.state-list > span:first-child {
- color: var(--muted-foreground);
- padding-right: 8px;
-}
-.docs-contents {
- position: sticky;
- top: 116px;
- align-self: start;
- display: flex;
- flex-direction: column;
- gap: 12px;
- padding-top: 40px;
+
+.specimen-install-meta {
+ display: grid;
+ grid-template-columns: auto minmax(0, 1fr);
+ gap: 0.75rem;
color: var(--muted-foreground);
- font-size: 14px;
}
-.docs-contents > span {
- color: var(--foreground);
- font-weight: 500;
-}
-.docs-contents a:hover {
+
+.specimen-install-meta span {
color: var(--presence);
+ font-size: 0.625rem;
+ font-weight: 750;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
+.specimen-install-meta svg {
+ width: 1rem;
+ height: 1rem;
+}
+
+.run-spine {
+ position: relative;
}
-.docs-contents > div {
- border-top: 1px solid var(--border);
- padding-top: 24px;
- margin-top: 16px;
-}
-.docs-contents > div > svg {
- width: 20px;
- height: 20px;
- color: var(--presence);
-}
-.docs-contents > div p {
- margin-block: 12px;
-}
-.docs-contents > div a {
- display: inline-flex;
- align-items: center;
- gap: 3px;
-}
-.docs-contents > div a svg {
- width: 14px;
- height: 14px;
-}
-.catalog-section {
- padding-top: 40px;
-}
-.catalog-section > h2 {
- font-size: 20px;
- font-weight: 500;
- display: flex;
- align-items: center;
- gap: 12px;
- padding-bottom: 16px;
-}
-.catalog-section > h2 > span {
- color: var(--muted-foreground);
- font-size: 14px;
+
+.run-spine::before {
+ position: absolute;
+ inset-block: 0.5rem;
+ inset-inline-start: 1.1rem;
+ width: 1px;
+ background: color-mix(in srgb, var(--presence) 38%, var(--border));
+ content: "";
}
-.docs-catalog-grid {
+
+.catalog-empty {
display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
+ min-height: 24rem;
+ place-items: center;
+ align-content: center;
+ border: 1px dashed var(--border);
+ border-radius: var(--radius-3);
+ padding: 2rem;
+ text-align: center;
}
-.catalog-tile {
- display: block;
+
+.catalog-empty__mark {
+ display: grid;
+ width: 2.75rem;
+ height: 2.75rem;
+ place-items: center;
border: 1px solid var(--border);
- border-radius: 8px;
- padding: 20px;
- background: var(--card);
- transition: border-color 150ms;
-}
-.catalog-tile:hover {
- border-color: var(--presence);
-}
-.catalog-tile > div {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: 12px;
-}
-.catalog-tile h3 {
- font-size: 16px;
- font-weight: 500;
-}
-.catalog-tile svg {
- width: 15px;
- height: 15px;
- color: var(--muted-foreground);
- flex-shrink: 0;
-}
-.catalog-tile p {
- font-size: 14px;
- color: var(--muted-foreground);
- margin-top: 10px;
- line-height: 1.6;
-}
-.docs-pagination {
- display: flex;
- justify-content: space-between;
- border-top: 1px solid var(--border);
- padding-top: 24px;
- margin-top: 48px;
-}
-.docs-pagination a {
- display: flex;
- align-items: center;
- gap: 10px;
+ border-radius: var(--radius-2);
+ color: var(--presence);
}
-.docs-pagination a:last-child {
- text-align: right;
+
+.catalog-empty__mark svg {
+ width: 1rem;
+ height: 1rem;
}
-.docs-pagination svg {
- width: 16px;
- height: 16px;
+
+.catalog-empty h2 {
+ margin: 1rem 0 0;
+ font-size: 1.125rem;
}
-.docs-pagination small {
- display: block;
+
+.catalog-empty p {
+ margin: 0.4rem 0 0;
color: var(--muted-foreground);
- font-size: 14px;
+ font-size: 0.8125rem;
}
-.docs-mobile-toggle {
- display: none;
-}
-.intro-callout {
- display: flex;
- align-items: flex-start;
- gap: 16px;
+
+.assembled-lab {
+ overflow: hidden;
+ scroll-margin-block-start: 0;
border: 1px solid var(--border);
- border-radius: 8px;
- padding: 24px;
+ border-radius: var(--radius-3);
background: var(--card);
- margin-top: 32px;
-}
-.intro-callout svg {
- width: 22px;
- height: 22px;
- flex-shrink: 0;
- color: var(--presence);
-}
-.intro-callout p {
- margin: 0;
- color: var(--muted-foreground);
- font-size: 15px;
- line-height: 1.7;
-}
-.guide-links {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 12px;
- padding-bottom: 36px;
-}
-.guide-links a {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- gap: 8px;
- border: 1px solid var(--border);
- padding: 20px;
- border-radius: 8px;
-}
-.guide-links p {
- margin: 0;
- font-size: 14px;
-}
-.guide-links svg {
- width: 16px;
- height: 16px;
-}
-.guide-layer {
- padding-block: 16px;
-}
-.guide-layer h3 {
- font-size: 16px;
- font-weight: 500;
- padding-bottom: 8px;
+ color: var(--card-foreground);
+ box-shadow: var(--elevation-2);
}
-.token-swatches {
- display: grid;
- grid-template-columns: repeat(4, minmax(0, 1fr));
- gap: 12px;
- padding-bottom: 24px;
+
+.assembled-lab__nav {
+ overflow-x: auto;
+ border-block-end: 1px solid var(--border);
+ padding: 0.75rem 1rem 0;
}
-.token-swatches > div {
+
+.assembled-lab__tabs {
+ width: max-content;
min-width: 0;
}
-.token-swatches span {
- display: block;
- height: 70px;
- border-radius: 7px;
- border: 1px solid var(--border);
+
+.assembled-lab__tabs [data-slot="tabs-trigger"] {
+ flex: none;
}
-.token-swatches code {
- display: block;
- font-size: 14px;
- color: var(--muted-foreground);
- padding-top: 8px;
- overflow-wrap: anywhere;
+
+.assembled-lab [data-slot="tabs"],
+.assembled-lab__stage,
+.lab-composer,
+.lab-message-stack {
+ min-width: 0;
}
-.lab-main {
- max-width: 1100px;
- padding: 64px 40px 80px;
+
+.assembled-lab__stage {
+ display: grid;
+ min-height: 27rem;
+ align-content: center;
+ background: color-mix(in srgb, var(--muted) 42%, var(--background));
+ padding: clamp(1rem, 3vw, 2rem);
+}
+
+.lab-composer,
+.lab-message-stack {
+ display: grid;
+ width: min(100%, 48rem);
+ gap: 1.25rem;
margin-inline: auto;
- min-height: 80vh;
-}
-.lab-main .docs-heading em {
- color: var(--presence);
-}
-.lab-surface {
- border: 1px solid var(--border);
- border-radius: 12px;
- margin-top: 40px;
- overflow: hidden;
-}
-.lab-tabs {
- border-bottom: 1px solid var(--border);
- padding: 12px 20px;
- overflow-x: auto;
-}
-.lab-stage {
- display: flex;
- flex-direction: column;
- gap: 28px;
- padding: 40px;
- min-height: 280px;
}
+
.lab-card-grid {
display: grid;
+ width: min(100%, 52rem);
grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 20px;
- padding: 32px;
-}
-.lab-caption {
- color: var(--muted-foreground);
- text-align: center;
- margin-top: 24px;
-}
-@media (min-width: 1400px) {
- .home-hero {
- padding-top: 105px;
- padding-bottom: 80px;
- }
-}
-@media (max-width: 1199px) {
- .site-header-inner {
- gap: 32px;
- padding-inline: 32px;
- }
- .docs-layout {
- grid-template-columns: 190px minmax(0, 1fr);
- gap: 32px;
- padding-inline: 32px;
- }
- .docs-contents {
- display: none;
- }
- .site-footer {
- flex-wrap: wrap;
- }
- .site-footer nav {
- gap: 16px;
- }
+ gap: 0.875rem;
+ margin-inline: auto;
}
-@media (max-width: 1000px) {
- .site-header-inner {
- gap: 28px;
- }
- .primary-nav {
- gap: 20px;
- }
- .site-search {
- width: 175px;
- }
- .home-main {
- padding-inline: 32px;
- }
- .showcase-workspace {
- grid-template-columns: minmax(0, 1fr) 225px;
- }
- .showcase-evidence {
- padding-inline: 20px;
- }
- .home-component-grid {
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
- .site-footer > p {
- display: none;
- }
+
+.assembled-lab__stage > [data-slot="card"] {
+ width: min(100%, 48rem);
+ margin-inline: auto;
}
-@media (max-width: 767px) {
- .site-header-inner {
- min-height: 64px;
- padding-inline: 20px;
- gap: 20px;
- }
- .site-brand > span {
- font-size: 23px;
- }
- .site-brand > svg {
- width: 28px;
- height: 28px;
- }
- .header-tools {
- gap: 6px;
+
+@media (max-width: 68rem) {
+ .specimen-topbar__inner {
+ grid-template-columns: auto 1fr;
+ padding-block: 0.75rem;
}
- .site-search {
- width: 160px;
+
+ .surface-switcher {
+ justify-self: end;
}
- .site-search kbd {
- display: none;
+
+ .specimen-topbar__actions {
+ display: grid;
+ width: 100%;
+ grid-column: 1 / -1;
+ grid-template-columns: minmax(0, 1fr) auto auto;
}
- .github-link {
- display: none;
+
+ .specimen-search {
+ width: 100%;
}
- .mobile-nav-button {
- display: inline-flex;
+
+ .specimen-shell {
+ min-height: 0;
+ grid-template-columns: 1fr;
}
- .primary-nav {
+
+ .specimen-rail {
+ position: static;
+ display: block;
+ height: auto;
+ overflow-x: auto;
+ border-inline-end: 0;
+ border-block-end: 1px solid var(--border);
+ padding: 0.5rem clamp(1rem, 4vw, 2rem);
+ }
+
+ .specimen-rail__context,
+ .specimen-rail__package {
display: none;
}
- .primary-nav[data-open="true"] {
- position: absolute;
- top: 64px;
- left: 0;
- right: 0;
+
+ .specimen-rail__nav {
display: flex;
- flex-direction: column;
- align-items: stretch;
- padding: 20px;
- background: var(--card);
- border-bottom: 1px solid var(--border);
- }
- .primary-nav a {
- padding: 8px;
- }
- .home-main {
- padding-inline: 20px;
- }
- .home-hero {
- padding-block: 62px 46px;
- }
- .home-hero h1 {
- font-size: clamp(44px, 8.8vw, 65px);
- letter-spacing: -2.7px;
- line-height: 1.13;
- margin-top: 28px;
- }
- .home-hero h1 em {
- letter-spacing: -2.4px;
- }
- .hero-description {
- font-size: 15px;
- max-width: 440px;
- }
- .release-note {
- gap: 7px;
- font-size: 14px;
- }
- .hero-cta {
- padding-inline: 15px;
- }
- .hero-foundations {
- gap: 12px;
- flex-wrap: wrap;
- justify-content: center;
- }
- .hero-install {
+ width: max-content;
+ min-width: 100%;
+ gap: 0.375rem;
+ margin: 0;
+ }
+
+ .specimen-rail__nav a {
+ min-width: 9rem;
+ flex: 1 0 auto;
+ border: 1px solid transparent;
+ background: color-mix(in srgb, var(--card) 72%, var(--background));
+ }
+
+ .specimen-main__inner {
+ padding-block-start: clamp(1.5rem, 4vw, 2.5rem);
+ }
+}
+
+@media (max-width: 48rem) {
+ .specimen-hero {
+ grid-template-columns: 1fr;
+ align-items: start;
+ }
+
+ .specimen-stats {
width: 100%;
}
- .code-block--compact pre {
- min-width: 0;
- font-size: 14px;
- }
- .section-overline {
- font-size: 14px;
- }
- .section-overline > span:last-child {
- display: none;
+
+ .specimen-hero {
+ gap: 1.25rem;
+ margin-block-end: 2rem;
+ padding-block-end: 1.25rem;
}
- .showcase-title {
- padding: 14px 16px;
+
+ .catalog-group__header {
+ align-items: start;
}
- .showcase-title > span:first-child {
- font-size: 14px;
- }
- .showcase-title [data-slot="badge"] {
- display: none;
+
+ .specimen-grid {
+ grid-template-columns: 1fr;
}
- .showcase-workspace {
- grid-template-columns: minmax(0, 1fr);
+
+ .specimen-card__header {
+ min-height: 0;
}
- .showcase-evidence {
- display: none;
- }
- .showcase-conversation {
- padding: 18px 18px 10px;
- }
- .showcase-context {
- font-size: 14px;
+}
+
+@media (max-width: 36rem) {
+ .specimen-topbar__inner {
+ gap: 0.75rem;
}
- .showcase-context span {
+
+ .specimen-brand small {
display: none;
}
- .showcase-footnote {
- font-size: 14px;
+
+ .specimen-topbar__actions {
+ grid-template-columns: minmax(0, 1fr) auto auto;
}
- .showcase-caption {
- gap: 16px;
- font-size: 14px;
+
+ .specimen-search {
+ grid-column: auto;
}
- .showcase-caption > span {
+
+ .scheme-control span {
display: none;
}
- .home-library {
- padding-top: 64px;
- }
- .section-heading {
- flex-direction: column;
- align-items: flex-start;
- gap: 20px;
+
+ .specimen-main__inner {
+ padding-inline: 1rem;
}
- .section-heading h2 {
- font-size: 30px;
+
+ .specimen-hero {
+ margin-block-end: 2rem;
}
- .home-component-grid {
- gap: 12px;
+
+ .specimen-hero h1 {
+ font-size: clamp(2.25rem, 11vw, 3.25rem);
}
- .home-component-preview {
- padding: 16px;
- min-height: 190px;
+
+ .catalog {
+ gap: 3rem;
}
- .principles {
- grid-template-columns: minmax(0, 1fr);
- gap: 32px;
- padding-block: 54px;
+
+ .catalog-group__header {
+ display: grid;
+ gap: 0.75rem;
}
- .home-closing {
- padding-block: 48px 64px;
+
+ .catalog-group__count {
+ justify-self: start;
}
- .home-closing h2 {
- font-size: 43px;
+
+ .specimen-stage,
+ .assembled-lab__stage {
+ padding: 1rem;
}
- .site-footer {
- padding: 24px 20px;
- gap: 20px;
- }
- .site-footer nav {
- margin-left: 0;
- flex-wrap: wrap;
- }
- .site-footer nav > span {
- display: none;
+
+ .assembled-lab__stage {
+ min-height: 24rem;
}
- .docs-layout {
- display: flex;
- flex-direction: column;
- gap: 0;
- padding-inline: 20px;
+
+ .lab-card-grid {
+ grid-template-columns: 1fr;
}
- .docs-mobile-toggle {
- display: block;
- padding-top: 20px;
+}
+
+@media (max-width: 24.375rem) {
+ html {
+ scroll-padding-top: 1rem;
}
- .docs-sidebar {
- display: none;
+
+ .specimen-topbar {
position: static;
- height: auto;
- max-height: 65vh;
- padding-top: 24px;
- padding-right: 0;
- }
- .docs-sidebar[data-open="true"] {
- display: block;
- }
- .docs-main {
- padding-top: 28px;
- }
- .docs-heading h1 {
- font-size: 40px;
}
- .component-stage {
- min-height: 240px;
- padding: 24px 16px;
- }
- .preview-caption {
- font-size: 14px;
- }
- .preview-caption span {
+
+ .specimen-brand > span:last-child {
display: none;
}
- .docs-catalog-grid {
- grid-template-columns: minmax(0, 1fr);
+
+ .specimen-brand__mark {
+ width: 2rem;
+ height: 2rem;
}
- .lab-main {
- padding: 40px 20px 60px;
+
+ .surface-switcher a,
+ .density-control button {
+ padding-inline: 0.45rem;
+ font-size: 0.6875rem;
}
- .lab-stage {
- padding: 24px 16px;
+
+ .specimen-topbar__actions {
+ gap: 0.375rem;
}
- .lab-card-grid {
- padding: 20px 16px;
- grid-template-columns: minmax(0, 1fr);
- }
-}
-@media (max-width: 479px) {
- .site-search {
- width: 130px;
- padding-inline: 8px;
- gap: 6px;
+
+ .specimen-hero h1 {
+ font-size: 2rem;
}
- .site-header-inner {
+
+ .specimen-main__inner {
+ padding-block-start: 16px;
padding-inline: 16px;
}
- .site-brand > span {
- font-size: 21px;
- }
- .brand-ui {
- font-size: 14px;
- }
- .header-tools {
- gap: 2px;
- }
- .home-main {
- padding-inline: 20px;
- }
- .home-hero h1 {
- font-size: 43px;
+
+ .specimen-hero {
+ gap: 1rem;
+ margin-block-end: 1.5rem;
}
- .release-note > .release-divider,
- .release-note > .release-divider + span {
- display: none;
- }
- .desktop-break {
- display: none;
- }
- .hero-actions {
- gap: 8px;
- }
- .hero-cta {
- padding-inline: 12px;
- font-size: 14px;
+
+ .specimen-hero__copy > p:last-child {
+ font-size: 0.8125rem;
+ line-height: 1.5;
}
- .home-component-grid {
- grid-template-columns: minmax(0, 1fr);
- }
- .guide-links {
- grid-template-columns: minmax(0, 1fr);
- }
- .token-swatches {
- grid-template-columns: repeat(2, minmax(0, 1fr));
+
+ .specimen-stats div {
+ min-width: 0;
+ padding-inline-start: 0.625rem;
}
- .search-results {
- position: fixed;
- top: 60px;
- left: 16px;
- right: 16px;
- width: auto;
+
+ .specimen-stats dt {
+ font-size: 0.55rem;
}
- .docs-pagination {
- gap: 12px;
+
+ .specimen-stage {
+ min-height: 14rem;
+ padding: 0.875rem;
}
}
-@media (max-width: 379px) {
- .site-header-inner {
- gap: 8px;
- }
- .site-search {
- width: 96px;
+
+@media (prefers-reduced-motion: reduce) {
+ .skip-link {
+ transition: none;
}
- .home-hero h1 {
- font-size: 37px;
- }
- .hero-actions {
- flex-direction: column;
- align-items: stretch;
- width: 100%;
- }
- .release-note {
- flex-wrap: wrap;
- justify-content: center;
+
+ .specimen-card {
+ translate: none;
}
}
-@media (prefers-reduced-motion: no-preference) {
- .home-hero {
- animation: appear 650ms ease-out both;
- }
- .showcase-section {
- animation: appear 650ms 100ms ease-out both;
+
+@media (forced-colors: active) {
+ .surface-switcher a[aria-current="page"],
+ .density-control button[aria-pressed="true"] {
+ border: 1px solid Highlight;
}
- @keyframes appear {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
+}
+
+::selection {
+ background: color-mix(in srgb, var(--presence) 32%, transparent);
+ color: var(--foreground);
+}
+
+@media (prefers-contrast: more) {
+ .specimen-card,
+ .specimen-rail,
+ .specimen-topbar {
+ background: var(--card);
+ backdrop-filter: none;
}
}
diff --git a/packages/ui/src/blocks/composer.tsx b/packages/ui/src/blocks/composer.tsx
index d373773..bfc138e 100644
--- a/packages/ui/src/blocks/composer.tsx
+++ b/packages/ui/src/blocks/composer.tsx
@@ -1,6 +1,7 @@
"use client";
import * as React from "react";
+import { Bot, CornerDownLeft, PenLine } from "lucide-react";
import { AttachmentChip } from "@opencoven/ui/components/attachment-chip";
import {
@@ -28,6 +29,7 @@ type ComposerProps = {
running?: boolean;
onStop?: () => void;
model?: string;
+ tools?: React.ReactNode;
density?: "default" | "compact";
className?: string;
};
@@ -43,29 +45,62 @@ function Composer({
running = false,
onStop,
model = "GPT-5.6 Sol",
+ tools,
density = "default",
className,
}: ComposerProps) {
+ const messageId = React.useId();
+ const hintId = React.useId();
+ const modeHint = {
+ chat: "Explore ideas without changing files.",
+ do: "Make changes with the context you provide.",
+ plan: "Map the approach before making changes.",
+ }[mode];
+
return (
-
+
+
+
+ Compose
+
+
+
+ {model}
+
+
+
Message
);
}
diff --git a/packages/ui/src/blocks/run-rail.tsx b/packages/ui/src/blocks/run-rail.tsx
index e5e9cb8..a729bfc 100644
--- a/packages/ui/src/blocks/run-rail.tsx
+++ b/packages/ui/src/blocks/run-rail.tsx
@@ -8,8 +8,20 @@ import {
MetricDisplay,
type MetricDisplayProps,
} from "@opencoven/ui/components/metric-display";
+import { PlanRow, type PlanRowProps } from "@opencoven/ui/components/plan-row";
+import {
+ ResourceRow,
+ type ResourceRowProps,
+} from "@opencoven/ui/components/resource-row";
import { Card } from "@opencoven/ui/components/ui/card";
import { cn } from "@opencoven/ui/lib/utils";
+import {
+ Activity,
+ ChartNoAxesCombined,
+ Files,
+ ListChecks,
+ ShieldCheck,
+} from "lucide-react";
type RunRailProps = {
metrics: MetricDisplayProps[];
@@ -23,6 +35,8 @@ type RunRailProps = {
used: number;
limit: number;
};
+ plan?: PlanRowProps[];
+ resources?: ResourceRowProps[];
density?: "default" | "compact";
className?: string;
};
@@ -32,6 +46,8 @@ function RunRail({
activity,
context,
budget,
+ plan = [],
+ resources = [],
density = "default",
className,
}: RunRailProps) {
@@ -40,21 +56,47 @@ function RunRail({
data-slot="run-rail"
data-density={density}
aria-label="Run status"
- className={cn("grid content-start gap-4", className)}
+ className={cn("grid min-w-0 content-start gap-4", className)}
>
-
- Run
+
+
+ Run overview
-
+
{metrics.map((metric) => (
))}
+ {plan.length > 0 ? (
+
+
+
+ Plan
+
+ {plan.filter((step) => step.status === "complete").length} /{" "}
+ {plan.length}
+
+
+
+ {plan.map((step, index) => (
+
+ ))}
+
+
+ ) : null}
-
+
+
Activity
+
+ {activity.some((item) => item.running) ? "Running" : "Idle"}
+
{activity.map((item, index) => (
@@ -66,8 +108,27 @@ function RunRail({
))}
+ {resources.length > 0 ? (
+
+
+
+ Changed files
+ {resources.length}
+
+
+ {resources.map((resource) => (
+
+ ))}
+
+
+ ) : null}
-
+
+
Limits
diff --git a/packages/ui/src/components/completion-palette.tsx b/packages/ui/src/components/completion-palette.tsx
index fc3caa4..8d1fe19 100644
--- a/packages/ui/src/components/completion-palette.tsx
+++ b/packages/ui/src/components/completion-palette.tsx
@@ -34,7 +34,7 @@ function CompletionPalette({
return (
-
+
{label}
{commands.map((command) => (
diff --git a/packages/ui/src/components/plan-row.tsx b/packages/ui/src/components/plan-row.tsx
index e79fa3f..2068721 100644
--- a/packages/ui/src/components/plan-row.tsx
+++ b/packages/ui/src/components/plan-row.tsx
@@ -30,7 +30,12 @@ function PlanRow({
className,
)}
>
-
+
Send
-
-
-
+ {onOpenOptions ? (
+
+
+
+ ) : null}
);
}
diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx
index 6f4ae7b..4a4cc30 100644
--- a/packages/ui/src/components/ui/button.tsx
+++ b/packages/ui/src/components/ui/button.tsx
@@ -11,7 +11,7 @@ const buttonVariants = cva(
variants: {
variant: {
primary:
- "bg-primary text-primary-foreground hover:bg-primary/85 active:translate-y-px",
+ "bg-primary text-primary-foreground hover:bg-[var(--oc-action-hover)] active:translate-y-px",
presence:
"bg-presence text-presence-foreground hover:bg-presence/85 active:translate-y-px",
outline:
diff --git a/packages/ui/src/styles/globals.css b/packages/ui/src/styles/globals.css
index 84cc7d2..8559295 100644
--- a/packages/ui/src/styles/globals.css
+++ b/packages/ui/src/styles/globals.css
@@ -54,31 +54,82 @@
:root {
color-scheme: light;
- /* Zinc seeds the neutral structure; these named variables are the contract. */
- --background: #f5f4f8;
- --foreground: #1c1b21;
- --card: #ffffff;
- --card-foreground: #1c1b21;
- --popover: #ffffff;
- --popover-foreground: #1c1b21;
- --primary: #27272a;
- --primary-foreground: #fafafa;
- --secondary: #eeedf3;
- --secondary-foreground: #27272a;
- --muted: #eeedf3;
- --muted-foreground: #65616d;
- --accent: #e4e2ea;
- --accent-foreground: #27272a;
- --presence: #6b5bbf;
- --presence-foreground: #ffffff;
- --destructive: #b3505e;
- --destructive-foreground: #ffffff;
- --success: #3e7d5c;
- --warning: #8a6a24;
- --information: #35618a;
- --border: #d8d6e0;
- --input: #d8d6e0;
- --ring: #6b5bbf;
+ /* OpenCoven/brand web profile v1.0.0, pinned to 798e2f5f2f1a69f5d156ccc0a9aafc4f7da55fc8. */
+ --oc-bg: #fcfafe;
+ --oc-surface-1: #ffffff;
+ --oc-surface-2: #f6f0fa;
+ --oc-surface-3: #ece3f2;
+ --oc-text: #1a1320;
+ --oc-text-muted: #5d5265;
+ --oc-text-subtle: #74667e;
+ --oc-border: #d8cce0;
+ --oc-border-strong: #b9a8c4;
+ --oc-action: #7a22ee;
+ --oc-action-hover: #6410cc;
+ --oc-on-action: #ffffff;
+ --oc-focus: #6e19da;
+ --oc-presence: #5e2a8a;
+ --oc-on-presence: #ffffff;
+ --oc-success-bg: #e7f6ec;
+ --oc-success-text: #17683a;
+ --oc-pending-bg: #fff4d3;
+ --oc-pending-text: #704b00;
+ --oc-blocked-bg: #fdebec;
+ --oc-blocked-text: #8a1c1c;
+ --oc-evidence-bg: #f2edf5;
+ --oc-evidence-text: #4a3f51;
+ --oc-font-display: Geist, Inter, "SF Pro Display", system-ui, sans-serif;
+ --oc-font-reading: Inter, "SF Pro Text", system-ui, sans-serif;
+ --oc-font-mono:
+ "SFMono-Regular", ui-monospace, Menlo, Monaco, Consolas, monospace;
+ --oc-type--1: clamp(0.875rem, 0.84rem + 0.15vw, 0.95rem);
+ --oc-type-0: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);
+ --oc-type-1: clamp(1.2rem, 1.08rem + 0.55vw, 1.5rem);
+ --oc-type-2: clamp(1.5rem, 1.28rem + 1vw, 2rem);
+ --oc-type-3: clamp(2rem, 1.55rem + 2vw, 3rem);
+ --oc-type-4: clamp(2.75rem, 1.9rem + 3.8vw, 5rem);
+ --oc-leading-reading: 1.62;
+ --oc-leading-heading: 1.05;
+ --oc-reading-measure: 68ch;
+ --oc-technical-measure: 80ch;
+ --oc-tracking-heading: -0.035em;
+ --oc-tracking-label: 0.12em;
+ --oc-space-1: 0.25rem;
+ --oc-space-2: 0.5rem;
+ --oc-space-3: 0.75rem;
+ --oc-space-4: 1rem;
+ --oc-space-6: 1.5rem;
+ --oc-space-8: 2rem;
+ --oc-space-12: 3rem;
+ --oc-space-16: 4rem;
+ --oc-space-24: 6rem;
+ --oc-shadow-1: 0 1px 2px rgb(26 19 32 / 8%);
+ --oc-shadow-2: 0 12px 32px rgb(26 19 32 / 12%);
+
+ --background: var(--oc-bg);
+ --foreground: var(--oc-text);
+ --card: var(--oc-surface-1);
+ --card-foreground: var(--oc-text);
+ --popover: var(--oc-surface-1);
+ --popover-foreground: var(--oc-text);
+ --primary: var(--oc-action);
+ --primary-foreground: var(--oc-on-action);
+ --secondary: var(--oc-surface-2);
+ --secondary-foreground: var(--oc-text);
+ --muted: var(--oc-surface-2);
+ --muted-foreground: var(--oc-text-muted);
+ --accent: var(--oc-surface-3);
+ --accent-foreground: var(--oc-text);
+ --presence: var(--oc-presence);
+ --presence-foreground: var(--oc-on-presence);
+ --destructive: var(--oc-blocked-text);
+ --destructive-foreground: var(--oc-bg);
+ --success: var(--oc-success-text);
+ --warning: var(--oc-pending-text);
+ --information: var(--oc-evidence-text);
+ --border: var(--oc-border);
+ --input: var(--oc-border);
+ --ring: var(--oc-focus);
--tool-read: #3f6f99;
--tool-write: #3e7d5c;
@@ -90,75 +141,72 @@
--chart-4: var(--tool-net);
--chart-5: var(--presence);
- --font-ui:
- Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI",
- sans-serif;
- --font-editorial: "New York", "Iowan Old Style", Georgia, serif;
- --font-numeric: "SFMono-Regular", ui-monospace, Menlo, Consolas, monospace;
+ --font-ui: var(--oc-font-reading);
+ --font-display: var(--oc-font-display);
+ --font-editorial: var(--oc-font-display);
+ --font-numeric: var(--oc-font-mono);
--density-control: 2rem;
- --density-row: 0.75rem;
- --density-panel: 1.5rem;
- --density-gap: 1rem;
+ --density-row: var(--oc-space-3);
+ --density-panel: var(--oc-space-6);
+ --density-gap: var(--oc-space-4);
--radius-1: 4px;
--radius-2: 8px;
--radius-3: 12px;
--radius-4: 16px;
- --elevation-1: 0 1px 2px rgb(28 27 33 / 8%);
- --elevation-2: 0 8px 28px rgb(28 27 33 / 10%);
- --elevation-3: 0 24px 70px rgb(28 27 33 / 14%);
+ --elevation-1: var(--oc-shadow-1);
+ --elevation-2: var(--oc-shadow-2);
+ --elevation-3: var(--oc-shadow-2);
--overlay: rgb(255 255 255 / 96%);
--motion-fast: 120ms;
--motion-standard: 180ms;
- --motion-slow: 240ms;
- --motion-ease: cubic-bezier(0.3, 0.7, 0.3, 1);
+ --motion-slow: 320ms;
+ --motion-ease: cubic-bezier(0.2, 0, 0, 1);
}
.dark {
color-scheme: dark;
- --background: #0b0b0d;
- --foreground: #f1eff4;
- --card: #131315;
- --card-foreground: #f1eff4;
- --popover: #202024;
- --popover-foreground: #f1eff4;
- --primary: #e4e4e7;
- --primary-foreground: #18181b;
- --secondary: #1a1a1e;
- --secondary-foreground: #f1eff4;
- --muted: #1a1a1e;
- --muted-foreground: #a6a2ac;
- --accent: #29282e;
- --accent-foreground: #f1eff4;
- --presence: #9386d0;
- --presence-foreground: #0b0b0d;
- --destructive: #c77882;
- --destructive-foreground: #0b0b0d;
- --success: #86b39a;
- --warning: #c9a86a;
- --information: #7fa5c9;
- --border: #35343b;
- --input: #35343b;
- --ring: #9386d0;
+ --oc-bg: #050409;
+ --oc-surface-1: #0f0d14;
+ --oc-surface-2: #17131e;
+ --oc-surface-3: #211a2b;
+ --oc-text: #f7f3fa;
+ --oc-text-muted: #cbbfd3;
+ --oc-text-subtle: #a998b5;
+ --oc-border: #3b3146;
+ --oc-border-strong: #5b4b69;
+ --oc-action: #8e3dff;
+ --oc-action-hover: #7a22ee;
+ --oc-on-action: #ffffff;
+ --oc-focus: #c9a7ff;
+ --oc-presence: #b991ff;
+ --oc-on-presence: #050409;
+ --oc-success-bg: #11281b;
+ --oc-success-text: #93e6b4;
+ --oc-pending-bg: #2b210d;
+ --oc-pending-text: #ffd27a;
+ --oc-blocked-bg: #321517;
+ --oc-blocked-text: #ffb4ac;
+ --oc-evidence-bg: #201a25;
+ --oc-evidence-text: #e8e0f0;
+ --oc-shadow-1: 0 1px 2px rgb(0 0 0 / 40%);
+ --oc-shadow-2: 0 16px 40px rgb(0 0 0 / 42%);
--tool-read: #7fa5c9;
--tool-write: #86b39a;
--tool-exec: #c9a86a;
--tool-net: #c77882;
- --elevation-1: 0 1px 2px rgb(0 0 0 / 40%);
- --elevation-2: 0 8px 28px rgb(0 0 0 / 45%);
- --elevation-3: 0 24px 70px rgb(0 0 0 / 55%);
--overlay: rgb(32 32 36 / 96%);
}
[data-density="compact"] {
--density-control: 1.75rem;
- --density-row: 0.5rem;
- --density-panel: 1rem;
- --density-gap: 0.625rem;
+ --density-row: var(--oc-space-2);
+ --density-panel: var(--oc-space-4);
+ --density-gap: var(--oc-space-2);
}
@layer base {
@@ -235,4 +283,10 @@
:focus-visible {
outline-color: Highlight;
}
+
+ [data-slot="tabs-trigger"][data-active],
+ [data-slot="mode-switch"] button[aria-pressed="true"] {
+ outline: 2px solid Highlight;
+ outline-offset: -4px;
+ }
}
diff --git a/packages/ui/tests/components.test.tsx b/packages/ui/tests/components.test.tsx
index d88c928..bf30afb 100644
--- a/packages/ui/tests/components.test.tsx
+++ b/packages/ui/tests/components.test.tsx
@@ -140,5 +140,12 @@ describe("OpenCoven UI", () => {
expect(css).toContain("--radius-3: 12px");
expect(css).toContain("--radius-4: 16px");
expect(css).toContain("@media (prefers-reduced-motion: reduce)");
+ expect(css).toContain("--oc-action: #8e3dff");
+ expect(css).toContain("--oc-action: #7a22ee");
+ expect(css).toContain("--oc-bg: #050409");
+ expect(css).toContain("--oc-bg: #fcfafe");
+ expect(css).toContain("--font-display: var(--oc-font-display)");
+ expect(css).toContain("--oc-space-6: 1.5rem");
+ expect(css).toContain("--primary: var(--oc-action)");
});
});
diff --git a/packages/ui/tests/enriched-blocks.test.tsx b/packages/ui/tests/enriched-blocks.test.tsx
new file mode 100644
index 0000000..e85888d
--- /dev/null
+++ b/packages/ui/tests/enriched-blocks.test.tsx
@@ -0,0 +1,111 @@
+import { fireEvent, render, screen } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { axe } from "vitest-axe";
+
+import { Composer, type ComposerProps } from "@opencoven/ui/blocks/composer";
+import { RunRail } from "@opencoven/ui/blocks/run-rail";
+import { SendControl } from "@opencoven/ui/components/send-control";
+
+const composerProps: ComposerProps = {
+ value: "Review the diff",
+ onValueChange: () => undefined,
+ mode: "do",
+ onModeChange: () => undefined,
+};
+
+describe("enriched blocks", () => {
+ it("only offers send options when a handler is supplied", async () => {
+ const user = userEvent.setup();
+ const onOpenOptions = vi.fn();
+ const { rerender } = render(
);
+ expect(
+ screen.queryByRole("button", { name: "Send options" }),
+ ).not.toBeInTheDocument();
+ rerender(
);
+ await user.click(screen.getByRole("button", { name: "Send options" }));
+ expect(onOpenOptions).toHaveBeenCalledOnce();
+ });
+ it("gives each composer an independent label and mode description", () => {
+ render(
+ <>
+
+
+ >,
+ );
+ const inputs = screen.getAllByRole("textbox", { name: "Message" });
+ expect(inputs[0]!.id).not.toBe(inputs[1]!.id);
+ expect(inputs[0]).toHaveAccessibleDescription(
+ "Make changes with the context you provide. Press Control or Command and Enter to send.",
+ );
+ expect(inputs[1]).toHaveAccessibleDescription(
+ "Map the approach before making changes. Press Control or Command and Enter to send.",
+ );
+ });
+
+ it("only submits a ready draft with the modifier shortcut", () => {
+ const onSend = vi.fn();
+ const { rerender } = render(
+
,
+ );
+ const input = screen.getByRole("textbox");
+ fireEvent.keyDown(input, { key: "Enter" });
+ expect(onSend).not.toHaveBeenCalled();
+ fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
+ fireEvent.keyDown(input, { key: "Enter", metaKey: true });
+ expect(onSend).toHaveBeenCalledTimes(2);
+ fireEvent.keyDown(input, {
+ key: "Enter",
+ metaKey: true,
+ isComposing: true,
+ });
+ rerender(
);
+ fireEvent.keyDown(input, { key: "Enter", metaKey: true });
+ rerender(
);
+ fireEvent.keyDown(input, { key: "Enter", ctrlKey: true });
+ expect(onSend).toHaveBeenCalledTimes(2);
+ });
+
+ it("renders functional supporting tools without another primary action", async () => {
+ const user = userEvent.setup();
+ const onAttach = vi.fn();
+ render(
+
Attach context}
+ />,
+ );
+ await user.click(screen.getByRole("button", { name: "Attach context" }));
+ expect(onAttach).toHaveBeenCalledOnce();
+ });
+
+ it("adds plan and resource sections only when provided", async () => {
+ const props = {
+ metrics: [{ value: 2, label: "Files" }],
+ activity: [{ tool: "read" as const, target: "composer.tsx" }],
+ context: { used: 100, total: 1000 },
+ budget: { used: 1, limit: 5 },
+ };
+ const { container, rerender } = render( );
+ expect(screen.queryByText("Plan")).not.toBeInTheDocument();
+ expect(screen.queryByText("Changed files")).not.toBeInTheDocument();
+ expect(screen.getByText("Idle")).toBeVisible();
+ rerender(
+ ,
+ );
+ expect(screen.getByText("1 / 2")).toBeVisible();
+ expect(screen.getByText("Running")).toBeVisible();
+ expect(screen.getByText("Changed files")).toBeVisible();
+ expect(screen.getByText("composer.tsx")).toBeVisible();
+ expect((await axe(container)).violations).toEqual([]);
+ });
+});
diff --git a/packages/ui/tests/specimens.test.tsx b/packages/ui/tests/specimens.test.tsx
new file mode 100644
index 0000000..35934a2
--- /dev/null
+++ b/packages/ui/tests/specimens.test.tsx
@@ -0,0 +1,186 @@
+import { act, fireEvent, render, screen, within } from "@testing-library/react";
+import userEvent from "@testing-library/user-event";
+import { axe } from "vitest-axe";
+
+import { ComposerDemo } from "../../../apps/specimens/src/block-demos";
+import {
+ CodeSnippet,
+ HighlightedCode,
+} from "../../../apps/specimens/src/code-snippet";
+import { Lab } from "../../../apps/specimens/src/lab";
+import { ComponentPreview } from "../../../apps/specimens/src/component-preview";
+
+describe("specimen source and scenes", () => {
+ it("covers rather than unmounts the preview and returns focus on Escape", async () => {
+ const user = userEvent.setup();
+ render(
+
+
+ ,
+ );
+ const draft = screen.getByRole("textbox", { name: "Draft" });
+ await user.type(draft, " here");
+ const toggle = screen.getByRole("tab", { name: "Source" });
+ await user.click(toggle);
+ expect(toggle).toHaveAttribute("aria-selected", "true");
+ expect(screen.getByRole("tabpanel", { name: "Source" })).toHaveTextContent(
+ "const ready = true;",
+ );
+ expect(draft).toBeInTheDocument();
+ expect(draft.closest(".specimen-live-panel")).toHaveAttribute("inert");
+ expect(screen.queryByRole("textbox")).not.toBeInTheDocument();
+ act(() => screen.getByLabelText("demo.tsx").focus());
+ await user.keyboard("{Escape}");
+ expect(
+ screen.queryByRole("tabpanel", { name: "Source" }),
+ ).not.toBeInTheDocument();
+ expect(screen.getByRole("textbox")).toHaveValue("Keep me here");
+ expect(screen.getByRole("tab", { name: "Preview" })).toHaveFocus();
+ expect(toggle).toHaveAttribute("aria-selected", "false");
+ });
+
+ it("highlights TypeScript, JSX and shell source without interpreting HTML", () => {
+ const source =
+ 'const label = " ";\n ';
+ const { container } = render( );
+ expect(container.textContent).toBe(source);
+ expect(container.querySelector("img")).toBeNull();
+ expect(container.querySelector(".hljs-keyword")).toHaveTextContent("const");
+ expect(container.querySelector(".hljs-string")).toHaveTextContent(" {
+ const user = userEvent.setup();
+ const copy = vi.spyOn(navigator.clipboard, "writeText").mockResolvedValue();
+ const code =
+ 'pnpm dlx shadcn@latest add "https://ui.opencoven.ai/r/composer.json"';
+ render(
+ ,
+ );
+ await user.click(
+ screen.getByRole("button", { name: "Copy Install composer" }),
+ );
+ expect(copy).toHaveBeenCalledWith(code);
+ expect(screen.getByRole("status")).toHaveTextContent(
+ "Copied to clipboard.",
+ );
+ copy.mockRejectedValueOnce(new Error("Permission denied"));
+ await user.click(
+ screen.getByRole("button", { name: "Copy Install composer" }),
+ );
+ expect(screen.getByRole("status")).toHaveTextContent("Could not copy.");
+ expect(screen.getByLabelText("Install composer")).toHaveTextContent(code);
+ copy.mockRestore();
+ });
+
+ it("keeps copy focus during clipboard work so Escape can restore Preview", async () => {
+ const user = userEvent.setup();
+ let finishCopy!: () => void;
+ const copy = vi.spyOn(navigator.clipboard, "writeText").mockImplementation(
+ () =>
+ new Promise((resolve) => {
+ finishCopy = resolve;
+ }),
+ );
+ render(
+
+
+ ,
+ );
+ await user.click(screen.getByRole("tab", { name: "Source" }));
+ const button = screen.getByRole("button", { name: "Copy demo.tsx" });
+ await user.click(button);
+ expect(button).toHaveFocus();
+ expect(button).toHaveAttribute("aria-disabled", "true");
+ expect(button).not.toHaveAttribute("disabled");
+ await act(async () => finishCopy());
+ expect(button).toHaveFocus();
+ await user.keyboard("{Escape}");
+ expect(screen.getByRole("tab", { name: "Preview" })).toHaveFocus();
+ copy.mockRestore();
+ });
+
+ it("supports carousel wraparound, keyboard tabs, and persistent drafts", async () => {
+ const user = userEvent.setup();
+ const { container } = render( );
+ const carousel = screen.getByRole("region", { name: "Component scenes" });
+ const input = within(carousel).getByRole("textbox", { name: "Message" });
+ fireEvent.change(input, { target: { value: "Keep this draft" } });
+ await user.click(screen.getByRole("button", { name: "Previous scene" }));
+ expect(screen.getByRole("tab", { name: "Cards" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+ await user.click(screen.getByRole("button", { name: "Next scene" }));
+ expect(screen.getByRole("textbox")).toHaveValue("Keep this draft");
+ act(() => screen.getByRole("tab", { name: "Composer" }).focus());
+ await user.keyboard("{ArrowRight}");
+ expect(screen.getByRole("tab", { name: "Run rail" })).toHaveAttribute(
+ "aria-selected",
+ "true",
+ );
+ expect(screen.getAllByRole("tabpanel")).toHaveLength(1);
+ await act(async () => {
+ expect((await axe(container)).violations).toEqual([]);
+ });
+ });
+
+ it("keeps demo send, stop, attachment and reset controls functional", async () => {
+ const user = userEvent.setup();
+ render( );
+ await user.click(screen.getByRole("button", { name: "Attach sample" }));
+ expect(screen.getByText("changes.diff")).toBeVisible();
+ await user.click(
+ screen.getByRole("button", { name: /remove changes.diff/i }),
+ );
+ expect(screen.queryByText("changes.diff")).not.toBeInTheDocument();
+ await user.click(screen.getByRole("button", { name: "Send" }));
+ expect(screen.getByRole("status")).toHaveTextContent("Demo run started.");
+ await user.click(screen.getByRole("button", { name: /stop/i }));
+ expect(screen.getByRole("status")).toHaveTextContent("Demo run stopped.");
+ await user.click(
+ screen.getByRole("button", { name: "Reset composer demo" }),
+ );
+ expect(screen.getByRole("textbox")).toHaveValue("");
+ expect(screen.getByRole("button", { name: "Send" })).toBeDisabled();
+ });
+
+ it("applies slash commands and keeps local action results when revisiting scenes", async () => {
+ const user = userEvent.setup();
+ render( );
+ await user.click(screen.getByRole("button", { name: "Commands" }));
+ await user.keyboard("{ArrowDown}");
+ await user.click(await screen.findByRole("menuitem", { name: /\/plan/ }));
+ expect(screen.getByRole("textbox")).toHaveValue(
+ "Plan a focused implementation",
+ );
+ expect(screen.getByRole("button", { name: "plan" })).toHaveAttribute(
+ "aria-pressed",
+ "true",
+ );
+ await user.click(screen.getByRole("tab", { name: "Actions" }));
+ await user.click(screen.getByRole("button", { name: /Attach context/ }));
+ await user.click(
+ screen.getByRole("button", { name: /Clarify the prompt/ }),
+ );
+ expect(screen.getByText("changes.diff · 8.1 KB")).toBeVisible();
+ await user.click(screen.getByRole("tab", { name: "Cards" }));
+ const summary = screen.getByText("A smaller composer").closest("summary")!;
+ await user.click(summary);
+ expect(summary.parentElement).toHaveAttribute("open");
+ await user.click(screen.getByRole("tab", { name: "Actions" }));
+ expect(screen.getByText("changes.diff · 8.1 KB")).toBeVisible();
+ expect(
+ screen.getByText(/Review the changed files for behavior regressions/),
+ ).toBeVisible();
+ });
+});
diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json
index 102e02b..b3ac73c 100644
--- a/packages/ui/tsconfig.json
+++ b/packages/ui/tsconfig.json
@@ -2,6 +2,7 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
+ "@opencoven/ui": ["./src/index.ts"],
"@opencoven/ui/*": ["./src/*"]
},
"types": ["vitest/globals", "@testing-library/jest-dom"]
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 29e6420..64217a4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -78,6 +78,9 @@ importers:
'@opencoven/ui':
specifier: workspace:*
version: link:../../packages/ui
+ highlight.js:
+ specifier: ^11.12.0
+ version: 11.12.0
lucide-react:
specifier: ^1.33.0
version: 1.33.0(react@19.2.8)
@@ -1876,6 +1879,10 @@ packages:
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+ highlight.js@11.12.0:
+ resolution: {integrity: sha512-nbfWpyRMcMrPMmDwJB+dhX/eiaPKtc2RB+0QZskqJ3WjRA/FDS0e9hZrx8EC/lbEv8gXy98FcDbNa/dspAaJMg==}
+ engines: {node: '>=12.0.0'}
+
hono@4.13.3:
resolution: {integrity: sha512-r8AO2mYHoLxSHkgafNeC/BXyb2vWRxD3jem4Ts+ptav8oTG5FIRifAjuJEmZI4bSvvc2ns0GxmIYiZnHqN3mMw==}
engines: {node: '>=16.9.0'}
@@ -4830,6 +4837,8 @@ snapshots:
dependencies:
hermes-estree: 0.25.1
+ highlight.js@11.12.0: {}
+
hono@4.13.3: {}
html-encoding-sniffer@6.0.0:
diff --git a/public/opencoven-mark.svg b/public/opencoven-mark.svg
new file mode 100644
index 0000000..c4817c3
--- /dev/null
+++ b/public/opencoven-mark.svg
@@ -0,0 +1 @@
+
diff --git a/public/r/button.json b/public/r/button.json
index e29a225..c2aa64a 100644
--- a/public/r/button.json
+++ b/public/r/button.json
@@ -14,7 +14,7 @@
"files": [
{
"path": "packages/ui/src/components/ui/button.tsx",
- "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent text-sm font-medium transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n primary:\n \"bg-primary text-primary-foreground hover:bg-primary/85 active:translate-y-px\",\n presence:\n \"bg-presence text-presence-foreground hover:bg-presence/85 active:translate-y-px\",\n outline:\n \"border-border bg-card text-card-foreground hover:bg-muted aria-expanded:bg-muted\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-accent aria-expanded:bg-accent\",\n ghost: \"text-foreground hover:bg-muted aria-expanded:bg-muted\",\n destructive:\n \"border-destructive/35 bg-destructive/12 text-destructive hover:bg-destructive/20\",\n link: \"text-presence underline-offset-4 hover:underline\",\n },\n density: {\n default: \"h-8 px-3\",\n compact: \"h-7 px-2 text-xs\",\n },\n size: {\n default: \"\",\n icon: \"aspect-square px-0\",\n },\n },\n defaultVariants: {\n variant: \"outline\",\n density: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant = \"outline\",\n density = \"default\",\n size = \"default\",\n ...props\n}: ButtonPrimitive.Props & VariantProps) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n",
+ "content": "\"use client\";\n\nimport { Button as ButtonPrimitive } from \"@base-ui/react/button\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-1.5 whitespace-nowrap rounded-md border border-transparent text-sm font-medium transition-colors outline-none focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/35 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0\",\n {\n variants: {\n variant: {\n primary:\n \"bg-primary text-primary-foreground hover:bg-[var(--oc-action-hover)] active:translate-y-px\",\n presence:\n \"bg-presence text-presence-foreground hover:bg-presence/85 active:translate-y-px\",\n outline:\n \"border-border bg-card text-card-foreground hover:bg-muted aria-expanded:bg-muted\",\n secondary:\n \"bg-secondary text-secondary-foreground hover:bg-accent aria-expanded:bg-accent\",\n ghost: \"text-foreground hover:bg-muted aria-expanded:bg-muted\",\n destructive:\n \"border-destructive/35 bg-destructive/12 text-destructive hover:bg-destructive/20\",\n link: \"text-presence underline-offset-4 hover:underline\",\n },\n density: {\n default: \"h-8 px-3\",\n compact: \"h-7 px-2 text-xs\",\n },\n size: {\n default: \"\",\n icon: \"aspect-square px-0\",\n },\n },\n defaultVariants: {\n variant: \"outline\",\n density: \"default\",\n size: \"default\",\n },\n },\n);\n\nfunction Button({\n className,\n variant = \"outline\",\n density = \"default\",\n size = \"default\",\n ...props\n}: ButtonPrimitive.Props & VariantProps) {\n return (\n \n );\n}\n\nexport { Button, buttonVariants };\n",
"type": "registry:ui",
"target": "@ui/button.tsx"
}
diff --git a/public/r/completion-palette.json b/public/r/completion-palette.json
index 0752019..91d9a89 100644
--- a/public/r/completion-palette.json
+++ b/public/r/completion-palette.json
@@ -9,7 +9,7 @@
"files": [
{
"path": "packages/ui/src/components/completion-palette.tsx",
- "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\ntype CompletionCommand = {\n id: string;\n label: string;\n description: string;\n shortcut?: string;\n disabled?: boolean;\n};\n\ntype CompletionPaletteProps = {\n trigger: React.ReactElement;\n commands: CompletionCommand[];\n onSelect: (command: CompletionCommand) => void;\n label?: string;\n};\n\nfunction CompletionPalette({\n trigger,\n commands,\n onSelect,\n label = \"Slash commands\",\n}: CompletionPaletteProps) {\n return (\n \n \n \n {label} \n {commands.map((command) => (\n onSelect(command)}\n >\n \n {command.label}\n \n \n {command.description}\n \n {command.shortcut ? (\n \n {command.shortcut}\n \n ) : null}\n \n ))}\n \n \n );\n}\n\nexport {\n CompletionPalette,\n type CompletionCommand,\n type CompletionPaletteProps,\n};\n",
+ "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\ntype CompletionCommand = {\n id: string;\n label: string;\n description: string;\n shortcut?: string;\n disabled?: boolean;\n};\n\ntype CompletionPaletteProps = {\n trigger: React.ReactElement;\n commands: CompletionCommand[];\n onSelect: (command: CompletionCommand) => void;\n label?: string;\n};\n\nfunction CompletionPalette({\n trigger,\n commands,\n onSelect,\n label = \"Slash commands\",\n}: CompletionPaletteProps) {\n return (\n \n \n \n {label} \n {commands.map((command) => (\n onSelect(command)}\n >\n \n {command.label}\n \n \n {command.description}\n \n {command.shortcut ? (\n \n {command.shortcut}\n \n ) : null}\n \n ))}\n \n \n );\n}\n\nexport {\n CompletionPalette,\n type CompletionCommand,\n type CompletionPaletteProps,\n};\n",
"type": "registry:component",
"target": "@components/completion-palette.tsx"
}
diff --git a/public/r/composer.json b/public/r/composer.json
index 8b5416d..58752ce 100644
--- a/public/r/composer.json
+++ b/public/r/composer.json
@@ -2,7 +2,10 @@
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "composer",
"title": "Composer",
- "description": "Intent-taking surface with draft, attachments, mode, model, and send state.",
+ "description": "Intent-taking surface with draft, attachments, tools, mode guidance, model, and send state.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/attachment-chip",
"@opencoven/mode-switch",
@@ -12,7 +15,7 @@
"files": [
{
"path": "packages/ui/src/blocks/composer.tsx",
- "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { AttachmentChip } from \"@/components/attachment-chip\";\nimport {\n ModeSwitch,\n type ComposerMode,\n} from \"@/components/mode-switch\";\nimport { SendControl } from \"@/components/send-control\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\n\ntype ComposerAttachment = {\n id: string;\n name: string;\n meta?: string;\n};\n\ntype ComposerProps = {\n value: string;\n onValueChange: (value: string) => void;\n mode: ComposerMode;\n onModeChange: (mode: ComposerMode) => void;\n attachments?: ComposerAttachment[];\n onRemoveAttachment?: (id: string) => void;\n onSend?: () => void;\n running?: boolean;\n onStop?: () => void;\n model?: string;\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction Composer({\n value,\n onValueChange,\n mode,\n onModeChange,\n attachments = [],\n onRemoveAttachment,\n onSend,\n running = false,\n onStop,\n model = \"GPT-5.6 Sol\",\n density = \"default\",\n className,\n}: ComposerProps) {\n return (\n \n );\n}\n\nexport { Composer, type ComposerAttachment, type ComposerProps };\n",
+ "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { Bot, CornerDownLeft, PenLine } from \"lucide-react\";\n\nimport { AttachmentChip } from \"@/components/attachment-chip\";\nimport {\n ModeSwitch,\n type ComposerMode,\n} from \"@/components/mode-switch\";\nimport { SendControl } from \"@/components/send-control\";\nimport { Textarea } from \"@/components/ui/textarea\";\nimport { cn } from \"@/lib/utils\";\n\ntype ComposerAttachment = {\n id: string;\n name: string;\n meta?: string;\n};\n\ntype ComposerProps = {\n value: string;\n onValueChange: (value: string) => void;\n mode: ComposerMode;\n onModeChange: (mode: ComposerMode) => void;\n attachments?: ComposerAttachment[];\n onRemoveAttachment?: (id: string) => void;\n onSend?: () => void;\n running?: boolean;\n onStop?: () => void;\n model?: string;\n tools?: React.ReactNode;\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction Composer({\n value,\n onValueChange,\n mode,\n onModeChange,\n attachments = [],\n onRemoveAttachment,\n onSend,\n running = false,\n onStop,\n model = \"GPT-5.6 Sol\",\n tools,\n density = \"default\",\n className,\n}: ComposerProps) {\n const messageId = React.useId();\n const hintId = React.useId();\n const modeHint = {\n chat: \"Explore ideas without changing files.\",\n do: \"Make changes with the context you provide.\",\n plan: \"Map the approach before making changes.\",\n }[mode];\n\n return (\n \n \n \n \n Compose\n \n \n \n {model}\n \n \n \n Message\n \n \n );\n}\n\nexport { Composer, type ComposerAttachment, type ComposerProps };\n",
"type": "registry:block",
"target": "@components/blocks/composer.tsx"
}
diff --git a/public/r/coven-theme.json b/public/r/coven-theme.json
index ad636d4..bff4d2f 100644
--- a/public/r/coven-theme.json
+++ b/public/r/coven-theme.json
@@ -2,11 +2,11 @@
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "coven-theme",
"title": "Coven semantic theme",
- "description": "Nova density, Zinc-seeded structure, lavender presence, and canonical tool-class tokens.",
+ "description": "OpenCoven web profile v1.0.0 palette, typography, spacing, explicit density, and canonical tool-class tokens.",
"files": [
{
"path": "packages/ui/src/styles/globals.css",
- "content": "@import \"tailwindcss\";\n\n@custom-variant dark (&:is(.dark *));\n@custom-variant compact (&:is([data-density=\"compact\"] *));\n\n@source \"../../../../apps/**/*.{ts,tsx}\";\n@source \"../**/*.{ts,tsx}\";\n\n@theme inline {\n --font-sans: var(--font-ui);\n --font-serif: var(--font-editorial);\n --font-mono: var(--font-numeric);\n\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-presence: var(--presence);\n --color-presence-foreground: var(--presence-foreground);\n --color-destructive: var(--destructive);\n --color-destructive-foreground: var(--destructive-foreground);\n --color-success: var(--success);\n --color-warning: var(--warning);\n --color-information: var(--information);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --color-tool-read: var(--tool-read);\n --color-tool-write: var(--tool-write);\n --color-tool-exec: var(--tool-exec);\n --color-tool-net: var(--tool-net);\n --color-chart-1: var(--chart-1);\n --color-chart-2: var(--chart-2);\n --color-chart-3: var(--chart-3);\n --color-chart-4: var(--chart-4);\n --color-chart-5: var(--chart-5);\n\n --radius-sm: var(--radius-1);\n --radius-md: var(--radius-2);\n --radius-lg: var(--radius-3);\n --radius-xl: var(--radius-4);\n}\n\n:root {\n color-scheme: light;\n\n /* Zinc seeds the neutral structure; these named variables are the contract. */\n --background: #f5f4f8;\n --foreground: #1c1b21;\n --card: #ffffff;\n --card-foreground: #1c1b21;\n --popover: #ffffff;\n --popover-foreground: #1c1b21;\n --primary: #27272a;\n --primary-foreground: #fafafa;\n --secondary: #eeedf3;\n --secondary-foreground: #27272a;\n --muted: #eeedf3;\n --muted-foreground: #65616d;\n --accent: #e4e2ea;\n --accent-foreground: #27272a;\n --presence: #6b5bbf;\n --presence-foreground: #ffffff;\n --destructive: #b3505e;\n --destructive-foreground: #ffffff;\n --success: #3e7d5c;\n --warning: #8a6a24;\n --information: #35618a;\n --border: #d8d6e0;\n --input: #d8d6e0;\n --ring: #6b5bbf;\n\n --tool-read: #3f6f99;\n --tool-write: #3e7d5c;\n --tool-exec: #8a6a24;\n --tool-net: #b3505e;\n --chart-1: var(--tool-read);\n --chart-2: var(--tool-exec);\n --chart-3: var(--tool-write);\n --chart-4: var(--tool-net);\n --chart-5: var(--presence);\n\n --font-ui:\n Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, \"Segoe UI\",\n sans-serif;\n --font-editorial: \"New York\", \"Iowan Old Style\", Georgia, serif;\n --font-numeric: \"SFMono-Regular\", ui-monospace, Menlo, Consolas, monospace;\n\n --density-control: 2rem;\n --density-row: 0.75rem;\n --density-panel: 1.5rem;\n --density-gap: 1rem;\n\n --radius-1: 4px;\n --radius-2: 8px;\n --radius-3: 12px;\n --radius-4: 16px;\n\n --elevation-1: 0 1px 2px rgb(28 27 33 / 8%);\n --elevation-2: 0 8px 28px rgb(28 27 33 / 10%);\n --elevation-3: 0 24px 70px rgb(28 27 33 / 14%);\n --overlay: rgb(255 255 255 / 96%);\n\n --motion-fast: 120ms;\n --motion-standard: 180ms;\n --motion-slow: 240ms;\n --motion-ease: cubic-bezier(0.3, 0.7, 0.3, 1);\n}\n\n.dark {\n color-scheme: dark;\n --background: #0b0b0d;\n --foreground: #f1eff4;\n --card: #131315;\n --card-foreground: #f1eff4;\n --popover: #202024;\n --popover-foreground: #f1eff4;\n --primary: #e4e4e7;\n --primary-foreground: #18181b;\n --secondary: #1a1a1e;\n --secondary-foreground: #f1eff4;\n --muted: #1a1a1e;\n --muted-foreground: #a6a2ac;\n --accent: #29282e;\n --accent-foreground: #f1eff4;\n --presence: #9386d0;\n --presence-foreground: #0b0b0d;\n --destructive: #c77882;\n --destructive-foreground: #0b0b0d;\n --success: #86b39a;\n --warning: #c9a86a;\n --information: #7fa5c9;\n --border: #35343b;\n --input: #35343b;\n --ring: #9386d0;\n\n --tool-read: #7fa5c9;\n --tool-write: #86b39a;\n --tool-exec: #c9a86a;\n --tool-net: #c77882;\n --elevation-1: 0 1px 2px rgb(0 0 0 / 40%);\n --elevation-2: 0 8px 28px rgb(0 0 0 / 45%);\n --elevation-3: 0 24px 70px rgb(0 0 0 / 55%);\n --overlay: rgb(32 32 36 / 96%);\n}\n\n[data-density=\"compact\"] {\n --density-control: 1.75rem;\n --density-row: 0.5rem;\n --density-panel: 1rem;\n --density-gap: 0.625rem;\n}\n\n@layer base {\n * {\n border-color: var(--border);\n }\n\n html {\n background: var(--background);\n }\n\n body {\n margin: 0;\n background: var(--background);\n color: var(--foreground);\n font-family: var(--font-ui);\n -webkit-font-smoothing: antialiased;\n }\n\n button,\n input,\n textarea,\n select {\n font: inherit;\n }\n\n :focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n }\n}\n\n@layer utilities {\n .numeric {\n font-family: var(--font-numeric);\n font-variant-numeric: tabular-nums;\n }\n\n .editorial {\n font-family: var(--font-editorial);\n }\n\n .surface {\n border: 1px solid var(--border);\n border-radius: var(--radius-3);\n background: var(--card);\n color: var(--card-foreground);\n box-shadow: var(--elevation-1);\n }\n}\n\n@keyframes coven-pulse {\n 0%,\n 100% {\n opacity: 0.45;\n }\n 50% {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media (forced-colors: active) {\n :focus-visible {\n outline-color: Highlight;\n }\n}\n",
+ "content": "@import \"tailwindcss\";\n\n@custom-variant dark (&:is(.dark *));\n@custom-variant compact (&:is([data-density=\"compact\"] *));\n\n@source \"../../../../apps/**/*.{ts,tsx}\";\n@source \"../**/*.{ts,tsx}\";\n\n@theme inline {\n --font-sans: var(--font-ui);\n --font-serif: var(--font-editorial);\n --font-mono: var(--font-numeric);\n\n --color-background: var(--background);\n --color-foreground: var(--foreground);\n --color-card: var(--card);\n --color-card-foreground: var(--card-foreground);\n --color-popover: var(--popover);\n --color-popover-foreground: var(--popover-foreground);\n --color-primary: var(--primary);\n --color-primary-foreground: var(--primary-foreground);\n --color-secondary: var(--secondary);\n --color-secondary-foreground: var(--secondary-foreground);\n --color-muted: var(--muted);\n --color-muted-foreground: var(--muted-foreground);\n --color-accent: var(--accent);\n --color-accent-foreground: var(--accent-foreground);\n --color-presence: var(--presence);\n --color-presence-foreground: var(--presence-foreground);\n --color-destructive: var(--destructive);\n --color-destructive-foreground: var(--destructive-foreground);\n --color-success: var(--success);\n --color-warning: var(--warning);\n --color-information: var(--information);\n --color-border: var(--border);\n --color-input: var(--input);\n --color-ring: var(--ring);\n --color-tool-read: var(--tool-read);\n --color-tool-write: var(--tool-write);\n --color-tool-exec: var(--tool-exec);\n --color-tool-net: var(--tool-net);\n --color-chart-1: var(--chart-1);\n --color-chart-2: var(--chart-2);\n --color-chart-3: var(--chart-3);\n --color-chart-4: var(--chart-4);\n --color-chart-5: var(--chart-5);\n\n --radius-sm: var(--radius-1);\n --radius-md: var(--radius-2);\n --radius-lg: var(--radius-3);\n --radius-xl: var(--radius-4);\n}\n\n:root {\n color-scheme: light;\n\n /* OpenCoven/brand web profile v1.0.0, pinned to 798e2f5f2f1a69f5d156ccc0a9aafc4f7da55fc8. */\n --oc-bg: #fcfafe;\n --oc-surface-1: #ffffff;\n --oc-surface-2: #f6f0fa;\n --oc-surface-3: #ece3f2;\n --oc-text: #1a1320;\n --oc-text-muted: #5d5265;\n --oc-text-subtle: #74667e;\n --oc-border: #d8cce0;\n --oc-border-strong: #b9a8c4;\n --oc-action: #7a22ee;\n --oc-action-hover: #6410cc;\n --oc-on-action: #ffffff;\n --oc-focus: #6e19da;\n --oc-presence: #5e2a8a;\n --oc-on-presence: #ffffff;\n --oc-success-bg: #e7f6ec;\n --oc-success-text: #17683a;\n --oc-pending-bg: #fff4d3;\n --oc-pending-text: #704b00;\n --oc-blocked-bg: #fdebec;\n --oc-blocked-text: #8a1c1c;\n --oc-evidence-bg: #f2edf5;\n --oc-evidence-text: #4a3f51;\n --oc-font-display: Geist, Inter, \"SF Pro Display\", system-ui, sans-serif;\n --oc-font-reading: Inter, \"SF Pro Text\", system-ui, sans-serif;\n --oc-font-mono:\n \"SFMono-Regular\", ui-monospace, Menlo, Monaco, Consolas, monospace;\n --oc-type--1: clamp(0.875rem, 0.84rem + 0.15vw, 0.95rem);\n --oc-type-0: clamp(1rem, 0.96rem + 0.2vw, 1.125rem);\n --oc-type-1: clamp(1.2rem, 1.08rem + 0.55vw, 1.5rem);\n --oc-type-2: clamp(1.5rem, 1.28rem + 1vw, 2rem);\n --oc-type-3: clamp(2rem, 1.55rem + 2vw, 3rem);\n --oc-type-4: clamp(2.75rem, 1.9rem + 3.8vw, 5rem);\n --oc-leading-reading: 1.62;\n --oc-leading-heading: 1.05;\n --oc-reading-measure: 68ch;\n --oc-technical-measure: 80ch;\n --oc-tracking-heading: -0.035em;\n --oc-tracking-label: 0.12em;\n --oc-space-1: 0.25rem;\n --oc-space-2: 0.5rem;\n --oc-space-3: 0.75rem;\n --oc-space-4: 1rem;\n --oc-space-6: 1.5rem;\n --oc-space-8: 2rem;\n --oc-space-12: 3rem;\n --oc-space-16: 4rem;\n --oc-space-24: 6rem;\n --oc-shadow-1: 0 1px 2px rgb(26 19 32 / 8%);\n --oc-shadow-2: 0 12px 32px rgb(26 19 32 / 12%);\n\n --background: var(--oc-bg);\n --foreground: var(--oc-text);\n --card: var(--oc-surface-1);\n --card-foreground: var(--oc-text);\n --popover: var(--oc-surface-1);\n --popover-foreground: var(--oc-text);\n --primary: var(--oc-action);\n --primary-foreground: var(--oc-on-action);\n --secondary: var(--oc-surface-2);\n --secondary-foreground: var(--oc-text);\n --muted: var(--oc-surface-2);\n --muted-foreground: var(--oc-text-muted);\n --accent: var(--oc-surface-3);\n --accent-foreground: var(--oc-text);\n --presence: var(--oc-presence);\n --presence-foreground: var(--oc-on-presence);\n --destructive: var(--oc-blocked-text);\n --destructive-foreground: var(--oc-bg);\n --success: var(--oc-success-text);\n --warning: var(--oc-pending-text);\n --information: var(--oc-evidence-text);\n --border: var(--oc-border);\n --input: var(--oc-border);\n --ring: var(--oc-focus);\n\n --tool-read: #3f6f99;\n --tool-write: #3e7d5c;\n --tool-exec: #8a6a24;\n --tool-net: #b3505e;\n --chart-1: var(--tool-read);\n --chart-2: var(--tool-exec);\n --chart-3: var(--tool-write);\n --chart-4: var(--tool-net);\n --chart-5: var(--presence);\n\n --font-ui: var(--oc-font-reading);\n --font-display: var(--oc-font-display);\n --font-editorial: var(--oc-font-display);\n --font-numeric: var(--oc-font-mono);\n\n --density-control: 2rem;\n --density-row: var(--oc-space-3);\n --density-panel: var(--oc-space-6);\n --density-gap: var(--oc-space-4);\n\n --radius-1: 4px;\n --radius-2: 8px;\n --radius-3: 12px;\n --radius-4: 16px;\n\n --elevation-1: var(--oc-shadow-1);\n --elevation-2: var(--oc-shadow-2);\n --elevation-3: var(--oc-shadow-2);\n --overlay: rgb(255 255 255 / 96%);\n\n --motion-fast: 120ms;\n --motion-standard: 180ms;\n --motion-slow: 320ms;\n --motion-ease: cubic-bezier(0.2, 0, 0, 1);\n}\n\n.dark {\n color-scheme: dark;\n --oc-bg: #050409;\n --oc-surface-1: #0f0d14;\n --oc-surface-2: #17131e;\n --oc-surface-3: #211a2b;\n --oc-text: #f7f3fa;\n --oc-text-muted: #cbbfd3;\n --oc-text-subtle: #a998b5;\n --oc-border: #3b3146;\n --oc-border-strong: #5b4b69;\n --oc-action: #8e3dff;\n --oc-action-hover: #7a22ee;\n --oc-on-action: #ffffff;\n --oc-focus: #c9a7ff;\n --oc-presence: #b991ff;\n --oc-on-presence: #050409;\n --oc-success-bg: #11281b;\n --oc-success-text: #93e6b4;\n --oc-pending-bg: #2b210d;\n --oc-pending-text: #ffd27a;\n --oc-blocked-bg: #321517;\n --oc-blocked-text: #ffb4ac;\n --oc-evidence-bg: #201a25;\n --oc-evidence-text: #e8e0f0;\n --oc-shadow-1: 0 1px 2px rgb(0 0 0 / 40%);\n --oc-shadow-2: 0 16px 40px rgb(0 0 0 / 42%);\n\n --tool-read: #7fa5c9;\n --tool-write: #86b39a;\n --tool-exec: #c9a86a;\n --tool-net: #c77882;\n --overlay: rgb(32 32 36 / 96%);\n}\n\n[data-density=\"compact\"] {\n --density-control: 1.75rem;\n --density-row: var(--oc-space-2);\n --density-panel: var(--oc-space-4);\n --density-gap: var(--oc-space-2);\n}\n\n@layer base {\n * {\n border-color: var(--border);\n }\n\n html {\n background: var(--background);\n }\n\n body {\n margin: 0;\n background: var(--background);\n color: var(--foreground);\n font-family: var(--font-ui);\n -webkit-font-smoothing: antialiased;\n }\n\n button,\n input,\n textarea,\n select {\n font: inherit;\n }\n\n :focus-visible {\n outline: 2px solid var(--ring);\n outline-offset: 2px;\n }\n}\n\n@layer utilities {\n .numeric {\n font-family: var(--font-numeric);\n font-variant-numeric: tabular-nums;\n }\n\n .editorial {\n font-family: var(--font-editorial);\n }\n\n .surface {\n border: 1px solid var(--border);\n border-radius: var(--radius-3);\n background: var(--card);\n color: var(--card-foreground);\n box-shadow: var(--elevation-1);\n }\n}\n\n@keyframes coven-pulse {\n 0%,\n 100% {\n opacity: 0.45;\n }\n 50% {\n opacity: 1;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *,\n *::before,\n *::after {\n scroll-behavior: auto !important;\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media (forced-colors: active) {\n :focus-visible {\n outline-color: Highlight;\n }\n\n [data-slot=\"tabs-trigger\"][data-active],\n [data-slot=\"mode-switch\"] button[aria-pressed=\"true\"] {\n outline: 2px solid Highlight;\n outline-offset: -4px;\n }\n}\n",
"type": "registry:theme",
"target": "src/styles/opencoven.css"
}
diff --git a/public/r/mode-switch.json b/public/r/mode-switch.json
index f43f4d9..f1210a0 100644
--- a/public/r/mode-switch.json
+++ b/public/r/mode-switch.json
@@ -10,7 +10,7 @@
"files": [
{
"path": "packages/ui/src/components/mode-switch.tsx",
- "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst MODES = [\"chat\", \"do\", \"plan\"] as const;\ntype ComposerMode = (typeof MODES)[number];\n\ntype ModeSwitchProps = {\n value: ComposerMode;\n onValueChange: (value: ComposerMode) => void;\n density?: \"default\" | \"compact\";\n disabled?: boolean;\n className?: string;\n};\n\nfunction ModeSwitch({\n value,\n onValueChange,\n density = \"default\",\n disabled = false,\n className,\n}: ModeSwitchProps) {\n return (\n \n {MODES.map((mode) => {\n const selected = value === mode;\n return (\n onValueChange(mode)}\n className={cn(\n \"rounded-sm border border-transparent px-2.5 text-sm font-medium capitalize text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50\",\n density === \"compact\" ? \"h-6 text-xs\" : \"h-7\",\n selected &&\n \"border-presence/25 bg-presence/12 font-semibold text-presence shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--presence)_22%,transparent)]\",\n )}\n >\n {mode}\n \n );\n })}\n
\n );\n}\n\nexport { ModeSwitch, type ComposerMode, type ModeSwitchProps };\n",
+ "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst MODES = [\"chat\", \"do\", \"plan\"] as const;\ntype ComposerMode = (typeof MODES)[number];\n\ntype ModeSwitchProps = {\n value: ComposerMode;\n onValueChange: (value: ComposerMode) => void;\n density?: \"default\" | \"compact\";\n disabled?: boolean;\n className?: string;\n};\n\nfunction ModeSwitch({\n value,\n onValueChange,\n density = \"default\",\n disabled = false,\n className,\n}: ModeSwitchProps) {\n return (\n \n {MODES.map((mode) => {\n const selected = value === mode;\n return (\n onValueChange(mode)}\n className={cn(\n \"rounded-sm border border-transparent px-2.5 text-sm font-medium capitalize text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:border-ring focus-visible:ring-2 focus-visible:ring-ring/30 disabled:pointer-events-none disabled:opacity-50\",\n density === \"compact\" ? \"h-6 text-xs\" : \"h-7\",\n selected &&\n \"border-presence/25 bg-presence/12 font-semibold text-presence shadow-[inset_0_0_0_1px_color-mix(in_srgb,var(--presence)_22%,transparent)]\",\n )}\n >\n {mode}\n \n );\n })}\n
\n );\n}\n\nexport { ModeSwitch, type ComposerMode, type ModeSwitchProps };\n",
"type": "registry:component",
"target": "@components/mode-switch.tsx"
}
diff --git a/public/r/plan-row.json b/public/r/plan-row.json
index ead7960..9ac1a0e 100644
--- a/public/r/plan-row.json
+++ b/public/r/plan-row.json
@@ -10,7 +10,7 @@
"files": [
{
"path": "packages/ui/src/components/plan-row.tsx",
- "content": "import { StatusIndicator, type Status } from \"./status-indicator\";\nimport { cn } from \"@/lib/utils\";\n\ntype PlanRowProps = {\n title: string;\n status: Extract;\n duration?: string;\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction PlanRow({\n title,\n status,\n duration,\n density = \"default\",\n className,\n}: PlanRowProps) {\n const displayedDuration =\n status === \"active\" ? \"now\" : status === \"pending\" ? \"—\" : duration;\n\n return (\n \n \n \n {title}\n \n \n {displayedDuration}\n \n
\n );\n}\n\nexport { PlanRow, type PlanRowProps };\n",
+ "content": "import { StatusIndicator, type Status } from \"./status-indicator\";\nimport { cn } from \"@/lib/utils\";\n\ntype PlanRowProps = {\n title: string;\n status: Extract;\n duration?: string;\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction PlanRow({\n title,\n status,\n duration,\n density = \"default\",\n className,\n}: PlanRowProps) {\n const displayedDuration =\n status === \"active\" ? \"now\" : status === \"pending\" ? \"—\" : duration;\n\n return (\n \n \n \n {title}\n \n \n {displayedDuration}\n \n
\n );\n}\n\nexport { PlanRow, type PlanRowProps };\n",
"type": "registry:component",
"target": "@components/plan-row.tsx"
}
diff --git a/public/r/registry.json b/public/r/registry.json
index c238559..bef35d3 100644
--- a/public/r/registry.json
+++ b/public/r/registry.json
@@ -24,7 +24,7 @@
"name": "coven-theme",
"type": "registry:theme",
"title": "Coven semantic theme",
- "description": "Nova density, Zinc-seeded structure, lavender presence, and canonical tool-class tokens.",
+ "description": "OpenCoven web profile v1.0.0 palette, typography, spacing, explicit density, and canonical tool-class tokens.",
"files": [
{
"path": "packages/ui/src/styles/globals.css",
@@ -577,7 +577,10 @@
"name": "composer",
"type": "registry:block",
"title": "Composer",
- "description": "Intent-taking surface with draft, attachments, mode, model, and send state.",
+ "description": "Intent-taking surface with draft, attachments, tools, mode guidance, model, and send state.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/attachment-chip",
"@opencoven/mode-switch",
@@ -608,12 +611,17 @@
"name": "run-rail",
"type": "registry:block",
"title": "Run rail",
- "description": "Live metrics, activity, context-window use, and budget.",
+ "description": "Live metrics, optional plan and changed files, activity, context-window use, and budget.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/activity-item",
"@opencoven/budget-pill",
"@opencoven/context-meter",
"@opencoven/metric-display",
+ "@opencoven/plan-row",
+ "@opencoven/resource-row",
"@opencoven/card"
],
"files": [
diff --git a/public/r/run-rail.json b/public/r/run-rail.json
index dfcc595..b24a2c1 100644
--- a/public/r/run-rail.json
+++ b/public/r/run-rail.json
@@ -2,18 +2,23 @@
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
"name": "run-rail",
"title": "Run rail",
- "description": "Live metrics, activity, context-window use, and budget.",
+ "description": "Live metrics, optional plan and changed files, activity, context-window use, and budget.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/activity-item",
"@opencoven/budget-pill",
"@opencoven/context-meter",
"@opencoven/metric-display",
+ "@opencoven/plan-row",
+ "@opencoven/resource-row",
"@opencoven/card"
],
"files": [
{
"path": "packages/ui/src/blocks/run-rail.tsx",
- "content": "import {\n ActivityItem,\n type ActivityItemProps,\n} from \"@/components/activity-item\";\nimport { BudgetPill } from \"@/components/budget-pill\";\nimport { ContextMeter } from \"@/components/context-meter\";\nimport {\n MetricDisplay,\n type MetricDisplayProps,\n} from \"@/components/metric-display\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\n\ntype RunRailProps = {\n metrics: MetricDisplayProps[];\n activity: ActivityItemProps[];\n context: {\n used: number;\n total: number;\n threshold?: number;\n };\n budget: {\n used: number;\n limit: number;\n };\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction RunRail({\n metrics,\n activity,\n context,\n budget,\n density = \"default\",\n className,\n}: RunRailProps) {\n return (\n \n \n
\n Run\n
\n
\n {metrics.map((metric) => (\n \n ))}\n \n
\n \n
\n Activity\n
\n
\n {activity.map((item, index) => (\n \n ))}\n \n
\n \n
\n Limits\n
\n
\n \n \n \n
\n \n );\n}\n\nexport { RunRail, type RunRailProps };\n",
+ "content": "import {\n ActivityItem,\n type ActivityItemProps,\n} from \"@/components/activity-item\";\nimport { BudgetPill } from \"@/components/budget-pill\";\nimport { ContextMeter } from \"@/components/context-meter\";\nimport {\n MetricDisplay,\n type MetricDisplayProps,\n} from \"@/components/metric-display\";\nimport { PlanRow, type PlanRowProps } from \"@/components/plan-row\";\nimport {\n ResourceRow,\n type ResourceRowProps,\n} from \"@/components/resource-row\";\nimport { Card } from \"@/components/ui/card\";\nimport { cn } from \"@/lib/utils\";\nimport {\n Activity,\n ChartNoAxesCombined,\n Files,\n ListChecks,\n ShieldCheck,\n} from \"lucide-react\";\n\ntype RunRailProps = {\n metrics: MetricDisplayProps[];\n activity: ActivityItemProps[];\n context: {\n used: number;\n total: number;\n threshold?: number;\n };\n budget: {\n used: number;\n limit: number;\n };\n plan?: PlanRowProps[];\n resources?: ResourceRowProps[];\n density?: \"default\" | \"compact\";\n className?: string;\n};\n\nfunction RunRail({\n metrics,\n activity,\n context,\n budget,\n plan = [],\n resources = [],\n density = \"default\",\n className,\n}: RunRailProps) {\n return (\n \n \n
\n \n Run overview\n
\n
\n {metrics.map((metric) => (\n \n ))}\n \n
\n {plan.length > 0 ? (\n \n
\n \n Plan\n \n {plan.filter((step) => step.status === \"complete\").length} /{\" \"}\n {plan.length}\n \n
\n
\n {plan.map((step, index) => (\n \n ))}\n \n
\n ) : null}\n \n
\n \n Activity\n \n {activity.some((item) => item.running) ? \"Running\" : \"Idle\"}\n \n
\n
\n {activity.map((item, index) => (\n \n ))}\n \n
\n {resources.length > 0 ? (\n \n
\n \n Changed files\n {resources.length} \n
\n
\n {resources.map((resource) => (\n \n ))}\n \n
\n ) : null}\n \n
\n \n Limits\n
\n
\n \n \n \n
\n \n );\n}\n\nexport { RunRail, type RunRailProps };\n",
"type": "registry:block",
"target": "@components/blocks/run-rail.tsx"
}
diff --git a/public/r/send-control.json b/public/r/send-control.json
index 76238ad..8f93535 100644
--- a/public/r/send-control.json
+++ b/public/r/send-control.json
@@ -12,7 +12,7 @@
"files": [
{
"path": "packages/ui/src/components/send-control.tsx",
- "content": "import { ArrowUp, CircleStop, ChevronDown } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype SendControlProps = {\n running?: boolean;\n disabled?: boolean;\n density?: \"default\" | \"compact\";\n onSend?: () => void;\n onStop?: () => void;\n onOpenOptions?: () => void;\n className?: string;\n};\n\nfunction SendControl({\n running = false,\n disabled = false,\n density = \"default\",\n onSend,\n onStop,\n onOpenOptions,\n className,\n}: SendControlProps) {\n if (running) {\n return (\n \n \n Stop run\n \n );\n }\n\n return (\n \n
\n Send\n \n \n
\n \n \n
\n );\n}\n\nexport { SendControl, type SendControlProps };\n",
+ "content": "import { ArrowUp, CircleStop, ChevronDown } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { cn } from \"@/lib/utils\";\n\ntype SendControlProps = {\n running?: boolean;\n disabled?: boolean;\n density?: \"default\" | \"compact\";\n onSend?: () => void;\n onStop?: () => void;\n onOpenOptions?: () => void;\n className?: string;\n};\n\nfunction SendControl({\n running = false,\n disabled = false,\n density = \"default\",\n onSend,\n onStop,\n onOpenOptions,\n className,\n}: SendControlProps) {\n if (running) {\n return (\n \n \n Stop run\n \n );\n }\n\n return (\n \n
\n Send\n \n \n {onOpenOptions ? (\n
\n \n \n ) : null}\n
\n );\n}\n\nexport { SendControl, type SendControlProps };\n",
"type": "registry:component",
"target": "@components/send-control.tsx"
}
diff --git a/registry.json b/registry.json
index c238559..bef35d3 100644
--- a/registry.json
+++ b/registry.json
@@ -24,7 +24,7 @@
"name": "coven-theme",
"type": "registry:theme",
"title": "Coven semantic theme",
- "description": "Nova density, Zinc-seeded structure, lavender presence, and canonical tool-class tokens.",
+ "description": "OpenCoven web profile v1.0.0 palette, typography, spacing, explicit density, and canonical tool-class tokens.",
"files": [
{
"path": "packages/ui/src/styles/globals.css",
@@ -577,7 +577,10 @@
"name": "composer",
"type": "registry:block",
"title": "Composer",
- "description": "Intent-taking surface with draft, attachments, mode, model, and send state.",
+ "description": "Intent-taking surface with draft, attachments, tools, mode guidance, model, and send state.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/attachment-chip",
"@opencoven/mode-switch",
@@ -608,12 +611,17 @@
"name": "run-rail",
"type": "registry:block",
"title": "Run rail",
- "description": "Live metrics, activity, context-window use, and budget.",
+ "description": "Live metrics, optional plan and changed files, activity, context-window use, and budget.",
+ "dependencies": [
+ "lucide-react"
+ ],
"registryDependencies": [
"@opencoven/activity-item",
"@opencoven/budget-pill",
"@opencoven/context-meter",
"@opencoven/metric-display",
+ "@opencoven/plan-row",
+ "@opencoven/resource-row",
"@opencoven/card"
],
"files": [
diff --git a/registry/blocks/registry.fragment.json b/registry/blocks/registry.fragment.json
index c10f586..87bd790 100644
--- a/registry/blocks/registry.fragment.json
+++ b/registry/blocks/registry.fragment.json
@@ -5,7 +5,8 @@
"name": "composer",
"type": "registry:block",
"title": "Composer",
- "description": "Intent-taking surface with draft, attachments, mode, model, and send state.",
+ "description": "Intent-taking surface with draft, attachments, tools, mode guidance, model, and send state.",
+ "dependencies": ["lucide-react"],
"registryDependencies": [
"attachment-chip",
"mode-switch",
@@ -28,12 +29,15 @@
"name": "run-rail",
"type": "registry:block",
"title": "Run rail",
- "description": "Live metrics, activity, context-window use, and budget.",
+ "description": "Live metrics, optional plan and changed files, activity, context-window use, and budget.",
+ "dependencies": ["lucide-react"],
"registryDependencies": [
"activity-item",
"budget-pill",
"context-meter",
"metric-display",
+ "plan-row",
+ "resource-row",
"card"
],
"files": [
diff --git a/registry/styles/registry.fragment.json b/registry/styles/registry.fragment.json
index f8f3051..af14e1f 100644
--- a/registry/styles/registry.fragment.json
+++ b/registry/styles/registry.fragment.json
@@ -5,7 +5,7 @@
"name": "coven-theme",
"type": "registry:theme",
"title": "Coven semantic theme",
- "description": "Nova density, Zinc-seeded structure, lavender presence, and canonical tool-class tokens.",
+ "description": "OpenCoven web profile v1.0.0 palette, typography, spacing, explicit density, and canonical tool-class tokens.",
"files": [
{
"path": "../../packages/ui/src/styles/globals.css",
diff --git a/scripts/mobile-quality-review.mjs b/scripts/mobile-quality-review.mjs
index 9e8c360..ddd8f56 100644
--- a/scripts/mobile-quality-review.mjs
+++ b/scripts/mobile-quality-review.mjs
@@ -62,6 +62,14 @@ const cases = [
density: "default",
textScale: 2,
},
+ {
+ name: "mobile-320-dark-wide-display",
+ width: 320,
+ scheme: "dark",
+ density: "default",
+ textScale: 2,
+ wideDisplayFont: true,
+ },
];
if (!chromePath) throw new Error("CHROME_PATH is required");
@@ -287,6 +295,9 @@ try {
document.documentElement.style.fontSize = ${JSON.stringify(
scenario.textScale ? `${scenario.textScale * 100}%` : "",
)};
+ if (${Boolean(scenario.wideDisplayFont)}) {
+ document.documentElement.style.setProperty("--oc-font-display", "ui-monospace, monospace");
+ }
})()`);
const measurement = await evaluate(`(async () => {
@@ -443,6 +454,19 @@ try {
tabListCount: cardLists.length,
activePanelCount: activePanels.length,
tabTargetCount: tabHeights.length,
+ viewTabCount: document.querySelectorAll(".specimen-view-tabs [role=tab]").length,
+ stateFooterCount: document.querySelectorAll(".specimen-states").length,
+ clippedCards: cards.filter(card => clipped(card) > 1).map(card => ({ id: card.id, overflow: clipped(card) })),
+ clippedStages: stages.filter(stage => clipped(stage) > 1).map(stage => ({
+ id: stage.closest(".specimen-card").id,
+ overflow: clipped(stage),
+ children: [...stage.querySelectorAll("*")].filter(child => clipped(child) > 1).map(child => ({
+ slot: child.dataset.slot,
+ tag: child.tagName,
+ text: child.textContent.slice(0, 80),
+ overflow: clipped(child),
+ })),
+ })),
maxCardOverflow: Math.max(0, ...cards.map(clipped)),
maxStageOverflow: Math.max(0, ...stages.map(clipped)),
maxTabRootOverflow: Math.max(0, ...cardTabRoots.map(clipped)),
@@ -480,6 +504,43 @@ try {
})()`);
const failures = [];
+ if (
+ measurement.documentOverflow > 1 ||
+ measurement.maxRailLinkContentOverflow > 1
+ ) {
+ measurement.overflowDiagnostic = await evaluate(`(() => {
+ const root = document.documentElement;
+ const owners = [];
+ for (const selector of [
+ ".specimen-topbar", ".specimen-rail", ".specimen-hero", ".catalog",
+ ".specimen-preview", ".specimen-view-tabs", ".specimen-code-tabs",
+ ".specimen-states", ".skip-link", ".sr-only",
+ ]) {
+ const elements = [...document.querySelectorAll(selector)];
+ const styles = elements.map(element => element.getAttribute("style"));
+ elements.forEach(element => element.style.setProperty("display", "none", "important"));
+ owners.push({ selector, documentOverflow: root.scrollWidth - root.clientWidth });
+ elements.forEach((element, index) => {
+ if (styles[index] === null) element.removeAttribute("style");
+ else element.setAttribute("style", styles[index]);
+ });
+ }
+ const links = [...document.querySelectorAll(".specimen-rail__nav a")].map(link => ({
+ text: link.textContent,
+ width: link.clientWidth,
+ scrollWidth: link.scrollWidth,
+ gap: getComputedStyle(link).gap,
+ children: [...link.children].map(child => ({
+ text: child.textContent, width: child.getBoundingClientRect().width,
+ scrollWidth: child.scrollWidth, minWidth: getComputedStyle(child).minWidth,
+ })),
+ }));
+ return { owners, links };
+ })()`);
+ console.error(
+ `${scenario.name} overflow diagnostic: ${JSON.stringify(measurement.overflowDiagnostic)}`,
+ );
+ }
if (measurement.cardCount !== 16) {
failures.push(`expected 16 cards, got ${measurement.cardCount}`);
}
@@ -498,9 +559,17 @@ try {
`expected 16 active card panels, got ${measurement.activePanelCount}`,
);
}
- if (measurement.tabTargetCount !== 48) {
+ if (measurement.tabTargetCount !== 32) {
+ failures.push(
+ `expected 32 code tab targets, got ${measurement.tabTargetCount}`,
+ );
+ }
+ if (
+ measurement.viewTabCount !== 32 ||
+ measurement.stateFooterCount !== 16
+ ) {
failures.push(
- `expected 48 card tab targets, got ${measurement.tabTargetCount}`,
+ "Preview/Source tabs or supported-state footers are missing",
);
}
if (measurement.documentOverflow > 1) {
@@ -561,10 +630,14 @@ try {
);
}
if (measurement.maxCardOverflow > 1) {
- failures.push(`card overflow ${measurement.maxCardOverflow}px`);
+ failures.push(
+ `card overflow: ${JSON.stringify(measurement.clippedCards)}`,
+ );
}
if (measurement.maxStageOverflow > 1) {
- failures.push(`stage overflow ${measurement.maxStageOverflow}px`);
+ failures.push(
+ `stage overflow: ${JSON.stringify(measurement.clippedStages)}`,
+ );
}
if (measurement.maxTabRootOverflow > 1) {
failures.push(`tab-root overflow ${measurement.maxTabRootOverflow}px`);
diff --git a/scripts/serve-dist.mjs b/scripts/serve-dist.mjs
new file mode 100644
index 0000000..e3419c4
--- /dev/null
+++ b/scripts/serve-dist.mjs
@@ -0,0 +1,44 @@
+import { createServer } from "node:http";
+import { readFile } from "node:fs/promises";
+import path from "node:path";
+
+const root = path.resolve(process.argv[2] ?? "apps/specimens/dist");
+const port = Number(process.argv[3] ?? 4321);
+
+const types = {
+ ".html": "text/html; charset=utf-8",
+ ".js": "text/javascript; charset=utf-8",
+ ".css": "text/css; charset=utf-8",
+ ".json": "application/json; charset=utf-8",
+ ".svg": "image/svg+xml",
+ ".png": "image/png",
+ ".woff2": "font/woff2",
+ ".map": "application/json; charset=utf-8",
+};
+
+createServer(async (request, response) => {
+ const requested = decodeURIComponent(
+ new URL(request.url, "http://x").pathname,
+ );
+ const candidate = path.join(root, requested.replace(/^\/+/, ""));
+
+ for (const file of [candidate, path.join(root, "index.html")]) {
+ if (!path.resolve(file).startsWith(root)) continue;
+ try {
+ const body = await readFile(file);
+ response.writeHead(200, {
+ "content-type": types[path.extname(file)] ?? "application/octet-stream",
+ "cache-control": "no-store",
+ });
+ response.end(body);
+ return;
+ } catch {
+ // fall through to SPA index
+ }
+ }
+
+ response.writeHead(404, { "content-type": "text/plain" });
+ response.end("not found");
+}).listen(port, "127.0.0.1", () => {
+ console.log(`serving ${root} on http://127.0.0.1:${port}`);
+});
diff --git a/scripts/test-registry-consumer.mjs b/scripts/test-registry-consumer.mjs
index 3bec70b..e0931f4 100644
--- a/scripts/test-registry-consumer.mjs
+++ b/scripts/test-registry-consumer.mjs
@@ -145,12 +145,15 @@ function run(command, args) {
}
try {
- const itemUrl = `http://127.0.0.1:${address.port}/composer.json`;
+ const items = ["composer", "run-rail"];
+ const itemUrls = items.map(
+ (item) => `http://127.0.0.1:${address.port}/${item}.json`,
+ );
await run("pnpm", [
"exec",
"shadcn",
"add",
- itemUrl,
+ ...itemUrls,
"--cwd",
consumer,
"--yes",
@@ -158,15 +161,21 @@ try {
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 item of items) {
+ const source = await readFile(
+ path.join(consumer, "src", "components", "blocks", `${item}.tsx`),
+ "utf8",
+ );
+ if (source.includes("@opencoven/ui")) {
+ throw new Error(
+ `Installed ${item} source retained package-internal aliases`,
+ );
+ }
}
- console.log(`Clean consumer installed and type-checked ${itemUrl}.`);
+ console.log(
+ `Clean consumer installed and type-checked ${items.join(" and ")}.`,
+ );
} finally {
await new Promise((resolve, reject) =>
server.close((error) => (error ? reject(error) : resolve())),
diff --git a/scripts/verify-contracts.mjs b/scripts/verify-contracts.mjs
index b7bc2a2..23dc885 100644
--- a/scripts/verify-contracts.mjs
+++ b/scripts/verify-contracts.mjs
@@ -11,6 +11,8 @@ const [
specimenCss,
specimenFixes,
specimenApp,
+ codeSnippet,
+ componentPreview,
button,
tooltip,
menu,
@@ -26,6 +28,8 @@ const [
read("apps/specimens/src/specimens.css"),
read("apps/specimens/src/specimens-fixes.css"),
read("apps/specimens/src/app.tsx"),
+ read("apps/specimens/src/code-snippet.tsx"),
+ read("apps/specimens/src/component-preview.tsx"),
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"),
@@ -41,20 +45,9 @@ const manifest = JSON.parse(packageJson);
const portable = JSON.parse(portableJson);
const vectors = JSON.parse(vectorsJson);
const specimenStyles = `${specimenCss}\n${specimenFixes}`;
-const [home, docs, catalog, codeBlock, examples, lab, main, registryJson] =
- await Promise.all([
- read("apps/specimens/src/home.tsx"),
- read("apps/specimens/src/docs.tsx"),
- read("apps/specimens/src/catalog.ts"),
- read("apps/specimens/src/code-block.tsx"),
- read("apps/specimens/src/examples.tsx"),
- read("apps/specimens/src/lab.tsx"),
- read("apps/specimens/src/main.tsx"),
- read("registry.json"),
- ]);
-const siteMarkup = [specimenApp, home, docs, codeBlock, lab].join("\n");
-const visualItems = JSON.parse(registryJson).items.filter((item) =>
- ["registry:ui", "registry:component", "registry:block"].includes(item.type),
+const specimenAt68 = specimenCss.slice(
+ specimenCss.indexOf("@media (max-width: 68rem)"),
+ specimenCss.indexOf("@media (max-width: 48rem)"),
);
const assertions = [
["style is base-nova", config.style === "base-nova"],
@@ -82,8 +75,10 @@ const assertions = [
],
[
"presence is independent from primary",
- tokens.includes("--presence: #9386d0") &&
- tokens.includes("--primary: #e4e4e7"),
+ tokens.includes("--presence: var(--oc-presence)") &&
+ tokens.includes("--primary: var(--oc-action)") &&
+ tokens.includes("--oc-presence: #b991ff") &&
+ tokens.includes("--oc-action: #8e3dff"),
],
[
"one exact radius scale is defined",
@@ -114,107 +109,168 @@ const assertions = [
),
],
[
- "site shell has stable landmarks",
- specimenApp.includes('className="site-header"') &&
- specimenApp.includes('className="skip-link"') &&
- [home, docs, lab].every((source) => source.includes('id="main-content"')),
+ "specimen shell has stable landmarks",
+ specimenApp.includes('className="specimen-topbar"') &&
+ specimenApp.includes('className="specimen-rail"') &&
+ specimenApp.includes('id="specimen-main"') &&
+ specimenApp.includes('className="skip-link"'),
],
[
- "catalog preserves task hierarchy",
- ["Foundations", "Composer controls", "Run & evidence", "Blocks"].every(
- (group) => catalog.includes(group),
- ) && docs.includes("groups.map"),
+ "catalog restores task hierarchy",
+ ["group-composer", "group-run-rail", "group-blocks"].every((id) =>
+ specimenApp.includes(id),
+ ) &&
+ specimenApp.includes('className="catalog-group__summary"') &&
+ specimenApp.includes("{group} "),
],
[
- "documentation separates registry and package consumption",
- docs.includes('id="installation"') &&
- docs.includes('label="Registry import"') &&
- docs.includes("packagePath") &&
- docs.includes("package imports"),
+ "CLI and React API tabs stay separate",
+ specimenApp.includes('CLI ') &&
+ specimenApp.includes(
+ 'React API ',
+ ) &&
+ !specimenApp.includes('API ') &&
+ specimenApp.includes('language="typescript"') &&
+ specimenApp.includes("label={`Import ${specimen.title}`}"),
],
[
- "install paths come from real registry metadata",
- catalog.includes('"../../../registry.json?raw"') &&
- catalog.includes("file.target") &&
- catalog.includes('replace("packages/ui/src/", "@opencoven/ui/")') &&
- catalog.includes("https://ui.opencoven.ai/r"),
+ "source shares the live preview viewport without resetting it",
+ componentPreview.includes('className="specimen-preview__canvas"') &&
+ componentPreview.includes("keepMounted") &&
+ componentPreview.includes("hidden={false}") &&
+ componentPreview.includes('visibility: sourceOpen ? "hidden"') &&
+ componentPreview.includes("inert={sourceOpen}") &&
+ /\.specimen-source-overlay\s*\{[^}]*position:\s*absolute;[^}]*inset:\s*0;/.test(
+ specimenCss,
+ ),
],
[
- "every visual registry item has a working example export",
- visualItems.every((item) => {
- const name = item.name
- .split("-")
- .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
- .join("");
- return examples.includes(`export function ${name}Example(`);
- }),
+ "supported states remain outside preview and source tabs",
+ specimenApp.includes('className="specimen-states"') &&
+ !specimenApp.includes('') &&
+ componentPreview.includes(''),
],
[
- "code examples are grounded in rendered source",
- catalog.includes('"./examples.tsx?raw"') &&
- docs.includes("exampleCode(entry)") &&
- docs.includes(" specimenCss.includes(`.${className}`),
+ ) &&
+ codeSnippet.includes("hljs.highlight(") &&
+ specimenApp.includes('language="bash"'),
],
[
- "density control remains explicit and persisted",
- docs.includes("Preview density") &&
- docs.includes('value="compact"') &&
- specimenApp.includes('"coven-ui:density"'),
+ "density control is explicit",
+ specimenApp.includes('aria-label="Display density"') &&
+ !specimenApp.includes("nth-child(2)"),
],
[
"mobile layout covers 390px",
- specimenCss.includes("@media (max-width: 479px)") &&
- specimenCss.includes("grid-template-columns: minmax(0, 1fr)"),
+ specimenStyles.includes("@media (max-width: 24.375rem)") &&
+ /\.specimen-grid\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\);/.test(
+ specimenCss,
+ ),
],
[
"minimum viewport floor does not scale with text",
- /html\s*\{[^}]*min-width:\s*320px/.test(specimenFixes),
+ /html\s*\{[^}]*min-width:\s*320px/.test(specimenStyles),
],
[
- "responsive documentation has labeled expandable navigation",
- docs.includes('aria-controls="docs-sidebar"') &&
- docs.includes("aria-expanded={menuOpen}") &&
- specimenCss.includes('.docs-sidebar[data-open="true"]'),
+ "responsive rail becomes compact navigation",
+ specimenAt68.startsWith("@media (max-width: 68rem)") &&
+ /\.specimen-shell\s*\{[^}]*grid-template-columns:\s*1fr;/.test(
+ specimenAt68,
+ ) &&
+ /\.specimen-rail__nav\s*\{[^}]*display:\s*flex;/.test(specimenAt68),
],
[
- "search works across documentation routes",
- specimenApp.includes("searchRef.current?.focus()") &&
- specimenApp.includes('event.key === "Escape"') &&
- !main.includes("stopImmediatePropagation"),
+ "responsive grids remove intrinsic sizing floors",
+ /\.specimen-shell[^{}]*\{[^}]*min-width:\s*0;/.test(specimenFixes) &&
+ /\.specimen-grid\s*\{[^}]*grid-template-columns:\s*minmax\(0,\s*1fr\);/.test(
+ specimenCss,
+ ),
],
[
- "search respects IME composition",
- specimenApp.includes("event.nativeEvent.isComposing") &&
- specimenApp.includes("event.keyCode === 229"),
+ "mobile catalog navigation exposes every section",
+ /\.specimen-rail__nav\s*\{[^}]*display:\s*grid;[^}]*grid-template-columns:\s*repeat\(\s*auto-fit,\s*minmax\(min\(100%,\s*5\.5rem\),\s*1fr\)\s*\);/.test(
+ specimenFixes,
+ ) && /\.specimen-rail__nav a\s*\{[^}]*min-width:\s*0;/.test(specimenFixes),
],
[
- "preview tabs use accessible primitives",
- docs.includes('Preview ') &&
- docs.includes('Code ') &&
- docs.includes(''),
+ "mobile card tabs preserve enlarged labels",
+ /\.specimen-card > \[data-slot="tabs"\] > \[data-slot="tabs-list"\]\s*\{[^}]*grid-template-columns:\s*repeat\(2,\s*minmax\(5\.25rem,\s*1fr\)\);[^}]*overflow-x:\s*auto;/.test(
+ specimenFixes,
+ ),
],
[
- "responsive previews remove intrinsic sizing floors",
- specimenFixes.includes("min-width: 0") &&
- specimenFixes.includes("max-width: 100%") &&
- specimenFixes.includes("overflow-x: auto"),
+ "mobile tab selection and focus stay inside scrollport",
+ /\.specimen-code-tabs \[role="tab"\]\[data-active\]::after\s*\{[^}]*inset-block-end:\s*0;/.test(
+ specimenFixes,
+ ) &&
+ /\.specimen-code-tabs \[role="tab"\]:focus-visible\s*\{[^}]*outline-offset:\s*-3px;/.test(
+ specimenFixes,
+ ),
],
[
- "all five lab views remain available",
- ["composer", "messages", "context", "actions", "cards"].every((name) =>
- lab.includes(``),
- ),
+ "text resize keeps shell chrome and hero contained",
+ specimenFixes.includes(
+ ".specimen-topbar__inner {\n display: flex;\n flex-wrap: wrap;",
+ ) &&
+ /\.specimen-topbar__actions\s*\{[^}]*display:\s*flex;[^}]*flex:\s*1 0 100%;[^}]*flex-wrap:\s*wrap;/.test(
+ specimenFixes,
+ ) &&
+ /\.specimen-search\s*\{[^}]*width:\s*auto;[^}]*min-width:\s*7rem;[^}]*flex:\s*1 1 10rem;/.test(
+ specimenFixes,
+ ) &&
+ /\.specimen-main__inner[^{}]*\{[^}]*box-sizing:\s*border-box;/.test(
+ specimenFixes,
+ ) &&
+ /\.specimen-stats\s*\{[^}]*grid-template-columns:\s*repeat\(3,\s*minmax\(0,\s*1fr\)\);/.test(
+ specimenStyles,
+ ),
+ ],
+ /*
+ * Optional decoration must stay outside public surfaces and yield to
+ * accessibility preferences. Flat canonical surfaces need no image reset.
+ */
+ [
+ "decorative layers stay out of public component surfaces",
+ !/\[data-slot="[^"]+"\][^{}]*\{[^}]*gradient\(/.test(specimenCss),
+ ],
+ [
+ "decorative layers yield to contrast and forced-colors preferences",
+ !/(?:gradient\(|background-image:)/.test(specimenCss) ||
+ (/@media \(prefers-contrast: more\)\s*\{[^@]*background-image:\s*none;/.test(
+ specimenCss,
+ ) &&
+ /@media \(forced-colors: active\)\s*\{[^@]*background-image:\s*none;/.test(
+ specimenCss,
+ )),
],
[
- "specimen chrome avoids decorative gradients",
- !specimenCss.includes("gradient("),
+ "specimen chrome never animates perpetually",
+ // Comments are stripped first so prose describing the rule cannot satisfy
+ // or violate it.
+ (() => {
+ const code = specimenCss.replace(/\/\*[\s\S]*?\*\//g, "");
+ return (
+ !code.includes("@keyframes") &&
+ !/animation(-name)?:\s*(?!none)/.test(code)
+ );
+ })(),
],
[
"filled action variants are explicit",
@@ -223,29 +279,44 @@ const assertions = [
];
const specimenSelectorPairs = [
- ["header actions", 'className="header-tools"', ".header-tools"],
- ["density control", 'className="density-select"', ".density-select"],
- ["documentation rail", 'className="docs-sidebar"', ".docs-sidebar"],
[
- "mobile navigation",
- 'className="docs-mobile-toggle"',
- ".docs-mobile-toggle",
+ "topbar actions",
+ 'className="specimen-topbar__actions"',
+ ".specimen-topbar__actions",
+ ],
+ ["density control", 'className="density-control"', ".density-control"],
+ ["scheme control", 'className="scheme-control"', ".scheme-control"],
+ [
+ "rail context",
+ 'className="specimen-rail__context"',
+ ".specimen-rail__context",
+ ],
+ ["rail kicker", 'className="specimen-kicker numeric"', ".specimen-kicker"],
+ [
+ "rail package",
+ 'className="specimen-rail__package"',
+ ".specimen-rail__package",
+ ],
+ ["hero", 'className="specimen-hero"', ".specimen-hero"],
+ ["hero copy", 'className="specimen-hero__copy"', ".specimen-hero__copy"],
+ ["hero stats", 'className="specimen-stats"', ".specimen-stats"],
+ [
+ "catalog eyebrow",
+ 'className="catalog-group__eyebrow numeric"',
+ ".catalog-group__eyebrow",
],
- ["homepage hero", 'className="home-hero"', ".home-hero"],
- ["live showcase", 'className="agent-showcase"', ".agent-showcase"],
[
- "component gallery",
- 'className="home-component-grid"',
- ".home-component-grid",
+ "catalog summary",
+ 'className="catalog-group__summary"',
+ ".catalog-group__summary",
],
- ["documentation preview", 'className="component-stage"', ".component-stage"],
- ["search results", 'className="search-results"', ".search-results"],
+ ["specimen grid", 'className="specimen-grid"', ".specimen-grid"],
];
for (const [name, markup, selector] of specimenSelectorPairs) {
assertions.push([
`${name} markup and CSS stay paired`,
- siteMarkup.includes(markup) && specimenStyles.includes(selector),
+ specimenApp.includes(markup) && specimenStyles.includes(selector),
]);
}
diff --git a/scripts/visual-review.mjs b/scripts/visual-review.mjs
index 60aabd0..46d0f0b 100644
--- a/scripts/visual-review.mjs
+++ b/scripts/visual-review.mjs
@@ -1,4 +1,4 @@
-import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
+import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
import { spawn } from "node:child_process";
import { tmpdir } from "node:os";
import path from "node:path";
@@ -9,6 +9,7 @@ const outputDir = path.resolve(
process.env.VISUAL_OUTPUT_DIR ?? "artifacts/visual-review",
);
const debuggingPort = Number(process.env.CHROME_DEBUGGING_PORT ?? 9222);
+const selectAllModifier = process.platform === "darwin" ? 4 : 2;
if (!chromePath) {
throw new Error("CHROME_PATH is required");
@@ -191,9 +192,382 @@ async function evaluateValue(client, expression, awaitPromise = false) {
return result.result?.value;
}
+async function waitForValue(client, expression, description) {
+ const deadline = Date.now() + 10_000;
+ while (Date.now() < deadline) {
+ if (await evaluateValue(client, expression)) return;
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ }
+ const state = await evaluateValue(
+ client,
+ `({
+ focus: document.activeElement?.tagName,
+ input: document.activeElement?.value,
+ search: document.querySelector(".specimen-search input")?.value,
+ cards: document.querySelectorAll(".specimen-card").length,
+ })`,
+ );
+ throw new Error(`Timed out: ${description}; ${JSON.stringify(state)}`);
+}
+
+async function clickElement(client, selector, text) {
+ const deadline = Date.now() + 10_000;
+ let point;
+ while (Date.now() < deadline) {
+ point = await evaluateValue(
+ client,
+ `(async () => {
+ const element = [...document.querySelectorAll(${JSON.stringify(selector)})]
+ .find(element => ${text ? `element.textContent.trim() === ${JSON.stringify(text)}` : "true"});
+ if (!element || element.disabled) return { ready: false, reason: "unavailable or disabled" };
+ element.scrollIntoView({ block: "center", inline: "nearest" });
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ const bounds = element.getBoundingClientRect();
+ const x = bounds.left + bounds.width / 2;
+ const y = bounds.top + bounds.height / 2;
+ const hit = document.elementFromPoint(x, y);
+ if (!element.contains(hit)) return { ready: false, reason: "obscured", hit: hit?.className, x, y };
+ return { ready: true, x, y };
+ })()`,
+ true,
+ );
+ if (point.ready) break;
+ await new Promise((resolve) => setTimeout(resolve, 50));
+ }
+ if (!point?.ready)
+ throw new Error(
+ `Control not clickable: ${selector} ${text ?? ""}; ${JSON.stringify(point)}`,
+ );
+ await client.send("Input.dispatchMouseEvent", {
+ type: "mousePressed",
+ button: "left",
+ clickCount: 1,
+ x: point.x,
+ y: point.y,
+ });
+ await client.send("Input.dispatchMouseEvent", {
+ type: "mouseReleased",
+ button: "left",
+ clickCount: 1,
+ x: point.x,
+ y: point.y,
+ });
+}
+
+async function pressKey(client, key, code, keyCode, modifiers = 0) {
+ for (const type of ["keyDown", "keyUp"]) {
+ await client.send("Input.dispatchKeyEvent", {
+ type,
+ key,
+ code,
+ windowsVirtualKeyCode: keyCode,
+ modifiers,
+ ...(code === "KeyA" &&
+ modifiers === selectAllModifier &&
+ type === "keyDown"
+ ? { commands: ["selectAll"] }
+ : {}),
+ });
+ }
+}
+
+async function auditInteractions(client, scenario) {
+ await pressKey(client, "k", "KeyK", 75, 2);
+ await waitForValue(
+ client,
+ `document.activeElement === document.querySelector(".specimen-search input")`,
+ "search shortcut focus",
+ );
+ await client.send("Input.insertText", { text: "no-such-specimen" });
+ await waitForRender(client, ".catalog-empty");
+ await pressKey(client, "a", "KeyA", 65, selectAllModifier);
+ await client.send("Input.insertText", { text: "Context meter" });
+ await waitForValue(
+ client,
+ `document.querySelectorAll(".specimen-card").length === 1 && Boolean(document.querySelector("#context-meter"))`,
+ "filtered specimen",
+ );
+ await pressKey(client, "a", "KeyA", 65, selectAllModifier);
+ await pressKey(client, "Backspace", "Backspace", 8);
+ await waitForValue(
+ client,
+ `document.querySelectorAll(".specimen-card").length === 16`,
+ "restored catalog",
+ );
+
+ await clickElement(client, ".scheme-control");
+ await clickElement(client, ".density-control button", "Compact");
+ await navigate(client, baseUrl);
+ await waitForValue(
+ client,
+ `!document.documentElement.classList.contains("dark") && document.documentElement.dataset.density === "compact"`,
+ "persisted display preferences",
+ );
+ await clickElement(client, ".scheme-control");
+ await clickElement(client, ".density-control button", "Cozy");
+
+ await clickElement(client, "#composer textarea");
+ await pressKey(client, "a", "KeyA", 65, selectAllModifier);
+ await client.send("Input.insertText", { text: "Preserve this audit draft" });
+ await clickElement(
+ client,
+ "#composer .specimen-view-tabs [role=tab]",
+ "Source",
+ );
+ await waitForRender(client, "#composer .specimen-source-overlay");
+ await client.send("Browser.grantPermissions", {
+ origin: new URL(baseUrl).origin,
+ permissions: ["clipboardReadWrite", "clipboardSanitizedWrite"],
+ });
+ await clickElement(
+ client,
+ '#composer .specimen-source-overlay button[aria-label="Copy composer.tsx"]',
+ );
+ await waitForValue(
+ client,
+ `document.querySelector("#composer .specimen-source-overlay [role=status]")?.textContent.includes("Copied")`,
+ "source copy feedback",
+ );
+ const copied = await evaluateValue(
+ client,
+ "navigator.clipboard.readText()",
+ true,
+ );
+ const expectedSource = await readFile(
+ "packages/ui/src/blocks/composer.tsx",
+ "utf8",
+ );
+ if (copied !== expectedSource)
+ throw new Error("Clipboard did not contain exact component source");
+ await pressKey(client, "Escape", "Escape", 27);
+ await waitForValue(
+ client,
+ `document.activeElement?.textContent.trim() === "Preview" && document.querySelector("#composer textarea")?.value === "Preserve this audit draft"`,
+ "source Escape and draft preservation",
+ );
+
+ await clickElement(client, "#composer button", "Attach sample");
+ await waitForValue(
+ client,
+ `document.querySelector("#composer").textContent.includes("changes.diff")`,
+ "sample attachment",
+ );
+ await clickElement(
+ client,
+ '#composer button[aria-label="Remove changes.diff"]',
+ );
+ await clickElement(client, "#composer button", "Commands");
+ await waitForRender(client, '[role="menu"]');
+ await pressKey(client, "End", "End", 35);
+ await pressKey(client, "Enter", "Enter", 13);
+ await waitForValue(
+ client,
+ `document.querySelector("#composer textarea").value === "Plan a focused implementation"`,
+ "slash command selection",
+ );
+ await clickElement(client, "#composer textarea");
+ await pressKey(client, "Enter", "Enter", 13, 2);
+ await waitForValue(
+ client,
+ `document.querySelector("#composer .demo-status").textContent.includes("started")`,
+ "keyboard send",
+ );
+ await clickElement(client, "#composer button", "Stop run");
+ await waitForValue(
+ client,
+ `document.querySelector("#composer .demo-status").textContent.includes("stopped")`,
+ "stop control",
+ );
+
+ const loaded = client.waitForEvent("Page.loadEventFired");
+ await clickElement(client, '.surface-switcher a[href="/lab"]');
+ await loaded;
+ await waitForRender(client, ".assembled-lab");
+ await clickElement(client, ".assembled-lab textarea");
+ await pressKey(client, "a", "KeyA", 65, selectAllModifier);
+ await client.send("Input.insertText", { text: "Keep this carousel draft" });
+ await clickElement(client, 'button[aria-label="Next scene"]');
+ await waitForValue(
+ client,
+ `document.querySelector(".assembled-lab__tabs [aria-selected=true]").dataset.sceneId === "run-rail"`,
+ "next scene",
+ );
+ await clickElement(client, 'button[aria-label="Previous scene"]');
+ await waitForValue(
+ client,
+ `document.querySelector(".lab-scene-panel:not([hidden]) textarea")?.value === "Keep this carousel draft"`,
+ "carousel draft preservation",
+ );
+ await clickElement(client, ".assembled-lab__tabs [data-scene-id=composer]");
+ await pressKey(client, "ArrowRight", "ArrowRight", 39);
+ await waitForValue(
+ client,
+ `document.querySelector(".assembled-lab__tabs [aria-selected=true]").dataset.sceneId === "run-rail"`,
+ "keyboard scene navigation",
+ );
+ await clickElement(client, ".assembled-lab__tabs [data-scene-id=actions]");
+ await clickElement(
+ client,
+ ".lab-action-grid button",
+ "Attach contextA sample diff, ready to reference",
+ );
+ await waitForValue(
+ client,
+ `document.querySelector(".lab-action-result").textContent.includes("changes.diff")`,
+ "action result",
+ );
+ await clickElement(client, ".assembled-lab__tabs [data-scene-id=cards]");
+ await clickElement(client, ".lab-artifact summary");
+ await waitForValue(
+ client,
+ `Boolean(document.querySelector(".lab-artifact details[open]"))`,
+ "artifact disclosure",
+ );
+ await clickElement(client, ".assembled-lab__tabs [data-scene-id=actions]");
+ await waitForValue(
+ client,
+ `document.querySelector(".lab-action-result").textContent.includes("changes.diff")`,
+ "persistent action state",
+ );
+ return `${scenario.mobile ? "Mobile" : "Desktop"} search, preferences, clipboard, source, composer, navigation, and artifact flows passed`;
+}
+
+async function captureContactSheets(client, receipts, name) {
+ const sheets = [];
+ for (let offset = 0; offset < receipts.length; offset += 8) {
+ const images = [];
+ for (const receipt of receipts.slice(offset, offset + 8)) {
+ const image = await readFile(path.join(outputDir, receipt.screenshot));
+ images.push({
+ label: receipt.id,
+ source: `data:image/png;base64,${image.toString("base64")}`,
+ });
+ }
+ const data = await evaluateValue(
+ client,
+ `(async () => {
+ const canvas = document.createElement("canvas");
+ canvas.width = 1120;
+ canvas.height = 1760;
+ const context = canvas.getContext("2d");
+ context.fillStyle = "#211a2b";
+ context.fillRect(0, 0, canvas.width, canvas.height);
+ const images = ${JSON.stringify(images)};
+ for (let index = 0; index < images.length; index++) {
+ const image = new Image();
+ image.src = images[index].source;
+ await image.decode();
+ const scale = Math.min(544 / image.width, 400 / image.height);
+ const x = (index % 2) * 560 + 8;
+ const y = Math.floor(index / 2) * 440;
+ context.fillStyle = "#f7f3fa";
+ context.font = "16px monospace";
+ context.fillText(images[index].label, x, y + 24);
+ context.drawImage(image, x, y + 32, image.width * scale, image.height * scale);
+ }
+ return canvas.toDataURL("image/png").split(",")[1];
+ })()`,
+ true,
+ );
+ const filename = `${name}-sheet-${offset / 8 + 1}.png`;
+ await writeFile(
+ path.join(outputDir, filename),
+ Buffer.from(data, "base64"),
+ );
+ sheets.push(filename);
+ }
+ return sheets;
+}
+
+async function auditInventory(client, scenario) {
+ const items = await evaluateValue(
+ client,
+ `[...document.querySelectorAll(".specimen-card")].map(card => ({ id: card.id, kind: card.dataset.kind }))`,
+ );
+ const receipts = [];
+ for (const item of items) {
+ const result = await evaluateValue(
+ client,
+ `(async () => {
+ const card = document.getElementById(${JSON.stringify(item.id)});
+ card.scrollIntoView({ block: "center" });
+ const frame = () => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ await frame();
+ const stage = card.querySelector(".specimen-stage");
+ const before = { page: document.documentElement.scrollHeight, card: card.getBoundingClientRect().height, scroll: scrollY };
+ const sourceTab = [...card.querySelectorAll(".specimen-view-tabs [role=tab]")].find(tab => tab.textContent.trim() === "Source");
+ sourceTab.focus({ preventScroll: true });
+ sourceTab.click();
+ await frame();
+ const overlay = card.querySelector(".specimen-source-overlay");
+ const canvas = card.querySelector(".specimen-preview__canvas");
+ const source = overlay.querySelector("code").textContent;
+ const result = {
+ source,
+ stable: Math.abs(before.page - document.documentElement.scrollHeight) < 1 &&
+ Math.abs(before.card - card.getBoundingClientRect().height) < 1 && Math.abs(before.scroll - scrollY) < 1,
+ covers: Math.abs(overlay.getBoundingClientRect().height - canvas.getBoundingClientRect().height) < 1 &&
+ card.querySelector(".specimen-live-panel").inert,
+ overflow: Math.max(card.scrollWidth - card.clientWidth, stage.scrollWidth - stage.clientWidth),
+ };
+ const previewTab = [...card.querySelectorAll(".specimen-view-tabs [role=tab]")].find(tab => tab.textContent.trim() === "Preview");
+ previewTab.focus({ preventScroll: true });
+ previewTab.click();
+ await frame();
+ return result;
+ })()`,
+ true,
+ );
+ const expected = await readFile(
+ `packages/ui/src/${item.kind}/${item.id}.tsx`,
+ "utf8",
+ );
+ if (
+ !result.stable ||
+ !result.covers ||
+ result.overflow > 1 ||
+ result.source !== expected
+ ) {
+ throw new Error(
+ `${scenario.name}/${item.id}: inventory mismatch ${JSON.stringify({ ...result, source: result.source.length })}`,
+ );
+ }
+ const screenshot = `${scenario.name}-${item.id}.png`;
+ const clip = await evaluateValue(
+ client,
+ `(() => {
+ const rect = document.getElementById(${JSON.stringify(item.id)}).getBoundingClientRect();
+ return { x: rect.left + scrollX, y: rect.top + scrollY, width: rect.width, height: rect.height, scale: 1 };
+ })()`,
+ );
+ const image = await client.send("Page.captureScreenshot", {
+ format: "png",
+ fromSurface: true,
+ captureBeyondViewport: true,
+ clip,
+ });
+ await writeFile(
+ path.join(outputDir, screenshot),
+ Buffer.from(image.data, "base64"),
+ );
+ receipts.push({
+ id: item.id,
+ screenshot,
+ stable: result.stable,
+ overflow: result.overflow,
+ });
+ }
+ return {
+ receipts,
+ sheets: await captureContactSheets(client, receipts, scenario.name),
+ };
+}
+
const scenarios = [
{
name: "library-dark-desktop",
+ auditInventory: true,
+ auditInteractions: true,
pathname: "/",
width: 1440,
height: 1000,
@@ -204,6 +578,8 @@ const scenarios = [
},
{
name: "library-dark-mobile",
+ auditInventory: true,
+ auditInteractions: true,
pathname: "/",
width: 390,
height: 844,
@@ -213,7 +589,7 @@ const scenarios = [
expected: "library",
},
{
- name: "library-install-desktop",
+ name: "library-cli-desktop",
pathname: "/",
width: 1440,
height: 1000,
@@ -221,15 +597,34 @@ const scenarios = [
density: "default",
mobile: false,
expected: "library",
- revealInstall: true,
- installTargetId: "mode-switch",
- expectedCli:
- "pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/mode-switch.json",
- expectedImport:
- 'import { ModeSwitch } from "@opencoven/ui/components/mode-switch";',
+ revealCodeTab: "CLI",
+ codeTargetId: "mode-switch",
+ expectedCode:
+ 'pnpm dlx shadcn@latest add "https://ui.opencoven.ai/r/mode-switch.json"',
+ expectedSyntaxRoles: [
+ "hljs-built_in",
+ "hljs-keyword",
+ "hljs-title",
+ "hljs-string",
+ ],
},
{
- name: "library-install-mobile-text-200",
+ name: "library-react-api-desktop",
+ pathname: "/",
+ width: 1440,
+ height: 1000,
+ scheme: "light",
+ density: "compact",
+ mobile: false,
+ expected: "library",
+ revealCodeTab: "React API",
+ codeTargetId: "session-header",
+ expectedCode:
+ 'import { SessionHeader } from "@opencoven/ui/blocks/session-header";',
+ expectedSyntaxRoles: ["hljs-keyword", "hljs-string"],
+ },
+ {
+ name: "library-react-api-mobile-text-200",
pathname: "/",
width: 320,
height: 900,
@@ -238,15 +633,15 @@ const scenarios = [
mobile: true,
expected: "library",
textScale: 2,
- revealInstall: true,
- installTargetId: "session-header",
- expectedCli:
- "pnpm dlx shadcn@latest add https://ui.opencoven.ai/r/session-header.json",
- expectedImport:
+ revealCodeTab: "React API",
+ codeTargetId: "session-header",
+ expectedCode:
'import { SessionHeader } from "@opencoven/ui/blocks/session-header";',
+ expectedSyntaxRoles: ["hljs-keyword", "hljs-string"],
},
{
name: "library-light-desktop",
+ auditInventory: true,
pathname: "/",
width: 1440,
height: 1000,
@@ -255,6 +650,74 @@ const scenarios = [
mobile: false,
expected: "library",
},
+ {
+ name: "library-forced-colors-desktop",
+ pathname: "/",
+ width: 1440,
+ height: 1000,
+ scheme: "dark",
+ density: "default",
+ mobile: false,
+ expected: "library",
+ forcedColors: true,
+ },
+ {
+ name: "library-composer-preview",
+ pathname: "/",
+ width: 1440,
+ height: 1000,
+ scheme: "dark",
+ density: "default",
+ mobile: false,
+ expected: "library",
+ previewTargetId: "composer",
+ codeTargetId: "composer",
+ },
+ {
+ name: "library-run-rail-preview",
+ pathname: "/",
+ width: 1440,
+ height: 1000,
+ scheme: "light",
+ density: "default",
+ mobile: false,
+ expected: "library",
+ previewTargetId: "run-rail",
+ codeTargetId: "run-rail",
+ expectedRunRailColumns: 2,
+ },
+ {
+ name: "library-composer-source",
+ pathname: "/",
+ width: 1440,
+ height: 1000,
+ scheme: "dark",
+ density: "default",
+ mobile: false,
+ expected: "library",
+ revealSource: true,
+ codeTargetId: "composer",
+ expectedCode: (
+ await readFile("packages/ui/src/blocks/composer.tsx", "utf8")
+ ).trim(),
+ expectedSyntaxRoles: ["hljs-keyword", "hljs-string", "hljs-name"],
+ },
+ {
+ name: "library-source-mobile",
+ pathname: "/",
+ width: 390,
+ height: 844,
+ scheme: "light",
+ density: "default",
+ mobile: true,
+ expected: "library",
+ revealSource: true,
+ codeTargetId: "mode-switch",
+ expectedCode: (
+ await readFile("packages/ui/src/components/mode-switch.tsx", "utf8")
+ ).trim(),
+ expectedSyntaxRoles: ["hljs-keyword", "hljs-string", "hljs-name"],
+ },
{
name: "library-dark-desktop-text-200",
pathname: "/",
@@ -268,6 +731,7 @@ const scenarios = [
},
{
name: "assembled-dark-desktop",
+ auditScenes: true,
pathname: "/lab",
width: 1440,
height: 1000,
@@ -275,9 +739,11 @@ const scenarios = [
density: "default",
mobile: false,
expected: "lab",
+ selectedScene: "run-rail",
},
{
name: "assembled-dark-mobile",
+ auditScenes: true,
pathname: "/lab",
width: 390,
height: 844,
@@ -286,6 +752,49 @@ const scenarios = [
mobile: true,
expected: "lab",
},
+ ...[
+ {
+ width: 320,
+ height: 700,
+ scheme: "dark",
+ density: "default",
+ mobile: true,
+ },
+ {
+ width: 390,
+ height: 844,
+ scheme: "light",
+ density: "default",
+ mobile: true,
+ },
+ {
+ width: 1280,
+ height: 720,
+ scheme: "light",
+ density: "default",
+ mobile: false,
+ },
+ {
+ width: 844,
+ height: 390,
+ scheme: "dark",
+ density: "compact",
+ mobile: true,
+ },
+ {
+ width: 1280,
+ height: 900,
+ scheme: "dark",
+ density: "default",
+ mobile: false,
+ textScale: 2,
+ },
+ ].map((scenario) => ({
+ ...scenario,
+ name: `assembled-${scenario.scheme}-${scenario.width}x${scenario.height}${scenario.textScale ? "-zoom" : ""}`,
+ pathname: "/lab",
+ expected: "lab",
+ })),
];
await mkdir(outputDir, { recursive: true });
@@ -317,6 +826,13 @@ chrome.stderr.on("data", (chunk) => chromeOutput.push(String(chunk)));
let client;
const results = [];
+const selectedScenarios = process.env.VISUAL_SCENARIO
+ ? scenarios.filter((scenario) =>
+ scenario.name.includes(process.env.VISUAL_SCENARIO),
+ )
+ : scenarios;
+if (selectedScenarios.length === 0)
+ throw new Error("No matching visual-review scenario");
try {
const page = await waitForPageTarget(
@@ -331,7 +847,16 @@ try {
features: [{ name: "prefers-reduced-motion", value: "reduce" }],
});
- for (const scenario of scenarios) {
+ for (const scenario of selectedScenarios) {
+ await client.send("Emulation.setEmulatedMedia", {
+ features: [
+ { name: "prefers-reduced-motion", value: "reduce" },
+ {
+ name: "forced-colors",
+ value: scenario.forcedColors ? "active" : "none",
+ },
+ ],
+ });
const runtimeErrors = [];
const removeExceptionListener = client.on(
"Runtime.exceptionThrown",
@@ -364,6 +889,10 @@ try {
screenWidth: scenario.width,
screenHeight: scenario.height,
});
+ await client.send("Emulation.setTouchEmulationEnabled", {
+ enabled: scenario.mobile,
+ maxTouchPoints: 1,
+ });
await navigate(client, new URL("/", baseUrl).href);
await evaluateValue(
@@ -393,20 +922,47 @@ try {
})()`,
true,
);
- if (scenario.revealInstall) {
+ let sourceLayoutBefore;
+ if (scenario.revealSource) {
+ sourceLayoutBefore = await evaluateValue(
+ client,
+ `(async () => {
+ const card = document.getElementById(${JSON.stringify(scenario.codeTargetId)});
+ card.scrollIntoView({ block: "center" });
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ const before = {
+ pageHeight: document.documentElement.scrollHeight,
+ cardHeight: card.getBoundingClientRect().height,
+ scrollY,
+ };
+ [...card.querySelectorAll(".specimen-view-tabs [role=tab]")].find(tab => tab.textContent.trim() === "Source").click();
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ return before;
+ })()`,
+ true,
+ );
+ } else if (scenario.revealCodeTab) {
await evaluateValue(
client,
`(async () => {
const card = document.getElementById(${JSON.stringify(
- scenario.installTargetId,
+ scenario.codeTargetId,
)});
- const installTab = card
+ const codeTab = card
? [...card.querySelectorAll('[role="tab"]')].find(
- (tab) => tab.textContent?.trim() === "Install",
+ (tab) =>
+ tab.textContent?.trim() === ${JSON.stringify(
+ scenario.revealCodeTab,
+ )},
)
: null;
- if (!installTab) throw new Error("Install tab is missing");
- installTab.click();
+ if (!codeTab) throw new Error("Requested code tab is missing");
+ const tabList = codeTab.closest('[data-slot="tabs-list"]');
+ if (${Boolean(scenario.expectScrollableCodeTabs)} && tabList) {
+ codeTab.scrollIntoView({ block: "nearest", inline: "nearest" });
+ await new Promise((resolve) => requestAnimationFrame(resolve));
+ }
+ codeTab.click();
card.scrollIntoView({ block: "center" });
await new Promise((resolve) =>
requestAnimationFrame(() => requestAnimationFrame(resolve)),
@@ -415,6 +971,15 @@ try {
})()`,
true,
);
+ } else if (scenario.previewTargetId) {
+ await evaluateValue(
+ client,
+ `(async () => {
+ document.getElementById(${JSON.stringify(scenario.previewTargetId)}).scrollIntoView({ block: "center" });
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ })()`,
+ true,
+ );
}
const layout = await evaluateValue(
@@ -433,21 +998,23 @@ try {
".specimen-brand, .surface-switcher, .specimen-search, .density-control, .scheme-control",
),
];
- const installCard = document.getElementById(${JSON.stringify(
- scenario.installTargetId ?? "mode-switch",
+ const codeCard = document.getElementById(${JSON.stringify(
+ scenario.codeTargetId ?? "mode-switch",
)});
- const installGrid = installCard?.querySelector(
- ".specimen-install-grid",
- );
- const installSnippets = installGrid
- ? [...installGrid.querySelectorAll(".specimen-code-snippet")]
+ const codeScope = ${Boolean(scenario.revealSource)} ? codeCard?.querySelector(".specimen-source-overlay") : codeCard;
+ const codeSnippets = codeScope
+ ? [...codeScope.querySelectorAll(".specimen-code-snippet")]
: [];
- const installCommands = installGrid
- ? [...installGrid.querySelectorAll(".specimen-command")]
+ const codeCommands = codeScope
+ ? [...codeScope.querySelectorAll(".specimen-command")]
: [];
- const syntaxTokens = installGrid
- ? [...installGrid.querySelectorAll('[class^="syntax-"]')]
+ const syntaxTokens = codeScope
+ ? [...codeScope.querySelectorAll('[class^="hljs-"]')]
: [];
+ const codeTabList = codeCard?.querySelector('.specimen-code-tabs');
+ const activeCodeTab = codeTabList?.querySelector(
+ '[role="tab"][aria-selected="true"]',
+ );
const cards = [...document.querySelectorAll(".specimen-card")];
const groups = [...document.querySelectorAll(".catalog-group")];
const lab = document.querySelector(".assembled-lab");
@@ -507,6 +1074,11 @@ try {
(candidate) => control.label + " / " + candidate.label,
),
);
+ const visibleCodeSnippets = codeSnippets.filter(isVisible);
+ const visibleCodeCommands = codeCommands.filter(isVisible);
+ const visibleSyntaxTokens = syntaxTokens.filter(isVisible);
+ const codeTabListBounds = bounds(codeTabList);
+ const activeCodeTabBounds = bounds(activeCodeTab);
return {
pathname: location.pathname,
@@ -514,6 +1086,41 @@ try {
viewportWidth: root.clientWidth,
scrollWidth: root.scrollWidth,
horizontalOverflow: Math.max(0, root.scrollWidth - root.clientWidth),
+ verticalOverflow: Math.max(0, root.scrollHeight - innerHeight),
+ pageHeight: root.scrollHeight,
+ sourceCardHeight: codeCard?.getBoundingClientRect().height,
+ scrollY,
+ sourceCoversPreview: (() => {
+ const overlay = codeCard?.querySelector(".specimen-source-overlay");
+ const canvas = codeCard?.querySelector(".specimen-preview__canvas");
+ if (!overlay || !canvas) return false;
+ const a = overlay.getBoundingClientRect();
+ const b = canvas.getBoundingClientRect();
+ const live = codeCard.querySelector(".specimen-live-panel");
+ const samplePoints = [
+ [a.left + 8, a.top + 8], [a.right - 8, a.top + 8],
+ [a.left + 8, a.bottom - 8], [a.right - 8, a.bottom - 8],
+ [a.left + a.width / 2, a.top + a.height / 2],
+ ];
+ return ["top","left","width","height"].every(key => Math.abs(a[key] - b[key]) < 1) &&
+ live.inert && live.getAttribute("aria-hidden") === "true" &&
+ samplePoints.every(([x, y]) => overlay.contains(document.elementFromPoint(x, y)));
+ })(),
+ sourceDimensions: (() => {
+ const overlay = codeCard?.querySelector(".specimen-source-overlay");
+ const canvas = codeCard?.querySelector(".specimen-preview__canvas");
+ const live = codeCard?.querySelector(".specimen-live-panel");
+ return overlay && canvas ? {
+ overlay: overlay.getBoundingClientRect().toJSON(),
+ canvas: canvas.getBoundingClientRect().toJSON(),
+ visibility: getComputedStyle(live).visibility,
+ inert: live.inert,
+ ariaHidden: live.getAttribute("aria-hidden"),
+ inlineStyle: live.getAttribute("style"),
+ } : null;
+ })(),
+ previewVisible: isVisible(codeCard?.querySelector(".specimen-preview")),
+ logoMask: getComputedStyle(document.querySelector(".specimen-brand__mark > span")).maskImage,
topbarVisible: isVisible(topbar),
railVisible: isVisible(rail),
mainVisible: isVisible(main),
@@ -525,32 +1132,55 @@ try {
heroHeight: heroBounds?.height ?? null,
contentTop: contentBounds?.top ?? null,
cardCount: cards.length,
+ stateFooterCount: document.querySelectorAll(".specimen-states").length,
+ libraryColumns: [...document.querySelectorAll(".specimen-grid")].map(grid => getComputedStyle(grid).gridTemplateColumns.split(" ").length),
+ runRailColumns: (() => {
+ const rail = codeCard?.querySelector('.specimen-stage > [data-slot="run-rail"]');
+ return rail ? getComputedStyle(rail).gridTemplateColumns.split(" ").length : null;
+ })(),
+ statesTabCount: [...document.querySelectorAll(".specimen-card [role=tab]")].filter(tab => tab.textContent.trim() === "States").length,
+ brandAction: getComputedStyle(root).getPropertyValue("--oc-action").trim(),
+ brandSpacing: getComputedStyle(root).getPropertyValue("--oc-space-6").trim(),
+ displayFont: getComputedStyle(document.querySelector("h1")).fontFamily,
+ forcedSelectionVisible: (() => {
+ const tab = document.querySelector(".specimen-view-tabs [aria-selected=true]");
+ if (!tab) return false;
+ const style = getComputedStyle(tab);
+ return style.outlineStyle === "solid" && parseFloat(style.outlineWidth) >= 2;
+ })(),
groupCount: groups.length,
labVisible: isVisible(lab),
tabCount: tabs.length,
scheme: root.classList.contains("dark") ? "dark" : "light",
density: root.dataset.density,
- installVisible: isVisible(installGrid),
- installSnippetCount: installSnippets.length,
- installCommandTexts: installCommands.map((command) =>
+ codeSnippetCount: visibleCodeSnippets.length,
+ codeCommandTexts: visibleCodeCommands.map((command) =>
command.textContent?.trim(),
),
- maxInstallCommandOverflow: Math.max(
+ maxCodeCommandOverflow: Math.max(
0,
- ...installCommands.map((command) =>
+ ...visibleCodeCommands.map((command) =>
Math.max(0, command.scrollWidth - command.clientWidth),
),
),
syntaxRoles: [
...new Set(
- syntaxTokens.flatMap((token) =>
+ visibleSyntaxTokens.flatMap((token) =>
[...token.classList].filter((name) =>
- name.startsWith("syntax-"),
+ name.startsWith("hljs-"),
),
),
),
].sort(),
- syntaxTokenCount: syntaxTokens.length,
+ syntaxTokenCount: visibleSyntaxTokens.length,
+ codeTabListOverflow: codeTabList
+ ? Math.max(0, codeTabList.scrollWidth - codeTabList.clientWidth)
+ : null,
+ activeCodeTabFullyVisible:
+ codeTabListBounds && activeCodeTabBounds
+ ? activeCodeTabBounds.left >= codeTabListBounds.left - 1 &&
+ activeCodeTabBounds.right <= codeTabListBounds.right + 1
+ : null,
topbarOverlaps,
internallyClipped,
};
@@ -579,7 +1209,30 @@ try {
);
const failures = [];
- if (!layout.topbarVisible || !layout.railVisible || !layout.mainVisible) {
+ if (scenario.forcedColors && !layout.forcedSelectionVisible) {
+ failures.push("selected preview tab loses its forced-colors indicator");
+ }
+ if (
+ scenario.expectedRunRailColumns &&
+ layout.runRailColumns !== scenario.expectedRunRailColumns
+ ) {
+ failures.push("run rail does not use the available preview width");
+ }
+ if (
+ layout.brandAction !==
+ (scenario.scheme === "dark" ? "#8e3dff" : "#7a22ee") ||
+ layout.brandSpacing !== "1.5rem" ||
+ !layout.displayFont.startsWith("Geist")
+ ) {
+ failures.push(
+ `canonical brand mismatch: ${layout.brandAction}, ${layout.brandSpacing}, ${layout.displayFont}`,
+ );
+ }
+ if (
+ !layout.topbarVisible ||
+ (scenario.expected !== "lab" && !layout.railVisible) ||
+ !layout.mainVisible
+ ) {
failures.push("required shell landmarks are not visible");
}
if (layout.horizontalOverflow > 1) {
@@ -593,27 +1246,45 @@ try {
);
}
if (
- scenario.revealInstall &&
- (!layout.installVisible ||
- layout.installSnippetCount !== 2 ||
- layout.syntaxTokenCount < 8 ||
- layout.maxInstallCommandOverflow > 1 ||
- !layout.installCommandTexts.includes(scenario.expectedCli) ||
- !layout.installCommandTexts.includes(scenario.expectedImport) ||
- ![
- "syntax-command",
- "syntax-keyword",
- "syntax-package",
- "syntax-punctuation",
- "syntax-string",
- "syntax-symbol",
- ].every((role) => layout.syntaxRoles.includes(role)))
+ (scenario.revealCodeTab || scenario.revealSource) &&
+ (layout.codeSnippetCount !== 1 ||
+ layout.syntaxTokenCount < 3 ||
+ !layout.previewVisible ||
+ layout.maxCodeCommandOverflow > 1 ||
+ !layout.codeCommandTexts.includes(scenario.expectedCode) ||
+ !scenario.expectedSyntaxRoles.every((role) =>
+ layout.syntaxRoles.includes(role),
+ ) ||
+ (scenario.expectScrollableCodeTabs &&
+ (layout.codeTabListOverflow === null ||
+ layout.codeTabListOverflow <= 1 ||
+ !layout.activeCodeTabFullyVisible)))
) {
failures.push(
- `install panel visible=${layout.installVisible} snippets=${layout.installSnippetCount} syntaxTokens=${layout.syntaxTokenCount} overflow=${layout.maxInstallCommandOverflow}px commands=${layout.installCommandTexts.join(" | ")} roles=${layout.syntaxRoles.join(",")}`,
+ `code panel=${scenario.revealCodeTab} snippets=${layout.codeSnippetCount} syntaxTokens=${layout.syntaxTokenCount} commandOverflow=${layout.maxCodeCommandOverflow}px tabOverflow=${layout.codeTabListOverflow}px activeTabVisible=${layout.activeCodeTabFullyVisible} commands=${layout.codeCommandTexts.join(" | ")} roles=${layout.syntaxRoles.join(",")}`,
);
}
if (
+ sourceLayoutBefore &&
+ (Math.abs(layout.pageHeight - sourceLayoutBefore.pageHeight) > 1 ||
+ Math.abs(layout.sourceCardHeight - sourceLayoutBefore.cardHeight) > 1 ||
+ Math.abs(layout.scrollY - sourceLayoutBefore.scrollY) > 1 ||
+ !layout.sourceCoversPreview)
+ ) {
+ failures.push(
+ `source changed layout: before=${JSON.stringify(sourceLayoutBefore)} after=${JSON.stringify(
+ {
+ pageHeight: layout.pageHeight,
+ cardHeight: layout.sourceCardHeight,
+ scrollY: layout.scrollY,
+ coversPreview: layout.sourceCoversPreview,
+ dimensions: layout.sourceDimensions,
+ },
+ )}`,
+ );
+ }
+ if (
+ scenario.expected !== "lab" &&
!scenario.mobile &&
(layout.stickyRailClearance === null || layout.stickyRailClearance < -1)
) {
@@ -674,7 +1345,11 @@ try {
}
if (
scenario.expected === "library" &&
- (layout.cardCount !== 16 || layout.groupCount !== 3)
+ (layout.cardCount !== 16 ||
+ layout.groupCount !== 3 ||
+ layout.stateFooterCount !== 16 ||
+ layout.statesTabCount !== 0 ||
+ layout.libraryColumns.some((count) => count !== 1))
) {
failures.push(
`library rendered ${layout.cardCount} cards across ${layout.groupCount} groups`,
@@ -682,12 +1357,132 @@ try {
}
if (
scenario.expected === "lab" &&
- (!layout.labVisible || layout.tabCount !== 5)
+ (!layout.labVisible ||
+ layout.tabCount !== 6 ||
+ layout.verticalOverflow > 1)
) {
failures.push(
- `assembled lab rendered visible=${layout.labVisible} tabs=${layout.tabCount}`,
+ `assembled lab rendered visible=${layout.labVisible} tabs=${layout.tabCount} page overflow=${layout.verticalOverflow}px`,
);
}
+ if (!layout.logoMask.includes("opencoven-mark.svg")) {
+ failures.push("canonical OpenCoven mark is missing");
+ }
+ if (scenario.expected === "lab") {
+ const sceneResults = await evaluateValue(
+ client,
+ `(async () => {
+ const results = [];
+ const tabs = [...document.querySelectorAll(".assembled-lab__tabs [role=tab]")];
+ for (const tab of tabs) {
+ tab.click();
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ const panels = [...document.querySelectorAll(".lab-scene-panel:not([hidden])")];
+ const panel = panels[0];
+ const nav = document.querySelector(".assembled-lab__nav").getBoundingClientRect();
+ const bounds = panel?.getBoundingClientRect();
+ results.push({
+ scene: tab.textContent.trim(),
+ panels: panels.length,
+ selected: tab.getAttribute("aria-selected"),
+ pageOverflow: document.documentElement.scrollHeight - innerHeight,
+ horizontalOverflow: panel ? panel.scrollWidth - panel.clientWidth : -1,
+ panelHeight: bounds?.height ?? 0,
+ panelOverflow: panel ? panel.scrollHeight - panel.clientHeight : -1,
+ navVisible: nav.top >= 0 && nav.bottom <= innerHeight,
+ });
+ }
+ tabs.find(tab => tab.getAttribute("data-scene-id") === ${JSON.stringify(scenario.selectedScene ?? "composer")})?.click();
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ return results;
+ })()`,
+ true,
+ );
+ layout.scenes = sceneResults;
+ if (scenario.auditScenes) {
+ const receipts = [];
+ for (const scene of sceneResults) {
+ await evaluateValue(
+ client,
+ `(async () => {
+ [...document.querySelectorAll(".assembled-lab__tabs [role=tab]")]
+ .find(tab => tab.textContent.trim() === ${JSON.stringify(scene.scene)}).click();
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ })()`,
+ true,
+ );
+ const screenshot = `${scenario.name}-${scene.scene.toLowerCase().replaceAll(" ", "-")}.png`;
+ const image = await client.send("Page.captureScreenshot", {
+ format: "png",
+ fromSurface: true,
+ captureBeyondViewport: false,
+ });
+ await writeFile(
+ path.join(outputDir, screenshot),
+ Buffer.from(image.data, "base64"),
+ );
+ receipts.push({ id: scene.scene, screenshot });
+ }
+ layout.sceneSheets = await captureContactSheets(
+ client,
+ receipts,
+ scenario.name,
+ );
+ await evaluateValue(
+ client,
+ `document.querySelector('.assembled-lab__tabs [data-scene-id="${scenario.selectedScene ?? "composer"}"]').click()`,
+ );
+ }
+ for (const scene of sceneResults) {
+ if (
+ scene.panels !== 1 ||
+ scene.selected !== "true" ||
+ scene.pageOverflow > 1 ||
+ scene.horizontalOverflow > 1 ||
+ scene.panelHeight < 80 ||
+ !scene.navVisible ||
+ (!scenario.mobile && !scenario.textScale && scene.panelOverflow > 1)
+ ) {
+ failures.push(`scene layout: ${JSON.stringify(scene)}`);
+ }
+ }
+ if (scenario.width === 390 && scenario.scheme === "dark") {
+ const swipeTarget = await evaluateValue(
+ client,
+ `(() => {
+ const viewport = document.querySelector(".lab-carousel__viewport").getBoundingClientRect();
+ return { x: viewport.right - 30, y: viewport.top + 15 };
+ })()`,
+ );
+ await client.send("Input.dispatchTouchEvent", {
+ type: "touchStart",
+ touchPoints: [swipeTarget],
+ });
+ await client.send("Input.dispatchTouchEvent", {
+ type: "touchMove",
+ touchPoints: [{ x: swipeTarget.x - 120, y: swipeTarget.y }],
+ });
+ await client.send("Input.dispatchTouchEvent", {
+ type: "touchEnd",
+ touchPoints: [],
+ });
+ await evaluateValue(
+ client,
+ `new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)))`,
+ true,
+ );
+ const swipedScene = await evaluateValue(
+ client,
+ `document.querySelector(".assembled-lab__tabs [aria-selected=true]")?.textContent.trim()`,
+ );
+ if (swipedScene !== "Run rail")
+ failures.push(`touch swipe selected ${swipedScene}`);
+ await evaluateValue(
+ client,
+ `document.querySelector(".assembled-lab__tabs [data-scene-id=composer]").click()`,
+ );
+ }
+ }
if (runtimeErrors.length > 0) {
failures.push(`runtime errors: ${runtimeErrors.join(" | ")}`);
}
@@ -700,6 +1495,19 @@ try {
const screenshotPath = path.join(outputDir, `${scenario.name}.png`);
await writeFile(screenshotPath, Buffer.from(screenshot.data, "base64"));
+ if (scenario.auditInventory) {
+ layout.inventory = await auditInventory(client, scenario);
+ }
+ if (scenario.auditInteractions) {
+ layout.interactions = await auditInteractions(client, scenario);
+ }
+ if (
+ runtimeErrors.length > 0 &&
+ failures.every((failure) => !failure.startsWith("runtime errors:"))
+ ) {
+ failures.push(`runtime errors: ${runtimeErrors.join(" | ")}`);
+ }
+
results.push({
...scenario,
layout,
diff --git a/scripts/zoom-probe.mjs b/scripts/zoom-probe.mjs
new file mode 100644
index 0000000..3c61b38
--- /dev/null
+++ b/scripts/zoom-probe.mjs
@@ -0,0 +1,88 @@
+/*
+ * Detail probe for the specimen shell's ritual-tech finish.
+ *
+ * visual-review.mjs captures whole pages at 1x, which is the right scale for
+ * layout regressions but too coarse to confirm a 1px circuitry trace actually
+ * paints. This grabs one card at 3x and asserts the decorative layers resolve
+ * to real colours rather than to an unresolved custom property.
+ *
+ * The evaluate callbacks below run in the page, not in Node.
+ */
+/* global getComputedStyle, document */
+import { mkdir } from "node:fs/promises";
+import { chromium } from "playwright";
+
+const baseUrl = process.env.BASE_URL ?? "http://127.0.0.1:4321";
+const outDir = process.env.ZOOM_OUTPUT_DIR ?? "artifacts/visual-luxe";
+const executablePath = process.env.CHROME_PATH;
+
+await mkdir(outDir, { recursive: true });
+
+const browser = await chromium.launch({
+ executablePath,
+ args: ["--no-sandbox"],
+});
+await mkdir(outDir, { recursive: true });
+const page = await browser.newPage({
+ viewport: { width: 1440, height: 1000 },
+ deviceScaleFactor: 3,
+ colorScheme: "dark",
+ reducedMotion: "reduce",
+});
+
+await page.goto(baseUrl, { waitUntil: "load" });
+await page.waitForSelector("#specimen-main");
+await page.waitForTimeout(600);
+
+const card = page.locator(".specimen-card").first();
+await card.scrollIntoViewIfNeeded();
+await page.waitForTimeout(300);
+await card.screenshot({ path: `${outDir}/zoom-card.png` });
+
+// Prove the circuitry trace is painted, not merely declared.
+const trace = await card.evaluate((node) => {
+ const style = getComputedStyle(node, "::before");
+ return {
+ content: style.content,
+ height: style.height,
+ backgroundImage: style.backgroundImage.slice(0, 160),
+ display: style.display,
+ };
+});
+
+const eyebrow = await page
+ .locator(".catalog-group__eyebrow")
+ .first()
+ .evaluate((node) => {
+ const style = getComputedStyle(node, "::before");
+ return {
+ width: style.width,
+ backgroundImage: style.backgroundImage.slice(0, 120),
+ };
+ });
+
+const bodyLayers = await page.evaluate(() => {
+ const style = getComputedStyle(document.body);
+ return {
+ layerCount: style.backgroundImage.split(/,(?![^(]*\))/).length,
+ hasRepeating: style.backgroundImage.includes("repeating-linear-gradient"),
+ hasRadial: style.backgroundImage.includes("radial-gradient"),
+ };
+});
+
+const animations = await page.evaluate(() =>
+ document
+ .getAnimations()
+ .filter((a) => a.playState === "running")
+ .map((a) => a.animationName ?? "unnamed"),
+);
+
+console.log(
+ JSON.stringify(
+ { trace, eyebrow, bodyLayers, runningAnimations: animations },
+ null,
+ 2,
+ ),
+);
+
+await browser.close();