diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 7a719d5..5b690e8 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -65,6 +65,7 @@
"p-queue": "^9.1.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
+ "react-scan": "^0.4.3",
"tailwind-merge": "^3.3.0",
"tailwindcss": "^4.1.12",
"typescript": "^5.9.2",
diff --git a/apps/desktop/src/renderer/index.html b/apps/desktop/src/renderer/index.html
index a4b3472..47d5286 100644
--- a/apps/desktop/src/renderer/index.html
+++ b/apps/desktop/src/renderer/index.html
@@ -6,7 +6,7 @@
diff --git a/apps/desktop/src/renderer/src/main.tsx b/apps/desktop/src/renderer/src/main.tsx
index 8fb0ece..40d43fa 100644
--- a/apps/desktop/src/renderer/src/main.tsx
+++ b/apps/desktop/src/renderer/src/main.tsx
@@ -1,3 +1,21 @@
+// CRITICAL: react-scan must be imported and configured BEFORE React
+import { scan } from "react-scan";
+
+// Configure react-scan before any React imports
+if (import.meta.env.DEV) {
+ scan({
+ enabled: true,
+ showToolbar: true,
+ animationSpeed: "slow",
+ log: false,
+ trackUnnecessaryRenders: true,
+ onRender: (fiber, renders) => {
+ console.log("[react-scan] render:", fiber.type?.name || fiber.type, renders.length);
+ },
+ });
+}
+
+// NOW import React and other dependencies
import { QueryClientProvider } from "@tanstack/react-query";
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
diff --git a/docs/plans/2026-02-02-react-scan-integration-design.md b/docs/plans/2026-02-02-react-scan-integration-design.md
new file mode 100644
index 0000000..fa66c2d
--- /dev/null
+++ b/docs/plans/2026-02-02-react-scan-integration-design.md
@@ -0,0 +1,122 @@
+# React-scan Integration Design
+
+**Date:** 2026-02-02
+**Target:** Desktop Renderer
+**Goal:** Integrate react-scan for performance visualization and debugging
+
+## Overview
+
+Integrate react-scan into the desktop renderer to provide real-time performance monitoring during development. React-scan will highlight component re-renders, detect unnecessary renders, and provide console diagnostics to help identify performance bottlenecks.
+
+## Configuration
+
+**Dependency:**
+- Add `react-scan` as dev dependency to `apps/desktop`
+- Installation: `pnpm add react-scan -D --filter desktop`
+
+**Settings:**
+- `enabled`: Controlled by `import.meta.env.DEV` (development-only)
+- `showToolbar`: `true` (draggable UI for runtime control)
+- `animationSpeed`: `"fast"` (visible but not distracting)
+- `log`: `true` (console output for render tracking)
+- `trackUnnecessaryRenders`: `true` (detect renders with no DOM changes)
+
+## Implementation
+
+### Critical Requirement
+
+React-scan must hijack React DevTools hooks before React initializes. This requires importing and configuring react-scan BEFORE any React imports in the entry point.
+
+### File Changes
+
+**`apps/desktop/src/renderer/src/main.tsx`:**
+
+```typescript
+// CRITICAL: react-scan must be imported FIRST, before React
+import { scan } from 'react-scan'
+
+// Configure react-scan before any React imports
+if (import.meta.env.DEV) {
+ scan({
+ enabled: true,
+ showToolbar: true,
+ animationSpeed: 'fast',
+ log: true,
+ trackUnnecessaryRenders: true,
+ })
+}
+
+// NOW import React and other dependencies
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+// ... rest of imports and app initialization
+```
+
+### Import Order
+
+1. `react-scan` import and configuration
+2. React imports (`react`, `react-dom`)
+3. Application imports
+4. Render logic
+
+Breaking this order will cause react-scan to fail silently.
+
+## Verification
+
+### Visual Indicators
+
+When running `pnpm dev --filter desktop`, the following should be visible:
+
+- **Toolbar:** Draggable UI element (typically bottom-right)
+- **Render highlights:** Colored outlines flash on component re-renders
+- **Color coding:** Cooler colors (blue) = infrequent, warmer colors (red) = frequent
+
+### Console Output
+
+With `log: true`, expect console messages:
+
+```
+[react-scan] Component rendered:
+[react-scan] Unnecessary render detected in
+```
+
+### Toolbar Features
+
+- Toggle scanning on/off
+- View render statistics per component
+- Inspect individual render causes
+- Performance bell icon for insights
+- Drag to screen edges to collapse
+
+### Expected Behavior
+
+Areas likely to show render activity:
+- **Terminal components:** Active during typing/output
+- **Sidebar navigation:** Highlights on route changes
+- **Dialogs:** Render on open/close
+- **Task/worktree lists:** Update on state changes
+
+## Development Workflow
+
+1. Start dev server: `pnpm dev --filter desktop`
+2. Open Electron app
+3. Interact with UI to trigger renders
+4. Observe toolbar and console for performance data
+5. Use insights to identify optimization opportunities
+
+## Success Criteria
+
+- [ ] React-scan toolbar visible in dev mode (requires running app to verify)
+- [ ] Component renders highlighted with colored outlines (requires running app to verify)
+- [ ] Console logs show render activity (requires running app to verify)
+- [ ] Unnecessary renders detected and reported (requires running app to verify)
+- [x] No performance impact in production builds (react-scan only enabled in dev mode)
+- [x] Type safety maintained (TypeScript errors = 0)
+
+## Notes
+
+- React-scan only runs in development mode (Vite's `import.meta.env.DEV`)
+- Production builds remain unaffected (no bundle size impact)
+- Toolbar can be hidden by dragging to screen edges
+- Fast animation speed balances visibility with minimal distraction
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 53ff3a9..b822417 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -226,6 +226,9 @@ importers:
react-dom:
specifier: ^19.2.0
version: 19.2.4(react@19.2.4)
+ react-scan:
+ specifier: ^0.4.3
+ version: 0.4.3(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1)
tailwind-merge:
specifier: ^3.3.0
version: 3.4.0
@@ -1605,6 +1608,12 @@ packages:
'@changesets/write@0.4.0':
resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==}
+ '@clack/core@0.3.5':
+ resolution: {integrity: sha512-5cfhQNH+1VQ2xLQlmzXMqUoiaH0lRBq9/CLW9lTyMbuKLC3+xEK01tHVvyut++mLOn5urSHmkm6I0Lg9MaJSTQ==}
+
+ '@clack/prompts@0.8.2':
+ resolution: {integrity: sha512-6b9Ab2UiZwJYA9iMyboYyW9yJvAO9V753ZhS+DHKEjZRKAxPPOb7MXXu84lsPFG+vZt6FRFniZ8rXi+zCIw4yQ==}
+
'@clerk/clerk-react@5.60.0':
resolution: {integrity: sha512-P88FncsJpq/3WZJhhlj+md8mYb35BIXpr462C/figwsBGHsinr8VuBQUMcMZZ/6M34C8ABfLTPa6PHVp6+3D5Q==}
engines: {node: '>=18.17.0'}
@@ -2605,6 +2614,12 @@ packages:
react: ^18.3.1 || ^19.0.0
react-dom: ^18.3.1 || ^19.0.0
+ '@pivanov/utils@0.0.2':
+ resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==}
+ peerDependencies:
+ react: '>=18'
+ react-dom: '>=18'
+
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
@@ -2617,6 +2632,14 @@ packages:
'@polka/url@1.0.0-next.29':
resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
+ '@preact/signals-core@1.12.2':
+ resolution: {integrity: sha512-5Yf8h1Ke3SMHr15xl630KtwPTW4sYDFkkxS0vQ8UiQLWwZQnrF9IKaVG1mN5VcJz52EcWs2acsc/Npjha/7ysA==}
+
+ '@preact/signals@1.3.3':
+ resolution: {integrity: sha512-FieIS2Z3iHAmjYTqsD3U3DEad1/bOicGN8wT34bbrdp422kQfkP7iwYgqTgx53wPdnsENaNkNNmScbi3RVZq8Q==}
+ peerDependencies:
+ preact: 10.x
+
'@publint/pack@0.1.3':
resolution: {integrity: sha512-dHDWeutAerz+Z2wFYAce7Y51vd4rbLBfUh0BNnyul4xKoVsPUVJBrOAFsJvtvYBwGFJSqKsxyyHf/7evZ8+Q5Q==}
engines: {node: '>=18'}
@@ -4243,6 +4266,9 @@ packages:
'@types/node@12.20.55':
resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==}
+ '@types/node@20.19.30':
+ resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==}
+
'@types/node@22.19.7':
resolution: {integrity: sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==}
@@ -4260,6 +4286,11 @@ packages:
peerDependencies:
'@types/react': ^19.2.0
+ '@types/react-reconciler@0.28.9':
+ resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==}
+ peerDependencies:
+ '@types/react': '*'
+
'@types/react-syntax-highlighter@15.5.13':
resolution: {integrity: sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==}
@@ -5209,6 +5240,11 @@ packages:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ bippy@0.3.34:
+ resolution: {integrity: sha512-vmptmU/20UdIWHHhq7qCSHhHzK7Ro3YJ1utU0fBG7ujUc58LEfTtilKxcF0IOgSjT5XLcm7CBzDjbv4lcKApGQ==}
+ peerDependencies:
+ react: '>=17.0.1'
+
birpc@2.9.0:
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
@@ -6715,6 +6751,10 @@ packages:
resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
engines: {node: '>=6'}
+ kleur@4.1.5:
+ resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
+ engines: {node: '>=6'}
+
knip@5.82.1:
resolution: {integrity: sha512-1nQk+5AcnkqL40kGQXfouzAEXkTR+eSrgo/8m1d0BMei4eAzFwghoXC4gOKbACgBiCof7hE8wkBVDsEvznf85w==}
engines: {node: '>=18.18.0'}
@@ -7561,6 +7601,9 @@ packages:
engines: {node: '>=14.0.0'}
hasBin: true
+ preact@10.28.3:
+ resolution: {integrity: sha512-tCmoRkPQLpBeWzpmbhryairGnhW9tKV6c6gr/w+RhoRoKEJwsjzipwp//1oCpGPOchvSLaAPlpcJi9MwMmoPyA==}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -7798,6 +7841,26 @@ packages:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
+ react-scan@0.4.3:
+ resolution: {integrity: sha512-jhAQuQ1nja6HUYrSpbmNFHqZPsRCXk8Yqu0lHoRIw9eb8N96uTfXCpVyQhTTnJ/nWqnwuvxbpKVG/oWZT8+iTQ==}
+ hasBin: true
+ peerDependencies:
+ '@remix-run/react': '>=1.0.0'
+ next: '>=13.0.0'
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-router: ^5.0.0 || ^6.0.0 || ^7.0.0
+ react-router-dom: ^5.0.0 || ^6.0.0 || ^7.0.0
+ peerDependenciesMeta:
+ '@remix-run/react':
+ optional: true
+ next:
+ optional: true
+ react-router:
+ optional: true
+ react-router-dom:
+ optional: true
+
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -8693,6 +8756,10 @@ packages:
resolution: {integrity: sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==}
engines: {node: '>=14.0.0'}
+ unplugin@2.1.0:
+ resolution: {integrity: sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==}
+ engines: {node: '>=18.12.0'}
+
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
@@ -9691,6 +9758,17 @@ snapshots:
human-id: 4.1.3
prettier: 2.8.8
+ '@clack/core@0.3.5':
+ dependencies:
+ picocolors: 1.1.1
+ sisteransi: 1.0.5
+
+ '@clack/prompts@0.8.2':
+ dependencies:
+ '@clack/core': 0.3.5
+ picocolors: 1.1.1
+ sisteransi: 1.0.5
+
'@clerk/clerk-react@5.60.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
'@clerk/shared': 3.44.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -10607,6 +10685,11 @@ snapshots:
react-dom: 19.2.4(react@19.2.4)
shiki: 3.12.2
+ '@pivanov/utils@0.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -10616,6 +10699,13 @@ snapshots:
'@polka/url@1.0.0-next.29': {}
+ '@preact/signals-core@1.12.2': {}
+
+ '@preact/signals@1.3.3(preact@10.28.3)':
+ dependencies:
+ '@preact/signals-core': 1.12.2
+ preact: 10.28.3
+
'@publint/pack@0.1.3': {}
'@quansync/fs@1.0.0':
@@ -12200,6 +12290,10 @@ snapshots:
'@types/node@12.20.55': {}
+ '@types/node@20.19.30':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/node@22.19.7':
dependencies:
undici-types: 6.21.0
@@ -12218,6 +12312,10 @@ snapshots:
dependencies:
'@types/react': 19.2.10
+ '@types/react-reconciler@0.28.9(@types/react@19.2.10)':
+ dependencies:
+ '@types/react': 19.2.10
+
'@types/react-syntax-highlighter@15.5.13':
dependencies:
'@types/react': 19.2.10
@@ -13951,6 +14049,13 @@ snapshots:
binary-extensions@2.3.0: {}
+ bippy@0.3.34(@types/react@19.2.10)(react@19.2.4):
+ dependencies:
+ '@types/react-reconciler': 0.28.9(@types/react@19.2.10)
+ react: 19.2.4
+ transitivePeerDependencies:
+ - '@types/react'
+
birpc@2.9.0: {}
bl@4.1.0:
@@ -15577,6 +15682,8 @@ snapshots:
kleur@3.0.3: {}
+ kleur@4.1.5: {}
+
knip@5.82.1(@types/node@22.19.7)(typescript@5.9.3):
dependencies:
'@nodelib/fs.walk': 1.2.8
@@ -16490,6 +16597,8 @@ snapshots:
commander: 9.5.0
optional: true
+ preact@10.28.3: {}
+
prelude-ls@1.2.1: {}
prettier-plugin-tailwindcss@0.6.14(@trivago/prettier-plugin-sort-imports@5.2.2(@vue/compiler-sfc@3.5.27)(prettier@3.8.1))(prettier@3.8.1):
@@ -16712,6 +16821,34 @@ snapshots:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
+ react-scan@0.4.3(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(rollup@4.57.1):
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/types': 7.28.6
+ '@clack/core': 0.3.5
+ '@clack/prompts': 0.8.2
+ '@pivanov/utils': 0.0.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@preact/signals': 1.3.3(preact@10.28.3)
+ '@rollup/pluginutils': 5.3.0(rollup@4.57.1)
+ '@types/node': 20.19.30
+ bippy: 0.3.34(@types/react@19.2.10)(react@19.2.4)
+ esbuild: 0.25.12
+ estree-walker: 3.0.3
+ kleur: 4.1.5
+ mri: 1.2.0
+ playwright: 1.58.1
+ preact: 10.28.3
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ tsx: 4.21.0
+ optionalDependencies:
+ unplugin: 2.1.0
+ transitivePeerDependencies:
+ - '@types/react'
+ - rollup
+ - supports-color
+
react-style-singleton@2.2.3(@types/react@19.2.10)(react@19.2.4):
dependencies:
get-nonce: 1.0.1
@@ -17669,6 +17806,12 @@ snapshots:
acorn: 8.15.0
webpack-virtual-modules: 0.6.2
+ unplugin@2.1.0:
+ dependencies:
+ acorn: 8.15.0
+ webpack-virtual-modules: 0.6.2
+ optional: true
+
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5