Repository navigation
vscode: builder row legibility — phase prefix + gate-specific icons for at-a-glance protocol/state visibility #810
Description
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 (
bellblocked /comment-discussionidle /circle-filledactive)
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:
- 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, butdev-approvalcould be either implement or review phase depending on protocol). - 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:
- Always visible — column truncation cuts the END, not the start. Phase stays on-screen even with long titles in a narrow sidebar.
- Same character cost —
[implement]is[implement]whether prefixed or suffixed. - 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.
- Phase visible across all 3 states — blocked and idle rows gain phase info they don't have today.
- 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:
- Icons stay pre-attentive (same argument that killed the original letter-badge proposal — see "Why this beats the original badge proposal" below). Different shapes get processed in parallel by the visual cortex, no decoding step needed once learned.
- Color uniformity preserved — all blocked = yellow. The user doesn't have to learn "yellow = urgent, gray = less urgent" alongside the gate type.
- Universal git icon for PR — anyone who's used git tools recognizes the pull-request glyph instantly.
- Graceful unknown-gate fallback — protocols that add new gates render as
belluntil the mapping is updated; no crash, no broken rendering. - Composable with everything else — works alongside Change A's phase prefix, doesn't conflict with the existing duration suffix, doesn't fight vscode: re-bucket Builders tree — actively-communicated blocked builders sort below active, truly-blocked stays on top #798's actively-comm-blocked predicate (vscode: re-bucket Builders tree — actively-communicated blocked builders sort below active, truly-blocked stays on top #798 changes the COLOR for actively-comm-blocked rows; this change varies SHAPE per gate — orthogonal axes).
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'sI= 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-approvalshow thebookcodicon (still warning-yellow color) - Blocked builders at
plan-approvalshow thechecklistcodicon (still warning-yellow color) - Blocked builders at
dev-approvalshow theplaycodicon (still warning-yellow color) - Blocked builders at
prgate show thegit-pull-requestcodicon (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.foregroundacross 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 menuwhenclauses) unchanged - Existing
b.idstable id unchanged (still drives accordion + reveal) - Reactive updates work — phase prefix and blocked-gate icon both re-render on
OverviewCacheSSE 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 smallgateIconFor(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
Protocolline + 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
- vscode: re-bucket Builders tree — actively-communicated blocked builders sort below active, truly-blocked stays on top #798 —
isActivelyCommBlockedpredicate (varies COLOR for actively-comm-blocked rows). This issue varies SHAPE for blocked rows per gate type. Orthogonal axes — they compose: adev-approval-blocked actively-comm builder would showplaycodicon in gray (or whatever color vscode: re-bucket Builders tree — actively-communicated blocked builders sort below active, truly-blocked stays on top #798 picks for the actively-comm bucket). - vscode: builder row legibility — phase prefix + gate-specific icons for at-a-glance protocol/state visibility #810 history — this issue originally proposed a 2-character
FileDecorationProviderbadge. That design was reconsidered after reading the existingbuilders.tscode (the trailing suffix already encodes phase + duration richly) and after the visual-design argument that letter codes are harder to read than icons. The revised proposal delivers the original goal (phase + status visibility at-a-glance) with no new infrastructure and no information loss — and adds the bonus gate-type-via-icon-shape signal that the original badge didn't think to encode.
Activity
[-]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[/+][-]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[/+]amrmelsayed commentedon May 29, 2026
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
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
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.
amrmelsayed commentedon May 29, 2026
On it! Working on this with the PIR protocol (plan + dev-approval gates before PR).
[PIR #810] Plan draft
[PIR #930] Plan revised — follow #810 [new]-prefix pattern
[PIR #810] Phase prefix + gate-specific icons for builder rows
[PIR #810] Show coarse protocolPhase in row prefix, not plan sub-phas…
[PIR #810] Lead the row label with the phase prefix, before the issue id
[PIR #810] Add protocolPhase to the local BuilderOverview interface
[PIR #810] Review + retrospective
[PIR #810] Use 'code' icon for dev-approval gate (reflects coding, no…
vscode CHANGELOG: #810 Builder row legibility — phase prefix + gate-s…
Merge docs/vscode-changelog: vscode CHANGELOG entries for #793, #799, #…