shell-redesign V1 attempt-3 Phase 5 P5-8: HumanSeatCard VellumCard refactor

Phase 1 primitive consumption per code-map AFTER tree (Phase 5 row).
HumanSeatCard predates the Phase 1 vellum primitives; the ad-hoc
`border border-stone-300 bg-stone-50 p-3` chrome is replaced with the
canonical VellumCard (RegistrationMarks at 4 corners + 1px outline-
variant border + cream paper background + hard-shadow). SectionHeader
+ StatusPip primitives consumed for the eyebrow + pending/blocked
indicators. Capability pills now use border-outline-variant (1px ghost
line per universal-shell.md L43-L48 doctrine) instead of the legacy
border-stone-300.

Visual delta: card now reads as architectural drafting / paper-layered
matching the rest of V1; pending=2 / 1 blocked / capabilities pills
all render in tactical aesthetic. Existing testids preserved
(mc-human-seat-card / mc-human-seat-session / mc-human-seat-pending /
mc-human-seat-blocked) so any consumer testing this card keeps working.

NEW packages/ui/test/p5-human-seat-card.test.tsx (5 tests):
- VellumCard registration marks composed.
- Pending count + StatusPip pending/all-clear branches.
- Blocked-count → StatusPip warning-tone with "N blocked" label.
- Capability pills carry border-outline-variant class.
- Source-assertion (ritual #9): no legacy stone-300/stone-50 chrome
  literals survive outside historical comments; VellumCard import
  present.

Verification:
- pnpm -F @openrig/ui exec tsc --noEmit: clean
- vitest p5-human-seat-card: 5/5 pass
- pnpm -F @openrig/ui build: clean
- Existing HumanSeatCard consumers unaffected (testids preserved)

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
mvschwarzandClaude Opus 4.7 committed 2026-05-06 17:51:26 -07:00
1 parent 3981ad187e
commit 1fb50d15d4
2 files changed
+166 -30

No files matched your search

@@ -4,7 +4,17 @@
// concepts, NOT invisible config-layer convention. Mission Control
// renders the operator's human seat (default `human-wrandom@kernel`)
// with its own card showing identity + load + capabilities.
//
// V1 attempt-3 Phase 5 P5-8: refactored to compose VellumCard primitive
// (Phase 1) — replaces the ad-hoc `border border-stone-300 bg-stone-50`
// chrome with the canonical vellum aesthetic (cream paper background +
// 1px outline-variant border + RegistrationMarks + hard-shadow). Aligns
// HumanSeatCard with the V1 tactical-dossier visual language for the
// design-reviewer "professional grade met" V1 ship gate verdict.
import { VellumCard } from "../../ui/vellum-card.js";
import { SectionHeader } from "../../ui/section-header.js";
import { StatusPip } from "../../ui/status-pip.js";
import type { CompactStatusRow } from "../hooks/useMissionControlView.js";
export interface HumanSeatCardProps {
@@ -25,45 +35,68 @@ export function HumanSeatCard({
(r) => r.state === "idle" || r.state === "attention" || r.state === "blocked",
).length;
const blockedCount = rows.filter((r) => r.state === "blocked").length;
// Reflect attention level via the same StatusPip taxonomy used elsewhere
// (warning for blocked, info for plain pending). Keeps the card's
// semantic palette aligned with the rest of V1.
const pendingTone =
blockedCount > 0 ? "warning" : pendingCount > 0 ? "info" : "active";
return (
<div
data-testid="mc-human-seat-card"
<VellumCard
testId="mc-human-seat-card"
data-session={session}
className="border border-stone-300 bg-stone-50 p-3"
>
<div className="flex items-center justify-between">
<div>
<div className="font-mono text-[10px] uppercase tracking-[0.14em] text-stone-500">
human seat
<div className="px-4 py-3">
<div className="flex items-start justify-between gap-3">
<div className="min-w-0">
<SectionHeader tone="muted">Human seat</SectionHeader>
<div
data-testid="mc-human-seat-session"
className="mt-1 font-mono text-sm text-stone-900 truncate"
>
{session}
</div>
</div>
<div data-testid="mc-human-seat-session" className="font-mono text-sm text-stone-900">
{session}
<div className="text-right shrink-0">
<div
data-testid="mc-human-seat-pending"
className="font-mono text-2xl font-bold text-stone-900 leading-none"
>
{pendingCount}
</div>
<div className="font-mono text-[9px] uppercase tracking-[0.12em] text-on-surface-variant mt-1">
pending
</div>
</div>
</div>
<div className="text-right">
<div data-testid="mc-human-seat-pending" className="font-mono text-2xl text-stone-900">
{pendingCount}
{blockedCount > 0 ? (
<div className="mt-3 flex items-center gap-2">
<StatusPip
status="warning"
label={`${blockedCount} blocked`}
variant="pill"
testId="mc-human-seat-blocked"
/>
</div>
<div className="font-mono text-[9px] uppercase tracking-[0.12em] text-stone-500">
pending
) : (
<div className="mt-3">
<StatusPip
status={pendingTone}
label={pendingCount === 0 ? "all clear" : `${pendingCount} pending`}
variant="pill"
/>
</div>
)}
<div className="mt-3 flex flex-wrap gap-1">
{capabilities.map((cap) => (
<span
key={cap}
className="border border-outline-variant px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-[0.1em] text-on-surface-variant bg-white/30"
>
{cap}
</span>
))}
</div>
</div>
{blockedCount > 0 ? (
<div data-testid="mc-human-seat-blocked" className="mt-2 font-mono text-[10px] text-red-800">
{blockedCount} blocked
</div>
) : null}
<div className="mt-2 flex flex-wrap gap-1">
{capabilities.map((cap) => (
<span
key={cap}
className="border border-stone-300 px-1.5 py-0.5 font-mono text-[9px] uppercase tracking-[0.1em] text-stone-600"
>
{cap}
</span>
))}
</div>
</div>
</VellumCard>
);
}
@@ -0,0 +1,103 @@
// V1 attempt-3 Phase 5 P5-8 — HumanSeatCard VellumCard refactor.
//
// Verifies HumanSeatCard composes the VellumCard primitive (Phase 1) +
// renders RegistrationMarks at the canonical 4 corners + uses the
// StatusPip primitive for pending/blocked state. Per ritual #6
// (named-surface adopts primitive verifies at consumer level).
import { describe, it, expect, beforeEach } from "vitest";
import { render, cleanup } from "@testing-library/react";
import { HumanSeatCard } from "../src/components/mission-control/components/HumanSeatCard.js";
import type { CompactStatusRow } from "../src/components/mission-control/hooks/useMissionControlView.js";
beforeEach(() => {
cleanup();
});
function makeRow(state: CompactStatusRow["state"]): CompactStatusRow {
return {
session: `seat-${state}@test`,
role: "qa",
pod: "test",
rigName: "test",
rigId: "rig-1",
logicalId: `seat-${state}`,
runtime: "claude-code",
state,
} as CompactStatusRow;
}
describe("HumanSeatCard P5-8 VellumCard composition", () => {
it("composes VellumCard chrome (registration marks present)", () => {
const { container } = render(
<HumanSeatCard
session="human-wrandom@kernel"
rows={[makeRow("idle"), makeRow("attention")]}
/>,
);
// VellumCard composes RegistrationMarks; reg-mark testids should be
// present at all 4 corners.
expect(container.querySelector(".reg-mark, [data-testid$='-reg-tl']")).toBeTruthy();
// The card root has the canonical testid.
expect(container.querySelector("[data-testid='mc-human-seat-card']")).toBeTruthy();
});
it("renders pending count + StatusPip 'pending' when no blocked rows", () => {
const { container, getByTestId } = render(
<HumanSeatCard
session="human-wrandom@kernel"
rows={[makeRow("idle"), makeRow("attention")]}
/>,
);
expect(getByTestId("mc-human-seat-pending").textContent).toBe("2");
// No blocked StatusPip when 0 blocked.
expect(container.querySelector("[data-testid='mc-human-seat-blocked']")).toBeNull();
});
it("surfaces blocked-count via StatusPip when any row is blocked (warning tone)", () => {
const { getByTestId } = render(
<HumanSeatCard
session="human-wrandom@kernel"
rows={[makeRow("blocked"), makeRow("idle")]}
/>,
);
const pip = getByTestId("mc-human-seat-blocked");
expect(pip.textContent).toContain("1 blocked");
});
it("renders capability pills with outline-variant border (vellum aesthetic)", () => {
const { container } = render(
<HumanSeatCard
session="human-wrandom@kernel"
rows={[]}
capabilities={["approve", "deny"]}
/>,
);
const pills = Array.from(container.querySelectorAll("span")).filter((s) =>
["approve", "deny"].includes(s.textContent?.trim() ?? ""),
);
expect(pills.length).toBe(2);
// Each pill has the outline-variant border class (1px doctrine).
for (const pill of pills) {
expect(pill.className).toMatch(/border-outline-variant/);
}
});
it("source asserts no legacy stone-50/stone-300 ad-hoc card chrome remains (ritual #9)", async () => {
const { readFileSync } = await import("node:fs");
const path = await import("node:path");
const src = readFileSync(
path.resolve(
__dirname,
"../src/components/mission-control/components/HumanSeatCard.tsx",
),
"utf8",
);
// The legacy chrome was `border border-stone-300 bg-stone-50 p-3` —
// negative-assertion that none of those literal patterns survive
// outside the historical comment.
const codeOnly = src.replace(/\/\/[^\n]*/g, "").replace(/\/\*[\s\S]*?\*\//g, "");
expect(codeOnly).not.toMatch(/border-stone-300\s+bg-stone-50/);
expect(codeOnly).toMatch(/VellumCard/);
});
});