Skip to content

feat(workspaces): IPC API and extension-side data layer for the panel - #1099

Open
EhabY wants to merge 2 commits into
mainfrom
ehab/devex-622-workspaces-ipc-api-extension-side-provider-with-data-layer
Open

feat(workspaces): IPC API and extension-side data layer for the panel#1099
EhabY wants to merge 2 commits into
mainfrom
ehab/devex-622-workspaces-ipc-api-extension-side-provider-with-data-layer

Conversation

@EhabY

@EhabY EhabY commented Aug 26, 2026

Copy link
Copy Markdown
Collaborator

Closes DEVEX-622.

Extension side of the new Workspaces view, behind coder.experimental.workspacesPanel. No UI yet: the panel prints the pushed state so the data flow can be verified, and the tree component library lands separately.

The contract

packages/shared/src/workspaces/ holds the typed API both sides import: types.ts for every type, api.ts for the messages.

Direction Messages
extension to webview stateUpdated
webview to extension ready, openWorkspace, viewInDashboard, refresh, setFilter, watchAgents

One notification carries only the fields that changed, so two changes land as one message and one render. Payloads carry decisions, not facts to derive:

  • capabilities lists the filters the user may select, so the webview applies no owner or deployment-support rules
  • the list carries loading, true only for fetches someone waits on (first list, filter switch, deliberate refresh), never for a poll, so nothing flickers
  • agents carry loading too, so an expanded row can tell "waiting on the socket" from "this agent reports no metadata"

ready replays the whole state, so a webview that just loaded (or reloaded) needs no data of its own.

The data layer

WorkspaceStore lists the active filter while visible and reports what changed. One update() records the change, diffs the state structurally, and pushes only the fields the webview lacks. Ported from the tree views: polling stops while hidden, exponential backoff, per-agent metadata over SSE, owner gating, and the HTTP 400 signal that a deployment predates a filter's query. Differences, all in the data layer: a CancellationTokenSource per fetch replaces the tree's pending-fetch bookkeeping, sockets open only for agents the webview reports as showing (and linger briefly after release, so re-expanding is instant), only cheap filters keep polling, and the list is pushed before sockets open.

Failures each have one owner: a failed fetch clears the list and retries with backoff, a rejected query becomes an unavailable filter, a metadata socket reports against its agent only, and user actions surface in a dialog. load cannot reject into a timer callback.

Split by concern: agentMetadataTracker.ts owns the watched set and its sockets, filters.ts owns each filter's query, role requirement and poll policy (shared with the tree views), and isOwner(user) moved to api-helper.ts for both deploymentManager and the panel.

Size

~950 lines of production code (panel, store, tracker, shared contract, wiring) and ~1350 of tests and mocks.

Notes for review

  • Pushes typed domain data (Workspace[], metadata maps) rather than tree nodes, same as the Tasks panel's Task[]: label, status and sort decisions stay in the webview.
  • When a deployment rejects the shared query, the filter disappears from the picker instead of showing the tree's "requires Coder 2.27.0 or newer" notice. Bringing that copy back belongs in a payload the UI ticket designs.
  • No deployment-change listener in extension.ts: every session change (same window or cross window) flows through the session store, which WorkspaceStore already listens to.
  • The mock WebviewView now lives in test/mocks/testHelpers.ts as createMockWebviewView, shared with the Tasks panel test.

@linear-code

linear-code Bot commented Aug 26, 2026

Copy link
Copy Markdown

DEVEX-622

@EhabY
EhabY force-pushed the ehab/devex-622-workspaces-ipc-api-extension-side-provider-with-data-layer branch 11 times, most recently from 8b0ad5e to a17482e Compare August 26, 2026 20:27
Adds the typed IPC contract for the experimental Workspaces panel and the
extension-side provider that owns its data, porting the tree views'
behaviors to push through IPC.

`packages/shared/src/workspaces` defines the contract: `stateUpdated` out;
`ready`, `openWorkspace`, `viewInDashboard`, `refresh`, `setFilter` and
`watchAgents` back. State is pushed as one update carrying only the fields
that changed, and the payloads carry decisions rather than facts to derive,
so the webview holds no data and applies no policy: it asks for the state
with `ready` and renders what arrives.

`WorkspaceStore` lists the active filter while visible, backs off on
failures, watches metadata for the agents the panel is showing, and reports
what changed. A cancellation token per fetch drops superseded results, the
list is pushed before sockets open, and a structural diff keeps quiet polls
off the wire. Filters that a deployment rejects stop being offered.

Split by concern, in their own layers:

- `src/workspace/agentMetadataTracker.ts`: the watched set and its sockets,
  which linger briefly after release so toggling a row reuses them
- `src/workspace/filters.ts`: each filter's query, presentation, role
  requirement and poll policy, shared with the tree views instead of
  duplicated

`isOwner(user)` moved to `src/api/api-helper.ts` for both `deploymentManager`
and the panel, since the `coder.isOwner` context is written after the session
change fires.

Existing tree views are untouched. The webview's placeholder App prints the
pushed state; the UI lands with the tree components.
@EhabY
EhabY force-pushed the ehab/devex-622-workspaces-ipc-api-extension-side-provider-with-data-layer branch from a17482e to 5ae7bef Compare August 26, 2026 20:36
…ew mock

- Drop WorkspacesPanelProvider.refresh() and its
  onDidChangeCurrentDeployment listener: every path that event covers
  already flows through the session store, which the store listens to
- Collapse the options-bag constructor into Object.assign so handlers
  keep reading this.store etc. directly
- Replace AgentMetadataTracker's single-knob options object with a
  lingerMs parameter
- Remove the dead disposed guard in handleSessionChange; the session
  subscription is disposed synchronously
- Promote the mock WebviewView into testHelpers.createMockWebviewView
  and reuse it from the tasks and workspaces test harnesses
@EhabY
EhabY marked this pull request as ready for review August 27, 2026 18:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant