@pennyfarthing/cyclist 10.2.0 → 10.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/LICENSE +14 -0
  2. package/dist/api/agent-load.js +1 -1
  3. package/dist/api/agent-load.js.map +1 -1
  4. package/dist/api/theme-agents.js +2 -2
  5. package/dist/api/theme-agents.js.map +1 -1
  6. package/dist/bikerack.d.ts +2 -0
  7. package/dist/bikerack.d.ts.map +1 -0
  8. package/dist/bikerack.js +43 -0
  9. package/dist/bikerack.js.map +1 -0
  10. package/dist/hooks/cyclist-pretooluse-hook.d.ts +60 -0
  11. package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +1 -0
  12. package/dist/hooks/cyclist-pretooluse-hook.js +57 -0
  13. package/dist/hooks/cyclist-pretooluse-hook.js.map +1 -0
  14. package/dist/hooks/pretooluse-hook.d.ts +89 -0
  15. package/dist/hooks/pretooluse-hook.d.ts.map +1 -0
  16. package/dist/hooks/pretooluse-hook.js +235 -0
  17. package/dist/hooks/pretooluse-hook.js.map +1 -0
  18. package/dist/notification-sound.d.ts +59 -0
  19. package/dist/notification-sound.d.ts.map +1 -0
  20. package/dist/notification-sound.js +219 -0
  21. package/dist/notification-sound.js.map +1 -0
  22. package/dist/plugin-loader.test.d.ts +17 -0
  23. package/dist/plugin-loader.test.d.ts.map +1 -0
  24. package/dist/plugin-loader.test.js +407 -0
  25. package/dist/plugin-loader.test.js.map +1 -0
  26. package/dist/public/css/react.css +1 -1
  27. package/dist/public/js/react/react.js +32 -32
  28. package/dist/server.d.ts +1 -0
  29. package/dist/server.d.ts.map +1 -1
  30. package/dist/server.js +9 -0
  31. package/dist/server.js.map +1 -1
  32. package/dist/sprint-data.d.ts +21 -0
  33. package/dist/sprint-data.d.ts.map +1 -1
  34. package/dist/sprint-data.js +26 -1
  35. package/dist/sprint-data.js.map +1 -1
  36. package/dist/theme-metadata.js +1 -1
  37. package/dist/theme-metadata.js.map +1 -1
  38. package/dist/websocket.js +2 -2
  39. package/dist/websocket.js.map +1 -1
  40. package/package.json +32 -33
  41. package/portraits/a-team/large/face-44442.png +0 -0
  42. package/portraits/a-team/medium/face-44442.png +0 -0
  43. package/portraits/alice-in-wonderland/large/tweedles-44342.png +0 -0
  44. package/portraits/alice-in-wonderland/medium/tweedles-44342.png +0 -0
  45. package/portraits/battlestar-galactica/large/baltar-53343.png +0 -0
  46. package/portraits/battlestar-galactica/medium/baltar-53343.png +0 -0
  47. package/portraits/blade-runner/large/tyrell-54232.png +0 -0
  48. package/portraits/blade-runner/medium/tyrell-54232.png +0 -0
  49. package/portraits/catch-22/large/major-34253.png +0 -0
  50. package/portraits/catch-22/medium/major-34253.png +0 -0
  51. package/portraits/control/large/burt-44342.png +0 -0
  52. package/portraits/control/medium/burt-44342.png +0 -0
  53. package/portraits/cowboy-bebop/large/ed-54342.png +0 -0
  54. package/portraits/cowboy-bebop/medium/ed-54342.png +0 -0
  55. package/portraits/discworld/large/moist-44342.png +0 -0
  56. package/portraits/discworld/medium/moist-44342.png +0 -0
  57. package/portraits/doctor-who/large/sarah-jane-44342.png +0 -0
  58. package/portraits/doctor-who/medium/sarah-jane-44342.png +0 -0
  59. package/portraits/dune/large/thufir-44342.png +0 -0
  60. package/portraits/dune/medium/thufir-44342.png +0 -0
  61. package/portraits/fifth-element/large/cornelius-54343.png +0 -0
  62. package/portraits/fifth-element/large/diva-53453.png +0 -0
  63. package/portraits/fifth-element/large/korben-34232.png +0 -0
  64. package/portraits/fifth-element/large/leeloo-54333.png +0 -0
  65. package/portraits/fifth-element/large/lindberg-34432.png +0 -0
  66. package/portraits/fifth-element/large/lindberg-44342.png +0 -0
  67. package/portraits/fifth-element/large/mondoshawan-55131.png +0 -0
  68. package/portraits/fifth-element/large/munro-25321.png +0 -0
  69. package/portraits/fifth-element/large/pacoli-45232.png +0 -0
  70. package/portraits/fifth-element/large/ruby-53544.png +0 -0
  71. package/portraits/fifth-element/large/zorg-45312.png +0 -0
  72. package/portraits/fifth-element/medium/cornelius-54343.png +0 -0
  73. package/portraits/fifth-element/medium/diva-53453.png +0 -0
  74. package/portraits/fifth-element/medium/korben-34232.png +0 -0
  75. package/portraits/fifth-element/medium/leeloo-54333.png +0 -0
  76. package/portraits/fifth-element/medium/lindberg-34432.png +0 -0
  77. package/portraits/fifth-element/medium/lindberg-44342.png +0 -0
  78. package/portraits/fifth-element/medium/mondoshawan-55131.png +0 -0
  79. package/portraits/fifth-element/medium/munro-25321.png +0 -0
  80. package/portraits/fifth-element/medium/pacoli-45232.png +0 -0
  81. package/portraits/fifth-element/medium/ruby-53544.png +0 -0
  82. package/portraits/fifth-element/medium/zorg-45312.png +0 -0
  83. package/portraits/firefly/large/book-44342.png +0 -0
  84. package/portraits/firefly/medium/book-44342.png +0 -0
  85. package/portraits/game-of-thrones/large/varys-44342.png +0 -0
  86. package/portraits/game-of-thrones/medium/varys-44342.png +0 -0
  87. package/portraits/harry-potter/large/hermione-44342.png +0 -0
  88. package/portraits/harry-potter/medium/hermione-44342.png +0 -0
  89. package/portraits/hitchhikers-guide/large/trillian-44342.png +0 -0
  90. package/portraits/hitchhikers-guide/medium/trillian-44342.png +0 -0
  91. package/portraits/lord-of-the-rings/large/elrond-44342.png +0 -0
  92. package/portraits/lord-of-the-rings/medium/elrond-44342.png +0 -0
  93. package/portraits/mad-max/large/the-dag-44342.png +0 -0
  94. package/portraits/mad-max/medium/the-dag-44342.png +0 -0
  95. package/portraits/mash/large/hawkeye-52544.png +0 -0
  96. package/portraits/mash/large/klinger-33543.png +0 -0
  97. package/portraits/mash/large/margaret-34343.png +0 -0
  98. package/portraits/mash/large/margaret-45443.png +0 -0
  99. package/portraits/mash/large/mulcahy-34352.png +0 -0
  100. package/portraits/mash/large/potter-44443.png +0 -0
  101. package/portraits/mash/large/potter-45342.png +0 -0
  102. package/portraits/mash/large/radar-24342.png +0 -0
  103. package/portraits/mash/large/radar-35254.png +0 -0
  104. package/portraits/mash/large/radar-45242.png +0 -0
  105. package/portraits/mash/large/winchester-55322.png +0 -0
  106. package/portraits/mash/medium/hawkeye-52544.png +0 -0
  107. package/portraits/mash/medium/klinger-33543.png +0 -0
  108. package/portraits/mash/medium/margaret-34343.png +0 -0
  109. package/portraits/mash/medium/margaret-45443.png +0 -0
  110. package/portraits/mash/medium/mulcahy-34352.png +0 -0
  111. package/portraits/mash/medium/potter-44443.png +0 -0
  112. package/portraits/mash/medium/potter-45342.png +0 -0
  113. package/portraits/mash/medium/radar-24342.png +0 -0
  114. package/portraits/mash/medium/radar-35254.png +0 -0
  115. package/portraits/mash/medium/radar-45242.png +0 -0
  116. package/portraits/mash/medium/winchester-55322.png +0 -0
  117. package/portraits/princess-bride/large/vizzini-54342.png +0 -0
  118. package/portraits/princess-bride/medium/vizzini-54342.png +0 -0
  119. package/portraits/sandman/large/lucien-54342.png +0 -0
  120. package/portraits/sandman/medium/lucien-54342.png +0 -0
  121. package/portraits/star-trek-tng/large/troi-44352.png +0 -0
  122. package/portraits/star-trek-tng/medium/troi-44352.png +0 -0
  123. package/portraits/star-wars/large/mothma-44342.png +0 -0
  124. package/portraits/star-wars/medium/mothma-44342.png +0 -0
  125. package/portraits/the-expanse/large/avasarala-44342.png +0 -0
  126. package/portraits/the-expanse/medium/avasarala-44342.png +0 -0
  127. package/portraits/the-matrix/large/oracle-44342.png +0 -0
  128. package/portraits/the-matrix/medium/oracle-44342.png +0 -0
  129. package/portraits/watchmen/large/veidt-44342.png +0 -0
  130. package/portraits/watchmen/medium/veidt-44342.png +0 -0
  131. package/portraits/west-wing/large/c-j-44342.png +0 -0
  132. package/portraits/west-wing/medium/c-j-44342.png +0 -0
  133. package/portraits/x-files/large/gunmen-44342.png +0 -0
  134. package/portraits/x-files/medium/gunmen-44342.png +0 -0
  135. package/src/public/App.tsx +34 -0
  136. package/src/public/components/AgentPopup.tsx +1 -0
  137. package/src/public/components/BikeRackIndex.tsx +54 -0
  138. package/src/public/components/BikeRackWorkspace.tsx +142 -0
  139. package/src/public/components/DockviewWorkspace.tsx +5 -6
  140. package/src/public/components/MessageView.tsx +2 -1
  141. package/src/public/components/PersonaHeader.tsx +2 -0
  142. package/src/public/components/StandalonePanel.tsx +84 -0
  143. package/src/public/components/TandemPortrait.tsx +1 -0
  144. package/src/public/components/panel-registry.ts +11 -0
  145. package/src/public/styles/dockview-theme.css +1 -1
  146. package/src/public/types/electron.d.ts +18 -0
  147. package/src/public/utils/messageFilters.ts +1 -0
  148. package/src/public/utils/slash-commands.ts +4 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pennyfarthing/cyclist",
3
- "version": "10.2.0",
3
+ "version": "10.3.1",
4
4
  "description": "Visual terminal interface for Claude Code",
5
5
  "author": "1898andCo",
6
6
  "type": "module",
@@ -19,37 +19,7 @@
19
19
  "src/public/",
20
20
  "portraits/"
21
21
  ],
22
- "scripts": {
23
- "dev": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron dist/main.js\"",
24
- "dev:cdp": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --remote-debugging-port=9222 dist/main.js\"",
25
- "dev:once": "npm run build && electron dist/main.js",
26
- "dev:web": "CYCLIST_ELECTRON_MODE= CYCLIST_DEV_WEB=1 CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} tsx watch src/server.ts",
27
- "dev:server": "tsx watch src/server.ts",
28
- "dev:debug": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect src/server.ts",
29
- "dev:debug-brk": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect-brk src/server.ts",
30
- "dev:electron-debug": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --inspect=9229 dist/main.js\"",
31
- "dev:vite": "vite --config vite.config.ts",
32
- "lint": "eslint src/",
33
- "build": "npm run build:commands && tsc --build && tsc -p tsconfig.preload.json && npm run build:react",
34
- "build:react": "vite build",
35
- "build:commands": "node scripts/generate-slash-commands.js",
36
- "build:electron": "npm run build && electron-builder -c.npmRebuild=false",
37
- "start": "node dist/server.js",
38
- "test": "vitest run",
39
- "test:watch": "vitest",
40
- "test:e2e": "playwright test",
41
- "test:e2e:web": "playwright test --project=web-chromium",
42
- "test:e2e:ui": "playwright test --ui",
43
- "test:e2e:debug": "playwright test --debug",
44
- "test:e2e:trace": "playwright show-trace",
45
- "prepack": "./scripts/copy-portraits.sh",
46
- "install:app": "./scripts/install-app.sh",
47
- "install:cli": "./scripts/install-cli.sh",
48
- "install:all": "npm run install:app && npm run install:cli"
49
- },
50
22
  "dependencies": {
51
- "@pennyfarthing/core": "workspace:^",
52
- "@pennyfarthing/shared": "workspace:^",
53
23
  "@radix-ui/react-alert-dialog": "^1.1.15",
54
24
  "@radix-ui/react-checkbox": "^1.3.3",
55
25
  "@radix-ui/react-collapsible": "^1.1.12",
@@ -79,7 +49,9 @@
79
49
  "ws": "^8.19.0",
80
50
  "xterm": "^5.3.0",
81
51
  "xterm-addon-fit": "^0.8.0",
82
- "yaml": "^2.8.2"
52
+ "yaml": "^2.8.2",
53
+ "@pennyfarthing/core": "^10.3.1",
54
+ "@pennyfarthing/shared": "^10.3.1"
83
55
  },
84
56
  "devDependencies": {
85
57
  "@electron/rebuild": "^3.6.0",
@@ -187,5 +159,32 @@
187
159
  ],
188
160
  "category": "Development"
189
161
  }
162
+ },
163
+ "scripts": {
164
+ "dev": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron dist/main.js\"",
165
+ "dev:cdp": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --remote-debugging-port=9222 dist/main.js\"",
166
+ "dev:once": "npm run build && electron dist/main.js",
167
+ "dev:web": "CYCLIST_ELECTRON_MODE= CYCLIST_DEV_WEB=1 CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} tsx watch src/server.ts",
168
+ "dev:server": "tsx watch src/server.ts",
169
+ "dev:debug": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect src/server.ts",
170
+ "dev:debug-brk": "CYCLIST_DEV_WEB=1 OTEL_DEBUG=true CYCLIST_PROJECT_DIR=${CYCLIST_PROJECT_DIR:-$PWD} node --inspect-brk src/server.ts",
171
+ "dev:electron-debug": "npm run build && concurrently -k -n tsc,preload,vite,electron -c blue,cyan,magenta,green \"tsc --watch --preserveWatchOutput\" \"tsc -p tsconfig.preload.json --watch --preserveWatchOutput\" \"vite build --watch\" \"electron --inspect=9229 dist/main.js\"",
172
+ "dev:vite": "vite --config vite.config.ts",
173
+ "lint": "eslint src/",
174
+ "build": "npm run build:commands && tsc --build && tsc -p tsconfig.preload.json && npm run build:react",
175
+ "build:react": "vite build",
176
+ "build:commands": "node scripts/generate-slash-commands.js",
177
+ "build:electron": "npm run build && electron-builder -c.npmRebuild=false",
178
+ "start": "node dist/server.js",
179
+ "test": "vitest run",
180
+ "test:watch": "vitest",
181
+ "test:e2e": "playwright test",
182
+ "test:e2e:web": "playwright test --project=web-chromium",
183
+ "test:e2e:ui": "playwright test --ui",
184
+ "test:e2e:debug": "playwright test --debug",
185
+ "test:e2e:trace": "playwright show-trace",
186
+ "install:app": "./scripts/install-app.sh",
187
+ "install:cli": "./scripts/install-cli.sh",
188
+ "install:all": "npm run install:app && npm run install:cli"
190
189
  }
191
- }
190
+ }
@@ -17,11 +17,14 @@ import {
17
17
  } from './components/DockviewWorkspace';
18
18
  import { CommandPaletteProvider } from './components/CommandPalette';
19
19
  import { ClaudeProvider } from './contexts/ClaudeContext';
20
+ import ClaudeContext from './contexts/ClaudeContext';
20
21
  import { MessageQueueProvider } from './contexts/MessageQueueContext';
21
22
  import { useLayoutPersistence } from './hooks/useLayoutPersistence';
22
23
  import { loadFontSettings, applyFontSettings } from './utils/font-presets';
23
24
  import { loadPresetFromProject, applyPreset } from './utils/color-presets';
24
25
  import { ErrorBoundary } from './components/ErrorBoundary';
26
+ import { StandalonePanel, getStandalonePanelName } from './components/StandalonePanel';
27
+ import { BikeRackWorkspace } from './components/BikeRackWorkspace';
25
28
  import ApprovalModal, { useApprovalModal } from './components/ApprovalModal';
26
29
  import { subscribeToPermissionRequests, sendPermissionResponse, createApprovalResponse } from './components/ApprovalModal';
27
30
  import type { ApprovalRequest, GrantScope } from './components/ApprovalModal';
@@ -198,6 +201,12 @@ function RootErrorFallback(): React.ReactElement {
198
201
  // =============================================================================
199
202
 
200
203
  export default function App(): React.ReactElement {
204
+ // Detect route mode (computed before hooks, used after)
205
+ const isBikeRackIndex = window.location.pathname === '/bikerack';
206
+ const standalonePanelName = getStandalonePanelName();
207
+
208
+ // --- All hooks called unconditionally (React rules of hooks) ---
209
+
201
210
  const { layout, isLoading, saveLayout } = useLayoutPersistence();
202
211
 
203
212
  // Set up reduced motion support
@@ -254,6 +263,31 @@ export default function App(): React.ReactElement {
254
263
  hide();
255
264
  }, [request, hide]);
256
265
 
266
+ // --- BikeRack routes (after all hooks) ---
267
+
268
+ // BikeRack Dockview workspace (MSSCI-14877) — /bikerack renders Dockview layout
269
+ // No-op ClaudeContext: BikeRack has no Claude CLI subprocess, skip WebSocket
270
+ if (isBikeRackIndex) {
271
+ const noop = () => () => {};
272
+ return (
273
+ <ClaudeContext.Provider value={{
274
+ send: () => {}, abort: () => {}, clear: () => {},
275
+ clearAndReload: () => {}, setMode: () => {},
276
+ isConnected: false, mode: 'default',
277
+ onMessage: noop, onComplete: noop, onError: noop,
278
+ onUserMessage: noop, onClear: noop,
279
+ }}>
280
+ <BikeRackWorkspace />
281
+ </ClaudeContext.Provider>
282
+ );
283
+ }
284
+
285
+ // BikeRack standalone panel routing (MSSCI-14821)
286
+ // URL-based detection only (Rule 10) — ?panel=X renders single panel full-screen
287
+ if (standalonePanelName) {
288
+ return <StandalonePanel />;
289
+ }
290
+
257
291
  return (
258
292
  <ErrorBoundary fallback={<RootErrorFallback />} panelName="App">
259
293
  <ClaudeProvider>
@@ -61,6 +61,7 @@ const AGENT_COLORS: Record<string, string> = {
61
61
  'ux-designer': '#f0abfc',
62
62
  'tech-writer': '#e5e5e5',
63
63
  orchestrator: '#e879f9',
64
+ ba: '#a3e635',
64
65
  };
65
66
 
66
67
  // =============================================================================
@@ -0,0 +1,54 @@
1
+ /**
2
+ * BikeRackIndex - Index page listing all available panels
3
+ *
4
+ * Story MSSCI-14822: BikeRackIndex panel listing page
5
+ * Epic: 101 (BikeRack Mode)
6
+ *
7
+ * Lists all 13 panels with links to standalone mode via ?panel=X.
8
+ * Styled with Tailwind dark mode, consistent with Cyclist.
9
+ *
10
+ * Rules:
11
+ * - No dockview-react imports (Rule 7)
12
+ * - No BikeRack-specific props (Rule 2)
13
+ * - URL-based detection only (Rule 10)
14
+ */
15
+
16
+ import React from 'react';
17
+
18
+ const PANELS = [
19
+ { id: 'sprint', label: 'Sprint', description: 'Story tracking and sprint progress' },
20
+ { id: 'git', label: 'Git', description: 'Repository status and branches' },
21
+ { id: 'diffs', label: 'Diffs', description: 'Code changes and diffs' },
22
+ { id: 'todos', label: 'Todos', description: 'Acceptance criteria tracking' },
23
+ { id: 'workflow', label: 'Workflow', description: 'Current workflow state' },
24
+ { id: 'background', label: 'Background', description: 'Background tasks' },
25
+ { id: 'audit', label: 'Audit', description: 'OTEL spans and logs' },
26
+ { id: 'changed', label: 'Changed', description: 'Changed files' },
27
+ { id: 'ac', label: 'AC', description: 'Acceptance criteria detail' },
28
+ { id: 'tty', label: 'TTY', description: 'Terminal output' },
29
+ { id: 'debug', label: 'Debug', description: 'Debug information' },
30
+ { id: 'bikelane', label: 'BikeLane', description: 'Workflow visualization' },
31
+ { id: 'settings', label: 'Settings', description: 'Theme, fonts, and display preferences' },
32
+ ] as const;
33
+
34
+ export function BikeRackIndex(): React.ReactElement {
35
+ return (
36
+ <div className="bg-slate-900 min-h-screen text-gray-200 p-8">
37
+ <div className="max-w-4xl mx-auto">
38
+ <h1 className="text-3xl font-bold text-white mb-8">BikeRack</h1>
39
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
40
+ {PANELS.map((panel) => (
41
+ <a
42
+ key={panel.id}
43
+ href={`/?panel=${panel.id}`}
44
+ className="block border border-slate-700 rounded-lg p-4 hover:bg-slate-800 hover:text-cyan-400 transition-colors"
45
+ >
46
+ <div className="text-lg font-semibold text-white mb-1">{panel.label}</div>
47
+ <p className="text-sm text-gray-400">{panel.description}</p>
48
+ </a>
49
+ ))}
50
+ </div>
51
+ </div>
52
+ </div>
53
+ );
54
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * BikeRackWorkspace - Dockview-based panel layout for BikeRack mode
3
+ *
4
+ * Story MSSCI-14877: Migrate BikeRack from index page to Dockview layout
5
+ * Epic: 102 (BikeRack Follow-up)
6
+ *
7
+ * Replaces BikeRackIndex with a proper Dockview layout.
8
+ * No MessagePanel (sacred center) — BikeRack is a monitoring dashboard.
9
+ * Single Dockview group — users can freely rearrange panels.
10
+ */
11
+
12
+ import React, { useCallback, useRef } from 'react';
13
+ import {
14
+ DockviewReact,
15
+ DockviewReadyEvent,
16
+ DockviewApi,
17
+ IDockviewPanelProps,
18
+ } from 'dockview-react';
19
+ import 'dockview-react/dist/styles/dockview.css';
20
+ import { ErrorBoundary } from './ErrorBoundary';
21
+ import { panelRegistry } from './panel-registry';
22
+ import PersonaHeader from './PersonaHeader.js';
23
+ import '../styles/dockview-theme.css';
24
+
25
+ // =============================================================================
26
+ // BikeRack Panel Definitions
27
+ // =============================================================================
28
+
29
+ /**
30
+ * Panels included in BikeRack Dockview mode.
31
+ * Unlike base Cyclist, BikeRack does NOT include MessagePanel.
32
+ */
33
+ export const BIKERACK_PANELS: string[] = [
34
+ 'sprint',
35
+ 'git',
36
+ 'diffs',
37
+ 'todo',
38
+ 'workflow',
39
+ 'background',
40
+ 'audit-log',
41
+ 'changed',
42
+ 'ac',
43
+ 'debug',
44
+ 'settings',
45
+ ];
46
+
47
+ const PANEL_TITLES: Record<string, string> = {
48
+ sprint: 'Sprint',
49
+ git: 'Git',
50
+ diffs: 'Diffs',
51
+ todo: 'Todo',
52
+ workflow: 'Workflow',
53
+ background: 'Subagents',
54
+ 'audit-log': 'Audit Log',
55
+ changed: 'Changed',
56
+ ac: 'AC',
57
+ debug: 'Debug',
58
+ settings: 'Settings',
59
+ };
60
+
61
+ // =============================================================================
62
+ // Panel Adapter
63
+ // =============================================================================
64
+
65
+ interface PanelAdapterParams {
66
+ panelId: string;
67
+ }
68
+
69
+ function PanelAdapter({ params }: IDockviewPanelProps<PanelAdapterParams>): React.ReactElement | null {
70
+ const Component = panelRegistry.get(params.panelId);
71
+
72
+ if (!Component) {
73
+ return (
74
+ <div data-testid={`panel-${params.panelId}`} className="dockview-panel-content">
75
+ <div style={{ padding: '16px', color: 'var(--text-secondary, #94a3b8)' }}>
76
+ {PANEL_TITLES[params.panelId] || params.panelId}
77
+ </div>
78
+ </div>
79
+ );
80
+ }
81
+
82
+ return (
83
+ <div data-testid={`panel-${params.panelId}`} className="dockview-panel-content">
84
+ <ErrorBoundary panelName={params.panelId}>
85
+ <div className="error-boundary-wrapper">
86
+ <Component />
87
+ </div>
88
+ </ErrorBoundary>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ // =============================================================================
94
+ // BikeRackWorkspace Component
95
+ // =============================================================================
96
+
97
+ export function BikeRackWorkspace(): React.ReactElement {
98
+ const apiRef = useRef<DockviewApi | null>(null);
99
+
100
+ const onReady = useCallback((event: DockviewReadyEvent) => {
101
+ const api = event.api;
102
+ apiRef.current = api;
103
+
104
+ // Single group — all panels as tabs, user can rearrange freely
105
+ const first = api.addPanel({
106
+ id: BIKERACK_PANELS[0],
107
+ component: 'PanelAdapter',
108
+ params: { panelId: BIKERACK_PANELS[0] },
109
+ title: PANEL_TITLES[BIKERACK_PANELS[0]],
110
+ });
111
+
112
+ for (let i = 1; i < BIKERACK_PANELS.length; i++) {
113
+ api.addPanel({
114
+ id: BIKERACK_PANELS[i],
115
+ component: 'PanelAdapter',
116
+ params: { panelId: BIKERACK_PANELS[i] },
117
+ position: { referencePanel: first.id },
118
+ title: PANEL_TITLES[BIKERACK_PANELS[i]],
119
+ });
120
+ }
121
+ }, []);
122
+
123
+ const components = { PanelAdapter };
124
+
125
+ return (
126
+ <div className="cyclist-app cyclist-dockview" style={{ height: '100vh', width: '100vw', display: 'flex', flexDirection: 'column' }}>
127
+ <div data-testid="bikerack-portrait-anchor" style={{ flexShrink: 0 }}>
128
+ <PersonaHeader />
129
+ </div>
130
+ <div className="flex-1" style={{ flexGrow: 1, minHeight: 0 }}>
131
+ <DockviewReact
132
+ className="dockview-container"
133
+ onReady={onReady}
134
+ components={components}
135
+ watermarkComponent={() => null}
136
+ />
137
+ </div>
138
+ </div>
139
+ );
140
+ }
141
+
142
+ export default BikeRackWorkspace;