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
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
id: bugfix-828
title: test-dashboard-e2e-strict-mode
protocol: bugfix
phase: pr
plan_phases: []
current_plan_phase: null
gates:
pr:
status: approved
requested_at: '2026-05-28T10:40:10.269Z'
approved_at: '2026-05-28T10:40:57.591Z'
iteration: 1
build_complete: false
history: []
started_at: '2026-05-28T10:30:53.869Z'
updated_at: '2026-05-28T10:40:57.592Z'
pr_history:
- phase: pr
pr_number: 917
branch: builder/bugfix-828
created_at: '2026-05-28T10:39:55.080Z'
pr_ready_for_human: false
49 changes: 49 additions & 0 deletions codev/state/bugfix-828_thread.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# bugfix-828 — strict-mode locator collision

## Investigate

Issue #828: scheduled Dashboard E2E failed because `.work-section:has-text("Recently Closed")` matched **two** sections — the actual Recently Closed section *and* the Backlog section, which renders issue #813 ("vscode: migrate Recently Closed view from sidebar to Codev panel tab") as a row whose title contains the substring "Recently Closed".

`:has-text()` does substring-match across the whole subtree, so any backlog item whose title contains a section heading literal poisons the selector.

Four occurrences of the loose pattern, all in `packages/codev/src/agent-farm/__tests__/e2e/work-view-backlog.test.ts`:

- L77 `Backlog`
- L116 `Backlog`
- L152 `Recently Closed`
- L173 `Recently Closed`

`Backlog` is also vulnerable — any future issue whose title contains "Backlog" would break L77/L116 the same way.

`grep -rn ':has-text' packages/codev/src/agent-farm/__tests__/e2e/` shows no other `.work-section:has-text` matches — the rest of the `:has-text` usage is on tab buttons and `.instance a`, which aren't vulnerable to the same collision.

## Plan

Replace all four with the heading-scoped form:

```ts
page.locator('.work-section:has(h3.work-section-title:text-is("Backlog"))')
```

`:text-is()` matches the exact text content of the heading, not a substring of the section subtree.

## Implement

All four `.work-section:has-text(...)` occurrences in `work-view-backlog.test.ts` replaced with `.work-section:has(h3.work-section-title:text-is("..."))`. Single short comment on the first occurrence explains the workaround (referencing #828). Diff: +60 / -5, test-only.

`porch check` build/tests both pass (after one-time core build to materialize `@cluesmith/codev-core` declaration files in the worktree).

## Review

CMAP-3 all APPROVE:
- Gemini — APPROVE / HIGH
- Codex — APPROVE / MEDIUM
- Claude — APPROVE / HIGH

No KEY_ISSUES from any reviewer. Claude noted `.tab-bar-item:has-text("Work")` uses the same `:has-text` pattern but is safe (button with fixed link text, no dynamic descendants) — verified during sweep.

PR #917 created with CMAP table in body. Architect notified.

## Awaiting

`pr` gate approval from human.
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,11 @@ test.describe('Work view: backlog clickability and artifacts', () => {
await workTab.click();
}

// Wait for backlog section to appear
const backlogSection = page.locator('.work-section:has-text("Backlog")');
// Scope to the heading element — `:has-text` would collide with any backlog
// item whose title contains the section name (see #828).
const backlogSection = page.locator(
'.work-section:has(h3.work-section-title:text-is("Backlog"))',
);
await expect(backlogSection).toBeVisible({ timeout: 15_000 });

// Check if there are backlog rows
Expand Down Expand Up @@ -113,7 +116,9 @@ test.describe('Work view: backlog clickability and artifacts', () => {
await workTab.click();
}

const backlogSection = page.locator('.work-section:has-text("Backlog")');
const backlogSection = page.locator(
'.work-section:has(h3.work-section-title:text-is("Backlog"))',
);
await expect(backlogSection).toBeVisible({ timeout: 15_000 });

// Check for artifact link buttons (they appear when items have specs/plans/reviews)
Expand Down Expand Up @@ -149,7 +154,9 @@ test.describe('Work view: backlog clickability and artifacts', () => {

if (data.recentlyClosed && data.recentlyClosed.length > 0) {
// Recently Closed section should be visible
const closedSection = page.locator('.work-section:has-text("Recently Closed")');
const closedSection = page.locator(
'.work-section:has(h3.work-section-title:text-is("Recently Closed"))',
);
await expect(closedSection).toBeVisible({ timeout: 5_000 });

// Items should be clickable links
Expand All @@ -170,7 +177,9 @@ test.describe('Work view: backlog clickability and artifacts', () => {
}
// If no recently closed items, the section should not be visible
else {
const closedSection = page.locator('.work-section:has-text("Recently Closed")');
const closedSection = page.locator(
'.work-section:has(h3.work-section-title:text-is("Recently Closed"))',
);
await expect(closedSection).not.toBeVisible();
}
});
Expand Down
Loading