From f5487bfad3e7e0141b2b00681f91be988bf436df Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:15:53 -0700 Subject: [PATCH 1/2] fix(board): blocked cards look like every other card, guard against card rails (TASK-746) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: otto Agent-Model: claude/claude-opus-5-5/default --- .../v2/__tests__/v2-layout-invariants.test.ts | 22 +++++++++++++++++-- frontend/src/v2/v2.css | 4 ---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index e6920c50f..4ffade49a 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -5134,11 +5134,29 @@ describe('task priority and lease signals (TASK-443)', () => { expect(css).toMatch(/--v2-status-live:\s*var\(--v2-accent\)/); expect(ruleBody(css, '.v2-board__priority--p2')).toContain('color: var(--v2-prio-p2-fg)'); }); - test('blocked is red with a thin edge on blocked cards only', () => { + test('blocked shows through the column dot and P0 badge, not a card rail', () => { expect(css).toMatch(/--v2-status-blocked:\s*#d92d20/); - expect(ruleBody(css, '.v2-board__card--blocked')).toContain('box-shadow: inset 3px 0 0 var(--v2-status-blocked-edge)'); + expect(css).not.toContain('--v2-status-blocked-edge'); + expect(css).not.toMatch(/\.v2-board__card--blocked\s*[,{]/); expect(ruleBody(css, '.v2-board__col--blocked .v2-board__col-name')).toContain('color: var(--v2-status-blocked)'); }); + // D225: a status rail down the side of a card reads as generated UI. A card is + // told apart by its column and badge, never by an inset side shadow or a + // coloured border-left. Quote and thread rails are typographic and exempt. + test('no card selector draws a status rail (inset side shadow or coloured border-left >= 2px)', () => { + const offenders: string[] = []; + for (const match of css.matchAll(/([^{}@]+)\{([^{}]*)\}/g)) { + const selector = match[1].trim(); + const body = match[2]; + if (!/card/i.test(selector) || /quote|thread/i.test(selector)) continue; + const insetRail = /box-shadow:[^;]*\binset\s+-?[1-9]\d*(?:\.\d+)?px\s+0(?:px)?\s+0/.test(body) + || /box-shadow:[^;]*\binset\s+0(?:px)?\s+-?[1-9]\d*(?:\.\d+)?px\s+0(?:px)?\s+0/.test(body); + const leftBorder = /border-left(?:-width)?:\s*([2-9]|\d{2,})(?:\.\d+)?px/.exec(body); + const leftColoured = leftBorder && !/border-left(?:-color)?:[^;]*(transparent|none)/.test(body); + if (insetRail || leftColoured) offenders.push(selector); + } + expect(offenders).toEqual([]); + }); test('live, unclaimed and expired ownership retain distinct dots', () => { expect(ruleBody(css, '.v2-board__owner-dot--live')).toContain('background: var(--v2-status-live)'); expect(ruleBody(css, '.v2-board__owner-dot--not-claimed')).toContain('border: 1px dashed var(--v2-status-pending)'); diff --git a/frontend/src/v2/v2.css b/frontend/src/v2/v2.css index 4cb83e1d5..cffc10918 100644 --- a/frontend/src/v2/v2.css +++ b/frontend/src/v2/v2.css @@ -140,7 +140,6 @@ body.modern-ui.v2-canvas { --v2-status-idle: var(--v2-border-strong); --v2-status-pending: #667085; --v2-status-blocked: #d92d20; - --v2-status-blocked-edge: #d92d20; /* Agent role colors */ --v2-pink: #f472b6; @@ -274,7 +273,6 @@ body.modern-ui.v2-canvas { --v2-prio-p2-fg: #8b94a5; --v2-status-pending: #7c8696; --v2-status-blocked: #f97066; - --v2-status-blocked-edge: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; --v2-rail-icon: #5f6979; @@ -348,7 +346,6 @@ body.modern-ui.v2-canvas { --v2-prio-p2-fg: #8b94a5; --v2-status-pending: #7c8696; --v2-status-blocked: #f97066; - --v2-status-blocked-edge: #f97066; --v2-text-primary: #e8ebf1; --v2-message-text: #e8ebf1; --v2-rail-icon: #5f6979; @@ -13817,7 +13814,6 @@ details[open] > .v2-settings__summary > .v2-settings__icon:last-child { transfor .v2-board__priority--p0 { background: var(--v2-prio-p0-bg); border-color: var(--v2-prio-p0-bg); color: var(--v2-prio-p0-fg); } .v2-board__priority--p2 { color: var(--v2-prio-p2-fg); } .v2-board__col--blocked .v2-board__col-name { color: var(--v2-status-blocked); } -.v2-board__card--blocked { box-shadow: inset 3px 0 0 var(--v2-status-blocked-edge); } .v2-board__card-title { font-size: 14px; line-height: 20px; } .v2-board__owner-line { display: flex; align-items: center; gap: 6px; font: 500 11px/16px var(--v2-font-mono); color: var(--v2-text-secondary); } .v2-board__owner-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; } From d06ede9f5c67d2cee2e2e0b9ad2d55a81cd891d3 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Sun, 1 Nov 2026 00:16:33 -0700 Subject: [PATCH 2/2] test(board): card-rail guard ignores comments and covers border-inline-start (TASK-746) Co-Authored-By: Claude Sonnet 5.5 Co-authored-by: otto Agent-Model: claude/claude-opus-5-5/default --- frontend/src/v2/__tests__/v2-layout-invariants.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts index 4ffade49a..888ded94c 100644 --- a/frontend/src/v2/__tests__/v2-layout-invariants.test.ts +++ b/frontend/src/v2/__tests__/v2-layout-invariants.test.ts @@ -5145,14 +5145,16 @@ describe('task priority and lease signals (TASK-443)', () => { // coloured border-left. Quote and thread rails are typographic and exempt. test('no card selector draws a status rail (inset side shadow or coloured border-left >= 2px)', () => { const offenders: string[] = []; - for (const match of css.matchAll(/([^{}@]+)\{([^{}]*)\}/g)) { + // Strip comments first: a comment before a rule would otherwise join its selector. + const bare = css.replace(/\/\*[\s\S]*?\*\//g, ''); + for (const match of bare.matchAll(/([^{}@]+)\{([^{}]*)\}/g)) { const selector = match[1].trim(); const body = match[2]; if (!/card/i.test(selector) || /quote|thread/i.test(selector)) continue; const insetRail = /box-shadow:[^;]*\binset\s+-?[1-9]\d*(?:\.\d+)?px\s+0(?:px)?\s+0/.test(body) || /box-shadow:[^;]*\binset\s+0(?:px)?\s+-?[1-9]\d*(?:\.\d+)?px\s+0(?:px)?\s+0/.test(body); - const leftBorder = /border-left(?:-width)?:\s*([2-9]|\d{2,})(?:\.\d+)?px/.exec(body); - const leftColoured = leftBorder && !/border-left(?:-color)?:[^;]*(transparent|none)/.test(body); + const leftBorder = /border-(?:left|inline-start)(?:-width)?:\s*([2-9]|\d{2,})(?:\.\d+)?px/.exec(body); + const leftColoured = leftBorder && !/border-(?:left|inline-start)(?:-color)?:[^;]*(transparent|none)/.test(body); if (insetRail || leftColoured) offenders.push(selector); } expect(offenders).toEqual([]);