diff --git a/apps/ui/e2e/__screens__/bridge-card.png b/apps/ui/e2e/__screens__/bridge-card.png index a7e9834c..93ce1ee6 100644 Binary files a/apps/ui/e2e/__screens__/bridge-card.png and b/apps/ui/e2e/__screens__/bridge-card.png differ diff --git a/apps/ui/e2e/__screens__/bridge.png b/apps/ui/e2e/__screens__/bridge.png index e499f6d7..d0af781a 100644 Binary files a/apps/ui/e2e/__screens__/bridge.png and b/apps/ui/e2e/__screens__/bridge.png differ diff --git a/apps/ui/e2e/__screens__/done.png b/apps/ui/e2e/__screens__/done.png index ca1c479a..bc035065 100644 Binary files a/apps/ui/e2e/__screens__/done.png and b/apps/ui/e2e/__screens__/done.png differ diff --git a/apps/ui/e2e/visual-smoke.spec.ts b/apps/ui/e2e/visual-smoke.spec.ts index 396f3107..8158449a 100644 --- a/apps/ui/e2e/visual-smoke.spec.ts +++ b/apps/ui/e2e/visual-smoke.spec.ts @@ -164,10 +164,10 @@ test.describe("visual smoke — legacy-palette baseline", () => { test("bridge — single card close-up", async ({ page }) => { await page.goto("/#/"); await page.locator(".bridge").waitFor({ state: "visible" }); - const card = page.locator(".card").first(); + const card = page.locator(".cx-card").first(); await card.waitFor({ state: "visible" }); await page.evaluate(() => document.fonts.ready); - // Cropped close-up clip of a single issue card (IssueCard.tsx:47 `.card`). + // Cropped close-up clip of a single issue card (IssueCard.tsx:47 `.cx-card`). await card.screenshot({ path: `${SCREENS}/bridge-card.png`, animations: "disabled", diff --git a/apps/ui/src/App.tsx b/apps/ui/src/App.tsx index cc6bee4d..7c6f0818 100644 --- a/apps/ui/src/App.tsx +++ b/apps/ui/src/App.tsx @@ -4,6 +4,7 @@ import { type Component, Show } from "solid-js"; import "./design/tokens.css"; import "./design/base.css"; import "./design/components/badge-glyph.css"; +import "./design/components/card.css"; import "./app.css"; import { LeftSidebar } from "./components/LeftSidebar"; import { RightSidebar } from "./components/RightSidebar"; diff --git a/apps/ui/src/app.css b/apps/ui/src/app.css index d9c31cf9..01b0781b 100644 --- a/apps/ui/src/app.css +++ b/apps/ui/src/app.css @@ -617,53 +617,17 @@ background: color-mix(in srgb, var(--cx-bg) 60%, transparent); } -/* ── Issue card ─────────────────────────────────────────────────────────── */ - -.card { +/* Board cells pack the DS card denser than its block/12px default (F3), + scoped to the board so it never leaks to other .cx-card surfaces. */ +.bridge-cell > .cx-card { display: flex; flex-direction: column; gap: 6px; text-align: left; - background: var(--cx-bg-panel); - border: 1px solid var(--cx-border); - border-left-width: 3px; - border-radius: var(--cx-radius-sm); - padding: 8px 10px; - color: var(--cx-text); - /* stylelint-disable declaration-property-value-disallowed-list -- TODO(motion migration, D9/foundation-T8): raw 0.12s/0.05s grandfathered by the SEA-2034 cutover lane, which never scoped the motion axis */ - transition: - border-color 0.12s, - background 0.12s, - transform 0.05s; - /* stylelint-enable declaration-property-value-disallowed-list */ -} - -.card:hover { - background: var(--cx-bg-hover); - border-color: var(--cx-border-strong); -} - -.card:active { - transform: translateY(1px); + padding: 9px 10px; } -.card.selected { - border-color: var(--cx-accent); - box-shadow: 0 0 0 1px var(--cx-accent); -} - -.card[data-priority="urgent"] { - border-left-color: var(--cx-error); -} -.card[data-priority="high"] { - border-left-color: var(--cx-warn); -} -.card[data-priority="medium"] { - border-left-color: var(--cx-accent); -} -.card[data-priority="low"] { - border-left-color: var(--cx-border-strong); -} +/* ── Issue card ─────────────────────────────────────────────────────────── */ .card-top { display: flex; @@ -672,9 +636,9 @@ } .card-issue { - color: var(--cx-accent); + color: var(--cx-text-faint); font-size: 11px; - font-weight: 600; + letter-spacing: 0.5px; font-family: var(--cx-font-ui); } diff --git a/apps/ui/src/boot-fixture.test.ts b/apps/ui/src/boot-fixture.test.ts index d66808b8..0c6302d6 100644 --- a/apps/ui/src/boot-fixture.test.ts +++ b/apps/ui/src/boot-fixture.test.ts @@ -28,8 +28,8 @@ describe("bootFixture (offline fixture boot)", () => { await flush(); // The board renders IssueCards from the clientless store's STUB_ISSUES - // seed. A `.card` element proves the shell mounted with fixture content. - expect(root.querySelector(".card")).not.toBeNull(); + // seed. A `.cx-card` element proves the shell mounted with fixture content. + expect(root.querySelector(".cx-card")).not.toBeNull(); // And a known fixture issue's title is in the rendered text — the board // is populated from the fixtures, not merely a mounted-but-empty shell. diff --git a/apps/ui/src/components/Bridge.test.tsx b/apps/ui/src/components/Bridge.test.tsx index 74efff71..cf6d7fdb 100644 --- a/apps/ui/src/components/Bridge.test.tsx +++ b/apps/ui/src/components/Bridge.test.tsx @@ -156,7 +156,7 @@ describe("Bridge card badges (Record B §3)", () => { // code is hidden, glyph only), Bridge PR rows do not (code shown). Defend // the distinction, not just badge presence — a dropped/inverted `compact` // on a consumer would otherwise ship green. - const cardBadge = container.querySelector(".card .cx-axis-badge"); + const cardBadge = container.querySelector(".cx-card .cx-axis-badge"); if (!cardBadge) throw new Error("no card axis badge"); expect(cardBadge.hasAttribute("data-compact")).toBe(true); clickTab(container, "PRs"); @@ -165,6 +165,28 @@ describe("Bridge card badges (Record B §3)", () => { expect(rowBadge.hasAttribute("data-compact")).toBe(false); }); + test("the selected card carries data-selected; others do not (presence toggle)", () => { + // T3 encodes selection solely as `.cx-card[data-selected]` (IssueCard.tsx: + // `sel ? "" : undefined`), which owns the accent left rule now that the + // priority stripe is gone. Defend the toggle against inversion / a dropped + // attribute: exactly one card is selected (the store seeds STUB_ISSUES[0]), + // and selecting a different issue moves the attribute to exactly one card. + const { store, container } = mountBridge(); + const selectedCards = () => + container.querySelectorAll(".cx-card[data-selected]"); + const allCards = container.querySelectorAll(".cx-card"); + expect(allCards.length).toBeGreaterThan(1); + expect(selectedCards()).toHaveLength(1); + + // Move the selection to a different fixture issue that the board renders. + const otherId = STUB_ISSUES.find( + (w) => w.id !== store.selectedIssueId(), + )?.id; + if (!otherId) throw new Error("need a second fixture issue"); + store.selectIssue(otherId); + expect(selectedCards()).toHaveLength(1); + }); + test("a card PR chip is a link that selects the issue and flips to the PRs tab", () => { const { store, container } = mountBridge(); const chip = container.querySelector('.card-pr[role="link"]'); diff --git a/apps/ui/src/components/IssueCard.tsx b/apps/ui/src/components/IssueCard.tsx index a6db2827..6bf795bf 100644 --- a/apps/ui/src/components/IssueCard.tsx +++ b/apps/ui/src/components/IssueCard.tsx @@ -44,9 +44,10 @@ export const IssueCard: Component<{ return (