Skip to content

vscode: Run/Stop Dev Server context-menu entries on builder rows ignore worktree.devCommand presence #975

@amrmelsayed

Description

@amrmelsayed

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:

{
  "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 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:

{
  "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 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:

  1. 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)?

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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

  • Right-clicking a builder row when no worktree.devCommand is configured does not show Run/Stop Dev Server entries
  • Right-clicking a builder row when worktree.devCommand IS configured (shared or layered) shows the entries normally
  • No regression to the Workspace view's existing correct gating
  • No regression to the keybinding behavior (or, if plan decides to gate them, the keybindings become silent rather than firing the underlying error)
  • Live-update: editing .codev/config.json to add/remove worktree.devCommand updates the menu visibility without a window reload (matching v3.1.1's live-refresh behavior for the Workspace view)

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)

Metadata

Metadata

Assignees

No one assigned

    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