shell-redesign V1 attempt-3 Phase 1: Foundation + 9 primitives

Phase 1 of the V1 shell redesign attempt-3, per qitem-20260506173041-56702b70
from orch-lead@openrig-velocity. Drift-disclosed structured-ACK + GO at
substrate/shared-docs/openrig-work/missions/recursive-self-improvement-v2/
shell-redesign-v1/attempt-3-driver-3-phase-1-ack.md.

Verify-and-reconcile (DRIFT-1): most design tokens already present at
baseline 24db2b0 (preserved through prior attempt reverts via commits
64afb7f + e316116). Phase 1 reconciles two specific deltas:

- DRIFT-2 fix: .reg-tl is now self-contained matching .reg-tr/bl/br.
  Each pseudo-element carries its own content + position + bg-color.
  Parent-qualified .reg-mark > .reg-tl rule preserved as harmless
  redundancy for the existing RigGraph.tsx:421 consumer.
- DRIFT-3 fix: paper-grid radial-gradient bumped from 0.5px → 1px
  (Option A). Visible at standard browser zoom; runtime-verified via
  getComputedStyle.

Authored 9 vellum primitives at packages/ui/src/components/ui/:
- registration-marks.tsx — 4-corner crosshair primitive
- vellum-card.tsx — primary card chrome (lifted grammar from RigCard.tsx)
- vellum-sheet.tsx — drawer/overlay translucent paper chrome
- section-header.tsx — canonical eyebrow
- status-pip.tsx — semantic 6-status indicator
- empty-state.tsx — unified empty/loading/error; variant=card composes
  VellumCard
- vellum-input.tsx — vellum form input (forwardRef)
- vellum-select.tsx — vellum form select (forwardRef)
- rig-stamp.tsx — rotated faded watermark

82 unit tests across 9 test files, including:
- CSS-source-assertion regression test for DRIFT-2 (jsdom can't render
  pseudo-elements; test reads globals.css directly; pseudo-element-paint
  test contract per discipline ritual #7).
- Negative-assertion test on StatusPip (does NOT render workspace-kind
  taxonomy labels; discipline ritual #8).
- DRIFT-3 paper-grid radius regression (asserts 1px in globals.css).

OUT-OF-SCOPE confirmed not touched: AppShell, Explorer, SharedDetailDrawer,
Sidebar (deletion), Dashboard, routes.tsx, all destination subdirs (for-you/,
topology/, project/, specs/, dashboard/, drawer/, chat/), drawer viewers/
triggers, mobile responsive, consumer migration, daemon code (SC-29
satisfied vacuously), CLI, existing canonical preserved components,
existing shadcn primitives.

Verification gates:
- tsc --noEmit: clean
- vitest: 648/649 (single failure in test/library-review.test.tsx is
  pre-existing on baseline 24db2b0, unrelated to Phase 1)
- vite build: succeeded in 1.98s
- agent-browser visual walk at 1440x900: paper-grid renders; no chrome
  regression on existing routes

Founder walk packet authored at:
substrate/shared-docs/openrig-work/missions/recursive-self-improvement-v2/
shell-redesign-v1/founder-walks/phase-1-walk.md

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-06 10:50:27 -07:00
1 parent 24db2b09e5
commit 9827de6a88
19 files changed
+1171 -3

No files matched your search

@@ -0,0 +1,89 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { VellumCard } from "./vellum-card";
export type EmptyStateVariant = "minimal" | "card";
export interface EmptyStateAction {
label: string;
onClick?: () => void;
href?: string;
}
export interface EmptyStateProps {
icon?: React.ReactNode;
label: string;
description?: React.ReactNode;
action?: EmptyStateAction;
variant?: EmptyStateVariant;
className?: string;
testId?: string;
}
function ActionButton({ action, testId }: { action: EmptyStateAction; testId?: string }) {
const cls =
"inline-flex items-center px-3 py-1 border border-stone-900 bg-white font-mono text-[10px] uppercase hover:bg-stone-100";
if (action.href) {
return (
<a href={action.href} className={cls} data-testid={testId ? `${testId}-action` : undefined}>
{action.label}
</a>
);
}
return (
<button
type="button"
onClick={action.onClick}
className={cls}
data-testid={testId ? `${testId}-action` : undefined}
>
{action.label}
</button>
);
}
export function EmptyState({
icon,
label,
description,
action,
variant = "minimal",
className,
testId,
}: EmptyStateProps) {
const body = (
<div
className={cn(
"flex flex-col items-center justify-center text-center p-6 gap-2",
className,
)}
data-testid={testId}
>
{icon ? (
<div className="text-on-surface-variant" aria-hidden="true">
{typeof icon === "string" ? <span className="text-2xl">{icon}</span> : icon}
</div>
) : null}
<div className="font-headline font-bold uppercase text-stone-900 text-sm tracking-tight">
{label}
</div>
{description ? (
<div className="text-xs text-on-surface-variant max-w-prose">{description}</div>
) : null}
{action ? (
<div className="pt-2">
<ActionButton action={action} testId={testId} />
</div>
) : null}
</div>
);
if (variant === "card") {
return (
<VellumCard variant="ghost" elevation="flat" registrationMarks={false}>
{body}
</VellumCard>
);
}
return body;
}
@@ -0,0 +1,20 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface RegistrationMarksProps {
testIdPrefix?: string;
className?: string;
}
export function RegistrationMarks({ testIdPrefix, className }: RegistrationMarksProps) {
const tid = (corner: "tl" | "tr" | "bl" | "br") =>
testIdPrefix ? `${testIdPrefix}-reg-${corner}` : undefined;
return (
<>
<span className={cn("reg-tl", className)} data-testid={tid("tl")} aria-hidden="true" />
<span className={cn("reg-tr", className)} data-testid={tid("tr")} aria-hidden="true" />
<span className={cn("reg-bl", className)} data-testid={tid("bl")} aria-hidden="true" />
<span className={cn("reg-br", className)} data-testid={tid("br")} aria-hidden="true" />
</>
);
}
@@ -0,0 +1,56 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export type RigStampSize = "sm" | "md" | "lg" | "xl";
export interface RigStampPosition {
top?: number | string;
left?: number | string;
right?: number | string;
bottom?: number | string;
}
export interface RigStampProps {
text: string;
position?: RigStampPosition;
size?: RigStampSize;
className?: string;
testId?: string;
}
const sizeClass: Record<RigStampSize, string> = {
sm: "text-[10px]",
md: "text-sm",
lg: "text-2xl",
xl: "text-4xl",
};
function toCss(v: number | string | undefined): string | undefined {
if (v === undefined) return undefined;
return typeof v === "number" ? `${v}px` : v;
}
export function RigStamp({
text,
position,
size = "md",
className,
testId,
}: RigStampProps) {
const style: React.CSSProperties = {
top: toCss(position?.top),
left: toCss(position?.left),
right: toCss(position?.right),
bottom: toCss(position?.bottom),
};
return (
<span
data-testid={testId}
className={cn("stamp-watermark", sizeClass[size], className)}
style={style}
aria-hidden="true"
>
{text}
</span>
);
}
@@ -0,0 +1,43 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export type SectionHeaderTone = "default" | "muted" | "emphasis";
type PolymorphicAs = "header" | "div" | "section";
export interface SectionHeaderProps extends React.HTMLAttributes<HTMLElement> {
children?: React.ReactNode;
right?: React.ReactNode;
tone?: SectionHeaderTone;
as?: PolymorphicAs;
}
const toneClass: Record<SectionHeaderTone, string> = {
default: "text-stone-900",
muted: "text-on-surface-variant",
emphasis: "text-tertiary",
};
export function SectionHeader({
children,
right,
tone = "default",
as = "header",
className,
...rest
}: SectionHeaderProps) {
const cls = cn(
"font-mono text-[10px] uppercase tracking-[0.18em] flex items-baseline justify-between gap-2",
toneClass[tone],
className,
);
const inner = (
<>
<span className="flex-1">{children}</span>
{right ? <span className="ml-auto flex items-center gap-2">{right}</span> : null}
</>
);
if (as === "div") return <div className={cls} {...rest}>{inner}</div>;
if (as === "section") return <section className={cls} {...rest}>{inner}</section>;
return <header className={cls} {...rest}>{inner}</header>;
}
@@ -0,0 +1,79 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export type StatusPipStatus =
| "active"
| "running"
| "stopped"
| "warning"
| "error"
| "info";
export type StatusPipVariant = "dot" | "pill";
export interface StatusPipProps {
status: StatusPipStatus;
label?: string;
variant?: StatusPipVariant;
className?: string;
testId?: string;
}
const toneDot: Record<StatusPipStatus, string> = {
active: "bg-success",
running: "bg-success",
stopped: "bg-stone-400",
warning: "bg-warning",
error: "bg-tertiary",
info: "bg-secondary",
};
const tonePill: Record<StatusPipStatus, string> = {
active: "border-success text-success",
running: "border-success text-success",
stopped: "border-stone-400 text-stone-500",
warning: "border-warning text-warning",
error: "border-tertiary text-tertiary",
info: "border-secondary text-secondary",
};
export function StatusPip({
status,
label,
variant = "dot",
className,
testId,
}: StatusPipProps) {
if (variant === "pill") {
return (
<span
data-testid={testId}
className={cn(
"inline-flex items-center gap-1 px-1.5 py-0.5 border font-mono text-[9px] uppercase tracking-wide",
tonePill[status],
className,
)}
role="status"
aria-label={label ?? status}
>
<span className={cn("w-1.5 h-1.5 rounded-full", toneDot[status])} aria-hidden="true" />
{label ?? status}
</span>
);
}
return (
<span
data-testid={testId}
className={cn("inline-flex items-center gap-1.5", className)}
role="status"
aria-label={label ?? status}
>
<span className={cn("w-2 h-2 rounded-full", toneDot[status])} aria-hidden="true" />
{label ? (
<span className="font-mono text-[9px] uppercase tracking-wide text-on-surface-variant">
{label}
</span>
) : null}
</span>
);
}
@@ -0,0 +1,97 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { RegistrationMarks } from "./registration-marks";
export type VellumCardElevation = "elevated" | "flat";
export type VellumCardVariant = "primary" | "ghost";
type PolymorphicAs = "div" | "article" | "section";
export interface VellumCardProps extends React.HTMLAttributes<HTMLElement> {
children?: React.ReactNode;
header?: React.ReactNode;
registrationMarks?: boolean;
elevation?: VellumCardElevation;
variant?: VellumCardVariant;
accentClass?: string;
href?: string;
as?: PolymorphicAs;
testId?: string;
}
const variantSurface: Record<VellumCardVariant, string> = {
primary: "bg-white border border-stone-900",
ghost: "bg-transparent border border-stone-300",
};
const elevationShadow: Record<VellumCardElevation, string> = {
elevated: "hard-shadow",
flat: "",
};
export function VellumCard({
children,
header,
registrationMarks = true,
elevation = "elevated",
variant = "primary",
accentClass,
href,
as = "div",
className,
testId,
...rest
}: VellumCardProps) {
const containerClass = cn(
variantSurface[variant],
elevationShadow[elevation],
"flex flex-col relative",
accentClass,
className,
);
const inner = (
<>
{header ? (
<div className="bg-stone-900 text-white px-4 py-1.5 font-mono text-[10px]">
{header}
</div>
) : null}
<div className="flex flex-col flex-1">{children}</div>
{registrationMarks ? <RegistrationMarks testIdPrefix={testId} /> : null}
</>
);
if (href) {
return (
<a
href={href}
className={containerClass}
data-testid={testId}
{...(rest as React.AnchorHTMLAttributes<HTMLAnchorElement>)}
>
{inner}
</a>
);
}
if (as === "article") {
return (
<article className={containerClass} data-testid={testId} {...rest}>
{inner}
</article>
);
}
if (as === "section") {
return (
<section className={containerClass} data-testid={testId} {...rest}>
{inner}
</section>
);
}
return (
<div className={containerClass} data-testid={testId} {...rest}>
{inner}
</div>
);
}
@@ -0,0 +1,25 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface VellumInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
testId?: string;
}
export const VellumInput = React.forwardRef<HTMLInputElement, VellumInputProps>(
({ className, testId, ...props }, ref) => {
return (
<input
ref={ref}
data-testid={testId}
className={cn(
"block w-full border border-stone-300 bg-white px-2 py-1 font-mono text-xs text-stone-900",
"hover:bg-stone-50 focus:border-stone-900 focus:bg-white focus:outline-none",
"disabled:bg-stone-100 disabled:text-stone-400 disabled:cursor-not-allowed",
className,
)}
{...props}
/>
);
},
);
VellumInput.displayName = "VellumInput";
@@ -0,0 +1,28 @@
import * as React from "react";
import { cn } from "@/lib/utils";
export interface VellumSelectProps extends React.SelectHTMLAttributes<HTMLSelectElement> {
testId?: string;
children?: React.ReactNode;
}
export const VellumSelect = React.forwardRef<HTMLSelectElement, VellumSelectProps>(
({ className, testId, children, ...props }, ref) => {
return (
<select
ref={ref}
data-testid={testId}
className={cn(
"block w-full border border-stone-300 bg-white px-2 py-1 font-mono text-xs text-stone-900",
"hover:bg-stone-50 focus:border-stone-900 focus:bg-white focus:outline-none",
"disabled:bg-stone-100 disabled:text-stone-400 disabled:cursor-not-allowed",
className,
)}
{...props}
>
{children}
</select>
);
},
);
VellumSelect.displayName = "VellumSelect";
@@ -0,0 +1,63 @@
import * as React from "react";
import { cn } from "@/lib/utils";
import { RegistrationMarks } from "./registration-marks";
export type VellumSheetEdge = "left" | "right";
export type VellumSheetWidth = "wide" | "narrow";
export interface VellumSheetProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode;
edge?: VellumSheetEdge;
width?: VellumSheetWidth;
onClose?: () => void;
testId?: string;
}
const widthClass: Record<VellumSheetWidth, string> = {
wide: "w-full lg:w-[45rem] lg:max-w-[80vw]",
narrow: "w-full lg:w-[22rem] lg:max-w-[60vw]",
};
const edgeClass: Record<VellumSheetEdge, string> = {
left: "border-r-2 border-stone-900",
right: "border-l-2 border-stone-900",
};
export function VellumSheet({
children,
edge = "right",
width = "wide",
onClose,
className,
testId,
...rest
}: VellumSheetProps) {
return (
<div
className={cn(
"vellum-heavy relative flex flex-col h-full shadow-[0_0_24px_rgba(0,0,0,0.08)]",
widthClass[width],
edgeClass[edge],
className,
)}
data-testid={testId}
role="dialog"
aria-modal="false"
{...rest}
>
{onClose ? (
<button
type="button"
onClick={onClose}
aria-label="Close sheet"
className="absolute top-2 right-2 z-10 px-2 py-0.5 border border-stone-900 bg-white font-mono text-[10px] hover:bg-stone-100"
data-testid={testId ? `${testId}-close` : undefined}
>
×
</button>
) : null}
<div className="flex-1 overflow-auto">{children}</div>
<RegistrationMarks testIdPrefix={testId} />
</div>
);
}
+3 -3
View File
@@ -70,7 +70,7 @@
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Blueprint dot grid — the paper texture */
background-image: radial-gradient(#d1d1cf 0.5px, transparent 0.5px);
background-image: radial-gradient(#d1d1cf 1px, transparent 1px);
background-size: 20px 20px;
}
@@ -91,8 +91,8 @@
.reg-mark > .reg-tl::after { content: ''; position: absolute; background-color: #546073; }
.reg-tl { position: absolute; top: 0; left: 0; width: 0; height: 0; }
.reg-tl::before { top: 0; left: 0; width: 1px; height: 8px; }
.reg-tl::after { top: 0; left: 0; width: 8px; height: 1px; }
.reg-tl::before { content: ''; position: absolute; top: 0; left: 0; width: 1px; height: 8px; background-color: #546073; }
.reg-tl::after { content: ''; position: absolute; top: 0; left: 0; width: 8px; height: 1px; background-color: #546073; }
.reg-tr { position: absolute; top: 0; right: 0; width: 0; height: 0; }
.reg-tr::before { content: ''; position: absolute; top: 0; right: 0; width: 1px; height: 8px; background-color: #546073; }
+75
View File
@@ -0,0 +1,75 @@
// V1 Shell Redesign — Phase 1 — EmptyState primitive.
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { EmptyState } from "../src/components/ui/empty-state";
describe("EmptyState (Phase 1 primitive)", () => {
it("renders label", () => {
render(<EmptyState label="NO ITEMS" />);
expect(screen.getByText("NO ITEMS")).toBeTruthy();
});
it("renders description when provided", () => {
render(<EmptyState label="X" description="More detail here" />);
expect(screen.getByText("More detail here")).toBeTruthy();
});
it("string icon rendered as text", () => {
render(<EmptyState label="X" icon="📭" testId="es" />);
expect(screen.getByTestId("es").textContent).toContain("📭");
});
it("ReactNode icon rendered as element", () => {
render(
<EmptyState
label="X"
icon={<span data-testid="ic">[icon]</span>}
/>,
);
expect(screen.getByTestId("ic")).toBeTruthy();
});
it("action with onClick renders button and fires", () => {
const onClick = vi.fn();
render(
<EmptyState label="X" action={{ label: "DO IT", onClick }} testId="es-b" />,
);
const btn = screen.getByTestId("es-b-action");
expect(btn.tagName).toBe("BUTTON");
fireEvent.click(btn);
expect(onClick).toHaveBeenCalledOnce();
});
it("action with href renders <a>", () => {
render(
<EmptyState label="X" action={{ label: "GO", href: "/foo" }} testId="es-l" />,
);
const link = screen.getByTestId("es-l-action") as HTMLAnchorElement;
expect(link.tagName).toBe("A");
expect(link.getAttribute("href")).toBe("/foo");
});
it("variant=card wraps in a VellumCard ghost+flat composition", () => {
const { container } = render(
<EmptyState label="X" variant="card" testId="es-c" />,
);
const body = container.querySelector("[data-testid='es-c']") as HTMLElement;
// The VellumCard wrapper renders the body inside an inner flex container,
// so VellumCard root = body.parentElement.parentElement.
const wrapper = body.parentElement?.parentElement as HTMLElement;
expect(wrapper.className).toContain("bg-transparent");
expect(wrapper.className).toContain("border-stone-300");
// Flat elevation = no hard-shadow.
expect(wrapper.className).not.toContain("hard-shadow");
});
it("variant=minimal does NOT wrap in VellumCard", () => {
const { container } = render(<EmptyState label="X" testId="es-m" />);
const root = container.querySelector("[data-testid='es-m']") as HTMLElement;
// No vellum-card wrapper around the testid.
const parent = root.parentElement as HTMLElement;
expect(parent.className ?? "").not.toContain("bg-transparent");
expect(parent.className ?? "").not.toContain("border-stone-900");
});
});
@@ -0,0 +1,99 @@
// V1 Shell Redesign — Phase 1 — RegistrationMarks primitive.
//
// API surface tests + CSS-source-assertion regression test for the
// pseudo-element-paint contract (discipline ritual #7).
//
// The CSS-source-assertion test enforces DRIFT-2 fix: each corner
// (.reg-tl, .reg-tr, .reg-bl, .reg-br) must have its own ::before and
// ::after rules carrying content + position + background-color, so each
// corner renders symmetrically without requiring a .reg-mark parent.
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { readFileSync } from "node:fs";
import path from "node:path";
import { RegistrationMarks } from "../src/components/ui/registration-marks";
const GLOBALS_CSS = readFileSync(
path.resolve(__dirname, "../src/globals.css"),
"utf8",
);
describe("RegistrationMarks (Phase 1 primitive)", () => {
it("renders 4 corner spans (tl/tr/bl/br)", () => {
const { container } = render(<RegistrationMarks />);
expect(container.querySelector(".reg-tl")).toBeTruthy();
expect(container.querySelector(".reg-tr")).toBeTruthy();
expect(container.querySelector(".reg-bl")).toBeTruthy();
expect(container.querySelector(".reg-br")).toBeTruthy();
});
it("attaches testIds when testIdPrefix is provided", () => {
const { getByTestId } = render(<RegistrationMarks testIdPrefix="card-x" />);
expect(getByTestId("card-x-reg-tl")).toBeTruthy();
expect(getByTestId("card-x-reg-tr")).toBeTruthy();
expect(getByTestId("card-x-reg-bl")).toBeTruthy();
expect(getByTestId("card-x-reg-br")).toBeTruthy();
});
it("omits testIds when testIdPrefix not provided", () => {
const { container } = render(<RegistrationMarks />);
const tl = container.querySelector(".reg-tl") as HTMLElement | null;
expect(tl?.dataset.testid).toBeUndefined();
});
it("merges className onto every corner span", () => {
const { container } = render(<RegistrationMarks className="opacity-50" />);
const corners = container.querySelectorAll(
".reg-tl, .reg-tr, .reg-bl, .reg-br",
);
expect(corners.length).toBe(4);
corners.forEach((c) => {
expect((c as HTMLElement).className).toContain("opacity-50");
});
});
it("each corner span carries aria-hidden true", () => {
const { container } = render(<RegistrationMarks />);
const corners = container.querySelectorAll(
".reg-tl, .reg-tr, .reg-bl, .reg-br",
);
corners.forEach((c) => {
expect(c.getAttribute("aria-hidden")).toBe("true");
});
});
});
// CSS-source-assertion: pseudo-element-paint test contract.
//
// jsdom does not paint pseudo-elements, so we verify the CSS source
// directly. Each corner class MUST carry self-contained ::before and
// ::after rules with content, position, and background-color — the
// DRIFT-2 fix from V1 attempt-3 dispatch.
describe("globals.css registration-mark CSS source (DRIFT-2 regression)", () => {
const corners = ["reg-tl", "reg-tr", "reg-bl", "reg-br"] as const;
for (const c of corners) {
it(`.${c}::before is self-contained (content + position + bg-color)`, () => {
// Match the standalone selector — NOT the .reg-mark > .${c} parent-qualified one.
const re = new RegExp(
`(^|[^>\\s])\\s*\\.${c}::before\\s*\\{[^}]*content:\\s*'';[^}]*position:\\s*absolute;[^}]*background-color:\\s*#546073;`,
"m",
);
expect(GLOBALS_CSS).toMatch(re);
});
it(`.${c}::after is self-contained (content + position + bg-color)`, () => {
const re = new RegExp(
`(^|[^>\\s])\\s*\\.${c}::after\\s*\\{[^}]*content:\\s*'';[^}]*position:\\s*absolute;[^}]*background-color:\\s*#546073;`,
"m",
);
expect(GLOBALS_CSS).toMatch(re);
});
}
it("paper-grid radial-gradient uses 1px dot radius (DRIFT-3)", () => {
expect(GLOBALS_CSS).toMatch(
/radial-gradient\(#d1d1cf\s+1px,\s*transparent\s+1px\)/,
);
});
});
+68
View File
@@ -0,0 +1,68 @@
// V1 Shell Redesign — Phase 1 — RigStamp primitive.
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { RigStamp } from "../src/components/ui/rig-stamp";
describe("RigStamp (Phase 1 primitive)", () => {
it("renders the text", () => {
render(<RigStamp text="VELOCITY" />);
expect(screen.getByText("VELOCITY")).toBeTruthy();
});
it("applies stamp-watermark class", () => {
const { container } = render(<RigStamp text="X" testId="rs" />);
const el = container.querySelector("[data-testid='rs']") as HTMLElement;
expect(el.className).toContain("stamp-watermark");
});
it("size=md is default and applies text-sm", () => {
const { container } = render(<RigStamp text="X" testId="rs-md" />);
const el = container.querySelector("[data-testid='rs-md']") as HTMLElement;
expect(el.className).toContain("text-sm");
});
it("size=xl applies text-4xl", () => {
const { container } = render(<RigStamp text="X" size="xl" testId="rs-xl" />);
const el = container.querySelector("[data-testid='rs-xl']") as HTMLElement;
expect(el.className).toContain("text-4xl");
});
it("size=sm applies the small class", () => {
const { container } = render(<RigStamp text="X" size="sm" testId="rs-sm" />);
const el = container.querySelector("[data-testid='rs-sm']") as HTMLElement;
expect(el.className).toContain("text-[10px]");
});
it("position numeric values become px", () => {
const { container } = render(
<RigStamp text="X" position={{ top: 12, right: 8 }} testId="rs-p" />,
);
const el = container.querySelector("[data-testid='rs-p']") as HTMLElement;
expect(el.style.top).toBe("12px");
expect(el.style.right).toBe("8px");
});
it("position string values pass through", () => {
const { container } = render(
<RigStamp text="X" position={{ left: "1rem", bottom: "auto" }} testId="rs-ps" />,
);
const el = container.querySelector("[data-testid='rs-ps']") as HTMLElement;
expect(el.style.left).toBe("1rem");
expect(el.style.bottom).toBe("auto");
});
it("merges className", () => {
const { container } = render(
<RigStamp text="X" className="opacity-25" testId="rs-c" />,
);
const el = container.querySelector("[data-testid='rs-c']") as HTMLElement;
expect(el.className).toContain("opacity-25");
});
it("aria-hidden true (decorative)", () => {
const { container } = render(<RigStamp text="X" testId="rs-a" />);
const el = container.querySelector("[data-testid='rs-a']") as HTMLElement;
expect(el.getAttribute("aria-hidden")).toBe("true");
});
});
+56
View File
@@ -0,0 +1,56 @@
// V1 Shell Redesign — Phase 1 — SectionHeader primitive.
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { SectionHeader } from "../src/components/ui/section-header";
describe("SectionHeader (Phase 1 primitive)", () => {
it("renders children", () => {
render(<SectionHeader>OVERVIEW</SectionHeader>);
expect(screen.getByText("OVERVIEW")).toBeTruthy();
});
it("applies canonical eyebrow classes", () => {
const { container } = render(<SectionHeader>X</SectionHeader>);
const el = container.querySelector("header") as HTMLElement;
expect(el.className).toContain("font-mono");
expect(el.className).toContain("text-[10px]");
expect(el.className).toContain("uppercase");
expect(el.className).toContain("tracking-[0.18em]");
});
it("renders right slot when provided", () => {
render(
<SectionHeader right={<span data-testid="rt">EXTRA</span>}>X</SectionHeader>,
);
expect(screen.getByTestId("rt")).toBeTruthy();
});
it("tone=muted applies on-surface-variant color", () => {
const { container } = render(<SectionHeader tone="muted">X</SectionHeader>);
expect((container.firstChild as HTMLElement).className).toContain(
"text-on-surface-variant",
);
});
it("tone=emphasis applies tertiary color", () => {
const { container } = render(<SectionHeader tone="emphasis">X</SectionHeader>);
expect((container.firstChild as HTMLElement).className).toContain("text-tertiary");
});
it("default as=header element", () => {
const { container } = render(<SectionHeader>X</SectionHeader>);
expect(container.querySelector("header")).toBeTruthy();
});
it("polymorphic as=div renders <div>", () => {
const { container } = render(<SectionHeader as="div">X</SectionHeader>);
expect(container.querySelector("header")).toBeNull();
expect(container.querySelector("div")).toBeTruthy();
});
it("polymorphic as=section renders <section>", () => {
const { container } = render(<SectionHeader as="section">X</SectionHeader>);
expect(container.querySelector("section")).toBeTruthy();
});
});
+74
View File
@@ -0,0 +1,74 @@
// V1 Shell Redesign — Phase 1 — StatusPip primitive.
//
// API surface + tone mapping + negative-assertion (discipline ritual #8):
// status-pip is for SEMANTIC status only, not kind-taxonomy. Tests assert
// it does not render workspace-kind labels by accident.
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { StatusPip, type StatusPipStatus } from "../src/components/ui/status-pip";
const ALL_STATUSES: StatusPipStatus[] = [
"active",
"running",
"stopped",
"warning",
"error",
"info",
];
describe("StatusPip (Phase 1 primitive)", () => {
it("renders dot variant by default with role=status", () => {
render(<StatusPip status="active" testId="sp" />);
expect(screen.getByTestId("sp").getAttribute("role")).toBe("status");
});
it.each(ALL_STATUSES)("dot variant tone for %s status maps to expected class", (status) => {
const { container } = render(<StatusPip status={status} testId={`sp-${status}`} />);
const root = container.querySelector(`[data-testid='sp-${status}']`) as HTMLElement;
const dot = root.querySelector("span[aria-hidden]") as HTMLElement;
if (status === "active" || status === "running") expect(dot.className).toContain("bg-success");
if (status === "stopped") expect(dot.className).toContain("bg-stone-400");
if (status === "warning") expect(dot.className).toContain("bg-warning");
if (status === "error") expect(dot.className).toContain("bg-tertiary");
if (status === "info") expect(dot.className).toContain("bg-secondary");
});
it("pill variant includes label text", () => {
render(<StatusPip status="error" label="FAILED" variant="pill" testId="sp-pill" />);
const root = screen.getByTestId("sp-pill");
expect(root.textContent).toContain("FAILED");
});
it("pill variant tone classes apply to the wrapper", () => {
const { container } = render(
<StatusPip status="warning" variant="pill" testId="sp-w-pill" />,
);
const root = container.querySelector("[data-testid='sp-w-pill']") as HTMLElement;
expect(root.className).toContain("border-warning");
expect(root.className).toContain("text-warning");
});
it("renders label in dot variant when provided", () => {
render(<StatusPip status="active" label="LIVE" testId="sp-dlbl" />);
expect(screen.getByText("LIVE")).toBeTruthy();
});
it("aria-label falls back to status when no label", () => {
render(<StatusPip status="info" testId="sp-aria" />);
expect(screen.getByTestId("sp-aria").getAttribute("aria-label")).toBe("info");
});
// Negative-assertion (ritual #8): StatusPip is for semantic status, not kind taxonomy.
// Make sure forbidden workspace-kind strings never bleed into the rendering.
const FORBIDDEN_KIND_LABELS = ["user", "project", "knowledge", "lab", "delivery"];
it("does NOT render workspace-kind taxonomy labels for any status", () => {
for (const s of ALL_STATUSES) {
const { container, unmount } = render(<StatusPip status={s} />);
for (const kind of FORBIDDEN_KIND_LABELS) {
expect(container.textContent ?? "").not.toMatch(new RegExp(`\\b${kind}\\b`, "i"));
}
unmount();
}
});
});
+95
View File
@@ -0,0 +1,95 @@
// V1 Shell Redesign — Phase 1 — VellumCard primitive.
//
// API surface tests: default render, header, registrationMarks toggle,
// elevation + variant variants, accentClass, href (Link wrapper),
// polymorphic as prop, testId.
import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import { VellumCard } from "../src/components/ui/vellum-card";
describe("VellumCard (Phase 1 primitive)", () => {
it("renders children", () => {
render(<VellumCard>HELLO</VellumCard>);
expect(screen.getByText("HELLO")).toBeTruthy();
});
it("default container carries vellum classes (bg-white + border-stone-900 + hard-shadow)", () => {
const { container } = render(<VellumCard testId="vc">x</VellumCard>);
const el = container.querySelector("[data-testid='vc']") as HTMLElement;
expect(el.className).toContain("bg-white");
expect(el.className).toContain("border-stone-900");
expect(el.className).toContain("hard-shadow");
});
it("renders 4 registration mark corners by default", () => {
const { container } = render(<VellumCard>x</VellumCard>);
expect(container.querySelector(".reg-tl")).toBeTruthy();
expect(container.querySelector(".reg-tr")).toBeTruthy();
expect(container.querySelector(".reg-bl")).toBeTruthy();
expect(container.querySelector(".reg-br")).toBeTruthy();
});
it("registrationMarks={false} omits all 4 corners", () => {
const { container } = render(
<VellumCard registrationMarks={false}>x</VellumCard>,
);
expect(container.querySelector(".reg-tl")).toBeNull();
expect(container.querySelector(".reg-tr")).toBeNull();
expect(container.querySelector(".reg-bl")).toBeNull();
expect(container.querySelector(".reg-br")).toBeNull();
});
it("renders dark header stripe when header prop given", () => {
render(<VellumCard header="RIG: VELOCITY">x</VellumCard>);
expect(screen.getByText("RIG: VELOCITY")).toBeTruthy();
});
it("variant=ghost replaces primary surface classes", () => {
const { container } = render(
<VellumCard variant="ghost" testId="vc-g">x</VellumCard>,
);
const el = container.querySelector("[data-testid='vc-g']") as HTMLElement;
expect(el.className).toContain("bg-transparent");
expect(el.className).not.toContain("bg-white");
});
it("elevation=flat omits hard-shadow", () => {
const { container } = render(
<VellumCard elevation="flat" testId="vc-f">x</VellumCard>,
);
const el = container.querySelector("[data-testid='vc-f']") as HTMLElement;
expect(el.className).not.toContain("hard-shadow");
});
it("accentClass merges into the container", () => {
const { container } = render(
<VellumCard accentClass="border-l-4 border-l-tertiary" testId="vc-a">x</VellumCard>,
);
const el = container.querySelector("[data-testid='vc-a']") as HTMLElement;
expect(el.className).toContain("border-l-tertiary");
});
it("href wraps in an <a>", () => {
const { container } = render(
<VellumCard href="/foo" testId="vc-l">x</VellumCard>,
);
const a = container.querySelector("a[data-testid='vc-l']");
expect(a).toBeTruthy();
expect(a?.getAttribute("href")).toBe("/foo");
});
it("as=article renders an <article>", () => {
const { container } = render(
<VellumCard as="article" testId="vc-art">x</VellumCard>,
);
expect(container.querySelector("article[data-testid='vc-art']")).toBeTruthy();
});
it("as=section renders a <section>", () => {
const { container } = render(
<VellumCard as="section" testId="vc-sec">x</VellumCard>,
);
expect(container.querySelector("section[data-testid='vc-sec']")).toBeTruthy();
});
});
+53
View File
@@ -0,0 +1,53 @@
// V1 Shell Redesign — Phase 1 — VellumInput primitive.
import { describe, it, expect } from "vitest";
import { render, fireEvent } from "@testing-library/react";
import { VellumInput } from "../src/components/ui/vellum-input";
import * as React from "react";
describe("VellumInput (Phase 1 primitive)", () => {
it("renders an <input>", () => {
const { container } = render(<VellumInput testId="vi" />);
const el = container.querySelector("input[data-testid='vi']");
expect(el).toBeTruthy();
});
it("applies vellum form classes", () => {
const { container } = render(<VellumInput testId="vi-c" />);
const el = container.querySelector("input[data-testid='vi-c']") as HTMLInputElement;
expect(el.className).toContain("border-stone-300");
expect(el.className).toContain("bg-white");
expect(el.className).toContain("font-mono");
expect(el.className).toContain("text-xs");
});
it("forwards ref to underlying input", () => {
const ref = React.createRef<HTMLInputElement>();
render(<VellumInput ref={ref} testId="vi-r" />);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe("INPUT");
});
it("forwards native props (value, onChange)", () => {
let captured = "";
const { container } = render(
<VellumInput
defaultValue="abc"
onChange={(e) => {
captured = (e.target as HTMLInputElement).value;
}}
testId="vi-n"
/>,
);
const el = container.querySelector("input[data-testid='vi-n']") as HTMLInputElement;
expect(el.value).toBe("abc");
fireEvent.change(el, { target: { value: "xyz" } });
expect(captured).toBe("xyz");
});
it("merges custom className", () => {
const { container } = render(<VellumInput className="border-red-500" testId="vi-m" />);
const el = container.querySelector("input[data-testid='vi-m']") as HTMLInputElement;
expect(el.className).toContain("border-red-500");
});
});
+71
View File
@@ -0,0 +1,71 @@
// V1 Shell Redesign — Phase 1 — VellumSelect primitive.
import { describe, it, expect } from "vitest";
import { render, fireEvent } from "@testing-library/react";
import { VellumSelect } from "../src/components/ui/vellum-select";
import * as React from "react";
describe("VellumSelect (Phase 1 primitive)", () => {
it("renders a <select>", () => {
const { container } = render(
<VellumSelect testId="vs">
<option value="a">A</option>
</VellumSelect>,
);
expect(container.querySelector("select[data-testid='vs']")).toBeTruthy();
});
it("renders option children", () => {
const { container } = render(
<VellumSelect testId="vs-opts">
<option value="a">Alpha</option>
<option value="b">Beta</option>
</VellumSelect>,
);
const opts = container.querySelectorAll("option");
expect(opts.length).toBe(2);
expect(opts[0].textContent).toBe("Alpha");
});
it("applies vellum form classes", () => {
const { container } = render(
<VellumSelect testId="vs-c">
<option>x</option>
</VellumSelect>,
);
const el = container.querySelector("select[data-testid='vs-c']") as HTMLSelectElement;
expect(el.className).toContain("border-stone-300");
expect(el.className).toContain("bg-white");
expect(el.className).toContain("font-mono");
});
it("forwards ref", () => {
const ref = React.createRef<HTMLSelectElement>();
render(
<VellumSelect ref={ref} testId="vs-r">
<option>x</option>
</VellumSelect>,
);
expect(ref.current).not.toBeNull();
expect(ref.current?.tagName).toBe("SELECT");
});
it("fires onChange", () => {
let captured = "";
const { container } = render(
<VellumSelect
defaultValue="a"
onChange={(e) => {
captured = (e.target as HTMLSelectElement).value;
}}
testId="vs-n"
>
<option value="a">A</option>
<option value="b">B</option>
</VellumSelect>,
);
const el = container.querySelector("select[data-testid='vs-n']") as HTMLSelectElement;
fireEvent.change(el, { target: { value: "b" } });
expect(captured).toBe("b");
});
});
+77
View File
@@ -0,0 +1,77 @@
// V1 Shell Redesign — Phase 1 — VellumSheet primitive.
//
// API surface tests: edge, width, onClose, testId, registration marks.
import { describe, it, expect, vi } from "vitest";
import { render, screen, fireEvent } from "@testing-library/react";
import { VellumSheet } from "../src/components/ui/vellum-sheet";
describe("VellumSheet (Phase 1 primitive)", () => {
it("renders children", () => {
render(<VellumSheet>BODY</VellumSheet>);
expect(screen.getByText("BODY")).toBeTruthy();
});
it("default carries vellum-heavy class", () => {
const { container } = render(<VellumSheet testId="vs">x</VellumSheet>);
const el = container.querySelector("[data-testid='vs']") as HTMLElement;
expect(el.className).toContain("vellum-heavy");
});
it("default width=wide applies the wide width class", () => {
const { container } = render(<VellumSheet testId="vs-w">x</VellumSheet>);
const el = container.querySelector("[data-testid='vs-w']") as HTMLElement;
expect(el.className).toContain("lg:w-[45rem]");
expect(el.className).toContain("lg:max-w-[80vw]");
});
it("width=narrow applies the narrow width class", () => {
const { container } = render(
<VellumSheet width="narrow" testId="vs-n">x</VellumSheet>,
);
const el = container.querySelector("[data-testid='vs-n']") as HTMLElement;
expect(el.className).toContain("lg:w-[22rem]");
});
it("edge=right (default) places left border", () => {
const { container } = render(<VellumSheet testId="vs-r">x</VellumSheet>);
const el = container.querySelector("[data-testid='vs-r']") as HTMLElement;
expect(el.className).toContain("border-l-2");
});
it("edge=left places right border", () => {
const { container } = render(
<VellumSheet edge="left" testId="vs-l">x</VellumSheet>,
);
const el = container.querySelector("[data-testid='vs-l']") as HTMLElement;
expect(el.className).toContain("border-r-2");
});
it("renders close button when onClose provided; clicking calls handler", () => {
const onClose = vi.fn();
render(
<VellumSheet onClose={onClose} testId="vs-c">x</VellumSheet>,
);
const closeBtn = screen.getByLabelText("Close sheet");
expect(closeBtn).toBeTruthy();
fireEvent.click(closeBtn);
expect(onClose).toHaveBeenCalledOnce();
});
it("omits close button when onClose not provided", () => {
const { container } = render(<VellumSheet>x</VellumSheet>);
expect(container.querySelector("button[aria-label='Close sheet']")).toBeNull();
});
it("renders 4 registration marks", () => {
const { container } = render(<VellumSheet>x</VellumSheet>);
expect(container.querySelectorAll(".reg-tl, .reg-tr, .reg-bl, .reg-br").length).toBe(4);
});
it("dialog role + aria-modal=false (non-modal drawer per content-drawer.md)", () => {
const { container } = render(<VellumSheet testId="vs-aria">x</VellumSheet>);
const el = container.querySelector("[data-testid='vs-aria']") as HTMLElement;
expect(el.getAttribute("role")).toBe("dialog");
expect(el.getAttribute("aria-modal")).toBe("false");
});
});