@pennyfarthing/core 11.3.8 → 11.4.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 (304) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -1
  3. package/packages/core/dist/public/css/react.css +1 -1
  4. package/packages/core/dist/public/js/react/react.js +24 -24
  5. package/packages/core/src/public/App.tsx +356 -0
  6. package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
  7. package/packages/core/src/public/components/AgentPopup.tsx +308 -0
  8. package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
  9. package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
  10. package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
  11. package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
  12. package/packages/core/src/public/components/CommandPalette.tsx +554 -0
  13. package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
  14. package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
  15. package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
  16. package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
  17. package/packages/core/src/public/components/ControlBar.tsx +636 -0
  18. package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
  19. package/packages/core/src/public/components/DiffViewer.tsx +585 -0
  20. package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
  21. package/packages/core/src/public/components/Editor.tsx +630 -0
  22. package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
  23. package/packages/core/src/public/components/FileTree.tsx +379 -0
  24. package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
  25. package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
  26. package/packages/core/src/public/components/FullFileTree.tsx +237 -0
  27. package/packages/core/src/public/components/HealthGauge.tsx +181 -0
  28. package/packages/core/src/public/components/Message.tsx +225 -0
  29. package/packages/core/src/public/components/MessageList.tsx +98 -0
  30. package/packages/core/src/public/components/MessageView.tsx +400 -0
  31. package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
  32. package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
  33. package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
  34. package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
  35. package/packages/core/src/public/components/QuickActions.tsx +267 -0
  36. package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
  37. package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
  38. package/packages/core/src/public/components/StatsStrip.tsx +162 -0
  39. package/packages/core/src/public/components/StreamingContent.tsx +77 -0
  40. package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
  41. package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
  42. package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
  43. package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
  44. package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
  45. package/packages/core/src/public/components/ToolStack.tsx +209 -0
  46. package/packages/core/src/public/components/ToolStatus.tsx +57 -0
  47. package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  48. package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  49. package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  50. package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  51. package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
  52. package/packages/core/src/public/components/panel-registry.ts +13 -0
  53. package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
  54. package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
  55. package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
  56. package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
  57. package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
  58. package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
  59. package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
  60. package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
  61. package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
  62. package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
  63. package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
  64. package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
  65. package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
  66. package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
  67. package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
  68. package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
  69. package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
  70. package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
  71. package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
  72. package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
  73. package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
  74. package/packages/core/src/public/components/panels/index.ts +24 -0
  75. package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
  76. package/packages/core/src/public/components/ui/badge.tsx +36 -0
  77. package/packages/core/src/public/components/ui/button.tsx +57 -0
  78. package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
  79. package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
  80. package/packages/core/src/public/components/ui/command.tsx +151 -0
  81. package/packages/core/src/public/components/ui/dialog.tsx +120 -0
  82. package/packages/core/src/public/components/ui/popover.tsx +31 -0
  83. package/packages/core/src/public/components/ui/progress.tsx +28 -0
  84. package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
  85. package/packages/core/src/public/components/ui/select.tsx +157 -0
  86. package/packages/core/src/public/components/ui/separator.tsx +29 -0
  87. package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
  88. package/packages/core/src/public/components/ui/switch.tsx +27 -0
  89. package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
  90. package/packages/core/src/public/components/ui/toggle.tsx +43 -0
  91. package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
  92. package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
  93. package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
  94. package/packages/core/src/public/css/theme-browser.css +550 -0
  95. package/packages/core/src/public/css/theme-system.css +630 -0
  96. package/packages/core/src/public/hooks/index.ts +49 -0
  97. package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
  98. package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
  99. package/packages/core/src/public/hooks/useClaude.ts +234 -0
  100. package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
  101. package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
  102. package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
  103. package/packages/core/src/public/hooks/useComplexity.ts +80 -0
  104. package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
  105. package/packages/core/src/public/hooks/useDependencies.ts +82 -0
  106. package/packages/core/src/public/hooks/useDiffs.ts +143 -0
  107. package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
  108. package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
  109. package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
  110. package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
  111. package/packages/core/src/public/hooks/useHotspots.ts +123 -0
  112. package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
  113. package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
  114. package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
  115. package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
  116. package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
  117. package/packages/core/src/public/hooks/usePersona.ts +112 -0
  118. package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
  119. package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
  120. package/packages/core/src/public/hooks/useSprint.ts +157 -0
  121. package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
  122. package/packages/core/src/public/hooks/useStory.ts +135 -0
  123. package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
  124. package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
  125. package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
  126. package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
  127. package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
  128. package/packages/core/src/public/hooks/useTodos.ts +93 -0
  129. package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
  130. package/packages/core/src/public/images/cyclist-dark.png +0 -0
  131. package/packages/core/src/public/images/cyclist-light.png +0 -0
  132. package/packages/core/src/public/index.html +14 -0
  133. package/packages/core/src/public/index.tsx +10 -0
  134. package/packages/core/src/public/lib/utils.ts +6 -0
  135. package/packages/core/src/public/styles/dockview-theme.css +376 -0
  136. package/packages/core/src/public/styles/tailwind.css +4454 -0
  137. package/packages/core/src/public/types/message.ts +51 -0
  138. package/packages/core/src/public/utils/avatar-service.ts +73 -0
  139. package/packages/core/src/public/utils/color-presets.ts +940 -0
  140. package/packages/core/src/public/utils/font-presets.ts +362 -0
  141. package/packages/core/src/public/utils/formatDuration.ts +14 -0
  142. package/packages/core/src/public/utils/markdown.ts +249 -0
  143. package/packages/core/src/public/utils/messageFilters.ts +128 -0
  144. package/packages/core/src/public/utils/slash-commands.ts +341 -0
  145. package/packages/core/src/public/utils/subagent-display.ts +146 -0
  146. package/packages/core/src/public/utils/syntax.ts +219 -0
  147. package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
  148. package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
  149. package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
  150. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  151. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  153. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  155. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  156. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  157. package/pennyfarthing-dist/pf/bc/cli.py +0 -1
  158. package/pennyfarthing-dist/pf/bc/focus.py +0 -1
  159. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  160. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  161. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  162. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  163. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  164. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  165. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  166. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  167. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  168. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  169. package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
  170. package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
  171. package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
  172. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
  173. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
  174. package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
  175. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  176. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  177. package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  178. package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
  179. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  181. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  182. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  183. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  184. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  185. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  186. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  187. package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
  188. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  190. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  191. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  193. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  194. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  195. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  196. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  197. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  199. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  204. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  207. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  208. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  209. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  210. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  212. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  213. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  214. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  215. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  216. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  217. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  218. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  220. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  221. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  222. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  223. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  224. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  225. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  226. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  227. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  228. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  229. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  230. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  231. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  235. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  236. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  238. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  239. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  240. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  241. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  243. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  245. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  250. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  251. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  253. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  255. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  257. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  259. package/pennyfarthing-dist/pf/settings/settings.py +44 -8
  260. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  261. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  264. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  266. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  267. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  270. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  274. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  275. package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
  276. package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
  277. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  278. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  279. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  280. package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
  281. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  282. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  283. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  285. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  287. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  289. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  290. package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
  291. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
  292. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
  293. package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
  294. package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
  295. package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
  296. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
  297. package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
  298. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
  299. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
  300. package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
  301. package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
  302. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
  303. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
  304. package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
@@ -0,0 +1,356 @@
1
+ /**
2
+ * Root React component for Cyclist
3
+ * Story MSSCI-12717 - React Migration
4
+ * Story MSSCI-12706 - Layout Persistence
5
+ * Story MSSCI-12769 - Font Customization
6
+ * Story MSSCI-12771 - Accessibility Compliance
7
+ *
8
+ * Renders the DockingWorkspace with all panels registered.
9
+ * Persists layout changes to config.local.yaml.
10
+ */
11
+
12
+ import React, { useEffect, useCallback, useState } from 'react';
13
+ import {
14
+ DockviewWorkspace,
15
+ registerPanelComponent,
16
+ PANEL_INVENTORY,
17
+ } from './components/DockviewWorkspace';
18
+ import { CommandPaletteProvider } from './components/CommandPalette';
19
+ import { ClaudeProvider } from './contexts/ClaudeContext';
20
+ import ClaudeContext from './contexts/ClaudeContext';
21
+ import { MessageQueueProvider } from './contexts/MessageQueueContext';
22
+ import { useLayoutPersistence } from './hooks/useLayoutPersistence';
23
+ import { loadFontSettings, applyFontSettings } from './utils/font-presets';
24
+ import { loadPresetFromProject, applyPreset } from './utils/color-presets';
25
+ import { ErrorBoundary } from './components/ErrorBoundary';
26
+ import { StandalonePanel, getStandalonePanelName } from './components/StandalonePanel';
27
+ import { BikeRackWorkspace } from './components/BikeRackWorkspace';
28
+ import ApprovalModal, { useApprovalModal } from './components/ApprovalModal';
29
+ import { subscribeToPermissionRequests, sendPermissionResponse, createApprovalResponse } from './components/ApprovalModal';
30
+ import type { ApprovalRequest, GrantScope } from './components/ApprovalModal';
31
+
32
+ // Environment discriminator injected by server.ts (ADR-0024)
33
+ declare global {
34
+ interface Window {
35
+ __CYCLIST_MODE__?: 'cyclist' | 'bikerack';
36
+ }
37
+ }
38
+
39
+ // Import all panel components
40
+ // Note: ProgressPanel split into Workflow/AC/Todo panels (MSSCI-14188)
41
+ import {
42
+ MessagePanel,
43
+ EnhancedSprintPanel,
44
+ GitPanel,
45
+ WorkflowPanel,
46
+ ACPanel,
47
+ TodoPanel,
48
+ BackgroundPanel,
49
+ DiffsPanel,
50
+ DebugPanel,
51
+ SettingsPanel,
52
+ AuditLogPanel,
53
+ TandemPanel,
54
+ } from './components/panels';
55
+
56
+ // =============================================================================
57
+ // Panel Registration
58
+ // =============================================================================
59
+
60
+ // Register all panels BEFORE render
61
+ // Center panel (sacred)
62
+ registerPanelComponent(PANEL_INVENTORY.MESSAGE, MessagePanel);
63
+
64
+ // Left sidebar panels
65
+ registerPanelComponent(PANEL_INVENTORY.GIT, GitPanel);
66
+ registerPanelComponent(PANEL_INVENTORY.DIFFS, DiffsPanel);
67
+ registerPanelComponent(PANEL_INVENTORY.DEBUG, DebugPanel);
68
+ registerPanelComponent(PANEL_INVENTORY.AUDIT_LOG, AuditLogPanel);
69
+
70
+ // Right sidebar panels
71
+ // Note: ProgressPanel split into Workflow/AC/Todo panels (MSSCI-14188)
72
+ registerPanelComponent(PANEL_INVENTORY.SPRINT, EnhancedSprintPanel);
73
+ registerPanelComponent(PANEL_INVENTORY.WORKFLOW, WorkflowPanel);
74
+ registerPanelComponent(PANEL_INVENTORY.AC, ACPanel);
75
+ registerPanelComponent(PANEL_INVENTORY.TODO, TodoPanel);
76
+ registerPanelComponent(PANEL_INVENTORY.BACKGROUND, BackgroundPanel);
77
+ registerPanelComponent(PANEL_INVENTORY.SETTINGS, SettingsPanel);
78
+ registerPanelComponent(PANEL_INVENTORY.TANDEM, TandemPanel);
79
+
80
+ // =============================================================================
81
+ // Skip Link Component
82
+ // =============================================================================
83
+
84
+ interface SkipLinkProps {
85
+ href: string;
86
+ children: React.ReactNode;
87
+ }
88
+
89
+ function SkipLink({ href, children }: SkipLinkProps): React.ReactElement {
90
+ const handleClick = useCallback((e: React.MouseEvent<HTMLAnchorElement>) => {
91
+ e.preventDefault();
92
+ const target = document.querySelector(href);
93
+ if (target) {
94
+ (target as HTMLElement).focus();
95
+ }
96
+ }, [href]);
97
+
98
+ return (
99
+ <a
100
+ href={href}
101
+ className="sr-only skip-link"
102
+ onClick={handleClick}
103
+ onFocus={(e) => e.currentTarget.classList.add('skip-link-visible')}
104
+ onBlur={(e) => e.currentTarget.classList.remove('skip-link-visible')}
105
+ >
106
+ {children}
107
+ </a>
108
+ );
109
+ }
110
+
111
+ // =============================================================================
112
+ // Reduced Motion Hook
113
+ // =============================================================================
114
+
115
+ function useReducedMotion(): void {
116
+ useEffect(() => {
117
+ // Check for prefers-reduced-motion
118
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
119
+
120
+ const updateReducedMotion = (matches: boolean) => {
121
+ if (matches) {
122
+ document.documentElement.classList.add('reduced-motion');
123
+ } else {
124
+ document.documentElement.classList.remove('reduced-motion');
125
+ }
126
+ };
127
+
128
+ // Set initial value
129
+ updateReducedMotion(mediaQuery.matches);
130
+
131
+ // Listen for changes
132
+ const listener = (e: MediaQueryListEvent) => {
133
+ updateReducedMotion(e.matches);
134
+ };
135
+
136
+ mediaQuery.addEventListener('change', listener);
137
+ return () => mediaQuery.removeEventListener('change', listener);
138
+ }, []);
139
+ }
140
+
141
+ // =============================================================================
142
+ // App Component
143
+ // =============================================================================
144
+
145
+ // =============================================================================
146
+ // Root Error Fallback
147
+ // =============================================================================
148
+
149
+ /**
150
+ * Root-level error fallback UI - shown when the entire app crashes
151
+ */
152
+ function RootErrorFallback(): React.ReactElement {
153
+ const handleReload = useCallback(() => {
154
+ window.location.reload();
155
+ }, []);
156
+
157
+ return (
158
+ <div style={{
159
+ display: 'flex',
160
+ flexDirection: 'column',
161
+ alignItems: 'center',
162
+ justifyContent: 'center',
163
+ height: '100vh',
164
+ backgroundColor: 'var(--bg-primary, #0a0a0f)',
165
+ color: 'var(--text-primary, #e2e8f0)',
166
+ fontFamily: 'system-ui, -apple-system, sans-serif',
167
+ padding: '24px',
168
+ textAlign: 'center',
169
+ }}>
170
+ <h1 style={{
171
+ fontSize: '24px',
172
+ fontWeight: 600,
173
+ marginBottom: '16px',
174
+ color: 'var(--status-error, #ef4444)',
175
+ }}>
176
+ Something went wrong
177
+ </h1>
178
+ <p style={{
179
+ fontSize: '14px',
180
+ color: 'var(--text-secondary, #94a3b8)',
181
+ marginBottom: '24px',
182
+ maxWidth: '400px',
183
+ }}>
184
+ Cyclist encountered an unexpected error. Check the console for details.
185
+ </p>
186
+ <button
187
+ onClick={handleReload}
188
+ style={{
189
+ padding: '8px 16px',
190
+ fontSize: '14px',
191
+ backgroundColor: 'var(--accent-primary, #6366f1)',
192
+ color: 'white',
193
+ border: 'none',
194
+ borderRadius: '6px',
195
+ cursor: 'pointer',
196
+ }}
197
+ >
198
+ Reload Cyclist
199
+ </button>
200
+ </div>
201
+ );
202
+ }
203
+
204
+ // =============================================================================
205
+ // App Component
206
+ // =============================================================================
207
+
208
+ export default function App(): React.ReactElement {
209
+ // Detect route mode (computed before hooks, used after)
210
+ const isBikeRackIndex = window.__CYCLIST_MODE__ === 'bikerack';
211
+ const standalonePanelName = getStandalonePanelName();
212
+
213
+ // --- All hooks called unconditionally (React rules of hooks) ---
214
+
215
+ const layoutEndpoint = isBikeRackIndex ? '/api/settings/bikerack-layout' : '/api/settings/layout';
216
+ const { layout, isLoading, saveLayout } = useLayoutPersistence(layoutEndpoint);
217
+
218
+ // Set up reduced motion support
219
+ useReducedMotion();
220
+
221
+ // Load and apply font settings on startup (MSSCI-12769)
222
+ useEffect(() => {
223
+ loadFontSettings().then(settings => {
224
+ applyFontSettings(settings);
225
+ });
226
+ }, []);
227
+
228
+ // Load and apply color preset on startup
229
+ useEffect(() => {
230
+ loadPresetFromProject().then(presetId => {
231
+ applyPreset(presetId);
232
+ });
233
+ }, []);
234
+
235
+ // ApprovalModal state management (MSSCI-14322)
236
+ const [requestQueue, setRequestQueue] = useState<ApprovalRequest[]>([]);
237
+ const { request, isOpen, show, hide } = useApprovalModal();
238
+
239
+ // Subscribe to permission requests via WebSocket
240
+ useEffect(() => {
241
+ const unsub = subscribeToPermissionRequests((incoming: ApprovalRequest) => {
242
+ setRequestQueue((prev) => [...prev, incoming]);
243
+ });
244
+ return unsub;
245
+ }, []);
246
+
247
+ // Show the next queued request when the current one is resolved
248
+ useEffect(() => {
249
+ if (!isOpen && requestQueue.length > 0) {
250
+ const [next, ...rest] = requestQueue;
251
+ setRequestQueue(rest);
252
+ show(next);
253
+ }
254
+ }, [isOpen, requestQueue, show]);
255
+
256
+ const handleApprove = useCallback((grantScope: GrantScope) => {
257
+ if (request) {
258
+ const response = createApprovalResponse(request.toolId, true, grantScope);
259
+ sendPermissionResponse(response);
260
+ }
261
+ hide();
262
+ }, [request, hide]);
263
+
264
+ const handleReject = useCallback(() => {
265
+ if (request) {
266
+ const response = createApprovalResponse(request.toolId, false);
267
+ sendPermissionResponse(response);
268
+ }
269
+ hide();
270
+ }, [request, hide]);
271
+
272
+ // --- BikeRack routes (after all hooks) ---
273
+
274
+ // BikeRack Dockview workspace (MSSCI-14877) — /bikerack renders Dockview layout
275
+ // No-op ClaudeContext: BikeRack has no Claude CLI subprocess, skip WebSocket
276
+ if (isBikeRackIndex) {
277
+ if (isLoading) {
278
+ return (
279
+ <div className="cyclist-loading" style={{ height: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
280
+ <div className="loading-spinner" aria-label="Loading layout..." />
281
+ </div>
282
+ );
283
+ }
284
+ const noop = () => () => {};
285
+ return (
286
+ <ClaudeContext.Provider value={{
287
+ send: () => {}, abort: () => {}, clear: () => {},
288
+ clearAndReload: () => {}, setMode: () => {},
289
+ isConnected: false, mode: 'default',
290
+ onMessage: noop, onComplete: noop, onError: noop,
291
+ onUserMessage: noop, onClear: noop,
292
+ }}>
293
+ <BikeRackWorkspace
294
+ initialLayout={layout ?? undefined}
295
+ onLayoutChange={saveLayout}
296
+ />
297
+ </ClaudeContext.Provider>
298
+ );
299
+ }
300
+
301
+ // BikeRack standalone panel routing (MSSCI-14821)
302
+ // URL-based detection only (Rule 10) — ?panel=X renders single panel full-screen
303
+ if (standalonePanelName) {
304
+ return <StandalonePanel />;
305
+ }
306
+
307
+ return (
308
+ <ErrorBoundary fallback={<RootErrorFallback />} panelName="App">
309
+ <ClaudeProvider>
310
+ <MessageQueueProvider>
311
+ <CommandPaletteProvider>
312
+ <div className="cyclist-app">
313
+ {/* Skip links for keyboard navigation (AC7) - always render first */}
314
+ <SkipLink href="#main-content">Skip to main content</SkipLink>
315
+ <SkipLink href="#message-input">Skip to input</SkipLink>
316
+ <SkipLink href="#sidebar-nav">Skip to navigation</SkipLink>
317
+
318
+ {/* Loading state - only show while actually loading */}
319
+ {isLoading ? (
320
+ <main id="main-content" tabIndex={-1}>
321
+ <div className="cyclist-loading">
322
+ <div className="loading-spinner" aria-label="Loading layout..." />
323
+ </div>
324
+ </main>
325
+ ) : (
326
+ /* Main content area - layout can be null for first-time users */
327
+ <main id="main-content" tabIndex={-1}>
328
+ <DockviewWorkspace
329
+ initialLayout={layout ?? undefined}
330
+ onLayoutChange={saveLayout}
331
+ />
332
+ </main>
333
+ )}
334
+
335
+ {/* Sidebar navigation target (for skip link) */}
336
+ <nav id="sidebar-nav" tabIndex={-1} style={{ display: 'contents' }} aria-hidden="true" />
337
+
338
+ {/* Message input target (for skip link) */}
339
+ <div id="message-input" tabIndex={-1} style={{ display: 'contents' }} aria-hidden="true" />
340
+ </div>
341
+
342
+ {/* ApprovalModal - renders via Radix Portal outside the React tree (MSSCI-14322) */}
343
+ <ApprovalModal
344
+ isOpen={isOpen}
345
+ toolName={request?.toolName ?? ''}
346
+ toolId={request?.toolId ?? ''}
347
+ input={request?.input ?? {}}
348
+ onApprove={handleApprove}
349
+ onReject={handleReject}
350
+ />
351
+ </CommandPaletteProvider>
352
+ </MessageQueueProvider>
353
+ </ClaudeProvider>
354
+ </ErrorBoundary>
355
+ );
356
+ }
@@ -0,0 +1,202 @@
1
+ import React, { useState, useEffect, useMemo } from 'react';
2
+ import { Button } from '@/components/ui/button';
3
+ import { Progress } from '@/components/ui/progress';
4
+ import { Skeleton } from '@/components/ui/skeleton';
5
+ import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible';
6
+ import { ToolDialog } from './dialogs/ToolDialog';
7
+ import { ConfirmDialog, useConfirmDialog } from './ConfirmDialog';
8
+ import { useAgentLoad } from '../hooks/useAgentLoad.js';
9
+ import type { AgentLoadEntry } from '../hooks/useAgentLoad.js';
10
+ import { formatComponentName } from './panels/DebugPanel';
11
+
12
+ const SIDECAR_FILES = ['patterns.md', 'gotchas.md', 'decisions.md'] as const;
13
+
14
+ export interface AgentLoadDialogProps {
15
+ isOpen: boolean;
16
+ onClose: () => void;
17
+ }
18
+
19
+ export function AgentLoadDialog({ isOpen, onClose }: AgentLoadDialogProps): React.ReactElement {
20
+ const { data, isLoading, error, refresh, pruneSidecar, pruneResult } = useAgentLoad();
21
+ const [expandedAgent, setExpandedAgent] = useState<string | null>(null);
22
+ const [pendingPrune, setPendingPrune] = useState<{ agent: string; file: string } | null>(null);
23
+
24
+ const { confirm, dialogProps } = useConfirmDialog({
25
+ title: 'Clear Sidecar',
26
+ message: pendingPrune
27
+ ? `Reset ${pendingPrune.file} for ${pendingPrune.agent}? This will replace the sidecar with its default template.`
28
+ : '',
29
+ confirmLabel: 'Confirm',
30
+ isDanger: true,
31
+ });
32
+
33
+ useEffect(() => {
34
+ if (isOpen) {
35
+ refresh();
36
+ }
37
+ }, [isOpen, refresh]);
38
+
39
+ const sortedAgents = useMemo(() => {
40
+ if (!data) return [];
41
+ return [...data.agents].sort((a, b) => (b.totalTokens ?? 0) - (a.totalTokens ?? 0));
42
+ }, [data]);
43
+
44
+ const maxTokens = useMemo(() => {
45
+ if (!sortedAgents.length) return 0;
46
+ return sortedAgents[0]?.totalTokens ?? 0;
47
+ }, [sortedAgents]);
48
+
49
+ const handleRowClick = (agent: string) => {
50
+ setExpandedAgent((prev) => (prev === agent ? null : agent));
51
+ };
52
+
53
+ const handleClear = async (agent: string, file: string) => {
54
+ setPendingPrune({ agent, file });
55
+ const confirmed = await confirm();
56
+ if (confirmed) {
57
+ await pruneSidecar(agent, file);
58
+ }
59
+ setPendingPrune(null);
60
+ };
61
+
62
+ const renderAgentRow = (entry: AgentLoadEntry) => {
63
+ const tokens = entry.totalTokens ?? 0;
64
+ const progressValue = maxTokens > 0 ? (tokens / maxTokens) * 100 : 0;
65
+ const isExpanded = expandedAgent === entry.agent;
66
+ // Color by token threshold: <3k green, <5k orange, >=5k red
67
+ const barColor = tokens >= 5000
68
+ ? 'bg-[var(--status-error,#f14c4c)]'
69
+ : tokens >= 3000
70
+ ? 'bg-[var(--status-warning,#cca700)]'
71
+ : 'bg-[var(--status-success,#4ec9b0)]';
72
+
73
+ return (
74
+ <Collapsible
75
+ key={entry.agent}
76
+ open={isExpanded}
77
+ onOpenChange={() => handleRowClick(entry.agent)}
78
+ >
79
+ <CollapsibleTrigger asChild>
80
+ <div
81
+ className="px-4 py-2 cursor-pointer hover:bg-muted/50 rounded-md transition-colors"
82
+ data-testid={`agent-row-${entry.agent}`}
83
+ >
84
+ <div className="flex items-center justify-between mb-1">
85
+ <span className="font-mono text-sm font-medium">{entry.agent}</span>
86
+ <span className="font-mono text-sm tabular-nums text-text-secondary">
87
+ {tokens.toLocaleString()}
88
+ </span>
89
+ </div>
90
+ <Progress
91
+ value={progressValue}
92
+ className="h-2.5 bg-[var(--border)]"
93
+ indicatorClassName={barColor}
94
+ />
95
+ </div>
96
+ </CollapsibleTrigger>
97
+ <CollapsibleContent>
98
+ <div className="mx-4 mb-3 rounded-md bg-muted/30 p-3 space-y-1">
99
+ {entry.components && entry.components.length > 0 ? (
100
+ entry.components.map((comp) => (
101
+ <div key={comp.name} className="flex items-center justify-between text-xs text-text-secondary">
102
+ <span>{formatComponentName(comp.name)}</span>
103
+ <span className="tabular-nums">{comp.tokens.toLocaleString()}</span>
104
+ </div>
105
+ ))
106
+ ) : (
107
+ <div className="text-xs text-text-secondary">No component breakdown available</div>
108
+ )}
109
+ <div className="pt-2 border-t border-muted/50 mt-2">
110
+ <div className="text-xs font-medium mb-1.5 text-text-secondary">Sidecars</div>
111
+ <div className="flex gap-2">
112
+ {SIDECAR_FILES.map((file) => (
113
+ <Button
114
+ key={file}
115
+ variant="ghost"
116
+ size="sm"
117
+ className="text-xs h-6"
118
+ onClick={(e) => {
119
+ e.stopPropagation();
120
+ handleClear(entry.agent, file);
121
+ }}
122
+ >
123
+ Clear {file}
124
+ </Button>
125
+ ))}
126
+ </div>
127
+ </div>
128
+ </div>
129
+ </CollapsibleContent>
130
+ </Collapsible>
131
+ );
132
+ };
133
+
134
+ const renderContent = () => {
135
+ if (isLoading) {
136
+ return (
137
+ <div className="space-y-3 p-4">
138
+ {Array.from({ length: 5 }).map((_, i) => (
139
+ <div key={i} data-testid={`skeleton-${i}`} className="space-y-1.5 px-4">
140
+ <div className="flex justify-between">
141
+ <Skeleton className="h-4 w-24" />
142
+ <Skeleton className="h-4 w-16" />
143
+ </div>
144
+ <Skeleton className="h-2.5 w-full rounded-full" />
145
+ </div>
146
+ ))}
147
+ </div>
148
+ );
149
+ }
150
+
151
+ if (error) {
152
+ return (
153
+ <div className="p-4 space-y-3">
154
+ <div className="p-4 rounded border border-[var(--status-error)]/20 bg-[var(--status-error)]/5 text-[var(--status-error)] text-sm">
155
+ {error.message}
156
+ </div>
157
+ <div className="text-center">
158
+ <Button variant="outline" size="sm" onClick={refresh}>
159
+ Retry
160
+ </Button>
161
+ </div>
162
+ </div>
163
+ );
164
+ }
165
+
166
+ if (!data) return null;
167
+
168
+ return (
169
+ <div className="space-y-0.5">
170
+ {pruneResult?.success && pruneResult.tokensFreed != null && (
171
+ <div className="text-xs text-green-600 px-4 py-1">
172
+ Freed {pruneResult.tokensFreed.toLocaleString()} tokens from {pruneResult.agent}/{pruneResult.file}
173
+ </div>
174
+ )}
175
+ <div className="flex items-center justify-between px-4 py-2">
176
+ <span data-testid="cached-at" className="text-xs text-[var(--text-muted)]">
177
+ Cached: {new Date(data.cachedAt).toLocaleString()}
178
+ </span>
179
+ <Button variant="ghost" size="sm" className="text-xs h-6 text-[var(--text-muted)] hover:text-[var(--text-primary)]" onClick={refresh}>
180
+ Refresh
181
+ </Button>
182
+ </div>
183
+ {sortedAgents.map((entry) => renderAgentRow(entry))}
184
+ </div>
185
+ );
186
+ };
187
+
188
+ return (
189
+ <>
190
+ <ToolDialog
191
+ open={isOpen}
192
+ onOpenChange={(open) => { if (!open) onClose(); }}
193
+ title="Agent Load Analysis"
194
+ description="Token usage breakdown for all agents"
195
+ className="max-w-2xl"
196
+ >
197
+ {renderContent()}
198
+ </ToolDialog>
199
+ <ConfirmDialog {...dialogProps} />
200
+ </>
201
+ );
202
+ }