@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,400 @@
1
+ /**
2
+ * MessageView Component
3
+ *
4
+ * Main container component for displaying conversation messages.
5
+ * Story MSSCI-12698 - MessageView Component with Streaming
6
+ *
7
+ * Features:
8
+ * - Render messages list with proper roles
9
+ * - Handle streaming content display
10
+ * - Markdown rendering with syntax highlighting
11
+ * - Tool call blocks with stacking
12
+ * - Subagent span grouping
13
+ * - Turn-based grouping with speaker labels
14
+ * - Auto-scroll behavior
15
+ */
16
+
17
+ import React, { useRef, useState, useCallback, useMemo } from 'react';
18
+ import { Badge } from '@/components/ui/badge';
19
+ import { Button } from '@/components/ui/button';
20
+ import MessageList, { MessageListHandle } from './MessageList';
21
+ import Message from './Message';
22
+ import ToolCallBlock from './ToolCallBlock';
23
+ import ToolStack from './ToolStack';
24
+ import SubagentSpan from './SubagentSpan';
25
+ import QuickActions from './QuickActions';
26
+ import { Separator } from '@/components/ui/separator';
27
+ import { isSkillContent, extractSkillLabel } from '../utils/messageFilters';
28
+ import { groupToolsIntoStacks, ToolStackData } from '../utils/toolStackGrouper';
29
+ import { usePersona } from '../hooks/usePersona';
30
+ import { useColorScheme } from '../hooks/useColorScheme';
31
+ import { useStatsStrip } from '../hooks/useStatsStrip';
32
+ import type { MessageData } from '../types/message';
33
+
34
+ // Agent colors matching CLI statusbar (from PersonaHeader)
35
+ const AGENT_COLORS: Record<string, string> = {
36
+ pm: '#a78bfa', sm: '#60a5fa', dev: '#4ade80', tea: '#2dd4bf',
37
+ reviewer: '#f87171', architect: '#fb923c', devops: '#22d3ee',
38
+ 'ux-designer': '#f0abfc', 'tech-writer': '#e5e5e5', orchestrator: '#e879f9',
39
+ ba: '#a3e635',
40
+ };
41
+
42
+ const AGENT_ABBREV: Record<string, string> = {
43
+ pm: 'PM', sm: 'SM', dev: 'DEV', tea: 'TEA', reviewer: 'REV',
44
+ architect: 'ARC', devops: 'OPS', 'ux-designer': 'UX', 'tech-writer': 'TW',
45
+ orchestrator: 'ORC', ba: 'BA',
46
+ };
47
+
48
+ interface MessageViewProps {
49
+ messages: MessageData[];
50
+ }
51
+
52
+ interface SubagentGroup {
53
+ parent_id: string;
54
+ type: string;
55
+ name: string;
56
+ messages: MessageData[];
57
+ }
58
+
59
+ interface ToolStackGroup {
60
+ isToolStack: true;
61
+ stack: ToolStackData;
62
+ }
63
+
64
+ type RenderItem = MessageData | SubagentGroup | ToolStackGroup;
65
+
66
+ interface Turn {
67
+ speaker: 'user' | 'agent' | 'system';
68
+ items: RenderItem[];
69
+ timestamp: number;
70
+ }
71
+
72
+ function formatTurnTime(timestamp: number): string {
73
+ return new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
74
+ }
75
+
76
+ /**
77
+ * Classify an item as 'user' or 'agent' for turn grouping.
78
+ * Tools, subagents, and stacks are all part of the agent's turn.
79
+ */
80
+ function speakerOf(item: RenderItem): 'user' | 'agent' | 'system' {
81
+ if ('isToolStack' in item || 'messages' in item) return 'agent';
82
+ const msg = item as MessageData;
83
+ if (msg.type === 'context_cleared') return 'system';
84
+ return (msg.type === 'user' || msg.type === 'bell_injected') ? 'user' : 'agent';
85
+ }
86
+
87
+ export default function MessageView({ messages }: MessageViewProps): React.ReactElement {
88
+ const messageListRef = useRef<MessageListHandle>(null);
89
+ const [isAtBottom, setIsAtBottom] = useState(true);
90
+ const { persona } = usePersona();
91
+ const colorScheme = useColorScheme();
92
+ const { projectInfo } = useStatsStrip();
93
+
94
+ // Persist subagent collapsed state across re-renders/remounts
95
+ const subagentCollapsedRef = useRef<Map<string, boolean>>(new Map());
96
+
97
+ const handleScrollChange = useCallback((atBottom: boolean) => {
98
+ setIsAtBottom(atBottom);
99
+ }, []);
100
+
101
+ const handleScrollToBottom = useCallback(() => {
102
+ messageListRef.current?.scrollToBottom('smooth');
103
+ }, []);
104
+
105
+ // Find the last non-streaming assistant message for QuickActions
106
+ const lastAssistantMessage = useMemo(() => {
107
+ for (let i = messages.length - 1; i >= 0; i--) {
108
+ if (messages[i].type === 'agent' && !messages[i].isStreaming) return messages[i];
109
+ }
110
+ return null;
111
+ }, [messages]);
112
+
113
+ // Single memo: messages → flat render items → turns
114
+ const { turns, toolResults, lastAgentItemIndex } = useMemo(() => {
115
+ // 1. Index tool results by ID
116
+ const results = new Map<string, MessageData>();
117
+ for (const msg of messages) {
118
+ if (msg.type === 'tool_result' && msg.tool_id) results.set(msg.tool_id, msg);
119
+ }
120
+
121
+ // 2. Filter and collect subagent groups in one pass
122
+ const filtered: MessageData[] = [];
123
+ const subagentGroups = new Map<string, SubagentGroup>();
124
+
125
+ // Track whether we've already emitted a skill label for this skill invocation.
126
+ // The first skill message gets replaced with a label; subsequent ones are dropped.
127
+ let pendingSkillLabel = false;
128
+
129
+ for (const msg of messages) {
130
+ if (msg.type === 'tool_result') continue;
131
+ if (msg.type === 'user' && isSkillContent(msg.content)) {
132
+ const label = extractSkillLabel(msg.content);
133
+ if (label && !pendingSkillLabel) {
134
+ // Replace the first skill message with a short label
135
+ pendingSkillLabel = true;
136
+ filtered.push({ ...msg, content: label });
137
+ }
138
+ // Drop all other skill body messages (pf agent start, <purpose>, etc.)
139
+ continue;
140
+ }
141
+ // Any non-skill user message resets the skill label tracker
142
+ if (msg.type === 'user') {
143
+ pendingSkillLabel = false;
144
+ }
145
+ if (msg.parent_id) {
146
+ let group = subagentGroups.get(msg.parent_id);
147
+ if (!group) {
148
+ group = { parent_id: msg.parent_id, type: msg.subagent_type || 'unknown', name: msg.subagent_name || 'unnamed', messages: [] };
149
+ subagentGroups.set(msg.parent_id, group);
150
+ }
151
+ group.messages.push(msg);
152
+ continue;
153
+ }
154
+ filtered.push(msg);
155
+ }
156
+
157
+ // 3. Build flat render list, replacing consecutive tool_use runs with stacks
158
+ const stacks = groupToolsIntoStacks(filtered);
159
+ const stackByToolId = new Map<string, ToolStackData>();
160
+ for (const stack of stacks) {
161
+ for (const tool of stack.tools) stackByToolId.set(tool.tool_id, stack);
162
+ }
163
+
164
+ const items: RenderItem[] = [];
165
+ const emittedStacks = new Set<string>();
166
+ const emittedSubagents = new Set<string>();
167
+
168
+ for (const msg of filtered) {
169
+ if (msg.type === 'tool_use' && msg.tool_id && stackByToolId.has(msg.tool_id)) {
170
+ const stack = stackByToolId.get(msg.tool_id)!;
171
+ if (!emittedStacks.has(stack.stackId)) {
172
+ emittedStacks.add(stack.stackId);
173
+ items.push({ isToolStack: true, stack });
174
+ }
175
+ } else {
176
+ items.push(msg);
177
+ }
178
+ }
179
+
180
+ // Insert subagent groups at the position of their first parent_id occurrence
181
+ // (They appear in the agent's turn, after the Task tool_use that spawned them)
182
+ // For now, just append them — they'll naturally land in the agent turn
183
+ for (const [, group] of subagentGroups) {
184
+ if (!emittedSubagents.has(group.parent_id)) {
185
+ emittedSubagents.add(group.parent_id);
186
+ items.push(group);
187
+ }
188
+ }
189
+
190
+ // 4. Find last agent message index (for throb control)
191
+ let lastAgent = -1;
192
+ for (let i = items.length - 1; i >= 0; i--) {
193
+ const item = items[i];
194
+ if (!('isToolStack' in item) && !('messages' in item) && (item as MessageData).type === 'agent') {
195
+ lastAgent = i;
196
+ break;
197
+ }
198
+ }
199
+
200
+ // 5. Group into turns
201
+ const turnList: Turn[] = [];
202
+ for (const item of items) {
203
+ const speaker = speakerOf(item);
204
+ const last = turnList[turnList.length - 1];
205
+ if (last && last.speaker === speaker) {
206
+ last.items.push(item);
207
+ } else {
208
+ turnList.push({
209
+ speaker,
210
+ items: [item],
211
+ timestamp: ('timestamp' in item) ? (item as MessageData).timestamp : Date.now(),
212
+ });
213
+ }
214
+ }
215
+
216
+ return { turns: turnList, toolResults: results, lastAgentItemIndex: lastAgent };
217
+ }, [messages]);
218
+
219
+ const renderItem = (item: RenderItem, globalIndex: number, isFirstInTurn: boolean) => {
220
+ if ('isToolStack' in item) {
221
+ return (
222
+ <ToolStack
223
+ key={`stack-${item.stack.stackId}`}
224
+ stack={item.stack}
225
+ toolResults={toolResults as Map<string, { type: 'tool_result'; tool_id: string; content: string; timestamp: number }>}
226
+ />
227
+ );
228
+ }
229
+
230
+ if ('messages' in item && 'parent_id' in item) {
231
+ const group = item as SubagentGroup;
232
+ const collapsed = subagentCollapsedRef.current.get(group.parent_id) ?? true;
233
+ return (
234
+ <SubagentSpan
235
+ key={`subagent-${group.parent_id}`}
236
+ type={group.type}
237
+ name={group.name}
238
+ messages={group.messages as any}
239
+ defaultCollapsed={collapsed}
240
+ onCollapseChange={(c) => subagentCollapsedRef.current.set(group.parent_id, c)}
241
+ />
242
+ );
243
+ }
244
+
245
+ const msg = item as MessageData;
246
+
247
+ if (msg.type === 'tool_use' && msg.tool_name && msg.tool_id) {
248
+ // AskUserQuestion is handled by the Reflector system (CYCLIST markers → QuickActions)
249
+ if (msg.tool_name === 'AskUserQuestion') return null;
250
+ const result = toolResults.get(msg.tool_id);
251
+ return (
252
+ <ToolCallBlock
253
+ key={`tool-${msg.tool_id}`}
254
+ toolUse={{
255
+ type: 'tool_use',
256
+ tool_name: msg.tool_name,
257
+ tool_id: msg.tool_id,
258
+ input: msg.input || {},
259
+ timestamp: msg.timestamp,
260
+ }}
261
+ result={result ? {
262
+ type: 'tool_result',
263
+ tool_id: result.tool_id!,
264
+ content: result.content || '',
265
+ timestamp: result.timestamp,
266
+ is_error: result.is_error,
267
+ durationMs: result.durationMs,
268
+ } : undefined}
269
+ />
270
+ );
271
+ }
272
+
273
+ return (
274
+ <Message
275
+ key={`msg-${globalIndex}-${msg.timestamp}`}
276
+ message={msg}
277
+ isLastAgentMessage={globalIndex === lastAgentItemIndex}
278
+ isFirstInTurn={isFirstInTurn}
279
+ />
280
+ );
281
+ };
282
+
283
+ // Empty state
284
+ if (messages.length === 0) {
285
+ return (
286
+ <div data-testid="message-view" className="message-view">
287
+ <div className="message-view-empty">
288
+ <div>
289
+ <img
290
+ src={colorScheme === 'dark' ? '/images/cyclist-dark.png' : '/images/cyclist-light.png'}
291
+ alt="Cyclist"
292
+ style={{ height: '2.5rem', opacity: 0.6, display: 'block', margin: '0 auto 0.5rem' }}
293
+ />
294
+ <div>Type <code style={{
295
+ background: 'var(--bg-tertiary, #0f0f1a)',
296
+ padding: '2px 6px',
297
+ borderRadius: '3px',
298
+ fontFamily: 'var(--font-mono, monospace)'
299
+ }}>/sm</code> to start</div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+ );
304
+ }
305
+
306
+ // Track a running global index across turns for lastAgentItemIndex matching
307
+ let globalIdx = 0;
308
+
309
+ return (
310
+ <div data-testid="message-view" className="message-view" role="log" aria-live="polite">
311
+ <MessageList
312
+ ref={messageListRef}
313
+ onScrollChange={handleScrollChange}
314
+ autoScroll={isAtBottom}
315
+ >
316
+ {turns.map((turn, turnIndex) => {
317
+ // System turns (context_cleared) render as a divider bar
318
+ if (turn.speaker === 'system') {
319
+ // Still increment globalIdx for system items
320
+ turn.items.forEach(() => globalIdx++);
321
+ return (
322
+ <div key={`turn-${turnIndex}`} className="turn-group turn-system">
323
+ <div className="context-cleared-bar">
324
+ <Separator className="context-cleared-line" />
325
+ <span className="context-cleared-label">
326
+ Context cleared
327
+ </span>
328
+ <span className="context-cleared-time">
329
+ {formatTurnTime(turn.timestamp)}
330
+ </span>
331
+ <Separator className="context-cleared-line" />
332
+ </div>
333
+ </div>
334
+ );
335
+ }
336
+
337
+ // Track which items in this turn are "first message" (non-tool, non-stack)
338
+ let seenMessage = false;
339
+
340
+ const agentName = persona?.character || 'Agent';
341
+ const role = persona?.role || null;
342
+ const roleAbbrev = role ? (AGENT_ABBREV[role] || role) : null;
343
+ const roleColor = role ? (AGENT_COLORS[role] || '#e879f9') : undefined;
344
+ const userName = projectInfo?.githubUsername || 'You';
345
+
346
+ return (
347
+ <div key={`turn-${turnIndex}`} className={`turn-group turn-${turn.speaker}`}>
348
+ <div className="turn-label">
349
+ <span className="turn-speaker">
350
+ {turn.speaker === 'user' ? userName : agentName}
351
+ </span>
352
+ {turn.speaker === 'agent' && roleAbbrev && (
353
+ <Badge
354
+ variant="default"
355
+ className="turn-role-badge"
356
+ style={{ backgroundColor: roleColor }}
357
+ >
358
+ {roleAbbrev}
359
+ </Badge>
360
+ )}
361
+ <span className="turn-timestamp">
362
+ {formatTurnTime(turn.timestamp)}
363
+ </span>
364
+ </div>
365
+ {turn.items.map((item) => {
366
+ const idx = globalIdx++;
367
+ const isMessage = !('isToolStack' in item) && !('messages' in item) && (item as MessageData).type !== 'tool_use';
368
+ const isFirst = isMessage && !seenMessage;
369
+ if (isMessage) seenMessage = true;
370
+ return renderItem(item, idx, isFirst);
371
+ })}
372
+ </div>
373
+ );
374
+ })}
375
+ </MessageList>
376
+
377
+ {lastAssistantMessage && (
378
+ <QuickActions message={lastAssistantMessage} />
379
+ )}
380
+
381
+ <div
382
+ data-testid="auto-scroll-indicator"
383
+ data-active={isAtBottom.toString()}
384
+ className="auto-scroll-indicator"
385
+ style={{ display: 'none' }}
386
+ />
387
+
388
+ <Button
389
+ variant="ghost"
390
+ size="icon"
391
+ data-testid="scroll-to-bottom-button"
392
+ className="scroll-to-bottom-button"
393
+ onClick={handleScrollToBottom}
394
+ style={{ visibility: isAtBottom ? 'hidden' : 'visible' }}
395
+ >
396
+
397
+ </Button>
398
+ </div>
399
+ );
400
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * ModeSwitch Component Styles
3
+ *
4
+ * Story MSSCI-12773 - ModeSwitch Component
5
+ *
6
+ * Color scheme:
7
+ * - Plan: Teal (#14b8a6)
8
+ * - Manual: Gray (#6b7280)
9
+ * - Accept: Purple/Lavender (#a78bfa)
10
+ *
11
+ * Note: The mode-option buttons are rendered as shadcn ToggleGroupItems which
12
+ * apply Tailwind utility classes from toggleVariants. We override those defaults
13
+ * here to preserve the custom ModeSwitch appearance and sliding highlight effect.
14
+ */
15
+
16
+ .mode-switch {
17
+ display: flex;
18
+ position: relative;
19
+ background: var(--bg-tertiary, rgba(0, 0, 0, 0.3));
20
+ border-radius: 6px;
21
+ padding: 2px;
22
+ gap: 0;
23
+ width: fit-content;
24
+ border: 1px solid rgba(255, 255, 255, 0.1);
25
+ }
26
+
27
+ .mode-switch--disabled {
28
+ opacity: 0.5;
29
+ pointer-events: none;
30
+ }
31
+
32
+ /* Sliding highlight element */
33
+ .mode-switch__highlight {
34
+ position: absolute;
35
+ top: 2px;
36
+ left: 2px;
37
+ width: calc(33.333% - 1.33px);
38
+ height: calc(100% - 4px);
39
+ border-radius: 4px;
40
+ transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
41
+ background-color 0.2s ease;
42
+ pointer-events: none;
43
+ z-index: 0;
44
+ }
45
+
46
+ /* Highlight colors based on mode */
47
+ .mode-switch__highlight[data-mode="plan"] {
48
+ background: var(--color-plan, #14b8a6);
49
+ box-shadow: 0 0 8px rgba(20, 184, 166, 0.4);
50
+ }
51
+
52
+ .mode-switch__highlight[data-mode="manual"] {
53
+ background: var(--color-manual, #6b7280);
54
+ box-shadow: 0 0 8px rgba(107, 114, 128, 0.3);
55
+ }
56
+
57
+ .mode-switch__highlight[data-mode="accept"] {
58
+ background: var(--color-accept, #a78bfa);
59
+ box-shadow: 0 0 8px rgba(167, 139, 250, 0.4);
60
+ }
61
+
62
+ /*
63
+ * Mode option buttons (ToggleGroupItem overrides)
64
+ *
65
+ * The shadcn ToggleGroupItem applies toggleVariants Tailwind classes including
66
+ * bg-transparent, hover:bg-muted, data-[state=on]:bg-accent, rounded-md, h-9, etc.
67
+ * We reset those here so the custom sliding highlight + color scheme takes effect.
68
+ */
69
+ .mode-option {
70
+ position: relative;
71
+ z-index: 1;
72
+ flex: 1;
73
+ padding: 6px 12px !important;
74
+ height: auto !important;
75
+ min-height: unset !important;
76
+ border: none !important;
77
+ border-radius: 0 !important;
78
+ background: transparent !important;
79
+ color: var(--text-secondary, rgba(255, 255, 255, 0.6));
80
+ font-size: 12px;
81
+ font-family: var(--font-ui, -apple-system, BlinkMacSystemFont, sans-serif);
82
+ font-weight: 500;
83
+ cursor: pointer;
84
+ transition: color 0.15s ease;
85
+ white-space: nowrap;
86
+ min-width: 60px;
87
+ text-align: center;
88
+ /* Reset shadcn shadow */
89
+ box-shadow: none !important;
90
+ }
91
+
92
+ /* Subtle color hints for inactive options */
93
+ .mode-option[data-mode="plan"]:not(.active) {
94
+ color: rgba(20, 184, 166, 0.7);
95
+ }
96
+
97
+ .mode-option[data-mode="manual"]:not(.active) {
98
+ color: rgba(156, 163, 175, 0.8);
99
+ }
100
+
101
+ .mode-option[data-mode="accept"]:not(.active) {
102
+ color: rgba(167, 139, 250, 0.7);
103
+ }
104
+
105
+ .mode-option:hover:not(.active):not(:disabled) {
106
+ background: transparent !important;
107
+ filter: brightness(1.3);
108
+ }
109
+
110
+ .mode-option.active {
111
+ color: var(--text-on-highlight, #ffffff);
112
+ background: transparent !important;
113
+ }
114
+
115
+ .mode-option:focus {
116
+ outline: none;
117
+ }
118
+
119
+ .mode-option:focus-visible {
120
+ outline: 2px solid var(--color-focus, #60a5fa) !important;
121
+ outline-offset: 2px;
122
+ border-radius: 4px !important;
123
+ /* Override shadcn ring styles */
124
+ box-shadow: none !important;
125
+ }
126
+
127
+ .mode-option:disabled {
128
+ cursor: not-allowed;
129
+ }
130
+
131
+ /*
132
+ * Text color adjustments for active states (contrast).
133
+ *
134
+ * The ToggleGroupItems are now nested inside a ToggleGroup wrapper (not direct
135
+ * siblings of the highlight div), so we use parent-scoped descendant selectors.
136
+ */
137
+ .mode-switch:has(.mode-switch__highlight[data-mode="plan"]) .mode-option[data-mode="plan"].active {
138
+ color: #ffffff;
139
+ }
140
+
141
+ .mode-switch:has(.mode-switch__highlight[data-mode="manual"]) .mode-option[data-mode="manual"].active {
142
+ color: #ffffff;
143
+ }
144
+
145
+ .mode-switch:has(.mode-switch__highlight[data-mode="accept"]) .mode-option[data-mode="accept"].active {
146
+ color: #1a1a2e;
147
+ }
148
+
149
+ /* Editor-specific positioning */
150
+ .editor-mode-switch {
151
+ margin-bottom: 8px;
152
+ }
153
+
154
+ /* Screen reader only text */
155
+ .visually-hidden {
156
+ position: absolute;
157
+ width: 1px;
158
+ height: 1px;
159
+ padding: 0;
160
+ margin: -1px;
161
+ overflow: hidden;
162
+ clip: rect(0, 0, 0, 0);
163
+ white-space: nowrap;
164
+ border: 0;
165
+ }