@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.
- package/dist/api/agent-load.d.ts +3 -0
- package/dist/api/agent-load.d.ts.map +1 -0
- package/dist/api/agent-load.js +124 -0
- package/dist/api/agent-load.js.map +1 -0
- package/dist/api/code-markers.d.ts +9 -0
- package/dist/api/code-markers.d.ts.map +1 -0
- package/dist/api/code-markers.js +62 -0
- package/dist/api/code-markers.js.map +1 -0
- package/dist/api/complexity.d.ts +3 -0
- package/dist/api/complexity.d.ts.map +1 -0
- package/dist/api/complexity.js +47 -0
- package/dist/api/complexity.js.map +1 -0
- package/dist/api/dead-code.d.ts +3 -0
- package/dist/api/dead-code.d.ts.map +1 -0
- package/dist/api/dead-code.js +70 -0
- package/dist/api/dead-code.js.map +1 -0
- package/dist/api/dependencies.d.ts +3 -0
- package/dist/api/dependencies.d.ts.map +1 -0
- package/dist/api/dependencies.js +43 -0
- package/dist/api/dependencies.js.map +1 -0
- package/dist/api/git.d.ts +3 -2
- package/dist/api/git.d.ts.map +1 -1
- package/dist/api/git.js +11 -6
- package/dist/api/git.js.map +1 -1
- package/dist/api/health-score.d.ts +3 -0
- package/dist/api/health-score.d.ts.map +1 -0
- package/dist/api/health-score.js +47 -0
- package/dist/api/health-score.js.map +1 -0
- package/dist/api/hotspots.d.ts.map +1 -1
- package/dist/api/hotspots.js +9 -1
- package/dist/api/hotspots.js.map +1 -1
- package/dist/api/index.d.ts +7 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +12 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/persona.d.ts +2 -0
- package/dist/api/persona.d.ts.map +1 -1
- package/dist/api/persona.js +19 -1
- package/dist/api/persona.js.map +1 -1
- package/dist/api/settings.js +1 -1
- package/dist/api/settings.js.map +1 -1
- package/dist/claude-service.d.ts +8 -2
- package/dist/claude-service.d.ts.map +1 -1
- package/dist/claude-service.js +21 -2
- package/dist/claude-service.js.map +1 -1
- package/dist/git-diff.d.ts.map +1 -1
- package/dist/git-diff.js +6 -5
- package/dist/git-diff.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +11 -2
- package/dist/main.js.map +1 -1
- package/dist/plugin-loader.d.ts +49 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/plugin-loader.js +92 -0
- package/dist/plugin-loader.js.map +1 -0
- package/dist/preload.js +12 -1
- package/dist/preload.js.map +1 -1
- package/dist/prime.d.ts +3 -2
- package/dist/prime.d.ts.map +1 -1
- package/dist/prime.js +25 -8
- package/dist/prime.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +50 -39
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +19 -16
- package/dist/server.js.map +1 -1
- package/dist/sprint-data.d.ts +6 -0
- package/dist/sprint-data.d.ts.map +1 -1
- package/dist/sprint-data.js +118 -67
- package/dist/sprint-data.js.map +1 -1
- package/dist/story-parser.js +1 -1
- package/dist/story-parser.js.map +1 -1
- package/dist/theme-metadata.js +2 -2
- package/dist/theme-metadata.js.map +1 -1
- package/dist/websocket.d.ts +0 -6
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +36 -40
- package/dist/websocket.js.map +1 -1
- package/package.json +2 -1
- package/portraits/fifth-element/large/cornelius-54343.png +0 -0
- package/portraits/fifth-element/large/diva-53453.png +0 -0
- package/portraits/fifth-element/large/korben-34232.png +0 -0
- package/portraits/fifth-element/large/leeloo-54333.png +0 -0
- package/portraits/fifth-element/large/lindberg-34432.png +0 -0
- package/portraits/fifth-element/large/mondoshawan-55131.png +0 -0
- package/portraits/fifth-element/large/munro-25321.png +0 -0
- package/portraits/fifth-element/large/pacoli-45232.png +0 -0
- package/portraits/fifth-element/large/ruby-53544.png +0 -0
- package/portraits/fifth-element/large/zorg-45312.png +0 -0
- package/portraits/fifth-element/medium/cornelius-54343.png +0 -0
- package/portraits/fifth-element/medium/diva-53453.png +0 -0
- package/portraits/fifth-element/medium/korben-34232.png +0 -0
- package/portraits/fifth-element/medium/leeloo-54333.png +0 -0
- package/portraits/fifth-element/medium/lindberg-34432.png +0 -0
- package/portraits/fifth-element/medium/mondoshawan-55131.png +0 -0
- package/portraits/fifth-element/medium/munro-25321.png +0 -0
- package/portraits/fifth-element/medium/pacoli-45232.png +0 -0
- package/portraits/fifth-element/medium/ruby-53544.png +0 -0
- package/portraits/fifth-element/medium/zorg-45312.png +0 -0
- package/src/public/App.tsx +0 -2
- package/src/public/components/AgentLoadDialog.tsx +202 -0
- package/src/public/components/AgentPopup.tsx +3 -5
- package/src/public/components/ContextSparkline.tsx +56 -0
- package/src/public/components/ControlBar.tsx +140 -6
- package/src/public/components/DeadCodeDialog.tsx +169 -0
- package/src/public/components/DockviewWorkspace.tsx +0 -3
- package/src/public/components/FullFileTree.tsx +18 -4
- package/src/public/components/HealthGauge.tsx +181 -0
- package/src/public/components/MessageView.tsx +23 -6
- package/src/public/components/PersonaHeader.tsx +46 -3
- package/src/public/components/TandemPortrait.tsx +71 -0
- package/src/public/components/ToolCallBlock.tsx +21 -6
- package/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
- package/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
- package/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
- package/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
- package/src/public/components/dialogs/ToolDialog.tsx +43 -0
- package/src/public/components/panels/ACPanel.tsx +1 -1
- package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +15 -30
- package/src/public/components/panels/DebugPanel.tsx +79 -3
- package/src/public/components/panels/GitPanel.tsx +25 -30
- package/src/public/components/panels/MessagePanel.tsx +44 -2
- package/src/public/components/panels/SettingsPanel.tsx +4 -4
- package/src/public/components/panels/SprintPanel.tsx +247 -123
- package/src/public/components/panels/index.ts +0 -1
- package/src/public/components/ui/dialog.tsx +3 -3
- package/src/public/css/theme-system.css +98 -11
- package/src/public/hooks/index.ts +4 -0
- package/src/public/hooks/useAgentLoad.ts +105 -0
- package/src/public/hooks/useCodeMarkers.ts +101 -0
- package/src/public/hooks/useColorScheme.ts +25 -10
- package/src/public/hooks/useComplexity.ts +80 -0
- package/src/public/hooks/useDeadCode.ts +99 -0
- package/src/public/hooks/useDependencies.ts +82 -0
- package/src/public/hooks/useHealthScore.ts +69 -0
- package/src/public/hooks/useHotspots.ts +11 -1
- package/src/public/hooks/usePersona.ts +26 -3
- package/src/public/hooks/useSprint.ts +7 -1
- package/src/public/styles/tailwind.css +389 -83
- package/src/public/utils/messageFilters.ts +77 -6
- package/src/public/utils/slash-commands.ts +3 -35
- package/dist/hooks/cyclist-pretooluse-hook.d.ts +0 -60
- package/dist/hooks/cyclist-pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/cyclist-pretooluse-hook.js +0 -57
- package/dist/hooks/cyclist-pretooluse-hook.js.map +0 -1
- package/dist/hooks/pretooluse-hook.d.ts +0 -89
- package/dist/hooks/pretooluse-hook.d.ts.map +0 -1
- package/dist/hooks/pretooluse-hook.js +0 -235
- package/dist/hooks/pretooluse-hook.js.map +0 -1
- package/dist/notification-sound.d.ts +0 -59
- package/dist/notification-sound.d.ts.map +0 -1
- package/dist/notification-sound.js +0 -219
- package/dist/notification-sound.js.map +0 -1
- 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:
|
|
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:
|
|
129
|
+
return { icon: <Check size={size} />, className: 'status-done' };
|
|
113
130
|
case 'in_progress':
|
|
114
|
-
return { icon:
|
|
131
|
+
return { icon: <Loader size={size} />, className: 'status-in-progress' };
|
|
115
132
|
case 'blocked':
|
|
116
|
-
return { icon:
|
|
133
|
+
return { icon: <AlertTriangle size={size} />, className: 'status-blocked' };
|
|
117
134
|
case 'backlog':
|
|
118
135
|
default:
|
|
119
|
-
return { icon:
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
(
|
|
188
|
-
|
|
189
|
-
|
|
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
|
-
//
|
|
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 &&
|
|
219
|
-
|
|
414
|
+
if (data?.epics && !hasInitializedExpansion.current) {
|
|
415
|
+
hasInitializedExpansion.current = true;
|
|
416
|
+
setExpandedEpics(new Set(activeEpics.map((e) => e.id)));
|
|
220
417
|
}
|
|
221
|
-
}, [data?.epics
|
|
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:
|
|
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
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
119
|
-
.
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
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';
|