@pennyfarthing/cyclist 9.4.0 → 10.0.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 (78) hide show
  1. package/LICENSE +14 -0
  2. package/dist/api/hook-request.d.ts +11 -0
  3. package/dist/api/hook-request.d.ts.map +1 -1
  4. package/dist/api/hook-request.js +126 -28
  5. package/dist/api/hook-request.js.map +1 -1
  6. package/dist/api/index.d.ts +1 -0
  7. package/dist/api/index.d.ts.map +1 -1
  8. package/dist/api/index.js +2 -0
  9. package/dist/api/index.js.map +1 -1
  10. package/dist/api/permissions.d.ts +16 -0
  11. package/dist/api/permissions.d.ts.map +1 -0
  12. package/dist/api/permissions.js +67 -0
  13. package/dist/api/permissions.js.map +1 -0
  14. package/dist/api/theme-agents.d.ts +4 -0
  15. package/dist/api/theme-agents.d.ts.map +1 -1
  16. package/dist/api/theme-agents.js +3 -0
  17. package/dist/api/theme-agents.js.map +1 -1
  18. package/dist/approval-gate.d.ts +3 -75
  19. package/dist/approval-gate.d.ts.map +1 -1
  20. package/dist/approval-gate.js +4 -121
  21. package/dist/approval-gate.js.map +1 -1
  22. package/dist/hooks/cyclist-pretooluse-hook.d.ts +60 -0
  23. package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +1 -0
  24. package/dist/hooks/cyclist-pretooluse-hook.js +57 -0
  25. package/dist/hooks/cyclist-pretooluse-hook.js.map +1 -0
  26. package/dist/hooks/pretooluse-hook.d.ts +89 -0
  27. package/dist/hooks/pretooluse-hook.d.ts.map +1 -0
  28. package/dist/hooks/pretooluse-hook.js +235 -0
  29. package/dist/hooks/pretooluse-hook.js.map +1 -0
  30. package/dist/main.d.ts +1 -134
  31. package/dist/main.d.ts.map +1 -1
  32. package/dist/main.js +42 -373
  33. package/dist/main.js.map +1 -1
  34. package/dist/menu-builder.d.ts +7 -1
  35. package/dist/menu-builder.d.ts.map +1 -1
  36. package/dist/menu-builder.js +36 -1
  37. package/dist/menu-builder.js.map +1 -1
  38. package/dist/otlp-receiver.d.ts.map +1 -1
  39. package/dist/otlp-receiver.js +6 -0
  40. package/dist/otlp-receiver.js.map +1 -1
  41. package/dist/public/css/react.css +1 -1
  42. package/dist/public/js/react/react.js +41 -41
  43. package/dist/server.d.ts.map +1 -1
  44. package/dist/server.js +14 -3
  45. package/dist/server.js.map +1 -1
  46. package/dist/settings-store.d.ts +3 -1
  47. package/dist/settings-store.d.ts.map +1 -1
  48. package/dist/settings-store.js +18 -9
  49. package/dist/settings-store.js.map +1 -1
  50. package/dist/websocket.d.ts +1 -0
  51. package/dist/websocket.d.ts.map +1 -1
  52. package/dist/websocket.js +48 -5
  53. package/dist/websocket.js.map +1 -1
  54. package/dist/workflow-presets.d.ts +72 -0
  55. package/dist/workflow-presets.d.ts.map +1 -0
  56. package/dist/workflow-presets.js +93 -0
  57. package/dist/workflow-presets.js.map +1 -0
  58. package/package.json +31 -32
  59. package/src/public/App.tsx +59 -1
  60. package/src/public/components/ApprovalModal/index.tsx +31 -1
  61. package/src/public/components/AskUserQuestionBlock.tsx +162 -0
  62. package/src/public/components/ControlBar.tsx +18 -19
  63. package/src/public/components/DockviewWorkspace.tsx +35 -5
  64. package/src/public/components/Message.tsx +58 -2
  65. package/src/public/components/MessageView.tsx +47 -3
  66. package/src/public/components/PersonaHeader.tsx +3 -1
  67. package/src/public/components/panels/BackgroundPanel.tsx +1 -1
  68. package/src/public/components/panels/MessagePanel.tsx +66 -4
  69. package/src/public/components/panels/SettingsPanel.tsx +3 -28
  70. package/src/public/components/panels/WorkflowPanel.tsx +25 -3
  71. package/src/public/contexts/ClaudeContext.tsx +16 -1
  72. package/src/public/hooks/useColorScheme.ts +27 -0
  73. package/src/public/hooks/usePlanModeExit.ts +105 -0
  74. package/src/public/styles/dockview-theme.css +31 -33
  75. package/src/public/styles/tailwind.css +199 -18
  76. package/src/public/types/message.ts +2 -1
  77. package/src/public/utils/askUserQuestion.ts +21 -0
  78. package/src/public/utils/markdown.ts +2 -2
@@ -32,7 +32,14 @@ interface SDKToolUseBlock {
32
32
  input?: Record<string, unknown>;
33
33
  }
34
34
 
35
- type SDKContentBlock = SDKTextBlock | SDKToolUseBlock | { type: string; text?: string };
35
+ interface SDKToolResultBlock {
36
+ type: 'tool_result';
37
+ tool_use_id?: string;
38
+ content?: string;
39
+ is_error?: boolean;
40
+ }
41
+
42
+ type SDKContentBlock = SDKTextBlock | SDKToolUseBlock | SDKToolResultBlock | { type: string; text?: string };
36
43
 
37
44
  interface SDKMessage {
38
45
  type: string;
@@ -141,7 +148,7 @@ function transformMessage(sdkMessage: SDKMessage): MessageData[] {
141
148
  return results;
142
149
  }
143
150
 
144
- // Handle user messages
151
+ // Handle user messages (may contain tool_result blocks for completed Task tools)
145
152
  if (sdkMessage.type === 'user') {
146
153
  let content = '';
147
154
  const contentArray = sdkMessage.message?.content || sdkMessage.content;
@@ -152,6 +159,23 @@ function transformMessage(sdkMessage: SDKMessage): MessageData[] {
152
159
  )
153
160
  .map(block => block.text)
154
161
  .join('');
162
+
163
+ // MSSCI-14394: Extract tool_result blocks from user messages.
164
+ // The SDK delivers tool results inside user-type messages with tool_use_id
165
+ // matching the original tool_use's tool_id. Without extracting these,
166
+ // the subagent cleanup code never fires and spans accumulate forever.
167
+ const toolResultBlocks = contentArray.filter(
168
+ (block): block is SDKToolResultBlock => block.type === 'tool_result'
169
+ );
170
+ for (const resultBlock of toolResultBlocks) {
171
+ results.push({
172
+ type: 'tool_result',
173
+ tool_id: resultBlock.tool_use_id,
174
+ content: typeof resultBlock.content === 'string' ? resultBlock.content : '',
175
+ timestamp,
176
+ is_error: resultBlock.is_error,
177
+ });
178
+ }
155
179
  } else if (typeof contentArray === 'string') {
156
180
  content = contentArray;
157
181
  }
@@ -216,15 +240,18 @@ export function MessagePanel(): React.ReactElement {
216
240
  isStopping,
217
241
  bellMode,
218
242
  relayMode,
243
+ contextPercent,
244
+ currentAgent,
219
245
  handleStop,
220
246
  handleForceStop,
221
247
  handleReset,
222
248
  handleBellModeChange,
223
249
  handleRelayModeChange,
250
+ handleTirePump,
224
251
  } = useControlBar();
225
252
 
226
253
  // Claude context for WebSocket communication
227
- const { send, abort, onMessage, onComplete, onError, onUserMessage, isConnected } = useClaudeContext();
254
+ const { send, abort, onMessage, onComplete, onError, onUserMessage, onClear, isConnected } = useClaudeContext();
228
255
 
229
256
  // Persona context - capture current persona to stamp on agent messages
230
257
  const { persona } = usePersona();
@@ -272,7 +299,26 @@ export function MessagePanel(): React.ReactElement {
272
299
  ? { ...msg, agentSlug: p.slug ?? undefined, agentTheme: p.theme ?? undefined, agentCharacter: p.character ?? undefined }
273
300
  : msg
274
301
  );
275
- setMessages(prev => [...prev, ...stamped]);
302
+
303
+ // MSSCI-14394: When tool_results arrive for completed Task tools, remove their
304
+ // subagent messages from the view (they have parent_id matching the tool_result's tool_id).
305
+ const completedTaskIds = stamped
306
+ .filter(m => m.type === 'tool_result' && !m.parent_id && m.tool_id)
307
+ .map(m => m.tool_id!);
308
+
309
+ if (completedTaskIds.length > 0) {
310
+ setMessages(prev => {
311
+ const idsToRemove = new Set(completedTaskIds.filter(id =>
312
+ prev.some(m => m.parent_id === id)
313
+ ));
314
+ if (idsToRemove.size > 0) {
315
+ return [...prev.filter(m => !m.parent_id || !idsToRemove.has(m.parent_id)), ...stamped];
316
+ }
317
+ return [...prev, ...stamped];
318
+ });
319
+ } else {
320
+ setMessages(prev => [...prev, ...stamped]);
321
+ }
276
322
  }
277
323
  }, []);
278
324
 
@@ -317,6 +363,19 @@ export function MessagePanel(): React.ReactElement {
317
363
  return cleanup;
318
364
  }, [onUserMessage]);
319
365
 
366
+ // Subscribe to clear events — insert a divider message
367
+ useEffect(() => {
368
+ const cleanup = onClear(() => {
369
+ setMessages(prev => [...prev, {
370
+ type: 'context_cleared',
371
+ content: 'Context cleared',
372
+ timestamp: Date.now(),
373
+ }]);
374
+ setIsProcessing(false);
375
+ });
376
+ return cleanup;
377
+ }, [onClear]);
378
+
320
379
  // Connect to Claude events via WebSocket context
321
380
  useEffect(() => {
322
381
  if (!isConnected) {
@@ -382,6 +441,9 @@ export function MessagePanel(): React.ReactElement {
382
441
  relayMode={relayMode}
383
442
  onBellModeChange={handleBellModeChange}
384
443
  onRelayModeChange={handleRelayModeChange}
444
+ contextPercent={contextPercent}
445
+ currentAgent={currentAgent}
446
+ onTirePump={handleTirePump}
385
447
  />
386
448
  </div>
387
449
  </div>
@@ -45,10 +45,6 @@ interface Settings {
45
45
  show_flow?: boolean;
46
46
  sidebar_width?: number;
47
47
  };
48
- notifications?: {
49
- phase_change?: boolean;
50
- sound?: boolean;
51
- };
52
48
  pennyfarthing?: {
53
49
  theme?: string;
54
50
  };
@@ -80,8 +76,8 @@ const PANEL_DISPLAY_NAMES: Record<string, string> = {
80
76
  settings: 'Settings',
81
77
  };
82
78
 
83
- // Panels that cannot be hidden (sacred center)
84
- const PROTECTED_PANELS = new Set(['message']);
79
+ // Panels that cannot be hidden
80
+ const PROTECTED_PANELS = new Set<string>();
85
81
 
86
82
  export function SettingsPanel(): React.ReactElement {
87
83
  const [settings, setSettings] = useState<Settings | null>(null);
@@ -147,6 +143,7 @@ export function SettingsPanel(): React.ReactElement {
147
143
 
148
144
  // Load color preset from project config
149
145
  loadPresetFromProject().then(presetId => {
146
+ applyPreset(presetId);
150
147
  setColorPreset(presetId);
151
148
  });
152
149
 
@@ -425,28 +422,6 @@ export function SettingsPanel(): React.ReactElement {
425
422
 
426
423
  <Separator className="my-2" />
427
424
 
428
- <section className="settings-section">
429
- <h4>Notifications</h4>
430
- <div className="toggle-setting">
431
- <Switch
432
- checked={settings.notifications?.phase_change || false}
433
- onCheckedChange={(checked: boolean) => handleToggle('notifications', 'phase_change', checked)}
434
- disabled={saving}
435
- />
436
- Phase change alerts
437
- </div>
438
- <div className="toggle-setting">
439
- <Switch
440
- checked={settings.notifications?.sound || false}
441
- onCheckedChange={(checked: boolean) => handleToggle('notifications', 'sound', checked)}
442
- disabled={saving}
443
- />
444
- Sound effects
445
- </div>
446
- </section>
447
-
448
- <Separator className="my-2" />
449
-
450
425
  <section className="settings-section">
451
426
  <h4>Panel Visibility</h4>
452
427
  <div className="panel-visibility-list">
@@ -10,9 +10,11 @@
10
10
  * Epic: epic-76 (Dockview Panel Migration)
11
11
  */
12
12
 
13
- import React from 'react';
13
+ import React, { useCallback } from 'react';
14
14
  import { Badge } from '@/components/ui/badge';
15
+ import { Button } from '@/components/ui/button';
15
16
  import { Skeleton } from '@/components/ui/skeleton';
17
+ import { useClaudeContext } from '../../contexts/ClaudeContext';
16
18
  import { useStory } from '../../hooks/useStory';
17
19
  import type { WorkflowPhase, AvailableWorkflow } from '../../../story-parser.js';
18
20
 
@@ -89,7 +91,10 @@ function SteppedProgress({ phases }: { phases: WorkflowPhase[] }): React.ReactEl
89
91
  // Available Workflows List (MSSCI-14301)
90
92
  // =============================================================================
91
93
 
92
- function AvailableWorkflowsList({ workflows }: { workflows: AvailableWorkflow[] }): React.ReactElement {
94
+ function AvailableWorkflowsList({ workflows, onStart }: {
95
+ workflows: AvailableWorkflow[];
96
+ onStart?: (workflow: AvailableWorkflow) => void;
97
+ }): React.ReactElement {
93
98
  return (
94
99
  <div className="available-workflows">
95
100
  <div className="available-workflows-header">
@@ -113,6 +118,17 @@ function AvailableWorkflowsList({ workflows }: { workflows: AvailableWorkflow[]
113
118
  {wf.type === 'stepped' && (
114
119
  <div className="workflow-entry-hint">/workflow start {wf.name}</div>
115
120
  )}
121
+ <div className="workflow-entry-footer">
122
+ <Button
123
+ variant="ghost"
124
+ size="sm"
125
+ className="workflow-start-button"
126
+ data-testid="workflow-start-button"
127
+ onClick={() => onStart?.(wf)}
128
+ >
129
+ Start
130
+ </Button>
131
+ </div>
116
132
  </div>
117
133
  ))}
118
134
  </div>
@@ -126,6 +142,12 @@ function AvailableWorkflowsList({ workflows }: { workflows: AvailableWorkflow[]
126
142
 
127
143
  export function WorkflowPanel(): React.ReactElement {
128
144
  const { story, isLoading, error, availableWorkflows } = useStory();
145
+ const { send, isConnected } = useClaudeContext();
146
+
147
+ const handleStartWorkflow = useCallback((wf: AvailableWorkflow) => {
148
+ if (!isConnected) return;
149
+ send(`/workflow start ${wf.name}`);
150
+ }, [send, isConnected]);
129
151
 
130
152
  if (isLoading) {
131
153
  return (
@@ -159,7 +181,7 @@ export function WorkflowPanel(): React.ReactElement {
159
181
  if (availableWorkflows && availableWorkflows.length > 0) {
160
182
  return (
161
183
  <div className="workflow-panel" data-testid="workflow-panel">
162
- <AvailableWorkflowsList workflows={availableWorkflows} />
184
+ <AvailableWorkflowsList workflows={availableWorkflows} onStart={handleStartWorkflow} />
163
185
  </div>
164
186
  );
165
187
  }
@@ -24,6 +24,7 @@ interface WebSocketClaudeMessage {
24
24
  type MessageCallback = (message: ClaudeMessage) => void;
25
25
  type CompleteCallback = () => void;
26
26
  type ErrorCallback = (error: string) => void;
27
+ type ClearCallback = () => void;
27
28
 
28
29
  /** User message sent via send() - for display in MessagePanel */
29
30
  interface UserMessageData {
@@ -56,6 +57,8 @@ interface ClaudeContextValue {
56
57
  onError: (callback: ErrorCallback) => () => void;
57
58
  /** Subscribe to user messages sent via send() - for display in MessagePanel */
58
59
  onUserMessage: (callback: UserMessageCallback) => () => void;
60
+ /** Subscribe to clear/reset events */
61
+ onClear: (callback: ClearCallback) => () => void;
59
62
  }
60
63
 
61
64
  // =============================================================================
@@ -83,6 +86,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
83
86
  const completeCallbacksRef = useRef<Set<CompleteCallback>>(new Set());
84
87
  const errorCallbacksRef = useRef<Set<ErrorCallback>>(new Set());
85
88
  const userMessageCallbacksRef = useRef<Set<UserMessageCallback>>(new Set());
89
+ const clearCallbacksRef = useRef<Set<ClearCallback>>(new Set());
86
90
 
87
91
  // Connect to WebSocket
88
92
  const connect = useCallback(() => {
@@ -204,6 +208,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
204
208
  }
205
209
 
206
210
  wsRef.current.send(JSON.stringify({ type: 'clear' }));
211
+ clearCallbacksRef.current.forEach(cb => cb());
207
212
  }, []);
208
213
 
209
214
  // Clear session and reload agent (TirePump)
@@ -215,6 +220,7 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
215
220
 
216
221
  console.log('[ClaudeContext] TirePump: clearAndReload agent:', agent);
217
222
  wsRef.current.send(JSON.stringify({ type: 'clearAndReload', agent }));
223
+ clearCallbacksRef.current.forEach(cb => cb());
218
224
  }, []);
219
225
 
220
226
  // Set permission mode
@@ -260,6 +266,14 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
260
266
  };
261
267
  }, []);
262
268
 
269
+ // Subscribe to clear/reset events
270
+ const onClear = useCallback((callback: ClearCallback) => {
271
+ clearCallbacksRef.current.add(callback);
272
+ return () => {
273
+ clearCallbacksRef.current.delete(callback);
274
+ };
275
+ }, []);
276
+
263
277
  const value = useMemo(() => ({
264
278
  send,
265
279
  abort,
@@ -272,7 +286,8 @@ export function ClaudeProvider({ children }: ClaudeProviderProps): React.ReactEl
272
286
  onComplete,
273
287
  onError,
274
288
  onUserMessage,
275
- }), [send, abort, clear, clearAndReload, setMode, isConnected, mode, onMessage, onComplete, onError, onUserMessage]);
289
+ onClear,
290
+ }), [send, abort, clear, clearAndReload, setMode, isConnected, mode, onMessage, onComplete, onError, onUserMessage, onClear]);
276
291
 
277
292
  return (
278
293
  <ClaudeContext.Provider value={value}>
@@ -0,0 +1,27 @@
1
+ /**
2
+ * useColorScheme Hook
3
+ *
4
+ * Tracks the user's preferred color scheme (light/dark) via
5
+ * the prefers-color-scheme media query.
6
+ */
7
+
8
+ import { useState, useEffect } from 'react';
9
+
10
+ export type ColorScheme = 'light' | 'dark';
11
+
12
+ export function useColorScheme(): ColorScheme {
13
+ const [scheme, setScheme] = useState<ColorScheme>(() =>
14
+ window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
15
+ );
16
+
17
+ useEffect(() => {
18
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
19
+ const handler = (e: MediaQueryListEvent) => {
20
+ setScheme(e.matches ? 'dark' : 'light');
21
+ };
22
+ mq.addEventListener('change', handler);
23
+ return () => mq.removeEventListener('change', handler);
24
+ }, []);
25
+
26
+ return scheme;
27
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * usePlanModeExit - Smooth plan mode exit with tirepump choice
3
+ *
4
+ * Story: MSSCI-14327
5
+ *
6
+ * Handles the transition from plan mode to accept mode after plan approval,
7
+ * and offers the user a choice to tirepump (commit/push) changes.
8
+ */
9
+
10
+ import { useState, useCallback } from 'react';
11
+ import type { Mode } from '../components/ModeSwitch/index';
12
+
13
+ /** The Claude CLI mode to transition to after plan exit */
14
+ export const PLAN_EXIT_MODE = 'acceptEdits';
15
+
16
+ export interface PlanModeExitOptions {
17
+ currentMode: Mode;
18
+ setMode: (mode: Mode) => void;
19
+ approved: boolean;
20
+ wsConnected?: boolean;
21
+ }
22
+
23
+ export interface TirepumpOption {
24
+ action: 'tirepump' | 'continue';
25
+ label: string;
26
+ }
27
+
28
+ export interface PlanModeExitResult {
29
+ showTirepumpChoice: boolean;
30
+ options?: TirepumpOption[];
31
+ localOnly?: boolean;
32
+ }
33
+
34
+ export interface TirepumpChoiceOptions {
35
+ choice: 'tirepump' | 'continue';
36
+ currentAgent?: string;
37
+ setMode?: (mode: Mode) => void;
38
+ onContextClear?: (data: { agent: string }) => void;
39
+ }
40
+
41
+ export interface TirepumpChoiceResult {
42
+ contextCleared: boolean;
43
+ action: 'tirepump' | 'continue';
44
+ }
45
+
46
+ const TIREPUMP_OPTIONS: TirepumpOption[] = [
47
+ { action: 'tirepump', label: 'Commit & push changes' },
48
+ { action: 'continue', label: 'Continue without committing' },
49
+ ];
50
+
51
+ export async function handlePlanModeExit(
52
+ options: PlanModeExitOptions
53
+ ): Promise<PlanModeExitResult> {
54
+ const { currentMode, setMode, approved, wsConnected } = options;
55
+
56
+ if (!approved) {
57
+ return { showTirepumpChoice: false };
58
+ }
59
+
60
+ // Transition from plan to accept if not already there
61
+ if (currentMode !== 'accept') {
62
+ setMode('accept');
63
+ }
64
+
65
+ const localOnly = wsConnected === false;
66
+
67
+ return {
68
+ showTirepumpChoice: true,
69
+ options: TIREPUMP_OPTIONS,
70
+ ...(localOnly && { localOnly: true }),
71
+ };
72
+ }
73
+
74
+ export async function handleTirepumpChoice(
75
+ options: TirepumpChoiceOptions
76
+ ): Promise<TirepumpChoiceResult> {
77
+ const { choice, currentAgent, onContextClear } = options;
78
+
79
+ if (choice === 'tirepump') {
80
+ onContextClear?.({ agent: currentAgent ?? '' });
81
+ return { contextCleared: true, action: 'tirepump' };
82
+ }
83
+
84
+ return { contextCleared: false, action: 'continue' };
85
+ }
86
+
87
+ export function usePlanModeExit() {
88
+ const [showChoice, setShowChoice] = useState(false);
89
+ const [exitResult, setExitResult] = useState<PlanModeExitResult | null>(null);
90
+
91
+ const exitPlanMode = useCallback(async (options: PlanModeExitOptions) => {
92
+ const result = await handlePlanModeExit(options);
93
+ setExitResult(result);
94
+ setShowChoice(result.showTirepumpChoice);
95
+ return result;
96
+ }, []);
97
+
98
+ const chooseTirepump = useCallback(async (options: TirepumpChoiceOptions) => {
99
+ const result = await handleTirepumpChoice(options);
100
+ setShowChoice(false);
101
+ return result;
102
+ }, []);
103
+
104
+ return { showChoice, exitResult, exitPlanMode, chooseTirepump };
105
+ }
@@ -21,7 +21,7 @@
21
21
  /* Dockview CSS variable mappings to Cyclist theme */
22
22
  --dv-group-view-background-color: var(--bg-secondary, #0f0f1a);
23
23
  --dv-tabs-and-actions-container-background-color: var(--bg-tertiary, #1a1a2e);
24
- --dv-activegroup-visiblepanel-tab-background-color: var(--accent-color, #6366f1);
24
+ --dv-activegroup-visiblepanel-tab-background-color: var(--accent, #6366f1);
25
25
  --dv-activegroup-visiblepanel-tab-color: var(--text-primary, #ffffff);
26
26
  --dv-activegroup-hiddenpanel-tab-background-color: var(--bg-tertiary, #1a1a2e);
27
27
  --dv-activegroup-hiddenpanel-tab-color: var(--text-secondary, #a1a1aa);
@@ -29,11 +29,11 @@
29
29
  --dv-inactivegroup-visiblepanel-tab-color: var(--text-secondary, #a1a1aa);
30
30
  --dv-inactivegroup-hiddenpanel-tab-background-color: var(--bg-tertiary, #1a1a2e);
31
31
  --dv-inactivegroup-hiddenpanel-tab-color: var(--text-muted, #71717a);
32
- --dv-separator-border: var(--border-color, #27272a);
33
- --dv-paneview-header-border-color: var(--border-color, #27272a);
32
+ --dv-separator-border: var(--border, #27272a);
33
+ --dv-paneview-header-border-color: var(--border, #27272a);
34
34
  --dv-tab-close-icon: var(--text-muted, #71717a);
35
35
  --dv-drag-over-background-color: rgba(99, 102, 241, 0.2);
36
- --dv-drag-over-border-color: var(--accent-color, #6366f1);
36
+ --dv-drag-over-border-color: var(--accent, #6366f1);
37
37
  }
38
38
 
39
39
  .dockview-container {
@@ -70,12 +70,12 @@
70
70
  }
71
71
 
72
72
  .cyclist-dockview .dv-tab:hover {
73
- background-color: var(--bg-hover, #27272a);
73
+ background-color: var(--bg-tertiary, #27272a);
74
74
  }
75
75
 
76
76
  /* Active tab indicator */
77
77
  .cyclist-dockview .dv-tab.dv-active-tab {
78
- border-bottom: 2px solid var(--accent-color, #6366f1);
78
+ border-bottom: 2px solid var(--accent, #6366f1);
79
79
  }
80
80
 
81
81
  /* Tab close button */
@@ -93,13 +93,11 @@
93
93
  ============================================================================= */
94
94
 
95
95
  .cyclist-dockview .dv-group-panel {
96
- border: 1px solid var(--border-color, #27272a);
96
+ border: 1px solid var(--border, #27272a);
97
97
  }
98
98
 
99
- /* Message panel - hide close button (sacred, cannot be closed) */
100
- .cyclist-dockview .dv-tab[data-panel-id="message"] .dv-close-action {
101
- display: none !important;
102
- }
99
+ /* Message panel tab bar is hidden via Dockview API (group.headerVisible = false)
100
+ in DockviewWorkspace.tsx - no CSS selector needed */
103
101
 
104
102
  /* =============================================================================
105
103
  Resize Sash Styles
@@ -111,11 +109,11 @@
111
109
  }
112
110
 
113
111
  .cyclist-dockview .dv-resize-container .dv-sash:hover {
114
- background-color: var(--accent-color, #6366f1);
112
+ background-color: var(--accent, #6366f1);
115
113
  }
116
114
 
117
115
  .cyclist-dockview .dv-resize-container .dv-sash:active {
118
- background-color: var(--accent-color-bright, #818cf8);
116
+ background-color: var(--accent-hover, #818cf8);
119
117
  }
120
118
 
121
119
  /* =============================================================================
@@ -151,12 +149,12 @@
151
149
 
152
150
  /* Focus indicators */
153
151
  .cyclist-dockview .dv-tab:focus-visible {
154
- outline: 2px solid var(--accent-color, #6366f1);
152
+ outline: 2px solid var(--accent, #6366f1);
155
153
  outline-offset: -2px;
156
154
  }
157
155
 
158
156
  .cyclist-dockview .dv-sash:focus-visible {
159
- outline: 2px solid var(--accent-color, #6366f1);
157
+ outline: 2px solid var(--accent, #6366f1);
160
158
  }
161
159
 
162
160
  /* Reduced motion */
@@ -206,7 +204,7 @@
206
204
  padding: 4px 8px;
207
205
  margin: 4px;
208
206
  background-color: var(--bg-tertiary, #1a1a2e);
209
- border: 1px solid var(--border-color, #27272a);
207
+ border: 1px solid var(--border, #27272a);
210
208
  border-radius: 4px;
211
209
  color: var(--text-secondary, #a1a1aa);
212
210
  font-size: 12px;
@@ -215,9 +213,9 @@
215
213
  }
216
214
 
217
215
  .cyclist-dockview .dv-tabs-overflow-dropdown-default:hover {
218
- background-color: var(--bg-hover, #27272a);
216
+ background-color: var(--bg-tertiary, #27272a);
219
217
  color: var(--text-primary, #ffffff);
220
- border-color: var(--accent-color, #6366f1);
218
+ border-color: var(--accent, #6366f1);
221
219
  }
222
220
 
223
221
  /* Overflow dropdown container (list of hidden tabs) */
@@ -229,7 +227,7 @@
229
227
  max-height: 300px;
230
228
  overflow-y: auto;
231
229
  background-color: var(--bg-tertiary, #1a1a2e);
232
- border: 1px solid var(--border-color, #27272a);
230
+ border: 1px solid var(--border, #27272a);
233
231
  border-radius: 6px;
234
232
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
235
233
  z-index: 1000;
@@ -249,12 +247,12 @@
249
247
  }
250
248
 
251
249
  .cyclist-dockview .dv-tabs-overflow-container .dv-tab:hover {
252
- background-color: var(--accent-color, #6366f1);
250
+ background-color: var(--accent, #6366f1);
253
251
  color: var(--text-primary, #ffffff);
254
252
  }
255
253
 
256
254
  .cyclist-dockview .dv-tabs-overflow-container .dv-tab:not(:last-child) {
257
- border-bottom: 1px solid var(--border-color, #27272a);
255
+ border-bottom: 1px solid var(--border, #27272a);
258
256
  }
259
257
 
260
258
  /* =============================================================================
@@ -293,7 +291,7 @@
293
291
  gap: 4px;
294
292
  padding: 6px 10px;
295
293
  background-color: var(--bg-tertiary, #1a1a2e);
296
- border: 1px solid var(--border-color, #27272a);
294
+ border: 1px solid var(--border, #27272a);
297
295
  border-radius: 6px;
298
296
  color: var(--text-secondary, #a1a1aa);
299
297
  cursor: pointer;
@@ -302,9 +300,9 @@
302
300
  }
303
301
 
304
302
  .panel-restore-button:hover {
305
- background-color: var(--bg-hover, #27272a);
303
+ background-color: var(--bg-tertiary, #27272a);
306
304
  color: var(--text-primary, #ffffff);
307
- border-color: var(--accent-color, #6366f1);
305
+ border-color: var(--accent, #6366f1);
308
306
  }
309
307
 
310
308
  .panel-restore-icon {
@@ -314,7 +312,7 @@
314
312
  }
315
313
 
316
314
  .panel-restore-count {
317
- background-color: var(--accent-color, #6366f1);
315
+ background-color: var(--accent, #6366f1);
318
316
  color: var(--text-primary, #ffffff);
319
317
  padding: 2px 6px;
320
318
  border-radius: 10px;
@@ -329,7 +327,7 @@
329
327
  margin-top: 4px;
330
328
  min-width: 160px;
331
329
  background-color: var(--bg-tertiary, #1a1a2e);
332
- border: 1px solid var(--border-color, #27272a);
330
+ border: 1px solid var(--border, #27272a);
333
331
  border-radius: 6px;
334
332
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
335
333
  overflow: hidden;
@@ -342,7 +340,7 @@
342
340
  color: var(--text-muted, #71717a);
343
341
  text-transform: uppercase;
344
342
  letter-spacing: 0.5px;
345
- border-bottom: 1px solid var(--border-color, #27272a);
343
+ border-bottom: 1px solid var(--border, #27272a);
346
344
  }
347
345
 
348
346
  .panel-restore-item {
@@ -359,12 +357,12 @@
359
357
  }
360
358
 
361
359
  .panel-restore-item:hover {
362
- background-color: var(--accent-color, #6366f1);
360
+ background-color: var(--accent, #6366f1);
363
361
  color: var(--text-primary, #ffffff);
364
362
  }
365
363
 
366
364
  .panel-restore-item:focus-visible {
367
- outline: 2px solid var(--accent-color, #6366f1);
365
+ outline: 2px solid var(--accent, #6366f1);
368
366
  outline-offset: -2px;
369
367
  }
370
368
 
@@ -396,7 +394,7 @@
396
394
  display: flex;
397
395
  align-items: center;
398
396
  justify-content: center;
399
- background-color: rgba(10, 10, 15, 0.9);
397
+ background-color: color-mix(in srgb, var(--bg-primary, #0a0a0f) 90%, transparent);
400
398
  z-index: 10;
401
399
  }
402
400
 
@@ -407,7 +405,7 @@
407
405
  gap: 12px;
408
406
  padding: 24px;
409
407
  background-color: var(--bg-tertiary, #1a1a2e);
410
- border: 1px solid var(--border-color, #27272a);
408
+ border: 1px solid var(--border, #27272a);
411
409
  border-radius: 8px;
412
410
  text-align: center;
413
411
  }
@@ -424,7 +422,7 @@
424
422
 
425
423
  .tty-restart-button {
426
424
  padding: 8px 16px;
427
- background-color: var(--accent-color, #6366f1);
425
+ background-color: var(--accent, #6366f1);
428
426
  border: none;
429
427
  border-radius: 6px;
430
428
  color: var(--text-primary, #ffffff);
@@ -439,7 +437,7 @@
439
437
  }
440
438
 
441
439
  .tty-restart-button:focus-visible {
442
- outline: 2px solid var(--accent-color, #6366f1);
440
+ outline: 2px solid var(--accent, #6366f1);
443
441
  outline-offset: 2px;
444
442
  }
445
443