packages/webui/docs/WEBUI_MIGRATION_PLAN_EN.md
packages/vscode-ide-companion is a VSCode extension whose core content is a WebView page with UI components provided by React. As the product line expands, more scenarios require building products with Web UI:
For excellent software engineering architecture, we need to unify and reuse UI components across products.
vscode-ide-companion/src/webview/ into an independent @qwen-code/webui packagepackages/vscode-ide-companion/src/webview/ contains 77 files:
webview/
├── App.tsx # Main entry
├── components/
│ ├── icons/ # 8 icon components
│ ├── layout/ # 8 layout components
│ │ ├── ChatHeader.tsx
│ │ ├── InputForm.tsx
│ │ ├── SessionSelector.tsx
│ │ ├── EmptyState.tsx
│ │ ├── Onboarding.tsx
│ │ └── ...
│ ├── messages/ # Message display components
│ │ ├── UserMessage.tsx
│ │ ├── Assistant/
│ │ ├── MarkdownRenderer/
│ │ ├── ThinkingMessage.tsx
│ │ ├── Waiting/
│ │ └── toolcalls/ # 16 tool call components
│ ├── PermissionDrawer/ # Permission request drawer
│ └── Tooltip.tsx
├── hooks/ # Custom hooks
├── handlers/ # Message handlers
├── styles/ # CSS styles
└── utils/ # Utility functions
Platform Coupling Points:
useVSCode hook - Calls acquireVsCodeApi() for message communicationhandlers/ - Handles VSCode message protocol../types/ directory┌─────────────────────────────────────────────────────────┐
│ App.tsx (Entry) │
├─────────────────────────────────────────────────────────┤
│ hooks/ │ handlers/ │ components/ │
│ ├─useVSCode ◄───┼──────────────────┼──────────────────┤
│ ├─useSession │ ├─MessageRouter │ ├─icons/ │
│ ├─useFileContext│ ├─AuthHandler │ ├─layout/ │
│ └─... │ └─... │ ├─messages/ │
│ │ │ └─PermDrawer/ │
├─────────────────────────────────────────────────────────┤
│ VSCode API (acquireVsCodeApi) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ Layer 3: Platform Adapters │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │VSCode Adapter│ │Chrome Adapter│ │ Web Adapter │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
├─────────┼────────────────┼────────────────┼────────────┤
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ Platform Context Provider │ │
│ └─────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 2: Chat Components │
│ ┌────────────┐ ┌────────────┐ ┌────────────┐ │
│ │ MessageList│ │ ChatHeader │ │ InputForm │ │
│ └────────────┘ └────────────┘ └────────────┘ │
├─────────────────────────────────────────────────────────┤
│ Layer 1: Primitives (Pure UI) │
│ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ Button │ │ Input │ │ Icons │ │Tooltip │ │
│ └────────┘ └────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────────┘
// @qwen-code/webui/src/context/PlatformContext.ts
interface PlatformContext {
// Message communication
postMessage: (message: unknown) => void;
onMessage: (handler: (message: unknown) => void) => () => void;
// File operations
openFile?: (path: string) => void;
attachFile?: () => void;
// Authentication
login?: () => void;
// Platform info
platform: 'vscode' | 'chrome' | 'web' | 'share';
}
Output formats:
dist/index.js) - Primary formatdist/index.cjs) - Compatibilitydist/index.d.ts)// vite.config.ts
export default defineConfig({
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'js' : 'cjs'}`,
},
rollupOptions: {
external: ['react', 'react-dom'],
},
},
});
// @qwen-code/webui/tailwind.preset.js
module.exports = {
theme: {
extend: {
colors: {
'app-primary': 'var(--app-primary)',
'app-background': 'var(--app-primary-background)',
'app-foreground': 'var(--app-primary-foreground)',
},
},
},
};
// Consumer's tailwind.config.js
module.exports = {
presets: [require('@qwen-code/webui/tailwind.preset')],
content: [
'./src/**/*.{ts,tsx}',
'./node_modules/@qwen-code/webui/dist/**/*.js',
],
};
packages/webui/
├── .storybook/
│ ├── main.ts # Storybook config
│ ├── preview.ts # Global decorators
│ └── manager.ts # UI config
└── src/
└── stories/ # Story files
| Component | Source Path | Complexity | Notes |
|---|---|---|---|
| Icons | components/icons/ | Low | 8 icon components, pure SVG |
| Tooltip | components/Tooltip.tsx | Low | Pure UI |
| WaitingMessage | messages/Waiting/ | Low | Loading state display |
| InterruptedMessage | messages/Waiting/ | Low | Interrupted state display |
| Component | Source Path | Dependency | Refactoring |
|---|---|---|---|
| UserMessage | messages/UserMessage.tsx | onFileClick | Props injection |
| AssistantMessage | messages/Assistant/ | onFileClick | Props injection |
| ThinkingMessage | messages/ThinkingMessage.tsx | onFileClick | Props injection |
| MarkdownRenderer | messages/MarkdownRenderer/ | None | Direct migration |
| EmptyState | layout/EmptyState.tsx | None | Direct migration |
| ChatHeader | layout/ChatHeader.tsx | callbacks | Props injection |
| Component | Source Path | Dependency | Refactoring |
|---|---|---|---|
| InputForm | layout/InputForm.tsx | Multiple callbacks | Context + Props |
| SessionSelector | layout/SessionSelector.tsx | session data | Props injection |
| CompletionMenu | layout/CompletionMenu.tsx | items data | Props injection |
| PermissionDrawer | PermissionDrawer/ | callbacks | Context + Props |
| ToolCall components | messages/toolcalls/ | Various tool displays | Modular migration |
| Component/Module | Notes |
|---|---|
| App.tsx | Main entry, contains business orchestration logic |
| hooks/ | Most require platform adaptation |
| handlers/ | VSCode message handling |
| Onboarding | Authentication related, platform-specific |
Developer ──► @qwen-code/webui ──► vscode-ide-companion
│ │ │
│ 1. Copy component to webui │
│ 2. Add Story for verification │
│ 3. Export from index.ts │
│ │ │
│ └──────────────────────┤
│ │
│ 4. Update import path
│ 5. Delete original component
│ 6. Build and test
// Before: vscode-ide-companion/src/webview/components/icons/index.ts
export { FileIcon } from './FileIcons.js';
// After: Update import
import { FileIcon } from '@qwen-code/webui';
// or import { FileIcon } from '@qwen-code/webui/icons';
Tailwind Class Name Tree Shaking
content config needs to include node_modules/@qwen-code/webuiCSS Variable Scope
var(--app-primary) need to be defined by consumersReact Version Compatibility
"react": "^18.0.0 || ^19.0.0"ESM/CJS Compatibility
Each migration task completion requires:
npm run build:vscode)| Phase | Tasks | Estimated Days | Parallelizable |
|---|---|---|---|
| Phase 0 | 5 | 2-3 days | Partially |
| Phase 1 | 4 | 1-2 days | Fully |
| Phase 2 | 4 | 2-3 days | Fully |
| Phase 3 | 5 | 3-4 days | Partially |
| Phase 4 | 2 | 3-4 days | Yes |
| Phase 5 | 3 | 2-3 days | Yes |
Total: Approximately 13-19 person-days (sequential execution), can be reduced to 1-2 weeks with parallel work
┌─────────────────────────────────────────────────────────────────┐
│ Development Workflow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Develop/Modify Component │
│ └── Edit files in @qwen-code/webui/src/ │
│ │
│ 2. Debug with Storybook │
│ └── npm run storybook (port 6006) │
│ └── View component in isolation │
│ └── Test different props/states │
│ │
│ 3. Build Library │
│ └── npm run build │
│ └── Outputs: dist/index.js, dist/index.cjs, dist/index.d.ts │
│ │
│ 4. Use in VSCode Extension │
│ └── import { Component } from '@qwen-code/webui' │
│ └── No UI code modifications in vscode-ide-companion │
│ │
└─────────────────────────────────────────────────────────────────┘
# Start Storybook for component development
cd packages/webui
npm run storybook
# Watch mode for library development
npm run dev
# Build library for production
npm run build
# Type checking
npm run typecheck
@qwen-code/webuivscode-ide-companion only imports and uses componentsPlatformContext for platform-specific behaviors