Skip to content

vscode: full webview Backlog for unified search input (Path B — replaces #891 stealth-merge) #906

@amrmelsayed

Description

@amrmelsayed

Problem

The Codev sidebar's Backlog needs an embedded text input that filters the rows live as the user types. The user wants the input to feel like a first-class part of the Backlog section — visually indistinguishable from VSCode's own SCM "CHANGES" pattern where the input sits inside the section, with items rendered directly below it in the same scrollable area.

Why not the per-view-webview approach (#891)

#891 explored two lighter approaches and rejected both after visual review in the running worktree:

  1. Shared search webview above Backlog + Builders — felt disconnected (two separate sections with their own headers).
  2. Per-view search webview directly above each TreeView — also felt disconnected; the unavoidable section divider between the search webview and the TreeView below made them read as two peer widgets, not "one thing".
  3. Option i: name: " " stealth-merge of the per-view webview — VSCode still reserves the section header bar (~22px) regardless of name, leaving a residual empty strip above the input. User verdict: "not good at all".

The fundamental constraint: VSCode's view contribution model makes every contributes.views entry a peer section with its own header and divider. You cannot place a webview's content inside another view's section. The only way to render [input → items] as a single visually-unified section is to make the entire section a webview — i.e., replace the Backlog TreeView with a WebviewView.

#891's in-flight branch (builder/pir-891) has the prep work — see "Carry forward" below.

Scope (Path B — full webview Backlog)

Convert

  • codev.backlog becomes a WebviewView (was: TreeView).
  • The webview renders:
    • Search input as the first element inside the section (matching VSCode's SCM CHANGES pattern)
    • Backlog rows rendered as HTML below the input, in the same scrollable area
    • All visually unified — one Backlog section, search input as its first child, items underneath
  • The BacklogProvider TreeView class is removed (or repurposed as a pure data projector — issue numbers + titles + areas → render model — with the webview doing all DOM work).

Visual fidelity (REQUIRED)

  • Theme integration via VSCode CSS variables. At minimum:
    • `--vscode-sideBar-background`
    • `--vscode-foreground`
    • `--vscode-input-background`, `--vscode-input-foreground`, `--vscode-input-border`, `--vscode-input-placeholderForeground`
    • `--vscode-list-hoverBackground`
    • `--vscode-list-activeSelectionBackground`, `--vscode-list-activeSelectionForeground`
    • `--vscode-list-inactiveSelectionBackground`
    • `--vscode-focusBorder`
    • `--vscode-font-family`, `--vscode-font-size`
  • Codicons for row icons (load via `@vscode/codicons` package — matches what the TreeView renders today: `issues` / `account` icons).
  • Hover, focus, selection states matching native list styling pixel-for-pixel.
  • Live theme switching (dark/light) propagates without reload.

Behavioral parity (REQUIRED — do NOT drop)

  • Keyboard navigation: ↑ / ↓ / Home / End / PgUp / PgDn move selection through rows.

  • Enter on a selected row fires the row's primary click action (today: `codev.viewBacklogIssue`).

  • Right-click context menu — replicate today's Backlog row actions. Five actions:

    • Reference Issue in Architect (inline button on the row today)
    • View Issue
    • Spawn Builder
    • Open Issue in Browser
    • Copy Issue Number

    Implementation: HTML/JS custom context menu (VSCode webviews can't contribute to `view/item/context`). Visually close to native.

  • ARIA roles for accessibility (`role='list'` / `role='listitem'`, or `role='treegrid'` if grouping by area is retained).

  • Group-by-area behavior preserved (alphabetical specific areas, then Uncategorized last). The `@cluesmith/codev-core/area-grouping` projection (`groupByArea`, `formatAreaForDisplay`) is shared with other views — reuse it for the render model.

  • Mine-first sort preserved within each group (current user's assigned items sort to top with the `account` codicon).

  • Group expand/collapse persistence — current behavior stores per-area expansion in `workspaceState` under `codev.backlogGroupExpansion`. New webview should preserve this (post-message to the extension on toggle, read back on reload).

Filter scope (per the #891 design discussion)

Case-insensitive substring match across:

  • `id` (issue number)
  • `title`
  • `area` (raw + formatted-for-display)
  • `labels[]` (full label name list, including `area/*`)
  • `assignees[]`
  • `author`

Performance (REQUIRED)

  • Backlog can have hundreds of issues. Use virtual scrolling — only render visible rows + a small buffer. Don't render 500 DOM nodes upfront.
  • Either:
    • Add `lit-virtualizer` as a dependency (small, well-maintained), OR
    • Implement simple manual windowing (~80 LOC: track scrollTop, compute visible range, render that range, pad above/below with sentinel divs)
  • The previous design preference was zero new dependencies. Manual windowing is the path-of-least-resistance.
  • Validate performance with a 500-row fixture before signaling ready.

Carry forward from #891's branch (`builder/pir-891`)

The in-flight #891 branch has prep work that should land with this PR:

  • Wire-format change (commit `5538edfa`): `labels: string[]` added to `OverviewBacklogItem` and `OverviewBuilder` in `packages/types/src/api.ts` and populated in `packages/codev/src/agent-farm/servers/overview.ts`. The new webview's filter scope needs this.
  • Labels-iteration defensive guard (commit `527c8f59`): `Array.isArray(labels) ? labels : []` pattern guards against stale-Tower wire payloads. The new webview's row-render path that iterates `item.labels` must apply the same guard.

What to drop from the #891 branch:

  • The `BacklogSearchViewProvider` separate webview — its filter logic ports into the new combined webview, but the standalone view goes away.
  • The `SearchState` class — filter state becomes local to the new webview component (transient `useState`-style; the extension host doesn't need to know).
  • The `codev.toggleBacklogSearch` command — replaced by clicking the input or using the title-bar focus shortcut.
  • The `codev.backlogSearch` view contribution in package.json.

What to keep from the #891 branch:

  • The wire-format change (above).
  • The labels-iteration guard pattern (apply at the new iteration site).
  • The placeholder copy `"Search backlog..."`.

#891 itself should be closed without merge (the per-view-webview implementation it shipped is being replaced by this issue). The architect decides whether to rebase #891's prep commits onto this issue's branch or cherry-pick them fresh.

Out of scope

  • Builders search. The cost of webview-replacing Builders (loses per-builder changed-files tree, SCM decorations, ~10 context menu actions, accordion) isn't justified by the benefit (users typically have <10 active builders in flight). Builders stays as a TreeView with no search input. See vscode: Backlog search — embed input inside the Backlog section (full WebviewView, replaces TreeView) #891 for the analysis.
  • Search across other Codev views (Pull Requests, Recently Closed, Team) — separate concern.
  • Search history / saved searches / multi-criteria advanced search — over-engineering for v1.
  • Persisting the filter query across sessions — filter is transient; cleared on workspace reload.
  • Match modes (case-sensitive / whole-word / regex) — plain substring only for v1. Can add later without changing public surface.

Acceptance criteria

  • Backlog renders as a single unified section: search input first, items below, in the same scrollable area.
  • Search filters items live as the user types (debounced ~150ms).
  • All today's Backlog row actions still work: View Issue (click), Reference Issue in Architect (inline button), Spawn Builder (right-click), Open in Browser (right-click), Copy Issue Number (right-click).
  • Keyboard navigation works: ↑/↓ at minimum; Home/End/PgUp/PgDn ideally; Enter fires View Issue.
  • Performance acceptable with 500+ rows (smooth scroll, no jank, virtual rendering).
  • Theme variables in use; switching dark/light updates colors live.
  • Group-by-area preserved with expand/collapse persistence in `workspaceState`.
  • Mine-first sort preserved within groups.
  • No regression: Builders view is unchanged (no search section, no buildersSearch view, no toggle icon).
  • Wire-format change from vscode: Backlog search — embed input inside the Backlog section (full WebviewView, replaces TreeView) #891 (`labels: string[]` on `OverviewBacklogItem` / `OverviewBuilder`) landed.
  • Labels-iteration defensively guarded (`Array.isArray(...) ? ... : []`) at every iteration site.
  • Tests pass (DOM logic should be unit-testable via jsdom or vitest's happy-dom).

Suggested protocol

This is a UX-heavy webview rewrite — visual verification in the running extension is the only way to know it's right. The architect's earlier estimate was ~30-40 hours of careful CSS+JS work.

PIR (Plan → Implement → Review) gives the human reviewer the `dev-approval` gate where they can run the worktree and visually confirm. That gate is the right surface for "does this actually look and feel native?". The previous attempt (#891) iterated multiple times at this gate, which is what the gate is for.

SPIR if the architect prefers a fuller spec/plan/review ceremony with consult at every phase. The webview component design (virtual scrolling algorithm, context-menu structure, message protocol) might warrant a spec.

Architect decides; this issue is the WHAT, not the HOW.

Related

Discovered while

PIR #891 dev-approval review on 2026-05-28: tried three lighter approaches (shared webview, per-view webviews, `name: " "` stealth-merge) and visually verified all three felt disconnected. Full WebviewView is the only path to genuine visual unification with VSCode's view contribution model.

Metadata

Metadata

Assignees

Labels

area/vscodeArea: VS Code extension

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions