Skip to content

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

Draft
EhabY wants to merge 1 commit into
mainfrom
ehab/devex-622-workspaces-ipc-api-extension-side-provider-with-data-layer
Draft

feat(workspaces): IPC API and extension-side data layer for the panel#1099
EhabY wants to merge 1 commit 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 components: 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 a change to two of them lands 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, and a filter that cannot load is never offered
  • the list carries loading, true only for fetches someone waits on (a first list, a filter switch, a deliberate refresh) and 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 is the one addition to the ticket's list: without it the first push races the webview's script load. It replays the whole state, so a webview that 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, so quiet polls cost nothing downstream. Ported from the tree views: polling stopped while hidden, exponential backoff, per-agent metadata over SSE, owner gating, and the HTTP 400 signal that a deployment predates a filter's query.

What it does differently, all in the data layer:

  • a CancellationTokenSource per fetch drops results of superseded fetches, replacing the tree's fetching/refetchPending/session-identity bookkeeping
  • metadata is watched only for agents the webview says it is showing, so a collapsed tree costs zero sockets where the tree opens one per agent
  • a released socket lingers briefly, so toggling a row (or hiding the view for a moment) reuses it and shows its last report at once
  • only filters marked poll keep polling; Shared and All list on demand, keeping expensive queries off the timer
  • the list is pushed before sockets open, so a slow socket cannot hold it back
  • a filter the deployment rejects is dropped from the offered set and falls back, instead of polling an endpoint that answers 400

Failures each have one owner and one visible outcome: a failed fetch clears the list, reports the message and retries with backoff; a rejected query becomes an unavailable filter, not an error; a metadata socket reports against its agent and never reads as a failure to list; user actions report in a dialog. Anything unexpected on the listing path is caught in one place, so load cannot reject into a timer callback.

Split by concern: src/workspace/agentMetadataTracker.ts owns the watched set and its sockets, and src/workspace/filters.ts owns each filter's query, presentation, role requirement and poll policy, shared with the tree views instead of duplicated (the only change to workspacesProvider.ts). 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.

Where the lines go

Area Lines
webviews/workspaces/store.ts 328 state, fetch loop, cancellation, polling, filters
webviews/workspaces/panelProvider.ts 223 IPC wiring and the two actions
workspace/agentMetadataTracker.ts 191 watched set, sockets, lingering
shared/workspaces/{types,api}.ts 100 the contract
workspace/filters.ts 63 per-filter policy
webview hook, App, wiring 52 useWorkspaces, placeholder, extension.ts
production 978
webviews/workspaces/*.test.ts + harness 874 store and panel behavior
workspace/{agentMetadataTracker,filters}.test.ts 295 the two units directly
webview/workspaces, api/api-helper, mocks 169 hook merge semantics, isOwner, factories
tests 1338

Full suite: 2538 passed. Typecheck, lint, format and build clean. Existing tree views untouched and functional.

Notes for review

  • Workspaces and agent metadata are pushed as typed domain data rather than generic tree nodes. Nodes would move label, status and sort decisions into the extension, where every UI tweak needs an extension change, and would cost the typed action params; the Tasks panel pushes Task[] for the same reason.
  • When a deployment rejects the shared query, the filter disappears from the picker rather than showing the tree's "requires Coder 2.27.0 or newer" notice. If we want that copy back it belongs in a payload the UI ticket designs.
  • The view keeps retainContextWhenHidden, and there is no visibility or theme re-send: the DOM survives hiding, useVscodeTheme in @repo/ui already re-renders on theme changes, and a discarded webview re-hydrates through ready.
  • openWorkspace reports telemetry as sidebar_workspace/sidebar_agent, so panel opens are not distinguishable from tree opens. A distinct WorkspaceOpenSource felt outside this ticket.
  • Files here drop the redundant folder prefix (webviews/workspaces/store.ts, workspace/filters.ts), which diverges from tasks, netcheck and speedtest. Happy to rename those in a separate pass.
  • test/unit/webviews/workspaces/harness.ts builds a mock WebviewView, as the Tasks panel test does inline. Worth promoting to test/mocks/testHelpers.ts next to createMockWebviewPanel, but that means touching the Tasks test, so I left it.

@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
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