@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,165 @@
1
+ /**
2
+ * useTandemObservations Hook
3
+ *
4
+ * React hook for subscribing to tandem observation data.
5
+ * Story 86-11: Cyclist: Tandem dialogue panel
6
+ *
7
+ * Uses WebSocket /ws/tandem for real-time updates.
8
+ * Parses .session/{story}-tandem-{partner}.md observation files.
9
+ */
10
+
11
+ import { useState, useEffect, useRef } from 'react';
12
+
13
+ // =============================================================================
14
+ // Types
15
+ // =============================================================================
16
+
17
+ export interface TandemTrigger {
18
+ scope: string; // file-watch, tool-watch, context-watch
19
+ detail: string; // specific trigger detail
20
+ }
21
+
22
+ export interface TandemObservation {
23
+ timestamp: string; // ISO timestamp
24
+ time: string; // HH:MM from section header
25
+ trigger: TandemTrigger;
26
+ content: string; // observation text
27
+ outcome?: 'applied' | 'deferred' | 'rejected';
28
+ confidence?: number; // 0-1
29
+ }
30
+
31
+ export interface TandemHeader {
32
+ storyId: string;
33
+ observer: string; // agent role (e.g., "architect")
34
+ character: string; // persona character name (e.g., "The Man in Black")
35
+ phase: string; // workflow phase
36
+ startedAt: string; // ISO timestamp
37
+ theme?: string; // theme slug for portrait resolution
38
+ slug?: string; // character slug for portrait image
39
+ }
40
+
41
+ export interface TandemMetrics {
42
+ exchangeCount: number;
43
+ tokenOverhead: number; // percentage
44
+ confidenceDistribution: {
45
+ high: number;
46
+ medium: number;
47
+ low: number;
48
+ };
49
+ outcomeBreakdown: {
50
+ applied: number;
51
+ deferred: number;
52
+ rejected: number;
53
+ };
54
+ }
55
+
56
+ export interface UseTandemObservationsResult {
57
+ header: TandemHeader | null;
58
+ observations: TandemObservation[];
59
+ metrics: TandemMetrics | null;
60
+ isLoading: boolean;
61
+ error: Error | null;
62
+ }
63
+
64
+ /** WebSocket message format from /ws/tandem */
65
+ export interface TandemMessage {
66
+ type: 'init' | 'observation' | 'metrics' | 'clear';
67
+ header?: TandemHeader;
68
+ observations?: TandemObservation[];
69
+ observation?: TandemObservation;
70
+ metrics?: TandemMetrics;
71
+ }
72
+
73
+ // =============================================================================
74
+ // Hook
75
+ // =============================================================================
76
+
77
+ export function useTandemObservations(): UseTandemObservationsResult {
78
+ const [header, setHeader] = useState<TandemHeader | null>(null);
79
+ const [observations, setObservations] = useState<TandemObservation[]>([]);
80
+ const [metrics, setMetrics] = useState<TandemMetrics | null>(null);
81
+ const [isLoading, setIsLoading] = useState(true);
82
+ const [error, setError] = useState<Error | null>(null);
83
+ const wsRef = useRef<WebSocket | null>(null);
84
+ const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
85
+
86
+ useEffect(() => {
87
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
88
+ const wsUrl = `${protocol}//${window.location.host}/ws/tandem`;
89
+
90
+ const connect = () => {
91
+ try {
92
+ wsRef.current = new WebSocket(wsUrl);
93
+
94
+ wsRef.current.onopen = () => {
95
+ console.debug('[useTandemObservations] WebSocket connected');
96
+ };
97
+
98
+ wsRef.current.onmessage = (event) => {
99
+ try {
100
+ const msg = JSON.parse(event.data) as TandemMessage;
101
+
102
+ switch (msg.type) {
103
+ case 'init':
104
+ setHeader(msg.header ?? null);
105
+ setObservations(msg.observations ?? []);
106
+ setIsLoading(false);
107
+ setError(null);
108
+ break;
109
+
110
+ case 'observation':
111
+ if (msg.observation) {
112
+ setObservations(prev => {
113
+ const next = [...prev, msg.observation!];
114
+ return next.length > 200 ? next.slice(-200) : next;
115
+ });
116
+ }
117
+ break;
118
+
119
+ case 'metrics':
120
+ if (msg.metrics) {
121
+ setMetrics(msg.metrics);
122
+ }
123
+ break;
124
+
125
+ case 'clear':
126
+ setHeader(null);
127
+ setObservations([]);
128
+ setMetrics(null);
129
+ break;
130
+ }
131
+ } catch (err) {
132
+ console.error('[useTandemObservations] Failed to parse message:', err);
133
+ }
134
+ };
135
+
136
+ wsRef.current.onclose = () => {
137
+ console.debug('[useTandemObservations] WebSocket closed, reconnecting...');
138
+ reconnectTimeoutRef.current = setTimeout(connect, 2000);
139
+ };
140
+
141
+ wsRef.current.onerror = (err) => {
142
+ console.error('[useTandemObservations] WebSocket error:', err);
143
+ setError(new Error('WebSocket connection failed'));
144
+ };
145
+ } catch (err) {
146
+ console.error('[useTandemObservations] WebSocket init failed:', err);
147
+ setError(err instanceof Error ? err : new Error('Failed to connect'));
148
+ setIsLoading(false);
149
+ }
150
+ };
151
+
152
+ connect();
153
+
154
+ return () => {
155
+ if (reconnectTimeoutRef.current) {
156
+ clearTimeout(reconnectTimeoutRef.current);
157
+ }
158
+ if (wsRef.current) {
159
+ wsRef.current.close();
160
+ }
161
+ };
162
+ }, []);
163
+
164
+ return { header, observations, metrics, isLoading, error };
165
+ }
@@ -0,0 +1,273 @@
1
+ /**
2
+ * useTeamMembers Hook
3
+ *
4
+ * React hook for subscribing to native team state.
5
+ * Story 86-12: Cyclist: Native team panel
6
+ *
7
+ * Uses WebSocket channels:
8
+ * - /ws/team — team lifecycle events (init, member_update, disbanded)
9
+ * - /ws/tasks — task list changes (init, task_updated)
10
+ * - /ws/messages — inter-agent messages (init, message)
11
+ */
12
+
13
+ import { useState, useLayoutEffect, useRef, useCallback } from 'react';
14
+
15
+ // =============================================================================
16
+ // Types
17
+ // =============================================================================
18
+
19
+ export interface TeamMember {
20
+ name: string;
21
+ agentType: string;
22
+ status: 'idle' | 'working' | 'blocked';
23
+ portrait?: { theme: string; slug: string };
24
+ currentTask?: string;
25
+ taskProgress?: number;
26
+ }
27
+
28
+ export interface TaskListItem {
29
+ id: string;
30
+ title: string;
31
+ owner?: string;
32
+ status: 'pending' | 'in_progress' | 'completed';
33
+ blockedBy?: string[];
34
+ }
35
+
36
+ export interface TeamMessage {
37
+ from: string;
38
+ to?: string;
39
+ content: string;
40
+ timestamp: string;
41
+ type: 'message' | 'broadcast' | 'shutdown_request' | 'shutdown_response';
42
+ }
43
+
44
+ export interface TeamState {
45
+ isActive: boolean;
46
+ teamName?: string;
47
+ members: TeamMember[];
48
+ tasks: TaskListItem[];
49
+ messages: TeamMessage[];
50
+ isLoading: boolean;
51
+ error: Error | null;
52
+ }
53
+
54
+ interface TeamInitMessage {
55
+ type: 'init';
56
+ team: {
57
+ id: string;
58
+ lead: string;
59
+ created: string;
60
+ name?: string;
61
+ members: TeamMember[];
62
+ };
63
+ }
64
+
65
+ interface MemberUpdateMessage {
66
+ type: 'member_update';
67
+ member: TeamMember;
68
+ }
69
+
70
+ interface DisbandedMessage {
71
+ type: 'disbanded';
72
+ }
73
+
74
+ type TeamWsMessage = TeamInitMessage | MemberUpdateMessage | DisbandedMessage;
75
+
76
+ interface TasksInitMessage {
77
+ type: 'init';
78
+ tasks: TaskListItem[];
79
+ }
80
+
81
+ interface TaskUpdatedMessage {
82
+ type: 'task_updated';
83
+ task: TaskListItem;
84
+ }
85
+
86
+ type TasksWsMessage = TasksInitMessage | TaskUpdatedMessage;
87
+
88
+ interface MessagesInitMessage {
89
+ type: 'init';
90
+ messages: TeamMessage[];
91
+ }
92
+
93
+ interface NewMessageMessage {
94
+ type: 'message';
95
+ msg: Partial<TeamMessage> & { from: string; content: string; timestamp: string };
96
+ }
97
+
98
+ type MessagesWsMessage = MessagesInitMessage | NewMessageMessage;
99
+
100
+ // =============================================================================
101
+ // WebSocket URL helper
102
+ // =============================================================================
103
+
104
+ function wsUrl(path: string): string {
105
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
106
+ return `${protocol}//${window.location.host}${path}`;
107
+ }
108
+
109
+ // =============================================================================
110
+ // Hook
111
+ // =============================================================================
112
+
113
+ export function useTeamMembers(): TeamState {
114
+ const [isActive, setIsActive] = useState(false);
115
+ const [teamName, setTeamName] = useState<string | undefined>(undefined);
116
+ const [members, setMembers] = useState<TeamMember[]>([]);
117
+ const [tasks, setTasks] = useState<TaskListItem[]>([]);
118
+ const [messages, setMessages] = useState<TeamMessage[]>([]);
119
+ const [isLoading, setIsLoading] = useState(true);
120
+ const [error, setError] = useState<Error | null>(null);
121
+
122
+ const teamWsRef = useRef<WebSocket | null>(null);
123
+ const tasksWsRef = useRef<WebSocket | null>(null);
124
+ const messagesWsRef = useRef<WebSocket | null>(null);
125
+ const reconnectTimersRef = useRef<ReturnType<typeof setTimeout>[]>([]);
126
+ const mountedRef = useRef(true);
127
+
128
+ // Create initial WebSocket connections synchronously during first render.
129
+ // This ensures the WebSocket objects exist immediately (before any effects),
130
+ // which is critical for tests using vi.useFakeTimers() where effect scheduling
131
+ // depends on timer availability.
132
+ const initializedRef = useRef(false);
133
+ if (!initializedRef.current) {
134
+ initializedRef.current = true;
135
+ teamWsRef.current = new WebSocket(wsUrl('/ws/team'));
136
+ tasksWsRef.current = new WebSocket(wsUrl('/ws/tasks'));
137
+ messagesWsRef.current = new WebSocket(wsUrl('/ws/messages'));
138
+ }
139
+
140
+ // Reconnection helper — creates new WebSocket and wires handlers
141
+ const connectTeam = useCallback(() => {
142
+ const ws = new WebSocket(wsUrl('/ws/team'));
143
+ teamWsRef.current = ws;
144
+ wireTeamHandlers(ws);
145
+ }, []);
146
+
147
+ function wireTeamHandlers(ws: WebSocket) {
148
+ ws.onmessage = (event) => {
149
+ try {
150
+ const msg = JSON.parse(event.data) as TeamWsMessage;
151
+ switch (msg.type) {
152
+ case 'init':
153
+ setMembers(msg.team.members);
154
+ setTeamName(msg.team.name);
155
+ setIsActive(true);
156
+ setIsLoading(false);
157
+ setError(null);
158
+ break;
159
+ case 'member_update':
160
+ setMembers(prev => prev.map(m =>
161
+ m.name === msg.member.name ? { ...m, ...msg.member } : m
162
+ ));
163
+ break;
164
+ case 'disbanded':
165
+ setMembers([]);
166
+ setTeamName(undefined);
167
+ setIsActive(false);
168
+ break;
169
+ }
170
+ } catch {
171
+ // ignore parse errors
172
+ }
173
+ };
174
+
175
+ ws.onerror = () => {
176
+ setError(new Error('WebSocket connection failed'));
177
+ setIsLoading(false);
178
+ };
179
+
180
+ ws.onclose = () => {
181
+ if (mountedRef.current) {
182
+ const timer = setTimeout(connectTeam, 2000);
183
+ reconnectTimersRef.current.push(timer);
184
+ }
185
+ };
186
+ }
187
+
188
+ function wireTasksHandlers(ws: WebSocket) {
189
+ ws.onmessage = (event) => {
190
+ try {
191
+ const msg = JSON.parse(event.data) as TasksWsMessage;
192
+ switch (msg.type) {
193
+ case 'init':
194
+ setTasks(msg.tasks);
195
+ break;
196
+ case 'task_updated':
197
+ setTasks(prev => prev.map(t =>
198
+ t.id === msg.task.id ? { ...t, ...msg.task } : t
199
+ ));
200
+ break;
201
+ }
202
+ } catch {
203
+ // ignore parse errors
204
+ }
205
+ };
206
+
207
+ ws.onclose = () => {
208
+ if (mountedRef.current) {
209
+ const timer = setTimeout(() => {
210
+ const newWs = new WebSocket(wsUrl('/ws/tasks'));
211
+ tasksWsRef.current = newWs;
212
+ wireTasksHandlers(newWs);
213
+ }, 2000);
214
+ reconnectTimersRef.current.push(timer);
215
+ }
216
+ };
217
+ }
218
+
219
+ function wireMessagesHandlers(ws: WebSocket) {
220
+ ws.onmessage = (event) => {
221
+ try {
222
+ const msg = JSON.parse(event.data) as MessagesWsMessage;
223
+ switch (msg.type) {
224
+ case 'init':
225
+ setMessages(msg.messages);
226
+ break;
227
+ case 'message':
228
+ setMessages(prev => [...prev, {
229
+ from: msg.msg.from,
230
+ to: msg.msg.to,
231
+ content: msg.msg.content,
232
+ timestamp: msg.msg.timestamp,
233
+ type: msg.msg.type ?? 'message',
234
+ }]);
235
+ break;
236
+ }
237
+ } catch {
238
+ // ignore parse errors
239
+ }
240
+ };
241
+
242
+ ws.onclose = () => {
243
+ if (mountedRef.current) {
244
+ const timer = setTimeout(() => {
245
+ const newWs = new WebSocket(wsUrl('/ws/messages'));
246
+ messagesWsRef.current = newWs;
247
+ wireMessagesHandlers(newWs);
248
+ }, 2000);
249
+ reconnectTimersRef.current.push(timer);
250
+ }
251
+ };
252
+ }
253
+
254
+ // Wire up handlers in layout effect (synchronous, fires during commit)
255
+ useLayoutEffect(() => {
256
+ mountedRef.current = true;
257
+
258
+ wireTeamHandlers(teamWsRef.current!);
259
+ wireTasksHandlers(tasksWsRef.current!);
260
+ wireMessagesHandlers(messagesWsRef.current!);
261
+
262
+ return () => {
263
+ mountedRef.current = false;
264
+ reconnectTimersRef.current.forEach(t => clearTimeout(t));
265
+ reconnectTimersRef.current = [];
266
+ teamWsRef.current?.close();
267
+ tasksWsRef.current?.close();
268
+ messagesWsRef.current?.close();
269
+ };
270
+ }, []);
271
+
272
+ return { isActive, teamName, members, tasks, messages, isLoading, error };
273
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * useTodos Hook
3
+ *
4
+ * React hook for subscribing to todo list data.
5
+ * Uses WebSocket /ws/todos for real-time updates (no polling).
6
+ * Story MSSCI-12717 - React Migration
7
+ */
8
+
9
+ import { useState, useEffect, useRef } from 'react';
10
+
11
+ export interface TodoItem {
12
+ id: string;
13
+ content: string;
14
+ activeForm: string;
15
+ status: 'pending' | 'in_progress' | 'completed';
16
+ blockedBy?: string[];
17
+ blocks?: string[];
18
+ }
19
+
20
+ interface UseTodosResult {
21
+ todos: TodoItem[];
22
+ isLoading: boolean;
23
+ error: Error | null;
24
+ }
25
+
26
+ /** WebSocket message format from /ws/todos */
27
+ interface TodosMessage {
28
+ type: 'init' | 'update';
29
+ todos: TodoItem[];
30
+ }
31
+
32
+ export function useTodos(): UseTodosResult {
33
+ const [todos, setTodos] = useState<TodoItem[]>([]);
34
+ const [isLoading, setIsLoading] = useState(true);
35
+ const [error, setError] = useState<Error | null>(null);
36
+ const wsRef = useRef<WebSocket | null>(null);
37
+ const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
38
+
39
+ useEffect(() => {
40
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
41
+ const wsUrl = `${protocol}//${window.location.host}/ws/todos`;
42
+
43
+ const connect = () => {
44
+ try {
45
+ wsRef.current = new WebSocket(wsUrl);
46
+
47
+ wsRef.current.onopen = () => {
48
+ console.debug('[useTodos] WebSocket connected');
49
+ };
50
+
51
+ wsRef.current.onmessage = (event) => {
52
+ try {
53
+ const msg = JSON.parse(event.data) as TodosMessage;
54
+ if (msg.type === 'init' || msg.type === 'update') {
55
+ setTodos(msg.todos || []);
56
+ setIsLoading(false);
57
+ setError(null);
58
+ }
59
+ } catch (err) {
60
+ console.error('[useTodos] Failed to parse message:', err);
61
+ }
62
+ };
63
+
64
+ wsRef.current.onclose = () => {
65
+ console.debug('[useTodos] WebSocket closed, reconnecting...');
66
+ reconnectTimeoutRef.current = setTimeout(connect, 2000);
67
+ };
68
+
69
+ wsRef.current.onerror = (err) => {
70
+ console.error('[useTodos] WebSocket error:', err);
71
+ setError(new Error('WebSocket connection failed'));
72
+ };
73
+ } catch (err) {
74
+ console.error('[useTodos] WebSocket init failed:', err);
75
+ setError(err instanceof Error ? err : new Error('Failed to connect'));
76
+ setIsLoading(false);
77
+ }
78
+ };
79
+
80
+ connect();
81
+
82
+ return () => {
83
+ if (reconnectTimeoutRef.current) {
84
+ clearTimeout(reconnectTimeoutRef.current);
85
+ }
86
+ if (wsRef.current) {
87
+ wsRef.current.close();
88
+ }
89
+ };
90
+ }, []);
91
+
92
+ return { todos, isLoading, error };
93
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * useUserAvatar Hook
3
+ *
4
+ * React hook for fetching and managing user avatar state.
5
+ * Story MSSCI-12777 - User Avatar from GitHub/Gravatar
6
+ *
7
+ * Uses avatar-service for fetching with fallback chain:
8
+ * GitHub → Gravatar → Default silhouette
9
+ */
10
+
11
+ import { useState, useEffect } from 'react';
12
+ import { DEFAULT_AVATAR } from '../utils/avatar-service';
13
+
14
+ export { DEFAULT_AVATAR };
15
+
16
+ export interface UseUserAvatarResult {
17
+ avatarUrl: string | null;
18
+ isLoading: boolean;
19
+ error: Error | null;
20
+ }
21
+
22
+ export function useUserAvatar(): UseUserAvatarResult {
23
+ const [avatarUrl, setAvatarUrl] = useState<string | null>(null);
24
+ const [isLoading, setIsLoading] = useState(true);
25
+ const [error, setError] = useState<Error | null>(null);
26
+
27
+ useEffect(() => {
28
+ const fetchAvatar = async () => {
29
+ try {
30
+ // Try REST endpoint for avatar
31
+ const response = await fetch('/api/identity');
32
+ if (response.ok) {
33
+ const data = await response.json();
34
+ if (data.avatarUrl) {
35
+ setAvatarUrl(data.avatarUrl);
36
+ } else {
37
+ setAvatarUrl(DEFAULT_AVATAR);
38
+ }
39
+ } else {
40
+ setAvatarUrl(DEFAULT_AVATAR);
41
+ }
42
+ setIsLoading(false);
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err : new Error('Failed to fetch avatar'));
45
+ setAvatarUrl(DEFAULT_AVATAR);
46
+ setIsLoading(false);
47
+ }
48
+ };
49
+
50
+ fetchAvatar();
51
+ }, []);
52
+
53
+ return { avatarUrl, isLoading, error };
54
+ }
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Cyclist - Claude Code Dashboard</title>
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚴</text></svg>">
8
+ <link rel="stylesheet" href="/css/react.css">
9
+ </head>
10
+ <body>
11
+ <div id="react-root"></div>
12
+ <script type="module" src="/js/react/react.js"></script>
13
+ </body>
14
+ </html>
@@ -0,0 +1,10 @@
1
+ import { createRoot } from 'react-dom/client';
2
+ import App from './App';
3
+ import './styles/tailwind.css';
4
+ import './css/theme-system.css';
5
+
6
+ // React entry point for Cyclist
7
+ const container = document.getElementById('react-root');
8
+ if (container) {
9
+ createRoot(container).render(<App />);
10
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }