@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.
- package/README.md +1 -1
- package/package.json +4 -1
- package/packages/core/dist/public/css/react.css +1 -1
- package/packages/core/dist/public/js/react/react.js +24 -24
- package/packages/core/src/public/App.tsx +356 -0
- package/packages/core/src/public/components/AgentLoadDialog.tsx +202 -0
- package/packages/core/src/public/components/AgentPopup.tsx +308 -0
- package/packages/core/src/public/components/ApprovalModal/ApprovalModal.css +35 -0
- package/packages/core/src/public/components/ApprovalModal/index.tsx +632 -0
- package/packages/core/src/public/components/BikeRackIndex.tsx +53 -0
- package/packages/core/src/public/components/BikeRackWorkspace.tsx +217 -0
- package/packages/core/src/public/components/CommandPalette.tsx +554 -0
- package/packages/core/src/public/components/ConfirmDialog.tsx +168 -0
- package/packages/core/src/public/components/ContextIndicator/ContextIndicator.css +85 -0
- package/packages/core/src/public/components/ContextIndicator/index.tsx +330 -0
- package/packages/core/src/public/components/ContextSparkline.tsx +56 -0
- package/packages/core/src/public/components/ControlBar.tsx +636 -0
- package/packages/core/src/public/components/DeadCodeDialog.tsx +169 -0
- package/packages/core/src/public/components/DiffViewer.tsx +585 -0
- package/packages/core/src/public/components/DockviewWorkspace.tsx +749 -0
- package/packages/core/src/public/components/Editor.tsx +630 -0
- package/packages/core/src/public/components/ErrorBoundary.tsx +67 -0
- package/packages/core/src/public/components/FileTree.tsx +379 -0
- package/packages/core/src/public/components/FontPicker/FontPicker.css +276 -0
- package/packages/core/src/public/components/FontPicker/index.tsx +430 -0
- package/packages/core/src/public/components/FullFileTree.tsx +237 -0
- package/packages/core/src/public/components/HealthGauge.tsx +181 -0
- package/packages/core/src/public/components/Message.tsx +225 -0
- package/packages/core/src/public/components/MessageList.tsx +98 -0
- package/packages/core/src/public/components/MessageView.tsx +400 -0
- package/packages/core/src/public/components/ModeSwitch/ModeSwitch.css +165 -0
- package/packages/core/src/public/components/ModeSwitch/index.tsx +372 -0
- package/packages/core/src/public/components/PersonaHeader.tsx +242 -0
- package/packages/core/src/public/components/ProjectInfoBar.tsx +45 -0
- package/packages/core/src/public/components/QuickActions.tsx +267 -0
- package/packages/core/src/public/components/SpanTimeline.tsx +352 -0
- package/packages/core/src/public/components/StandalonePanel.tsx +82 -0
- package/packages/core/src/public/components/StatsStrip.tsx +162 -0
- package/packages/core/src/public/components/StreamingContent.tsx +77 -0
- package/packages/core/src/public/components/SubagentSpan.tsx +180 -0
- package/packages/core/src/public/components/TandemPortrait.tsx +72 -0
- package/packages/core/src/public/components/ThemePalette/ThemePalette.css +179 -0
- package/packages/core/src/public/components/ThemePalette/index.tsx +326 -0
- package/packages/core/src/public/components/ToolCallBlock.tsx +252 -0
- package/packages/core/src/public/components/ToolStack.tsx +209 -0
- package/packages/core/src/public/components/ToolStatus.tsx +57 -0
- package/packages/core/src/public/components/dialogs/CodeMarkersDialog.tsx +169 -0
- package/packages/core/src/public/components/dialogs/ComplexityDialog.tsx +163 -0
- package/packages/core/src/public/components/dialogs/DependenciesDialog.tsx +120 -0
- package/packages/core/src/public/components/dialogs/HotspotsDialog.tsx +451 -0
- package/packages/core/src/public/components/dialogs/ToolDialog.tsx +43 -0
- package/packages/core/src/public/components/panel-registry.ts +13 -0
- package/packages/core/src/public/components/panels/ACPanel.tsx +93 -0
- package/packages/core/src/public/components/panels/AcceptanceCriteriaPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/AuditLogPanel.tsx +489 -0
- package/packages/core/src/public/components/panels/BackgroundPanel.tsx +115 -0
- package/packages/core/src/public/components/panels/BikeLanePanel.tsx +214 -0
- package/packages/core/src/public/components/panels/DebugPanel.tsx +344 -0
- package/packages/core/src/public/components/panels/DiffView.tsx +109 -0
- package/packages/core/src/public/components/panels/DiffsPanel.tsx +56 -0
- package/packages/core/src/public/components/panels/GitPanel.tsx +260 -0
- package/packages/core/src/public/components/panels/HotspotsPanel.tsx +365 -0
- package/packages/core/src/public/components/panels/MessageFeed.tsx +39 -0
- package/packages/core/src/public/components/panels/MessagePanel.tsx +497 -0
- package/packages/core/src/public/components/panels/ProgressPanel.tsx +189 -0
- package/packages/core/src/public/components/panels/SettingsPanel.tsx +361 -0
- package/packages/core/src/public/components/panels/SprintPanel.tsx +723 -0
- package/packages/core/src/public/components/panels/TandemPanel.tsx +104 -0
- package/packages/core/src/public/components/panels/TaskTracker.tsx +48 -0
- package/packages/core/src/public/components/panels/TeamPanel.tsx +64 -0
- package/packages/core/src/public/components/panels/TeamRoster.tsx +67 -0
- package/packages/core/src/public/components/panels/TodoPanel.tsx +142 -0
- package/packages/core/src/public/components/panels/WorkflowPanel.tsx +224 -0
- package/packages/core/src/public/components/panels/index.ts +24 -0
- package/packages/core/src/public/components/ui/alert-dialog.tsx +139 -0
- package/packages/core/src/public/components/ui/badge.tsx +36 -0
- package/packages/core/src/public/components/ui/button.tsx +57 -0
- package/packages/core/src/public/components/ui/checkbox.tsx +28 -0
- package/packages/core/src/public/components/ui/collapsible.tsx +9 -0
- package/packages/core/src/public/components/ui/command.tsx +151 -0
- package/packages/core/src/public/components/ui/dialog.tsx +120 -0
- package/packages/core/src/public/components/ui/popover.tsx +31 -0
- package/packages/core/src/public/components/ui/progress.tsx +28 -0
- package/packages/core/src/public/components/ui/scroll-area.tsx +46 -0
- package/packages/core/src/public/components/ui/select.tsx +157 -0
- package/packages/core/src/public/components/ui/separator.tsx +29 -0
- package/packages/core/src/public/components/ui/skeleton.tsx +15 -0
- package/packages/core/src/public/components/ui/switch.tsx +27 -0
- package/packages/core/src/public/components/ui/toggle-group.tsx +59 -0
- package/packages/core/src/public/components/ui/toggle.tsx +43 -0
- package/packages/core/src/public/components/ui/tooltip.tsx +30 -0
- package/packages/core/src/public/contexts/ClaudeContext.tsx +311 -0
- package/packages/core/src/public/contexts/MessageQueueContext.tsx +143 -0
- package/packages/core/src/public/css/theme-browser.css +550 -0
- package/packages/core/src/public/css/theme-system.css +630 -0
- package/packages/core/src/public/hooks/index.ts +49 -0
- package/packages/core/src/public/hooks/useAgentLoad.ts +105 -0
- package/packages/core/src/public/hooks/useBackgroundTasks.ts +131 -0
- package/packages/core/src/public/hooks/useClaude.ts +234 -0
- package/packages/core/src/public/hooks/useCodeMarkers.ts +101 -0
- package/packages/core/src/public/hooks/useColorScheme.ts +42 -0
- package/packages/core/src/public/hooks/useCommandHistory.ts +99 -0
- package/packages/core/src/public/hooks/useComplexity.ts +80 -0
- package/packages/core/src/public/hooks/useDeadCode.ts +99 -0
- package/packages/core/src/public/hooks/useDependencies.ts +82 -0
- package/packages/core/src/public/hooks/useDiffs.ts +143 -0
- package/packages/core/src/public/hooks/useFileBrowser.ts +73 -0
- package/packages/core/src/public/hooks/useFocusPanel.ts +137 -0
- package/packages/core/src/public/hooks/useGitStatus.ts +233 -0
- package/packages/core/src/public/hooks/useHealthScore.ts +71 -0
- package/packages/core/src/public/hooks/useHotspots.ts +123 -0
- package/packages/core/src/public/hooks/useLayoutPersistence.ts +141 -0
- package/packages/core/src/public/hooks/useMarkdownParser.ts +36 -0
- package/packages/core/src/public/hooks/useMarkerActions.ts +234 -0
- package/packages/core/src/public/hooks/useMessageQueue.ts +380 -0
- package/packages/core/src/public/hooks/useMessageStream.ts +131 -0
- package/packages/core/src/public/hooks/usePersona.ts +112 -0
- package/packages/core/src/public/hooks/usePlanModeExit.ts +105 -0
- package/packages/core/src/public/hooks/useResponsiveLayout.ts +173 -0
- package/packages/core/src/public/hooks/useSprint.ts +157 -0
- package/packages/core/src/public/hooks/useStatsStrip.ts +204 -0
- package/packages/core/src/public/hooks/useStory.ts +135 -0
- package/packages/core/src/public/hooks/useSubagentHelper.ts +64 -0
- package/packages/core/src/public/hooks/useSyntaxHighlighter.ts +52 -0
- package/packages/core/src/public/hooks/useTabCompletion.ts +124 -0
- package/packages/core/src/public/hooks/useTandemObservations.ts +165 -0
- package/packages/core/src/public/hooks/useTeamMembers.ts +273 -0
- package/packages/core/src/public/hooks/useTodos.ts +93 -0
- package/packages/core/src/public/hooks/useUserAvatar.ts +54 -0
- package/packages/core/src/public/images/cyclist-dark.png +0 -0
- package/packages/core/src/public/images/cyclist-light.png +0 -0
- package/packages/core/src/public/index.html +14 -0
- package/packages/core/src/public/index.tsx +10 -0
- package/packages/core/src/public/lib/utils.ts +6 -0
- package/packages/core/src/public/styles/dockview-theme.css +376 -0
- package/packages/core/src/public/styles/tailwind.css +4454 -0
- package/packages/core/src/public/types/message.ts +51 -0
- package/packages/core/src/public/utils/avatar-service.ts +73 -0
- package/packages/core/src/public/utils/color-presets.ts +940 -0
- package/packages/core/src/public/utils/font-presets.ts +362 -0
- package/packages/core/src/public/utils/formatDuration.ts +14 -0
- package/packages/core/src/public/utils/markdown.ts +249 -0
- package/packages/core/src/public/utils/messageFilters.ts +128 -0
- package/packages/core/src/public/utils/slash-commands.ts +341 -0
- package/packages/core/src/public/utils/subagent-display.ts +146 -0
- package/packages/core/src/public/utils/syntax.ts +219 -0
- package/packages/core/src/public/utils/toolIntentSummarizer.ts +199 -0
- package/packages/core/src/public/utils/toolStackGrouper.ts +106 -0
- package/packages/core/src/public/utils/toolTypeColors.ts +45 -0
- package/pennyfarthing-dist/pf/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/__pycache__/context.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/focus.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/__pycache__/split.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bc/cli.py +0 -1
- package/pennyfarthing-dist/pf/bc/focus.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/base_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/git_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/launcher.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/portrait_resolver.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/sprint_panel.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_data.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/story_detail_screen.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/__pycache__/tui.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bikerack/base_panel.py +0 -1
- package/pennyfarthing-dist/pf/bikerack/events.py +1 -7
- package/pennyfarthing-dist/pf/bikerack/git_panel.py +273 -10
- package/pennyfarthing-dist/pf/bikerack/portrait_resolver.py +21 -0
- package/pennyfarthing-dist/pf/bikerack/sprint_panel.py +58 -1
- package/pennyfarthing-dist/pf/bikerack/tui.py +5 -20
- package/pennyfarthing-dist/pf/bmad/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/__pycache__/parser.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/bmad/parser.py +15 -9
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/codemarkers/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/output.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/pr_config.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/__pycache__/themes.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/common/pr_config.py +27 -2
- package/pennyfarthing-dist/pf/complexity/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/complexity/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/consultation/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/deadcode/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/dependencies/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/epic/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/git_group/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/complete_phase.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/marker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/phase_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/handoff/__pycache__/resolve_gate.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/__main__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/formatters.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/healthscore/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/bell_mode.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_breaker.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/context_warning.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/cyclist_pretooluse.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/pre_edit_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/reflector_check.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/schema_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_start.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/session_stop.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/sprint_yaml_validation.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hooks/__pycache__/statusline.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/analyze.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/hotspots/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/bidirectional.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/claim.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/client.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/create.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/operations.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/reconcile.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/story.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/jira/__pycache__/sync.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/launch/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/models.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/persona.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/session.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/tiers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/prime/__pycache__/workflow.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/session/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/__pycache__/settings.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/settings/settings.py +44 -8
- package/pennyfarthing-dist/pf/sprint/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/archive_epic.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/epic_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/loader.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/status.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_add.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_finish.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/story_update.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validate_cmd.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/validator.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/work.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/__pycache__/yaml_io.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/sprint/story_finish.py +14 -2
- package/pennyfarthing-dist/pf/sprint/validator.py +7 -7
- package/pennyfarthing-dist/pf/tests/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/conftest.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/__pycache__/test_sprint_validator.cpython-314-pytest-9.0.2.pyc +0 -0
- package/pennyfarthing-dist/pf/tests/test_sprint_validator.py +44 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/theme/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/validate/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/cli.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/helpers.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/scale.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/pf/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/pennyfarthing-dist/scripts/lib/find-root.sh +1 -1
- package/pennyfarthing-dist/scripts/misc/migrate_bmad_workflow.py +0 -1
- package/pennyfarthing-dist/scripts/portraits/generate-portraits.py +13 -13
- package/pennyfarthing-dist/scripts/workflow/check.py +4 -6
- package/pennyfarthing-dist/scripts/workflow/complete-step.py +2 -2
- package/pennyfarthing-dist/skills/skill-registry.yaml +19 -0
- package/pennyfarthing-dist/workflows/tdd-tandem.yaml +15 -2
- package/packages/core/dist/workflow/__test_context_watch__/.session/.tandem-turn-counter +0 -1
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-session.md +0 -3
- package/packages/core/dist/workflow/__test_context_watch__/.session/95-6-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/.session/95-4-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_file_watch__/workdir/trigger.ts +0 -1
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-architect.md +0 -6
- package/packages/core/dist/workflow/__test_tool_watch__/.session/95-5-tandem-toolcalls.jsonl +0 -1
- package/pennyfarthing-dist/pf/bikerack/changed_panel.py +0 -201
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface FileComplexity {
|
|
4
|
+
path: string;
|
|
5
|
+
total_lines: number;
|
|
6
|
+
longest_function: number;
|
|
7
|
+
avg_cyclomatic_complexity: number;
|
|
8
|
+
max_nesting_depth: number;
|
|
9
|
+
function_count: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ComplexityData {
|
|
13
|
+
success: boolean;
|
|
14
|
+
target_path: string;
|
|
15
|
+
file_count: number;
|
|
16
|
+
files: FileComplexity[];
|
|
17
|
+
error?: string | null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface UseComplexityOptions {
|
|
21
|
+
path?: string;
|
|
22
|
+
top?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface UseComplexityReturn {
|
|
26
|
+
data: ComplexityData | null;
|
|
27
|
+
isLoading: boolean;
|
|
28
|
+
error: Error | null;
|
|
29
|
+
refresh: () => void;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useComplexity(options: UseComplexityOptions): UseComplexityReturn {
|
|
33
|
+
const [data, setData] = useState<ComplexityData | null>(null);
|
|
34
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
35
|
+
const [error, setError] = useState<Error | null>(null);
|
|
36
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
37
|
+
|
|
38
|
+
const fetchComplexity = useCallback(() => {
|
|
39
|
+
if (abortRef.current) {
|
|
40
|
+
abortRef.current.abort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
abortRef.current = controller;
|
|
45
|
+
|
|
46
|
+
setIsLoading(true);
|
|
47
|
+
setError(null);
|
|
48
|
+
|
|
49
|
+
const params = new URLSearchParams();
|
|
50
|
+
if (options.path) params.set('path', options.path);
|
|
51
|
+
if (options.top) params.set('top', String(options.top));
|
|
52
|
+
|
|
53
|
+
fetch(`/api/complexity?${params}`, { signal: controller.signal })
|
|
54
|
+
.then((res) => {
|
|
55
|
+
if (!res.ok) {
|
|
56
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
57
|
+
}
|
|
58
|
+
return res.json();
|
|
59
|
+
})
|
|
60
|
+
.then((json: ComplexityData) => {
|
|
61
|
+
setData(json);
|
|
62
|
+
setIsLoading(false);
|
|
63
|
+
})
|
|
64
|
+
.catch((err) => {
|
|
65
|
+
if (err.name === 'AbortError') return;
|
|
66
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
67
|
+
setIsLoading(false);
|
|
68
|
+
});
|
|
69
|
+
}, [options.path, options.top]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
return () => {
|
|
73
|
+
if (abortRef.current) {
|
|
74
|
+
abortRef.current.abort();
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}, []);
|
|
78
|
+
|
|
79
|
+
return { data, isLoading, error, refresh: fetchComplexity };
|
|
80
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface StaleFile {
|
|
4
|
+
path: string;
|
|
5
|
+
last_commit_date: string;
|
|
6
|
+
days_since_last_commit: number;
|
|
7
|
+
size_bytes: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UnusedExport {
|
|
11
|
+
symbol: string;
|
|
12
|
+
file: string;
|
|
13
|
+
line: number;
|
|
14
|
+
export_type: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DeadCodeData {
|
|
18
|
+
success: boolean;
|
|
19
|
+
repo_name?: string;
|
|
20
|
+
repo_path?: string;
|
|
21
|
+
time_window_days?: number;
|
|
22
|
+
stale_files?: StaleFile[];
|
|
23
|
+
unused_exports?: UnusedExport[];
|
|
24
|
+
stale_file_count?: number;
|
|
25
|
+
unused_export_count?: number;
|
|
26
|
+
total_files?: number;
|
|
27
|
+
total_exports_scanned?: number;
|
|
28
|
+
repo_results?: DeadCodeData[];
|
|
29
|
+
error?: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface UseDeadCodeOptions {
|
|
33
|
+
days: number;
|
|
34
|
+
repo?: string;
|
|
35
|
+
layer?: 'stale' | 'exports' | 'all';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface UseDeadCodeReturn {
|
|
39
|
+
data: DeadCodeData | null;
|
|
40
|
+
isLoading: boolean;
|
|
41
|
+
error: Error | null;
|
|
42
|
+
refresh: () => void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function useDeadCode(options: UseDeadCodeOptions): UseDeadCodeReturn {
|
|
46
|
+
const [data, setData] = useState<DeadCodeData | null>(null);
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
const [error, setError] = useState<Error | null>(null);
|
|
49
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
50
|
+
|
|
51
|
+
const fetchDeadCode = useCallback(() => {
|
|
52
|
+
// Cancel any in-flight request
|
|
53
|
+
if (abortRef.current) {
|
|
54
|
+
abortRef.current.abort();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const controller = new AbortController();
|
|
58
|
+
abortRef.current = controller;
|
|
59
|
+
|
|
60
|
+
setIsLoading(true);
|
|
61
|
+
setError(null);
|
|
62
|
+
|
|
63
|
+
const params = new URLSearchParams({
|
|
64
|
+
days: String(options.days),
|
|
65
|
+
layer: options.layer || 'all',
|
|
66
|
+
});
|
|
67
|
+
if (options.repo) {
|
|
68
|
+
params.set('repo', options.repo);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
fetch(`/api/dead-code?${params}`, { signal: controller.signal })
|
|
72
|
+
.then((res) => {
|
|
73
|
+
if (!res.ok) {
|
|
74
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
75
|
+
}
|
|
76
|
+
return res.json();
|
|
77
|
+
})
|
|
78
|
+
.then((json: DeadCodeData) => {
|
|
79
|
+
setData(json);
|
|
80
|
+
setIsLoading(false);
|
|
81
|
+
})
|
|
82
|
+
.catch((err) => {
|
|
83
|
+
if (err.name === 'AbortError') return;
|
|
84
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
85
|
+
setIsLoading(false);
|
|
86
|
+
});
|
|
87
|
+
}, [options.days, options.repo, options.layer]);
|
|
88
|
+
|
|
89
|
+
// Cleanup abort controller on unmount
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
return () => {
|
|
92
|
+
if (abortRef.current) {
|
|
93
|
+
abortRef.current.abort();
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
return { data, isLoading, error, refresh: fetchDeadCode };
|
|
99
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
export interface OutdatedPackage {
|
|
4
|
+
name: string;
|
|
5
|
+
current: string;
|
|
6
|
+
wanted: string;
|
|
7
|
+
latest: string;
|
|
8
|
+
type: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SecurityAdvisory {
|
|
12
|
+
severity: string;
|
|
13
|
+
count: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface DependenciesData {
|
|
17
|
+
success: boolean;
|
|
18
|
+
target_path: string;
|
|
19
|
+
outdated: OutdatedPackage[];
|
|
20
|
+
advisories: SecurityAdvisory[];
|
|
21
|
+
error?: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseDependenciesOptions {
|
|
25
|
+
path?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UseDependenciesReturn {
|
|
29
|
+
data: DependenciesData | null;
|
|
30
|
+
isLoading: boolean;
|
|
31
|
+
error: Error | null;
|
|
32
|
+
refresh: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useDependencies(options: UseDependenciesOptions): UseDependenciesReturn {
|
|
36
|
+
const [data, setData] = useState<DependenciesData | null>(null);
|
|
37
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
38
|
+
const [error, setError] = useState<Error | null>(null);
|
|
39
|
+
const abortRef = useRef<AbortController | null>(null);
|
|
40
|
+
|
|
41
|
+
const fetchDependencies = useCallback(() => {
|
|
42
|
+
if (abortRef.current) {
|
|
43
|
+
abortRef.current.abort();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
abortRef.current = controller;
|
|
48
|
+
|
|
49
|
+
setIsLoading(true);
|
|
50
|
+
setError(null);
|
|
51
|
+
|
|
52
|
+
const params = new URLSearchParams();
|
|
53
|
+
if (options.path) params.set('path', options.path);
|
|
54
|
+
|
|
55
|
+
fetch(`/api/dependencies?${params}`, { signal: controller.signal })
|
|
56
|
+
.then((res) => {
|
|
57
|
+
if (!res.ok) {
|
|
58
|
+
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
|
59
|
+
}
|
|
60
|
+
return res.json();
|
|
61
|
+
})
|
|
62
|
+
.then((json: DependenciesData) => {
|
|
63
|
+
setData(json);
|
|
64
|
+
setIsLoading(false);
|
|
65
|
+
})
|
|
66
|
+
.catch((err) => {
|
|
67
|
+
if (err.name === 'AbortError') return;
|
|
68
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
69
|
+
setIsLoading(false);
|
|
70
|
+
});
|
|
71
|
+
}, [options.path]);
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
return () => {
|
|
75
|
+
if (abortRef.current) {
|
|
76
|
+
abortRef.current.abort();
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
}, []);
|
|
80
|
+
|
|
81
|
+
return { data, isLoading, error, refresh: fetchDependencies };
|
|
82
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useDiffs Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for subscribing to diff data via WebSocket.
|
|
5
|
+
* Story MSSCI-12717 - React Migration
|
|
6
|
+
* Story MSSCI-14238 - Git-based diffs (replaces OTEL-based extraction)
|
|
7
|
+
*
|
|
8
|
+
* IPC DEPRECATED - Now uses WebSocket /ws/diffs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
12
|
+
|
|
13
|
+
export interface DiffData {
|
|
14
|
+
id?: string;
|
|
15
|
+
path: string;
|
|
16
|
+
/** @deprecated Use `diff` field for raw git diff content */
|
|
17
|
+
original: string;
|
|
18
|
+
/** @deprecated Use `diff` field for raw git diff content */
|
|
19
|
+
modified: string;
|
|
20
|
+
/** Raw git diff output (MSSCI-14238) */
|
|
21
|
+
diff?: string;
|
|
22
|
+
toolName: string;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
/** File status from git (MSSCI-14238) */
|
|
25
|
+
status?: 'modified' | 'added' | 'deleted' | 'renamed';
|
|
26
|
+
/** Line additions count (MSSCI-14238) */
|
|
27
|
+
additions?: number;
|
|
28
|
+
/** Line deletions count (MSSCI-14238) */
|
|
29
|
+
deletions?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface UseDiffsResult {
|
|
33
|
+
diffs: DiffData[];
|
|
34
|
+
selectedDiff: DiffData | null;
|
|
35
|
+
selectDiff: (path: string) => void;
|
|
36
|
+
clearDiffs: () => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const WS_RECONNECT_DELAY = 2000;
|
|
40
|
+
|
|
41
|
+
export function useDiffs(): UseDiffsResult {
|
|
42
|
+
const [diffs, setDiffs] = useState<DiffData[]>([]);
|
|
43
|
+
const [selectedDiff, setSelectedDiff] = useState<DiffData | null>(null);
|
|
44
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
48
|
+
let mounted = true;
|
|
49
|
+
|
|
50
|
+
const connect = () => {
|
|
51
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
52
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/diffs`);
|
|
53
|
+
wsRef.current = ws;
|
|
54
|
+
|
|
55
|
+
ws.onopen = () => {
|
|
56
|
+
console.log('[useDiffs] WebSocket connected');
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
ws.onmessage = (event) => {
|
|
60
|
+
try {
|
|
61
|
+
const data = JSON.parse(event.data);
|
|
62
|
+
|
|
63
|
+
if (data.type === 'init') {
|
|
64
|
+
// Initial load of existing diffs
|
|
65
|
+
const initialDiffs = (data.diffs || []) as DiffData[];
|
|
66
|
+
setDiffs(initialDiffs);
|
|
67
|
+
if (initialDiffs.length > 0) {
|
|
68
|
+
setSelectedDiff(initialDiffs[initialDiffs.length - 1]);
|
|
69
|
+
}
|
|
70
|
+
} else if (data.type === 'diff') {
|
|
71
|
+
// New diff arrived
|
|
72
|
+
const diffData = data.diff as DiffData;
|
|
73
|
+
setDiffs(prev => {
|
|
74
|
+
// Update or add diff
|
|
75
|
+
const existing = prev.findIndex(d => d.path === diffData.path);
|
|
76
|
+
if (existing >= 0) {
|
|
77
|
+
const updated = [...prev];
|
|
78
|
+
updated[existing] = diffData;
|
|
79
|
+
return updated;
|
|
80
|
+
}
|
|
81
|
+
return [...prev, diffData];
|
|
82
|
+
});
|
|
83
|
+
// Auto-select new diff
|
|
84
|
+
setSelectedDiff(diffData);
|
|
85
|
+
} else if (data.type === 'refresh') {
|
|
86
|
+
// Full refresh of diffs (MSSCI-14238: git cache refresh)
|
|
87
|
+
const refreshedDiffs = (data.diffs || []) as DiffData[];
|
|
88
|
+
setDiffs(refreshedDiffs);
|
|
89
|
+
// Keep current selection if it still exists
|
|
90
|
+
if (refreshedDiffs.length > 0) {
|
|
91
|
+
setSelectedDiff(prev => {
|
|
92
|
+
if (prev) {
|
|
93
|
+
const stillExists = refreshedDiffs.find(d => d.path === prev.path);
|
|
94
|
+
if (stillExists) return stillExists;
|
|
95
|
+
}
|
|
96
|
+
return refreshedDiffs[refreshedDiffs.length - 1];
|
|
97
|
+
});
|
|
98
|
+
} else {
|
|
99
|
+
setSelectedDiff(null);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error('[useDiffs] Failed to parse message:', err);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
ws.onclose = () => {
|
|
108
|
+
if (mounted) {
|
|
109
|
+
reconnectTimer = setTimeout(connect, WS_RECONNECT_DELAY);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
ws.onerror = (err) => {
|
|
114
|
+
console.error('[useDiffs] WebSocket error:', err);
|
|
115
|
+
ws.close();
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
connect();
|
|
120
|
+
|
|
121
|
+
return () => {
|
|
122
|
+
mounted = false;
|
|
123
|
+
if (reconnectTimer) clearTimeout(reconnectTimer);
|
|
124
|
+
wsRef.current?.close();
|
|
125
|
+
};
|
|
126
|
+
}, []);
|
|
127
|
+
|
|
128
|
+
const selectDiff = useCallback((path: string) => {
|
|
129
|
+
const diff = diffs.find(d => d.path === path);
|
|
130
|
+
setSelectedDiff(diff || null);
|
|
131
|
+
}, [diffs]);
|
|
132
|
+
|
|
133
|
+
const clearDiffs = useCallback(() => {
|
|
134
|
+
setDiffs([]);
|
|
135
|
+
setSelectedDiff(null);
|
|
136
|
+
// Send clear message to server
|
|
137
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
138
|
+
wsRef.current.send(JSON.stringify({ type: 'clear' }));
|
|
139
|
+
}
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
return { diffs, selectedDiff, selectDiff, clearDiffs };
|
|
143
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFileBrowser Hook
|
|
3
|
+
*
|
|
4
|
+
* Fetches directory listings from /api/files for the full file tree.
|
|
5
|
+
* Lazy-loads subdirectories on demand.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from 'react';
|
|
9
|
+
|
|
10
|
+
export interface DirectoryEntry {
|
|
11
|
+
name: string;
|
|
12
|
+
path: string;
|
|
13
|
+
type: 'file' | 'directory';
|
|
14
|
+
isModified?: boolean;
|
|
15
|
+
size?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DirectoryListing {
|
|
19
|
+
path: string;
|
|
20
|
+
entries: DirectoryEntry[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface DirectoryCache {
|
|
24
|
+
[path: string]: DirectoryEntry[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface UseFileBrowserResult {
|
|
28
|
+
cache: DirectoryCache;
|
|
29
|
+
loading: Set<string>;
|
|
30
|
+
error: string | null;
|
|
31
|
+
fetchDirectory: (dirPath: string) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function useFileBrowser(): UseFileBrowserResult {
|
|
35
|
+
const [cache, setCache] = useState<DirectoryCache>({});
|
|
36
|
+
const [loading, setLoading] = useState<Set<string>>(new Set());
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
const fetchDirectory = useCallback(async (dirPath: string) => {
|
|
40
|
+
// Already cached or loading
|
|
41
|
+
if (cache[dirPath] || loading.has(dirPath)) return;
|
|
42
|
+
|
|
43
|
+
setLoading(prev => new Set(prev).add(dirPath));
|
|
44
|
+
setError(null);
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const params = dirPath ? `?path=${encodeURIComponent(dirPath)}` : '';
|
|
48
|
+
const res = await fetch(`/api/files${params}`);
|
|
49
|
+
if (!res.ok) throw new Error(`Failed to list directory: ${res.statusText}`);
|
|
50
|
+
const json = await res.json();
|
|
51
|
+
// API may return { entries: [...] } or a raw array
|
|
52
|
+
const entries: DirectoryEntry[] = Array.isArray(json) ? json : (json.entries ?? []);
|
|
53
|
+
|
|
54
|
+
// Sort: directories first, then files, alphabetical within each
|
|
55
|
+
const sorted = entries.sort((a, b) => {
|
|
56
|
+
if (a.type !== b.type) return a.type === 'directory' ? -1 : 1;
|
|
57
|
+
return a.name.localeCompare(b.name);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
setCache(prev => ({ ...prev, [dirPath || '__root__']: sorted }));
|
|
61
|
+
} catch (err) {
|
|
62
|
+
setError(err instanceof Error ? err.message : 'Failed to load directory');
|
|
63
|
+
} finally {
|
|
64
|
+
setLoading(prev => {
|
|
65
|
+
const next = new Set(prev);
|
|
66
|
+
next.delete(dirPath);
|
|
67
|
+
return next;
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}, [cache, loading]);
|
|
71
|
+
|
|
72
|
+
return { cache, loading, error, fetchDirectory };
|
|
73
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useFocusPanel Hook
|
|
3
|
+
*
|
|
4
|
+
* React hook for handling panel focus events via /ws/focus WebSocket.
|
|
5
|
+
* Story MSSCI-14977 - BikeShow client layout stash/restore on panel focus
|
|
6
|
+
* Epic 104: /bc CLI Panel Focus
|
|
7
|
+
*
|
|
8
|
+
* Listens to /ws/focus WebSocket for focus events.
|
|
9
|
+
*
|
|
10
|
+
* Multi-group layouts (Cyclist): maximizeGroup/exitMaximizedGroup
|
|
11
|
+
* Single-group layouts (BikeRack): activate target tab, stash previous
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { useState, useEffect, useRef } from 'react';
|
|
15
|
+
import type { DockviewApi } from 'dockview-react';
|
|
16
|
+
|
|
17
|
+
export interface UseFocusPanelResult {
|
|
18
|
+
/** Currently focused panel ID, or null if not in focus mode */
|
|
19
|
+
focusedPanel: string | null;
|
|
20
|
+
/** Whether the workspace is currently in single-panel focus mode */
|
|
21
|
+
isInFocusMode: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** WebSocket message format from /ws/focus */
|
|
25
|
+
interface FocusMessage {
|
|
26
|
+
type: 'init' | 'update';
|
|
27
|
+
focus: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hook for managing panel focus mode.
|
|
32
|
+
*
|
|
33
|
+
* @param api - Dockview API instance for layout manipulation, or null if not ready
|
|
34
|
+
* @returns Focus state including current focused panel and mode
|
|
35
|
+
*/
|
|
36
|
+
export function useFocusPanel(api: DockviewApi | null): UseFocusPanelResult {
|
|
37
|
+
const [focusedPanel, setFocusedPanel] = useState<string | null>(null);
|
|
38
|
+
const [isInFocusMode, setIsInFocusMode] = useState(false);
|
|
39
|
+
|
|
40
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
41
|
+
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
|
42
|
+
const isMountedRef = useRef(true);
|
|
43
|
+
|
|
44
|
+
// Refs to access latest values inside WebSocket callbacks without re-creating the effect
|
|
45
|
+
const apiRef = useRef(api);
|
|
46
|
+
apiRef.current = api;
|
|
47
|
+
|
|
48
|
+
// Stash the previously active panel ID for single-group reset
|
|
49
|
+
const previousActivePanelRef = useRef<string | null>(null);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
isMountedRef.current = true;
|
|
53
|
+
|
|
54
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
55
|
+
const wsUrl = `${protocol}//${window.location.host}/ws/focus`;
|
|
56
|
+
|
|
57
|
+
const handleFocusChange = (focus: string | null) => {
|
|
58
|
+
const currentApi = apiRef.current;
|
|
59
|
+
if (!currentApi) return;
|
|
60
|
+
|
|
61
|
+
if (focus !== null) {
|
|
62
|
+
const panel = currentApi.getPanel(focus);
|
|
63
|
+
if (!panel) return;
|
|
64
|
+
|
|
65
|
+
const isMultiGroup = currentApi.groups.length > 1;
|
|
66
|
+
|
|
67
|
+
if (isMultiGroup) {
|
|
68
|
+
// Multi-group (Cyclist): maximize the target panel's group
|
|
69
|
+
currentApi.maximizeGroup(panel);
|
|
70
|
+
} else {
|
|
71
|
+
// Single-group (BikeRack): stash current active, switch tab
|
|
72
|
+
if (!previousActivePanelRef.current) {
|
|
73
|
+
previousActivePanelRef.current = currentApi.activePanel?.id ?? null;
|
|
74
|
+
}
|
|
75
|
+
panel.api.setActive();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setIsInFocusMode(true);
|
|
79
|
+
setFocusedPanel(focus);
|
|
80
|
+
} else {
|
|
81
|
+
// Reset
|
|
82
|
+
const isMaximized = currentApi.hasMaximizedGroup();
|
|
83
|
+
|
|
84
|
+
if (isMaximized) {
|
|
85
|
+
// Multi-group: exit maximize
|
|
86
|
+
currentApi.exitMaximizedGroup();
|
|
87
|
+
} else if (previousActivePanelRef.current) {
|
|
88
|
+
// Single-group: restore previous active tab
|
|
89
|
+
const prev = currentApi.getPanel(previousActivePanelRef.current);
|
|
90
|
+
if (prev) prev.api.setActive();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
previousActivePanelRef.current = null;
|
|
94
|
+
setIsInFocusMode(false);
|
|
95
|
+
setFocusedPanel(null);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const connect = () => {
|
|
100
|
+
if (!isMountedRef.current) return;
|
|
101
|
+
|
|
102
|
+
wsRef.current = new WebSocket(wsUrl);
|
|
103
|
+
|
|
104
|
+
wsRef.current.onmessage = (event: MessageEvent) => {
|
|
105
|
+
try {
|
|
106
|
+
const msg = JSON.parse(event.data) as FocusMessage;
|
|
107
|
+
if (msg.type === 'update') {
|
|
108
|
+
// Live /bc commands — apply focus change immediately
|
|
109
|
+
handleFocusChange(msg.focus);
|
|
110
|
+
}
|
|
111
|
+
// 'init' messages are ignored — focus is ephemeral, not persistent.
|
|
112
|
+
// Stale focus values in config would destroy the layout on page load.
|
|
113
|
+
} catch {
|
|
114
|
+
// Ignore malformed messages
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
wsRef.current.onclose = () => {
|
|
119
|
+
reconnectTimeoutRef.current = setTimeout(connect, 2000);
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
connect();
|
|
124
|
+
|
|
125
|
+
return () => {
|
|
126
|
+
isMountedRef.current = false;
|
|
127
|
+
if (reconnectTimeoutRef.current) {
|
|
128
|
+
clearTimeout(reconnectTimeoutRef.current);
|
|
129
|
+
}
|
|
130
|
+
if (wsRef.current) {
|
|
131
|
+
wsRef.current.close();
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
return { focusedPanel, isInFocusMode };
|
|
137
|
+
}
|