Skip to content

Commit d78da19

Browse files
authored
Merge pull request RooCodeInc#664 from RooVetGit/cte/shadcn-ui-storybook
Add shadcn/ui + Storybook
2 parents bedd6fd + eefb721 commit d78da19

29 files changed

+12971
-11024
lines changed

.vscode/extensions.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
"recommendations": [
55
"dbaeumer.vscode-eslint",
66
"connor4312.esbuild-problem-matchers",
7-
"ms-vscode.extension-test-runner"
7+
"ms-vscode.extension-test-runner",
8+
"csstools.postcss",
9+
"bradlc.vscode-tailwindcss",
10+
"tobermory.es6-string-html"
811
]
912
}

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,9 +255,15 @@ Roo Code is available on:
255255
```bash
256256
code --install-extension bin/roo-code-4.0.0.vsix
257257
```
258-
5. **Debug**:
258+
5. **Start the webview (Vite/React app with HMR)**:
259+
```bash
260+
npm run dev
261+
```
262+
6. **Debug**:
259263
- Press `F5` (or **Run****Start Debugging**) in VSCode to open a new session with Roo Code loaded.
260264

265+
Changes to the webview will appear immediately. Changes to the core extension will require a restart of the extension host.
266+
261267
We use [changesets](https://github.com/changesets/changesets) for versioning and publishing. Check our `CHANGELOG.md` for release notes.
262268

263269
---

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@
227227
"lint": "eslint src --ext ts && npm run lint --prefix webview-ui",
228228
"package": "npm run build:webview && npm run check-types && npm run lint && node esbuild.js --production",
229229
"pretest": "npm run compile-tests && npm run compile && npm run lint",
230-
"start:webview": "cd webview-ui && npm run start",
230+
"dev": "cd webview-ui && npm run dev",
231231
"test": "jest && npm run test:webview",
232232
"test:webview": "cd webview-ui && npm run test",
233233
"test:extension": "vscode-test",

src/__mocks__/vscode.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,11 @@ const vscode = {
5252
this.id = id
5353
}
5454
},
55+
ExtensionMode: {
56+
Production: 1,
57+
Development: 2,
58+
Test: 3,
59+
},
5560
}
5661

5762
module.exports = vscode

src/core/webview/ClineProvider.ts

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,11 @@ export class ClineProvider implements vscode.WebviewViewProvider {
279279
enableScripts: true,
280280
localResourceRoots: [this.context.extensionUri],
281281
}
282-
webviewView.webview.html = this.getHtmlContent(webviewView.webview)
282+
283+
webviewView.webview.html =
284+
this.context.extensionMode === vscode.ExtensionMode.Development
285+
? this.getHMRHtmlContent(webviewView.webview)
286+
: this.getHtmlContent(webviewView.webview)
283287

284288
// Sets up an event listener to listen for messages passed from the webview view context
285289
// and executes code based on the message that is recieved
@@ -403,6 +407,62 @@ export class ClineProvider implements vscode.WebviewViewProvider {
403407
await this.view?.webview.postMessage(message)
404408
}
405409

410+
private getHMRHtmlContent(webview: vscode.Webview): string {
411+
const nonce = getNonce()
412+
413+
const stylesUri = getUri(webview, this.context.extensionUri, ["webview-ui", "build", "assets", "index.css"])
414+
const codiconsUri = getUri(webview, this.context.extensionUri, [
415+
"node_modules",
416+
"@vscode",
417+
"codicons",
418+
"dist",
419+
"codicon.css",
420+
])
421+
422+
const file = "src/index.tsx"
423+
const localPort = "5173"
424+
const localServerUrl = `localhost:${localPort}`
425+
const scriptUri = `http://${localServerUrl}/${file}`
426+
427+
const reactRefresh = /*html*/ `
428+
<script nonce="${nonce}" type="module">
429+
import RefreshRuntime from "http://localhost:${localPort}/@react-refresh"
430+
RefreshRuntime.injectIntoGlobalHook(window)
431+
window.$RefreshReg$ = () => {}
432+
window.$RefreshSig$ = () => (type) => type
433+
window.__vite_plugin_react_preamble_installed__ = true
434+
</script>
435+
`
436+
437+
const csp = [
438+
"default-src 'none'",
439+
`font-src ${webview.cspSource}`,
440+
`style-src ${webview.cspSource} 'unsafe-inline' https://* http://${localServerUrl} http://0.0.0.0:${localPort}`,
441+
`img-src ${webview.cspSource} data:`,
442+
`script-src 'unsafe-eval' https://* http://${localServerUrl} http://0.0.0.0:${localPort} 'nonce-${nonce}'`,
443+
`connect-src https://* ws://${localServerUrl} ws://0.0.0.0:${localPort} http://${localServerUrl} http://0.0.0.0:${localPort}`,
444+
]
445+
446+
return /*html*/ `
447+
<!DOCTYPE html>
448+
<html lang="en">
449+
<head>
450+
<meta charset="utf-8">
451+
<meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no">
452+
<meta http-equiv="Content-Security-Policy" content="${csp.join("; ")}">
453+
<link rel="stylesheet" type="text/css" href="${stylesUri}">
454+
<link href="${codiconsUri}" rel="stylesheet" />
455+
<title>Roo Code</title>
456+
</head>
457+
<body>
458+
<div id="root"></div>
459+
${reactRefresh}
460+
<script type="module" src="${scriptUri}"></script>
461+
</body>
462+
</html>
463+
`
464+
}
465+
406466
/**
407467
* Defines and returns the HTML that should be rendered within the webview panel.
408468
*
@@ -558,7 +618,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
558618
}
559619
}
560620

561-
let currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string
621+
const currentConfigName = (await this.getGlobalState("currentApiConfigName")) as string
562622

563623
if (currentConfigName) {
564624
if (!(await this.configManager.hasConfig(currentConfigName))) {
@@ -1134,7 +1194,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11341194
if (message.text && message.apiConfiguration) {
11351195
try {
11361196
await this.configManager.saveConfig(message.text, message.apiConfiguration)
1137-
let listApiConfig = await this.configManager.listConfig()
1197+
const listApiConfig = await this.configManager.listConfig()
11381198

11391199
await Promise.all([
11401200
this.updateGlobalState("listApiConfigMeta", listApiConfig),
@@ -1159,7 +1219,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
11591219
await this.configManager.saveConfig(newName, message.apiConfiguration)
11601220
await this.configManager.deleteConfig(oldName)
11611221

1162-
let listApiConfig = await this.configManager.listConfig()
1222+
const listApiConfig = await this.configManager.listConfig()
11631223
const config = listApiConfig?.find((c) => c.name === newName)
11641224

11651225
// Update listApiConfigMeta first to ensure UI has latest data
@@ -1217,7 +1277,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12171277
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
12181278

12191279
// If this was the current config, switch to first available
1220-
let currentApiConfigName = await this.getGlobalState("currentApiConfigName")
1280+
const currentApiConfigName = await this.getGlobalState("currentApiConfigName")
12211281
if (message.text === currentApiConfigName && listApiConfig?.[0]?.name) {
12221282
const apiConfig = await this.configManager.loadConfig(listApiConfig[0].name)
12231283
await Promise.all([
@@ -1237,7 +1297,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12371297
break
12381298
case "getListApiConfiguration":
12391299
try {
1240-
let listApiConfig = await this.configManager.listConfig()
1300+
const listApiConfig = await this.configManager.listConfig()
12411301
await this.updateGlobalState("listApiConfigMeta", listApiConfig)
12421302
this.postMessageToWebview({ type: "listApiConfig", listApiConfig })
12431303
} catch (error) {
@@ -1277,7 +1337,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
12771337
this.outputChannel.appendLine(
12781338
`Failed to update timeout for ${message.serverName}: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,
12791339
)
1280-
vscode.window.showErrorMessage(`Failed to update server timeout`)
1340+
vscode.window.showErrorMessage("Failed to update server timeout")
12811341
}
12821342
}
12831343
break
@@ -1630,7 +1690,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
16301690
async refreshGlamaModels() {
16311691
const glamaModelsFilePath = path.join(await this.ensureCacheDirectoryExists(), GlobalFileNames.glamaModels)
16321692

1633-
let models: Record<string, ModelInfo> = {}
1693+
const models: Record<string, ModelInfo> = {}
16341694
try {
16351695
const response = await axios.get("https://glama.ai/api/gateway/v1/models")
16361696
/*
@@ -1720,7 +1780,7 @@ export class ClineProvider implements vscode.WebviewViewProvider {
17201780
GlobalFileNames.openRouterModels,
17211781
)
17221782

1723-
let models: Record<string, ModelInfo> = {}
1783+
const models: Record<string, ModelInfo> = {}
17241784
try {
17251785
const response = await axios.get("https://openrouter.ai/api/v1/models")
17261786
/*

src/core/webview/__tests__/ClineProvider.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ jest.mock("vscode", () => ({
108108
uriScheme: "vscode",
109109
language: "en",
110110
},
111+
ExtensionMode: {
112+
Production: 1,
113+
Development: 2,
114+
Test: 3,
115+
},
111116
}))
112117

113118
// Mock sound utility

webview-ui/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@
2121
npm-debug.log*
2222
yarn-debug.log*
2323
yarn-error.log*
24+
25+
*storybook.log

webview-ui/.storybook/main.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { StorybookConfig } from "@storybook/react-vite"
2+
3+
const config: StorybookConfig = {
4+
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
5+
addons: [
6+
"@storybook/addon-onboarding",
7+
"@storybook/addon-essentials",
8+
"@chromatic-com/storybook",
9+
"@storybook/addon-interactions",
10+
],
11+
framework: {
12+
name: "@storybook/react-vite",
13+
options: {},
14+
},
15+
}
16+
export default config

webview-ui/.storybook/preview.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { Preview } from "@storybook/react"
2+
3+
import "../src/index.css"
4+
import "./vscode.css"
5+
6+
const preview: Preview = {
7+
parameters: {
8+
controls: {
9+
matchers: {
10+
color: /(background|color)$/i,
11+
date: /Date$/i,
12+
},
13+
},
14+
},
15+
}
16+
17+
export default preview

webview-ui/.storybook/vscode.css

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Use `Developer: Generate Color Theme From Current Settings` to generate themes
3+
* using your current VSCode settings.
4+
*
5+
* See: https://code.visualstudio.com/docs/getstarted/themes
6+
*/
7+
8+
:root {
9+
--vscode-editor-background: #1f1f1f; /* "editor.background" */
10+
--vscode-editor-foreground: #cccccc; /* "editor.foreground" */
11+
--vscode-menu-background: #1f1f1f; /* "menu.background" */
12+
--vscode-menu-foreground: #cccccc; /* "menu.foreground" */
13+
--vscode-button-background: #0078d4; /* "button.background" */
14+
--vscode-button-foreground: #ffffff; /* "button.foreground" */
15+
--vscode-button-secondaryBackground: #313131; /* "button.secondaryBackground" */
16+
--vscode-button-secondaryForeground: #cccccc; /* "button.secondaryForeground" */
17+
--vscode-disabledForeground: red; /* "disabledForeground" */
18+
--vscode-descriptionForeground: #9d9d9d; /* "descriptionForeground" */
19+
--vscode-focusBorder: #0078d4; /* "focusBorder" */
20+
--vscode-errorForeground: #f85149; /* "errorForeground" */
21+
--vscode-widget-border: #313131; /* "widget.border" */
22+
--vscode-input-background: #313131; /* "input.background" */
23+
--vscode-input-foreground: #cccccc; /* "input.foreground" */
24+
--vscode-input-border: #3c3c3c; /* "input.border" */
25+
26+
/* I can't find these in the output of `Developer: Generate Color Theme From Current Settings` */
27+
--vscode-charts-red: red;
28+
--vscode-charts-blue: blue;
29+
--vscode-charts-yellow: yellow;
30+
--vscode-charts-orange: orange;
31+
--vscode-charts-green: green;
32+
}

0 commit comments

Comments
 (0)