@pennyfarthing/core 11.3.7 → 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 (405) hide show
  1. package/README.md +1 -1
  2. package/package.json +17 -16
  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/hooks/cyclist-pretooluse-hook.sh +0 -0
  230. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  231. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  234. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  235. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  236. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  237. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  238. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  239. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  240. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  241. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  243. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  245. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  250. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  251. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  253. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  255. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  257. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  259. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  260. package/pennyfarthing-dist/pf/settings/settings.py +44 -8
  261. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  264. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  266. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  267. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  270. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  274. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  275. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  276. package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
  277. package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
  278. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  280. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  281. package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
  282. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  283. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  285. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  287. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  289. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  290. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  291. package/pennyfarthing-dist/scripts/core/agent-session.sh +0 -0
  292. package/pennyfarthing-dist/scripts/core/check-context.sh +0 -0
  293. package/pennyfarthing-dist/scripts/core/dialogue-manager.sh +0 -0
  294. package/pennyfarthing-dist/scripts/core/pf.sh +0 -0
  295. package/pennyfarthing-dist/scripts/core/phase-check-start.sh +0 -0
  296. package/pennyfarthing-dist/scripts/core/prime.sh +0 -0
  297. package/pennyfarthing-dist/scripts/cyclist/is-cyclist.sh +0 -0
  298. package/pennyfarthing-dist/scripts/git/create-feature-branches.sh +0 -0
  299. package/pennyfarthing-dist/scripts/git/git-status-all.sh +0 -0
  300. package/pennyfarthing-dist/scripts/git/install-git-hooks.sh +0 -0
  301. package/pennyfarthing-dist/scripts/git/release.sh +0 -0
  302. package/pennyfarthing-dist/scripts/git/worktree-manager.sh +0 -0
  303. package/pennyfarthing-dist/scripts/health/drift-detection.sh +0 -0
  304. package/pennyfarthing-dist/scripts/hooks/bell-mode-hook.sh +0 -0
  305. package/pennyfarthing-dist/scripts/hooks/context-circuit-breaker.sh +0 -0
  306. package/pennyfarthing-dist/scripts/hooks/context-warning.sh +0 -0
  307. package/pennyfarthing-dist/scripts/hooks/cyclist-pretooluse-hook.sh +0 -0
  308. package/pennyfarthing-dist/scripts/hooks/dispatcher-template.sh +0 -0
  309. package/pennyfarthing-dist/scripts/hooks/otel-auto-config.sh +0 -0
  310. package/pennyfarthing-dist/scripts/hooks/post-merge.sh +0 -0
  311. package/pennyfarthing-dist/scripts/hooks/pre-commit.sh +0 -0
  312. package/pennyfarthing-dist/scripts/hooks/pre-edit-check.sh +0 -0
  313. package/pennyfarthing-dist/scripts/hooks/pre-push.sh +0 -0
  314. package/pennyfarthing-dist/scripts/hooks/question-reflector-check.sh +0 -0
  315. package/pennyfarthing-dist/scripts/hooks/question_reflector_check.py +0 -0
  316. package/pennyfarthing-dist/scripts/hooks/schema-validation.sh +0 -0
  317. package/pennyfarthing-dist/scripts/hooks/session-start.sh +0 -0
  318. package/pennyfarthing-dist/scripts/hooks/session-stop.sh +0 -0
  319. package/pennyfarthing-dist/scripts/hooks/sprint-yaml-validation.sh +0 -0
  320. package/pennyfarthing-dist/scripts/hooks/welcome-hook.sh +0 -0
  321. package/pennyfarthing-dist/scripts/jira/create-jira-epic.sh +0 -0
  322. package/pennyfarthing-dist/scripts/jira/create-jira-story.sh +0 -0
  323. package/pennyfarthing-dist/scripts/jira/jira-claim-story.sh +0 -0
  324. package/pennyfarthing-dist/scripts/jira/jira-reconcile.sh +0 -0
  325. package/pennyfarthing-dist/scripts/jira/jira-sync-story.sh +0 -0
  326. package/pennyfarthing-dist/scripts/jira/sync-epic-jira.sh +0 -0
  327. package/pennyfarthing-dist/scripts/lib/background-tasks.sh +0 -0
  328. package/pennyfarthing-dist/scripts/lib/checkpoint.sh +0 -0
  329. package/pennyfarthing-dist/scripts/lib/common.sh +0 -0
  330. package/pennyfarthing-dist/scripts/lib/env.sh +0 -0
  331. package/pennyfarthing-dist/scripts/lib/file-lock.sh +0 -0
  332. package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
  333. package/pennyfarthing-dist/scripts/lib/logging.sh +0 -0
  334. package/pennyfarthing-dist/scripts/lib/retry.sh +0 -0
  335. package/pennyfarthing-dist/scripts/lib/run-pf.sh +0 -0
  336. package/pennyfarthing-dist/scripts/maintenance/migrate-theme-schema.mjs +0 -0
  337. package/pennyfarthing-dist/scripts/maintenance/sidecar-health.sh +0 -0
  338. package/pennyfarthing-dist/scripts/misc/add-short-names.sh +0 -0
  339. package/pennyfarthing-dist/scripts/misc/add_short_names.py +0 -0
  340. package/pennyfarthing-dist/scripts/misc/backlog.sh +0 -0
  341. package/pennyfarthing-dist/scripts/misc/check-status.sh +0 -0
  342. package/pennyfarthing-dist/scripts/misc/find-related-work.sh +0 -0
  343. package/pennyfarthing-dist/scripts/misc/generate-skill-docs.sh +0 -0
  344. package/pennyfarthing-dist/scripts/misc/log-skill-usage.sh +0 -0
  345. package/pennyfarthing-dist/scripts/misc/migrate-bmad-workflow.sh +0 -0
  346. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
  347. package/pennyfarthing-dist/scripts/misc/repo-scan.sh +0 -0
  348. package/pennyfarthing-dist/scripts/misc/repo-utils.sh +0 -0
  349. package/pennyfarthing-dist/scripts/misc/run-ci.sh +0 -0
  350. package/pennyfarthing-dist/scripts/misc/run-timestamp.sh +0 -0
  351. package/pennyfarthing-dist/scripts/misc/session-cleanup.sh +0 -0
  352. package/pennyfarthing-dist/scripts/misc/skill-usage-report.sh +0 -0
  353. package/pennyfarthing-dist/scripts/misc/statusline.sh +0 -0
  354. package/pennyfarthing-dist/scripts/misc/uninstall.sh +0 -0
  355. package/pennyfarthing-dist/scripts/misc/validate-subagent-frontmatter.sh +0 -0
  356. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
  357. package/pennyfarthing-dist/scripts/portraits/generate-portraits.sh +0 -0
  358. package/pennyfarthing-dist/scripts/portraits/generate-tandem-portraits.sh +0 -0
  359. package/pennyfarthing-dist/scripts/story/create-story.sh +0 -0
  360. package/pennyfarthing-dist/scripts/story/size-story.sh +0 -0
  361. package/pennyfarthing-dist/scripts/story/story-template.sh +0 -0
  362. package/pennyfarthing-dist/scripts/tests/check.test.sh +0 -0
  363. package/pennyfarthing-dist/scripts/tests/dev-story-workflow-import.test.sh +0 -0
  364. package/pennyfarthing-dist/scripts/tests/epics-and-stories-workflow-import.test.sh +0 -0
  365. package/pennyfarthing-dist/scripts/tests/handoff-phase-update.test.sh +0 -0
  366. package/pennyfarthing-dist/scripts/tests/implementation-readiness-workflow-import.test.sh +0 -0
  367. package/pennyfarthing-dist/scripts/tests/migrate-bmad-workflow.test.sh +0 -0
  368. package/pennyfarthing-dist/scripts/tests/prd-workflow-import.test.sh +0 -0
  369. package/pennyfarthing-dist/scripts/tests/project-context-workflow-import.test.sh +0 -0
  370. package/pennyfarthing-dist/scripts/tests/test-character-voice.sh +0 -0
  371. package/pennyfarthing-dist/scripts/tests/test-drift-detection.sh +0 -0
  372. package/pennyfarthing-dist/scripts/tests/test-post-merge-hook.sh +0 -0
  373. package/pennyfarthing-dist/scripts/tests/test-session-checkpoint.sh +0 -0
  374. package/pennyfarthing-dist/scripts/tests/test-solo-command.sh +0 -0
  375. package/pennyfarthing-dist/scripts/tests/ux-design-workflow-import.test.sh +0 -0
  376. package/pennyfarthing-dist/scripts/theme/list-themes.sh +0 -0
  377. package/pennyfarthing-dist/scripts/validation/validate-agent-schema.sh +0 -0
  378. package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
  379. package/pennyfarthing-dist/scripts/workflow/check.sh +0 -0
  380. package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
  381. package/pennyfarthing-dist/scripts/workflow/finish-story.sh +0 -0
  382. package/pennyfarthing-dist/scripts/workflow/fix-session-phase.sh +0 -0
  383. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.py +0 -0
  384. package/pennyfarthing-dist/scripts/workflow/get-workflow-type.sh +0 -0
  385. package/pennyfarthing-dist/scripts/workflow/list-workflows.sh +0 -0
  386. package/pennyfarthing-dist/scripts/workflow/phase-owner.sh +0 -0
  387. package/pennyfarthing-dist/scripts/workflow/resume-workflow.sh +0 -0
  388. package/pennyfarthing-dist/scripts/workflow/show-workflow.sh +0 -0
  389. package/pennyfarthing-dist/scripts/workflow/start-workflow.sh +0 -0
  390. package/pennyfarthing-dist/scripts/workflow/workflow-status.sh +0 -0
  391. package/pennyfarthing-dist/skills/pf-story/scripts/create-story.sh +0 -0
  392. package/pennyfarthing-dist/skills/pf-story/scripts/size-story.sh +0 -0
  393. package/pennyfarthing-dist/skills/pf-story/scripts/story-template.sh +0 -0
  394. package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
  395. package/pennyfarthing-dist/workflows/release/steps/step-10-publish.md +41 -9
  396. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
  397. package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
  398. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
  399. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
  400. package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
  401. package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
  402. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
  403. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
  404. package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
  405. package/scripts/README.md +0 -41
@@ -0,0 +1,267 @@
1
+ /**
2
+ * QuickActions Component
3
+ *
4
+ * Renders action buttons based on CYCLIST markers detected in assistant messages.
5
+ * Supports handoff buttons, yes/no questions, choices, and continue.
6
+ *
7
+ * For open questions with suggestions (<!-- CYCLIST:QUESTION:open:suggested text -->),
8
+ * pre-fills the editor via 'cyclist:suggest-prompt' event instead of showing buttons.
9
+ *
10
+ * Story: MSSCI-12787 - Implement CYCLIST Marker Parsing and Action Buttons
11
+ * Story: MSSCI-12771 - Accessibility Compliance (ARIA labels)
12
+ */
13
+
14
+ import React, { useState, useEffect, useCallback } from 'react';
15
+ import { Button } from '@/components/ui/button';
16
+ import { useMarkerActions } from '../hooks/useMarkerActions';
17
+ import { useClaudeContext } from '../contexts/ClaudeContext';
18
+ import type { MessageData } from '../types/message';
19
+
20
+ interface ActionItem {
21
+ label: string;
22
+ command: string;
23
+ }
24
+
25
+ interface QuickActionsPropsWithMessage {
26
+ message: MessageData;
27
+ actions?: never;
28
+ onAction?: (response: string) => void;
29
+ }
30
+
31
+ interface QuickActionsPropsWithActions {
32
+ message?: never;
33
+ actions: ActionItem[];
34
+ onAction?: (response: string) => void;
35
+ }
36
+
37
+ type QuickActionsProps = QuickActionsPropsWithMessage | QuickActionsPropsWithActions;
38
+
39
+ /**
40
+ * Get relay mode setting from REST API
41
+ */
42
+ async function getRelayMode(): Promise<boolean> {
43
+ try {
44
+ const response = await fetch('/api/settings');
45
+ if (response.ok) {
46
+ const settings = await response.json();
47
+ return settings?.workflow?.relay_mode ?? false;
48
+ }
49
+ } catch {
50
+ // Ignore errors
51
+ }
52
+ return false;
53
+ }
54
+
55
+ export default function QuickActions(props: QuickActionsProps): React.ReactElement | null {
56
+ // Get send function from Claude context
57
+ const { send: claudeSend } = useClaudeContext();
58
+
59
+ /**
60
+ * Send a message to Claude via WebSocket context
61
+ */
62
+ const sendMessage = useCallback((text: string): void => {
63
+ console.log('[QuickActions] sendMessage called:', text);
64
+ claudeSend(text, []);
65
+ }, [claudeSend]);
66
+ const { onAction } = props;
67
+ const [isDisabled, setIsDisabled] = useState(false);
68
+ const [relayMode, setRelayMode] = useState(false);
69
+
70
+ // Support both message-based and actions-based props
71
+ const message = 'message' in props ? props.message : undefined;
72
+ const directActions = 'actions' in props ? props.actions : undefined;
73
+
74
+ const markerActions = useMarkerActions(message?.content);
75
+
76
+ // Reset disabled state when message changes (new assistant response)
77
+ useEffect(() => {
78
+ setIsDisabled(false);
79
+ }, [message?.timestamp]);
80
+
81
+ // Check relay mode on mount
82
+ useEffect(() => {
83
+ getRelayMode().then(setRelayMode);
84
+ }, []);
85
+
86
+ // Clear QuickActions when user submits any message
87
+ useEffect(() => {
88
+ const handleUserSubmit = () => {
89
+ setIsDisabled(true);
90
+ };
91
+ window.addEventListener('cyclist:user-submit', handleUserSubmit);
92
+ return () => {
93
+ window.removeEventListener('cyclist:user-submit', handleUserSubmit);
94
+ };
95
+ }, []);
96
+
97
+ // Auto-execute handoff when relay mode is ON
98
+ useEffect(() => {
99
+ if (markerActions?.type === 'handoff' && relayMode && markerActions.value) {
100
+ // Auto-execute after short delay
101
+ const timer = setTimeout(() => {
102
+ sendMessage(markerActions.value!);
103
+ setIsDisabled(true);
104
+ onAction?.(markerActions.value!);
105
+ }, 100);
106
+ return () => clearTimeout(timer);
107
+ }
108
+
109
+ if (markerActions?.type === 'invoke' && markerActions.value) {
110
+ // INVOKE always auto-executes
111
+ const timer = setTimeout(() => {
112
+ sendMessage(markerActions.value!);
113
+ setIsDisabled(true);
114
+ onAction?.(markerActions.value!);
115
+ }, 100);
116
+ return () => clearTimeout(timer);
117
+ }
118
+ }, [markerActions, relayMode, onAction]);
119
+
120
+ // Pre-fill editor with suggested prompt for open questions
121
+ useEffect(() => {
122
+ if (markerActions?.type === 'open' && markerActions.responses && markerActions.responses.length > 0) {
123
+ const suggestion = markerActions.responses[0];
124
+ window.dispatchEvent(new CustomEvent('cyclist:suggest-prompt', {
125
+ detail: { prompt: suggestion }
126
+ }));
127
+ }
128
+ }, [markerActions]);
129
+
130
+ const handleButtonClick = useCallback((response: string) => {
131
+ console.log('[QuickActions] Button clicked:', response);
132
+ setIsDisabled(true);
133
+ sendMessage(response);
134
+ onAction?.(response);
135
+ }, [onAction]);
136
+
137
+ // If using direct actions prop (for accessibility testing), render those
138
+ if (directActions && directActions.length > 0) {
139
+ return (
140
+ <div className="quick-actions">
141
+ <div className="quick-actions-buttons">
142
+ {directActions.map((action) => (
143
+ <Button
144
+ variant="secondary"
145
+ size="sm"
146
+ type="button"
147
+ key={action.command}
148
+ className="quick-action-btn"
149
+ onClick={() => handleButtonClick(action.command)}
150
+ disabled={isDisabled}
151
+ aria-label={`${action.label}: ${action.command}`}
152
+ >
153
+ {action.label}
154
+ </Button>
155
+ ))}
156
+ </div>
157
+ </div>
158
+ );
159
+ }
160
+
161
+ // No marker actions to render
162
+ if (!markerActions) {
163
+ return null;
164
+ }
165
+
166
+ // Open questions - no buttons needed, editor is pre-filled via useEffect
167
+ if (markerActions.type === 'open') {
168
+ return null;
169
+ }
170
+
171
+ // Continue marker - suppress the generic Continue button entirely
172
+ // Users can always type to continue; showing a disabled button is confusing
173
+ if (markerActions.type === 'continue') {
174
+ return null;
175
+ }
176
+
177
+ // Auto-execute types don't render buttons
178
+ if (markerActions.type === 'invoke') {
179
+ return (
180
+ <div className="quick-actions">
181
+ <span className="auto-invoke-status">Invoking {markerActions.value}...</span>
182
+ </div>
183
+ );
184
+ }
185
+
186
+ // Auto-executing handoff in relay mode
187
+ if (markerActions.type === 'handoff' && relayMode) {
188
+ return (
189
+ <div className="quick-actions">
190
+ <span className="auto-invoke-status">Handing off to {markerActions.value}...</span>
191
+ </div>
192
+ );
193
+ }
194
+
195
+ return (
196
+ <div className="quick-actions">
197
+ {/* Handoff buttons */}
198
+ {markerActions.type === 'handoff' && markerActions.responses && (
199
+ <div className="quick-actions-buttons">
200
+ {markerActions.responses.map((response) => (
201
+ <Button
202
+ variant="secondary"
203
+ size="sm"
204
+ type="button"
205
+ key={response}
206
+ className="quick-action-btn"
207
+ onClick={() => handleButtonClick(response)}
208
+ disabled={isDisabled}
209
+ aria-label={`Continue with ${response}`}
210
+ >
211
+ {response}
212
+ </Button>
213
+ ))}
214
+ </div>
215
+ )}
216
+
217
+ {/* Yes/No buttons */}
218
+ {markerActions.type === 'yesno' && (
219
+ <div className="quick-actions-buttons">
220
+ <Button
221
+ variant="secondary"
222
+ size="sm"
223
+ type="button"
224
+ className="quick-action-btn"
225
+ onClick={() => handleButtonClick('Yes')}
226
+ disabled={isDisabled}
227
+ aria-label="Answer Yes"
228
+ >
229
+ Yes
230
+ </Button>
231
+ <Button
232
+ variant="secondary"
233
+ size="sm"
234
+ type="button"
235
+ className="quick-action-btn"
236
+ onClick={() => handleButtonClick('No')}
237
+ disabled={isDisabled}
238
+ aria-label="Answer No"
239
+ >
240
+ No
241
+ </Button>
242
+ </div>
243
+ )}
244
+
245
+ {/* Choice buttons */}
246
+ {markerActions.type === 'choices' && markerActions.choices && (
247
+ <div className="quick-actions-buttons">
248
+ {markerActions.choices.map((choice) => (
249
+ <Button
250
+ variant="secondary"
251
+ size="sm"
252
+ type="button"
253
+ key={choice.number}
254
+ className="quick-action-btn"
255
+ onClick={() => handleButtonClick(choice.text)}
256
+ disabled={isDisabled}
257
+ aria-label={`Choose option ${choice.number}: ${choice.text}`}
258
+ >
259
+ {choice.text}
260
+ </Button>
261
+ ))}
262
+ </div>
263
+ )}
264
+ </div>
265
+ );
266
+ }
267
+
@@ -0,0 +1,352 @@
1
+ /**
2
+ * SpanTimeline Component
3
+ *
4
+ * Visual timeline of OTEL spans showing tool execution over time.
5
+ *
6
+ * Features:
7
+ * - Horizontal timeline with spans as bars
8
+ * - Color-coded by tool type
9
+ * - Click to expand span details
10
+ * - Real-time updates via WebSocket
11
+ * - Zoom and scroll controls
12
+ */
13
+
14
+ import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
15
+ import { Button } from '@/components/ui/button';
16
+ import { Skeleton } from '@/components/ui/skeleton';
17
+ import { Badge } from '@/components/ui/badge';
18
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
19
+
20
+ // =============================================================================
21
+ // Types
22
+ // =============================================================================
23
+
24
+ interface EnrichedSpan {
25
+ spanId: string;
26
+ traceId: string;
27
+ toolName: string;
28
+ startTime: number;
29
+ endTime?: number;
30
+ durationMs: number;
31
+ status: 'running' | 'completed' | 'error';
32
+ success: boolean;
33
+ error?: string;
34
+ enrichment: {
35
+ filePath?: string;
36
+ command?: string;
37
+ pattern?: string;
38
+ summary?: string;
39
+ };
40
+ }
41
+
42
+ interface SpanTimelineProps {
43
+ /** Height of the timeline in pixels */
44
+ height?: number;
45
+ /** Whether to auto-scroll to newest spans */
46
+ autoScroll?: boolean;
47
+ }
48
+
49
+ // Tool colors for visual distinction
50
+ const TOOL_COLORS: Record<string, string> = {
51
+ Read: '#4ade80', // Green
52
+ Write: '#f87171', // Red
53
+ Edit: '#fb923c', // Orange
54
+ Bash: '#60a5fa', // Blue
55
+ Grep: '#a78bfa', // Purple
56
+ Glob: '#2dd4bf', // Teal
57
+ Task: '#e879f9', // Magenta
58
+ WebFetch: '#22d3ee', // Cyan
59
+ WebSearch: '#fbbf24', // Yellow
60
+ };
61
+
62
+ // =============================================================================
63
+ // Helper Functions
64
+ // =============================================================================
65
+
66
+ function formatTime(timestamp: number): string {
67
+ return new Date(timestamp).toLocaleTimeString();
68
+ }
69
+
70
+ function formatDuration(ms: number): string {
71
+ if (ms < 1000) return `${ms}ms`;
72
+ if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
73
+ return `${(ms / 60000).toFixed(1)}m`;
74
+ }
75
+
76
+ function getToolColor(toolName: string): string {
77
+ return TOOL_COLORS[toolName] || '#888888';
78
+ }
79
+
80
+ // =============================================================================
81
+ // Component
82
+ // =============================================================================
83
+
84
+ export function SpanTimeline({ height = 200, autoScroll = true }: SpanTimelineProps): React.ReactElement {
85
+ const [spans, setSpans] = useState<EnrichedSpan[]>([]);
86
+ const [selectedSpan, setSelectedSpan] = useState<EnrichedSpan | null>(null);
87
+ const [loading, setLoading] = useState(true);
88
+ const [zoomLevel, setZoomLevel] = useState(1); // 1 = 1px per 100ms
89
+ const timelineRef = useRef<HTMLDivElement>(null);
90
+ const containerRef = useRef<HTMLDivElement>(null);
91
+
92
+ // Fetch initial spans
93
+ useEffect(() => {
94
+ fetch('/api/spans?limit=100')
95
+ .then(res => res.ok ? res.json() : { spans: [] })
96
+ .then(data => {
97
+ setSpans(data.spans || []);
98
+ setLoading(false);
99
+ })
100
+ .catch(() => setLoading(false));
101
+ }, []);
102
+
103
+ // Subscribe to real-time updates
104
+ useEffect(() => {
105
+ const ws = new WebSocket(`ws://${window.location.host}/ws/spans`);
106
+
107
+ ws.onmessage = (event) => {
108
+ try {
109
+ const data = JSON.parse(event.data);
110
+ if (data.type === 'span' && data.span) {
111
+ setSpans(prev => {
112
+ // Update existing span or add new one
113
+ const existing = prev.findIndex(s => s.spanId === data.span.spanId);
114
+ if (existing >= 0) {
115
+ const updated = [...prev];
116
+ updated[existing] = data.span;
117
+ return updated;
118
+ }
119
+ return [...prev, data.span].slice(-100); // Keep last 100
120
+ });
121
+ }
122
+ } catch (err) {
123
+ console.error('[SpanTimeline] WebSocket parse error:', err);
124
+ }
125
+ };
126
+
127
+ return () => ws.close();
128
+ }, []);
129
+
130
+ // Auto-scroll to right (newest spans)
131
+ useEffect(() => {
132
+ if (autoScroll && containerRef.current) {
133
+ containerRef.current.scrollLeft = containerRef.current.scrollWidth;
134
+ }
135
+ }, [spans, autoScroll]);
136
+
137
+ // Calculate timeline bounds
138
+ const { minTime, maxTime, timeRange } = useMemo(() => {
139
+ if (spans.length === 0) {
140
+ const now = Date.now();
141
+ return { minTime: now - 60000, maxTime: now, timeRange: 60000 };
142
+ }
143
+
144
+ const times = spans.flatMap(s => [s.startTime, s.endTime || s.startTime + s.durationMs]);
145
+ const min = Math.min(...times);
146
+ const max = Math.max(...times);
147
+ const range = Math.max(max - min, 10000); // At least 10 seconds
148
+
149
+ return { minTime: min, maxTime: max, timeRange: range };
150
+ }, [spans]);
151
+
152
+ // Calculate timeline width based on zoom
153
+ const timelineWidth = useMemo(() => {
154
+ return Math.max(800, (timeRange / 100) * zoomLevel);
155
+ }, [timeRange, zoomLevel]);
156
+
157
+ // Handle span click
158
+ const handleSpanClick = useCallback((span: EnrichedSpan) => {
159
+ setSelectedSpan(selectedSpan?.spanId === span.spanId ? null : span);
160
+ }, [selectedSpan]);
161
+
162
+ // Zoom controls
163
+ const handleZoomIn = useCallback(() => {
164
+ setZoomLevel(prev => Math.min(prev * 1.5, 10));
165
+ }, []);
166
+
167
+ const handleZoomOut = useCallback(() => {
168
+ setZoomLevel(prev => Math.max(prev / 1.5, 0.1));
169
+ }, []);
170
+
171
+ const handleZoomReset = useCallback(() => {
172
+ setZoomLevel(1);
173
+ }, []);
174
+
175
+ if (loading) {
176
+ return (
177
+ <div className="span-timeline-loading p-2 space-y-2">
178
+ <div className="flex gap-2 items-center">
179
+ <Skeleton className="h-4 w-20" />
180
+ <div className="flex-1" />
181
+ <Skeleton className="h-6 w-6" />
182
+ <Skeleton className="h-6 w-6" />
183
+ <Skeleton className="h-6 w-6" />
184
+ </div>
185
+ <Skeleton className="h-[120px] w-full" />
186
+ </div>
187
+ );
188
+ }
189
+
190
+ return (
191
+ <TooltipProvider delayDuration={300}>
192
+ <div className="span-timeline" style={{ height }}>
193
+ {/* Toolbar */}
194
+ <div className="span-timeline-toolbar">
195
+ <span className="span-count">{spans.length} spans</span>
196
+ <div className="zoom-controls">
197
+ <Tooltip>
198
+ <TooltipTrigger asChild>
199
+ <Button variant="ghost" size="icon" onClick={handleZoomOut}>−</Button>
200
+ </TooltipTrigger>
201
+ <TooltipContent>Zoom out</TooltipContent>
202
+ </Tooltip>
203
+ <Tooltip>
204
+ <TooltipTrigger asChild>
205
+ <Button variant="ghost" size="icon" onClick={handleZoomReset}>⟳</Button>
206
+ </TooltipTrigger>
207
+ <TooltipContent>Reset zoom</TooltipContent>
208
+ </Tooltip>
209
+ <Tooltip>
210
+ <TooltipTrigger asChild>
211
+ <Button variant="ghost" size="icon" onClick={handleZoomIn}>+</Button>
212
+ </TooltipTrigger>
213
+ <TooltipContent>Zoom in</TooltipContent>
214
+ </Tooltip>
215
+ </div>
216
+ </div>
217
+
218
+ {/* Timeline container */}
219
+ <div className="span-timeline-container" ref={containerRef}>
220
+ <div
221
+ className="span-timeline-track"
222
+ ref={timelineRef}
223
+ style={{ width: timelineWidth }}
224
+ >
225
+ {/* Time axis */}
226
+ <div className="span-timeline-axis">
227
+ {Array.from({ length: Math.ceil(timeRange / 10000) + 1 }, (_, i) => {
228
+ const time = minTime + i * 10000;
229
+ const left = ((time - minTime) / timeRange) * timelineWidth;
230
+ return (
231
+ <div
232
+ key={i}
233
+ className="axis-tick"
234
+ style={{ left }}
235
+ >
236
+ <span className="tick-label">{formatTime(time)}</span>
237
+ </div>
238
+ );
239
+ })}
240
+ </div>
241
+
242
+ {/* Span bars */}
243
+ <div className="span-timeline-bars">
244
+ {spans.map((span, index) => {
245
+ const left = ((span.startTime - minTime) / timeRange) * timelineWidth;
246
+ const width = Math.max(4, (span.durationMs / timeRange) * timelineWidth);
247
+ const top = (index % 5) * 28 + 4; // Stack in 5 rows
248
+
249
+ return (
250
+ <Tooltip key={span.spanId}>
251
+ <TooltipTrigger asChild>
252
+ <div
253
+ className={`span-bar ${span.success ? '' : 'error'} ${
254
+ selectedSpan?.spanId === span.spanId ? 'selected' : ''
255
+ }`}
256
+ style={{
257
+ left,
258
+ width,
259
+ top,
260
+ backgroundColor: getToolColor(span.toolName),
261
+ }}
262
+ onClick={() => handleSpanClick(span)}
263
+ >
264
+ <span className="span-bar-label">{span.toolName}</span>
265
+ </div>
266
+ </TooltipTrigger>
267
+ <TooltipContent>{`${span.toolName}: ${formatDuration(span.durationMs)}`}</TooltipContent>
268
+ </Tooltip>
269
+ );
270
+ })}
271
+ </div>
272
+ </div>
273
+ </div>
274
+
275
+ {/* Selected span details */}
276
+ {selectedSpan && (
277
+ <div className="span-timeline-details">
278
+ <div className="detail-header">
279
+ <Badge
280
+ variant="default"
281
+ className="tool-badge"
282
+ style={{ backgroundColor: getToolColor(selectedSpan.toolName) }}
283
+ >
284
+ {selectedSpan.toolName}
285
+ </Badge>
286
+ <span className="duration">{formatDuration(selectedSpan.durationMs)}</span>
287
+ <span className={`status ${selectedSpan.success ? 'success' : 'error'}`}>
288
+ {selectedSpan.success ? '✓' : '✗'}
289
+ </span>
290
+ <Button
291
+ variant="ghost"
292
+ size="icon"
293
+ className="close-details"
294
+ onClick={() => setSelectedSpan(null)}
295
+ >
296
+ ×
297
+ </Button>
298
+ </div>
299
+ <div className="detail-body">
300
+ <div className="detail-row">
301
+ <label>Time:</label>
302
+ <span>{formatTime(selectedSpan.startTime)}</span>
303
+ </div>
304
+ {selectedSpan.enrichment.filePath && (
305
+ <div className="detail-row">
306
+ <label>File:</label>
307
+ <span className="mono">{selectedSpan.enrichment.filePath}</span>
308
+ </div>
309
+ )}
310
+ {selectedSpan.enrichment.command && (
311
+ <div className="detail-row">
312
+ <label>Command:</label>
313
+ <span className="mono">{selectedSpan.enrichment.command}</span>
314
+ </div>
315
+ )}
316
+ {selectedSpan.enrichment.pattern && (
317
+ <div className="detail-row">
318
+ <label>Pattern:</label>
319
+ <span className="mono">{selectedSpan.enrichment.pattern}</span>
320
+ </div>
321
+ )}
322
+ {selectedSpan.enrichment.summary && (
323
+ <div className="detail-row">
324
+ <label>Summary:</label>
325
+ <span>{selectedSpan.enrichment.summary}</span>
326
+ </div>
327
+ )}
328
+ {selectedSpan.error && (
329
+ <div className="detail-row error">
330
+ <label>Error:</label>
331
+ <span>{selectedSpan.error}</span>
332
+ </div>
333
+ )}
334
+ </div>
335
+ </div>
336
+ )}
337
+
338
+ {/* Legend */}
339
+ <div className="span-timeline-legend">
340
+ {Object.entries(TOOL_COLORS).slice(0, 6).map(([tool, color]) => (
341
+ <div key={tool} className="legend-item">
342
+ <span className="legend-color" style={{ backgroundColor: color }} />
343
+ <span className="legend-label">{tool}</span>
344
+ </div>
345
+ ))}
346
+ </div>
347
+ </div>
348
+ </TooltipProvider>
349
+ );
350
+ }
351
+
352
+ export default SpanTimeline;
@@ -0,0 +1,82 @@
1
+ /**
2
+ * StandalonePanel - Full-screen panel wrapper for BikeRack mode
3
+ *
4
+ * Story MSSCI-14821: StandalonePanel wrapper and ?panel=X client routing
5
+ * Epic: 101 (BikeRack Mode)
6
+ *
7
+ * Renders a single panel full-screen based on ?panel=X URL parameter.
8
+ * PANEL_REGISTRY is the single source of truth for routing (CE-2).
9
+ *
10
+ * Rules:
11
+ * - No dockview-react imports (Rule 7)
12
+ * - No BikeRack-specific props to panels (Rule 2)
13
+ * - URL-based detection only (Rule 10)
14
+ */
15
+
16
+ import React from 'react';
17
+ import {
18
+ EnhancedSprintPanel,
19
+ GitPanel,
20
+ DiffsPanel,
21
+ TodoPanel,
22
+ WorkflowPanel,
23
+ BackgroundPanel,
24
+ AuditLogPanel,
25
+ ACPanel,
26
+ DebugPanel,
27
+ BikeLanePanel,
28
+ SettingsPanel,
29
+ ProgressPanel,
30
+ } from './panels';
31
+
32
+ /**
33
+ * Registry mapping panel URL names to their components.
34
+ * Single source of truth for standalone panel routing (CE-2).
35
+ */
36
+ export const PANEL_REGISTRY: Record<string, React.ComponentType> = {
37
+ sprint: EnhancedSprintPanel,
38
+ git: GitPanel,
39
+ diffs: DiffsPanel,
40
+ todos: TodoPanel,
41
+ workflow: WorkflowPanel,
42
+ background: BackgroundPanel,
43
+ audit: AuditLogPanel,
44
+ ac: ACPanel,
45
+ debug: DebugPanel,
46
+ bikelane: BikeLanePanel,
47
+ settings: SettingsPanel,
48
+ progress: ProgressPanel,
49
+ };
50
+
51
+ /**
52
+ * Detect standalone panel mode from URL parameters (Rule 10).
53
+ */
54
+ export function getStandalonePanelName(): string | null {
55
+ const params = new URLSearchParams(window.location.search);
56
+ return params.get('panel');
57
+ }
58
+
59
+ /**
60
+ * StandalonePanel wrapper - renders a single panel full-screen.
61
+ */
62
+ export function StandalonePanel(): React.ReactElement {
63
+ const panelName = getStandalonePanelName();
64
+ const PanelComponent = panelName ? PANEL_REGISTRY[panelName] : null;
65
+
66
+ if (!PanelComponent) {
67
+ return (
68
+ <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100vh', width: '100vw', backgroundColor: 'var(--bg-primary, #1a1a2e)', color: 'var(--text-primary, #e4e4e7)' }}>
69
+ <h1>Panel not found</h1>
70
+ <p>
71
+ <a href="/" style={{ color: 'var(--accent, #818cf8)' }}>Back to BikeRack</a>
72
+ </p>
73
+ </div>
74
+ );
75
+ }
76
+
77
+ return (
78
+ <div style={{ height: '100vh', width: '100vw', overflow: 'auto', backgroundColor: 'var(--bg-primary, #1a1a2e)', color: 'var(--text-primary, #e4e4e7)' }}>
79
+ <PanelComponent />
80
+ </div>
81
+ );
82
+ }