Symptom
Right-clicking a builder row in the Builders tree shows Run Dev Server / Stop Dev Server menu entries even when worktree.devCommand is not configured in .codev/config.json. The Workspace view's inline rows correctly hide when no dev command exists; the builder-row context menu does not.
Picking either entry in this state runs against a missing command — either a confusing error toast or a silent no-op — instead of just not offering the option.
Why the divergence
packages/vscode/package.json:386-394 — the offending entries:
The when clause filters by view + viewItem family only — no worktree.devCommand presence check.
Compare packages/vscode/package.json:396-404 — the Workspace view rows, which work correctly:
These work because the viewItem values (workspace-dev-start / workspace-dev-stop) are only emitted by the TreeView provider when devCommand is configured. The menu entry filtering by viewItem implicitly inherits the config gate — config-absent ⇒ row-absent ⇒ menu-inert. The builder-row wildcards bypass that gate because the row itself is unrelated to the dev command's existence.
Design questions (plan-approval)
The mechanical fix is a codev.hasDevCommand context key on the when clause. But several real design calls inform what shape of context key:
-
Where is the context key set? Extension activation only (simple, but stale on config edits — and #.config-edit live-refresh was an explicit feature in v3.1.1)? Or subscribe to the same config-change events the Workspace view already consumes (consistent, more wiring)?
-
Shared vs layered config. .codev/config.json is the shared file; .codev/config.local.json is the per-engineer override sibling. Does the key reflect shared only or shared + local layered? Per-engineer dev commands are a real use case (e.g. a teammate's preferred port-set). Probably the layered view, matching how the Workspace view already resolves dev command.
-
Per-worktree overrides. Can a builder's .builders/<id>/.codev/config.json define its own worktree.devCommand that the shared root doesn't have? If yes, the context-key's value may legitimately differ per row, in which case a single global context key can't carry the truth and we need per-row metadata (e.g. on the viewItem itself — builder-with-dev- vs builder-no-dev-). If no, one global key suffices.
-
Keybindings. ctrl+alt+r / cmd+alt+r (packages/vscode/package.json:558-567) currently fire codev.runWorkspaceDev unconditionally. Should they also be gated by codev.hasDevCommand? The keybinding currently just falls through to whatever the command does (which is probably error / no-op). Gating means the keystroke is silent if no dev command — defensible, but losing the explicit error feedback. Plan should pick.
-
Command palette. All five Codev: Run/Stop ...Dev... palette entries are visible regardless of config today. Same question — gate or not? Palette discoverability vs surfacing commands that won't work.
-
What "presence" means. Is "configured but empty string" ("devCommand": "") present-but-disabled, or treated as absent? Plan call.
Implementation surface (bounded)
packages/vscode/src/extension.ts — set initial context key, subscribe to config changes that affect it
packages/vscode/src/config-resolver.ts (or equivalent — wherever the layered shared+local resolve happens) — surface a hasDevCommand() accessor that mirrors the Workspace view's current logic
packages/vscode/package.json — add && codev.hasDevCommand to two view/item/context when clauses; possibly extend to keybindings and commandPalette per plan decisions above
- Test surface: a vscode vitest assertion that
package.json's contributes match expected when shapes; manual at dev-approval
Acceptance
Out of scope
- Reshaping
worktree.devCommand itself or what it can contain
- The dashboard's equivalent dev-command surfacing
- The "Open Dev URL" rows from
worktree.devUrls (separate concern, same gating principle though — worth a sibling audit at plan time)
Why PIR
Multiple real design calls (questions 1-6 above) deserve plan-approval rather than ad-hoc implementation choices. The visual surface (context menu visibility across multiple config states + live config-edit cases) needs dev-approval running-app verification — PR diff alone won't catch "context key didn't refresh after the config-change event."
Related
- v3.1.1's Workspace view live-refreshes on config edits (the live-update precedent)
- v3.0.6's Codev-managed dev server for the current workspace (the feature that introduced the gating that's broken on the builder-row side)
Symptom
Right-clicking a builder row in the Builders tree shows Run Dev Server / Stop Dev Server menu entries even when
worktree.devCommandis not configured in.codev/config.json. The Workspace view's inline rows correctly hide when no dev command exists; the builder-row context menu does not.Picking either entry in this state runs against a missing command — either a confusing error toast or a silent no-op — instead of just not offering the option.
Why the divergence
packages/vscode/package.json:386-394— the offending entries:{ "command": "codev.runWorktreeDev", "when": "view == codev.builders && viewItem =~ /^(builder|blocked-builder|awaiting-builder)-/", "group": "3_dev@2" }, { "command": "codev.stopWorktreeDev", "when": "view == codev.builders && viewItem =~ /^(builder|blocked-builder|awaiting-builder)-/", "group": "3_dev@3" }The
whenclause filters by view + viewItem family only — noworktree.devCommandpresence check.Compare
packages/vscode/package.json:396-404— the Workspace view rows, which work correctly:{ "command": "codev.runWorkspaceDev", "when": "view == codev.workspace && viewItem == workspace-dev-start", ... }, { "command": "codev.stopWorkspaceDev", "when": "view == codev.workspace && viewItem == workspace-dev-stop", ... }These work because the
viewItemvalues (workspace-dev-start/workspace-dev-stop) are only emitted by the TreeView provider whendevCommandis configured. The menu entry filtering by viewItem implicitly inherits the config gate — config-absent ⇒ row-absent ⇒ menu-inert. The builder-row wildcards bypass that gate because the row itself is unrelated to the dev command's existence.Design questions (plan-approval)
The mechanical fix is a
codev.hasDevCommandcontext key on thewhenclause. But several real design calls inform what shape of context key:Where is the context key set? Extension activation only (simple, but stale on config edits — and
#.config-editlive-refresh was an explicit feature in v3.1.1)? Or subscribe to the same config-change events the Workspace view already consumes (consistent, more wiring)?Shared vs layered config.
.codev/config.jsonis the shared file;.codev/config.local.jsonis the per-engineer override sibling. Does the key reflect shared only or shared + local layered? Per-engineer dev commands are a real use case (e.g. a teammate's preferred port-set). Probably the layered view, matching how the Workspace view already resolves dev command.Per-worktree overrides. Can a builder's
.builders/<id>/.codev/config.jsondefine its ownworktree.devCommandthat the shared root doesn't have? If yes, the context-key's value may legitimately differ per row, in which case a single global context key can't carry the truth and we need per-row metadata (e.g. on theviewItemitself —builder-with-dev-vsbuilder-no-dev-). If no, one global key suffices.Keybindings.
ctrl+alt+r/cmd+alt+r(packages/vscode/package.json:558-567) currently firecodev.runWorkspaceDevunconditionally. Should they also be gated bycodev.hasDevCommand? The keybinding currently just falls through to whatever the command does (which is probably error / no-op). Gating means the keystroke is silent if no dev command — defensible, but losing the explicit error feedback. Plan should pick.Command palette. All five
Codev: Run/Stop ...Dev...palette entries are visible regardless of config today. Same question — gate or not? Palette discoverability vs surfacing commands that won't work.What "presence" means. Is "configured but empty string" (
"devCommand": "") present-but-disabled, or treated as absent? Plan call.Implementation surface (bounded)
packages/vscode/src/extension.ts— set initial context key, subscribe to config changes that affect itpackages/vscode/src/config-resolver.ts(or equivalent — wherever the layered shared+local resolve happens) — surface ahasDevCommand()accessor that mirrors the Workspace view's current logicpackages/vscode/package.json— add&& codev.hasDevCommandto twoview/item/contextwhenclauses; possibly extend tokeybindingsandcommandPaletteper plan decisions abovepackage.json's contributes match expectedwhenshapes; manual atdev-approvalAcceptance
worktree.devCommandis configured does not show Run/Stop Dev Server entriesworktree.devCommandIS configured (shared or layered) shows the entries normally.codev/config.jsonto add/removeworktree.devCommandupdates the menu visibility without a window reload (matching v3.1.1's live-refresh behavior for the Workspace view)Out of scope
worktree.devCommanditself or what it can containworktree.devUrls(separate concern, same gating principle though — worth a sibling audit at plan time)Why PIR
Multiple real design calls (questions 1-6 above) deserve plan-approval rather than ad-hoc implementation choices. The visual surface (context menu visibility across multiple config states + live config-edit cases) needs
dev-approvalrunning-app verification — PR diff alone won't catch "context key didn't refresh after the config-change event."Related