feat: implement sidebar

- add sidebar
- update assets to use new task-master logo
- change to task master instead of taskr
This commit is contained in:
Ralph Khreish
2025-07-31 12:42:10 +03:00
parent 8ad9ccd6b7
commit cd92be61e5
20 changed files with 620 additions and 119 deletions

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 224 291" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M101.635 286.568L71.4839 256.414C65.6092 250.539 65.6092 241.03 71.4839 235.155L142.52 164.11C144.474 162.156 147.643 162.156 149.61 164.11L176.216 190.719C178.17 192.673 181.339 192.673 183.305 190.719L189.719 184.305C191.673 182.35 191.673 179.181 189.719 177.214L163.113 150.605C161.159 148.651 161.159 145.481 163.113 143.514L191.26 115.365C193.214 113.41 193.214 110.241 191.26 108.274L182.316 99.3291C180.362 97.3748 177.193 97.3748 175.226 99.3291L55.8638 218.706C49.989 224.581 40.4816 224.581 34.6068 218.706L4.4061 188.501C-1.4687 182.626 -1.4687 173.117 4.4061 167.242L23.8342 147.811C25.7883 145.857 25.7883 142.688 23.8342 140.721L4.78187 121.666C-1.09293 115.791 -1.09293 106.282 4.78187 100.406L34.7195 70.4527C40.5943 64.5772 50.1017 64.5772 55.9765 70.4527L75.555 90.0335C77.5091 91.9879 80.6782 91.9879 82.6448 90.0335L124.144 48.5292C126.098 46.5749 126.098 43.4054 124.144 41.4385L115.463 32.7568C113.509 30.8025 110.34 30.8025 108.374 32.7568L99.8683 41.2632C97.9143 43.2175 94.7451 43.2175 92.7785 41.2632L82.1438 30.6271C80.1897 28.6728 80.1897 25.5033 82.1438 23.5364L101.271 4.40662C107.146 -1.46887 116.653 -1.46887 122.528 4.40662L152.478 34.3604C158.353 40.2359 158.353 49.7444 152.478 55.6199L82.6323 125.474C80.6782 127.429 77.5091 127.429 75.5425 125.474L48.8741 98.8029C46.9201 96.8486 43.7509 96.8486 41.7843 98.8029L33.1036 107.485C31.1496 109.439 31.1496 112.608 33.1036 114.575L59.2458 140.721C61.1999 142.675 61.1999 145.844 59.2458 147.811L32.7404 174.32C30.7863 176.274 30.7863 179.444 32.7404 181.411L41.6841 190.355C43.6382 192.31 46.8073 192.31 48.7739 190.355L168.136 70.9789C174.011 65.1034 183.518 65.1034 189.393 70.9789L219.594 101.183C225.469 107.059 225.469 116.567 219.594 122.443L198.537 143.502C196.583 145.456 196.583 148.626 198.537 150.592L218.053 170.111C223.928 175.986 223.928 185.495 218.053 191.37L190.37 219.056C184.495 224.932 174.988 224.932 169.113 219.056L149.597 199.538C147.643 197.584 144.474 197.584 142.508 199.538L99.8057 242.245C97.8516 244.2 97.8516 247.369 99.8057 249.336L108.699 258.231C110.653 260.185 113.823 260.185 115.789 258.231L122.954 251.065C124.908 249.11 128.077 249.11 130.044 251.065L140.679 261.701C142.633 263.655 142.633 266.825 140.679 268.791L122.879 286.593C117.004 292.469 107.497 292.469 101.622 286.593L101.635 286.568Z" fill="#CCCCCC"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 224 291" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M101.635 286.568L71.4839 256.414C65.6092 250.539 65.6092 241.03 71.4839 235.155L142.52 164.11C144.474 162.156 147.643 162.156 149.61 164.11L176.216 190.719C178.17 192.673 181.339 192.673 183.305 190.719L189.719 184.305C191.673 182.35 191.673 179.181 189.719 177.214L163.113 150.605C161.159 148.651 161.159 145.481 163.113 143.514L191.26 115.365C193.214 113.41 193.214 110.241 191.26 108.274L182.316 99.3291C180.362 97.3748 177.193 97.3748 175.226 99.3291L55.8638 218.706C49.989 224.581 40.4816 224.581 34.6068 218.706L4.4061 188.501C-1.4687 182.626 -1.4687 173.117 4.4061 167.242L23.8342 147.811C25.7883 145.857 25.7883 142.688 23.8342 140.721L4.78187 121.666C-1.09293 115.791 -1.09293 106.282 4.78187 100.406L34.7195 70.4527C40.5943 64.5772 50.1017 64.5772 55.9765 70.4527L75.555 90.0335C77.5091 91.9879 80.6782 91.9879 82.6448 90.0335L124.144 48.5292C126.098 46.5749 126.098 43.4054 124.144 41.4385L115.463 32.7568C113.509 30.8025 110.34 30.8025 108.374 32.7568L99.8683 41.2632C97.9143 43.2175 94.7451 43.2175 92.7785 41.2632L82.1438 30.6271C80.1897 28.6728 80.1897 25.5033 82.1438 23.5364L101.271 4.40662C107.146 -1.46887 116.653 -1.46887 122.528 4.40662L152.478 34.3604C158.353 40.2359 158.353 49.7444 152.478 55.6199L82.6323 125.474C80.6782 127.429 77.5091 127.429 75.5425 125.474L48.8741 98.8029C46.9201 96.8486 43.7509 96.8486 41.7843 98.8029L33.1036 107.485C31.1496 109.439 31.1496 112.608 33.1036 114.575L59.2458 140.721C61.1999 142.675 61.1999 145.844 59.2458 147.811L32.7404 174.32C30.7863 176.274 30.7863 179.444 32.7404 181.411L41.6841 190.355C43.6382 192.31 46.8073 192.31 48.7739 190.355L168.136 70.9789C174.011 65.1034 183.518 65.1034 189.393 70.9789L219.594 101.183C225.469 107.059 225.469 116.567 219.594 122.443L198.537 143.502C196.583 145.456 196.583 148.626 198.537 150.592L218.053 170.111C223.928 175.986 223.928 185.495 218.053 191.37L190.37 219.056C184.495 224.932 174.988 224.932 169.113 219.056L149.597 199.538C147.643 197.584 144.474 197.584 142.508 199.538L99.8057 242.245C97.8516 244.2 97.8516 247.369 99.8057 249.336L108.699 258.231C110.653 260.185 113.823 260.185 115.789 258.231L122.954 251.065C124.908 249.11 128.077 249.11 130.044 251.065L140.679 261.701C142.633 263.655 142.633 266.825 140.679 268.791L122.879 286.593C117.004 292.469 107.497 292.469 101.622 286.593L101.635 286.568Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 224 291" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M101.635 286.568L71.4839 256.414C65.6092 250.539 65.6092 241.03 71.4839 235.155L142.52 164.11C144.474 162.156 147.643 162.156 149.61 164.11L176.216 190.719C178.17 192.673 181.339 192.673 183.305 190.719L189.719 184.305C191.673 182.35 191.673 179.181 189.719 177.214L163.113 150.605C161.159 148.651 161.159 145.481 163.113 143.514L191.26 115.365C193.214 113.41 193.214 110.241 191.26 108.274L182.316 99.3291C180.362 97.3748 177.193 97.3748 175.226 99.3291L55.8638 218.706C49.989 224.581 40.4816 224.581 34.6068 218.706L4.4061 188.501C-1.4687 182.626 -1.4687 173.117 4.4061 167.242L23.8342 147.811C25.7883 145.857 25.7883 142.688 23.8342 140.721L4.78187 121.666C-1.09293 115.791 -1.09293 106.282 4.78187 100.406L34.7195 70.4527C40.5943 64.5772 50.1017 64.5772 55.9765 70.4527L75.555 90.0335C77.5091 91.9879 80.6782 91.9879 82.6448 90.0335L124.144 48.5292C126.098 46.5749 126.098 43.4054 124.144 41.4385L115.463 32.7568C113.509 30.8025 110.34 30.8025 108.374 32.7568L99.8683 41.2632C97.9143 43.2175 94.7451 43.2175 92.7785 41.2632L82.1438 30.6271C80.1897 28.6728 80.1897 25.5033 82.1438 23.5364L101.271 4.40662C107.146 -1.46887 116.653 -1.46887 122.528 4.40662L152.478 34.3604C158.353 40.2359 158.353 49.7444 152.478 55.6199L82.6323 125.474C80.6782 127.429 77.5091 127.429 75.5425 125.474L48.8741 98.8029C46.9201 96.8486 43.7509 96.8486 41.7843 98.8029L33.1036 107.485C31.1496 109.439 31.1496 112.608 33.1036 114.575L59.2458 140.721C61.1999 142.675 61.1999 145.844 59.2458 147.811L32.7404 174.32C30.7863 176.274 30.7863 179.444 32.7404 181.411L41.6841 190.355C43.6382 192.31 46.8073 192.31 48.7739 190.355L168.136 70.9789C174.011 65.1034 183.518 65.1034 189.393 70.9789L219.594 101.183C225.469 107.059 225.469 116.567 219.594 122.443L198.537 143.502C196.583 145.456 196.583 148.626 198.537 150.592L218.053 170.111C223.928 175.986 223.928 185.495 218.053 191.37L190.37 219.056C184.495 224.932 174.988 224.932 169.113 219.056L149.597 199.538C147.643 197.584 144.474 197.584 142.508 199.538L99.8057 242.245C97.8516 244.2 97.8516 247.369 99.8057 249.336L108.699 258.231C110.653 260.185 113.823 260.185 115.789 258.231L122.954 251.065C124.908 249.11 128.077 249.11 130.044 251.065L140.679 261.701C142.633 263.655 142.633 266.825 140.679 268.791L122.879 286.593C117.004 292.469 107.497 292.469 101.622 286.593L101.635 286.568Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@@ -109,7 +109,7 @@ The automation ensures these fields stay in sync between `package.json` and `pac
```json ```json
{ {
"version": "1.0.2", // ✅ AUTO-SYNCED "version": "1.0.2", // ✅ AUTO-SYNCED
"publisher": "DavidMaliglowka", // ⚠️ MUST MATCH MANUALLY "publisher": "Hamster", // ⚠️ MUST MATCH MANUALLY
"displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH MANUALLY "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH MANUALLY
"description": "...", // ⚠️ MUST MATCH MANUALLY "description": "...", // ⚠️ MUST MATCH MANUALLY
"engines": { "vscode": "^1.93.0" }, // ⚠️ MUST MATCH MANUALLY "engines": { "vscode": "^1.93.0" }, // ⚠️ MUST MATCH MANUALLY

View File

@@ -102,7 +102,7 @@ When updating extension metadata, ensure these fields match between `package.jso
```json ```json
{ {
"version": "1.0.1", // ⚠️ MUST MATCH "version": "1.0.1", // ⚠️ MUST MATCH
"publisher": "DavidMaliglowka", // ⚠️ MUST MATCH "publisher": "Hamster", // ⚠️ MUST MATCH
"displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH "displayName": "taskr: Task Master Kanban", // ⚠️ MUST MATCH
"description": "A visual Kanban board...", // ⚠️ MUST MATCH "description": "A visual Kanban board...", // ⚠️ MUST MATCH
} }

View File

@@ -118,12 +118,52 @@ async function main() {
plugins: [esbuildProblemMatcherPlugin, aliasPlugin] plugins: [esbuildProblemMatcherPlugin, aliasPlugin]
}); });
// Build configuration for the React sidebar
const sidebarCtx = await esbuild.context({
entryPoints: ['src/webview/sidebar.tsx'],
bundle: true,
format: 'iife',
globalName: 'SidebarApp',
minify: production,
sourcemap: !production ? 'inline' : false,
sourcesContent: !production,
platform: 'browser',
outdir: 'dist',
logLevel: 'silent',
target: ['es2020'],
jsx: 'automatic',
jsxImportSource: 'react',
external: ['*.css'],
alias: {
react: path.resolve(__dirname, '../../node_modules/react'),
'react-dom': path.resolve(__dirname, '../../node_modules/react-dom')
},
define: {
'process.env.NODE_ENV': production ? '"production"' : '"development"',
global: 'globalThis'
},
...(production && {
drop: ['debugger'],
pure: ['console.log', 'console.debug', 'console.trace']
}),
plugins: [esbuildProblemMatcherPlugin, aliasPlugin]
});
if (watch) { if (watch) {
await Promise.all([extensionCtx.watch(), webviewCtx.watch()]); await Promise.all([
extensionCtx.watch(),
webviewCtx.watch(),
sidebarCtx.watch()
]);
} else { } else {
await Promise.all([extensionCtx.rebuild(), webviewCtx.rebuild()]); await Promise.all([
extensionCtx.rebuild(),
webviewCtx.rebuild(),
sidebarCtx.rebuild()
]);
await extensionCtx.dispose(); await extensionCtx.dispose();
await webviewCtx.dispose(); await webviewCtx.dispose();
await sidebarCtx.dispose();
} }
} }

View File

@@ -1,10 +1,10 @@
{ {
"name": "taskr", "name": "extension",
"private": true, "private": true,
"displayName": "Task Master Kanban", "displayName": "Task Master",
"description": "A visual Kanban board interface for Task Master projects in VS Code", "description": "A visual Kanban board interface for Task Master projects in VS Code",
"version": "1.1.0", "version": "1.1.0",
"publisher": "DavidMaliglowka", "publisher": "Hamster",
"icon": "assets/icon.png", "icon": "assets/icon.png",
"engines": { "engines": {
"vscode": "^1.93.0" "vscode": "^1.93.0"
@@ -12,24 +12,52 @@
"categories": ["AI", "Visualization", "Education", "Other"], "categories": ["AI", "Visualization", "Education", "Other"],
"main": "./dist/extension.js", "main": "./dist/extension.js",
"contributes": { "contributes": {
"viewsContainers": {
"activitybar": [
{
"id": "taskmaster",
"title": "Task Master",
"icon": "assets/sidebar-icon.svg"
}
]
},
"views": {
"taskmaster": [
{
"id": "taskmaster.welcome",
"name": "Task Master",
"type": "webview"
}
]
},
"commands": [ "commands": [
{ {
"command": "taskr.showKanbanBoard", "command": "tm.showKanbanBoard",
"title": "Task Master Kanban: Show Board" "title": "Task Master: Show Board",
"icon": "$(checklist)"
}, },
{ {
"command": "taskr.checkConnection", "command": "tm.checkConnection",
"title": "Task Master Kanban: Check Connection" "title": "Task Master: Check Connection"
}, },
{ {
"command": "taskr.reconnect", "command": "tm.reconnect",
"title": "Task Master Kanban: Reconnect" "title": "Task Master: Reconnect"
}, },
{ {
"command": "taskr.openSettings", "command": "tm.openSettings",
"title": "Task Master Kanban: Open Settings" "title": "Task Master: Open Settings"
} }
], ],
"menus": {
"view/title": [
{
"command": "tm.showKanbanBoard",
"when": "view == taskmaster.welcome",
"group": "navigation"
}
]
},
"configuration": { "configuration": {
"title": "Task Master Kanban", "title": "Task Master Kanban",
"properties": { "properties": {
@@ -89,6 +117,13 @@
"maximum": 60000, "maximum": 60000,
"description": "Health check interval in milliseconds" "description": "Health check interval in milliseconds"
}, },
"taskmaster.mcp.requestTimeoutMs": {
"type": "number",
"default": 300000,
"minimum": 30000,
"maximum": 600000,
"description": "MCP request timeout in milliseconds (default: 5 minutes)"
},
"taskmaster.ui.autoRefresh": { "taskmaster.ui.autoRefresh": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@@ -192,14 +227,18 @@
"vscode:prepublish": "npm run build", "vscode:prepublish": "npm run build",
"build": "npm run build:js && npm run build:css", "build": "npm run build:js && npm run build:css",
"build:js": "node ./esbuild.js --production", "build:js": "node ./esbuild.js --production",
"build:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --minify", "build:css": "npm run build:css:main && npm run build:css:sidebar",
"build:css:main": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --minify",
"build:css:sidebar": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/sidebar.css --minify",
"package": "npm exec node ./package.mjs", "package": "npm exec node ./package.mjs",
"package:direct": "node ./package.mjs", "package:direct": "node ./package.mjs",
"debug:env": "node ./debug-env.mjs", "debug:env": "node ./debug-env.mjs",
"compile": "node ./esbuild.js", "compile": "node ./esbuild.js",
"watch": "npm run watch:js & npm run watch:css", "watch": "npm run watch:js & npm run watch:css",
"watch:js": "node ./esbuild.js --watch", "watch:js": "node ./esbuild.js --watch",
"watch:css": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch", "watch:css": "npm run watch:css:main & npm run watch:css:sidebar",
"watch:css:main": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/index.css --watch",
"watch:css:sidebar": "npx @tailwindcss/cli -i ./src/webview/index.css -o ./dist/sidebar.css --watch",
"test": "vscode-test", "test": "vscode-test",
"check-types": "tsc --noEmit" "check-types": "tsc --noEmit"
}, },

View File

@@ -30,7 +30,13 @@ try {
fs.ensureDirSync(targetDistDir); fs.ensureDirSync(targetDistDir);
// Only copy the files we need (exclude .map files) // Only copy the files we need (exclude .map files)
const filesToCopy = ['extension.js', 'index.js', 'index.css']; const filesToCopy = [
'extension.js',
'index.js',
'index.css',
'sidebar.js',
'sidebar.css'
];
for (const file of filesToCopy) { for (const file of filesToCopy) {
const srcFile = path.resolve(distDir, file); const srcFile = path.resolve(distDir, file);
const destFile = path.resolve(targetDistDir, file); const destFile = path.resolve(targetDistDir, file);
@@ -127,7 +133,7 @@ try {
// Use the synced version for output // Use the synced version for output
const finalVersion = devPackage.version; const finalVersion = devPackage.version;
console.log( console.log(
`\nYour extension will be packaged to: vsix-build/taskr-kanban-${finalVersion}.vsix` `\nYour extension will be packaged to: vsix-build/task-master-${finalVersion}.vsix`
); );
} catch (error) { } catch (error) {
console.error('\n❌ Packaging failed!'); console.error('\n❌ Packaging failed!');

View File

@@ -1,9 +1,9 @@
{ {
"name": "taskr-kanban", "name": "task-master",
"displayName": "taskr: Task Master Kanban", "displayName": "Task Master",
"description": "A visual Kanban board interface for Task Master projects in VS Code", "description": "A visual Kanban board interface for Task Master projects in VS Code",
"version": "1.0.0", "version": "1.0.0",
"publisher": "DavidMaliglowka", "publisher": "Hamster",
"icon": "assets/icon.png", "icon": "assets/icon.png",
"engines": { "engines": {
"vscode": "^1.93.0" "vscode": "^1.93.0"
@@ -44,29 +44,29 @@
], ],
"repository": "https://github.com/eyaltoledano/claude-task-master", "repository": "https://github.com/eyaltoledano/claude-task-master",
"activationEvents": [ "activationEvents": [
"onCommand:taskr.showKanbanBoard", "onCommand:tm.showKanbanBoard",
"onCommand:taskr.checkConnection", "onCommand:tm.checkConnection",
"onCommand:taskr.reconnect", "onCommand:tm.reconnect",
"onCommand:taskr.openSettings" "onCommand:tm.openSettings"
], ],
"main": "./dist/extension.js", "main": "./dist/extension.js",
"contributes": { "contributes": {
"commands": [ "commands": [
{ {
"command": "taskr.showKanbanBoard", "command": "tm.showKanbanBoard",
"title": "Task Master Kanban: Show Board" "title": "Task Master: Show Board"
}, },
{ {
"command": "taskr.checkConnection", "command": "tm.checkConnection",
"title": "Task Master Kanban: Check Connection" "title": "Task Master: Check Connection"
}, },
{ {
"command": "taskr.reconnect", "command": "tm.reconnect",
"title": "Task Master Kanban: Reconnect" "title": "Task Master: Reconnect"
}, },
{ {
"command": "taskr.openSettings", "command": "tm.openSettings",
"title": "Task Master Kanban: Open Settings" "title": "Task Master: Open Settings"
} }
], ],
"configuration": { "configuration": {
@@ -128,6 +128,13 @@
"maximum": 60000, "maximum": 60000,
"description": "Health check interval in milliseconds" "description": "Health check interval in milliseconds"
}, },
"taskmaster.mcp.requestTimeoutMs": {
"type": "number",
"default": 300000,
"minimum": 30000,
"maximum": 600000,
"description": "MCP request timeout in milliseconds (default: 5 minutes)"
},
"taskmaster.ui.autoRefresh": { "taskmaster.ui.autoRefresh": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,

View File

@@ -0,0 +1,23 @@
import React from 'react';
interface TaskMasterLogoProps {
className?: string;
}
export const TaskMasterLogo: React.FC<TaskMasterLogoProps> = ({
className = ''
}) => {
return (
<svg
className={className}
viewBox="0 0 224 291"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M101.635 286.568L71.4839 256.414C65.6092 250.539 65.6092 241.03 71.4839 235.155L142.52 164.11C144.474 162.156 147.643 162.156 149.61 164.11L176.216 190.719C178.17 192.673 181.339 192.673 183.305 190.719L189.719 184.305C191.673 182.35 191.673 179.181 189.719 177.214L163.113 150.605C161.159 148.651 161.159 145.481 163.113 143.514L191.26 115.365C193.214 113.41 193.214 110.241 191.26 108.274L182.316 99.3291C180.362 97.3748 177.193 97.3748 175.226 99.3291L55.8638 218.706C49.989 224.581 40.4816 224.581 34.6068 218.706L4.4061 188.501C-1.4687 182.626 -1.4687 173.117 4.4061 167.242L23.8342 147.811C25.7883 145.857 25.7883 142.688 23.8342 140.721L4.78187 121.666C-1.09293 115.791 -1.09293 106.282 4.78187 100.406L34.7195 70.4527C40.5943 64.5772 50.1017 64.5772 55.9765 70.4527L75.555 90.0335C77.5091 91.9879 80.6782 91.9879 82.6448 90.0335L124.144 48.5292C126.098 46.5749 126.098 43.4054 124.144 41.4385L115.463 32.7568C113.509 30.8025 110.34 30.8025 108.374 32.7568L99.8683 41.2632C97.9143 43.2175 94.7451 43.2175 92.7785 41.2632L82.1438 30.6271C80.1897 28.6728 80.1897 25.5033 82.1438 23.5364L101.271 4.40662C107.146 -1.46887 116.653 -1.46887 122.528 4.40662L152.478 34.3604C158.353 40.2359 158.353 49.7444 152.478 55.6199L82.6323 125.474C80.6782 127.429 77.5091 127.429 75.5425 125.474L48.8741 98.8029C46.9201 96.8486 43.7509 96.8486 41.7843 98.8029L33.1036 107.485C31.1496 109.439 31.1496 112.608 33.1036 114.575L59.2458 140.721C61.1999 142.675 61.1999 145.844 59.2458 147.811L32.7404 174.32C30.7863 176.274 30.7863 179.444 32.7404 181.411L41.6841 190.355C43.6382 192.31 46.8073 192.31 48.7739 190.355L168.136 70.9789C174.011 65.1034 183.518 65.1034 189.393 70.9789L219.594 101.183C225.469 107.059 225.469 116.567 219.594 122.443L198.537 143.502C196.583 145.456 196.583 148.626 198.537 150.592L218.053 170.111C223.928 175.986 223.928 185.495 218.053 191.37L190.37 219.056C184.495 224.932 174.988 224.932 169.113 219.056L149.597 199.538C147.643 197.584 144.474 197.584 142.508 199.538L99.8057 242.245C97.8516 244.2 97.8516 247.369 99.8057 249.336L108.699 258.231C110.653 260.185 113.823 260.185 115.789 258.231L122.954 251.065C124.908 249.11 128.077 249.11 130.044 251.065L140.679 261.701C142.633 263.655 142.633 266.825 140.679 268.791L122.879 286.593C117.004 292.469 107.497 292.469 101.622 286.593L101.635 286.568Z"
fill="currentColor"
/>
</svg>
);
};

View File

@@ -16,6 +16,7 @@ import {
createMCPConfigFromSettings createMCPConfigFromSettings
} from './utils/mcpClient'; } from './utils/mcpClient';
import { TaskMasterApi } from './utils/task-master-api'; import { TaskMasterApi } from './utils/task-master-api';
import { SidebarWebviewManager } from './services/sidebar-webview-manager';
let logger: ExtensionLogger; let logger: ExtensionLogger;
let mcpClient: MCPClientManager; let mcpClient: MCPClientManager;
@@ -25,6 +26,7 @@ let pollingService: PollingService;
let webviewManager: WebviewManager; let webviewManager: WebviewManager;
let events: EventEmitter; let events: EventEmitter;
let configService: ConfigService; let configService: ConfigService;
let sidebarManager: SidebarWebviewManager;
export async function activate(context: vscode.ExtensionContext) { export async function activate(context: vscode.ExtensionContext) {
try { try {
@@ -57,12 +59,16 @@ export async function activate(context: vscode.ExtensionContext) {
webviewManager = new WebviewManager(context, repository, events, logger); webviewManager = new WebviewManager(context, repository, events, logger);
webviewManager.setConfigService(configService); webviewManager.setConfigService(configService);
// Sidebar webview manager
sidebarManager = new SidebarWebviewManager(context.extensionUri);
// Initialize connection // Initialize connection
await initializeConnection(); await initializeConnection();
// Set MCP client and API after connection // Set MCP client and API after connection
webviewManager.setMCPClient(mcpClient); webviewManager.setMCPClient(mcpClient);
webviewManager.setApi(api); webviewManager.setApi(api);
sidebarManager.setApi(api);
// Register commands // Register commands
registerCommands(context); registerCommands(context);
@@ -121,6 +127,9 @@ async function initializeConnection() {
status: 'Connected' status: 'Connected'
}); });
} }
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
} else { } else {
throw new Error(testResult.error || 'Connection test failed'); throw new Error(testResult.error || 'Connection test failed');
} }
@@ -134,6 +143,9 @@ async function initializeConnection() {
status: 'Disconnected' status: 'Disconnected'
}); });
} }
if (sidebarManager) {
sidebarManager.updateConnectionStatus();
}
handleConnectionError(error); handleConnectionError(error);
} }
@@ -166,27 +178,36 @@ function handleConnectionError(error: any) {
function registerCommands(context: vscode.ExtensionContext) { function registerCommands(context: vscode.ExtensionContext) {
// Main command // Main command
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('taskr.showKanbanBoard', async () => { vscode.commands.registerCommand('tm.showKanbanBoard', async () => {
await webviewManager.createOrShowPanel(); await webviewManager.createOrShowPanel();
}) })
); );
// Utility commands // Utility commands
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('taskr.refreshTasks', async () => { vscode.commands.registerCommand('tm.refreshTasks', async () => {
await repository.refresh(); await repository.refresh();
vscode.window.showInformationMessage('Tasks refreshed!'); vscode.window.showInformationMessage('Tasks refreshed!');
}) })
); );
context.subscriptions.push( context.subscriptions.push(
vscode.commands.registerCommand('taskr.openSettings', () => { vscode.commands.registerCommand('tm.openSettings', () => {
vscode.commands.executeCommand( vscode.commands.executeCommand(
'workbench.action.openSettings', 'workbench.action.openSettings',
'@ext:taskr taskmaster' '@ext:taskr taskmaster'
); );
}) })
); );
// Register sidebar view provider
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(
'taskmaster.welcome',
sidebarManager
)
);
} }
export function deactivate() { export function deactivate() {

View File

@@ -112,9 +112,9 @@ export class ErrorHandler {
) )
.then((action) => { .then((action) => {
if (action === 'Retry') { if (action === 'Retry') {
vscode.commands.executeCommand('taskr.reconnect'); vscode.commands.executeCommand('tm.reconnect');
} else if (action === 'Settings') { } else if (action === 'Settings') {
vscode.commands.executeCommand('taskr.openSettings'); vscode.commands.executeCommand('tm.openSettings');
} }
}); });
} else { } else {

View File

@@ -0,0 +1,90 @@
import * as vscode from 'vscode';
import type { TaskMasterApi } from '../utils/task-master-api';
export class SidebarWebviewManager implements vscode.WebviewViewProvider {
private webviewView?: vscode.WebviewView;
private api?: TaskMasterApi;
constructor(private readonly extensionUri: vscode.Uri) {}
setApi(api: TaskMasterApi): void {
this.api = api;
// Update connection status if webview exists
if (this.webviewView) {
this.updateConnectionStatus();
}
}
resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
token: vscode.CancellationToken
): void {
this.webviewView = webviewView;
webviewView.webview.options = {
enableScripts: true,
localResourceRoots: [
vscode.Uri.joinPath(this.extensionUri, 'dist'),
vscode.Uri.joinPath(this.extensionUri, 'assets')
]
};
webviewView.webview.html = this.getHtmlContent(webviewView.webview);
// Handle messages from the webview
webviewView.webview.onDidReceiveMessage((message) => {
if (message.command === 'openBoard') {
vscode.commands.executeCommand('tm.showKanbanBoard');
}
});
// Update connection status on load
this.updateConnectionStatus();
}
updateConnectionStatus(): void {
if (!this.webviewView || !this.api) return;
const status = this.api.getConnectionStatus();
this.webviewView.webview.postMessage({
type: 'connectionStatus',
data: status
});
}
private getHtmlContent(webview: vscode.Webview): string {
const scriptUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'dist', 'sidebar.js')
);
const styleUri = webview.asWebviewUri(
vscode.Uri.joinPath(this.extensionUri, 'dist', 'sidebar.css')
);
const nonce = this.getNonce();
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src ${webview.cspSource} https:; script-src 'nonce-${nonce}'; style-src ${webview.cspSource} 'unsafe-inline';">
<link href="${styleUri}" rel="stylesheet">
<title>Task Master</title>
</head>
<body>
<div id="root"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
}
private getNonce(): string {
let text = '';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
}

View File

@@ -22,14 +22,25 @@ export class TaskRepository extends EventEmitter {
super(); super();
} }
async getAll(): Promise<Task[]> { async getAll(options?: {
// Return from cache if valid tag?: string;
if (this.cache && Date.now() - this.cacheTimestamp < this.CACHE_DURATION) { withSubtasks?: boolean;
return this.cache; }): Promise<Task[]> {
// If a tag is specified, always fetch fresh data
const shouldUseCache =
!options?.tag &&
this.cache &&
Date.now() - this.cacheTimestamp < this.CACHE_DURATION;
if (shouldUseCache) {
return this.cache || [];
} }
try { try {
const result = await this.api.getTasks({ withSubtasks: true }); const result = await this.api.getTasks({
withSubtasks: options?.withSubtasks ?? true,
tag: options?.tag
});
if (result.success && result.data) { if (result.success && result.data) {
this.cache = result.data; this.cache = result.data;

View File

@@ -58,6 +58,20 @@ export class WebviewManager {
} }
); );
// Set the icon for the webview tab
panel.iconPath = {
light: vscode.Uri.joinPath(
this.context.extensionUri,
'assets',
'icon-light.svg'
),
dark: vscode.Uri.joinPath(
this.context.extensionUri,
'assets',
'icon-dark.svg'
)
};
this.panels.add(panel); this.panels.add(panel);
panel.webview.html = this.getWebviewContent(panel.webview); panel.webview.html = this.getWebviewContent(panel.webview);
@@ -122,7 +136,11 @@ export class WebviewManager {
return; return;
case 'getTasks': case 'getTasks':
response = await this.repository.getAll(); // Pass options to getAll including tag if specified
response = await this.repository.getAll({
tag: data?.tag,
withSubtasks: data?.withSubtasks ?? true
});
break; break;
case 'updateTaskStatus': case 'updateTaskStatus':
@@ -279,9 +297,9 @@ export class WebviewManager {
name: data.tagName, name: data.tagName,
projectRoot: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath projectRoot: vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
}); });
// Clear cache and refresh tasks for the new tag // Clear cache and fetch tasks for the new tag
await this.repository.refresh(); await this.repository.refresh();
const tasks = await this.repository.getAll(); const tasks = await this.repository.getAll({ tag: data.tagName });
this.broadcast('tasksUpdated', { tasks, source: 'tag-switch' }); this.broadcast('tasksUpdated', { tasks, source: 'tag-switch' });
response = { success: true }; response = { success: true };
} catch (error) { } catch (error) {
@@ -293,6 +311,13 @@ export class WebviewManager {
} }
break; break;
case 'openExternal':
// Open external URL
if (message.url) {
vscode.env.openExternal(vscode.Uri.parse(message.url));
}
return;
default: default:
throw new Error(`Unknown message type: ${type}`); throw new Error(`Unknown message type: ${type}`);
} }

View File

@@ -8,6 +8,7 @@ export interface MCPConfig {
args: string[]; args: string[];
cwd?: string; cwd?: string;
env?: Record<string, string>; env?: Record<string, string>;
timeout?: number;
} }
export interface MCPServerStatus { export interface MCPServerStatus {
@@ -210,9 +211,6 @@ export class MCPClientManager {
}; };
logger.log('MCP client connected successfully'); logger.log('MCP client connected successfully');
vscode.window.showInformationMessage(
'Task Master connected successfully'
);
} catch (error) { } catch (error) {
logger.error('Failed to connect to MCP server:', error); logger.error('Failed to connect to MCP server:', error);
this.status = { this.status = {
@@ -275,10 +273,21 @@ export class MCPClientManager {
} }
try { try {
const result = await this.client.callTool({ // Use the configured timeout or default to 5 minutes
name: toolName, const timeout = this.config.timeout || 300000; // 5 minutes default
arguments: arguments_
}); logger.log(`Calling MCP tool "${toolName}" with timeout: ${timeout}ms`);
const result = await this.client.callTool(
{
name: toolName,
arguments: arguments_
},
undefined,
{
timeout: timeout
}
);
return result; return result;
} catch (error) { } catch (error) {
@@ -297,6 +306,7 @@ export class MCPClientManager {
return false; return false;
} }
// listTools is a simple metadata request, no need for extended timeout
const result = await this.client.listTools(); const result = await this.client.listTools();
logger.log( logger.log(
'Available MCP tools:', 'Available MCP tools:',
@@ -347,6 +357,7 @@ export function createMCPConfigFromSettings(): MCPConfig {
vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd(); vscode.workspace.workspaceFolders?.[0]?.uri.fsPath || process.cwd();
const cwd = config.get<string>('mcp.cwd', defaultCwd); const cwd = config.get<string>('mcp.cwd', defaultCwd);
const env = config.get<Record<string, string>>('mcp.env'); const env = config.get<Record<string, string>>('mcp.env');
const timeout = config.get<number>('mcp.requestTimeoutMs', 300000);
logger.log('✅ Using workspace directory:', defaultCwd); logger.log('✅ Using workspace directory:', defaultCwd);
@@ -377,6 +388,7 @@ export function createMCPConfigFromSettings(): MCPConfig {
command, command,
args, args,
cwd: cwd || defaultCwd, cwd: cwd || defaultCwd,
env env,
timeout
}; };
} }

View File

@@ -0,0 +1,138 @@
import React from 'react';
import { ExternalLink, Terminal, MessageSquare, Plus } from 'lucide-react';
import { TaskMasterLogo } from '../../components/TaskMasterLogo';
interface EmptyStateProps {
currentTag: string;
}
export const EmptyState: React.FC<EmptyStateProps> = ({ currentTag }) => {
return (
<div className="flex items-center justify-center h-full overflow-auto">
<div className="max-w-2xl mx-auto text-center p-8">
{/* Empty state illustration */}
<div className="mb-8 max-w-96 mx-auto">
<TaskMasterLogo className="w-32 h-32 mx-auto text-vscode-foreground/20" />
</div>
<h2 className="text-2xl font-semibold mb-2 text-vscode-foreground">
No tasks in "{currentTag}" tag
</h2>
<p className="text-vscode-foreground/70 mb-8">
Get started by adding tasks to this tag using the commands below
</p>
{/* Command suggestions */}
<div className="space-y-4 text-left">
<div className="bg-vscode-editor-background/50 border border-vscode-panel-border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<Terminal className="w-4 h-4 text-vscode-terminal-ansiGreen" />
<h3 className="font-medium">CLI Commands</h3>
</div>
<div className="space-y-2">
<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
<span className="text-vscode-terminal-ansiYellow">
task-master
</span>{' '}
<span className="text-vscode-terminal-ansiCyan">parse-prd</span>{' '}
<span className="text-vscode-foreground/70">
&lt;path-to-prd&gt;
</span>{' '}
<span className="text-vscode-terminal-ansiMagenta">
--append
</span>
<div className="text-xs text-vscode-foreground/50 mt-1">
Parse a PRD and append tasks to current tag
</div>
</div>
<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
<span className="text-vscode-terminal-ansiYellow">
task-master
</span>{' '}
<span className="text-vscode-terminal-ansiCyan">add-task</span>{' '}
<span className="text-vscode-terminal-ansiMagenta">
--prompt
</span>{' '}
<span className="text-vscode-foreground/70">
"Your task description"
</span>
<div className="text-xs text-vscode-foreground/50 mt-1">
Add a single task with AI assistance
</div>
</div>
<div className="bg-vscode-editor-background rounded p-2 font-mono text-sm">
<span className="text-vscode-terminal-ansiYellow">
task-master
</span>{' '}
<span className="text-vscode-terminal-ansiCyan">add-task</span>{' '}
<span className="text-vscode-terminal-ansiMagenta">--help</span>
<div className="text-xs text-vscode-foreground/50 mt-1">
View all options for adding tasks
</div>
</div>
</div>
</div>
<div className="bg-vscode-editor-background/50 border border-vscode-panel-border rounded-lg p-4">
<div className="flex items-center gap-2 mb-2">
<MessageSquare className="w-4 h-4 text-vscode-textLink-foreground" />
<h3 className="font-medium">MCP Examples</h3>
</div>
<div className="space-y-2 text-sm">
<div className="flex items-start gap-2">
<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
<div>
<div className="text-vscode-foreground">
"Add a task to tag {currentTag}: Implement user
authentication"
</div>
</div>
</div>
<div className="flex items-start gap-2">
<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
<div>
<div className="text-vscode-foreground">
"Parse this PRD and add tasks to {currentTag}: [paste PRD
content]"
</div>
</div>
</div>
<div className="flex items-start gap-2">
<Plus className="w-4 h-4 mt-0.5 text-vscode-foreground/50" />
<div>
<div className="text-vscode-foreground">
"Create 5 tasks for building a REST API in tag {currentTag}"
</div>
</div>
</div>
</div>
</div>
{/* Documentation link */}
<div className="flex justify-center pt-4">
<a
href="https://docs.task-master.dev"
className="inline-flex items-center gap-2 text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground transition-colors"
onClick={(e) => {
e.preventDefault();
// Use VS Code API to open external link
if (window.acquireVsCodeApi) {
const vscode = window.acquireVsCodeApi();
vscode.postMessage({
type: 'openExternal',
url: 'https://docs.task-master.dev'
});
}
}}
>
<ExternalLink className="w-4 h-4" />
<span className="text-sm font-medium">
View Task Master Documentation
</span>
</a>
</div>
</div>
</div>
</div>
);
};

View File

@@ -0,0 +1,58 @@
import React, { useState, useEffect } from 'react';
import { TaskMasterLogo } from '../../components/TaskMasterLogo';
interface SidebarViewProps {
initialConnectionStatus?: boolean;
}
export const SidebarView: React.FC<SidebarViewProps> = ({
initialConnectionStatus = false
}) => {
const [isConnected, setIsConnected] = useState(initialConnectionStatus);
const vscode = window.acquireVsCodeApi ? window.acquireVsCodeApi() : null;
useEffect(() => {
const handleMessage = (event: MessageEvent) => {
const message = event.data;
if (message.type === 'connectionStatus') {
setIsConnected(message.data.isConnected);
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
const handleOpenBoard = () => {
vscode?.postMessage({ command: 'openBoard' });
};
return (
<div className="h-full flex items-center justify-center p-6">
<div className="text-center">
<TaskMasterLogo className="w-20 h-20 mx-auto mb-5 opacity-80 text-vscode-foreground" />
<h2 className="text-xl font-semibold mb-6 text-vscode-foreground">
Task Master
</h2>
<button
onClick={handleOpenBoard}
className="w-full px-4 py-2 bg-vscode-button-background text-vscode-button-foreground rounded hover:bg-vscode-button-hoverBackground transition-colors text-sm font-medium"
>
Open Kanban Board
</button>
<div
className={`mt-5 p-2.5 bg-vscode-editor-background rounded text-xs ${
isConnected
? 'border-l-[3px] border-vscode-testing-iconPassed'
: 'border-l-[3px] border-vscode-testing-iconFailed'
}`}
>
Status: {isConnected ? 'Connected' : 'Disconnected'}
</div>
</div>
</div>
);
};

View File

@@ -14,6 +14,7 @@ import { TaskCard } from './TaskCard';
import { TaskEditModal } from './TaskEditModal'; import { TaskEditModal } from './TaskEditModal';
import { PollingStatus } from './PollingStatus'; import { PollingStatus } from './PollingStatus';
import { TagDropdown } from './TagDropdown'; import { TagDropdown } from './TagDropdown';
import { EmptyState } from './EmptyState';
import { useVSCodeContext } from '../contexts/VSCodeContext'; import { useVSCodeContext } from '../contexts/VSCodeContext';
import { kanbanStatuses, HEADER_HEIGHT } from '../constants'; import { kanbanStatuses, HEADER_HEIGHT } from '../constants';
import type { TaskMasterTask, TaskUpdates } from '../types'; import type { TaskMasterTask, TaskUpdates } from '../types';
@@ -177,8 +178,11 @@ export const TaskMasterKanban: React.FC = () => {
async (tagName: string) => { async (tagName: string) => {
console.log('Switching to tag:', tagName); console.log('Switching to tag:', tagName);
await sendMessage({ type: 'switchTag', data: { tagName } }); await sendMessage({ type: 'switchTag', data: { tagName } });
// After switching tags, fetch the new tasks // After switching tags, fetch the new tasks for that specific tag
const tasksData = await sendMessage({ type: 'getTasks' }); const tasksData = await sendMessage({
type: 'getTasks',
data: { tag: tagName }
});
console.log('Received new tasks for tag', tagName, ':', { console.log('Received new tasks for tag', tagName, ':', {
tasksData, tasksData,
isArray: Array.isArray(tasksData), isArray: Array.isArray(tasksData),
@@ -276,72 +280,76 @@ export const TaskMasterKanban: React.FC = () => {
className="flex-1 px-4 py-4 overflow-hidden" className="flex-1 px-4 py-4 overflow-hidden"
style={{ height: `${kanbanHeight}px` }} style={{ height: `${kanbanHeight}px` }}
> >
<KanbanProvider {tasks.length === 0 ? (
onDragStart={handleDragStart} <EmptyState currentTag={currentTag} />
onDragEnd={handleDragEnd} ) : (
className="kanban-container w-full h-full overflow-x-auto overflow-y-hidden" <KanbanProvider
dragOverlay={ onDragStart={handleDragStart}
activeTask ? <TaskCard task={activeTask} dragging /> : null onDragEnd={handleDragEnd}
} className="kanban-container w-full h-full overflow-x-auto overflow-y-hidden"
> dragOverlay={
<div className="flex gap-4 h-full min-w-fit"> activeTask ? <TaskCard task={activeTask} dragging /> : null
{kanbanStatuses.map((status) => { }
const statusTasks = tasksByStatus[status.id] || []; >
const hasScrollbar = statusTasks.length > 4; <div className="flex gap-4 h-full min-w-fit">
{kanbanStatuses.map((status) => {
const statusTasks = tasksByStatus[status.id] || [];
const hasScrollbar = statusTasks.length > 4;
return ( return (
<KanbanBoard <KanbanBoard
key={status.id} key={status.id}
id={status.id} id={status.id}
title={status.name} title={status.name}
className={`
w-80 flex flex-col
border border-vscode-border/30
rounded-lg
bg-vscode-sidebar-background/50
`}
>
<KanbanHeader
name={`${status.name} (${statusTasks.length})`}
color={status.color}
className="px-3 py-3 text-sm font-medium flex-shrink-0 border-b border-vscode-border/30"
/>
<div
className={` className={`
flex flex-col gap-2 w-80 flex flex-col
overflow-y-auto overflow-x-hidden border border-vscode-border/30
p-2 rounded-lg
scrollbar-thin scrollbar-track-transparent bg-vscode-sidebar-background/50
${hasScrollbar ? 'pr-1' : ''}
`} `}
style={{
maxHeight: `${kanbanHeight - 80}px`
}}
> >
<KanbanCards column={status.id}> <KanbanHeader
{statusTasks.map((task) => ( name={`${status.name} (${statusTasks.length})`}
<TaskCard color={status.color}
key={task.id} className="px-3 py-3 text-sm font-medium flex-shrink-0 border-b border-vscode-border/30"
task={task} />
onViewDetails={(taskId) => { <div
console.log( className={`
'🔍 Navigating to task details:', flex flex-col gap-2
taskId overflow-y-auto overflow-x-hidden
); p-2
dispatch({ scrollbar-thin scrollbar-track-transparent
type: 'NAVIGATE_TO_TASK', ${hasScrollbar ? 'pr-1' : ''}
payload: taskId `}
}); style={{
}} maxHeight: `${kanbanHeight - 80}px`
/> }}
))} >
</KanbanCards> <KanbanCards column={status.id}>
</div> {statusTasks.map((task) => (
</KanbanBoard> <TaskCard
); key={task.id}
})} task={task}
</div> onViewDetails={(taskId) => {
</KanbanProvider> console.log(
'🔍 Navigating to task details:',
taskId
);
dispatch({
type: 'NAVIGATE_TO_TASK',
payload: taskId
});
}}
/>
))}
</KanbanCards>
</div>
</KanbanBoard>
);
})}
</div>
</KanbanProvider>
)}
</div> </div>
</div> </div>

View File

@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { SidebarView } from './components/SidebarView';
import './index.css';
// Mount the React app
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<SidebarView />
</React.StrictMode>
);