@pennyfarthing/cyclist 10.0.3 → 10.2.0

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 (154) hide show
  1. package/dist/api/agent-load.d.ts +3 -0
  2. package/dist/api/agent-load.d.ts.map +1 -0
  3. package/dist/api/agent-load.js +124 -0
  4. package/dist/api/agent-load.js.map +1 -0
  5. package/dist/api/code-markers.d.ts +9 -0
  6. package/dist/api/code-markers.d.ts.map +1 -0
  7. package/dist/api/code-markers.js +62 -0
  8. package/dist/api/code-markers.js.map +1 -0
  9. package/dist/api/complexity.d.ts +3 -0
  10. package/dist/api/complexity.d.ts.map +1 -0
  11. package/dist/api/complexity.js +47 -0
  12. package/dist/api/complexity.js.map +1 -0
  13. package/dist/api/dead-code.d.ts +3 -0
  14. package/dist/api/dead-code.d.ts.map +1 -0
  15. package/dist/api/dead-code.js +70 -0
  16. package/dist/api/dead-code.js.map +1 -0
  17. package/dist/api/dependencies.d.ts +3 -0
  18. package/dist/api/dependencies.d.ts.map +1 -0
  19. package/dist/api/dependencies.js +43 -0
  20. package/dist/api/dependencies.js.map +1 -0
  21. package/dist/api/git.d.ts +3 -2
  22. package/dist/api/git.d.ts.map +1 -1
  23. package/dist/api/git.js +11 -6
  24. package/dist/api/git.js.map +1 -1
  25. package/dist/api/health-score.d.ts +3 -0
  26. package/dist/api/health-score.d.ts.map +1 -0
  27. package/dist/api/health-score.js +47 -0
  28. package/dist/api/health-score.js.map +1 -0
  29. package/dist/api/hotspots.d.ts.map +1 -1
  30. package/dist/api/hotspots.js +9 -1
  31. package/dist/api/hotspots.js.map +1 -1
  32. package/dist/api/index.d.ts +7 -1
  33. package/dist/api/index.d.ts.map +1 -1
  34. package/dist/api/index.js +12 -2
  35. package/dist/api/index.js.map +1 -1
  36. package/dist/api/persona.d.ts +2 -0
  37. package/dist/api/persona.d.ts.map +1 -1
  38. package/dist/api/persona.js +19 -1
  39. package/dist/api/persona.js.map +1 -1
  40. package/dist/api/settings.js +1 -1
  41. package/dist/api/settings.js.map +1 -1
  42. package/dist/claude-service.d.ts +8 -2
  43. package/dist/claude-service.d.ts.map +1 -1
  44. package/dist/claude-service.js +21 -2
  45. package/dist/claude-service.js.map +1 -1
  46. package/dist/git-diff.d.ts.map +1 -1
  47. package/dist/git-diff.js +6 -5
  48. package/dist/git-diff.js.map +1 -1
  49. package/dist/main.d.ts.map +1 -1
  50. package/dist/main.js +11 -2
  51. package/dist/main.js.map +1 -1
  52. package/dist/plugin-loader.d.ts +49 -0
  53. package/dist/plugin-loader.d.ts.map +1 -0
  54. package/dist/plugin-loader.js +92 -0
  55. package/dist/plugin-loader.js.map +1 -0
  56. package/dist/preload.js +12 -1
  57. package/dist/preload.js.map +1 -1
  58. package/dist/prime.d.ts +3 -2
  59. package/dist/prime.d.ts.map +1 -1
  60. package/dist/prime.js +25 -8
  61. package/dist/prime.js.map +1 -1
  62. package/dist/public/css/react.css +1 -1
  63. package/dist/public/js/react/react.js +50 -39
  64. package/dist/server.d.ts.map +1 -1
  65. package/dist/server.js +19 -16
  66. package/dist/server.js.map +1 -1
  67. package/dist/sprint-data.d.ts +6 -0
  68. package/dist/sprint-data.d.ts.map +1 -1
  69. package/dist/sprint-data.js +118 -67
  70. package/dist/sprint-data.js.map +1 -1
  71. package/dist/story-parser.js +1 -1
  72. package/dist/story-parser.js.map +1 -1
  73. package/dist/theme-metadata.js +2 -2
  74. package/dist/theme-metadata.js.map +1 -1
  75. package/dist/websocket.d.ts +0 -6
  76. package/dist/websocket.d.ts.map +1 -1
  77. package/dist/websocket.js +36 -40
  78. package/dist/websocket.js.map +1 -1
  79. package/package.json +2 -1
  80. package/portraits/fifth-element/large/cornelius-54343.png +0 -0
  81. package/portraits/fifth-element/large/diva-53453.png +0 -0
  82. package/portraits/fifth-element/large/korben-34232.png +0 -0
  83. package/portraits/fifth-element/large/leeloo-54333.png +0 -0
  84. package/portraits/fifth-element/large/lindberg-34432.png +0 -0
  85. package/portraits/fifth-element/large/mondoshawan-55131.png +0 -0
  86. package/portraits/fifth-element/large/munro-25321.png +0 -0
  87. package/portraits/fifth-element/large/pacoli-45232.png +0 -0
  88. package/portraits/fifth-element/large/ruby-53544.png +0 -0
  89. package/portraits/fifth-element/large/zorg-45312.png +0 -0
  90. package/portraits/fifth-element/medium/cornelius-54343.png +0 -0
  91. package/portraits/fifth-element/medium/diva-53453.png +0 -0
  92. package/portraits/fifth-element/medium/korben-34232.png +0 -0
  93. package/portraits/fifth-element/medium/leeloo-54333.png +0 -0
  94. package/portraits/fifth-element/medium/lindberg-34432.png +0 -0
  95. package/portraits/fifth-element/medium/mondoshawan-55131.png +0 -0
  96. package/portraits/fifth-element/medium/munro-25321.png +0 -0
  97. package/portraits/fifth-element/medium/pacoli-45232.png +0 -0
  98. package/portraits/fifth-element/medium/ruby-53544.png +0 -0
  99. package/portraits/fifth-element/medium/zorg-45312.png +0 -0
  100. package/src/public/App.tsx +0 -2
  101. package/src/public/components/AgentLoadDialog.tsx +202 -0
  102. package/src/public/components/AgentPopup.tsx +3 -5
  103. package/src/public/components/ContextSparkline.tsx +56 -0
  104. package/src/public/components/ControlBar.tsx +140 -6
  105. package/src/public/components/DeadCodeDialog.tsx +169 -0
  106. package/src/public/components/DockviewWorkspace.tsx +0 -3
  107. package/src/public/components/FullFileTree.tsx +18 -4
  108. package/src/public/components/HealthGauge.tsx +181 -0
  109. package/src/public/components/MessageView.tsx +23 -6
  110. package/src/public/components/PersonaHeader.tsx +46 -3
  111. package/src/public/components/TandemPortrait.tsx +71 -0
  112. package/src/public/components/ToolCallBlock.tsx +21 -6
  113. package/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  114. package/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  115. package/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  116. package/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  117. package/src/public/components/dialogs/ToolDialog.tsx +43 -0
  118. package/src/public/components/panels/ACPanel.tsx +1 -1
  119. package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +15 -30
  120. package/src/public/components/panels/DebugPanel.tsx +79 -3
  121. package/src/public/components/panels/GitPanel.tsx +25 -30
  122. package/src/public/components/panels/MessagePanel.tsx +44 -2
  123. package/src/public/components/panels/SettingsPanel.tsx +4 -4
  124. package/src/public/components/panels/SprintPanel.tsx +247 -123
  125. package/src/public/components/panels/index.ts +0 -1
  126. package/src/public/components/ui/dialog.tsx +3 -3
  127. package/src/public/css/theme-system.css +98 -11
  128. package/src/public/hooks/index.ts +4 -0
  129. package/src/public/hooks/useAgentLoad.ts +105 -0
  130. package/src/public/hooks/useCodeMarkers.ts +101 -0
  131. package/src/public/hooks/useColorScheme.ts +25 -10
  132. package/src/public/hooks/useComplexity.ts +80 -0
  133. package/src/public/hooks/useDeadCode.ts +99 -0
  134. package/src/public/hooks/useDependencies.ts +82 -0
  135. package/src/public/hooks/useHealthScore.ts +69 -0
  136. package/src/public/hooks/useHotspots.ts +11 -1
  137. package/src/public/hooks/usePersona.ts +26 -3
  138. package/src/public/hooks/useSprint.ts +7 -1
  139. package/src/public/styles/tailwind.css +389 -83
  140. package/src/public/utils/messageFilters.ts +77 -6
  141. package/src/public/utils/slash-commands.ts +3 -35
  142. package/dist/hooks/cyclist-pretooluse-hook.d.ts +0 -60
  143. package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +0 -1
  144. package/dist/hooks/cyclist-pretooluse-hook.js +0 -57
  145. package/dist/hooks/cyclist-pretooluse-hook.js.map +0 -1
  146. package/dist/hooks/pretooluse-hook.d.ts +0 -89
  147. package/dist/hooks/pretooluse-hook.d.ts.map +0 -1
  148. package/dist/hooks/pretooluse-hook.js +0 -235
  149. package/dist/hooks/pretooluse-hook.js.map +0 -1
  150. package/dist/notification-sound.d.ts +0 -59
  151. package/dist/notification-sound.d.ts.map +0 -1
  152. package/dist/notification-sound.js +0 -219
  153. package/dist/notification-sound.js.map +0 -1
  154. package/src/public/types/electron.d.ts +0 -18
@@ -5,7 +5,8 @@
5
5
  * Story MSSCI-14189 - Enhanced Sprint Panel with story management and epic actions
6
6
  */
7
7
 
8
- import React, { useState, useEffect, useCallback } from 'react';
8
+ import React, { useState, useEffect, useCallback, useRef } from 'react';
9
+ import { Check, Loader, Circle, AlertTriangle } from 'lucide-react';
9
10
  import { Button } from '@/components/ui/button';
10
11
  import { Badge } from '@/components/ui/badge';
11
12
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
@@ -85,6 +86,21 @@ export function SprintPanel(): React.ReactElement {
85
86
  // Enhanced Sprint Panel (MSSCI-14189)
86
87
  // =============================================================================
87
88
 
89
+ /**
90
+ * Format email to short display name: "keith.avery@..." -> "K. Avery"
91
+ */
92
+ function formatAssignee(email: string | null | undefined): string | null {
93
+ if (!email) return null;
94
+ const local = email.split('@')[0];
95
+ const parts = local.split('.');
96
+ if (parts.length >= 2) {
97
+ const first = parts[0].charAt(0).toUpperCase();
98
+ const last = parts[parts.length - 1].charAt(0).toUpperCase() + parts[parts.length - 1].slice(1);
99
+ return `${first}. ${last}`;
100
+ }
101
+ return local;
102
+ }
103
+
88
104
  /**
89
105
  * Calculate epic progress (done points / total points)
90
106
  */
@@ -106,17 +122,18 @@ function isEpicCompleted(epic: SprintEpic): boolean {
106
122
  /**
107
123
  * Get status badge content and class for a story status
108
124
  */
109
- function getStatusBadgeInfo(status: SprintStory['status']): { icon: string; className: string } {
125
+ function getStatusBadgeInfo(status: SprintStory['status']): { icon: React.ReactElement; className: string } {
126
+ const size = 12;
110
127
  switch (status) {
111
128
  case 'done':
112
- return { icon: '✓', className: 'status-done' };
129
+ return { icon: <Check size={size} />, className: 'status-done' };
113
130
  case 'in_progress':
114
- return { icon: '●', className: 'status-in-progress' };
131
+ return { icon: <Loader size={size} />, className: 'status-in-progress' };
115
132
  case 'blocked':
116
- return { icon: '⚠', className: 'status-blocked' };
133
+ return { icon: <AlertTriangle size={size} />, className: 'status-blocked' };
117
134
  case 'backlog':
118
135
  default:
119
- return { icon: '○', className: 'status-backlog' };
136
+ return { icon: <Circle size={size} />, className: 'status-backlog' };
120
137
  }
121
138
  }
122
139
 
@@ -157,6 +174,23 @@ function ContextIndicator({
157
174
  );
158
175
  }
159
176
 
177
+ /**
178
+ * Priority label component - muted text abbreviation
179
+ */
180
+ function PriorityDot({ priority, storyId }: { priority?: string | null; storyId: string }): React.ReactElement | null {
181
+ if (!priority) return null;
182
+ return (
183
+ <span
184
+ className="priority-label"
185
+ data-testid={`story-priority-${storyId}`}
186
+ data-priority={priority}
187
+ title={priority}
188
+ >
189
+ {priority}
190
+ </span>
191
+ );
192
+ }
193
+
160
194
  /**
161
195
  * Status badge component for stories
162
196
  */
@@ -182,12 +216,16 @@ function JiraLink({ jiraKey, storyId }: { jiraKey: string; storyId: string }): R
182
216
  const handleClick = (e: React.MouseEvent) => {
183
217
  e.preventDefault();
184
218
  const url = getJiraUrl(jiraKey);
185
- // Use electronAPI if available, otherwise open in new tab
186
- if (typeof window !== 'undefined' && (window as any).electronAPI?.shell?.openExternal) {
187
- (window as any).electronAPI.shell.openExternal(url);
188
- } else {
189
- window.open(url, '_blank');
219
+ try {
220
+ const api = (window as any).electronAPI;
221
+ if (api?.shell?.openExternal) {
222
+ api.shell.openExternal(url);
223
+ return;
224
+ }
225
+ } catch {
226
+ // electronAPI not available or call failed
190
227
  }
228
+ window.open(url, '_blank');
191
229
  };
192
230
 
193
231
  return (
@@ -202,6 +240,158 @@ function JiraLink({ jiraKey, storyId }: { jiraKey: string; storyId: string }): R
202
240
  );
203
241
  }
204
242
 
243
+ /**
244
+ * EpicGroup - Renders a single epic with its stories
245
+ */
246
+ function EpicGroup({
247
+ epic,
248
+ isExpanded,
249
+ isArchiving,
250
+ onToggle,
251
+ onKeyDown,
252
+ onArchive,
253
+ }: {
254
+ epic: SprintEpic;
255
+ isExpanded: boolean;
256
+ isArchiving: boolean;
257
+ onToggle: (id: string) => void;
258
+ onKeyDown: (id: string, e: React.KeyboardEvent) => void;
259
+ onArchive: (id: string) => void;
260
+ }): React.ReactElement {
261
+ const { done, total } = calculateEpicProgress(epic);
262
+ const completed = isEpicCompleted(epic);
263
+
264
+ return (
265
+ <div
266
+ className={`epic-group ${completed ? 'epic-completed' : ''}`}
267
+ data-testid={`epic-group-${epic.id}`}
268
+ >
269
+ {/* Epic Header */}
270
+ <div className="epic-header">
271
+ <Button
272
+ variant="ghost"
273
+ size="icon"
274
+ className="epic-toggle"
275
+ data-testid={`epic-toggle-${epic.id}`}
276
+ onClick={() => onToggle(epic.id)}
277
+ onKeyDown={(e) => onKeyDown(epic.id, e)}
278
+ aria-expanded={isExpanded}
279
+ >
280
+ {isExpanded ? '▼' : '▶'}
281
+ </Button>
282
+ <span className="epic-title">{epic.title}</span>
283
+ {epic.jiraKey && <span className="epic-jira">{epic.jiraKey}</span>}
284
+ <ContextIndicator hasContext={epic.hasContext ?? false} testIdPrefix="epic" id={epic.id} />
285
+ {completed && epic.hasContext && (
286
+ <Badge variant="default" className="epic-ready-badge" data-testid={`epic-ready-badge-${epic.id}`}>
287
+ Ready
288
+ </Badge>
289
+ )}
290
+
291
+ {/* Progress bar */}
292
+ <div
293
+ className="epic-progress"
294
+ data-testid={`epic-progress-${epic.id}`}
295
+ data-done={String(done)}
296
+ data-total={String(total)}
297
+ >
298
+ <div
299
+ className="progress-bar"
300
+ style={{ width: `${total > 0 ? (done / total) * 100 : 0}%` }}
301
+ />
302
+ </div>
303
+ <span
304
+ className="epic-progress-label"
305
+ data-testid={`epic-progress-label-${epic.id}`}
306
+ >
307
+ {done}/{total} pts
308
+ </span>
309
+
310
+ {/* Archive button for completed epics */}
311
+ {completed && (
312
+ <>
313
+ {isArchiving && (
314
+ <span data-testid={`archive-loading-${epic.id}`}>...</span>
315
+ )}
316
+ <Button
317
+ variant="outline"
318
+ size="sm"
319
+ className="archive-button"
320
+ data-testid={`archive-button-${epic.id}`}
321
+ aria-label={`Archive ${epic.id}`}
322
+ disabled={isArchiving}
323
+ onClick={() => onArchive(epic.id)}
324
+ >
325
+ Archive
326
+ </Button>
327
+ </>
328
+ )}
329
+ </div>
330
+
331
+ {/* Stories list (collapsible) */}
332
+ {isExpanded && (
333
+ <div className="epic-stories">
334
+ {epic.stories.map((story) => {
335
+ const hasContext = story.hasContext ?? false;
336
+ const isBlocked = story.status === 'blocked';
337
+ const assigneeDisplay = formatAssignee(story.assignedTo);
338
+ return (
339
+ <div
340
+ key={story.id}
341
+ className={`story-item ${!hasContext ? 'missing-context' : ''} ${isBlocked ? 'story-blocked' : ''}`}
342
+ data-testid={`story-item-${story.id}`}
343
+ data-status={story.status}
344
+ data-story-id={story.id}
345
+ aria-label={`${story.id}: ${story.title}`}
346
+ >
347
+ <PriorityDot priority={story.priority} storyId={story.id} />
348
+ <StatusBadge status={story.status} storyId={story.id} />
349
+ {story.jiraKey && <JiraLink jiraKey={story.jiraKey} storyId={story.id} />}
350
+ <div className="story-info">
351
+ <span className="story-title">{story.title}</span>
352
+ <span className="story-meta">
353
+ {assigneeDisplay && (
354
+ <span
355
+ className="story-assignee"
356
+ data-testid={`story-assignee-${story.id}`}
357
+ >
358
+ {assigneeDisplay}
359
+ </span>
360
+ )}
361
+ {story.workflow && (
362
+ <span
363
+ className="story-workflow-badge"
364
+ data-testid={`story-workflow-${story.id}`}
365
+ >
366
+ {story.workflow}
367
+ </span>
368
+ )}
369
+ {story.status === 'done' && story.completed && (
370
+ <span
371
+ className="story-completed-date"
372
+ data-testid={`story-completed-${story.id}`}
373
+ >
374
+ {story.completed}
375
+ </span>
376
+ )}
377
+ </span>
378
+ </div>
379
+ <ContextIndicator hasContext={hasContext} testIdPrefix="story" id={story.id} />
380
+ <span
381
+ className="story-points"
382
+ data-testid={`story-points-${story.id}`}
383
+ >
384
+ {story.points}
385
+ </span>
386
+ </div>
387
+ );
388
+ })}
389
+ </div>
390
+ )}
391
+ </div>
392
+ );
393
+ }
394
+
205
395
  /**
206
396
  * EnhancedSprintPanel - Full sprint management with epic actions
207
397
  */
@@ -213,12 +403,19 @@ export function EnhancedSprintPanel(): React.ReactElement {
213
403
  const [confirmArchive, setConfirmArchive] = useState<string | null>(null);
214
404
  const [actionError, setActionError] = useState<Error | null>(null);
215
405
 
216
- // Expand all epics by default when data first loads
406
+ // Split epics into active (has non-done stories) vs completed (all stories done)
407
+ const activeEpics = data?.epics.filter((e) => !isEpicCompleted(e)) ?? [];
408
+ const completedEpics = data?.epics.filter((e) => isEpicCompleted(e)) ?? [];
409
+
410
+ // Expand only active epics by default when data first loads (once only)
411
+ // Completed epics start collapsed
412
+ const hasInitializedExpansion = useRef(false);
217
413
  useEffect(() => {
218
- if (data?.epics && expandedEpics.size === 0) {
219
- setExpandedEpics(new Set(data.epics.map((e) => e.id)));
414
+ if (data?.epics && !hasInitializedExpansion.current) {
415
+ hasInitializedExpansion.current = true;
416
+ setExpandedEpics(new Set(activeEpics.map((e) => e.id)));
220
417
  }
221
- }, [data?.epics, expandedEpics.size]);
418
+ }, [data?.epics]);
222
419
 
223
420
  // Toggle epic expansion
224
421
  const toggleEpic = useCallback((epicId: string) => {
@@ -374,7 +571,7 @@ export function EnhancedSprintPanel(): React.ReactElement {
374
571
 
375
572
  <Separator className="my-2" />
376
573
 
377
- {/* Section 2: Epic Tree View */}
574
+ {/* Section 2: Active Epics */}
378
575
  <section data-section="epics">
379
576
  <h2>Current Epics</h2>
380
577
  <div data-testid="epic-tree-view">
@@ -384,116 +581,43 @@ export function EnhancedSprintPanel(): React.ReactElement {
384
581
  <p className="hint">Promote an epic from Future Initiatives to get started</p>
385
582
  </div>
386
583
  )}
387
- {data?.epics.map((epic) => {
388
- const { done, total } = calculateEpicProgress(epic);
389
- const completed = isEpicCompleted(epic);
390
- const isExpanded = expandedEpics.has(epic.id);
391
- const isArchiving = loadingActions.has(`archive-${epic.id}`);
392
-
393
- return (
394
- <div
395
- key={epic.id}
396
- className={`epic-group ${completed ? 'epic-completed' : ''}`}
397
- data-testid={`epic-group-${epic.id}`}
398
- >
399
- {/* Epic Header */}
400
- <div className="epic-header">
401
- <Button
402
- variant="ghost"
403
- size="icon"
404
- className="epic-toggle"
405
- data-testid={`epic-toggle-${epic.id}`}
406
- onClick={() => toggleEpic(epic.id)}
407
- onKeyDown={(e) => handleEpicKeyDown(epic.id, e)}
408
- aria-expanded={isExpanded}
409
- >
410
- {isExpanded ? '▼' : '▶'}
411
- </Button>
412
- <span className="epic-title">{epic.title}</span>
413
- {epic.jiraKey && <span className="epic-jira">{epic.jiraKey}</span>}
414
- <ContextIndicator hasContext={epic.hasContext ?? false} testIdPrefix="epic" id={epic.id} />
415
- {completed && epic.hasContext && (
416
- <Badge variant="default" className="epic-ready-badge" data-testid={`epic-ready-badge-${epic.id}`}>
417
- Ready
418
- </Badge>
419
- )}
420
-
421
- {/* Progress bar */}
422
- <div
423
- className="epic-progress"
424
- data-testid={`epic-progress-${epic.id}`}
425
- data-done={String(done)}
426
- data-total={String(total)}
427
- >
428
- <div
429
- className="progress-bar"
430
- style={{ width: `${total > 0 ? (done / total) * 100 : 0}%` }}
431
- />
432
- </div>
433
- <span
434
- className="epic-progress-label"
435
- data-testid={`epic-progress-label-${epic.id}`}
436
- >
437
- {done}/{total} pts
438
- </span>
439
-
440
- {/* Archive button for completed epics */}
441
- {completed && (
442
- <>
443
- {isArchiving && (
444
- <span data-testid={`archive-loading-${epic.id}`}>...</span>
445
- )}
446
- <Button
447
- variant="outline"
448
- size="sm"
449
- className="archive-button"
450
- data-testid={`archive-button-${epic.id}`}
451
- aria-label={`Archive ${epic.id}`}
452
- disabled={isArchiving}
453
- onClick={() => setConfirmArchive(epic.id)}
454
- >
455
- Archive
456
- </Button>
457
- </>
458
- )}
459
- </div>
460
-
461
- {/* Stories list (collapsible) */}
462
- {isExpanded && (
463
- <div className="epic-stories">
464
- {epic.stories.map((story) => {
465
- const hasContext = story.hasContext ?? false;
466
- const isBlocked = story.status === 'blocked';
467
- return (
468
- <div
469
- key={story.id}
470
- className={`story-item ${!hasContext ? 'missing-context' : ''} ${isBlocked ? 'story-blocked' : ''}`}
471
- data-testid={`story-item-${story.id}`}
472
- data-status={story.status}
473
- data-story-id={story.id}
474
- aria-label={`${story.id}: ${story.title}`}
475
- >
476
- <StatusBadge status={story.status} storyId={story.id} />
477
- {story.jiraKey && <JiraLink jiraKey={story.jiraKey} storyId={story.id} />}
478
- <span className="story-title">{story.title}</span>
479
- <ContextIndicator hasContext={hasContext} testIdPrefix="story" id={story.id} />
480
- <span
481
- className="story-points"
482
- data-testid={`story-points-${story.id}`}
483
- >
484
- {story.points}
485
- </span>
486
- </div>
487
- );
488
- })}
489
- </div>
490
- )}
491
- </div>
492
- );
493
- })}
584
+ {activeEpics.map((epic) => (
585
+ <EpicGroup
586
+ key={epic.id}
587
+ epic={epic}
588
+ isExpanded={expandedEpics.has(epic.id)}
589
+ isArchiving={loadingActions.has(`archive-${epic.id}`)}
590
+ onToggle={toggleEpic}
591
+ onKeyDown={handleEpicKeyDown}
592
+ onArchive={setConfirmArchive}
593
+ />
594
+ ))}
494
595
  </div>
495
596
  </section>
496
597
 
598
+ {/* Section 2b: Completed Epics */}
599
+ {completedEpics.length > 0 && (
600
+ <>
601
+ <Separator className="my-2" />
602
+ <section data-section="completed-epics">
603
+ <h2>Completed Epics</h2>
604
+ <div data-testid="completed-epics-section">
605
+ {completedEpics.map((epic) => (
606
+ <EpicGroup
607
+ key={epic.id}
608
+ epic={epic}
609
+ isExpanded={expandedEpics.has(epic.id)}
610
+ isArchiving={loadingActions.has(`archive-${epic.id}`)}
611
+ onToggle={toggleEpic}
612
+ onKeyDown={handleEpicKeyDown}
613
+ onArchive={setConfirmArchive}
614
+ />
615
+ ))}
616
+ </div>
617
+ </section>
618
+ </>
619
+ )}
620
+
497
621
  <Separator className="my-2" />
498
622
 
499
623
  {/* Section 3: Future Initiatives */}
@@ -18,7 +18,6 @@ export { DebugPanel } from './DebugPanel';
18
18
  export { SettingsPanel } from './SettingsPanel';
19
19
  export { AuditLogPanel } from './AuditLogPanel';
20
20
  export { TTYPanel } from './TTYPanel';
21
- export { HotspotsPanel } from './HotspotsPanel';
22
21
 
23
22
  // Legacy exports - kept for backwards compatibility and tests
24
23
  export { AcceptanceCriteriaPanel, ConnectedAcceptanceCriteriaPanel } from './AcceptanceCriteriaPanel';
@@ -36,7 +36,7 @@ const DialogContent = React.forwardRef<
36
36
  <DialogPrimitive.Content
37
37
  ref={ref}
38
38
  className={cn(
39
- "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-[var(--border)] bg-card text-card-foreground p-6 shadow-2xl shadow-black/40 duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40
40
  className
41
41
  )}
42
42
  {...props}
@@ -86,7 +86,7 @@ const DialogTitle = React.forwardRef<
86
86
  <DialogPrimitive.Title
87
87
  ref={ref}
88
88
  className={cn(
89
- "text-lg font-semibold leading-none tracking-tight",
89
+ "text-base font-medium leading-none tracking-tight",
90
90
  className
91
91
  )}
92
92
  {...props}
@@ -100,7 +100,7 @@ const DialogDescription = React.forwardRef<
100
100
  >(({ className, ...props }, ref) => (
101
101
  <DialogPrimitive.Description
102
102
  ref={ref}
103
- className={cn("text-sm text-muted-foreground", className)}
103
+ className={cn("text-sm text-[var(--text-secondary)]", className)}
104
104
  {...props}
105
105
  />
106
106
  ))
@@ -108,18 +108,12 @@
108
108
 
109
109
  /* =============================================================================
110
110
  Utility Classes for Theme Colors
111
- ============================================================================= */
112
-
113
- /* Backgrounds */
114
- .bg-primary { background-color: var(--bg-primary); }
115
- .bg-secondary { background-color: var(--bg-secondary); }
116
- .bg-tertiary { background-color: var(--bg-tertiary); }
117
111
 
118
- /* Text */
119
- .text-primary { color: var(--text-primary); }
120
- .text-secondary { color: var(--text-secondary); }
121
- .text-muted { color: var(--text-muted); }
122
- .text-accent { color: var(--accent); }
112
+ NOTE: .bg-primary, .text-secondary, .text-muted etc. are generated by
113
+ Tailwind from tailwind.config.js. Do NOT duplicate them here — specificity
114
+ conflicts cause unpredictable results. Only define classes that Tailwind
115
+ does NOT generate (status colors, border-default, border-focus).
116
+ ============================================================================= */
123
117
 
124
118
  /* Status */
125
119
  .text-success { color: var(--status-success); }
@@ -541,3 +535,96 @@
541
535
  .tool-stack-content .tool-historical {
542
536
  opacity: 0.85;
543
537
  }
538
+
539
+ /* =============================================================================
540
+ ACPanel Styling (MSSCI-14763) - Tufte Treatment
541
+ ============================================================================= */
542
+
543
+ /* Panel container */
544
+ .ac-panel {
545
+ padding: 0.5rem;
546
+ }
547
+
548
+ /* Content wrapper — Tufte left border accent */
549
+ .ac-content {
550
+ border-left: 2px solid var(--border);
551
+ padding-left: 0.5rem;
552
+ transition: border-color 0.15s ease;
553
+ }
554
+
555
+ .ac-content:hover {
556
+ border-left-color: var(--accent);
557
+ }
558
+
559
+ /* Progress counter — above the bar, not overlaid */
560
+ .ac-panel .progress-text {
561
+ font-size: 0.6875rem;
562
+ font-family: var(--font-mono);
563
+ font-variant-numeric: tabular-nums;
564
+ color: var(--text-muted);
565
+ margin-bottom: 0.25rem;
566
+ display: block;
567
+ }
568
+
569
+ /* Progress bar — Tufte: thin line, no rounded corners */
570
+ .ac-panel .progress-bar-container {
571
+ position: relative;
572
+ height: 4px;
573
+ background: var(--border);
574
+ border-radius: 0;
575
+ margin-bottom: 0.5rem;
576
+ overflow: hidden;
577
+ }
578
+
579
+ .ac-panel .progress-bar {
580
+ height: 100%;
581
+ background: var(--accent);
582
+ border-radius: 0;
583
+ transition: width 0.3s ease;
584
+ }
585
+
586
+ /* Criteria list */
587
+ .ac-list {
588
+ display: flex;
589
+ flex-direction: column;
590
+ gap: 0.125rem;
591
+ }
592
+
593
+ /* Individual criterion */
594
+ .ac-item {
595
+ display: flex;
596
+ align-items: baseline;
597
+ gap: 0.375rem;
598
+ padding: 0.125rem 0;
599
+ font-size: 0.8125rem;
600
+ color: var(--text-primary);
601
+ }
602
+
603
+ .ac-item.ac-done {
604
+ color: var(--text-muted);
605
+ }
606
+
607
+ /* Status icon */
608
+ .ac-icon {
609
+ width: 1rem;
610
+ text-align: center;
611
+ flex-shrink: 0;
612
+ font-size: 0.75rem;
613
+ color: var(--text-secondary);
614
+ }
615
+
616
+ .ac-done .ac-icon {
617
+ color: var(--status-success);
618
+ }
619
+
620
+ /* Criterion text */
621
+ .ac-text {
622
+ flex: 1;
623
+ min-width: 0;
624
+ line-height: 1.4;
625
+ }
626
+
627
+ .ac-done .ac-text {
628
+ text-decoration: line-through;
629
+ text-decoration-color: var(--text-muted);
630
+ }
@@ -43,3 +43,7 @@ export type { UseMarkdownParserResult } from './useMarkdownParser';
43
43
 
44
44
  export { useSyntaxHighlighter } from './useSyntaxHighlighter';
45
45
  export type { UseSyntaxHighlighterResult } from './useSyntaxHighlighter';
46
+
47
+ // Agent load analysis
48
+ export { useAgentLoad } from './useAgentLoad';
49
+ export type { AgentLoadData, AgentLoadEntry, PruneResult } from './useAgentLoad';