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
47 changes: 41 additions & 6 deletions frontend/src/v2/__tests__/v2-layout-invariants.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5041,16 +5041,28 @@ describe('task priority and lease signals (TASK-443)', () => {
expect(badge).toContain('var(--v2-font-mono)');
expect(badge).toContain('border-radius: 4px');
expect(badge).toContain('background: none');
expect(badge).toContain('color: var(--v2-prio-ink)');
expect(badge).toContain('border: 1px solid var(--v2-prio-edge)');
// P0 is the only filled badge: red fill, white label (B-lite, Sam 10-31).
const urgent = ruleBody(css, '.v2-board__priority--p0');
expect(urgent).toContain('border-color: var(--v2-text-primary)');
expect(urgent).toContain('color: var(--v2-text-primary)');
expect(urgent).not.toMatch(/background/);
expect(urgent).toContain('background: var(--v2-prio-p0-bg)');
expect(urgent).toContain('color: var(--v2-prio-p0-fg)');
// Status and priority colour are tokens, so the D215 colour choice is a value swap.
expect(css).toMatch(/--v2-prio-p0-bg:\s*#b42318/);
expect(css).toMatch(/--v2-prio-p2-fg:\s*#667085/);
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', () => {
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(ruleBody(css, '.v2-board__col--blocked .v2-board__col-name')).toContain('color: var(--v2-status-blocked)');
});
test('live, unclaimed and expired ownership retain distinct dots', () => {
expect(ruleBody(css, '.v2-board__owner-dot--live')).toContain('background: var(--v2-accent)');
expect(ruleBody(css, '.v2-board__owner-dot--not-claimed')).toContain('border: 1px dashed var(--v2-border-strong)');
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)');
const expired = ruleBody(css, '.v2-board__owner-dot--expired');
expect(expired).toContain('border: 1px solid var(--v2-border-strong)');
expect(expired).toContain('border: 1px solid var(--v2-status-idle)');
expect(expired).toContain('background: none');
});
});
Expand Down Expand Up @@ -5212,3 +5224,26 @@ describe('v2 board on a phone (TASK-576)', () => {
expect(page).toContain('<V2MobileTabs needsYouCount={attention.count ?? 0} />');
});
});

describe('pod board column scroll (TASK-698)', () => {
const css = read('../v2.css');
test('the board route scrolls inside its columns, not the page', () => {
// The feature body was the scroller and its legacy wrapper had auto height, so the
// columns' max-height:100% resolved to nothing (85.5x at 1440, measured 12f608b71).
const body = ruleBody(css, '.v2-feature--board .v2-feature__body');
expect(body).toContain('display: flex');
expect(body).toContain('overflow: hidden');
const legacy = ruleBody(css, '.v2-feature--board .v2-feature__legacy');
expect(legacy).toContain('width: 100%');
expect(legacy).toContain('min-height: 0');
expect(legacy).toContain('display: flex');
expect(ruleBody(css, '.v2-feature--board .v2-board')).toContain('flex: 1 1 auto');
expect(ruleBody(css, '.v2-board__col')).toContain('overflow-y: auto');
expect(ruleBody(css, '.v2-board__col')).toContain('max-height: 100%');
});
test('a long focus list is bounded so the columns keep room below it', () => {
const focus = ruleBody(css, '.v2-board__focus');
expect(focus).toContain('max-height: min(35vh, 260px)');
expect(focus).toContain('overflow-y: auto');
});
});
6 changes: 3 additions & 3 deletions frontend/src/v2/components/V2PodBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,7 @@ const V2PodBoard: React.FC = () => {
{COLUMNS.map((col) => {
const items = columnTasks.get(col.key) || [];
return (
<section key={col.key} className="v2-board__col" aria-label={t(col.labelKey)}>
<section key={col.key} className={`v2-board__col${col.key === 'blocked' ? ' v2-board__col--blocked' : ''}`} aria-label={t(col.labelKey)}>
<div className="v2-board__col-head">
<span className="v2-board__col-name">{t(col.labelKey)}</span>
<span className="v2-board__col-count">{items.length}</span>
Expand All @@ -744,7 +744,7 @@ const V2PodBoard: React.FC = () => {
<div className="v2-board__col-empty">{t(col.emptyKey)}</div>
)}
{items.map((task) => (
<article key={task.taskId} className={`v2-board__card${col.key === 'done' ? ' v2-board__card--done' : ''}`}>
<article key={task.taskId} className={`v2-board__card${col.key === 'done' ? ' v2-board__card--done' : ''}${col.key === 'blocked' ? ' v2-board__card--blocked' : ''}`}>
<button
type="button"
className="v2-board__card-main"
Expand All @@ -753,7 +753,7 @@ const V2PodBoard: React.FC = () => {
>
<span className="v2-board__card-kicker">
<span className="v2-board__card-id">{task.taskId}</span>
<span className={`v2-board__priority${task.priority === 'P0' ? ' v2-board__priority--p0' : ''}`}>{task.priority || 'P2'}</span>
<span className={`v2-board__priority${task.priority === 'P0' ? ' v2-board__priority--p0' : ''}${(task.priority || 'P2') === 'P2' ? ' v2-board__priority--p2' : ''}`}>{task.priority || 'P2'}</span>
</span>
<span className="v2-board__card-title">{task.title}</span>
{ownerLine(task)}
Expand Down
72 changes: 66 additions & 6 deletions frontend/src/v2/v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,20 @@ body.modern-ui.v2-canvas {
--v2-danger: #ef4444;
--v2-danger-text: #b91c1c;
--v2-danger-soft: rgba(239, 68, 68, 0.10);
/* TASK-687/698: board status and priority colour, the D215 B-lite choice (Sam,
10-31). Blocked and P0 are red; in progress stays cobalt; P1, P2, pending and
done stay grey, with no green. Each state keeps its shape. Dark values are in
both dark blocks below. */
--v2-prio-p0-bg: #b42318;
--v2-prio-p0-fg: #ffffff;
--v2-prio-ink: var(--v2-text-secondary);
--v2-prio-p2-fg: #667085;
--v2-prio-edge: var(--v2-border);
--v2-status-live: var(--v2-accent);
--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;
Expand Down Expand Up @@ -198,6 +212,12 @@ body.modern-ui.v2-canvas {
--v2-border: #364051;
--v2-border-soft: #262d3a;
--v2-border-strong: #5f6979;
--v2-prio-p0-bg: #f97066;
--v2-prio-p0-fg: #0b0e13;
--v2-prio-p2-fg: #8b94a5;
--v2-status-pending: var(--v2-border-strong);
--v2-status-blocked: #f97066;
--v2-status-blocked-edge: #f97066;
--v2-text-primary: #e8ebf1;
--v2-message-text: #e8ebf1;
--v2-text-secondary: #aeb6c4;
Expand Down Expand Up @@ -244,6 +264,12 @@ body.modern-ui.v2-canvas {
--v2-border: #364051;
--v2-border-soft: #262d3a;
--v2-border-strong: #5f6979;
--v2-prio-p0-bg: #f97066;
--v2-prio-p0-fg: #0b0e13;
--v2-prio-p2-fg: #8b94a5;
--v2-status-pending: var(--v2-border-strong);
--v2-status-blocked: #f97066;
--v2-status-blocked-edge: #f97066;
--v2-text-primary: #e8ebf1;
--v2-message-text: #e8ebf1;
--v2-text-secondary: #aeb6c4;
Expand Down Expand Up @@ -8443,6 +8469,32 @@ body.modern-ui.v2-canvas {
background: var(--v2-bg-subtle);
}

/* TASK-698: the board fills the pane and the columns scroll inside it. The feature
body was the scroller and its legacy wrapper had auto height, so `height: 100%`
and the column `max-height: 100%` resolved to nothing and the page grew to
~77,000px (85.5× at 1440, 97.9× at 390, measured by ux-lead at 12f608b71).
Scoped to the board route so other feature pages keep their scroll. */
.v2-feature--board .v2-feature__body {
display: flex;
flex-direction: column;
overflow: hidden;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
/* margin auto stops a flex item stretching, so the board sized to its 1240px
max-content width and ran off a 390px phone. An explicit width keeps it full
width on a phone, and the auto margin still centres it on desktop. */
.v2-feature--board .v2-feature__legacy {
flex: 1 1 auto;
width: 100%;
min-height: 0;
margin: 0 auto;
display: flex;
flex-direction: column;
}
.v2-feature--board .v2-board {
flex: 1 1 auto;
height: auto;
}

.v2-board__head {
display: flex;
align-items: center;
Expand Down Expand Up @@ -8522,6 +8574,11 @@ body.modern-ui.v2-canvas {
border-radius: 12px;
background: var(--v2-bg);
flex: 0 0 auto;
/* A long focus list scrolls inside its own box, so the columns keep room below
it on a short viewport (1440x560, 390x560). vh, not %: the board's height is
auto, so a percentage would not resolve. */
max-height: min(35vh, 260px);
overflow-y: auto;
}

.v2-board__focus-head {
Expand Down Expand Up @@ -13522,19 +13579,22 @@ body.modern-ui.v2-canvas {
.v2-board__priority {
display: inline-block;
padding: 0 5px;
border: 1px solid var(--v2-border);
border: 1px solid var(--v2-prio-edge);
border-radius: 4px;
font: 500 11px/16px var(--v2-font-mono);
color: var(--v2-text-secondary);
color: var(--v2-prio-ink);
background: none;
}
.v2-board__priority--p0 { border-color: var(--v2-text-primary); color: var(--v2-text-primary); }
.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; }
.v2-board__owner-dot--live { background: var(--v2-accent); }
.v2-board__owner-dot--not-claimed { border: 1px dashed var(--v2-border-strong); }
.v2-board__owner-dot--expired { border: 1px solid var(--v2-border-strong); background: none; }
.v2-board__owner-dot--live { background: var(--v2-status-live); }
.v2-board__owner-dot--not-claimed { border: 1px dashed var(--v2-status-pending); }
.v2-board__owner-dot--expired { border: 1px solid var(--v2-status-idle); background: none; }
.v2-board__card--done { border-color: var(--v2-border-soft); }
.v2-board__card--done .v2-board__card-title { color: var(--v2-text-secondary); }
.v2-root button.v2-board__card-move { min-height: 32px; padding: 0 10px; border-radius: 4px; }
Expand Down
Loading