From d707d298186143042d9c4cfbf184b6cec9170991 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Thu, 4 Jun 2026 07:40:33 +1000 Subject: [PATCH 01/34] chore(porch): 921 init pir --- .../status.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 codev/projects/921-vscode-codev-dev-surface-botto/status.yaml diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml new file mode 100644 index 000000000..9b1a888f0 --- /dev/null +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -0,0 +1,18 @@ +id: '921' +title: vscode-codev-dev-surface-botto +protocol: pir +phase: plan +plan_phases: [] +current_plan_phase: null +gates: + plan-approval: + status: pending + dev-approval: + status: pending + pr: + status: pending +iteration: 1 +build_complete: false +history: [] +started_at: '2026-06-03T21:40:33.254Z' +updated_at: '2026-06-03T21:40:33.255Z' From 064a17b980ede13bdb8857a1997fb9f1edc7f888 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Thu, 4 Jun 2026 09:34:22 +1000 Subject: [PATCH 02/34] [PIR #921] Plan draft (design-of-record; held pending #812) --- .../921-vscode-codev-dev-surface-botto.md | 224 ++++++++++++++++++ codev/state/pir-921_thread.md | 44 ++++ 2 files changed, 268 insertions(+) create mode 100644 codev/plans/921-vscode-codev-dev-surface-botto.md create mode 100644 codev/state/pir-921_thread.md diff --git a/codev/plans/921-vscode-codev-dev-surface-botto.md b/codev/plans/921-vscode-codev-dev-surface-botto.md new file mode 100644 index 000000000..9b77bf360 --- /dev/null +++ b/codev/plans/921-vscode-codev-dev-surface-botto.md @@ -0,0 +1,224 @@ +# PIR Plan: Codev Dev surface — bottom-panel tab + always-visible status-bar chip + +> **Status: design-of-record, HELD pending #812.** Per architect decision at the +> `plan-approval` gate, #921 stays whole (the panel tab lives in #812's shared +> `codevPanel` container) and implementation does **not** begin until #812 +> ("introduce a Codev panel tab / bottom-area view container") ships. This plan +> is the approved design the implement phase will execute once that dependency +> clears. #812 has been raised to the architect for scheduling so this work can +> unblock rather than wait passively. + +## Understanding + +`afx dev` runs a **single** dev PTY (one slot across `{main + all builders}` — +they all bind main's ports, so only one can run at a time). Today that PTY is +surfaced only as a generic VSCode terminal tab named `Codev: (dev)` +(`terminal-manager.ts` → `openDevTerminal`, a `CodevPseudoterminal` over a Tower +WebSocket, placed in `TerminalLocation.Panel`). It is visually indistinguishable +from any other terminal, so a reviewer — typically someone running +`afx dev ` to exercise a builder's running worktree at PIR's +`dev-approval` gate — cannot tell at a glance: + +- **Is a dev running at all?** +- **Which target is it?** (`main` vs `pir-809` — easy to lose across worktrees.) +- **How do I stop / restart it fast** without hunting the terminal dropdown or + opening the Workspace sidebar view (which costs sidebar real-estate each time)? + +The Workspace view's Start/Stop Dev row partially addresses this, but only when +the sidebar is on that view. The need is **ambient awareness + fast control of +the single dev server** — surfaced where it's always visible and quick to act on. + +### The use case is awareness + control, NOT terminal fidelity + +This framing (confirmed with the architect) is load-bearing for the design. The +user does not need ANSI-faithful scrollback, search, or copy in a *new* surface — +the existing native terminal tab already does that well and **stays**. The new +surfaces exist to answer "is it running / which target / stop-restart" at a +glance. That rules out the heavy "re-implement a terminal in a webview (xterm.js ++ a second Tower WS subscription)" path: it adds failure surface and +re-implements terminal features for no user-need gain. + +### Why this depends on #812 (editorial, not technical) + +- The **status-bar chip** depends on nothing — it is a `createStatusBarItem`. +- The **panel tab** needs *a* bottom-panel `viewsContainer` to mount into. #812 + creates the shared `codevPanel` container (the same one #813 / #814 / #815 + migrate into). This PR *could* declare its own throwaway container instead, but + that would create exactly the interim bottom-container proliferation #812 is + meant to prevent. So the dependency is a **consolidation** choice: mount in the + shared container rather than spawn a one-off. Per architect decision, #921 + stays whole and waits for `codevPanel` rather than shipping a partial or a + throwaway container. + +## Proposed Change + +Two complementary surfaces, both driven off the existing dev-terminal lifecycle +(`TerminalManager.onDidChangeDevTerminals`, `listDevTerminals()`), with the +native terminal tab left intact as the output surface (COEXIST). + +### Surface 1 — Codev Dev panel tab (`codev.devServer`) + +A **`TreeDataProvider`** (not a webview) mounted in #812's `codevPanel` +container. TreeDataProvider is chosen deliberately: it matches every existing +Codev view (`status.ts`, `team.ts`, `recently-closed.ts`), needs no webview / +CSP / xterm machinery, and is the right weight for a status surface. Because the +use case is status + control (output stays in the native terminal), a tree of +status rows + title-bar actions fully covers it. + +- **Tab title**: `Codev Dev`. +- **Rows (status header)** while running: + - `Target: ` (`main` or `pir-XXX`, via the dev terminal's `builderId` → + friendly name; reuse `resolveWorkspaceDevTarget` naming). + - `Running for ` (e.g. `4m 32s`) — refreshed every second by a timer + that fires `onDidChangeTreeData` while a dev is running, disposed on stop. + - `Port: ` — **best-effort**: derive from `worktree.devUrls` / `devCommand` + config if present; **omit the row entirely if undetectable** (no guessing). +- **Idle / stopped states** (design call #1 + lifecycle): + - Never-run / idle → the view contributes a single placeholder row + (`No dev running — start via afx dev or the Workspace view`). The + tab is present-but-empty rather than vanishing, so the container tab strip is + stable. (The chip remains the always-visible "is it running" signal.) + - After a dev stops → an epitaph row `Stopped — last target , ran ` + until the user dismisses it or starts another dev. The actual log remains in + the native terminal, so we do not try to preserve output here. +- **Tab badge**: a small activity dot on the `Codev Dev` tab when a dev is + running and the user is focused on another `codevPanel` tab (VSCode + `TreeView.badge`). +- **Title-bar actions** (`view/title`, `group: navigation`, guarded by a + `codev.devServerRunning` context key where action requires a live dev): + - `Stop Dev Server` (`$(debug-stop)`) → `codev.devServer.stop`. + - `Restart Dev Server` (`$(debug-restart)`) → `codev.devServer.restart` + (stop current target, then start the same target). + - `Switch Target` (`$(arrow-swap)`) → `codev.devServer.switchTarget`: Quick + Pick of `main` + builders; reuses the single-slot swap semantics of + `startDevForTarget`. **Always shown** (design call #5 — consistent placement). + - `Reveal in Workspace View` (`$(eye)`) → `codev.devServer.revealInWorkspace`: + focus `codev.workspace` and its Dev Server row. + +### Surface 2 — status-bar chip + +- A **second, independent** `StatusBarItem` (`StatusBarAlignment.Left`, + priority **99** — left of the existing connection/builder-count item at 100). +- **Visibility**: created when a dev starts, disposed when it stops (driven by + `onDidChangeDevTerminals`). +- **Text**: `$(zap) Dev: ` (e.g. `$(zap) Dev: pir-809`). +- **Background**: `new vscode.ThemeColor('statusBarItem.prominentBackground')` + (design call #4 — canonical; theme-safe, no hand-coded color). +- **Tooltip**: `Codev dev server running for · Click to focus Codev Dev panel`. +- **Click**: `codev.devServer.focus` — reveal the `Codev Dev` tab (open the panel + if closed, switch to the tab if on another). Thin breadcrumb; no Quick Pick + layer between chip and tab. + +### Shared lifecycle plumbing + +- A small client-side map `builderId → startedAt` populated when `openDevTerminal` + fires and cleared on `closeDevTerminal`, so uptime and the epitaph "ran Xs" have + a start time (`listDevTerminals()` currently carries only `{builderId, terminalId}`). +- Both surfaces subscribe to the **single** `onDidChangeDevTerminals` event and + re-derive state from `listDevTerminals()` (single source of truth). Target + swaps (stop A → start B) update both surfaces in lockstep because both reads + go through the same event + list. + +### Resolved design calls (from the issue) + +| # | Question | Decision | +|---|----------|----------| +| 1 | Tab when no dev running | Present-but-placeholder (stable tab strip; chip is the always-visible signal). Post-stop shows an epitaph row. | +| 2 | PTY output rendering | **No output rendering in-tab.** Status-header tree only; output stays in the native terminal. Justified by the use case (awareness/control, not fidelity). Plain-log tail is a possible future enhancement, explicitly out of scope here. | +| 3 | Replace vs coexist with native terminal | **Coexist.** Native `Codev: (dev)` terminal stays as the output surface; safest for muscle memory and avoids re-plumbing. | +| 4 | Chip background | `prominentBackground`. | +| 5 | `Switch Target` visibility | Always shown. | + +## Files to Change + +> All under `packages/vscode/`. Executed in the implement phase **after #812 lands** +> (so `codevPanel` exists in `package.json`'s `viewsContainers.panel`). + +- `packages/vscode/src/views/dev-server.ts` — **new.** `DevServerTreeProvider` + implementing `TreeDataProvider`: renders the status header / + placeholder / epitaph rows, owns the 1s uptime refresh timer, exposes + `onDidChangeTreeData`. Subscribes to `terminalManager.onDidChangeDevTerminals`. +- `packages/vscode/src/views/dev-server-format.ts` — **new (pure helpers).** + `formatUptime(ms)`, target-name derivation, port-from-config extraction. Pure + and unit-tested (vitest, `src/__tests__/`). +- `packages/vscode/src/commands/dev-server-actions.ts` — **new.** Thin command + handlers `stop` / `restart` / `switchTarget` / `revealInWorkspace` / `focus`, + delegating to existing `dev-shared.ts` (`startDevForTarget`, `stopDevForTarget`) + and `terminalManager`. +- `packages/vscode/src/terminal-manager.ts` — add the `builderId → startedAt` + map (set in `openDevTerminal` ~`:220`, cleared in `closeDevTerminal` ~`:244`) + and a getter so the view/chip can read start times. No change to existing + terminal behavior. +- `packages/vscode/src/extension.ts` — create/dispose the chip `StatusBarItem` + (driven by `onDidChangeDevTerminals`), register `codev.devServer.*` commands + (`regCli` guard), register the `codev.devServer` tree view, maintain the + `codev.devServerRunning` context key. +- `packages/vscode/package.json`: + - add `codev.devServer` view inside the `codevPanel` `viewsContainer` (created + by #812) under `contributes.views`; + - add the five `codev.devServer.*` command declarations (titles + icons); + - add the four `view/title` menu entries (`when: view == codev.devServer`, + `group: navigation`, plus `codev.devServerRunning` gating where needed). + +## Risks & Alternatives Considered + +- **Risk — #812 not yet merged (the reason for the hold).** Mounting into a + non-existent `codevPanel` container fails. Mitigation: implementation gated on + #812; #812 raised for scheduling. *No code lands until `codevPanel` exists.* +- **Risk — port is often undetectable.** `listDevTerminals()` carries no port and + there's no stdout parsing today. Mitigation: best-effort from config only; omit + the row when unknown rather than guess. Acceptance is written as "port if known". +- **Risk — uptime needs a start timestamp not currently tracked.** Mitigation: the + small `startedAt` map in `TerminalManager`; if a dev predates extension + activation (reconnect), show `Running` without a duration rather than a wrong one. +- **Risk — two surfaces drift out of sync on swaps.** Mitigation: both derive from + the single `onDidChangeDevTerminals` + `listDevTerminals()`; no independent state. +- **Risk — regressing the Workspace view's dev row.** Mitigation: this PR only + *adds* surfaces and reads the same `listDevTerminals()`; no change to + `workspace.ts`'s row logic. Covered by the test plan. +- **Alternative — xterm.js webview + replace native terminal (design call #2/#3 + heavy path).** Rejected: re-implements terminal features for no use-case gain, + adds a second WS subscription and CSP/webview surface. The native terminal + already serves output. +- **Alternative — ship chip + commands now, defer only the tab (avoid the hold).** + Technically viable (chip has no #812 dependency) and was proposed; architect + chose to keep #921 whole and hold for consolidation. Recorded here as the + considered-and-rejected option. +- **Alternative — standalone throwaway container now, re-home into `codevPanel` + later.** Rejected: creates the interim container proliferation #812 exists to + prevent. + +## Test Plan + +> Reviewer exercises this at the `dev-approval` gate **once implementation +> proceeds** (post-#812). Until then this plan is held and no running build exists. + +- **Unit (vitest, `src/__tests__/dev-server-format.test.ts`)**: `formatUptime` + (seconds, minutes, `4m 32s`, hour rollover, 0s edge), target-name derivation + (`main` vs `pir-XXX`), port extraction (present in config → value; absent → + `null`/omit). +- **Manual — chip**: start `afx dev main` and `afx dev `; chip appears + bottom-left as `$(zap) Dev: ` with prominent tint; tooltip correct; + click focuses the Codev Dev tab (opening the panel if closed). Stop → chip + disappears. +- **Manual — tab**: status header shows correct target, live-ticking uptime, and + port when derivable (omitted otherwise); title-bar Stop / Restart / Switch + Target / Reveal-in-Workspace each behave as labeled; activity-dot badge shows + when running and focused on another `codevPanel` tab; post-stop epitaph row. +- **Manual — swap lockstep**: with `pir-809` dev running, Switch Target → `main`; + both chip and tab update to `main` together; the single-slot swap prompt fires + as today. +- **Manual — Workspace view parity**: confirm the existing Start/Stop Dev row + reflects the same state; no regression. +- **Themes**: verify chip + tab render cleanly in Dark, Light, and High-Contrast + (ThemeColor / theme CSS vars only — no hand-coded colors). + +## Dependency & Sequencing (summary) + +1. **Blocked on #812** (`codevPanel` container) — raised to architect for + scheduling. +2. This plan is approved as design-of-record and **held** at/after + `plan-approval`. The implement phase begins only once #812 has merged and + `codevPanel` exists in `packages/vscode/package.json`. +3. No sibling dependency on #813 / #814 / #815 — independent tabs in the same + container; any order after #812. diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md new file mode 100644 index 000000000..721681cce --- /dev/null +++ b/codev/state/pir-921_thread.md @@ -0,0 +1,44 @@ +# PIR-921 Thread — Codev Dev panel tab + status-bar chip + +## Plan phase — investigation (start) + +Issue #921: dedicated "Codev Dev" bottom-panel tab + always-visible status-bar chip for the `afx dev` PTY. + +### Key findings from investigation + +1. **Hard dependency #812 (codevPanel viewsContainer) is NOT landed.** + - #812 still OPEN; no `codevPanel` in `packages/vscode/package.json` (only the `codev` activitybar container). + - No branch / sibling builder working on it. So it is not in flight. + - Issue #921 states it "cannot land until #812 ships." → genuine blocker / fork. + +2. **Dev PTY is already a native VSCode terminal tab.** + - `terminal-manager.ts` opens `Codev: (dev)` as a `CodevPseudoterminal` (WebSocket → Tower) in `TerminalLocation.Panel`. Tracked client-side keyed `dev-`; `listDevTerminals()` → `{builderId, terminalId}[]` (no port, no friendly target). + - `onDidChangeDevTerminals` fires on start/stop/swap → the re-render hook both surfaces will subscribe to. + - No Tower "dev started/stopped" SSE event; state is inferred client-side. + +3. **Technical constraint**: VSCode can't embed a native terminal inside a custom panel webview. "Scrolling PTY output in the tab" ⇒ either xterm.js in a webview re-subscribing to the Tower WS (heavy; loses native search/copy/links) OR plain log lines. Reframes design calls #2 (rendering) and #3 (replace vs coexist). + +4. Existing status-bar item: single connection-status item at `extension.ts:56,126` (Left, prio 100). New dev chip is a *second*, independent item (prio 99 per issue). + +5. Views are all TreeDataProviders; no WebviewViewProvider exists yet. WebviewPanel (editor tab) precedent: `backlog-search-panel.ts` (theme-safe CSS via `var(--vscode-*)`, CSP nonce, postMessage). Tests: vitest in `src/__tests__/` for pure helpers. + +### Forks raised to architect (awaiting direction before writing plan) +- #812 strategy: absorb minimal codevPanel into this PR vs ship chip+commands now & defer the tab vs pause. +- PTY-output rendering + replace/coexist (design calls #2/#3) given the embed constraint. + +Design calls #1/#4/#5 will adopt the issue's canonical picks (hide-when-idle / prominentBackground / always-show Switch) unless redirected. + +## Plan phase — decision + plan written + +Architect decisions (via two AskUserQuestion rounds): +- Use case confirmed: ambient awareness + fast control of the single dev server, NOT terminal fidelity. +- #812 dependency is editorial (consolidation), not technical — but architect chose: **keep #921 whole, write full plan as design-of-record, HOLD for #812.** No implementation now. +- Action: raise #812 for scheduling. + +Plan written to `codev/plans/921-vscode-codev-dev-surface-botto.md`. Key design decisions baked in: +- Tab = TreeDataProvider (status header + actions), NOT a webview/xterm. Output stays in the native terminal (coexist). +- Chip = 2nd StatusBarItem (Left, prio 99), prominentBackground, click→focus tab. +- Design calls: #1 placeholder+epitaph, #2 no in-tab output, #3 coexist, #4 prominentBackground, #5 Switch always-shown. +- Implementation gated on #812 (codevPanel container). Files-to-change is the shovel-ready spec for the implement phase post-#812. + +Next: commit plan, porch done/next → plan-approval gate pending, notify architect to schedule #812, then hold. From 4885fb2bdc8633cef0a2b547fda678e2645b6ec0 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Thu, 4 Jun 2026 09:34:35 +1000 Subject: [PATCH 03/34] chore(porch): 921 plan-approval gate-requested --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 9b1a888f0..ab4b5e5ba 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -7,6 +7,7 @@ current_plan_phase: null gates: plan-approval: status: pending + requested_at: '2026-06-03T23:34:35.312Z' dev-approval: status: pending pr: @@ -15,4 +16,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-03T21:40:33.255Z' +updated_at: '2026-06-03T23:34:35.313Z' From 688faf0355c1fbe5e07e3fd5f7f4622f9d7dafcd Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 15:02:35 +1000 Subject: [PATCH 04/34] [PIR #921] Plan revised: #812 landed, plan now active + codevPanel integration contract --- .../921-vscode-codev-dev-surface-botto.md | 322 +++++++++--------- codev/state/pir-921_thread.md | 12 + 2 files changed, 175 insertions(+), 159 deletions(-) diff --git a/codev/plans/921-vscode-codev-dev-surface-botto.md b/codev/plans/921-vscode-codev-dev-surface-botto.md index 9b77bf360..b8698fa43 100644 --- a/codev/plans/921-vscode-codev-dev-surface-botto.md +++ b/codev/plans/921-vscode-codev-dev-surface-botto.md @@ -1,224 +1,228 @@ -# PIR Plan: Codev Dev surface — bottom-panel tab + always-visible status-bar chip +# PIR Plan: Codev Dev surface (bottom-panel tab + always-visible status-bar chip) -> **Status: design-of-record, HELD pending #812.** Per architect decision at the -> `plan-approval` gate, #921 stays whole (the panel tab lives in #812's shared -> `codevPanel` container) and implementation does **not** begin until #812 -> ("introduce a Codev panel tab / bottom-area view container") ships. This plan -> is the approved design the implement phase will execute once that dependency -> clears. #812 has been raised to the architect for scheduling so this work can -> unblock rather than wait passively. +> **Status: unblocked and ready to implement.** The hard prerequisite #812 (the +> shared `codevPanel` bottom-panel viewsContainer) landed via PR #990 (merged at +> c21a112) and is on `main`. This plan is the design the implement phase executes +> once the `plan-approval` gate is approved. The earlier "held pending #812" +> framing no longer applies. ## Understanding -`afx dev` runs a **single** dev PTY (one slot across `{main + all builders}` — -they all bind main's ports, so only one can run at a time). Today that PTY is -surfaced only as a generic VSCode terminal tab named `Codev: (dev)` -(`terminal-manager.ts` → `openDevTerminal`, a `CodevPseudoterminal` over a Tower -WebSocket, placed in `TerminalLocation.Panel`). It is visually indistinguishable -from any other terminal, so a reviewer — typically someone running -`afx dev ` to exercise a builder's running worktree at PIR's -`dev-approval` gate — cannot tell at a glance: - -- **Is a dev running at all?** -- **Which target is it?** (`main` vs `pir-809` — easy to lose across worktrees.) -- **How do I stop / restart it fast** without hunting the terminal dropdown or +`afx dev` runs a **single** dev PTY (one slot across `{main + all builders}`, +because they all bind main's ports, so only one can run at a time). Today that +PTY is surfaced only as a generic VSCode terminal tab named `Codev: (dev)` +(`terminal-manager.ts` openDevTerminal: a `CodevPseudoterminal` over a Tower +WebSocket, placed in `TerminalLocation.Panel`). It looks identical to any other +terminal, so a reviewer (typically someone running `afx dev ` to +exercise a builder's running worktree at PIR's `dev-approval` gate) cannot tell +at a glance: + +- Is a dev running at all? +- Which target is it? (`main` vs `pir-809`, easy to lose track of across worktrees.) +- How do I stop or restart it fast, without hunting the terminal dropdown or opening the Workspace sidebar view (which costs sidebar real-estate each time)? -The Workspace view's Start/Stop Dev row partially addresses this, but only when -the sidebar is on that view. The need is **ambient awareness + fast control of -the single dev server** — surfaced where it's always visible and quick to act on. - -### The use case is awareness + control, NOT terminal fidelity - -This framing (confirmed with the architect) is load-bearing for the design. The -user does not need ANSI-faithful scrollback, search, or copy in a *new* surface — -the existing native terminal tab already does that well and **stays**. The new -surfaces exist to answer "is it running / which target / stop-restart" at a -glance. That rules out the heavy "re-implement a terminal in a webview (xterm.js -+ a second Tower WS subscription)" path: it adds failure surface and -re-implements terminal features for no user-need gain. - -### Why this depends on #812 (editorial, not technical) - -- The **status-bar chip** depends on nothing — it is a `createStatusBarItem`. -- The **panel tab** needs *a* bottom-panel `viewsContainer` to mount into. #812 - creates the shared `codevPanel` container (the same one #813 / #814 / #815 - migrate into). This PR *could* declare its own throwaway container instead, but - that would create exactly the interim bottom-container proliferation #812 is - meant to prevent. So the dependency is a **consolidation** choice: mount in the - shared container rather than spawn a one-off. Per architect decision, #921 - stays whole and waits for `codevPanel` rather than shipping a partial or a - throwaway container. +The Workspace view's Start/Stop Dev row partially addresses this, but only while +the sidebar is on that view. The genuine need is **ambient awareness plus fast +control of the single dev server**, surfaced where it is always visible and +quick to act on. + +### The use case is awareness and control, not terminal fidelity + +This framing (confirmed with the architect) is load-bearing. The user does not +need ANSI-faithful scrollback, search, or copy in a *new* surface. The existing +native terminal tab already does that well and **stays**. The new surfaces exist +to answer "is it running / which target / stop-restart" at a glance. That rules +out the heavy "re-implement a terminal in a webview (xterm.js plus a second Tower +WS subscription)" path: it adds failure surface and re-implements terminal +features for no use-case gain. + +### How this integrates with #812's container + +#812 shipped the `codevPanel` container (`viewsContainers.panel`, title "Codev") +plus a scaffolding placeholder view (`codev.placeholder`) gated by the +`codev.panelContainerEmpty` context key (seeded `true` in +`extension.ts`). The documented contract: when a real view registers in +`codevPanel`, it flips `codev.panelContainerEmpty` to `false`, and the +placeholder hides itself. Our `codev.devServer` view is that first real tab, so +the implementation must set that context key `false` on registration. (#813 / +#814 / #815 will do the same as they migrate in; the key is shared scaffolding, +not owned by any one tab.) ## Proposed Change Two complementary surfaces, both driven off the existing dev-terminal lifecycle (`TerminalManager.onDidChangeDevTerminals`, `listDevTerminals()`), with the -native terminal tab left intact as the output surface (COEXIST). +native terminal tab left intact as the output surface (coexist). -### Surface 1 — Codev Dev panel tab (`codev.devServer`) +### Surface 1: Codev Dev panel tab (`codev.devServer`) -A **`TreeDataProvider`** (not a webview) mounted in #812's `codevPanel` +A **`TreeDataProvider`** (not a webview) registered in #812's `codevPanel` container. TreeDataProvider is chosen deliberately: it matches every existing Codev view (`status.ts`, `team.ts`, `recently-closed.ts`), needs no webview / CSP / xterm machinery, and is the right weight for a status surface. Because the -use case is status + control (output stays in the native terminal), a tree of -status rows + title-bar actions fully covers it. +use case is status plus control (output stays in the native terminal), a tree of +status rows plus title-bar actions fully covers it. - **Tab title**: `Codev Dev`. -- **Rows (status header)** while running: - - `Target: ` (`main` or `pir-XXX`, via the dev terminal's `builderId` → - friendly name; reuse `resolveWorkspaceDevTarget` naming). - - `Running for ` (e.g. `4m 32s`) — refreshed every second by a timer - that fires `onDidChangeTreeData` while a dev is running, disposed on stop. - - `Port: ` — **best-effort**: derive from `worktree.devUrls` / `devCommand` - config if present; **omit the row entirely if undetectable** (no guessing). -- **Idle / stopped states** (design call #1 + lifecycle): - - Never-run / idle → the view contributes a single placeholder row - (`No dev running — start via afx dev or the Workspace view`). The - tab is present-but-empty rather than vanishing, so the container tab strip is - stable. (The chip remains the always-visible "is it running" signal.) - - After a dev stops → an epitaph row `Stopped — last target , ran ` +- **Rows (status header) while running**: + - `Target: ` (`main` or `pir-XXX`, from the dev terminal's `builderId` + mapped to a friendly name; reuse `resolveWorkspaceDevTarget` naming). + - `Running for ` (for example `4m 32s`), refreshed every second by a + timer that fires `onDidChangeTreeData` while a dev is running and is disposed + on stop. + - `Port: `, **best-effort**: derive from `worktree.devUrls` / `devCommand` + config when present; **omit the row entirely if undetectable** (no guessing). +- **Idle / stopped states** (design call #1 plus lifecycle): + - Never-run / idle: the view shows a single placeholder row + (`No dev running. Start via afx dev or the Workspace view`). The tab + is present-but-empty rather than vanishing, so the container tab strip stays + stable. The chip remains the always-visible "is it running" signal. + - After a dev stops: an epitaph row (`Stopped. Last target , ran `) until the user dismisses it or starts another dev. The actual log remains in the native terminal, so we do not try to preserve output here. - **Tab badge**: a small activity dot on the `Codev Dev` tab when a dev is running and the user is focused on another `codevPanel` tab (VSCode `TreeView.badge`). -- **Title-bar actions** (`view/title`, `group: navigation`, guarded by a - `codev.devServerRunning` context key where action requires a live dev): - - `Stop Dev Server` (`$(debug-stop)`) → `codev.devServer.stop`. - - `Restart Dev Server` (`$(debug-restart)`) → `codev.devServer.restart` - (stop current target, then start the same target). - - `Switch Target` (`$(arrow-swap)`) → `codev.devServer.switchTarget`: Quick - Pick of `main` + builders; reuses the single-slot swap semantics of - `startDevForTarget`. **Always shown** (design call #5 — consistent placement). - - `Reveal in Workspace View` (`$(eye)`) → `codev.devServer.revealInWorkspace`: +- **Title-bar actions** (`view/title`, `group: navigation`, gated by a + `codev.devServerRunning` context key where the action requires a live dev): + - `Stop Dev Server` (`$(debug-stop)`) calls `codev.devServer.stop`. + - `Restart Dev Server` (`$(debug-restart)`) calls `codev.devServer.restart` + (stop the current target, then start the same target). + - `Switch Target` (`$(arrow-swap)`) calls `codev.devServer.switchTarget`: a + Quick Pick of `main` plus builders, reusing the single-slot swap semantics of + `startDevForTarget`. **Always shown** (design call #5, consistent placement). + - `Reveal in Workspace View` (`$(eye)`) calls `codev.devServer.revealInWorkspace`: focus `codev.workspace` and its Dev Server row. -### Surface 2 — status-bar chip +### Surface 2: status-bar chip -- A **second, independent** `StatusBarItem` (`StatusBarAlignment.Left`, - priority **99** — left of the existing connection/builder-count item at 100). +- A **second, independent** `StatusBarItem` (`StatusBarAlignment.Left`, priority + **99**, left of the existing connection / builder-count item at 100). - **Visibility**: created when a dev starts, disposed when it stops (driven by `onDidChangeDevTerminals`). -- **Text**: `$(zap) Dev: ` (e.g. `$(zap) Dev: pir-809`). +- **Text**: `$(zap) Dev: ` (for example `$(zap) Dev: pir-809`). - **Background**: `new vscode.ThemeColor('statusBarItem.prominentBackground')` - (design call #4 — canonical; theme-safe, no hand-coded color). -- **Tooltip**: `Codev dev server running for · Click to focus Codev Dev panel`. -- **Click**: `codev.devServer.focus` — reveal the `Codev Dev` tab (open the panel - if closed, switch to the tab if on another). Thin breadcrumb; no Quick Pick - layer between chip and tab. + (design call #4, canonical; theme-safe, no hand-coded color). +- **Tooltip**: `Codev dev server running for . Click to focus Codev Dev panel`. +- **Click**: `codev.devServer.focus`, which reveals the `Codev Dev` tab (opens the + panel if closed, switches to the tab if on another). A thin breadcrumb, no Quick + Pick layer between chip and tab. ### Shared lifecycle plumbing -- A small client-side map `builderId → startedAt` populated when `openDevTerminal` - fires and cleared on `closeDevTerminal`, so uptime and the epitaph "ran Xs" have - a start time (`listDevTerminals()` currently carries only `{builderId, terminalId}`). +- A small client-side map `builderId -> startedAt`, populated when + `openDevTerminal` fires and cleared on `closeDevTerminal`, so uptime and the + epitaph "ran Xs" have a start time (`listDevTerminals()` currently carries only + `{builderId, terminalId}`). - Both surfaces subscribe to the **single** `onDidChangeDevTerminals` event and - re-derive state from `listDevTerminals()` (single source of truth). Target - swaps (stop A → start B) update both surfaces in lockstep because both reads - go through the same event + list. + re-derive state from `listDevTerminals()` (single source of truth). Target swaps + (stop A, start B) update both surfaces in lockstep because both reads go through + the same event plus list. ### Resolved design calls (from the issue) | # | Question | Decision | |---|----------|----------| -| 1 | Tab when no dev running | Present-but-placeholder (stable tab strip; chip is the always-visible signal). Post-stop shows an epitaph row. | -| 2 | PTY output rendering | **No output rendering in-tab.** Status-header tree only; output stays in the native terminal. Justified by the use case (awareness/control, not fidelity). Plain-log tail is a possible future enhancement, explicitly out of scope here. | -| 3 | Replace vs coexist with native terminal | **Coexist.** Native `Codev: (dev)` terminal stays as the output surface; safest for muscle memory and avoids re-plumbing. | +| 1 | Tab when no dev running | Present-but-placeholder (stable tab strip; the chip is the always-visible signal). Post-stop shows an epitaph row. | +| 2 | PTY output rendering | No output rendering in-tab. Status-header tree only; output stays in the native terminal. Justified by the use case (awareness / control, not fidelity). A plain-log tail is a possible future enhancement, explicitly out of scope here. | +| 3 | Replace vs coexist with native terminal | Coexist. The native `Codev: (dev)` terminal stays as the output surface; safest for muscle memory and avoids re-plumbing. | | 4 | Chip background | `prominentBackground`. | | 5 | `Switch Target` visibility | Always shown. | ## Files to Change -> All under `packages/vscode/`. Executed in the implement phase **after #812 lands** -> (so `codevPanel` exists in `package.json`'s `viewsContainers.panel`). +All under `packages/vscode/`. The branch will first merge `origin/main` to pick up +#812's `codevPanel` container before these changes apply. -- `packages/vscode/src/views/dev-server.ts` — **new.** `DevServerTreeProvider` - implementing `TreeDataProvider`: renders the status header / - placeholder / epitaph rows, owns the 1s uptime refresh timer, exposes +- `packages/vscode/src/views/dev-server.ts` (new): `DevServerTreeProvider` + implementing `TreeDataProvider`, rendering the status header / + placeholder / epitaph rows, owning the 1s uptime refresh timer, exposing `onDidChangeTreeData`. Subscribes to `terminalManager.onDidChangeDevTerminals`. -- `packages/vscode/src/views/dev-server-format.ts` — **new (pure helpers).** +- `packages/vscode/src/views/dev-server-format.ts` (new, pure helpers): `formatUptime(ms)`, target-name derivation, port-from-config extraction. Pure and unit-tested (vitest, `src/__tests__/`). -- `packages/vscode/src/commands/dev-server-actions.ts` — **new.** Thin command +- `packages/vscode/src/commands/dev-server-actions.ts` (new): thin command handlers `stop` / `restart` / `switchTarget` / `revealInWorkspace` / `focus`, delegating to existing `dev-shared.ts` (`startDevForTarget`, `stopDevForTarget`) and `terminalManager`. -- `packages/vscode/src/terminal-manager.ts` — add the `builderId → startedAt` - map (set in `openDevTerminal` ~`:220`, cleared in `closeDevTerminal` ~`:244`) - and a getter so the view/chip can read start times. No change to existing +- `packages/vscode/src/terminal-manager.ts`: add the `builderId -> startedAt` map + (set in `openDevTerminal` near :220, cleared in `closeDevTerminal` near :244) + and a getter so the view and chip can read start times. No change to existing terminal behavior. -- `packages/vscode/src/extension.ts` — create/dispose the chip `StatusBarItem` - (driven by `onDidChangeDevTerminals`), register `codev.devServer.*` commands - (`regCli` guard), register the `codev.devServer` tree view, maintain the - `codev.devServerRunning` context key. +- `packages/vscode/src/extension.ts`: create and dispose the chip `StatusBarItem` + (driven by `onDidChangeDevTerminals`); register the `codev.devServer.*` commands + (`regCli` guard); register the `codev.devServer` tree view; set + `codev.panelContainerEmpty` to `false` once `codev.devServer` is registered (so + #812's placeholder yields); maintain the `codev.devServerRunning` context key. - `packages/vscode/package.json`: - - add `codev.devServer` view inside the `codevPanel` `viewsContainer` (created - by #812) under `contributes.views`; - - add the five `codev.devServer.*` command declarations (titles + icons); + - add `codev.devServer` view inside the existing `codevPanel` viewsContainer + under `contributes.views`; + - add the five `codev.devServer.*` command declarations (titles plus icons); - add the four `view/title` menu entries (`when: view == codev.devServer`, `group: navigation`, plus `codev.devServerRunning` gating where needed). -## Risks & Alternatives Considered +## Risks and Alternatives Considered -- **Risk — #812 not yet merged (the reason for the hold).** Mounting into a - non-existent `codevPanel` container fails. Mitigation: implementation gated on - #812; #812 raised for scheduling. *No code lands until `codevPanel` exists.* -- **Risk — port is often undetectable.** `listDevTerminals()` carries no port and - there's no stdout parsing today. Mitigation: best-effort from config only; omit +- **Risk: placeholder coexistence with #812.** If our view registers but does not + flip `codev.panelContainerEmpty` false, both the placeholder signpost and the + real tab show. Mitigation: set the key false on registration (per #812's + contract above); covered by a contributes/extension test. +- **Risk: port is often undetectable.** `listDevTerminals()` carries no port and + there is no stdout parsing today. Mitigation: best-effort from config only, omit the row when unknown rather than guess. Acceptance is written as "port if known". -- **Risk — uptime needs a start timestamp not currently tracked.** Mitigation: the +- **Risk: uptime needs a start timestamp not currently tracked.** Mitigation: the small `startedAt` map in `TerminalManager`; if a dev predates extension activation (reconnect), show `Running` without a duration rather than a wrong one. -- **Risk — two surfaces drift out of sync on swaps.** Mitigation: both derive from - the single `onDidChangeDevTerminals` + `listDevTerminals()`; no independent state. -- **Risk — regressing the Workspace view's dev row.** Mitigation: this PR only - *adds* surfaces and reads the same `listDevTerminals()`; no change to - `workspace.ts`'s row logic. Covered by the test plan. -- **Alternative — xterm.js webview + replace native terminal (design call #2/#3 - heavy path).** Rejected: re-implements terminal features for no use-case gain, - adds a second WS subscription and CSP/webview surface. The native terminal - already serves output. -- **Alternative — ship chip + commands now, defer only the tab (avoid the hold).** - Technically viable (chip has no #812 dependency) and was proposed; architect - chose to keep #921 whole and hold for consolidation. Recorded here as the - considered-and-rejected option. -- **Alternative — standalone throwaway container now, re-home into `codevPanel` - later.** Rejected: creates the interim container proliferation #812 exists to - prevent. +- **Risk: the two surfaces drift out of sync on swaps.** Mitigation: both derive + from the single `onDidChangeDevTerminals` plus `listDevTerminals()`, with no + independent state. +- **Risk: regressing the Workspace view's dev row.** Mitigation: this PR only adds + surfaces and reads the same `listDevTerminals()`; no change to `workspace.ts`'s + row logic. Covered by the test plan. +- **Alternative: xterm.js webview plus replace the native terminal (design call + #2/#3 heavy path).** Rejected: re-implements terminal features for no use-case + gain, adds a second WS subscription and CSP / webview surface. The native + terminal already serves output. +- **Alternative: ship chip plus commands first, defer only the tab.** Technically + viable (the chip has no #812 dependency) and was proposed; the architect chose to + keep #921 whole. Now moot since #812 has landed. Recorded for history. ## Test Plan -> Reviewer exercises this at the `dev-approval` gate **once implementation -> proceeds** (post-#812). Until then this plan is held and no running build exists. +The reviewer exercises this at the `dev-approval` gate, running the worktree via +`afx dev ` or VSCode's Run Dev Server. - **Unit (vitest, `src/__tests__/dev-server-format.test.ts`)**: `formatUptime` - (seconds, minutes, `4m 32s`, hour rollover, 0s edge), target-name derivation - (`main` vs `pir-XXX`), port extraction (present in config → value; absent → - `null`/omit). -- **Manual — chip**: start `afx dev main` and `afx dev `; chip appears - bottom-left as `$(zap) Dev: ` with prominent tint; tooltip correct; - click focuses the Codev Dev tab (opening the panel if closed). Stop → chip + (seconds, minutes, `4m 32s`, hour rollover, 0s edge); target-name derivation + (`main` vs `pir-XXX`); port extraction (present in config yields value, absent + yields null / omit). +- **Unit (contributes)**: `codev.devServer` is declared in the `codevPanel` + container; extension registration flips `codev.panelContainerEmpty` false + (mirrors #812's existing `contributes-panel.test.ts`). +- **Manual, chip**: start `afx dev main` and `afx dev `; the chip appears + bottom-left as `$(zap) Dev: ` with the prominent tint; tooltip correct; + click focuses the Codev Dev tab (opening the panel if closed). Stop, the chip disappears. -- **Manual — tab**: status header shows correct target, live-ticking uptime, and - port when derivable (omitted otherwise); title-bar Stop / Restart / Switch - Target / Reveal-in-Workspace each behave as labeled; activity-dot badge shows - when running and focused on another `codevPanel` tab; post-stop epitaph row. -- **Manual — swap lockstep**: with `pir-809` dev running, Switch Target → `main`; - both chip and tab update to `main` together; the single-slot swap prompt fires - as today. -- **Manual — Workspace view parity**: confirm the existing Start/Stop Dev row - reflects the same state; no regression. -- **Themes**: verify chip + tab render cleanly in Dark, Light, and High-Contrast - (ThemeColor / theme CSS vars only — no hand-coded colors). - -## Dependency & Sequencing (summary) - -1. **Blocked on #812** (`codevPanel` container) — raised to architect for - scheduling. -2. This plan is approved as design-of-record and **held** at/after - `plan-approval`. The implement phase begins only once #812 has merged and - `codevPanel` exists in `packages/vscode/package.json`. -3. No sibling dependency on #813 / #814 / #815 — independent tabs in the same - container; any order after #812. +- **Manual, tab**: the status header shows the correct target, live-ticking uptime, + and port when derivable (omitted otherwise); title-bar Stop / Restart / Switch + Target / Reveal-in-Workspace each behave as labeled; the activity-dot badge shows + when running and focused on another `codevPanel` tab; the post-stop epitaph row + appears. +- **Manual, swap lockstep**: with `pir-809` dev running, Switch Target to `main`; + both the chip and the tab update to `main` together; the single-slot swap prompt + fires as today. +- **Manual, Workspace view parity**: the existing Start/Stop Dev row reflects the + same state; no regression. +- **Manual, placeholder**: with #812's placeholder present, registering our view + hides the signpost (only `Codev Dev` shows, not both). +- **Themes**: the chip and tab render cleanly in Dark, Light, and High-Contrast + (ThemeColor / theme CSS vars only, no hand-coded colors). + +## Dependency and Sequencing + +1. #812 (`codevPanel` container) has landed (PR #990, c21a112). No longer blocking. +2. Implementation merges `origin/main` first to pick up `codevPanel`, then proceeds + per Files to Change above, honoring the `codev.panelContainerEmpty` contract. +3. No sibling dependency on #813 / #814 / #815. They are independent tabs in the + same container, any order. diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md index 721681cce..04223adb8 100644 --- a/codev/state/pir-921_thread.md +++ b/codev/state/pir-921_thread.md @@ -42,3 +42,15 @@ Plan written to `codev/plans/921-vscode-codev-dev-surface-botto.md`. Key design - Implementation gated on #812 (codevPanel container). Files-to-change is the shovel-ready spec for the implement phase post-#812. Next: commit plan, porch done/next → plan-approval gate pending, notify architect to schedule #812, then hold. + +## #812 landed (2026-06-05) — plan updated to active + +Architect: hard prerequisite #812 merged (PR #990, c21a112). codevPanel container now on main. +Verified on origin/main: +- `viewsContainers.panel` has `codevPanel` (title "Codev"). +- `views.codevPanel` has scaffolding `codev.placeholder` gated by context key `codev.panelContainerEmpty` (seeded true in extension.ts:363). +- Contract (panel-placeholder.ts): a real view registering in codevPanel must flip `codev.panelContainerEmpty` false so the signpost hides. Our `codev.devServer` is the first real tab → must set that key false on register. + +Updated plan: removed the "HELD" framing → status now "unblocked, ready to implement"; folded in the panelContainerEmpty integration contract (extension.ts must flip it false; added a contributes test + manual placeholder check); dropped em dashes per new writing guidance. + +Gate state: still `plan-approval` gate_pending. I cannot self-approve (strict mode). Plan is accurate and ready; waiting on the human to approve the gate. Implementation will merge origin/main first to get codevPanel, then proceed. From 26fa5bebe27cde7520a4bca99500f8706c321865 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:09:55 +1000 Subject: [PATCH 05/34] chore(porch): 921 plan-approval gate-approved --- .../projects/921-vscode-codev-dev-surface-botto/status.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index ab4b5e5ba..826d6e024 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -6,8 +6,9 @@ plan_phases: [] current_plan_phase: null gates: plan-approval: - status: pending + status: approved requested_at: '2026-06-03T23:34:35.312Z' + approved_at: '2026-06-05T09:09:55.929Z' dev-approval: status: pending pr: @@ -16,4 +17,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-03T23:34:35.313Z' +updated_at: '2026-06-05T09:09:55.930Z' From daff167d2702e6783abe89bdd6233d694f8079bc Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:10:03 +1000 Subject: [PATCH 06/34] chore(porch): 921 implement phase-transition --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 826d6e024..99787b3ba 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -1,7 +1,7 @@ id: '921' title: vscode-codev-dev-surface-botto protocol: pir -phase: plan +phase: implement plan_phases: [] current_plan_phase: null gates: @@ -17,4 +17,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T09:09:55.930Z' +updated_at: '2026-06-05T09:10:03.176Z' From 1996981f86637f5e26c728f12107d8dda8d455a5 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 07/34] [PIR #921] Track dev-terminal start times for uptime --- packages/vscode/src/terminal-manager.ts | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/vscode/src/terminal-manager.ts b/packages/vscode/src/terminal-manager.ts index 358e1c0b7..031706b9e 100644 --- a/packages/vscode/src/terminal-manager.ts +++ b/packages/vscode/src/terminal-manager.ts @@ -27,6 +27,12 @@ export class TerminalManager { private readonly _onDidChangeDevTerminals = new vscode.EventEmitter(); /** Fires whenever the set of open dev terminals changes (start/stop/swap/cleanup). */ readonly onDidChangeDevTerminals = this._onDidChangeDevTerminals.event; + /** + * When each dev terminal began running, keyed by builderId. `listDevTerminals` + * carries only ids, so the Codev Dev surface (#921) reads start times here to + * render uptime. Set on a fresh open (not a refocus), cleared on close. + */ + private readonly devStartedAt = new Map(); private readonly overviewCache: OverviewCache; constructor( @@ -233,9 +239,22 @@ export class TerminalManager { // Tab title matches the builder-tab format (`Codev: `) with a // `(dev)` suffix so the pairing is obvious in the tab strip. await this.openTerminal(terminalId, 'dev', `Codev: ${builderName} (dev)`, key, focus); + // Fresh open (the refocus path returned above), so stamp the start time for + // uptime. A re-spawn that replaced a stale terminal lands here too and + // correctly resets the clock. + this.devStartedAt.set(builderId, Date.now()); this._onDidChangeDevTerminals.fire(); } + /** + * Wall-clock ms (epoch) when the dev terminal for `builderId` started, or + * undefined if no dev is tracked for it (e.g. a dev that predates this + * extension activation). Read by the Codev Dev surface (#921) for uptime. + */ + getDevStartedAt(builderId: string): number | undefined { + return this.devStartedAt.get(builderId); + } + /** * Dispose the VSCode terminal tab for a builder's dev server, if any. * Used by `codev.stopWorktreeDev` after killing the Tower-side PTY so the @@ -248,6 +267,7 @@ export class TerminalManager { existing.pty.close(); existing.terminal.dispose(); this.terminals.delete(key); + this.devStartedAt.delete(builderId); this._onDidChangeDevTerminals.fire(); } @@ -269,7 +289,10 @@ export class TerminalManager { this.terminals.delete(key); if (key.startsWith('dev-')) { devClosed = true; } } - if (devClosed) { this._onDidChangeDevTerminals.fire(); } + if (devClosed) { + this.devStartedAt.delete(builderId); + this._onDidChangeDevTerminals.fire(); + } } /** From c80ceb49de4c2f78daade58dfe542433846e3fde Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 08/34] [PIR #921] Add pure dev-status formatters (uptime, port) + tests --- .../src/__tests__/dev-server-format.test.ts | 63 ++++++++++++++++ .../vscode/src/views/dev-server-format.ts | 72 +++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 packages/vscode/src/__tests__/dev-server-format.test.ts create mode 100644 packages/vscode/src/views/dev-server-format.ts diff --git a/packages/vscode/src/__tests__/dev-server-format.test.ts b/packages/vscode/src/__tests__/dev-server-format.test.ts new file mode 100644 index 000000000..c5b83eafc --- /dev/null +++ b/packages/vscode/src/__tests__/dev-server-format.test.ts @@ -0,0 +1,63 @@ +import { describe, it, expect } from 'vitest'; +import { formatUptime, extractDevPort } from '../views/dev-server-format.js'; + +describe('formatUptime', () => { + it('renders sub-minute durations as seconds', () => { + expect(formatUptime(0)).toBe('0s'); + expect(formatUptime(5_000)).toBe('5s'); + expect(formatUptime(59_000)).toBe('59s'); + }); + + it('renders minutes with zero-padded seconds', () => { + expect(formatUptime(60_000)).toBe('1m 00s'); + expect(formatUptime(272_000)).toBe('4m 32s'); + expect(formatUptime(3_599_000)).toBe('59m 59s'); + }); + + it('rolls over into hours with zero-padded minutes', () => { + expect(formatUptime(3_600_000)).toBe('1h 00m'); + expect(formatUptime(3_900_000)).toBe('1h 05m'); + expect(formatUptime(90_000_000)).toBe('25h 00m'); + }); + + it('clamps negative durations to 0s', () => { + expect(formatUptime(-1_000)).toBe('0s'); + }); +}); + +describe('extractDevPort', () => { + it('returns null when no config', () => { + expect(extractDevPort(null)).toBeNull(); + expect(extractDevPort(undefined)).toBeNull(); + expect(extractDevPort({})).toBeNull(); + }); + + it('reads the port from the first devUrls entry', () => { + expect(extractDevPort({ devUrls: [{ url: 'http://localhost:3000/' }] })).toBe(3000); + expect(extractDevPort({ + devUrls: [{ url: 'http://localhost/' }, { url: 'http://localhost:5173' }], + })).toBe(5173); + }); + + it('falls back to a port mentioned in devCommand', () => { + expect(extractDevPort({ devCommand: 'next dev --port 3001' })).toBe(3001); + expect(extractDevPort({ devCommand: 'vite -p 5174' })).toBe(5174); + expect(extractDevPort({ devCommand: 'PORT=4000 npm run dev' })).toBe(4000); + expect(extractDevPort({ devCommand: 'serve :8080' })).toBe(8080); + }); + + it('prefers devUrls over devCommand', () => { + expect(extractDevPort({ + devUrls: [{ url: 'http://localhost:3000' }], + devCommand: 'next dev --port 9999', + })).toBe(3000); + }); + + it('returns null for an unparseable url and a portless command', () => { + expect(extractDevPort({ devUrls: [{ url: 'not a url' }], devCommand: 'pnpm dev' })).toBeNull(); + }); + + it('rejects out-of-range ports', () => { + expect(extractDevPort({ devCommand: 'run --port 99999' })).toBeNull(); + }); +}); diff --git a/packages/vscode/src/views/dev-server-format.ts b/packages/vscode/src/views/dev-server-format.ts new file mode 100644 index 000000000..cd4b42159 --- /dev/null +++ b/packages/vscode/src/views/dev-server-format.ts @@ -0,0 +1,72 @@ +/** + * Pure formatting helpers for the Codev Dev surface (#921): the panel tab's + * status header and the status-bar chip. Kept free of `vscode` imports so they + * unit-test in plain node (vitest). + */ + +/** + * Human-readable elapsed time for the "Running for ..." row, from a millisecond + * duration. `4m 32s`, `1h 05m`, `0s`. Negative durations (clock skew, a + * start-time read after the stop) clamp to `0s` rather than render nonsense. + */ +export function formatUptime(ms: number): string { + const totalSeconds = Math.max(0, Math.floor(ms / 1000)); + const seconds = totalSeconds % 60; + const minutes = Math.floor(totalSeconds / 60) % 60; + const hours = Math.floor(totalSeconds / 3600); + if (hours > 0) { + return `${hours}h ${String(minutes).padStart(2, '0')}m`; + } + if (minutes > 0) { + return `${minutes}m ${String(seconds).padStart(2, '0')}s`; + } + return `${seconds}s`; +} + +/** Minimal view of the worktree config this module reads — see ResolvedWorktreeConfig. */ +export interface DevPortSource { + devCommand?: string | null; + devUrls?: Array<{ url: string }>; +} + +/** + * Best-effort port for the status header. The dev PTY does not report its port, + * so we infer it from config only: first a `devUrls` entry's URL, then a port + * mentioned in the `devCommand` (`--port 3000`, `-p 3000`, `PORT=3000`, or a + * bare `:3000`). Returns null when nothing is derivable — the caller omits the + * row rather than guess. + */ +export function extractDevPort(config: DevPortSource | null | undefined): number | null { + if (!config) { return null; } + + for (const { url } of config.devUrls ?? []) { + const port = portFromUrl(url); + if (port !== null) { return port; } + } + + const cmd = config.devCommand ?? ''; + const match = cmd.match(/(?:--port[ =]|-p\s+|PORT=|:)(\d{2,5})\b/); + if (match) { + const port = Number(match[1]); + if (isValidPort(port)) { return port; } + } + + return null; +} + +function portFromUrl(url: string): number | null { + try { + const parsed = new URL(url); + if (parsed.port) { + const port = Number(parsed.port); + return isValidPort(port) ? port : null; + } + } catch { + // not a parseable URL — ignore + } + return null; +} + +function isValidPort(port: number): boolean { + return Number.isInteger(port) && port >= 1 && port <= 65535; +} From c492354cc3f6eb44b0d1d5d54127e02861c1ad5b Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 09/34] [PIR #921] Add DevServerTreeProvider for the Codev Dev panel tab --- packages/vscode/src/views/dev-server.ts | 125 ++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 packages/vscode/src/views/dev-server.ts diff --git a/packages/vscode/src/views/dev-server.ts b/packages/vscode/src/views/dev-server.ts new file mode 100644 index 000000000..33c85d3f4 --- /dev/null +++ b/packages/vscode/src/views/dev-server.ts @@ -0,0 +1,125 @@ +import * as vscode from 'vscode'; +import type { ConnectionManager } from '../connection-manager.js'; +import type { TerminalManager } from '../terminal-manager.js'; +import { loadWorktreeConfig } from '../load-worktree-config.js'; +import { formatUptime, extractDevPort } from './dev-server-format.js'; + +/** + * The "Codev Dev" panel tab (#921). A status surface for the single `afx dev` + * PTY: which target is running, for how long, and on what port (best-effort). + * Deliberately *not* an output mirror — the native `Codev: (dev)` + * terminal stays as the place to read stdout. The actionable controls + * (Stop / Restart / Switch Target / Reveal) live in the view's title bar + * (see package.json `view/title`), not as rows here. + * + * Single source of truth is `TerminalManager.listDevTerminals()` (single-slot: + * at most one dev). We re-derive on every `onDidChangeDevTerminals` and keep a + * 1s ticker running while a dev is up so the uptime row stays live. + */ +export class DevServerTreeProvider implements vscode.TreeDataProvider { + private readonly _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + /** The running dev, or null. `startedAt` is null when its start time is unknown + * (a dev that predates activation) so we render "Running" without a fake clock. */ + private running: { builderId: string; startedAt: number | null } | null = null; + /** Last-stopped summary, shown until another dev starts or the user dismisses the tab. */ + private epitaph: { target: string; ranMs: number | null } | null = null; + private port: number | null = null; + private ticker: ReturnType | null = null; + private readonly subscription: vscode.Disposable; + + constructor( + private readonly connectionManager: ConnectionManager, + private readonly terminalManager: TerminalManager, + ) { + this.subscription = terminalManager.onDidChangeDevTerminals(() => this.onDevTerminalsChanged()); + this.onDevTerminalsChanged(); // seed from any dev already running at activation + } + + /** True iff a dev is currently running — drives the `codev.devServerRunning` context key. */ + isRunning(): boolean { + return this.running !== null; + } + + getTreeItem(element: vscode.TreeItem): vscode.TreeItem { + return element; + } + + getChildren(): vscode.TreeItem[] { + if (this.running) { + const rows = [ + this.row(`Target: ${this.running.builderId}`, 'server'), + this.running.startedAt === null + ? this.row('Running', 'watch') + : this.row(`Running for ${formatUptime(Date.now() - this.running.startedAt)}`, 'watch'), + ]; + if (this.port !== null) { + rows.push(this.row(`Port: ${this.port}`, 'plug')); + } + return rows; + } + if (this.epitaph) { + const ran = this.epitaph.ranMs === null ? '' : `, ran ${formatUptime(this.epitaph.ranMs)}`; + return [this.row(`Stopped. Last target ${this.epitaph.target}${ran}`, 'circle-slash')]; + } + return [this.row('No dev running. Start via `afx dev ` or the Workspace view.', 'info')]; + } + + private row(label: string, icon: string): vscode.TreeItem { + const item = new vscode.TreeItem(label); + item.iconPath = new vscode.ThemeIcon(icon); + return item; + } + + private onDevTerminalsChanged(): void { + const current = this.terminalManager.listDevTerminals()[0]; + if (current) { + if (!this.running || this.running.builderId !== current.builderId) { + const startedAt = this.terminalManager.getDevStartedAt(current.builderId) ?? null; + this.running = { builderId: current.builderId, startedAt }; + this.epitaph = null; + this.port = null; + this.refreshPort(current.builderId); + this.startTicker(); + } + } else { + if (this.running) { + this.epitaph = { + target: this.running.builderId, + ranMs: this.running.startedAt === null ? null : Date.now() - this.running.startedAt, + }; + } + this.running = null; + this.port = null; + this.stopTicker(); + } + this._onDidChangeTreeData.fire(); + } + + /** Best-effort port from the Tower-merged worktree config. Async; guarded so a + * late resolve for a since-swapped target is ignored. */ + private async refreshPort(builderId: string): Promise { + const config = await loadWorktreeConfig(this.connectionManager); + if (this.running?.builderId !== builderId) { return; } + this.port = extractDevPort(config); + this._onDidChangeTreeData.fire(); + } + + private startTicker(): void { + if (this.ticker) { return; } + this.ticker = setInterval(() => this._onDidChangeTreeData.fire(), 1000); + } + + private stopTicker(): void { + if (!this.ticker) { return; } + clearInterval(this.ticker); + this.ticker = null; + } + + dispose(): void { + this.stopTicker(); + this.subscription.dispose(); + this._onDidChangeTreeData.dispose(); + } +} From 500f899b318d412c1855fac2bde84e7e3bb1ef3d Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 10/34] [PIR #921] Add dev-server title-bar actions (stop/restart/switch/reveal) --- .../vscode/src/commands/dev-server-actions.ts | 72 ++++++++++++++++ packages/vscode/src/commands/dev-shared.ts | 86 +++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 packages/vscode/src/commands/dev-server-actions.ts diff --git a/packages/vscode/src/commands/dev-server-actions.ts b/packages/vscode/src/commands/dev-server-actions.ts new file mode 100644 index 000000000..cfbc0f3f7 --- /dev/null +++ b/packages/vscode/src/commands/dev-server-actions.ts @@ -0,0 +1,72 @@ +/** + * Title-bar actions for the Codev Dev panel tab (#921). Thin handlers that + * delegate to the shared dev core (dev-shared.ts) and the existing stop + * command — the tab surfaces controls; the orchestration lives where every + * other dev front-end's does. + */ + +import * as vscode from 'vscode'; +import type { ConnectionManager } from '../connection-manager.js'; +import type { TerminalManager } from '../terminal-manager.js'; +import { stopWorktreeDev } from './stop-worktree-dev.js'; +import { + startDevForTarget, + restartDevForTarget, + resolveDevTargetById, + listSwitchTargets, +} from './dev-shared.js'; + +/** Stop the running dev. Single-slot, so this is the one dev in the registry. */ +export async function stopDevServer( + connectionManager: ConnectionManager, + terminalManager: TerminalManager, +): Promise { + await stopWorktreeDev(connectionManager, terminalManager); +} + +/** Stop and respawn the dev for whatever target is currently running. */ +export async function restartDevServer( + connectionManager: ConnectionManager, + terminalManager: TerminalManager, +): Promise { + const running = terminalManager.listDevTerminals()[0]; + if (!running) { + vscode.window.showInformationMessage('Codev: No dev server is running'); + return; + } + const target = await resolveDevTargetById(connectionManager, running.builderId); + if (!target) { + vscode.window.showErrorMessage(`Codev: Could not resolve a target to restart for ${running.builderId}`); + return; + } + await restartDevForTarget(connectionManager, terminalManager, target); +} + +/** Quick Pick a dev target (main or a builder) and start it — startDevForTarget + * surfaces the single-slot swap prompt when another target is already running. */ +export async function switchDevTarget( + connectionManager: ConnectionManager, + terminalManager: TerminalManager, +): Promise { + const targets = await listSwitchTargets(connectionManager); + if (targets.length === 0) { + vscode.window.showInformationMessage('Codev: No dev targets available'); + return; + } + const running = terminalManager.listDevTerminals()[0]?.builderId; + const picked = await vscode.window.showQuickPick( + targets.map(t => ({ + label: t.id === running ? `$(check) ${t.name}` : t.name, + description: t.id === running ? 'running' : undefined, + target: t, + })), + { placeHolder: 'Select dev target' }, + ); + if (!picked) { return; } + await startDevForTarget(connectionManager, terminalManager, picked.target); +} + +/** Focus the Workspace sidebar view, where the Dev Server row lives. */ +export async function revealDevInWorkspace(): Promise { + await vscode.commands.executeCommand('codev.workspace.focus'); +} diff --git a/packages/vscode/src/commands/dev-shared.ts b/packages/vscode/src/commands/dev-shared.ts index 6c2cbb0c6..68ed95c77 100644 --- a/packages/vscode/src/commands/dev-shared.ts +++ b/packages/vscode/src/commands/dev-shared.ts @@ -143,6 +143,92 @@ export async function startDevForTarget( vscode.window.showInformationMessage(`Codev: Dev server started for ${target.name}`); } +/** + * Restart the dev for `target`: stop it (if running) and start it again, with + * the same kill→wait→grace sequencing a swap uses so the OS has released the + * port before the respawn binds it. Reuses startDevForTarget for the spawn. + */ +export async function restartDevForTarget( + connectionManager: ConnectionManager, + terminalManager: TerminalManager, + target: DevTarget, +): Promise { + const client = connectionManager.getClient(); + if (!client || connectionManager.getState() !== 'connected') { + vscode.window.showErrorMessage('Codev: Not connected to Tower'); + return; + } + const found = terminalManager.listDevTerminals().find(d => d.builderId === target.id); + if (found) { + await client.killTerminal(found.terminalId); + terminalManager.closeDevTerminal(target.id); + try { + await waitForTerminalGone(client, found.terminalId); + } catch (err) { + vscode.window.showErrorMessage(`Codev: ${(err as Error).message}`); + return; + } + await new Promise((r) => setTimeout(r, SWAP_GRACE_MS)); + } + await startDevForTarget(connectionManager, terminalManager, target); +} + +/** The main checkout root for the active window. In a builder worktree + * (`/.builders/`) that's two levels up; otherwise the window root. */ +function mainRootOf(workspacePath: string): string { + if (path.basename(path.dirname(workspacePath)) === '.builders') { + return path.dirname(path.dirname(workspacePath)); + } + return workspacePath; +} + +/** + * Every target a dev can run for: `main` plus each builder that has a worktree. + * Builder ids/names use the worktree basename (e.g. `pir-809`), matching the + * `afx dev` / Workspace-view convention and the chip's display. + */ +export async function listSwitchTargets(connectionManager: ConnectionManager): Promise { + const client = connectionManager.getClient(); + const workspacePath = connectionManager.getWorkspacePath(); + if (!client || !workspacePath) { return []; } + const targets: DevTarget[] = [{ id: 'main', cwd: mainRootOf(workspacePath), name: 'main' }]; + const overview = await client.getOverview(workspacePath); + for (const b of overview?.builders ?? []) { + if (!b.worktreePath) { continue; } + const name = path.basename(b.worktreePath); + targets.push({ id: name, cwd: b.worktreePath, name }); + } + return targets; +} + +/** + * Resolve a full DevTarget from a running dev's builderId (which carries no + * cwd), so Restart can respawn it. Checks `main`, then this window's own target, + * then the builder overview (matched by worktree basename or overview id). + */ +export async function resolveDevTargetById( + connectionManager: ConnectionManager, + builderId: string, +): Promise { + const workspacePath = connectionManager.getWorkspacePath(); + if (!workspacePath) { return null; } + if (builderId === 'main') { + return { id: 'main', cwd: mainRootOf(workspacePath), name: 'main' }; + } + const local = resolveWorkspaceDevTarget(workspacePath); + if (local.id === builderId) { return local; } + const client = connectionManager.getClient(); + const overview = client ? await client.getOverview(workspacePath) : null; + for (const b of overview?.builders ?? []) { + if (!b.worktreePath) { continue; } + const name = path.basename(b.worktreePath); + if (name === builderId || b.id === builderId) { + return { id: name, cwd: b.worktreePath, name }; + } + } + return null; +} + /** Stop the dev PTY for a single target id (scoped — does not touch others). */ export async function stopDevForTarget( connectionManager: ConnectionManager, From 9e2520fdf090cfdf4044b6e4a2efbed3928ea8a9 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 11/34] [PIR #921] Wire Codev Dev view, status-bar chip, and context keys --- packages/vscode/src/extension.ts | 60 +++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index edacbd143..37b3a2c9a 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -11,6 +11,7 @@ import { viewDiff, activateDiffView, diffUrisForChange } from './commands/view-d import { runWorktreeDev } from './commands/run-worktree-dev.js'; import { stopWorktreeDev } from './commands/stop-worktree-dev.js'; import { runWorkspaceDev, stopWorkspaceDev } from './commands/run-workspace-dev.js'; +import { stopDevServer, restartDevServer, switchDevTarget, revealDevInWorkspace } from './commands/dev-server-actions.js'; import { openDevUrl } from './commands/open-dev-url.js'; import { pasteImage } from './commands/paste-image.js'; import { openWorktreeFolder } from './commands/open-worktree-folder.js'; @@ -39,6 +40,7 @@ import { RecentlyClosedProvider } from './views/recently-closed.js'; import { TeamProvider } from './views/team.js'; import { StatusProvider } from './views/status.js'; import { PanelPlaceholderProvider } from './views/panel-placeholder.js'; +import { DevServerTreeProvider } from './views/dev-server.js'; import { WorkspaceProvider } from './views/workspace.js'; import { BuilderTreeItem } from './views/builder-tree-item.js'; import { BuilderFileTreeItem } from './views/builder-file-tree-item.js'; @@ -55,6 +57,10 @@ let connectionManager: ConnectionManager | null = null; let terminalManager: TerminalManager | null = null; let outputChannel: vscode.OutputChannel | null = null; let statusBarItem: vscode.StatusBarItem | null = null; +// Always-visible chip shown only while an `afx dev` PTY is running (#921). +// Created lazily on dev start, disposed on stop — distinct from the connection +// status item above. +let devChipItem: vscode.StatusBarItem | null = null; /** * Resolve a builder id from a command argument. @@ -346,6 +352,8 @@ export async function activate(context: vscode.ExtensionContext) { const workspaceProvider = new WorkspaceProvider(connectionManager, terminalManager!); // Holds the CLI preflight row (#791); it self-refreshes on `onPreflightChange`. const statusProvider = new StatusProvider(connectionManager); + // Codev Dev panel tab (#921) — the first real view in #812's codevPanel. + const devServerProvider = new DevServerTreeProvider(connectionManager, terminalManager!); context.subscriptions.push( buildersView, pullRequestsView, @@ -355,12 +363,48 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.registerTreeDataProvider('codev.team', teamProvider), vscode.window.registerTreeDataProvider('codev.status', statusProvider), vscode.window.registerTreeDataProvider('codev.placeholder', new PanelPlaceholderProvider()), + vscode.window.registerTreeDataProvider('codev.devServer', devServerProvider), + { dispose: () => devServerProvider.dispose() }, ); - // Panel container (#812) is scaffolding: the placeholder view shows only - // while no real panel-side view has registered. Follow-up PRs (#813/#814/ - // #815) flip this key false as they migrate views in, hiding the signpost. - vscode.commands.executeCommand('setContext', 'codev.panelContainerEmpty', true); + // Panel container (#812) ships a placeholder signpost gated by + // `codev.panelContainerEmpty`. codev.devServer (#921) is a real, always-present + // panel view, so the container is never empty — flip the key false to hide the + // signpost. (Sibling tabs #813/#814/#815 set the same key; idempotent.) + vscode.commands.executeCommand('setContext', 'codev.panelContainerEmpty', false); + + // Status-bar chip + title-bar gating for the dev surface (#921). Both derive + // from the single dev-terminal source of truth, so the chip, the Codev Dev + // tab, and the title-bar Stop/Restart actions stay in lockstep on every + // start/stop/swap. One subscription, named handler (no duplicate listeners). + const updateDevChip = (target: string | null): void => { + if (target) { + if (!devChipItem) { + devChipItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 99); + devChipItem.command = 'codev.devServer.focus'; // VSCode's auto view-focus command + } + devChipItem.text = `$(zap) Dev: ${target}`; + // StatusBarItem.backgroundColor only honors error/warning backgrounds + // (VSCode API constraint), so the "prominent, not alarming" look + // (#921 design call #4) is applied via the foreground instead. + devChipItem.color = new vscode.ThemeColor('statusBarItem.prominentForeground'); + devChipItem.tooltip = `Codev dev server running for ${target}. Click to focus Codev Dev panel`; + devChipItem.show(); + } else if (devChipItem) { + devChipItem.dispose(); + devChipItem = null; + } + }; + const refreshDevSurface = (): void => { + const target = terminalManager?.listDevTerminals()[0]?.builderId ?? null; + updateDevChip(target); + vscode.commands.executeCommand('setContext', 'codev.devServerRunning', target !== null); + }; + context.subscriptions.push( + terminalManager.onDidChangeDevTerminals(refreshDevSurface), + { dispose: () => { devChipItem?.dispose(); devChipItem = null; } }, + ); + refreshDevSurface(); // seed from any dev already running at activation // VSCode gives no control over a panel tab's position, so a freshly // contributed container lands last and spills into the `...` overflow. @@ -754,6 +798,14 @@ export async function activate(context: vscode.ExtensionContext) { runWorkspaceDev(connectionManager!, terminalManager!)), regCli('codev.stopWorkspaceDev', () => stopWorkspaceDev(connectionManager!, terminalManager!)), + regCli('codev.devServer.stop', () => + stopDevServer(connectionManager!, terminalManager!)), + regCli('codev.devServer.restart', () => + restartDevServer(connectionManager!, terminalManager!)), + regCli('codev.devServer.switchTarget', () => + switchDevTarget(connectionManager!, terminalManager!)), + reg('codev.devServer.revealInWorkspace', () => + revealDevInWorkspace()), reg('codev.openDevUrl', (urlArg?: unknown) => openDevUrl(connectionManager!, typeof urlArg === 'string' ? urlArg : undefined)), reg('codev.pasteImage', () => From a92363f33a37ac16c2f158e18936ed34d1e1b9cc Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:25:56 +1000 Subject: [PATCH 12/34] [PIR #921] Contribute codev.devServer view, commands, title-bar menus --- packages/vscode/package.json | 63 +++++++++++++++- .../__tests__/contributes-dev-server.test.ts | 71 +++++++++++++++++++ .../src/__tests__/contributes-panel.test.ts | 17 +++-- 3 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 packages/vscode/src/__tests__/contributes-dev-server.test.ts diff --git a/packages/vscode/package.json b/packages/vscode/package.json index a80fac65e..ca1031e4d 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -198,6 +198,30 @@ "command": "codev.stopWorkspaceDev", "title": "Codev: Stop Dev Server (this workspace)" }, + { + "command": "codev.devServer.stop", + "title": "Stop Dev Server", + "category": "Codev", + "icon": "$(debug-stop)" + }, + { + "command": "codev.devServer.restart", + "title": "Restart Dev Server", + "category": "Codev", + "icon": "$(debug-restart)" + }, + { + "command": "codev.devServer.switchTarget", + "title": "Switch Target", + "category": "Codev", + "icon": "$(arrow-swap)" + }, + { + "command": "codev.devServer.revealInWorkspace", + "title": "Reveal in Workspace View", + "category": "Codev", + "icon": "$(eye)" + }, { "command": "codev.openDevUrl", "title": "Codev: Open Dev URL" @@ -336,6 +360,22 @@ { "command": "codev.stopWorktreeDev", "when": "false" + }, + { + "command": "codev.devServer.stop", + "when": "codev.devServerRunning" + }, + { + "command": "codev.devServer.restart", + "when": "codev.devServerRunning" + }, + { + "command": "codev.devServer.switchTarget", + "when": "codev.hasDevCommand" + }, + { + "command": "codev.devServer.revealInWorkspace", + "when": "false" } ], "view/item/context": [ @@ -520,6 +560,26 @@ "command": "codev.reconnect", "when": "view == codev.status", "group": "navigation" + }, + { + "command": "codev.devServer.stop", + "when": "view == codev.devServer && codev.devServerRunning", + "group": "navigation@1" + }, + { + "command": "codev.devServer.restart", + "when": "view == codev.devServer && codev.devServerRunning", + "group": "navigation@2" + }, + { + "command": "codev.devServer.switchTarget", + "when": "view == codev.devServer", + "group": "navigation@3" + }, + { + "command": "codev.devServer.revealInWorkspace", + "when": "view == codev.devServer", + "group": "navigation@4" } ], "comments/commentThread/context": [ @@ -617,7 +677,8 @@ { "id": "codev.status", "name": "Status" } ], "codevPanel": [ - { "id": "codev.placeholder", "name": "Codev", "when": "codev.panelContainerEmpty", "visibility": "collapsed" } + { "id": "codev.placeholder", "name": "Codev", "when": "codev.panelContainerEmpty", "visibility": "collapsed" }, + { "id": "codev.devServer", "name": "Codev Dev" } ] }, "configuration": { diff --git a/packages/vscode/src/__tests__/contributes-dev-server.test.ts b/packages/vscode/src/__tests__/contributes-dev-server.test.ts new file mode 100644 index 000000000..cce2aa1af --- /dev/null +++ b/packages/vscode/src/__tests__/contributes-dev-server.test.ts @@ -0,0 +1,71 @@ +/** + * Contributes invariants for the Codev Dev surface (#921): + * - the `codev.devServer` view is the real tab in #812's `codevPanel` container; + * - extension.ts flips `codev.panelContainerEmpty` false (so #812's placeholder + * yields) and registers the view + the chip refresh; + * - the four title-bar actions are declared with icons and the right `when` + * gating (Stop/Restart only while running; Switch/Reveal always). + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +const ROOT = resolve(__dirname, '../..'); +const PKG = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf8')); +const EXT_SRC = readFileSync(resolve(ROOT, 'src/extension.ts'), 'utf8'); + +interface View { id: string; name: string; when?: string } +interface Menu { command: string; when?: string; group?: string } +interface Command { command: string; title: string; icon?: string } + +const views = PKG.contributes.views as Record; +const titleMenus = (PKG.contributes.menus['view/title'] ?? []) as Menu[]; +const commands = PKG.contributes.commands as Command[]; + +describe('codev.devServer view (#921)', () => { + it('lives in the codevPanel container with the title "Codev Dev"', () => { + const dev = (views.codevPanel ?? []).find((v) => v.id === 'codev.devServer'); + expect(dev).toBeDefined(); + expect(dev!.name).toBe('Codev Dev'); + // Always present (no `when`), so the container is never empty. + expect(dev!.when).toBeUndefined(); + }); +}); + +describe('codev.devServer title-bar actions (#921)', () => { + const action = (command: string) => titleMenus.find((m) => m.command === command); + + it('declares Stop/Restart gated on a running dev', () => { + for (const cmd of ['codev.devServer.stop', 'codev.devServer.restart']) { + expect(action(cmd)?.when).toBe('view == codev.devServer && codev.devServerRunning'); + } + }); + + it('declares Switch Target and Reveal shown whenever the view is active', () => { + for (const cmd of ['codev.devServer.switchTarget', 'codev.devServer.revealInWorkspace']) { + expect(action(cmd)?.when).toBe('view == codev.devServer'); + } + }); + + it('gives each action an icon', () => { + const byId = Object.fromEntries(commands.map((c) => [c.command, c])); + expect(byId['codev.devServer.stop']?.icon).toBe('$(debug-stop)'); + expect(byId['codev.devServer.restart']?.icon).toBe('$(debug-restart)'); + expect(byId['codev.devServer.switchTarget']?.icon).toBe('$(arrow-swap)'); + expect(byId['codev.devServer.revealInWorkspace']?.icon).toBe('$(eye)'); + }); +}); + +describe('extension.ts wiring (#921)', () => { + it('registers the dev-server tree view', () => { + expect(EXT_SRC).toMatch( + /registerTreeDataProvider\(['"]codev\.devServer['"], devServerProvider\)/, + ); + }); + + it('drives the chip + devServerRunning context key off the dev-terminal event', () => { + expect(EXT_SRC).toMatch(/onDidChangeDevTerminals\(refreshDevSurface\)/); + expect(EXT_SRC).toMatch(/setContext['"],\s*['"]codev\.devServerRunning['"]/); + }); +}); diff --git a/packages/vscode/src/__tests__/contributes-panel.test.ts b/packages/vscode/src/__tests__/contributes-panel.test.ts index 38106f249..7cf9eebe3 100644 --- a/packages/vscode/src/__tests__/contributes-panel.test.ts +++ b/packages/vscode/src/__tests__/contributes-panel.test.ts @@ -2,10 +2,15 @@ * Invariants for the Codev panel container scaffolding (#812): * - a `panel` viewsContainer `codevPanel` is declared with the Codev icon; * - the activitybar container is untouched; - * - the panel hosts exactly one placeholder view, gated by the + * - the panel hosts the placeholder view, gated by the * `codev.panelContainerEmpty` context key and collapsed by default; * - the existing sidebar views are unchanged (regression guard); * - extension.ts wires the placeholder provider and the context key. + * + * Note: once #921 added the real `codev.devServer` view, the panel is no longer + * empty — the context key is seeded `false` and a second view is present. Those + * invariants are covered in contributes-dev-server.test.ts; this file keeps the + * #812 scaffolding guards (placeholder shape, sidebar regression). */ import { describe, it, expect } from 'vitest'; @@ -39,10 +44,10 @@ describe('codevPanel viewsContainer (#812)', () => { }); describe('codevPanel placeholder view (#812)', () => { - it('registers exactly one placeholder view, gated and collapsed', () => { + it('registers the placeholder view, gated and collapsed', () => { const panelViews = views.codevPanel ?? []; - expect(panelViews).toHaveLength(1); - expect(panelViews[0]).toMatchObject({ + const placeholder = panelViews.find((v) => v.id === 'codev.placeholder'); + expect(placeholder).toMatchObject({ id: 'codev.placeholder', name: 'Codev', when: 'codev.panelContainerEmpty', @@ -71,9 +76,9 @@ describe('extension.ts wiring (#812)', () => { ); }); - it('seeds the panelContainerEmpty context key true', () => { + it('seeds the panelContainerEmpty context key false (a real panel view is registered, #921)', () => { expect(EXT_SRC).toMatch( - /setContext['"],\s*['"]codev\.panelContainerEmpty['"],\s*true/, + /setContext['"],\s*['"]codev\.panelContainerEmpty['"],\s*false/, ); }); From f6a4263ecd4bafdaf1eb6eb1deb410f97f2be1a3 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:26:13 +1000 Subject: [PATCH 13/34] [PIR #921] Update builder thread (implement) --- codev/state/pir-921_thread.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md index 04223adb8..c70a91c4d 100644 --- a/codev/state/pir-921_thread.md +++ b/codev/state/pir-921_thread.md @@ -54,3 +54,19 @@ Verified on origin/main: Updated plan: removed the "HELD" framing → status now "unblocked, ready to implement"; folded in the panelContainerEmpty integration contract (extension.ts must flip it false; added a contributes test + manual placeholder check); dropped em dashes per new writing guidance. Gate state: still `plan-approval` gate_pending. I cannot self-approve (strict mode). Plan is accurate and ready; waiting on the human to approve the gate. Implementation will merge origin/main first to get codevPanel, then proceed. + +## Implement phase complete (plan-approval approved, #812 merged) + +Implemented both surfaces on top of #812's codevPanel: +- `views/dev-server.ts` — DevServerTreeProvider (status header: Target / Running for / Port; placeholder + epitaph states; 1s uptime ticker; best-effort port via Tower worktree config). +- `views/dev-server-format.ts` + tests — pure formatUptime + extractDevPort. +- `commands/dev-server-actions.ts` + dev-shared helpers (restartDevForTarget, listSwitchTargets, resolveDevTargetById) — Stop/Restart/Switch Target/Reveal. +- `terminal-manager.ts` — builderId→startedAt map + getDevStartedAt for uptime. +- `extension.ts` — register codev.devServer view; status-bar chip (Left, prio 99) lifecycle on onDidChangeDevTerminals; codev.devServerRunning context key; flip codev.panelContainerEmpty=false. +- `package.json` — codev.devServer view in codevPanel; 4 command decls; 4 view/title menus; commandPalette gating. +- Tests: new contributes-dev-server.test.ts; updated #812's contributes-panel.test.ts for the now-false key + 2nd view. + +### Deviation worth flagging at dev-approval +- Chip "prominent" tint: VSCode `StatusBarItem.backgroundColor` ONLY honors error/warning backgrounds (verified in @types/vscode). So `prominentBackground` as a *background* is silently ignored. Implemented the issue's "prominent, not alarming" intent via `color = statusBarItem.prominentForeground` (the icon/text tint) instead. Visually verify at dev-approval; if a stronger cue is wanted, warningBackground is the only API-honored background (issue called it too alarming). + +Build: check-types ✓ lint ✓ esbuild ✓. Unit: 310 pass (26 suites). Next: push, porch done → dev-approval gate. From 1ff1dde6424fd79b1174878cbdc48e5f8a987f17 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 19:26:47 +1000 Subject: [PATCH 14/34] chore(porch): 921 dev-approval gate-requested --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 99787b3ba..66ecae380 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -11,10 +11,11 @@ gates: approved_at: '2026-06-05T09:09:55.929Z' dev-approval: status: pending + requested_at: '2026-06-05T09:26:47.546Z' pr: status: pending iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T09:10:03.176Z' +updated_at: '2026-06-05T09:26:47.547Z' From 5c73dd9b25058429c64205329b739f5360416d0f Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:29:24 +1000 Subject: [PATCH 15/34] [PIR #921] Make Reveal a Codev-sidebar show/hide toggle --- packages/vscode/package.json | 17 ++++++++++++++++- .../__tests__/contributes-dev-server.test.ts | 17 +++++++++++++---- .../vscode/src/commands/dev-server-actions.ts | 12 +++++++++++- packages/vscode/src/extension.ts | 4 +++- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index ca1031e4d..f79db2b84 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -222,6 +222,12 @@ "category": "Codev", "icon": "$(eye)" }, + { + "command": "codev.devServer.hideSidebar", + "title": "Hide Codev Sidebar", + "category": "Codev", + "icon": "$(eye-closed)" + }, { "command": "codev.openDevUrl", "title": "Codev: Open Dev URL" @@ -376,6 +382,10 @@ { "command": "codev.devServer.revealInWorkspace", "when": "false" + }, + { + "command": "codev.devServer.hideSidebar", + "when": "false" } ], "view/item/context": [ @@ -578,7 +588,12 @@ }, { "command": "codev.devServer.revealInWorkspace", - "when": "view == codev.devServer", + "when": "view == codev.devServer && !(sideBarVisible && activeViewlet == 'workbench.view.extension.codev')", + "group": "navigation@4" + }, + { + "command": "codev.devServer.hideSidebar", + "when": "view == codev.devServer && sideBarVisible && activeViewlet == 'workbench.view.extension.codev'", "group": "navigation@4" } ], diff --git a/packages/vscode/src/__tests__/contributes-dev-server.test.ts b/packages/vscode/src/__tests__/contributes-dev-server.test.ts index cce2aa1af..328cd3de4 100644 --- a/packages/vscode/src/__tests__/contributes-dev-server.test.ts +++ b/packages/vscode/src/__tests__/contributes-dev-server.test.ts @@ -42,10 +42,18 @@ describe('codev.devServer title-bar actions (#921)', () => { } }); - it('declares Switch Target and Reveal shown whenever the view is active', () => { - for (const cmd of ['codev.devServer.switchTarget', 'codev.devServer.revealInWorkspace']) { - expect(action(cmd)?.when).toBe('view == codev.devServer'); - } + it('shows Switch Target whenever the view is active', () => { + expect(action('codev.devServer.switchTarget')?.when).toBe('view == codev.devServer'); + }); + + it('pairs Reveal / Hide as a sidebar toggle on the Codev viewlet visibility', () => { + const codevSidebarShown = "sideBarVisible && activeViewlet == 'workbench.view.extension.codev'"; + // Reveal shows when the Codev sidebar is NOT the active, visible viewlet. + expect(action('codev.devServer.revealInWorkspace')?.when) + .toBe(`view == codev.devServer && !(${codevSidebarShown})`); + // Hide shows when it is — the complementary half of the toggle. + expect(action('codev.devServer.hideSidebar')?.when) + .toBe(`view == codev.devServer && ${codevSidebarShown}`); }); it('gives each action an icon', () => { @@ -54,6 +62,7 @@ describe('codev.devServer title-bar actions (#921)', () => { expect(byId['codev.devServer.restart']?.icon).toBe('$(debug-restart)'); expect(byId['codev.devServer.switchTarget']?.icon).toBe('$(arrow-swap)'); expect(byId['codev.devServer.revealInWorkspace']?.icon).toBe('$(eye)'); + expect(byId['codev.devServer.hideSidebar']?.icon).toBe('$(eye-closed)'); }); }); diff --git a/packages/vscode/src/commands/dev-server-actions.ts b/packages/vscode/src/commands/dev-server-actions.ts index cfbc0f3f7..80d362f55 100644 --- a/packages/vscode/src/commands/dev-server-actions.ts +++ b/packages/vscode/src/commands/dev-server-actions.ts @@ -66,7 +66,17 @@ export async function switchDevTarget( await startDevForTarget(connectionManager, terminalManager, picked.target); } -/** Focus the Workspace sidebar view, where the Dev Server row lives. */ +/** + * Open the Codev sidebar and focus the Workspace view, where the Dev Server row + * lives. Paired with `hideCodevSidebar` to form a show/hide toggle on the tab's + * title bar (the two `view/title` entries swap on the sidebar-visibility context + * keys, mirroring the Backlog view's show-all / mine-only toggle). + */ export async function revealDevInWorkspace(): Promise { await vscode.commands.executeCommand('codev.workspace.focus'); } + +/** Close the sidebar — the toggle-off half of the Codev Dev tab's sidebar control. */ +export async function hideCodevSidebar(): Promise { + await vscode.commands.executeCommand('workbench.action.closeSidebar'); +} diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 37b3a2c9a..c29d64547 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -11,7 +11,7 @@ import { viewDiff, activateDiffView, diffUrisForChange } from './commands/view-d import { runWorktreeDev } from './commands/run-worktree-dev.js'; import { stopWorktreeDev } from './commands/stop-worktree-dev.js'; import { runWorkspaceDev, stopWorkspaceDev } from './commands/run-workspace-dev.js'; -import { stopDevServer, restartDevServer, switchDevTarget, revealDevInWorkspace } from './commands/dev-server-actions.js'; +import { stopDevServer, restartDevServer, switchDevTarget, revealDevInWorkspace, hideCodevSidebar } from './commands/dev-server-actions.js'; import { openDevUrl } from './commands/open-dev-url.js'; import { pasteImage } from './commands/paste-image.js'; import { openWorktreeFolder } from './commands/open-worktree-folder.js'; @@ -806,6 +806,8 @@ export async function activate(context: vscode.ExtensionContext) { switchDevTarget(connectionManager!, terminalManager!)), reg('codev.devServer.revealInWorkspace', () => revealDevInWorkspace()), + reg('codev.devServer.hideSidebar', () => + hideCodevSidebar()), reg('codev.openDevUrl', (urlArg?: unknown) => openDevUrl(connectionManager!, typeof urlArg === 'string' ? urlArg : undefined)), reg('codev.pasteImage', () => From 344ef85b6438fb2f651e1cef10300536cdd0506a Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:29:24 +1000 Subject: [PATCH 16/34] [PIR #921] Update builder thread (reveal toggle) --- codev/state/pir-921_thread.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md index c70a91c4d..fd75e297e 100644 --- a/codev/state/pir-921_thread.md +++ b/codev/state/pir-921_thread.md @@ -70,3 +70,11 @@ Implemented both surfaces on top of #812's codevPanel: - Chip "prominent" tint: VSCode `StatusBarItem.backgroundColor` ONLY honors error/warning backgrounds (verified in @types/vscode). So `prominentBackground` as a *background* is silently ignored. Implemented the issue's "prominent, not alarming" intent via `color = statusBarItem.prominentForeground` (the icon/text tint) instead. Visually verify at dev-approval; if a stronger cue is wanted, warningBackground is the only API-honored background (issue called it too alarming). Build: check-types ✓ lint ✓ esbuild ✓. Unit: 310 pass (26 suites). Next: push, porch done → dev-approval gate. + +## dev-approval feedback iter-1: Reveal → sidebar toggle + +Reviewer feedback: "Reveal in Workspace View" had weak value (navigated from the richer dev tab to the poorer Workspace dev row). Observed its real effect is opening the Codev sidebar. Per direction, repurposed it as a show/hide toggle: +- Open half = `codev.devServer.revealInWorkspace` → `codev.workspace.focus` (opens sidebar + focuses Workspace view). Shown when Codev sidebar is NOT the active/visible viewlet. +- Close half = `codev.devServer.hideSidebar` → `workbench.action.closeSidebar`. Shown when it is. +- Paired via `when` on `sideBarVisible && activeViewlet == 'workbench.view.extension.codev'`, mirroring the Backlog show-all/mine-only eye/eye-closed toggle idiom. Icons $(eye)/$(eye-closed). +Build/lint/types ✓, 311 unit tests ✓. Gate still dev-approval (no porch done re-run; iterating in place). From de9a52ad38561fe1cd035d0bfd2791e78831f8ec Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:33:20 +1000 Subject: [PATCH 17/34] [PIR #921] Rename Reveal action to 'Show Codev Sidebar' for toggle symmetry --- packages/vscode/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index f79db2b84..c683b0202 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -218,7 +218,7 @@ }, { "command": "codev.devServer.revealInWorkspace", - "title": "Reveal in Workspace View", + "title": "Show Codev Sidebar", "category": "Codev", "icon": "$(eye)" }, From 0ef6ad6386756fbc9d37d5226779f62641465326 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:34:43 +1000 Subject: [PATCH 18/34] [PIR #921] Rename revealInWorkspace command id to showSidebar --- packages/vscode/package.json | 6 +++--- .../vscode/src/__tests__/contributes-dev-server.test.ts | 4 ++-- packages/vscode/src/commands/dev-server-actions.ts | 2 +- packages/vscode/src/extension.ts | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/vscode/package.json b/packages/vscode/package.json index c683b0202..fc5406bec 100644 --- a/packages/vscode/package.json +++ b/packages/vscode/package.json @@ -217,7 +217,7 @@ "icon": "$(arrow-swap)" }, { - "command": "codev.devServer.revealInWorkspace", + "command": "codev.devServer.showSidebar", "title": "Show Codev Sidebar", "category": "Codev", "icon": "$(eye)" @@ -380,7 +380,7 @@ "when": "codev.hasDevCommand" }, { - "command": "codev.devServer.revealInWorkspace", + "command": "codev.devServer.showSidebar", "when": "false" }, { @@ -587,7 +587,7 @@ "group": "navigation@3" }, { - "command": "codev.devServer.revealInWorkspace", + "command": "codev.devServer.showSidebar", "when": "view == codev.devServer && !(sideBarVisible && activeViewlet == 'workbench.view.extension.codev')", "group": "navigation@4" }, diff --git a/packages/vscode/src/__tests__/contributes-dev-server.test.ts b/packages/vscode/src/__tests__/contributes-dev-server.test.ts index 328cd3de4..7295100fd 100644 --- a/packages/vscode/src/__tests__/contributes-dev-server.test.ts +++ b/packages/vscode/src/__tests__/contributes-dev-server.test.ts @@ -49,7 +49,7 @@ describe('codev.devServer title-bar actions (#921)', () => { it('pairs Reveal / Hide as a sidebar toggle on the Codev viewlet visibility', () => { const codevSidebarShown = "sideBarVisible && activeViewlet == 'workbench.view.extension.codev'"; // Reveal shows when the Codev sidebar is NOT the active, visible viewlet. - expect(action('codev.devServer.revealInWorkspace')?.when) + expect(action('codev.devServer.showSidebar')?.when) .toBe(`view == codev.devServer && !(${codevSidebarShown})`); // Hide shows when it is — the complementary half of the toggle. expect(action('codev.devServer.hideSidebar')?.when) @@ -61,7 +61,7 @@ describe('codev.devServer title-bar actions (#921)', () => { expect(byId['codev.devServer.stop']?.icon).toBe('$(debug-stop)'); expect(byId['codev.devServer.restart']?.icon).toBe('$(debug-restart)'); expect(byId['codev.devServer.switchTarget']?.icon).toBe('$(arrow-swap)'); - expect(byId['codev.devServer.revealInWorkspace']?.icon).toBe('$(eye)'); + expect(byId['codev.devServer.showSidebar']?.icon).toBe('$(eye)'); expect(byId['codev.devServer.hideSidebar']?.icon).toBe('$(eye-closed)'); }); }); diff --git a/packages/vscode/src/commands/dev-server-actions.ts b/packages/vscode/src/commands/dev-server-actions.ts index 80d362f55..78ceaead0 100644 --- a/packages/vscode/src/commands/dev-server-actions.ts +++ b/packages/vscode/src/commands/dev-server-actions.ts @@ -72,7 +72,7 @@ export async function switchDevTarget( * title bar (the two `view/title` entries swap on the sidebar-visibility context * keys, mirroring the Backlog view's show-all / mine-only toggle). */ -export async function revealDevInWorkspace(): Promise { +export async function showCodevSidebar(): Promise { await vscode.commands.executeCommand('codev.workspace.focus'); } diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index c29d64547..63679f747 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -11,7 +11,7 @@ import { viewDiff, activateDiffView, diffUrisForChange } from './commands/view-d import { runWorktreeDev } from './commands/run-worktree-dev.js'; import { stopWorktreeDev } from './commands/stop-worktree-dev.js'; import { runWorkspaceDev, stopWorkspaceDev } from './commands/run-workspace-dev.js'; -import { stopDevServer, restartDevServer, switchDevTarget, revealDevInWorkspace, hideCodevSidebar } from './commands/dev-server-actions.js'; +import { stopDevServer, restartDevServer, switchDevTarget, showCodevSidebar, hideCodevSidebar } from './commands/dev-server-actions.js'; import { openDevUrl } from './commands/open-dev-url.js'; import { pasteImage } from './commands/paste-image.js'; import { openWorktreeFolder } from './commands/open-worktree-folder.js'; @@ -804,8 +804,8 @@ export async function activate(context: vscode.ExtensionContext) { restartDevServer(connectionManager!, terminalManager!)), regCli('codev.devServer.switchTarget', () => switchDevTarget(connectionManager!, terminalManager!)), - reg('codev.devServer.revealInWorkspace', () => - revealDevInWorkspace()), + reg('codev.devServer.showSidebar', () => + showCodevSidebar()), reg('codev.devServer.hideSidebar', () => hideCodevSidebar()), reg('codev.openDevUrl', (urlArg?: unknown) => From c3cd9aa1f0211240a10ed038a33133e1722469ad Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:37:51 +1000 Subject: [PATCH 19/34] [PIR #921] Use $(server-process) for the dev chip instead of $(zap) --- packages/vscode/src/extension.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 63679f747..18a198400 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -383,7 +383,8 @@ export async function activate(context: vscode.ExtensionContext) { devChipItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 99); devChipItem.command = 'codev.devServer.focus'; // VSCode's auto view-focus command } - devChipItem.text = `$(zap) Dev: ${target}`; + // server-process (a running dev server), not zap — $(zap) reads as AI/sparkle in VSCode. + devChipItem.text = `$(server-process) Dev: ${target}`; // StatusBarItem.backgroundColor only honors error/warning backgrounds // (VSCode API constraint), so the "prominent, not alarming" look // (#921 design call #4) is applied via the foreground instead. From d2b5f76305eebcaac0fd2063863245d59a3e6973 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:38:40 +1000 Subject: [PATCH 20/34] chore(porch): 921 dev-approval gate-approved --- .../projects/921-vscode-codev-dev-surface-botto/status.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 66ecae380..cb9502a48 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -10,12 +10,13 @@ gates: requested_at: '2026-06-03T23:34:35.312Z' approved_at: '2026-06-05T09:09:55.929Z' dev-approval: - status: pending + status: approved requested_at: '2026-06-05T09:26:47.546Z' + approved_at: '2026-06-05T10:38:40.588Z' pr: status: pending iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T09:26:47.547Z' +updated_at: '2026-06-05T10:38:40.589Z' From 92c9ce9aa6b9182154a5f5fab99403462fd46521 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:39:01 +1000 Subject: [PATCH 21/34] chore(porch): 921 review phase-transition --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index cb9502a48..b8e7c75ea 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -1,7 +1,7 @@ id: '921' title: vscode-codev-dev-surface-botto protocol: pir -phase: implement +phase: review plan_phases: [] current_plan_phase: null gates: @@ -19,4 +19,4 @@ iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:38:40.589Z' +updated_at: '2026-06-05T10:39:01.954Z' From 2bd13e3020768595cc285cf5157550488bb44dde Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:41:28 +1000 Subject: [PATCH 22/34] [PIR #921] Review + retrospective --- codev/resources/arch.md | 3 +- codev/resources/lessons-learned.md | 2 + .../921-vscode-codev-dev-surface-botto.md | 75 +++++++++++++++++++ 3 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 codev/reviews/921-vscode-codev-dev-surface-botto.md diff --git a/codev/resources/arch.md b/codev/resources/arch.md index 5bb2606da..236b531df 100644 --- a/codev/resources/arch.md +++ b/codev/resources/arch.md @@ -1056,7 +1056,8 @@ The VS Code extension (`packages/vscode`) is a thin client over Tower's existing - **Subpath exports**: `codev-core` uses subpath exports (`./tower-client`, `./escape-buffer`, etc.) to prevent Node builtins from leaking into the dashboard's Vite build. - **Injectable auth**: `TowerClient` accepts a `getAuthKey` callback. CLI uses `ensureLocalKey()` (creates key if missing). Extension uses `readLocalKey()` + `SecretStorage` (never creates keys). - **Editor-tab webviews (#920)**: Richer-than-TreeView surfaces use `vscode.window.createWebviewPanel` (editor area), not a sidebar `WebviewView`. Pattern: the panel is a thin view that posts debounced criteria to the extension host; **filtering/sorting runs host-side** in vscode-free pure helpers (`views/backlog-filter.ts`, vitest-tested) so logic stays testable and sensitive data (e.g. issue bodies) never crosses into the webview — only display rows do. HTML/CSS/JS live in a sibling `*.template.ts` (no esbuild asset-copy step); theming is **CSS variables only** (`--vscode-*`) so dark/light/high-contrast render natively; CSP is nonce'd. First instance: the "Search Backlog" panel (`webviews/backlog-search-panel.ts`), fed by the dedicated `issue-search` forge concept → `GET /api/issue-search` (kept separate from `issue-list` so `/api/overview` stays body-free). -- **Panel view container (#812)**: The extension contributes view containers to **two** locations — `activitybar.codev` (the 7-section sidebar) and `panel.codevPanel` (bottom panel, wide-short geometry). The panel exists as scaffolding for views whose shape suits a wide layout (timelines, rosters, tables); migrations are tracked separately (#813/#814/#815). Panel views are plain `TreeDataProvider`s, identical in kind to sidebar providers — VS Code lets the same view types live in either location. A `codev.panelContainerEmpty` context key gates a placeholder view that hides once real views register. VS Code gives no control over panel-tab *position* (it lands last, in the `…` overflow), so the extension does a one-time, globalState-guarded reveal (`workbench.view.extension.codevPanel`) on first activation for discoverability. +- **Panel view container (#812)**: The extension contributes view containers to **two** locations — `activitybar.codev` (the 7-section sidebar) and `panel.codevPanel` (bottom panel, wide-short geometry). The panel exists as scaffolding for views whose shape suits a wide layout (timelines, rosters, tables); migrations are tracked separately (#813/#814/#815). Panel views are plain `TreeDataProvider`s, identical in kind to sidebar providers — VS Code lets the same view types live in either location. A `codev.panelContainerEmpty` context key gates a placeholder view that hides once real views register — **#921's `codev.devServer` is the first such view, so the panel now ships non-empty** (the key is seeded `false`). VS Code gives no control over panel-tab *position* (it lands last, in the `…` overflow), so the extension does a one-time, globalState-guarded reveal (`workbench.view.extension.codevPanel`) on first activation for discoverability. +- **Codev Dev surface (#921)**: The single `afx dev` PTY gets two complementary surfaces, both driven off the one `TerminalManager.onDidChangeDevTerminals` event (single source of truth, so chip and tab never drift on start/stop/swap): a `codev.devServer` panel `TreeDataProvider` (first real `codevPanel` tenant — status header of target / live-ticking uptime / best-effort port, plus title-bar Stop / Restart / Switch-Target / Show-Hide-sidebar actions gated by a `codev.devServerRunning` key) and an always-visible **status-bar chip** (`StatusBarItem`, left, priority 99) shown only while a dev runs, clicking through to the tab. The native `Codev: (dev)` terminal stays as the output surface (coexist) — the new tab is a status/control surface, not an output mirror, so there is no second PTY/xterm re-plumbing. Uptime needs a start time `listDevTerminals()` doesn't carry, so `TerminalManager` keeps a `builderId → startedAt` map. Pure formatters (`views/dev-server-format.ts`: uptime, port-from-config) are vitest-tested. - **Startup CLI preflight (#791)**: On `activate()` the extension verifies the `codev` CLI is installed and at least its own `package.json` version (`codev --version`, resolved like `resolveAfxPath`, cached per session, 400ms-bounded, fire-and-forget so activation never blocks). Missing → `Get started with Codev` walkthrough; outdated → upgrade notification; either dismissed → CLI-dependent commands no-op with one "run setup" toast. Commands register through two helpers — `reg` (unguarded) and `regCli` (guarded) — so the registrar name *is* the guard policy (no separate list). Preflight also sets the `codev.cliReady` context key, which drives the walkthrough's Verify-step completion. Lives in `src/preflight/` (`preflight-core.ts` pure + unit-tested, `preflight.ts` vscode glue). ## Repository Dual Nature diff --git a/codev/resources/lessons-learned.md b/codev/resources/lessons-learned.md index 3122ca8d9..cad5750f8 100644 --- a/codev/resources/lessons-learned.md +++ b/codev/resources/lessons-learned.md @@ -306,6 +306,8 @@ Generalizable wisdom extracted from review documents, ordered by impact. Updated - [From #952] When grouping a UI by an **open/growing enum**, map the raw values onto a **closed canonical set** instead of minting one group per distinct value. Grouping the Builders tree by raw `protocolPhase` would yield ~19 group headers across the 9 protocols (and grow with every new protocol); folding all phase ids into 6 fixed lifecycle stages via a `PHASE_TO_STAGE` map caps the tree at a constant 7 groups forever — an unmapped future phase falls into a bounded `unknown` bucket rather than expanding the set. Bound the axis, don't enumerate it. - [From #952] For a view control that switches between **two equal modes** (not on/off), the idiomatic VSCode pattern is **two commands with mutually-exclusive `when`-clauses** on a context key (only one renders at a time) — this is what built-in list/tree toggles use and it allows a distinct icon per target. VSCode *also* supports a single-button `toggled` menu property (renders pressed/highlighted when a `when`-expr is true), but that shows one icon only and reads as on/off, so it fits genuine binary toggles (e.g. a "show all" eye) better than a two-equal-modes axis swap. - [From #952] When several behaviors vary together per mode (here: bucketing fn, expansion store, row prefix, flatten rule all switch on stage-vs-area), bundle them behind **one strategy object per mode** rather than scattering `if (mode === …)` across the consumer. The provider holds `Record` + an `active()` accessor and delegates; adding a mode becomes one new object, and the correlated decisions can't drift out of sync. +- [From #921] VSCode's `StatusBarItem.backgroundColor` honors **only** `statusBarItem.errorBackground` and `warningBackground` (stated in the API docs) — any other `ThemeColor` (e.g. `prominentBackground`) is silently ignored, so the chip won't tint. For a "prominent, not alarming" cue, set the **foreground** instead (`color = ThemeColor('statusBarItem.prominentForeground')`) and reserve the two supported backgrounds for genuine error/warning states. Verify the API's stated constraints (it's in the `.d.ts` doc comment) rather than trusting a design spec's color name. +- [From #921] `$(zap)` (lightning) now reads as the **AI / sparkle** glyph across VSCode surfaces (Copilot et al.) — avoid it for non-AI features. Pick a literal glyph for what the thing *is* (`$(server-process)` for a dev server), so the status bar doesn't imply an AI affordance that isn't there. ## Documentation diff --git a/codev/reviews/921-vscode-codev-dev-surface-botto.md b/codev/reviews/921-vscode-codev-dev-surface-botto.md new file mode 100644 index 000000000..9ac70d35c --- /dev/null +++ b/codev/reviews/921-vscode-codev-dev-surface-botto.md @@ -0,0 +1,75 @@ +# PIR Review: Codev Dev surface (bottom-panel tab + always-visible status-bar chip) + +Fixes #921 + +## Summary + +Adds two complementary VSCode surfaces for the single `afx dev` PTY so a reviewer can see at a glance whether a dev is running, for which target, and stop/restart it fast without hunting the terminal dropdown. A **`codev.devServer` panel tab** (the first real view in #812's `codevPanel` container) shows a status header — target / live uptime / best-effort port — with title-bar Stop / Restart / Switch Target / Show-Hide-sidebar actions; an always-visible **status-bar chip** (`$(server-process) Dev: `) appears only while a dev runs and clicks through to the tab. Both derive from the one `TerminalManager.onDidChangeDevTerminals` event, and the native `Codev: (dev)` terminal stays as the output surface (coexist), so there is no PTY re-plumbing. + +## Files Changed + +- `packages/vscode/package.json` (+78 / -…) — `codev.devServer` view in `codevPanel`; 5 command declarations; 4 title-bar menu entries (Stop/Restart gated on running, Switch always, Show↔Hide sidebar toggle); command-palette gating +- `packages/vscode/src/views/dev-server.ts` (+125) — new `DevServerTreeProvider` (status header / placeholder / epitaph rows, 1s uptime ticker, best-effort port) +- `packages/vscode/src/views/dev-server-format.ts` (+72) — new pure helpers `formatUptime` / `extractDevPort` +- `packages/vscode/src/commands/dev-server-actions.ts` (+82) — new title-bar handlers (stop / restart / switch / show / hide) +- `packages/vscode/src/commands/dev-shared.ts` (+86) — `restartDevForTarget`, `listSwitchTargets`, `resolveDevTargetById` +- `packages/vscode/src/extension.ts` (+63 / -…) — register the view; status-bar chip lifecycle; `codev.devServerRunning` key; flip `codev.panelContainerEmpty` false +- `packages/vscode/src/terminal-manager.ts` (+25) — `builderId → startedAt` map + `getDevStartedAt` for uptime +- `packages/vscode/src/__tests__/dev-server-format.test.ts` (+63) — unit tests for the pure helpers +- `packages/vscode/src/__tests__/contributes-dev-server.test.ts` (+80) — contributes/wiring invariants +- `packages/vscode/src/__tests__/contributes-panel.test.ts` (+17 / -…) — updated #812 guards for the now-false key + second view +- `codev/resources/arch.md`, `codev/resources/lessons-learned.md` — see sections below + +## Commits + +- `1996981f` [PIR #921] Track dev-terminal start times for uptime +- `c80ceb49` [PIR #921] Add pure dev-status formatters (uptime, port) + tests +- `c492354c` [PIR #921] Add DevServerTreeProvider for the Codev Dev panel tab +- `500f899b` [PIR #921] Add dev-server title-bar actions (stop/restart/switch/reveal) +- `9e2520fd` [PIR #921] Wire Codev Dev view, status-bar chip, and context keys +- `a92363f3` [PIR #921] Contribute codev.devServer view, commands, title-bar menus +- `5c73dd9b` [PIR #921] Make Reveal a Codev-sidebar show/hide toggle +- `de9a52ad` [PIR #921] Rename Reveal action to 'Show Codev Sidebar' for toggle symmetry +- `0ef6ad63` [PIR #921] Rename revealInWorkspace command id to showSidebar +- `c3cd9aa1` [PIR #921] Use $(server-process) for the dev chip instead of $(zap) + +(plus thread-file updates) + +## Test Results + +- `pnpm check-types`: ✓ pass +- `pnpm lint`: ✓ pass +- `node esbuild.js` (bundle): ✓ pass +- `pnpm test:unit`: ✓ pass (311 tests, ~11 new across `dev-server-format` and `contributes-dev-server`) +- Manual verification: performed by the reviewer at the `dev-approval` gate against the running worktree — exercised the chip + panel tab, the Show/Hide sidebar toggle, and iterated on the chip icon (`$(zap)` → `$(server-process)`, since `$(zap)` reads as AI) and the toggle's label/command-id naming. + +## Architecture Updates + +Updated `codev/resources/arch.md`. The existing **Panel view container (#812)** decision noted the panel was scaffolding whose placeholder "hides once real views register" — #921 makes `codev.devServer` the first such view, so I amended that line (the panel now ships non-empty, key seeded `false`) and added a **Codev Dev surface (#921)** key-design-decision bullet documenting the two-surface design, the single-event source of truth, the coexist-with-terminal choice (status surface, not an output mirror), and the `startedAt` map that backs uptime. + +## Lessons Learned Updates + +Added two UI/UX entries to `codev/resources/lessons-learned.md`: +- VSCode's `StatusBarItem.backgroundColor` honors only `errorBackground` / `warningBackground`; `prominentBackground` as a *background* is silently ignored, so a "prominent, not alarming" chip cue must use the **foreground** (`prominentForeground`) instead. (Verify the API's documented constraint, don't trust a spec's color name.) +- `$(zap)` now reads as the AI/sparkle glyph in VSCode — use a literal glyph (`$(server-process)`) for non-AI features. + +The two-command `when`-clause toggle pattern used for Show/Hide sidebar was already captured under #952, so it isn't duplicated. + +## Things to Look At During PR Review + +- **Chip tint (deliberate deviation from the plan's literal wording).** The plan/issue said `prominentBackground`; the VSCode API only honors error/warning *backgrounds*, so I used `color = statusBarItem.prominentForeground` instead. If a stronger cue is wanted, `warningBackground` is the only API-honored background (the issue called it too alarming). See `extension.ts` `updateDevChip`. +- **Show/Hide sidebar toggle `when` clauses.** They hinge on `sideBarVisible && activeViewlet == 'workbench.view.extension.codev'`. If the Codev container is dragged into the *secondary* side panel, `activeViewlet` won't match and the toggle falls back to always showing "Show Codev Sidebar." Edge case (Codev defaults to the primary activity bar), but worth knowing. +- **Switch Target / Restart target ids.** `listSwitchTargets` / `resolveDevTargetById` use the worktree **basename** (e.g. `pir-809`) as the target id, matching the `afx dev` / Workspace-view convention and the chip display. A dev started via the older builder-row path (`run-worktree-dev`, which uses the overview id) could in principle not match by id — pre-existing inconsistency, not introduced here, and the dominant `afx dev` path is consistent. +- **Best-effort port.** Omitted when not derivable from `worktree.devUrls` / `devCommand` (no guessing). This repo's `devCommand` may not expose a port, so expect the Port row absent here. + +## How to Test Locally + +- **View diff**: VSCode sidebar → right-click builder `pir-921` → **View Diff** +- **Run dev server**: VSCode sidebar → **Run Dev Server**, or `afx dev pir-921` +- **What to verify** (mapped to the plan's Test Plan): + - chip appears bottom-left (`$(server-process) Dev: `) only while a dev runs; click focuses the Codev Dev tab; disappears on stop + - panel tab status header: correct target, live-ticking uptime, port row present only when derivable; placeholder when idle; "Stopped …" epitaph after stop + - title-bar Stop / Restart / Switch Target behave as labeled; Show ⇄ Hide sidebar toggles correctly + - target swap updates chip + tab in lockstep + - #812 placeholder hidden (only Codev Dev shows, not both) + - Dark / Light / High-Contrast render cleanly From 4c6cbad1688d1d7ae64400b0434092c8939a23db Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:41:44 +1000 Subject: [PATCH 23/34] chore(porch): 921 record PR #996 --- .../921-vscode-codev-dev-surface-botto/status.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index b8e7c75ea..42bcf823c 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -19,4 +19,9 @@ iteration: 1 build_complete: false history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:39:01.954Z' +updated_at: '2026-06-05T10:41:44.213Z' +pr_history: + - phase: review + pr_number: 996 + branch: builder/pir-921 + created_at: '2026-06-05T10:41:44.212Z' From 39b37f29de938f344ad2771a2272bf20b99d6998 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:41:51 +1000 Subject: [PATCH 24/34] chore(porch): 921 review build-complete --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 42bcf823c..28b61877c 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -16,10 +16,10 @@ gates: pr: status: pending iteration: 1 -build_complete: false +build_complete: true history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:41:44.213Z' +updated_at: '2026-06-05T10:41:51.120Z' pr_history: - phase: review pr_number: 996 From 52d2f4c55f78ce217b797b8ab8b38fe53f81533a Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:42:25 +1000 Subject: [PATCH 25/34] [PIR #921] Update builder thread (review + consultation) --- codev/state/pir-921_thread.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md index fd75e297e..e353b42d0 100644 --- a/codev/state/pir-921_thread.md +++ b/codev/state/pir-921_thread.md @@ -78,3 +78,12 @@ Reviewer feedback: "Reveal in Workspace View" had weak value (navigated from the - Close half = `codev.devServer.hideSidebar` → `workbench.action.closeSidebar`. Shown when it is. - Paired via `when` on `sideBarVisible && activeViewlet == 'workbench.view.extension.codev'`, mirroring the Backlog show-all/mine-only eye/eye-closed toggle idiom. Icons $(eye)/$(eye-closed). Build/lint/types ✓, 311 unit tests ✓. Gate still dev-approval (no porch done re-run; iterating in place). + +## Review phase (dev-approval approved) + +- Wrote codev/reviews/921-*.md (Summary / Files / Commits / Test Results / Arch Updates / Lessons / Things-to-look-at / How-to-test). +- arch.md: updated #812 panel bullet (codev.devServer = first real panel view → key seeded false) + added "Codev Dev surface (#921)" key-design bullet. +- lessons-learned.md: added StatusBarItem.backgroundColor (only error/warning honored → use prominentForeground) + $(zap)=AI-glyph gotchas. (The two-command when-clause toggle was already under #952.) +- PR #996 opened (body = review file, Fixes #921). Recorded with porch. +- porch structural checks ✓ (pr_exists, review_has_arch_updates, review_has_lessons_updates). +- 3-way consultation (gemini/codex/claude, type=impl) running in background — single advisory pass (max_iterations:1). Next: read verdicts, handle any REQUEST_CHANGES (fix+regression test or rebut; escalate either way), notify architect, wait at pr gate. From 5a841f9e0e165402b003d4ed601592095bb8edb7 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:50:58 +1000 Subject: [PATCH 26/34] [PIR #921] Refresh dev surfaces when a dev terminal is closed manually --- .../src/__tests__/terminal-manager.test.ts | 19 +++++++++++++++++++ packages/vscode/src/terminal-manager.ts | 14 +++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/vscode/src/__tests__/terminal-manager.test.ts b/packages/vscode/src/__tests__/terminal-manager.test.ts index 2473dec7c..8f1aee95f 100644 --- a/packages/vscode/src/__tests__/terminal-manager.test.ts +++ b/packages/vscode/src/__tests__/terminal-manager.test.ts @@ -65,3 +65,22 @@ describe('Spec 786 Phase 6 — TerminalManager per-name keying', () => { expect(TM_SRC).toMatch(/Codev: Architect \(\$\{architectName\}\)/); }); }); + +describe('#921 — dev surface refresh on manual terminal close', () => { + // Regression guard: a dev terminal closed via the generic onDidCloseTerminal + // path (tab ✕ / process exit) must clear devStartedAt AND re-fire + // onDidChangeDevTerminals, or the chip / Codev Dev tab / devServerRunning + // context strand as "running". The explicit close paths fired the event; the + // generic path previously only unmapped. Source-level per this file's harness + // rationale (constructing TerminalManager needs heavy vscode mocking). + const closeHandler = TM_SRC.split('onDidCloseTerminal((t)')[1]?.split('terminal.show')[0] ?? ''; + + it('clears devStartedAt for a dev terminal closed via the generic path', () => { + expect(closeHandler).toMatch(/mapKey\.startsWith\(['"]dev-['"]\)/); + expect(closeHandler).toMatch(/devStartedAt\.delete\(/); + }); + + it('re-fires the dev-terminal change event from the generic close path', () => { + expect(closeHandler).toMatch(/_onDidChangeDevTerminals\.fire\(\)/); + }); +}); diff --git a/packages/vscode/src/terminal-manager.ts b/packages/vscode/src/terminal-manager.ts index 031706b9e..73ce44c71 100644 --- a/packages/vscode/src/terminal-manager.ts +++ b/packages/vscode/src/terminal-manager.ts @@ -396,9 +396,21 @@ export class TerminalManager { const disposable = vscode.window.onDidCloseTerminal((t) => { if (t !== terminal) { return; } pty.close(); - if (this.terminals.get(mapKey)?.terminal === terminal) { + const wasTracked = this.terminals.get(mapKey)?.terminal === terminal; + if (wasTracked) { this.terminals.delete(mapKey); } + // A dev terminal closed via this generic path (tab ✕, or the dev process + // exiting) must refresh the dev surfaces (#921) too — the explicit + // closeDevTerminal/closeBuilderTerminal paths fire the event, but a manual + // close reaches only here, which previously just unmapped and left the + // chip / tab / `codev.devServerRunning` stranded as "running". Guarded by + // `wasTracked` so the explicit-close path (which deletes first, then + // dispose()s the terminal) doesn't double-fire. + if (wasTracked && mapKey.startsWith('dev-')) { + this.devStartedAt.delete(mapKey.slice('dev-'.length)); + this._onDidChangeDevTerminals.fire(); + } disposable.dispose(); }); From 6d250eb42c290d71f3a56eba34cc6cd759ddfe03 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:50:58 +1000 Subject: [PATCH 27/34] [PIR #921] Add Codev Dev tab badge via createTreeView --- .../src/__tests__/contributes-dev-server.test.ts | 7 ++++--- packages/vscode/src/extension.ts | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/vscode/src/__tests__/contributes-dev-server.test.ts b/packages/vscode/src/__tests__/contributes-dev-server.test.ts index 7295100fd..1557e193c 100644 --- a/packages/vscode/src/__tests__/contributes-dev-server.test.ts +++ b/packages/vscode/src/__tests__/contributes-dev-server.test.ts @@ -67,14 +67,15 @@ describe('codev.devServer title-bar actions (#921)', () => { }); describe('extension.ts wiring (#921)', () => { - it('registers the dev-server tree view', () => { + it('creates the dev-server tree view via createTreeView (for the badge handle)', () => { expect(EXT_SRC).toMatch( - /registerTreeDataProvider\(['"]codev\.devServer['"], devServerProvider\)/, + /createTreeView\(['"]codev\.devServer['"], \{ treeDataProvider: devServerProvider \}\)/, ); }); - it('drives the chip + devServerRunning context key off the dev-terminal event', () => { + it('drives the chip + devServerRunning context key + tab badge off the dev-terminal event', () => { expect(EXT_SRC).toMatch(/onDidChangeDevTerminals\(refreshDevSurface\)/); expect(EXT_SRC).toMatch(/setContext['"],\s*['"]codev\.devServerRunning['"]/); + expect(EXT_SRC).toMatch(/devServerView\.badge\s*=/); }); }); diff --git a/packages/vscode/src/extension.ts b/packages/vscode/src/extension.ts index 18a198400..6a835761f 100644 --- a/packages/vscode/src/extension.ts +++ b/packages/vscode/src/extension.ts @@ -41,6 +41,7 @@ import { TeamProvider } from './views/team.js'; import { StatusProvider } from './views/status.js'; import { PanelPlaceholderProvider } from './views/panel-placeholder.js'; import { DevServerTreeProvider } from './views/dev-server.js'; +import { formatTargetName } from './views/dev-server-format.js'; import { WorkspaceProvider } from './views/workspace.js'; import { BuilderTreeItem } from './views/builder-tree-item.js'; import { BuilderFileTreeItem } from './views/builder-file-tree-item.js'; @@ -353,7 +354,10 @@ export async function activate(context: vscode.ExtensionContext) { // Holds the CLI preflight row (#791); it self-refreshes on `onPreflightChange`. const statusProvider = new StatusProvider(connectionManager); // Codev Dev panel tab (#921) — the first real view in #812's codevPanel. + // createTreeView (not registerTreeDataProvider) so we hold the handle and can + // set TreeView.badge — the activity dot the plan calls for while a dev runs. const devServerProvider = new DevServerTreeProvider(connectionManager, terminalManager!); + const devServerView = vscode.window.createTreeView('codev.devServer', { treeDataProvider: devServerProvider }); context.subscriptions.push( buildersView, pullRequestsView, @@ -363,7 +367,7 @@ export async function activate(context: vscode.ExtensionContext) { vscode.window.registerTreeDataProvider('codev.team', teamProvider), vscode.window.registerTreeDataProvider('codev.status', statusProvider), vscode.window.registerTreeDataProvider('codev.placeholder', new PanelPlaceholderProvider()), - vscode.window.registerTreeDataProvider('codev.devServer', devServerProvider), + devServerView, { dispose: () => devServerProvider.dispose() }, ); @@ -397,9 +401,15 @@ export async function activate(context: vscode.ExtensionContext) { } }; const refreshDevSurface = (): void => { - const target = terminalManager?.listDevTerminals()[0]?.builderId ?? null; + const builderId = terminalManager?.listDevTerminals()[0]?.builderId ?? null; + const target = builderId ? formatTargetName(builderId) : null; updateDevChip(target); vscode.commands.executeCommand('setContext', 'codev.devServerRunning', target !== null); + // Activity dot on the Codev Dev tab while a dev runs — visible when the + // user is on another codevPanel tab (plan's tab-badge requirement). + devServerView.badge = target + ? { value: 1, tooltip: `Dev server running for ${target}` } + : undefined; }; context.subscriptions.push( terminalManager.onDidChangeDevTerminals(refreshDevSurface), From 81028944188a81ec02cef04264d573b38205325a Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:50:58 +1000 Subject: [PATCH 28/34] [PIR #921] Normalize dev target name (basename + formatTargetName) --- .../vscode/src/__tests__/dev-server-format.test.ts | 14 +++++++++++++- packages/vscode/src/commands/run-worktree-dev.ts | 7 ++++++- packages/vscode/src/views/dev-server-format.ts | 11 +++++++++++ packages/vscode/src/views/dev-server.ts | 6 +++--- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/vscode/src/__tests__/dev-server-format.test.ts b/packages/vscode/src/__tests__/dev-server-format.test.ts index c5b83eafc..628d15422 100644 --- a/packages/vscode/src/__tests__/dev-server-format.test.ts +++ b/packages/vscode/src/__tests__/dev-server-format.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect } from 'vitest'; -import { formatUptime, extractDevPort } from '../views/dev-server-format.js'; +import { formatUptime, extractDevPort, formatTargetName } from '../views/dev-server-format.js'; describe('formatUptime', () => { it('renders sub-minute durations as seconds', () => { @@ -61,3 +61,15 @@ describe('extractDevPort', () => { expect(extractDevPort({ devCommand: 'run --port 99999' })).toBeNull(); }); }); + +describe('formatTargetName', () => { + it('passes through already-friendly ids', () => { + expect(formatTargetName('main')).toBe('main'); + expect(formatTargetName('pir-809')).toBe('pir-809'); + }); + + it('strips the canonical builder- role prefix', () => { + expect(formatTargetName('builder-pir-809')).toBe('pir-809'); + expect(formatTargetName('builder-spir-42')).toBe('spir-42'); + }); +}); diff --git a/packages/vscode/src/commands/run-worktree-dev.ts b/packages/vscode/src/commands/run-worktree-dev.ts index 9a9d5098e..5f2d251ec 100644 --- a/packages/vscode/src/commands/run-worktree-dev.ts +++ b/packages/vscode/src/commands/run-worktree-dev.ts @@ -8,6 +8,7 @@ */ import * as vscode from 'vscode'; +import * as path from 'node:path'; import { resolveAgentName } from '@cluesmith/codev-core/agent-names'; import type { ConnectionManager } from '../connection-manager.js'; import type { TerminalManager } from '../terminal-manager.js'; @@ -55,7 +56,11 @@ export async function runWorktreeDev( const builderName = namedBuilder?.name ?? builder.id; await startDevForTarget(connectionManager, terminalManager, { - id: builder.id, + // Key the dev slot on the worktree basename (e.g. `pir-809`), not the raw + // overview id (which can be the numeric status.yaml id like `921`), so the + // dev surfaces (#921) show a friendly target and the id matches the + // afx-dev / Workspace-view / Switch-Target convention. + id: path.basename(builder.worktreePath), cwd: builder.worktreePath, name: builderName, }); diff --git a/packages/vscode/src/views/dev-server-format.ts b/packages/vscode/src/views/dev-server-format.ts index cd4b42159..38041111e 100644 --- a/packages/vscode/src/views/dev-server-format.ts +++ b/packages/vscode/src/views/dev-server-format.ts @@ -23,6 +23,17 @@ export function formatUptime(ms: number): string { return `${seconds}s`; } +/** + * Friendly display name for a dev target id shown in the chip and status header. + * The dev-slot id is normally already friendly (`main`, or a worktree basename + * like `pir-809`), but a canonical role id (`builder-pir-809`) can reach here; + * strip that prefix so the surface never shows `builder-…`. Anything else passes + * through unchanged. + */ +export function formatTargetName(builderId: string): string { + return builderId.startsWith('builder-') ? builderId.slice('builder-'.length) : builderId; +} + /** Minimal view of the worktree config this module reads — see ResolvedWorktreeConfig. */ export interface DevPortSource { devCommand?: string | null; diff --git a/packages/vscode/src/views/dev-server.ts b/packages/vscode/src/views/dev-server.ts index 33c85d3f4..da1d4e044 100644 --- a/packages/vscode/src/views/dev-server.ts +++ b/packages/vscode/src/views/dev-server.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import type { ConnectionManager } from '../connection-manager.js'; import type { TerminalManager } from '../terminal-manager.js'; import { loadWorktreeConfig } from '../load-worktree-config.js'; -import { formatUptime, extractDevPort } from './dev-server-format.js'; +import { formatUptime, extractDevPort, formatTargetName } from './dev-server-format.js'; /** * The "Codev Dev" panel tab (#921). A status surface for the single `afx dev` @@ -49,7 +49,7 @@ export class DevServerTreeProvider implements vscode.TreeDataProvider` or the Workspace view.', 'info')]; } From 2c78356ba3594d2aad7d57246a7b7a3336196176 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:50:58 +1000 Subject: [PATCH 29/34] [PIR #921] Record consultation findings + dispositions in review --- codev/reviews/921-vscode-codev-dev-surface-botto.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/codev/reviews/921-vscode-codev-dev-surface-botto.md b/codev/reviews/921-vscode-codev-dev-surface-botto.md index 9ac70d35c..833d70756 100644 --- a/codev/reviews/921-vscode-codev-dev-surface-botto.md +++ b/codev/reviews/921-vscode-codev-dev-surface-botto.md @@ -40,7 +40,7 @@ Adds two complementary VSCode surfaces for the single `afx dev` PTY so a reviewe - `pnpm check-types`: ✓ pass - `pnpm lint`: ✓ pass - `node esbuild.js` (bundle): ✓ pass -- `pnpm test:unit`: ✓ pass (311 tests, ~11 new across `dev-server-format` and `contributes-dev-server`) +- `pnpm test:unit`: ✓ pass (315 tests, ~17 new across `dev-server-format`, `contributes-dev-server`, and `terminal-manager` — includes the consultation-fix regression guards) - Manual verification: performed by the reviewer at the `dev-approval` gate against the running worktree — exercised the chip + panel tab, the Show/Hide sidebar toggle, and iterated on the chip icon (`$(zap)` → `$(server-process)`, since `$(zap)` reads as AI) and the toggle's label/command-id naming. ## Architecture Updates @@ -55,6 +55,15 @@ Added two UI/UX entries to `codev/resources/lessons-learned.md`: The two-command `when`-clause toggle pattern used for Show/Hide sidebar was already captured under #952, so it isn't duplicated. +## 3-Way Consultation (single advisory pass) — findings and dispositions + +PIR runs the consultation once (`max_iterations: 1`) with no automated re-review, so each finding below is dispositioned here for the human at the `pr` gate. Verdicts: **Gemini REQUEST_CHANGES**, **Codex REQUEST_CHANGES**, **Claude APPROVE**. Full outputs in `codev/projects/921-*/921-review-iter1-*.txt`. + +- **[FIXED — real bug] Manual terminal-close left the surfaces stale (Codex, blocker).** Closing the dev terminal via its tab ✕ (or the dev process exiting) reached only the generic `onDidCloseTerminal` path, which unmapped the terminal but never cleared `devStartedAt` or re-fired `onDidChangeDevTerminals` — so the chip / tab / `codev.devServerRunning` stayed "running." This broke the "dev stops via terminal exit → chip disappears" acceptance criterion. Fixed in `terminal-manager.ts` (fire + clear on the generic path, guarded by `wasTracked` to avoid double-firing with the explicit Stop path). Regression guard added in `terminal-manager.test.ts` (source-level, per that file's documented harness constraint). +- **[FIXED — real plan gap] Missing tab badge (Gemini + Codex + Claude).** The plan called for a `TreeView.badge`; the view was registered via `registerTreeDataProvider`, which yields no handle to set `.badge`. Switched to `createTreeView('codev.devServer', …)` and set `devServerView.badge` while a dev runs (cleared on stop). Contributes test updated. +- **[FIXED — real plan gap] Target name not normalized (Codex).** `OverviewBuilder.id` can be the numeric `status.yaml` id (e.g. `921`), so the Builders-row dev path (`run-worktree-dev`) could render `Dev: 921`. Root-fixed by keying that path on the worktree basename (`pir-921`), matching the afx-dev / Workspace / Switch-Target convention; also added the plan's promised `formatTargetName` pure helper (strips a `builder-` role prefix) with tests, applied in the chip and the tab. +- **[REBUTTED — not a defect] "Reveal in Workspace View" replaced by Show/Hide Sidebar (Codex).** This was an explicit human directive at the `dev-approval` gate (the reviewer asked to make it a toggle and rename it); the plan predates that feedback. Claude classified it as a legitimate iterated deviation. No change warranted. + ## Things to Look At During PR Review - **Chip tint (deliberate deviation from the plan's literal wording).** The plan/issue said `prominentBackground`; the VSCode API only honors error/warning *backgrounds*, so I used `color = statusBarItem.prominentForeground` instead. If a stronger cue is wanted, `warningBackground` is the only API-honored background (the issue called it too alarming). See `extension.ts` `updateDevChip`. From 5eda5dcd16bce03fd1ba902faa671ee23fdae87f Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:51:13 +1000 Subject: [PATCH 30/34] [PIR #921] Update builder thread (consultation fixes) --- codev/state/pir-921_thread.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/codev/state/pir-921_thread.md b/codev/state/pir-921_thread.md index e353b42d0..7bbeb4c80 100644 --- a/codev/state/pir-921_thread.md +++ b/codev/state/pir-921_thread.md @@ -87,3 +87,12 @@ Build/lint/types ✓, 311 unit tests ✓. Gate still dev-approval (no porch done - PR #996 opened (body = review file, Fixes #921). Recorded with porch. - porch structural checks ✓ (pr_exists, review_has_arch_updates, review_has_lessons_updates). - 3-way consultation (gemini/codex/claude, type=impl) running in background — single advisory pass (max_iterations:1). Next: read verdicts, handle any REQUEST_CHANGES (fix+regression test or rebut; escalate either way), notify architect, wait at pr gate. + +## Consultation results (single pass) + fixes + +Verdicts: gemini=REQUEST_CHANGES, codex=REQUEST_CHANGES, claude=APPROVE. +- FIXED (real bug, codex blocker): manual dev-terminal close left chip/tab/context stale — generic onDidCloseTerminal now clears devStartedAt + fires onDidChangeDevTerminals for dev terminals (guarded by wasTracked vs double-fire). Regression guard in terminal-manager.test.ts. +- FIXED (plan gap, all 3): tab badge — switched codev.devServer to createTreeView, set devServerView.badge while running. +- FIXED (plan gap, codex): target name — OverviewBuilder.id can be numeric (status.yaml id) → run-worktree-dev now keys on worktree basename; added formatTargetName helper (strips builder- prefix) + tests, applied in chip + tab. +- REBUTTED (codex): reveal→Show/Hide toggle was the human's explicit dev-approval directive; plan predates it (Claude agreed it's a legit deviation). +Build/lint/types ✓, 315 unit tests ✓ (+4). Dispositions recorded in review "3-Way Consultation" section. Next: porch next (record verdicts, fire pr gate), notify architect leading with REQUEST_CHANGES+dispositions, wait at pr gate. From a848248e21e9eba2582869d46eceb23b5e760d85 Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:51:58 +1000 Subject: [PATCH 31/34] chore(porch): 921 pr gate-requested --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 28b61877c..1a5bb93fb 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -15,13 +15,15 @@ gates: approved_at: '2026-06-05T10:38:40.588Z' pr: status: pending + requested_at: '2026-06-05T10:51:58.152Z' iteration: 1 build_complete: true history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:41:51.120Z' +updated_at: '2026-06-05T10:51:58.155Z' pr_history: - phase: review pr_number: 996 branch: builder/pir-921 created_at: '2026-06-05T10:41:44.212Z' +pr_ready_for_human: true From 87805f6c2a13fff76c6262738817195519eab52a Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:54:30 +1000 Subject: [PATCH 32/34] chore(porch): 921 pr gate-approved --- .../921-vscode-codev-dev-surface-botto/status.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 1a5bb93fb..192b2fe72 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -14,16 +14,17 @@ gates: requested_at: '2026-06-05T09:26:47.546Z' approved_at: '2026-06-05T10:38:40.588Z' pr: - status: pending + status: approved requested_at: '2026-06-05T10:51:58.152Z' + approved_at: '2026-06-05T10:54:29.963Z' iteration: 1 build_complete: true history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:51:58.155Z' +updated_at: '2026-06-05T10:54:29.965Z' pr_history: - phase: review pr_number: 996 branch: builder/pir-921 created_at: '2026-06-05T10:41:44.212Z' -pr_ready_for_human: true +pr_ready_for_human: false From fb8b122ac4b0687c834768bae80907ae565a762c Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:54:41 +1000 Subject: [PATCH 33/34] chore(porch): 921 protocol complete --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index 192b2fe72..e195c19e3 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -1,7 +1,7 @@ id: '921' title: vscode-codev-dev-surface-botto protocol: pir -phase: review +phase: verified plan_phases: [] current_plan_phase: null gates: @@ -21,7 +21,7 @@ iteration: 1 build_complete: true history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:54:29.965Z' +updated_at: '2026-06-05T10:54:40.985Z' pr_history: - phase: review pr_number: 996 From 61360723f2215273fe7dc1144c5c2711ed895d0f Mon Sep 17 00:00:00 2001 From: Amr Elsayed Date: Fri, 5 Jun 2026 20:54:52 +1000 Subject: [PATCH 34/34] chore(porch): 921 PR #996 merged --- codev/projects/921-vscode-codev-dev-surface-botto/status.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml index e195c19e3..6b1986138 100644 --- a/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml +++ b/codev/projects/921-vscode-codev-dev-surface-botto/status.yaml @@ -21,10 +21,12 @@ iteration: 1 build_complete: true history: [] started_at: '2026-06-03T21:40:33.254Z' -updated_at: '2026-06-05T10:54:40.985Z' +updated_at: '2026-06-05T10:54:52.060Z' pr_history: - phase: review pr_number: 996 branch: builder/pir-921 created_at: '2026-06-05T10:41:44.212Z' + merged: true + merged_at: '2026-06-05T10:54:52.059Z' pr_ready_for_human: false