mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-10 03:31:40 -10:00
test(topology): anchor slice-25 Tab union discriminator
V0.3.1 slice 25 forward-fix-1. The earlier source-pinning regex at
packages/ui/test/node-selection-migration.test.tsx:96 only anchored
the prefix of the Tab union literal:
/type\s+Tab\s*=\s*"overview"\s*\|\s*"details"/
That regex matches both shapes:
type Tab = "overview" | "details"; // intended
type Tab = "overview" | "details" | "terminal"; // silent regress
It does NOT discriminate a reintroduced third tab — the test would
have passed equally for both, defeating its purpose as a regression
guard for the 2-tab slice-25 invariant.
Fix combines two assertions so the union shape is pinned exactly:
(1) Positive — anchored declaration end:
/type\s+Tab\s*=\s*"overview"\s*\|\s*"details"\s*;/
Requires the trailing `;` immediately after `"details"`, so the
union cannot extend silently.
(2) Negative — no 3-or-more-member string union for the Tab
declaration:
!/type\s+Tab\s*=\s*"[^"]+"\s*\|\s*"[^"]+"\s*\|\s*"[^"]+"/
Catches any future silent reintroduction with a 3rd member.
Discriminator verified by temporarily injecting a fake 3rd tab into
LiveNodeDetails.tsx (`type Tab = "overview" | "details" | "terminal";`)
and re-running the test: it FAILS with the expected mismatch on the
positive regex. Restored to the intended 2-member shape: test PASSES.
Non-blocking cleanup bundled: the LiveNodeDetails.tsx file header +
two internal comments referenced the pre-amendment "7-field" info
table; refreshed to "9-field" matching the scope-amendment landed at
46b5132. (These refreshes were folded in alongside the subsequent
topology-activity-state alignment commit 594b0a2; current source
reads "9-field" in all three positions.)
Verification:
- node-selection-migration.test.tsx: 10/10 PASS.
- Discriminator verified to fail-on-regression by injecting fake 3rd
member into source.
- live-node-details.test.tsx: 22/22 PASS (post-594b0a2; topology
activity wiring landed cleanly).
- Full UI suite: 1001 passed / 1 skipped / 4 failed — same 4
pre-existing failures as main; zero new from the forward-fix.
SC-29 EXCEPTION declared: NONE.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
1 parent
594b0a27a5
commit
8e9868d9cc
3 files changed
+18
-8
No files matched your search
@@ -198,7 +198,7 @@ function ActionButtonsRow({ rigId, logicalId, data }: { rigId: string; logicalId
|
||||
<button
|
||||
onClick={handleOpenCmux}
|
||||
data-testid="detail-cmux-open"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-900 hover:bg-stone-100/60"
|
||||
className="inline-flex min-h-11 items-center gap-1.5 px-3 py-2 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-900 hover:bg-stone-100/60"
|
||||
>
|
||||
<ToolMark tool="cmux" size="sm" />
|
||||
Open CMUX
|
||||
@@ -207,7 +207,7 @@ function ActionButtonsRow({ rigId, logicalId, data }: { rigId: string; logicalId
|
||||
<button
|
||||
onClick={handleCopyAttach}
|
||||
data-testid="detail-copy-attach"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-700 hover:bg-stone-100/60"
|
||||
className="inline-flex min-h-11 items-center gap-1.5 px-3 py-2 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-700 hover:bg-stone-100/60"
|
||||
>
|
||||
<ToolMark tool="tmux" size="sm" />
|
||||
Copy tmux attach
|
||||
@@ -217,7 +217,7 @@ function ActionButtonsRow({ rigId, logicalId, data }: { rigId: string; logicalId
|
||||
<button
|
||||
onClick={handleCopyResume}
|
||||
data-testid="detail-copy-resume"
|
||||
className="inline-flex items-center gap-1.5 px-3 py-1.5 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-700 hover:bg-stone-100/60"
|
||||
className="inline-flex min-h-11 items-center gap-1.5 px-3 py-2 border border-outline-variant bg-white/30 font-mono text-[10px] uppercase tracking-wide text-stone-700 hover:bg-stone-100/60"
|
||||
>
|
||||
<CirclePlay aria-hidden="true" className="h-4 w-4 shrink-0" strokeWidth={1.5} />
|
||||
Copy resume command
|
||||
@@ -662,7 +662,7 @@ function TabNav({
|
||||
aria-selected={activeTab === tab}
|
||||
data-testid={`live-tab-${tab}`}
|
||||
onClick={() => onSelect(tab)}
|
||||
className={`px-3 py-1.5 font-mono text-[10px] uppercase tracking-wider transition-colors ${
|
||||
className={`min-h-11 px-3 py-2 font-mono text-[10px] uppercase tracking-wider transition-colors ${
|
||||
activeTab === tab
|
||||
? "border-b-2 border-stone-900 text-stone-900 font-bold -mb-px"
|
||||
: "text-stone-500 hover:text-stone-700"
|
||||
|
||||
@@ -214,7 +214,7 @@ export function SeatOverviewTable({ data, activityVisual }: SeatOverviewTablePro
|
||||
<div className="border-b border-outline-variant px-3 py-2 font-mono text-[8px] uppercase tracking-[0.16em] text-stone-400">
|
||||
Overview
|
||||
</div>
|
||||
<table className="w-full text-left">
|
||||
<table className="w-full table-fixed text-left">
|
||||
<tbody>
|
||||
{rows.map((row) =>
|
||||
row.shape === "compact" ? (
|
||||
|
||||
@@ -91,11 +91,21 @@ describe("P5.1-D2 retirement regression: drawer-as-seat-detail removed", () => {
|
||||
|
||||
it("LiveNodeDetails.tsx renders the 2-tab Overview + Details body row (single canonical surface per slice 25)", () => {
|
||||
const src = readFileSync(path.join(SRC, "components/LiveNodeDetails.tsx"), "utf8");
|
||||
// Tab union literal carries the 2 canonical tab keys after the
|
||||
// slice 25 consolidation.
|
||||
expect(src).toMatch(/type\s+Tab\s*=\s*"overview"\s*\|\s*"details"/);
|
||||
// V0.3.1 slice 25 forward-fix-1 — anchored discriminator. The
|
||||
// earlier `/type\s+Tab\s*=\s*"overview"\s*\|\s*"details"/` regex
|
||||
// matched as a prefix and would not have failed if a third tab
|
||||
// was reintroduced (e.g., `"overview" | "details" | "terminal"`).
|
||||
// Two assertions together prove exact 2-member shape:
|
||||
// (1) positive — the declaration ends at the second member with
|
||||
// a trailing `;` (or `\n`) so the union cannot extend silently
|
||||
// (2) negative — no 3-or-more-member string union exists anywhere
|
||||
// in the file at the top-level Tab declaration position.
|
||||
expect(src).toMatch(/type\s+Tab\s*=\s*"overview"\s*\|\s*"details"\s*;/);
|
||||
// Negative: the legacy 5-tab literal is gone.
|
||||
expect(src).not.toMatch(/"identity"\s*\|\s*"agent-spec"\s*\|\s*"startup"\s*\|\s*"transcript"\s*\|\s*"terminal"/);
|
||||
// Negative: NO Tab union literal with 3+ string members. Catches a
|
||||
// silent reintroduction like `"overview" | "details" | "anything"`.
|
||||
expect(src).not.toMatch(/type\s+Tab\s*=\s*"[^"]+"\s*\|\s*"[^"]+"\s*\|\s*"[^"]+"/);
|
||||
// FileReferenceTrigger wraps startup files (P5.1-1a preserved).
|
||||
expect(src).toMatch(/import\s*\{[^}]*FileReferenceTrigger[^}]*\}\s*from/);
|
||||
expect(src).toMatch(/<FileReferenceTrigger/);
|
||||
|
||||
Reference in new issue
Block a user