@pennyfarthing/core 11.3.8 → 11.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -1
  3. package/packages/core/dist/public/css/react.css +1 -1
  4. package/packages/core/dist/public/js/react/react.js +24 -24
  5. package/packages/core/src/public/App.tsx +356 -0
  6. package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
  7. package/packages/core/src/public/components/AgentPopup.tsx +308 -0
  8. package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
  9. package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
  10. package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
  11. package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
  12. package/packages/core/src/public/components/CommandPalette.tsx +554 -0
  13. package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
  14. package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
  15. package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
  16. package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
  17. package/packages/core/src/public/components/ControlBar.tsx +636 -0
  18. package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
  19. package/packages/core/src/public/components/DiffViewer.tsx +585 -0
  20. package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
  21. package/packages/core/src/public/components/Editor.tsx +630 -0
  22. package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
  23. package/packages/core/src/public/components/FileTree.tsx +379 -0
  24. package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
  25. package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
  26. package/packages/core/src/public/components/FullFileTree.tsx +237 -0
  27. package/packages/core/src/public/components/HealthGauge.tsx +181 -0
  28. package/packages/core/src/public/components/Message.tsx +225 -0
  29. package/packages/core/src/public/components/MessageList.tsx +98 -0
  30. package/packages/core/src/public/components/MessageView.tsx +400 -0
  31. package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
  32. package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
  33. package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
  34. package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
  35. package/packages/core/src/public/components/QuickActions.tsx +267 -0
  36. package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
  37. package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
  38. package/packages/core/src/public/components/StatsStrip.tsx +162 -0
  39. package/packages/core/src/public/components/StreamingContent.tsx +77 -0
  40. package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
  41. package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
  42. package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
  43. package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
  44. package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
  45. package/packages/core/src/public/components/ToolStack.tsx +209 -0
  46. package/packages/core/src/public/components/ToolStatus.tsx +57 -0
  47. package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
  48. package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
  49. package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
  50. package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
  51. package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
  52. package/packages/core/src/public/components/panel-registry.ts +13 -0
  53. package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
  54. package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
  55. package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
  56. package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
  57. package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
  58. package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
  59. package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
  60. package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
  61. package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
  62. package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
  63. package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
  64. package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
  65. package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
  66. package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
  67. package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
  68. package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
  69. package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
  70. package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
  71. package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
  72. package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
  73. package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
  74. package/packages/core/src/public/components/panels/index.ts +24 -0
  75. package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
  76. package/packages/core/src/public/components/ui/badge.tsx +36 -0
  77. package/packages/core/src/public/components/ui/button.tsx +57 -0
  78. package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
  79. package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
  80. package/packages/core/src/public/components/ui/command.tsx +151 -0
  81. package/packages/core/src/public/components/ui/dialog.tsx +120 -0
  82. package/packages/core/src/public/components/ui/popover.tsx +31 -0
  83. package/packages/core/src/public/components/ui/progress.tsx +28 -0
  84. package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
  85. package/packages/core/src/public/components/ui/select.tsx +157 -0
  86. package/packages/core/src/public/components/ui/separator.tsx +29 -0
  87. package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
  88. package/packages/core/src/public/components/ui/switch.tsx +27 -0
  89. package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
  90. package/packages/core/src/public/components/ui/toggle.tsx +43 -0
  91. package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
  92. package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
  93. package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
  94. package/packages/core/src/public/css/theme-browser.css +550 -0
  95. package/packages/core/src/public/css/theme-system.css +630 -0
  96. package/packages/core/src/public/hooks/index.ts +49 -0
  97. package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
  98. package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
  99. package/packages/core/src/public/hooks/useClaude.ts +234 -0
  100. package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
  101. package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
  102. package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
  103. package/packages/core/src/public/hooks/useComplexity.ts +80 -0
  104. package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
  105. package/packages/core/src/public/hooks/useDependencies.ts +82 -0
  106. package/packages/core/src/public/hooks/useDiffs.ts +143 -0
  107. package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
  108. package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
  109. package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
  110. package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
  111. package/packages/core/src/public/hooks/useHotspots.ts +123 -0
  112. package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
  113. package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
  114. package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
  115. package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
  116. package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
  117. package/packages/core/src/public/hooks/usePersona.ts +112 -0
  118. package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
  119. package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
  120. package/packages/core/src/public/hooks/useSprint.ts +157 -0
  121. package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
  122. package/packages/core/src/public/hooks/useStory.ts +135 -0
  123. package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
  124. package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
  125. package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
  126. package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
  127. package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
  128. package/packages/core/src/public/hooks/useTodos.ts +93 -0
  129. package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
  130. package/packages/core/src/public/images/cyclist-dark.png +0 -0
  131. package/packages/core/src/public/images/cyclist-light.png +0 -0
  132. package/packages/core/src/public/index.html +14 -0
  133. package/packages/core/src/public/index.tsx +10 -0
  134. package/packages/core/src/public/lib/utils.ts +6 -0
  135. package/packages/core/src/public/styles/dockview-theme.css +376 -0
  136. package/packages/core/src/public/styles/tailwind.css +4454 -0
  137. package/packages/core/src/public/types/message.ts +51 -0
  138. package/packages/core/src/public/utils/avatar-service.ts +73 -0
  139. package/packages/core/src/public/utils/color-presets.ts +940 -0
  140. package/packages/core/src/public/utils/font-presets.ts +362 -0
  141. package/packages/core/src/public/utils/formatDuration.ts +14 -0
  142. package/packages/core/src/public/utils/markdown.ts +249 -0
  143. package/packages/core/src/public/utils/messageFilters.ts +128 -0
  144. package/packages/core/src/public/utils/slash-commands.ts +341 -0
  145. package/packages/core/src/public/utils/subagent-display.ts +146 -0
  146. package/packages/core/src/public/utils/syntax.ts +219 -0
  147. package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
  148. package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
  149. package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
  150. package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
  151. package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
  152. package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
  153. package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
  154. package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
  155. package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
  156. package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
  157. package/pennyfarthing-dist/pf/bc/cli.py +0 -1
  158. package/pennyfarthing-dist/pf/bc/focus.py +0 -1
  159. package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
  160. package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
  161. package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
  162. package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
  163. package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
  164. package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
  165. package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
  166. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
  167. package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
  168. package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
  169. package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
  170. package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
  171. package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
  172. package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
  173. package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
  174. package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
  175. package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
  176. package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
  177. package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
  178. package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
  179. package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
  180. package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
  181. package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
  182. package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
  183. package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
  184. package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
  185. package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
  186. package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
  187. package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
  188. package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
  189. package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
  190. package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
  191. package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
  192. package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
  193. package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
  194. package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
  195. package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
  196. package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
  197. package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
  198. package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
  199. package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
  200. package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
  201. package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
  202. package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
  203. package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
  204. package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
  205. package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
  206. package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
  207. package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
  208. package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
  209. package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
  210. package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
  211. package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
  212. package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
  213. package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
  214. package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
  215. package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
  216. package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
  217. package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
  218. package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
  219. package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
  220. package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
  221. package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
  222. package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
  223. package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
  224. package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
  225. package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
  226. package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
  227. package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
  228. package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
  229. package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
  230. package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
  231. package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
  232. package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
  233. package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
  234. package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
  235. package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
  236. package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
  237. package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
  238. package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
  239. package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
  240. package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
  241. package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
  242. package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
  243. package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
  244. package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
  245. package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
  246. package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
  247. package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
  248. package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
  249. package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
  250. package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
  251. package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
  252. package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
  253. package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
  254. package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
  255. package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
  256. package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
  257. package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
  258. package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
  259. package/pennyfarthing-dist/pf/settings/settings.py +44 -8
  260. package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
  261. package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
  262. package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
  263. package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
  264. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
  265. package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
  266. package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
  267. package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
  268. package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
  269. package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
  270. package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
  271. package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
  272. package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
  273. package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
  274. package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
  275. package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
  276. package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
  277. package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
  278. package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
  279. package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
  280. package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
  281. package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
  282. package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
  283. package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
  284. package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
  285. package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
  286. package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
  287. package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
  288. package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
  289. package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
  290. package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
  291. package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
  292. package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
  293. package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
  294. package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
  295. package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
  296. package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
  297. package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
  298. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
  299. package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
  300. package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
  301. package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
  302. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
  303. package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
  304. package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
@@ -0,0 +1,372 @@
1
+ /**
2
+ * ModeSwitch Component
3
+ *
4
+ * A 3-way toggle for Plan/Manual/Accept permission modes with sliding highlight animation.
5
+ * Story MSSCI-12773 - ModeSwitch Component
6
+ *
7
+ * Features:
8
+ * - Three mode options: Plan, Manual, Accept
9
+ * - Sliding highlight animation on mode change
10
+ * - Keyboard accessible (arrow keys via Radix ToggleGroup, Cmd+1/2/3 shortcuts)
11
+ * - ARIA radiogroup semantics provided by Radix ToggleGroup
12
+ * - Color-coded modes: Plan (teal), Manual (gray), Accept (purple)
13
+ * - Tooltips with mode descriptions via Radix Tooltip
14
+ *
15
+ * Refactored to use shadcn ToggleGroup primitive (replaces custom roving tabindex,
16
+ * manual keyboard handling, and manual ARIA management).
17
+ */
18
+
19
+ import React, { useState, useRef, useCallback, useEffect } from 'react';
20
+
21
+ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
22
+ import {
23
+ Tooltip,
24
+ TooltipTrigger,
25
+ TooltipContent,
26
+ TooltipProvider,
27
+ } from '@/components/ui/tooltip';
28
+ import { cn } from '@/lib/utils';
29
+
30
+ import './ModeSwitch.css';
31
+
32
+ // =============================================================================
33
+ // Types and Constants
34
+ // =============================================================================
35
+
36
+ export type Mode = 'plan' | 'manual' | 'accept';
37
+
38
+ export const MODES: Mode[] = ['plan', 'manual', 'accept'];
39
+
40
+ export const MODE_LABELS: Record<Mode, string> = {
41
+ plan: 'Plan',
42
+ manual: 'Manual',
43
+ accept: 'Accept',
44
+ };
45
+
46
+ export const MODE_DESCRIPTIONS: Record<Mode, string> = {
47
+ plan: 'Read-only exploration mode',
48
+ manual: 'Ask for permission before actions',
49
+ accept: 'Auto-accept file edits',
50
+ };
51
+
52
+ // =============================================================================
53
+ // Mode Mapping (UI <-> Claude CLI)
54
+ // =============================================================================
55
+
56
+ /** Map UI mode names to Claude CLI mode names */
57
+ export const MODE_TO_CLAUDE: Record<Mode, string> = {
58
+ plan: 'plan',
59
+ manual: 'default',
60
+ accept: 'acceptEdits',
61
+ };
62
+
63
+ /** Map Claude CLI mode names back to UI mode names */
64
+ export const CLAUDE_TO_MODE: Record<string, Mode> = {
65
+ plan: 'plan',
66
+ default: 'manual',
67
+ acceptEdits: 'accept',
68
+ };
69
+
70
+ // =============================================================================
71
+ // Keyboard Shortcuts (AC6)
72
+ // =============================================================================
73
+
74
+ /** Cmd+1/2/3 shortcuts to switch modes */
75
+ export const MODE_SHORTCUTS: Record<string, Mode> = {
76
+ '1': 'plan',
77
+ '2': 'manual',
78
+ '3': 'accept',
79
+ };
80
+
81
+ // =============================================================================
82
+ // Tooltip Support (AC7)
83
+ // =============================================================================
84
+
85
+ /** Flag indicating tooltips are enabled via Radix Tooltip */
86
+ export const TOOLTIP_ENABLED = true;
87
+
88
+ export interface ModeSwitchProps {
89
+ /** Current active mode */
90
+ mode?: Mode;
91
+ /** Default mode if uncontrolled */
92
+ defaultMode?: Mode;
93
+ /** Callback when mode changes */
94
+ onModeChange?: (mode: Mode) => void;
95
+ /** Additional CSS class */
96
+ className?: string;
97
+ /** Disable the component */
98
+ disabled?: boolean;
99
+ }
100
+
101
+ // =============================================================================
102
+ // Utility Functions
103
+ // =============================================================================
104
+
105
+ /**
106
+ * Get the default mode (manual)
107
+ */
108
+ export function getDefaultMode(): Mode {
109
+ return 'manual';
110
+ }
111
+
112
+ // =============================================================================
113
+ // useModeSwitch Hook
114
+ // =============================================================================
115
+
116
+ interface UseModesSwitchResult {
117
+ mode: Mode;
118
+ setMode: (mode: Mode) => void;
119
+ nextMode: () => void;
120
+ prevMode: () => void;
121
+ }
122
+
123
+ export function useModeSwitch(initialMode: Mode = 'manual'): UseModesSwitchResult {
124
+ const [mode, setModeState] = useState<Mode>(initialMode);
125
+
126
+ const setMode = useCallback((newMode: Mode) => {
127
+ if (MODES.includes(newMode)) {
128
+ setModeState(newMode);
129
+ }
130
+ }, []);
131
+
132
+ const nextMode = useCallback(() => {
133
+ const currentIndex = MODES.indexOf(mode);
134
+ const nextIndex = (currentIndex + 1) % MODES.length;
135
+ setModeState(MODES[nextIndex]);
136
+ }, [mode]);
137
+
138
+ const prevMode = useCallback(() => {
139
+ const currentIndex = MODES.indexOf(mode);
140
+ const prevIndex = (currentIndex - 1 + MODES.length) % MODES.length;
141
+ setModeState(MODES[prevIndex]);
142
+ }, [mode]);
143
+
144
+ return { mode, setMode, nextMode, prevMode };
145
+ }
146
+
147
+ // =============================================================================
148
+ // useModeSwitchShortcuts Hook (AC6)
149
+ // =============================================================================
150
+
151
+ /**
152
+ * Hook to register Cmd+1/2/3 keyboard shortcuts for mode switching
153
+ */
154
+ export function useModeSwitchShortcuts(onModeChange: (mode: Mode) => void): void {
155
+ useEffect(() => {
156
+ const handler = (e: globalThis.KeyboardEvent) => {
157
+ // Check for Cmd (Mac) or Ctrl (Windows/Linux)
158
+ if ((e.metaKey || e.ctrlKey) && MODE_SHORTCUTS[e.key]) {
159
+ e.preventDefault();
160
+ onModeChange(MODE_SHORTCUTS[e.key]);
161
+ }
162
+ };
163
+
164
+ document.addEventListener('keydown', handler);
165
+ return () => document.removeEventListener('keydown', handler);
166
+ }, [onModeChange]);
167
+ }
168
+
169
+ // =============================================================================
170
+ // useModeSync Hook (Backend Integration via WebSocket)
171
+ // =============================================================================
172
+
173
+ interface UseModeSyncResult {
174
+ mode: Mode;
175
+ setMode: (mode: Mode) => void;
176
+ isLoading: boolean;
177
+ }
178
+
179
+ /**
180
+ * Hook to sync UI mode state with Claude backend via WebSocket
181
+ * Migrated from IPC to WebSocket for unified communication
182
+ */
183
+ export function useModeSync(): UseModeSyncResult {
184
+ const [mode, setModeState] = useState<Mode>('manual');
185
+ const [isLoading, setIsLoading] = useState(true);
186
+ const wsRef = useRef<WebSocket | null>(null);
187
+
188
+ // Connect to WebSocket and sync mode
189
+ useEffect(() => {
190
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
191
+ const wsUrl = `${protocol}//${window.location.host}/ws/claude`;
192
+ const ws = new WebSocket(wsUrl);
193
+ wsRef.current = ws;
194
+
195
+ ws.onopen = () => {
196
+ // Request current mode from server
197
+ ws.send(JSON.stringify({ type: 'getMode' }));
198
+ };
199
+
200
+ ws.onmessage = (event) => {
201
+ try {
202
+ const data = JSON.parse(event.data);
203
+ if (data.type === 'mode' && data.mode) {
204
+ const uiMode = CLAUDE_TO_MODE[data.mode] || 'manual';
205
+ setModeState(uiMode);
206
+ setIsLoading(false);
207
+ console.log('[ModeSwitch] Mode synced:', data.mode, '->', uiMode);
208
+ }
209
+ } catch (err) {
210
+ console.error('[ModeSwitch] Failed to parse mode response:', err);
211
+ }
212
+ };
213
+
214
+ ws.onerror = () => {
215
+ setIsLoading(false);
216
+ };
217
+
218
+ // Timeout fallback if server doesn't respond
219
+ const timeout = setTimeout(() => {
220
+ if (isLoading) {
221
+ setIsLoading(false);
222
+ }
223
+ }, 2000);
224
+
225
+ return () => {
226
+ clearTimeout(timeout);
227
+ if (ws.readyState === WebSocket.OPEN) {
228
+ ws.close();
229
+ }
230
+ wsRef.current = null;
231
+ };
232
+ }, []);
233
+
234
+ // Set mode on Claude backend via WebSocket
235
+ const setMode = useCallback((newMode: Mode) => {
236
+ const claudeMode = MODE_TO_CLAUDE[newMode];
237
+
238
+ if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
239
+ wsRef.current.send(JSON.stringify({ type: 'setMode', mode: claudeMode }));
240
+ setModeState(newMode);
241
+ console.log('[ModeSwitch] Mode set to:', newMode, '->', claudeMode);
242
+ } else {
243
+ // WebSocket not connected, just update local state
244
+ setModeState(newMode);
245
+ console.warn('[ModeSwitch] WebSocket not connected, mode set locally only');
246
+ }
247
+ }, []);
248
+
249
+ return { mode, setMode, isLoading };
250
+ }
251
+
252
+ // =============================================================================
253
+ // ModeSwitch Component
254
+ // =============================================================================
255
+
256
+ export function ModeSwitch({
257
+ mode: controlledMode,
258
+ defaultMode = 'manual',
259
+ onModeChange,
260
+ className = '',
261
+ disabled = false,
262
+ }: ModeSwitchProps): React.ReactElement {
263
+ // Support both controlled and uncontrolled usage
264
+ const [internalMode, setInternalMode] = useState<Mode>(defaultMode);
265
+ const [reducedMotion, setReducedMotion] = useState(false);
266
+ const mode = controlledMode ?? internalMode;
267
+
268
+ // Check for reduced motion preference
269
+ useEffect(() => {
270
+ const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
271
+ setReducedMotion(mediaQuery.matches);
272
+
273
+ const listener = (e: MediaQueryListEvent) => {
274
+ setReducedMotion(e.matches);
275
+ };
276
+
277
+ mediaQuery.addEventListener('change', listener);
278
+ return () => mediaQuery.removeEventListener('change', listener);
279
+ }, []);
280
+
281
+ // Calculate highlight position based on current mode
282
+ const modeIndex = MODES.indexOf(mode);
283
+
284
+ const handleModeChange = useCallback((newMode: Mode) => {
285
+ if (disabled) return;
286
+ if (!controlledMode) {
287
+ setInternalMode(newMode);
288
+ }
289
+ onModeChange?.(newMode);
290
+ }, [controlledMode, disabled, onModeChange]);
291
+
292
+ /**
293
+ * Radix ToggleGroup fires onValueChange with the new value string.
294
+ * When the user clicks the already-selected item, value is '' (deselect);
295
+ * we ignore that to enforce "always one selected".
296
+ */
297
+ const handleValueChange = useCallback(
298
+ (value: string) => {
299
+ if (!value) return; // prevent deselect
300
+ handleModeChange(value as Mode);
301
+ },
302
+ [handleModeChange],
303
+ );
304
+
305
+ return (
306
+ <div
307
+ className={cn(
308
+ 'mode-switch',
309
+ disabled && 'mode-switch--disabled',
310
+ reducedMotion && 'reduced-motion',
311
+ className,
312
+ )}
313
+ data-testid="mode-switch"
314
+ style={reducedMotion ? { transition: 'none' } : undefined}
315
+ >
316
+ {/* Sliding highlight */}
317
+ <div
318
+ className="mode-switch__highlight"
319
+ data-testid="mode-highlight"
320
+ style={{
321
+ transform: `translateX(${modeIndex * 100}%)`,
322
+ transition: reducedMotion ? 'none' : undefined,
323
+ }}
324
+ data-mode={mode}
325
+ />
326
+
327
+ {/* Mode options via shadcn ToggleGroup */}
328
+ <TooltipProvider delayDuration={400}>
329
+ <ToggleGroup
330
+ type="single"
331
+ value={mode}
332
+ onValueChange={handleValueChange}
333
+ disabled={disabled}
334
+ className="relative z-[1] gap-0"
335
+ aria-label="Permission mode"
336
+ >
337
+ {MODES.map((m) => {
338
+ const isActive = mode === m;
339
+ return (
340
+ <Tooltip key={m}>
341
+ <TooltipTrigger asChild>
342
+ <ToggleGroupItem
343
+ value={m}
344
+ data-mode={m}
345
+ data-testid={`mode-${m}`}
346
+ aria-label={`${MODE_LABELS[m]} mode: ${MODE_DESCRIPTIONS[m]}`}
347
+ className={cn(
348
+ 'mode-option',
349
+ isActive && 'active',
350
+ )}
351
+ >
352
+ {MODE_LABELS[m]}
353
+ </ToggleGroupItem>
354
+ </TooltipTrigger>
355
+ <TooltipContent side="bottom">
356
+ {MODE_DESCRIPTIONS[m]}
357
+ </TooltipContent>
358
+ </Tooltip>
359
+ );
360
+ })}
361
+ </ToggleGroup>
362
+ </TooltipProvider>
363
+
364
+ {/* Screen reader announcement */}
365
+ <div className="visually-hidden" role="status" aria-live="polite">
366
+ {`${MODE_LABELS[mode]} mode selected`}
367
+ </div>
368
+ </div>
369
+ );
370
+ }
371
+
372
+ export default ModeSwitch;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * PersonaHeader Component
3
+ *
4
+ * Displays the current agent persona with portrait, character name, theme, role.
5
+ * Story MSSCI-12700 - PersonaHeader Component
6
+ * Story 72-3 - Catchphrase and Badge Improvements
7
+ *
8
+ * Features:
9
+ * - Portrait image display with adjacent role badge
10
+ * - Character name display with prominent styling
11
+ * - Random catchphrase from theme
12
+ * - Color-coded role badge matching CLI statusbar
13
+ * - Real-time updates via IPC subscriptions
14
+ * - Graceful handling of missing data
15
+ * - Accessible with ARIA labels
16
+ */
17
+
18
+ import React, { useState, useCallback, useRef, useEffect } from 'react';
19
+ import { Badge } from '@/components/ui/badge';
20
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
21
+ import { usePersona } from '../hooks/usePersona';
22
+ import { useColorScheme } from '../hooks/useColorScheme';
23
+ import { AgentPopup } from './AgentPopup';
24
+ import TandemPortrait from './TandemPortrait';
25
+
26
+ // Agent colors matching CLI statusbar (statusline.sh)
27
+ const AGENT_COLORS: Record<string, string> = {
28
+ pm: '#a78bfa', // Purple - strategic
29
+ sm: '#60a5fa', // Blue - coordination
30
+ dev: '#4ade80', // Green - building
31
+ tea: '#2dd4bf', // Teal - testing
32
+ reviewer: '#f87171', // Red - critical eye
33
+ architect: '#fb923c', // Orange - design
34
+ devops: '#22d3ee', // Cyan - infrastructure
35
+ 'ux-designer': '#f0abfc', // Pink - design
36
+ 'tech-writer': '#e5e5e5', // White/light gray - documentation
37
+ orchestrator: '#e879f9', // Magenta - coordination
38
+ ba: '#a3e635', // Lime - discovery
39
+ };
40
+
41
+ // Abbreviated role names for compact badge display
42
+ const AGENT_ABBREV: Record<string, string> = {
43
+ pm: 'PM',
44
+ sm: 'SM',
45
+ dev: 'DEV',
46
+ tea: 'TEA',
47
+ reviewer: 'REV',
48
+ architect: 'ARC',
49
+ devops: 'OPS',
50
+ 'ux-designer': 'UX',
51
+ 'tech-writer': 'TW',
52
+ orchestrator: 'ORC',
53
+ ba: 'BA',
54
+ };
55
+
56
+ // Convert kebab-case theme name to Title Case (e.g., "princess-bride" -> "Princess Bride")
57
+ function humanizeTheme(theme: string): string {
58
+ return theme
59
+ .split('-')
60
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
61
+ .join(' ');
62
+ }
63
+
64
+ export default function PersonaHeader(): React.ReactElement {
65
+ const { persona, isStreaming } = usePersona();
66
+ const colorScheme = useColorScheme();
67
+ const [portraitError, setPortraitError] = useState(false);
68
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
69
+ const [isCompact, setIsCompact] = useState(false);
70
+
71
+ const character = persona?.character || 'Agent';
72
+ const theme = persona?.theme || 'default';
73
+ const role = persona?.role || 'agent';
74
+ const slug = persona?.slug;
75
+ const quote = persona?.quote;
76
+ const tandemAgent = persona?.tandemAgent;
77
+
78
+ // Observation pulse: one-shot animation on primary portrait when backseat starts thinking
79
+ const [observationPulse, setObservationPulse] = useState(false);
80
+ const prevThinkingRef = useRef(false);
81
+ const portraitRef = useRef<HTMLDivElement>(null);
82
+
83
+ useEffect(() => {
84
+ const wasThinking = prevThinkingRef.current;
85
+ const isThinking = tandemAgent?.isThinking ?? false;
86
+ prevThinkingRef.current = isThinking;
87
+
88
+ if (!wasThinking && isThinking) {
89
+ setObservationPulse(true);
90
+ }
91
+ }, [tandemAgent?.isThinking]);
92
+
93
+ const handlePulseEnd = useCallback(() => {
94
+ setObservationPulse(false);
95
+ }, []);
96
+
97
+ const handleOpenPopup = useCallback(() => {
98
+ setIsPopupOpen(true);
99
+ }, []);
100
+
101
+ const handleClosePopup = useCallback(() => {
102
+ setIsPopupOpen(false);
103
+ }, []);
104
+
105
+ // Get role color, fallback to magenta
106
+ const roleColor = AGENT_COLORS[role] || '#e879f9';
107
+
108
+ // Don't render if no persona data
109
+ if (!persona?.character) {
110
+ return <div className="persona-header empty" data-testid="persona-header" />;
111
+ }
112
+
113
+ return (
114
+ <>
115
+ <TooltipProvider delayDuration={300}>
116
+ <div
117
+ className={`persona-header clickable${isCompact ? ' compact' : ''}`}
118
+ data-testid="persona-header"
119
+ role="button"
120
+ tabIndex={0}
121
+ aria-label="Current agent persona - click to view team"
122
+ aria-live="polite"
123
+ onClick={handleOpenPopup}
124
+ onKeyDown={(e) => e.key === 'Enter' && handleOpenPopup()}
125
+ >
126
+ <div className="persona-portrait-group">
127
+ <div
128
+ className={`persona-portrait${isStreaming ? ' avatar-thinking' : ''}${observationPulse ? ' avatar-observation-pulse' : ''}`}
129
+ data-testid="persona-portrait"
130
+ ref={portraitRef}
131
+ onAnimationEnd={handlePulseEnd}
132
+ >
133
+ {slug && theme && !portraitError ? (
134
+ <img
135
+ src={`/portraits/${theme}/medium/${slug}.png`}
136
+ alt={character}
137
+ className="portrait-image"
138
+ onError={() => setPortraitError(true)}
139
+ />
140
+ ) : (
141
+ <span className="portrait-fallback">🤖</span>
142
+ )}
143
+ </div>
144
+ {tandemAgent && (
145
+ <TandemPortrait
146
+ character={tandemAgent.character}
147
+ role={tandemAgent.role}
148
+ slug={tandemAgent.slug}
149
+ theme={tandemAgent.theme}
150
+ isActive={true}
151
+ isThinking={tandemAgent.isThinking}
152
+ />
153
+ )}
154
+ {tandemAgent && (
155
+ <span className="visually-hidden" role="status" aria-live="polite" data-testid="tandem-sr-status">
156
+ {tandemAgent.isThinking
157
+ ? `${tandemAgent.character} is thinking`
158
+ : `${tandemAgent.character} observing`}
159
+ </span>
160
+ )}
161
+ </div>
162
+ <div className="persona-info">
163
+ <div className="persona-name-row">
164
+ <Tooltip>
165
+ <TooltipTrigger asChild>
166
+ <Badge
167
+ variant="default"
168
+ className="persona-role"
169
+ data-testid="persona-role"
170
+ style={{ backgroundColor: roleColor }}
171
+ >
172
+ {AGENT_ABBREV[role] || role}
173
+ </Badge>
174
+ </TooltipTrigger>
175
+ <TooltipContent>{role}</TooltipContent>
176
+ </Tooltip>
177
+ <Tooltip>
178
+ <TooltipTrigger asChild>
179
+ <span
180
+ className="persona-character"
181
+ data-testid="persona-character"
182
+ >
183
+ {character}
184
+ </span>
185
+ </TooltipTrigger>
186
+ <TooltipContent>{character}</TooltipContent>
187
+ </Tooltip>
188
+ <Tooltip>
189
+ <TooltipTrigger asChild>
190
+ <span
191
+ className="persona-theme"
192
+ data-testid="persona-theme"
193
+ >
194
+ {humanizeTheme(theme)}
195
+ </span>
196
+ </TooltipTrigger>
197
+ <TooltipContent>{`Theme: ${theme}`}</TooltipContent>
198
+ </Tooltip>
199
+ </div>
200
+ {quote && (
201
+ <Tooltip>
202
+ <TooltipTrigger asChild>
203
+ <span
204
+ className="persona-catchphrase"
205
+ data-testid="persona-catchphrase"
206
+ >
207
+ "{quote}"
208
+ </span>
209
+ </TooltipTrigger>
210
+ <TooltipContent>{quote}</TooltipContent>
211
+ </Tooltip>
212
+ )}
213
+ </div>
214
+ <img
215
+ src={tandemAgent
216
+ ? `/portraits/${theme}/medium/cyclist-tandem.png`
217
+ : (colorScheme === 'dark' ? '/images/cyclist-dark.png' : '/images/cyclist-light.png')}
218
+ alt="Cyclist"
219
+ className="persona-branding"
220
+ />
221
+ <button
222
+ className="persona-collapse-toggle"
223
+ onClick={(e) => {
224
+ e.stopPropagation();
225
+ setIsCompact(!isCompact);
226
+ }}
227
+ aria-label={isCompact ? 'Expand header' : 'Collapse header'}
228
+ >
229
+ {isCompact ? '▼' : '▲'}
230
+ </button>
231
+ </div>
232
+ </TooltipProvider>
233
+
234
+ <AgentPopup
235
+ isOpen={isPopupOpen}
236
+ onClose={handleClosePopup}
237
+ currentRole={role}
238
+ currentTheme={theme}
239
+ />
240
+ </>
241
+ );
242
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * ProjectInfoBar Component
3
+ *
4
+ * Slim info bar displaying the project directory name.
5
+ * Sits between PersonaHeader and Dockview tabs in BikeRack mode.
6
+ * Story 110-6: Project directory indicator in TUI header
7
+ */
8
+
9
+ import React, { useState, useEffect } from 'react';
10
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
11
+
12
+ interface ProjectInfo {
13
+ name: string;
14
+ path: string;
15
+ }
16
+
17
+ export default function ProjectInfoBar(): React.ReactElement {
18
+ const [info, setInfo] = useState<ProjectInfo | null>(null);
19
+
20
+ useEffect(() => {
21
+ fetch('/api/project-info')
22
+ .then(res => res.ok ? res.json() : null)
23
+ .then(data => { if (data) setInfo(data); })
24
+ .catch(() => {});
25
+ }, []);
26
+
27
+ if (!info) {
28
+ return <div className="project-info-bar" data-testid="project-info-bar" />;
29
+ }
30
+
31
+ return (
32
+ <TooltipProvider delayDuration={300}>
33
+ <div className="project-info-bar" data-testid="project-info-bar">
34
+ <Tooltip>
35
+ <TooltipTrigger asChild>
36
+ <span className="project-info-dir" data-testid="project-info-dir">
37
+ {info.name}
38
+ </span>
39
+ </TooltipTrigger>
40
+ <TooltipContent>{info.path}</TooltipContent>
41
+ </Tooltip>
42
+ </div>
43
+ </TooltipProvider>
44
+ );
45
+ }