@pennyfarthing/cyclist 10.4.0 → 11.0.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 +1 -2
- package/dist/api/agent-load.d.ts.map +1 -1
- package/dist/api/agent-load.js +2 -123
- package/dist/api/agent-load.js.map +1 -1
- package/dist/api/audit-log.d.ts +1 -17
- package/dist/api/audit-log.d.ts.map +1 -1
- package/dist/api/audit-log.js +2 -162
- package/dist/api/audit-log.js.map +1 -1
- package/dist/api/background-tasks.d.ts +1 -26
- package/dist/api/background-tasks.d.ts.map +1 -1
- package/dist/api/background-tasks.js +2 -55
- package/dist/api/background-tasks.js.map +1 -1
- package/dist/api/bell.d.ts +1 -18
- package/dist/api/bell.d.ts.map +1 -1
- package/dist/api/bell.js +2 -33
- package/dist/api/bell.js.map +1 -1
- package/dist/api/code-markers.d.ts +1 -8
- package/dist/api/code-markers.d.ts.map +1 -1
- package/dist/api/code-markers.js +2 -61
- package/dist/api/code-markers.js.map +1 -1
- package/dist/api/complexity.d.ts +1 -2
- package/dist/api/complexity.d.ts.map +1 -1
- package/dist/api/complexity.js +2 -46
- package/dist/api/complexity.js.map +1 -1
- package/dist/api/context.d.ts +1 -37
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -143
- package/dist/api/context.js.map +1 -1
- package/dist/api/dead-code.d.ts +1 -2
- package/dist/api/dead-code.d.ts.map +1 -1
- package/dist/api/dead-code.js +2 -69
- package/dist/api/dead-code.js.map +1 -1
- package/dist/api/dependencies.d.ts +1 -2
- package/dist/api/dependencies.d.ts.map +1 -1
- package/dist/api/dependencies.js +2 -42
- package/dist/api/dependencies.js.map +1 -1
- package/dist/api/evaluation.d.ts +1 -19
- package/dist/api/evaluation.d.ts.map +1 -1
- package/dist/api/evaluation.js +2 -127
- package/dist/api/evaluation.js.map +1 -1
- package/dist/api/file-browser.d.ts +1 -8
- package/dist/api/file-browser.d.ts.map +1 -1
- package/dist/api/file-browser.js +2 -114
- package/dist/api/file-browser.js.map +1 -1
- package/dist/api/git.d.ts +1 -46
- package/dist/api/git.d.ts.map +1 -1
- package/dist/api/git.js +2 -354
- package/dist/api/git.js.map +1 -1
- package/dist/api/health-score.d.ts +1 -2
- package/dist/api/health-score.d.ts.map +1 -1
- package/dist/api/health-score.js +2 -46
- package/dist/api/health-score.js.map +1 -1
- package/dist/api/hook-request.d.ts +1 -40
- package/dist/api/hook-request.d.ts.map +1 -1
- package/dist/api/hook-request.js +2 -277
- package/dist/api/hook-request.js.map +1 -1
- package/dist/api/hotspots.d.ts +1 -2
- package/dist/api/hotspots.d.ts.map +1 -1
- package/dist/api/hotspots.js +2 -61
- package/dist/api/hotspots.js.map +1 -1
- package/dist/api/identity.d.ts +1 -16
- package/dist/api/identity.d.ts.map +1 -1
- package/dist/api/identity.js +2 -78
- package/dist/api/identity.js.map +1 -1
- package/dist/api/index.d.ts +1 -34
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -44
- package/dist/api/index.js.map +1 -1
- package/dist/api/mode.d.ts +1 -22
- package/dist/api/mode.d.ts.map +1 -1
- package/dist/api/mode.js +2 -37
- package/dist/api/mode.js.map +1 -1
- package/dist/api/otlp.d.ts +1 -2
- package/dist/api/otlp.d.ts.map +1 -1
- package/dist/api/otlp.js +2 -46
- package/dist/api/otlp.js.map +1 -1
- package/dist/api/permissions.d.ts +1 -15
- package/dist/api/permissions.d.ts.map +1 -1
- package/dist/api/permissions.js +2 -66
- package/dist/api/permissions.js.map +1 -1
- package/dist/api/persona.d.ts +1 -8
- package/dist/api/persona.d.ts.map +1 -1
- package/dist/api/persona.js +2 -67
- package/dist/api/persona.js.map +1 -1
- package/dist/api/portrait.d.ts +1 -5
- package/dist/api/portrait.d.ts.map +1 -1
- package/dist/api/portrait.js +2 -27
- package/dist/api/portrait.js.map +1 -1
- package/dist/api/settings.d.ts +1 -53
- package/dist/api/settings.d.ts.map +1 -1
- package/dist/api/settings.js +2 -464
- package/dist/api/settings.js.map +1 -1
- package/dist/api/spans.d.ts +1 -16
- package/dist/api/spans.d.ts.map +1 -1
- package/dist/api/spans.js +2 -244
- package/dist/api/spans.js.map +1 -1
- package/dist/api/stats.d.ts +1 -12
- package/dist/api/stats.d.ts.map +1 -1
- package/dist/api/stats.js +2 -84
- package/dist/api/stats.js.map +1 -1
- package/dist/api/story.d.ts +1 -2
- package/dist/api/story.d.ts.map +1 -1
- package/dist/api/story.js +2 -14
- package/dist/api/story.js.map +1 -1
- package/dist/api/telemetry.d.ts +1 -18
- package/dist/api/telemetry.d.ts.map +1 -1
- package/dist/api/telemetry.js +2 -164
- package/dist/api/telemetry.js.map +1 -1
- package/dist/api/theme-agents.d.ts +1 -60
- package/dist/api/theme-agents.d.ts.map +1 -1
- package/dist/api/theme-agents.js +2 -213
- package/dist/api/theme-agents.js.map +1 -1
- package/dist/api/todos.d.ts +1 -32
- package/dist/api/todos.d.ts.map +1 -1
- package/dist/api/todos.js +2 -43
- package/dist/api/todos.js.map +1 -1
- package/dist/api/token-stats.d.ts +1 -7
- package/dist/api/token-stats.d.ts.map +1 -1
- package/dist/api/token-stats.js +2 -35
- package/dist/api/token-stats.js.map +1 -1
- package/dist/api/welcome.d.ts +1 -21
- package/dist/api/welcome.d.ts.map +1 -1
- package/dist/api/welcome.js +2 -34
- package/dist/api/welcome.js.map +1 -1
- package/dist/bikerack.js +2 -2
- package/dist/bikerack.js.map +1 -1
- package/dist/env.d.ts +6 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +10 -0
- package/dist/env.js.map +1 -0
- package/dist/focus.d.ts +53 -0
- package/dist/focus.d.ts.map +1 -0
- package/dist/focus.js +122 -0
- package/dist/focus.js.map +1 -0
- package/dist/git-cache.d.ts +1 -0
- package/dist/git-cache.d.ts.map +1 -1
- package/dist/git-cache.js +3 -1
- package/dist/git-cache.js.map +1 -1
- package/dist/menu-builder.d.ts.map +1 -1
- package/dist/menu-builder.js +0 -1
- package/dist/menu-builder.js.map +1 -1
- package/dist/prime.d.ts +3 -3
- package/dist/prime.d.ts.map +1 -1
- package/dist/prime.js +38 -14
- package/dist/prime.js.map +1 -1
- package/dist/public/css/react.css +1 -1
- package/dist/public/js/react/react.js +53 -61
- package/dist/server.d.ts +18 -85
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +105 -405
- package/dist/server.js.map +1 -1
- package/dist/sprint-data.d.ts +1 -1
- package/dist/sprint-data.d.ts.map +1 -1
- package/dist/sprint-data.js +2 -2
- package/dist/sprint-data.js.map +1 -1
- package/dist/theme-metadata.d.ts +3 -3
- package/dist/theme-metadata.d.ts.map +1 -1
- package/dist/theme-metadata.js +4 -4
- package/dist/theme-metadata.js.map +1 -1
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +53 -75
- package/dist/websocket.js.map +1 -1
- package/package.json +2 -6
- package/portraits/hogans-heroes/large/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/large/carter-34352.png +0 -0
- package/portraits/hogans-heroes/large/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/large/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/large/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/large/klink-23434.png +0 -0
- package/portraits/hogans-heroes/large/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/large/marya-53543.png +0 -0
- package/portraits/hogans-heroes/large/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/large/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/large/underground-55131.png +0 -0
- package/portraits/hogans-heroes/medium/burkhalter-35312.png +0 -0
- package/portraits/hogans-heroes/medium/carter-34352.png +0 -0
- package/portraits/hogans-heroes/medium/hochstetter-45314.png +0 -0
- package/portraits/hogans-heroes/medium/hogan-44541.png +0 -0
- package/portraits/hogans-heroes/medium/kinch-35241.png +0 -0
- package/portraits/hogans-heroes/medium/klink-23434.png +0 -0
- package/portraits/hogans-heroes/medium/lebeau-45443.png +0 -0
- package/portraits/hogans-heroes/medium/marya-53543.png +0 -0
- package/portraits/hogans-heroes/medium/newkirk-54432.png +0 -0
- package/portraits/hogans-heroes/medium/schultz-42453.png +0 -0
- package/portraits/hogans-heroes/medium/underground-55131.png +0 -0
- package/portraits/monty-python/large/announcer-44441.png +0 -0
- package/portraits/monty-python/large/arguer-35412.png +0 -0
- package/portraits/monty-python/large/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/large/colonel-35423.png +0 -0
- package/portraits/monty-python/large/counsellor-45341.png +0 -0
- package/portraits/monty-python/large/gumbys-23524.png +0 -0
- package/portraits/monty-python/large/nudge-43533.png +0 -0
- package/portraits/monty-python/large/praline-45413.png +0 -0
- package/portraits/monty-python/large/silly-walks-55322.png +0 -0
- package/portraits/monty-python/large/wensleydale-54451.png +0 -0
- package/portraits/monty-python/large/xim-nez-43534.png +0 -0
- package/portraits/monty-python/medium/announcer-44441.png +0 -0
- package/portraits/monty-python/medium/arguer-35412.png +0 -0
- package/portraits/monty-python/medium/bicycle-repair-man-35241.png +0 -0
- package/portraits/monty-python/medium/colonel-35423.png +0 -0
- package/portraits/monty-python/medium/counsellor-45341.png +0 -0
- package/portraits/monty-python/medium/gumbys-23524.png +0 -0
- package/portraits/monty-python/medium/nudge-43533.png +0 -0
- package/portraits/monty-python/medium/praline-45413.png +0 -0
- package/portraits/monty-python/medium/silly-walks-55322.png +0 -0
- package/portraits/monty-python/medium/wensleydale-54451.png +0 -0
- package/portraits/monty-python/medium/xim-nez-43534.png +0 -0
- package/portraits/stephen-king/large/andy-55231.png +0 -0
- package/portraits/stephen-king/large/christine-25112.png +0 -0
- package/portraits/stephen-king/large/danny-53243.png +0 -0
- package/portraits/stephen-king/large/flagg-55311.png +0 -0
- package/portraits/stephen-king/large/gaunt-54421.png +0 -0
- package/portraits/stephen-king/large/jack-44224.png +0 -0
- package/portraits/stephen-king/large/johnny-44353.png +0 -0
- package/portraits/stephen-king/large/margaret-15415.png +0 -0
- package/portraits/stephen-king/large/paul-45233.png +0 -0
- package/portraits/stephen-king/large/pennywise-54411.png +0 -0
- package/portraits/stephen-king/large/roland-35121.png +0 -0
- package/portraits/stephen-king/medium/andy-55231.png +0 -0
- package/portraits/stephen-king/medium/christine-25112.png +0 -0
- package/portraits/stephen-king/medium/danny-53243.png +0 -0
- package/portraits/stephen-king/medium/flagg-55311.png +0 -0
- package/portraits/stephen-king/medium/gaunt-54421.png +0 -0
- package/portraits/stephen-king/medium/jack-44224.png +0 -0
- package/portraits/stephen-king/medium/johnny-44353.png +0 -0
- package/portraits/stephen-king/medium/margaret-15415.png +0 -0
- package/portraits/stephen-king/medium/paul-45233.png +0 -0
- package/portraits/stephen-king/medium/pennywise-54411.png +0 -0
- package/portraits/stephen-king/medium/roland-35121.png +0 -0
- package/portraits/star-trek-tng/large/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/large/data-55241.png +0 -0
- package/portraits/star-trek-tng/large/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/large/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/large/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/large/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/large/miles-35342.png +0 -0
- package/portraits/star-trek-tng/large/q-53521.png +0 -0
- package/portraits/star-trek-tng/large/spock-45231.png +0 -0
- package/portraits/star-trek-tng/large/troi-44352.png +0 -0
- package/portraits/star-trek-tng/medium/beverly-44352.png +0 -0
- package/portraits/star-trek-tng/medium/data-55241.png +0 -0
- package/portraits/star-trek-tng/medium/deanna-43353.png +0 -0
- package/portraits/star-trek-tng/medium/geordi-54342.png +0 -0
- package/portraits/star-trek-tng/medium/jean-luc-45342.png +0 -0
- package/portraits/star-trek-tng/medium/kathryn-45332.png +0 -0
- package/portraits/star-trek-tng/medium/miles-35342.png +0 -0
- package/portraits/star-trek-tng/medium/q-53521.png +0 -0
- package/portraits/star-trek-tng/medium/spock-45231.png +0 -0
- package/portraits/star-trek-tng/medium/troi-44352.png +0 -0
- package/src/public/App.tsx +0 -340
- package/src/public/components/AgentLoadDialog.tsx +0 -202
- package/src/public/components/AgentPopup.tsx +0 -308
- package/src/public/components/ApprovalModal/ApprovalModal.css +0 -35
- package/src/public/components/ApprovalModal/index.tsx +0 -632
- package/src/public/components/BikeRackIndex.tsx +0 -54
- package/src/public/components/BikeRackWorkspace.tsx +0 -142
- package/src/public/components/CommandPalette.tsx +0 -555
- package/src/public/components/ConfirmDialog.tsx +0 -168
- package/src/public/components/ContextIndicator/ContextIndicator.css +0 -85
- package/src/public/components/ContextIndicator/index.tsx +0 -330
- package/src/public/components/ContextSparkline.tsx +0 -56
- package/src/public/components/ControlBar.tsx +0 -636
- package/src/public/components/DeadCodeDialog.tsx +0 -169
- package/src/public/components/DiffViewer.tsx +0 -585
- package/src/public/components/DockviewWorkspace.tsx +0 -737
- package/src/public/components/Editor.tsx +0 -630
- package/src/public/components/ErrorBoundary.tsx +0 -67
- package/src/public/components/FileTree.tsx +0 -379
- package/src/public/components/FontPicker/FontPicker.css +0 -276
- package/src/public/components/FontPicker/index.tsx +0 -430
- package/src/public/components/FullFileTree.tsx +0 -237
- package/src/public/components/HealthGauge.tsx +0 -181
- package/src/public/components/Message.tsx +0 -225
- package/src/public/components/MessageList.tsx +0 -98
- package/src/public/components/MessageView.tsx +0 -400
- package/src/public/components/ModeSwitch/ModeSwitch.css +0 -165
- package/src/public/components/ModeSwitch/index.tsx +0 -372
- package/src/public/components/PersonaHeader.tsx +0 -240
- package/src/public/components/QuickActions.tsx +0 -267
- package/src/public/components/SpanTimeline.tsx +0 -352
- package/src/public/components/StandalonePanel.tsx +0 -84
- package/src/public/components/StatsStrip.tsx +0 -162
- package/src/public/components/StreamingContent.tsx +0 -77
- package/src/public/components/SubagentSpan.tsx +0 -180
- package/src/public/components/TandemPortrait.tsx +0 -72
- package/src/public/components/ThemePalette/ThemePalette.css +0 -179
- package/src/public/components/ThemePalette/index.tsx +0 -326
- package/src/public/components/ToolCallBlock.tsx +0 -252
- package/src/public/components/ToolStack.tsx +0 -209
- package/src/public/components/ToolStatus.tsx +0 -57
- package/src/public/components/dialogs/CodeMarkersDialog.tsx +0 -169
- package/src/public/components/dialogs/ComplexityDialog.tsx +0 -163
- package/src/public/components/dialogs/DependenciesDialog.tsx +0 -120
- package/src/public/components/dialogs/HotspotsDialog.tsx +0 -451
- package/src/public/components/dialogs/ToolDialog.tsx +0 -43
- package/src/public/components/panel-registry.ts +0 -11
- package/src/public/components/panels/ACPanel.tsx +0 -93
- package/src/public/components/panels/AcceptanceCriteriaPanel.tsx +0 -104
- package/src/public/components/panels/AuditLogPanel.tsx +0 -465
- package/src/public/components/panels/BackgroundPanel.tsx +0 -115
- package/src/public/components/panels/BikeLanePanel.tsx +0 -214
- package/src/public/components/panels/ChangedPanel.tsx +0 -65
- package/src/public/components/panels/DebugPanel.tsx +0 -344
- package/src/public/components/panels/DiffsPanel.tsx +0 -155
- package/src/public/components/panels/GitPanel.tsx +0 -216
- package/src/public/components/panels/HotspotsPanel.tsx +0 -365
- package/src/public/components/panels/MessagePanel.tsx +0 -497
- package/src/public/components/panels/SettingsPanel.tsx +0 -453
- package/src/public/components/panels/SprintPanel.tsx +0 -670
- package/src/public/components/panels/TTYPanel.tsx +0 -299
- package/src/public/components/panels/TodoPanel.tsx +0 -142
- package/src/public/components/panels/WorkflowPanel.tsx +0 -224
- package/src/public/components/panels/index.ts +0 -24
- package/src/public/components/ui/alert-dialog.tsx +0 -139
- package/src/public/components/ui/badge.tsx +0 -36
- package/src/public/components/ui/button.tsx +0 -57
- package/src/public/components/ui/checkbox.tsx +0 -28
- package/src/public/components/ui/collapsible.tsx +0 -9
- package/src/public/components/ui/command.tsx +0 -151
- package/src/public/components/ui/dialog.tsx +0 -120
- package/src/public/components/ui/popover.tsx +0 -31
- package/src/public/components/ui/progress.tsx +0 -28
- package/src/public/components/ui/scroll-area.tsx +0 -46
- package/src/public/components/ui/select.tsx +0 -157
- package/src/public/components/ui/separator.tsx +0 -29
- package/src/public/components/ui/skeleton.tsx +0 -15
- package/src/public/components/ui/switch.tsx +0 -27
- package/src/public/components/ui/toggle-group.tsx +0 -59
- package/src/public/components/ui/toggle.tsx +0 -43
- package/src/public/components/ui/tooltip.tsx +0 -30
- package/src/public/contexts/ClaudeContext.tsx +0 -311
- package/src/public/contexts/MessageQueueContext.tsx +0 -143
- package/src/public/css/theme-browser.css +0 -550
- package/src/public/css/theme-system.css +0 -630
- package/src/public/hooks/index.ts +0 -49
- package/src/public/hooks/useAgentLoad.ts +0 -105
- package/src/public/hooks/useBackgroundTasks.ts +0 -131
- package/src/public/hooks/useClaude.ts +0 -234
- package/src/public/hooks/useCodeMarkers.ts +0 -101
- package/src/public/hooks/useColorScheme.ts +0 -42
- package/src/public/hooks/useCommandHistory.ts +0 -99
- package/src/public/hooks/useComplexity.ts +0 -80
- package/src/public/hooks/useDeadCode.ts +0 -99
- package/src/public/hooks/useDependencies.ts +0 -82
- package/src/public/hooks/useDiffs.ts +0 -143
- package/src/public/hooks/useFileBrowser.ts +0 -71
- package/src/public/hooks/useGitStatus.ts +0 -233
- package/src/public/hooks/useHealthScore.ts +0 -69
- package/src/public/hooks/useHotspots.ts +0 -123
- package/src/public/hooks/useLayoutPersistence.ts +0 -138
- package/src/public/hooks/useMarkdownParser.ts +0 -36
- package/src/public/hooks/useMarkerActions.ts +0 -234
- package/src/public/hooks/useMessageQueue.ts +0 -380
- package/src/public/hooks/useMessageStream.ts +0 -131
- package/src/public/hooks/usePersona.ts +0 -112
- package/src/public/hooks/usePlanModeExit.ts +0 -105
- package/src/public/hooks/useResponsiveLayout.ts +0 -173
- package/src/public/hooks/useSprint.ts +0 -147
- package/src/public/hooks/useStatsStrip.ts +0 -204
- package/src/public/hooks/useStory.ts +0 -135
- package/src/public/hooks/useSubagentHelper.ts +0 -64
- package/src/public/hooks/useSyntaxHighlighter.ts +0 -52
- package/src/public/hooks/useTabCompletion.ts +0 -124
- package/src/public/hooks/useTodos.ts +0 -93
- package/src/public/hooks/useUserAvatar.ts +0 -54
- package/src/public/index.tsx +0 -10
- package/src/public/lib/utils.ts +0 -6
- package/src/public/styles/dockview-theme.css +0 -459
- package/src/public/styles/tailwind.css +0 -4396
- package/src/public/types/electron.d.ts +0 -18
- package/src/public/types/message.ts +0 -51
- package/src/public/utils/avatar-service.ts +0 -73
- package/src/public/utils/color-presets.ts +0 -940
- package/src/public/utils/font-presets.ts +0 -362
- package/src/public/utils/formatDuration.ts +0 -14
- package/src/public/utils/markdown.ts +0 -249
- package/src/public/utils/messageFilters.ts +0 -128
- package/src/public/utils/slash-commands.ts +0 -353
- package/src/public/utils/subagent-display.ts +0 -146
- package/src/public/utils/syntax.ts +0 -219
- package/src/public/utils/toolIntentSummarizer.ts +0 -199
- package/src/public/utils/toolStackGrouper.ts +0 -106
- package/src/public/utils/toolTypeColors.ts +0 -45
|
@@ -1,670 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SprintPanel - Display current story/sprint info
|
|
3
|
-
*
|
|
4
|
-
* Story MSSCI-12717 - React Migration
|
|
5
|
-
* Story MSSCI-14189 - Enhanced Sprint Panel with story management and epic actions
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
9
|
-
import { Check, Loader, Circle, AlertTriangle } from 'lucide-react';
|
|
10
|
-
import { Button } from '@/components/ui/button';
|
|
11
|
-
import { Badge } from '@/components/ui/badge';
|
|
12
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
13
|
-
import { Skeleton } from '@/components/ui/skeleton';
|
|
14
|
-
import { Separator } from '@/components/ui/separator';
|
|
15
|
-
import { useStory } from '../../hooks/useStory';
|
|
16
|
-
import { useSprint, type SprintStory, type SprintEpic, type FutureEpic } from '../../hooks/useSprint';
|
|
17
|
-
|
|
18
|
-
// =============================================================================
|
|
19
|
-
// Original SprintPanel (unchanged)
|
|
20
|
-
// =============================================================================
|
|
21
|
-
|
|
22
|
-
export function SprintPanel(): React.ReactElement {
|
|
23
|
-
const { story, isLoading, error } = useStory();
|
|
24
|
-
|
|
25
|
-
if (isLoading) {
|
|
26
|
-
return (
|
|
27
|
-
<div className="sprint-panel loading" data-testid="sprint-panel">
|
|
28
|
-
<div className="space-y-2 p-2">
|
|
29
|
-
<Skeleton className="h-4 w-24" />
|
|
30
|
-
<Skeleton className="h-5 w-48" />
|
|
31
|
-
<Skeleton className="h-4 w-32" />
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (error) {
|
|
38
|
-
return (
|
|
39
|
-
<div className="sprint-panel error" data-testid="sprint-panel">
|
|
40
|
-
<div className="error-message">{error.message}</div>
|
|
41
|
-
</div>
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!story) {
|
|
46
|
-
return (
|
|
47
|
-
<div className="sprint-panel empty" data-testid="sprint-panel">
|
|
48
|
-
<div className="placeholder">No active story</div>
|
|
49
|
-
<p className="hint">Use /sprint work to start a story</p>
|
|
50
|
-
</div>
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return (
|
|
55
|
-
<div className="sprint-panel" data-testid="sprint-panel">
|
|
56
|
-
<div className="story-header">
|
|
57
|
-
<span className="story-id">{story.id}</span>
|
|
58
|
-
{story.status && <span className="story-status">{story.status}</span>}
|
|
59
|
-
</div>
|
|
60
|
-
<h3 className="story-title">{story.title}</h3>
|
|
61
|
-
{story.phase && (
|
|
62
|
-
<div className="story-phase">
|
|
63
|
-
Phase: <strong>{story.phase}</strong>
|
|
64
|
-
</div>
|
|
65
|
-
)}
|
|
66
|
-
{story.workflow && (
|
|
67
|
-
<div className="story-workflow">
|
|
68
|
-
Workflow: {story.workflow}
|
|
69
|
-
</div>
|
|
70
|
-
)}
|
|
71
|
-
{story.points && (
|
|
72
|
-
<div className="story-points">
|
|
73
|
-
Points: {story.points}
|
|
74
|
-
</div>
|
|
75
|
-
)}
|
|
76
|
-
{story.epic && (
|
|
77
|
-
<div className="story-epic">
|
|
78
|
-
Epic: {story.epic}
|
|
79
|
-
</div>
|
|
80
|
-
)}
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// =============================================================================
|
|
86
|
-
// Enhanced Sprint Panel (MSSCI-14189)
|
|
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
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Calculate epic progress (done points / total points)
|
|
106
|
-
*/
|
|
107
|
-
function calculateEpicProgress(epic: SprintEpic): { done: number; total: number } {
|
|
108
|
-
const total = epic.stories.reduce((sum, s) => sum + s.points, 0);
|
|
109
|
-
const done = epic.stories
|
|
110
|
-
.filter((s) => s.status === 'done')
|
|
111
|
-
.reduce((sum, s) => sum + s.points, 0);
|
|
112
|
-
return { done, total };
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Check if epic is fully completed (all stories done)
|
|
117
|
-
*/
|
|
118
|
-
function isEpicCompleted(epic: SprintEpic): boolean {
|
|
119
|
-
return epic.stories.length > 0 && epic.stories.every((s) => s.status === 'done');
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Get status badge content and class for a story status
|
|
124
|
-
*/
|
|
125
|
-
function getStatusBadgeInfo(status: SprintStory['status']): { icon: React.ReactElement; className: string } {
|
|
126
|
-
const size = 12;
|
|
127
|
-
switch (status) {
|
|
128
|
-
case 'done':
|
|
129
|
-
return { icon: <Check size={size} />, className: 'status-done' };
|
|
130
|
-
case 'in_progress':
|
|
131
|
-
return { icon: <Loader size={size} />, className: 'status-in-progress' };
|
|
132
|
-
case 'blocked':
|
|
133
|
-
return { icon: <AlertTriangle size={size} />, className: 'status-blocked' };
|
|
134
|
-
case 'backlog':
|
|
135
|
-
default:
|
|
136
|
-
return { icon: <Circle size={size} />, className: 'status-backlog' };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Build Jira ticket URL
|
|
142
|
-
*/
|
|
143
|
-
function getJiraUrl(jiraKey: string): string {
|
|
144
|
-
return `https://1898andco.atlassian.net/browse/${jiraKey}`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Context indicator component for epics and stories
|
|
149
|
-
*/
|
|
150
|
-
function ContextIndicator({
|
|
151
|
-
hasContext,
|
|
152
|
-
testIdPrefix,
|
|
153
|
-
id,
|
|
154
|
-
}: {
|
|
155
|
-
hasContext: boolean;
|
|
156
|
-
testIdPrefix: 'epic' | 'story';
|
|
157
|
-
id: string;
|
|
158
|
-
}): React.ReactElement {
|
|
159
|
-
const tooltipText = hasContext ? 'Context file exists' : 'No context file';
|
|
160
|
-
return (
|
|
161
|
-
<Tooltip>
|
|
162
|
-
<TooltipTrigger asChild>
|
|
163
|
-
<span
|
|
164
|
-
className={`context-indicator ${hasContext ? 'has-context' : 'no-context'}`}
|
|
165
|
-
data-testid={`${testIdPrefix}-context-indicator-${id}`}
|
|
166
|
-
data-has-context={String(hasContext)}
|
|
167
|
-
title={tooltipText}
|
|
168
|
-
>
|
|
169
|
-
{hasContext ? '📄' : ''}
|
|
170
|
-
</span>
|
|
171
|
-
</TooltipTrigger>
|
|
172
|
-
<TooltipContent>{tooltipText}</TooltipContent>
|
|
173
|
-
</Tooltip>
|
|
174
|
-
);
|
|
175
|
-
}
|
|
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
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Status badge component for stories
|
|
196
|
-
*/
|
|
197
|
-
function StatusBadge({ status, storyId }: { status: SprintStory['status']; storyId: string }): React.ReactElement {
|
|
198
|
-
const { icon, className } = getStatusBadgeInfo(status);
|
|
199
|
-
return (
|
|
200
|
-
<Badge
|
|
201
|
-
variant={status === 'blocked' ? 'destructive' : status === 'done' ? 'default' : 'secondary'}
|
|
202
|
-
className={`story-status-badge ${className}`}
|
|
203
|
-
data-testid={`story-status-badge-${storyId}`}
|
|
204
|
-
data-status={status}
|
|
205
|
-
aria-label={`Status: ${status}`}
|
|
206
|
-
>
|
|
207
|
-
{icon}
|
|
208
|
-
</Badge>
|
|
209
|
-
);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Jira link component for stories
|
|
214
|
-
*/
|
|
215
|
-
function JiraLink({ jiraKey, storyId }: { jiraKey: string; storyId: string }): React.ReactElement {
|
|
216
|
-
const handleClick = (e: React.MouseEvent) => {
|
|
217
|
-
e.preventDefault();
|
|
218
|
-
const url = getJiraUrl(jiraKey);
|
|
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
|
|
227
|
-
}
|
|
228
|
-
window.open(url, '_blank');
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
return (
|
|
232
|
-
<a
|
|
233
|
-
className="jira-link cyclist-link"
|
|
234
|
-
data-testid={`story-jira-link-${storyId}`}
|
|
235
|
-
href={getJiraUrl(jiraKey)}
|
|
236
|
-
onClick={handleClick}
|
|
237
|
-
>
|
|
238
|
-
{jiraKey}
|
|
239
|
-
</a>
|
|
240
|
-
);
|
|
241
|
-
}
|
|
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
|
-
|
|
395
|
-
/**
|
|
396
|
-
* EnhancedSprintPanel - Full sprint management with epic actions
|
|
397
|
-
*/
|
|
398
|
-
export function EnhancedSprintPanel(): React.ReactElement {
|
|
399
|
-
// Use the sprint hook for data fetching via WebSocket
|
|
400
|
-
const { data, isLoading, error } = useSprint();
|
|
401
|
-
const [expandedEpics, setExpandedEpics] = useState<Set<string>>(new Set());
|
|
402
|
-
const [loadingActions, setLoadingActions] = useState<Set<string>>(new Set());
|
|
403
|
-
const [confirmArchive, setConfirmArchive] = useState<string | null>(null);
|
|
404
|
-
const [actionError, setActionError] = useState<Error | null>(null);
|
|
405
|
-
|
|
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);
|
|
413
|
-
useEffect(() => {
|
|
414
|
-
if (data?.epics && !hasInitializedExpansion.current) {
|
|
415
|
-
hasInitializedExpansion.current = true;
|
|
416
|
-
setExpandedEpics(new Set(activeEpics.map((e) => e.id)));
|
|
417
|
-
}
|
|
418
|
-
}, [data?.epics]);
|
|
419
|
-
|
|
420
|
-
// Toggle epic expansion
|
|
421
|
-
const toggleEpic = useCallback((epicId: string) => {
|
|
422
|
-
setExpandedEpics((prev) => {
|
|
423
|
-
const next = new Set(prev);
|
|
424
|
-
if (next.has(epicId)) {
|
|
425
|
-
next.delete(epicId);
|
|
426
|
-
} else {
|
|
427
|
-
next.add(epicId);
|
|
428
|
-
}
|
|
429
|
-
return next;
|
|
430
|
-
});
|
|
431
|
-
}, []);
|
|
432
|
-
|
|
433
|
-
// Handle epic toggle via keyboard
|
|
434
|
-
const handleEpicKeyDown = useCallback(
|
|
435
|
-
(epicId: string, event: React.KeyboardEvent) => {
|
|
436
|
-
if (event.key === 'Enter' || event.key === ' ') {
|
|
437
|
-
event.preventDefault();
|
|
438
|
-
toggleEpic(epicId);
|
|
439
|
-
}
|
|
440
|
-
},
|
|
441
|
-
[toggleEpic]
|
|
442
|
-
);
|
|
443
|
-
|
|
444
|
-
// Archive epic action
|
|
445
|
-
const handleArchive = useCallback(
|
|
446
|
-
async (epicId: string) => {
|
|
447
|
-
setLoadingActions((prev) => new Set(prev).add(`archive-${epicId}`));
|
|
448
|
-
setConfirmArchive(null);
|
|
449
|
-
setActionError(null); // Clear any previous errors
|
|
450
|
-
|
|
451
|
-
try {
|
|
452
|
-
// Use electronAPI if available (Electron mode), otherwise REST endpoint (web mode)
|
|
453
|
-
if (typeof window !== 'undefined' && (window as any).electronAPI?.sprint?.archiveEpic) {
|
|
454
|
-
await (window as any).electronAPI.sprint.archiveEpic(epicId);
|
|
455
|
-
} else {
|
|
456
|
-
const response = await fetch(`/api/sprint/archive-epic/${epicId}`, { method: 'POST' });
|
|
457
|
-
if (!response.ok) throw new Error('Archive failed');
|
|
458
|
-
}
|
|
459
|
-
// Success - error already cleared at start
|
|
460
|
-
} catch (err) {
|
|
461
|
-
setActionError(err instanceof Error ? err : new Error('Archive failed'));
|
|
462
|
-
} finally {
|
|
463
|
-
setLoadingActions((prev) => {
|
|
464
|
-
const next = new Set(prev);
|
|
465
|
-
next.delete(`archive-${epicId}`);
|
|
466
|
-
return next;
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
},
|
|
470
|
-
[]
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
// Promote epic action
|
|
474
|
-
const handlePromote = useCallback(
|
|
475
|
-
async (epicId: string) => {
|
|
476
|
-
setLoadingActions((prev) => new Set(prev).add(`promote-${epicId}`));
|
|
477
|
-
setActionError(null); // Clear any previous errors
|
|
478
|
-
|
|
479
|
-
try {
|
|
480
|
-
// Use electronAPI if available (Electron mode), otherwise REST endpoint (web mode)
|
|
481
|
-
if (typeof window !== 'undefined' && (window as any).electronAPI?.sprint?.promoteEpic) {
|
|
482
|
-
await (window as any).electronAPI.sprint.promoteEpic(epicId);
|
|
483
|
-
} else {
|
|
484
|
-
const response = await fetch(`/api/sprint/promote-epic/${epicId}`, { method: 'POST' });
|
|
485
|
-
if (!response.ok) throw new Error('Promote failed');
|
|
486
|
-
}
|
|
487
|
-
// Success - error already cleared at start
|
|
488
|
-
} catch (err) {
|
|
489
|
-
setActionError(err instanceof Error ? err : new Error('Promote failed'));
|
|
490
|
-
} finally {
|
|
491
|
-
setLoadingActions((prev) => {
|
|
492
|
-
const next = new Set(prev);
|
|
493
|
-
next.delete(`promote-${epicId}`);
|
|
494
|
-
return next;
|
|
495
|
-
});
|
|
496
|
-
}
|
|
497
|
-
},
|
|
498
|
-
[]
|
|
499
|
-
);
|
|
500
|
-
|
|
501
|
-
// Loading state
|
|
502
|
-
if (isLoading) {
|
|
503
|
-
return (
|
|
504
|
-
<div className="enhanced-sprint-panel" data-testid="enhanced-sprint-panel">
|
|
505
|
-
<div className="loading-state space-y-3 p-2" data-testid="sprint-panel-loading">
|
|
506
|
-
<Skeleton className="h-5 w-32" />
|
|
507
|
-
<Skeleton className="h-4 w-full" />
|
|
508
|
-
<Separator className="my-2" />
|
|
509
|
-
<Skeleton className="h-5 w-28" />
|
|
510
|
-
<Skeleton className="h-8 w-full" />
|
|
511
|
-
<Skeleton className="h-8 w-full" />
|
|
512
|
-
<Separator className="my-2" />
|
|
513
|
-
<Skeleton className="h-5 w-36" />
|
|
514
|
-
<Skeleton className="h-6 w-3/4" />
|
|
515
|
-
</div>
|
|
516
|
-
</div>
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// Error toast (show either WebSocket error or action error)
|
|
521
|
-
const displayError = error || actionError;
|
|
522
|
-
const errorToast = displayError ? (
|
|
523
|
-
<div className="error-toast" data-testid="error-toast">
|
|
524
|
-
{displayError.message}
|
|
525
|
-
</div>
|
|
526
|
-
) : null;
|
|
527
|
-
|
|
528
|
-
// Confirmation dialog
|
|
529
|
-
const confirmDialog = confirmArchive ? (
|
|
530
|
-
<div className="confirm-dialog" data-testid="confirm-archive-dialog">
|
|
531
|
-
<p>Are you sure you want to archive this epic?</p>
|
|
532
|
-
<Button variant="destructive" size="sm" data-testid="confirm-archive-yes" onClick={() => handleArchive(confirmArchive)}>
|
|
533
|
-
Yes
|
|
534
|
-
</Button>
|
|
535
|
-
<Button variant="outline" size="sm" data-testid="confirm-archive-no" onClick={() => setConfirmArchive(null)}>
|
|
536
|
-
No
|
|
537
|
-
</Button>
|
|
538
|
-
</div>
|
|
539
|
-
) : null;
|
|
540
|
-
|
|
541
|
-
return (
|
|
542
|
-
<TooltipProvider delayDuration={300}>
|
|
543
|
-
<div className="enhanced-sprint-panel" data-testid="enhanced-sprint-panel">
|
|
544
|
-
{errorToast}
|
|
545
|
-
{confirmDialog}
|
|
546
|
-
|
|
547
|
-
{/* Section 1: Current Story */}
|
|
548
|
-
<section data-section="current-story">
|
|
549
|
-
<h2>Current Story</h2>
|
|
550
|
-
{data?.currentStory ? (
|
|
551
|
-
<div data-testid="current-story-section">
|
|
552
|
-
<span className="story-id">{data.currentStory.id}</span>
|
|
553
|
-
<span className="story-title">{data.currentStory.title}</span>
|
|
554
|
-
<span className="story-status">{data.currentStory.status}</span>
|
|
555
|
-
<span className="story-points" data-testid="current-story-points">
|
|
556
|
-
{data.currentStory.points} pts
|
|
557
|
-
</span>
|
|
558
|
-
</div>
|
|
559
|
-
) : data?.nextStory ? (
|
|
560
|
-
<div data-testid="next-up-section">
|
|
561
|
-
<span className="next-up-label">Next up:</span>
|
|
562
|
-
<span className="story-id">{data.nextStory.id}</span>
|
|
563
|
-
<span className="story-title">{data.nextStory.title}</span>
|
|
564
|
-
</div>
|
|
565
|
-
) : (
|
|
566
|
-
<div data-testid="no-stories-section">
|
|
567
|
-
<span>No active story</span>
|
|
568
|
-
</div>
|
|
569
|
-
)}
|
|
570
|
-
</section>
|
|
571
|
-
|
|
572
|
-
<Separator className="my-2" />
|
|
573
|
-
|
|
574
|
-
{/* Section 2: Active Epics */}
|
|
575
|
-
<section data-section="epics">
|
|
576
|
-
<h2>Current Epics</h2>
|
|
577
|
-
<div data-testid="epic-tree-view">
|
|
578
|
-
{(!data?.epics || data.epics.length === 0) && (
|
|
579
|
-
<div className="empty-state" data-testid="no-epics-section">
|
|
580
|
-
<span>No epics in current sprint</span>
|
|
581
|
-
<p className="hint">Promote an epic from Future Initiatives to get started</p>
|
|
582
|
-
</div>
|
|
583
|
-
)}
|
|
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
|
-
))}
|
|
595
|
-
</div>
|
|
596
|
-
</section>
|
|
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
|
-
|
|
621
|
-
<Separator className="my-2" />
|
|
622
|
-
|
|
623
|
-
{/* Section 3: Future Initiatives */}
|
|
624
|
-
<section data-section="future">
|
|
625
|
-
<h2>Future Initiatives</h2>
|
|
626
|
-
<div data-testid="future-initiatives-section">
|
|
627
|
-
{data?.futureEpics.map((epic) => {
|
|
628
|
-
const isPromoting = loadingActions.has(`promote-${epic.id}`);
|
|
629
|
-
const canPromote = epic.status === 'ready';
|
|
630
|
-
|
|
631
|
-
return (
|
|
632
|
-
<div
|
|
633
|
-
key={epic.id}
|
|
634
|
-
className="future-epic"
|
|
635
|
-
data-testid={`future-epic-${epic.id}`}
|
|
636
|
-
>
|
|
637
|
-
<span className="future-epic-title">{epic.title}</span>
|
|
638
|
-
<span className="future-epic-points">{epic.estimatedPoints} pts</span>
|
|
639
|
-
<Badge
|
|
640
|
-
variant={epic.status === 'ready' ? 'default' : 'secondary'}
|
|
641
|
-
className="future-epic-status"
|
|
642
|
-
data-testid={`future-epic-status-${epic.id}`}
|
|
643
|
-
data-status={epic.status}
|
|
644
|
-
>
|
|
645
|
-
{epic.status}
|
|
646
|
-
</Badge>
|
|
647
|
-
{canPromote && (
|
|
648
|
-
<Button
|
|
649
|
-
variant="default"
|
|
650
|
-
size="sm"
|
|
651
|
-
className="promote-button"
|
|
652
|
-
data-testid={`promote-button-${epic.id}`}
|
|
653
|
-
aria-label={`Promote ${epic.id} to current sprint`}
|
|
654
|
-
disabled={isPromoting}
|
|
655
|
-
onClick={() => handlePromote(epic.id)}
|
|
656
|
-
>
|
|
657
|
-
Promote
|
|
658
|
-
</Button>
|
|
659
|
-
)}
|
|
660
|
-
</div>
|
|
661
|
-
);
|
|
662
|
-
})}
|
|
663
|
-
</div>
|
|
664
|
-
</section>
|
|
665
|
-
</div>
|
|
666
|
-
</TooltipProvider>
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
export default SprintPanel;
|