Skip to content

Conversation

@EhabY
Copy link
Collaborator

@EhabY EhabY commented Jan 28, 2026

Summary

Adds a React-based webview system to the extension using pnpm workspaces. This provides the foundation for building rich UI panels (like the Tasks panel) with React while maintaining type safety between the extension and webviews.

Architecture

packages/
├── shared/                      # Shared types and React utilities
│   ├── src/
│   │   ├── index.ts             # WebviewMessage type
│   │   └── react/               # VS Code webview hooks
│   │       ├── api.ts           # postMessage, getState, setState
│   │       └── hooks.ts         # useMessage, useVsCodeState
│   ├── extension.d.ts           # Types exposed to extension (excludes react/)
│   └── vite.config.base.ts      # Shared Vite config factory
└── tasks/                       # Tasks panel webview (placeholder)
    └── src/
        ├── index.tsx            # Entry point
        └── App.tsx              # Root component

src/webviews/
├── util.ts                      # getWebviewHtml() with CSP
└── tasks/
    └── TasksPanel.ts            # WebviewViewProvider

Key Features

  • Type-safe message passing: WebviewMessage type shared between extension and webviews
  • React hooks: useMessage() for receiving messages, useVsCodeState() for persisted state
  • Secure CSP: Nonce-based Content Security Policy for script execution
  • Type isolation: Extension can only import types from @coder/shared (not React code) via extension.d.ts
  • Proper lifecycle management: Disposable pattern for event listeners, visibility change handling

Changes

  • Add pnpm workspace configuration
  • Add @coder/shared package with types and React hooks
  • Add @coder/tasks-webview package (placeholder Tasks panel)
  • Add src/webviews/util.ts with getWebviewHtml() helper
  • Add src/webviews/tasks/TasksPanel.ts WebviewViewProvider
  • Register Tasks panel in package.json (visible when authenticated + devMode)
  • Add ESLint rules for React
  • Add webviews documentation to CONTRIBUTING.md

Introduce a webview architecture for building rich UI panels using React 19,
Vite, and @vscode-elements/react-elements. Webviews are organized as pnpm
workspace packages under packages/.

Architecture:
- packages/shared: Shared types and React hooks for VS Code API
- packages/tasks: Example Tasks panel webview
- src/webviews/: Extension-side WebviewViewProvider implementations
- vite.config.base.ts: Shared Vite config factory for webviews

Key features:
- Type-safe message passing between extension and webviews
- CSP-compliant HTML generation with nonce-based script loading
- Vite with SWC for fast development builds
- Webview watch via `pnpm dev:webviews`

Build commands:
- `pnpm build` - Build webviews + extension
- `pnpm watch` - Watch extension
- `pnpm dev:webviews` - Rebuild webviews on change
@EhabY EhabY self-assigned this Jan 28, 2026
@EhabY EhabY force-pushed the add-webview-architecture branch from 7cc1aaf to 487407b Compare January 29, 2026 10:52
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, looks like a good base for webviews.

Comment on lines +15 to +25
export function postMessage(message: WebviewMessage): void {
getVsCodeApi().postMessage(message);
}

export function getState<T>(): T | undefined {
return getVsCodeApi().getState() as T | undefined;
}

export function setState<T>(state: T): void {
getVsCodeApi().setState(state);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not seen how this is used yet but do we need the wrappers? Seems like callers could grab the API via getVsCodeApi() and then call whatever they need, without having to duplicate everything.

Not a big deal either way.

If we keep these wrappers though there is probably no need to export getVsCodeApi() (it seems to be unused externally right now). Maybe the wrappers are better actually because it could be confusing why there is both a getVsCodeApi() and an acquireVsCodeApi() with such similar names.

// Singleton - acquireVsCodeApi can only be called once
let vscodeApi: WebviewApi<unknown> | undefined;

declare function acquireVsCodeApi(): WebviewApi<unknown>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we throw a comment on here saying VS Code provides this inside webviews?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually do we need it? I removed it and still get completion and docs for acquireVsCodeApi, but maybe something is just cached.

@@ -0,0 +1,25 @@
import type { WebviewApi } from "vscode-webview";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bit of a nit, but this is in react yet has no React-related code. It feels more webview related than React related to me.

Also instead of calling this @coder/shared what do you think about calling it @coder/webview?


import type { WebviewMessage } from "../index";

// Singleton - acquireVsCodeApi can only be called once
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we expand on this a little, say something like trying to acquire the API more than once will throw an error.

Comment on lines +31 to +32
// No sourcemaps in production (not needed in packaged extension)
sourcemap: !production,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be nice to have sourcemaps for stack traces.

Comment on lines +97 to +98
pnpm watch # Rebuild extension on changes
pnpm dev:webviews # Rebuild webviews on changes (run in separate terminal)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah bummer is there no way to watch everything with one command?

},
},
rules: {
// Only add React-specific rules, TS rules already applied via **/*.ts config above
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

**/*.ts would not match .tsx would it? Or wait does it?

"build": "pnpm build:webviews && tsc --noEmit && node esbuild.mjs",
"build:production": "NODE_ENV=production pnpm build:webviews && tsc --noEmit && node esbuild.mjs --production",
"build:webviews": "pnpm -r --filter \"./packages/*\" build",
"dev:webviews": "pnpm -r --filter \"./packages/*\" --parallel dev",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For parity should this be watch:webviews?

vscode.Uri.joinPath(baseUri, "index.js"),
);
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(baseUri, "index.css"),
Copy link
Member

@code-asher code-asher Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tasks example has no stylesheet, should we add a blank one for now?

"tasks",
);

// Re-initialize when visibility changes (webview may have been restored)
Copy link
Member

@code-asher code-asher Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the webview is restored does the HTML not get loaded again? Which means it would send ready and we would send back init and would not need this block. Actually having this block could mean we would send init before the plugin said it was ready.

But, if the HTML does stay loaded when the webview is not visible, maybe we should send a visible event instead, so the plugin can tell the difference between a fresh load and being toggled.

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.

2 participants