Skip to content

vscode: builder row legibility — phase prefix + gate-specific icons for at-a-glance protocol/state visibility #810

Closed

Description

@amrmelsayed

Problem

Builder rows in the Builders tree show three pieces of info today (packages/vscode/src/views/builders.ts:89-99 and :119-123):

  • Label: #<id> <issueTitle> <stateLabel> — state-dispatched suffix
  • Icon: state-dispatched (bell blocked / comment-discussion idle / circle-filled active)

Two distinct legibility gaps at a glance:

Gap 1 — Phase isn't visible at-a-glance

The <stateLabel> is:

  • Blocked: blocked on <gate> [<elapsed>]
  • Idle: waiting on input [<elapsed> silent]
  • Active: [<phase>]

The phase name is only visible for active builders, and only as a trailing suffix. Two problems:

  1. Blocked rows hide the phase entirely. A row reading #791 Startup preflight ⏳ blocked on plan-approval [12m] doesn't tell you the builder is in the plan phase (you might infer from the gate name, but dev-approval could be either implement or review phase depending on protocol).
  2. Long titles truncate the trailing phase off-screen. When the sidebar is narrow or the title is long (typical for our issue titles), the [implement] suffix gets clipped — the leftmost characters survive, the rightmost (phase indicator) are first to go.

Gap 2 — Gate type isn't visible at-a-glance for blocked rows

When a builder hits a gate, all blocked rows show the same bell icon regardless of which kind of review is needed:

  • A spec-approval blocked builder looks identical to
  • A plan-approval blocked builder, which looks identical to
  • A dev-approval blocked builder, which looks identical to
  • A PR-gate blocked builder

The gate name lives only in the label text (blocked on <gate>). A user scanning the tree to triage "any plan reviews queued?" has to read each blocked row's text, or hover for tooltip. The icon — the most pre-attentively processed signal in the row — carries zero gate-type information.

Revised proposal — two compositional changes

Both changes operate on the same file (builders.ts:89-123), both make the Builders tree more legible at a glance, both are tiny. Shipping them together is one PR, one review pass, one visual-verification cycle.

Change A — phase prefix after the issue number

Move the phase indicator from a trailing suffix to a leading prefix immediately after the issue number:

#<id> [<phase>] <issueTitle> <stateLabel>

Where <stateLabel> keeps the existing state-dispatched form without the now-redundant [<phase>] active-state variant:

  • Blocked: blocked on <gate> [<elapsed>]
  • Idle: waiting on input [<elapsed> silent]
  • Active: (empty — phase prefix already covers it)

Examples:

Today Revised
#882 refactor: extract gitignore... [implement] #882 [implement] refactor: extract gitignore...
#791 Startup preflight blocked on plan-approval [12m] #791 [plan] Startup preflight blocked on plan-approval [12m]
#794 Notification refactor waiting on input [5m silent] #794 [implement] Notification refactor waiting on input [5m silent]

Why prefix beats suffix:

  1. Always visible — column truncation cuts the END, not the start. Phase stays on-screen even with long titles in a narrow sidebar.
  2. Same character cost — [implement] is [implement] whether prefixed or suffixed.
  3. Pre-attentive position — the eye scans left-to-right; phase shows up in the second-most-prominent position (after the issue ID itself) instead of buried at the row's tail.
  4. Phase visible across all 3 states — blocked and idle rows gain phase info they don't have today.
  5. Removes the redundant active-state [<phase>] suffix — phase only appears once per row, not duplicated.

Change B — gate-specific codicons for blocked builders

Today's universal bell icon (line 119-120) becomes a small mapping that picks the codicon by gate name. Color stays uniform (notificationsWarningIcon.foreground warning yellow) so "needs your attention" remains the consistent signal — the shape encodes WHAT kind of attention:

Gate Codicon Why
spec-approval book Document review (spec is prose-heavy)
plan-approval checklist Plan/structure review
dev-approval play Run-and-verify (PIR's "test before PR" semantic)
pr git-pull-request Universal git icon — instantly recognizable
unknown / future gates bell Fallback so new gates don't break rendering

Why this works well:

Why this combined proposal beats the original badge design

This issue's original framing was a 2-character FileDecorationProvider-driven badge (IA, PC, FB, etc.) replacing the existing icons. That approach was reconsidered for three reasons:

  • Letter codes are harder to read than icons. Icons are pre-attentively processed; letters require sequential decoding even after learning.
  • The Source Control A/M/D analogy doesn't hold. A/M/D is 3 mutually-exclusive values in one dimension; the original badge had 2 dimensions (phase × status) with combinatorial space and protocol-dependent phase letters (SPIR's I = Implement, BUGFIX's I = Investigate).
  • The original suffix already encodes phase + duration richly (e.g. blocked on plan-approval [12m]). A 2-letter badge replacement would lose duration info that today's suffix carries.

The revised proposal keeps icons as the primary at-a-glance signal (status + now gate type), adds phase as a leading text prefix (always-visible position), and preserves the existing state-specific suffix for blocked/idle duration. Strict information gain across two dimensions (phase visibility, gate-type visibility), no loss.

Implementation

Both changes land in packages/vscode/src/views/builders.ts. Single PR, ~15 LOC total.

Change A — label construction (builders.ts:89-99)

Current:

const phaseLabel = isBlocked
  ? `blocked on ${b.blocked}${waitTime}`
  : isIdle
  ? `waiting on input${idleTime}`
  : `[${b.phase}]`;
const item = new BuilderTreeItem(b.id, `#${b.issueId ?? b.id} ${b.issueTitle ?? ''} ${phaseLabel}`);

Becomes:

const stateLabel = isBlocked
  ? ` blocked on ${b.blocked}${waitTime}`
  : isIdle
  ? ` waiting on input${idleTime}`
  : '';
const phasePrefix = b.phase ? `[${b.phase}] ` : '';
const item = new BuilderTreeItem(
  b.id,
  `#${b.issueId ?? b.id} ${phasePrefix}${b.issueTitle ?? ''}${stateLabel}`,
);

Handle the edge case b.phase empty (rare transient init state) by omitting the prefix — row reads #<id> <title> rather than #<id> [] <title>.

Change B — gate-icon mapping (builders.ts:119-123)

Current:

item.iconPath = isBlocked
  ? new vscode.ThemeIcon('bell', new vscode.ThemeColor('notificationsWarningIcon.foreground'))
  : isIdle
  ? new vscode.ThemeIcon('comment-discussion', new vscode.ThemeColor('notificationsInfoIcon.foreground'))
  : new vscode.ThemeIcon('circle-filled', new vscode.ThemeColor('testing.iconPassed'));

Becomes:

// Gate-specific icons keep the warning-yellow color uniform (still "needs your attention")
// but encode WHAT KIND of review via the codicon shape. Falls back to `bell` for unknown
// or future gates so new protocols don't break rendering.
const GATE_ICONS: Record<string, string> = {
  'spec-approval': 'book',
  'plan-approval': 'checklist',
  'dev-approval': 'play',
  'pr': 'git-pull-request',
};
const blockedIcon = (b.blocked && GATE_ICONS[b.blocked]) || 'bell';
item.iconPath = isBlocked
  ? new vscode.ThemeIcon(blockedIcon, new vscode.ThemeColor('notificationsWarningIcon.foreground'))
  : isIdle
  ? new vscode.ThemeIcon('comment-discussion', new vscode.ThemeColor('notificationsInfoIcon.foreground'))
  : new vscode.ThemeIcon('circle-filled', new vscode.ThemeColor('testing.iconPassed'));

No FileDecorationProvider, no custom URI scheme, no new infrastructure. The tooltip stays unchanged.

Acceptance criteria

Change A — phase prefix

  • Every builder row's label includes a [<phase>] prefix immediately after the issue number, before the issue title
  • When phase is empty (transient init state), no prefix appears (no [] literal)
  • Active-state rows no longer carry a trailing [<phase>] (it's now the prefix; redundancy removed)
  • Blocked-state rows still carry blocked on <gate> [<elapsed>] after the title
  • Idle-state rows still carry waiting on input [<elapsed> silent] after the title

Change B — gate-specific icons

  • Blocked builders at spec-approval show the book codicon (still warning-yellow color)
  • Blocked builders at plan-approval show the checklist codicon (still warning-yellow color)
  • Blocked builders at dev-approval show the play codicon (still warning-yellow color)
  • Blocked builders at pr gate show the git-pull-request codicon (still warning-yellow color)
  • Blocked builders at any other gate (unknown / future) fall back to bell (still warning-yellow color) — never crash, never render without an icon
  • Color stays uniform notificationsWarningIcon.foreground across every gate variant
  • Mapping documented in a comment so future-gate additions are obvious where to edit

General

  • Idle-state icon unchanged (comment-discussion + info color)
  • Active-state icon unchanged (circle-filled + passed color)
  • Existing tooltip (Protocol: ... | Mode: ... | Progress: ...%) unchanged
  • Existing contextValue (used by menu when clauses) unchanged
  • Existing b.id stable id unchanged (still drives accordion + reveal)
  • Reactive updates work — phase prefix and blocked-gate icon both re-render on OverviewCache SSE ticks when the underlying field changes
  • Test in packages/vscode/src/__tests__/ covers: all 3 state variants of the label (active/blocked/idle), the empty-phase edge case, and at least one gate-mapping per row for the icon variants (could be a unit test on a small gateIconFor(gateName) helper if Change B is extracted)

Out of scope

  • FileDecorationProvider-based 2-character badge — explicitly rejected (see "Why this combined proposal beats the original badge design" above)
  • Color-coding the phase prefix — keep it as monochrome bracket text; icons already carry color signal for status
  • Color-coding the gate-specific icons differently — explicitly uniform yellow across all gates so "needs your attention" stays the consistent meta-signal
  • Localizing the phase names or gate names — phase + gate strings come from porch state as English-only today; that's a separate concern
  • Replacing the idle/active icons — only the blocked-state icon dispatches by gate; idle and active stay as today
  • Showing phase or gate in the tooltip — already covered by the tooltip's Protocol line + the row's text label; tooltip enrichment is a separate concern
  • Status-bar segment per gate type (e.g. 2 plan / 1 dev / 1 pr) — a natural sibling idea, but a separate UI surface; file separately if desired
  • Grouping/sorting blocked builders by gate type within the blocked bucket — a natural sibling idea, but a separate behavior change; file separately if desired

Related

Activity

  1. self-assigned this
    on May 21, 2026
  2. added
    area/vscodeArea: VS Code extension
    projectNew project or feature
    and removed on May 21, 2026
  3. changed the title [-]vscode: builder row badges — 2-char phase+status indicator (like git A/M/D/R) with state-colored letters[/-] [+]vscode: builder row — phase prefix after issue number for at-a-glance protocol position[/+] on May 27, 2026
  4. changed the title [-]vscode: builder row — phase prefix after issue number for at-a-glance protocol position[/-] [+]vscode: builder row legibility — phase prefix + gate-specific icons for at-a-glance protocol/state visibility[/+] on May 27, 2026
  5. amrmelsayed commented on May 29, 2026

    @amrmelsayed
    CollaboratorAuthor

    Follow-up from #927/#928: the shared `GATE_LABELS` map now includes a new entry — `verify-approval` → `"verify review"` — which flows into VSCode's builder tree, gate toast, and status-bar counter via `detectBlocked` / `detectBlockedGate` / `detectBlockedSince` (see `packages/codev/src/agent-farm/servers/overview.ts:421-432`). With #810's gate-icon mapping as currently scoped, a verify-blocked builder would fall through to the `bell` fallback. That works, but it conflates verify-approval with "any future unmapped gate."

    Suggest adding verify-approval to the proposed Change B mapping explicitly. Recommended codicon: `verified` — semantically perfect match (it's literally VSCode's "verified badge" glyph) and visually distinct from the other gate icons (`book`, `checklist`, `play`, `git-pull-request`).

    Recommended edit to the Change B table

    Gate Codicon Why
    `spec-approval` `book` Document review (spec is prose-heavy)
    `plan-approval` `checklist` Plan/structure review
    `dev-approval` `play` Run-and-verify (PIR's "test before PR" semantic)
    `pr` `git-pull-request` Universal git icon — instantly recognizable
    `verify-approval` `verified` Post-merge production verification — VSCode's verified-badge glyph
    unknown / future gates `bell` Fallback so new gates don't break rendering

    Recommended edit to the Change B implementation snippet

    ```ts
    const GATE_ICONS: Record<string, string> = {
    'spec-approval': 'book',
    'plan-approval': 'checklist',
    'dev-approval': 'play',
    'pr': 'git-pull-request',
    'verify-approval': 'verified', // NEW (post-#927)
    };
    ```

    Recommended addition to the Change B acceptance criteria

    • Blocked builders at `verify-approval` show the `verified` codicon (still warning-yellow color)

    This addition is fully consistent with #810's design rationale: shape encodes WHAT KIND of attention, color stays uniform yellow. The `verified` codicon is the only one that semantically matches "post-merge verification" without overloading an existing icon's meaning.

  6. amrmelsayed commented on May 29, 2026

    @amrmelsayed
    CollaboratorAuthor

    On it! Working on this with the PIR protocol (plan + dev-approval gates before PR).

  7. added 8 commits that reference this issue on May 30, 2026
  8. added 2 commits that reference this issue on May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/vscodeArea: VS Code extensionprojectNew project or feature

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions