Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified apps/ui/e2e/__screens__/bridge-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/bridge.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified apps/ui/e2e/__screens__/done.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions apps/ui/e2e/visual-smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions apps/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
50 changes: 7 additions & 43 deletions apps/ui/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

Expand Down
4 changes: 2 additions & 2 deletions apps/ui/src/boot-fixture.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
24 changes: 23 additions & 1 deletion apps/ui/src/components/Bridge.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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<HTMLElement>('.card-pr[role="link"]');
Expand Down
7 changes: 4 additions & 3 deletions apps/ui/src/components/IssueCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ export const IssueCard: Component<{
return (
<button
type="button"
class="card"
data-priority={props.issue.priority}
classList={{ selected: props.issue.id === store.selectedIssueId() }}
class="cx-card"
data-selected={
props.issue.id === store.selectedIssueId() ? "" : undefined
}
onClick={() => store.selectIssue(props.issue.id)}
onDblClick={openAssignedAgent}
>
Expand Down
Loading