@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,749 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DockviewWorkspace - Dockview-based panel management for Cyclist
|
|
3
|
+
*
|
|
4
|
+
* Story MSSCI-14001: Replace DockingWorkspace with Dockview
|
|
5
|
+
* Epic: epic-76 (Dockview Panel Migration)
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Three-region layout (left sidebar, center sacred, right sidebar)
|
|
9
|
+
* - Message view is sacred (fixed center, cannot be closed or moved)
|
|
10
|
+
* - Tabbed panels in sidebars with drag-and-drop
|
|
11
|
+
* - Responsive breakpoints (auto-collapse at <1024px)
|
|
12
|
+
* - Layout persistence via native Dockview toJSON/fromJSON
|
|
13
|
+
* - Theme integration via CSS custom properties
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import React, { useEffect, useRef, useCallback, useState } from 'react';
|
|
17
|
+
import { Button } from '@/components/ui/button';
|
|
18
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
19
|
+
import {
|
|
20
|
+
DockviewReact,
|
|
21
|
+
DockviewReadyEvent,
|
|
22
|
+
IDockviewPanelProps,
|
|
23
|
+
DockviewApi,
|
|
24
|
+
IDockviewPanel,
|
|
25
|
+
SerializedDockview,
|
|
26
|
+
DockviewDefaultTab,
|
|
27
|
+
} from 'dockview-react';
|
|
28
|
+
import 'dockview-react/dist/styles/dockview.css';
|
|
29
|
+
import { ErrorBoundary } from './ErrorBoundary';
|
|
30
|
+
import { panelRegistry, type PanelComponent } from './panel-registry';
|
|
31
|
+
import { useResponsiveLayout, MIN_DIMENSIONS, SIDEBAR_WIDTHS } from '../hooks/useResponsiveLayout';
|
|
32
|
+
import { useFocusPanel } from '../hooks/useFocusPanel.js';
|
|
33
|
+
import '../styles/dockview-theme.css';
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Panel Inventory - All available panels in Cyclist
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
export const PANEL_INVENTORY = {
|
|
40
|
+
// Left sidebar panels
|
|
41
|
+
GIT: 'git',
|
|
42
|
+
DIFFS: 'diffs',
|
|
43
|
+
DEBUG: 'debug',
|
|
44
|
+
AUDIT_LOG: 'audit-log',
|
|
45
|
+
// Center panel (sacred)
|
|
46
|
+
MESSAGE: 'message',
|
|
47
|
+
// Right sidebar panels
|
|
48
|
+
SPRINT: 'sprint',
|
|
49
|
+
WORKFLOW: 'workflow',
|
|
50
|
+
AC: 'ac',
|
|
51
|
+
TODO: 'todo',
|
|
52
|
+
BACKGROUND: 'background',
|
|
53
|
+
SETTINGS: 'settings',
|
|
54
|
+
PROGRESS: 'progress',
|
|
55
|
+
TANDEM: 'tandem',
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
export type PanelId = typeof PANEL_INVENTORY[keyof typeof PANEL_INVENTORY];
|
|
59
|
+
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Panel Component Registry
|
|
62
|
+
// =============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register a panel component by ID
|
|
66
|
+
*/
|
|
67
|
+
export function registerPanelComponent(id: string, component: PanelComponent): void {
|
|
68
|
+
panelRegistry.set(id, component);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Global API Reference
|
|
73
|
+
// =============================================================================
|
|
74
|
+
|
|
75
|
+
let dockviewApiRef: DockviewApi | null = null;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get the Dockview API instance for external access
|
|
79
|
+
*/
|
|
80
|
+
export function getDockviewApi(): DockviewApi | null {
|
|
81
|
+
return dockviewApiRef;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Panel group definitions (needed for restore logic)
|
|
85
|
+
// Exported so layout persistence can merge missing panels
|
|
86
|
+
export const LEFT_SIDEBAR_PANELS = [PANEL_INVENTORY.GIT, PANEL_INVENTORY.DIFFS, PANEL_INVENTORY.DEBUG, PANEL_INVENTORY.AUDIT_LOG] as const;
|
|
87
|
+
export const RIGHT_SIDEBAR_PANELS = [
|
|
88
|
+
PANEL_INVENTORY.SPRINT,
|
|
89
|
+
PANEL_INVENTORY.WORKFLOW,
|
|
90
|
+
PANEL_INVENTORY.AC,
|
|
91
|
+
PANEL_INVENTORY.TODO,
|
|
92
|
+
PANEL_INVENTORY.BACKGROUND,
|
|
93
|
+
PANEL_INVENTORY.SETTINGS,
|
|
94
|
+
PANEL_INVENTORY.TANDEM,
|
|
95
|
+
] as const;
|
|
96
|
+
|
|
97
|
+
// Title Case display names for tab headers (AC4: Story 75-5)
|
|
98
|
+
const PANEL_TITLES: Record<string, string> = {
|
|
99
|
+
git: 'Git',
|
|
100
|
+
diffs: 'Diffs',
|
|
101
|
+
debug: 'Debug',
|
|
102
|
+
'audit-log': 'Audit Log',
|
|
103
|
+
message: 'Message',
|
|
104
|
+
sprint: 'Sprint',
|
|
105
|
+
workflow: 'Workflow',
|
|
106
|
+
ac: 'AC',
|
|
107
|
+
todo: 'Todo',
|
|
108
|
+
background: 'Subagents',
|
|
109
|
+
settings: 'Settings',
|
|
110
|
+
progress: 'Progress',
|
|
111
|
+
tandem: 'Tandem',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Track closed panels for restoration
|
|
115
|
+
const closedPanels: Set<string> = new Set();
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get list of closed panels that can be restored
|
|
119
|
+
*/
|
|
120
|
+
export function getClosedPanels(): string[] {
|
|
121
|
+
return Array.from(closedPanels);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Restore a previously closed panel
|
|
126
|
+
*/
|
|
127
|
+
export function restorePanel(panelId: string): boolean {
|
|
128
|
+
const api = dockviewApiRef;
|
|
129
|
+
if (!api) return false;
|
|
130
|
+
|
|
131
|
+
// If panel already exists in Dockview, nothing to restore
|
|
132
|
+
if (api.getPanel(panelId)) return false;
|
|
133
|
+
|
|
134
|
+
// Determine which group to add it to
|
|
135
|
+
const isLeftPanel = LEFT_SIDEBAR_PANELS.includes(panelId as any);
|
|
136
|
+
const isRightPanel = RIGHT_SIDEBAR_PANELS.includes(panelId as any);
|
|
137
|
+
|
|
138
|
+
// Find a reference panel in the appropriate group
|
|
139
|
+
let referencePanel: IDockviewPanel | undefined;
|
|
140
|
+
|
|
141
|
+
if (isLeftPanel) {
|
|
142
|
+
for (const id of LEFT_SIDEBAR_PANELS) {
|
|
143
|
+
const panel = api.getPanel(id);
|
|
144
|
+
if (panel) {
|
|
145
|
+
referencePanel = panel;
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
} else if (isRightPanel) {
|
|
150
|
+
for (const id of RIGHT_SIDEBAR_PANELS) {
|
|
151
|
+
const panel = api.getPanel(id);
|
|
152
|
+
if (panel) {
|
|
153
|
+
referencePanel = panel;
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Add the panel back
|
|
160
|
+
api.addPanel({
|
|
161
|
+
id: panelId,
|
|
162
|
+
component: 'PanelAdapter',
|
|
163
|
+
params: { panelId },
|
|
164
|
+
position: referencePanel ? { referencePanel: referencePanel.id } : undefined,
|
|
165
|
+
title: PANEL_TITLES[panelId] || panelId,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
closedPanels.delete(panelId);
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// =============================================================================
|
|
173
|
+
// Panel Adapter - Wraps existing panels for Dockview
|
|
174
|
+
// =============================================================================
|
|
175
|
+
|
|
176
|
+
interface PanelAdapterParams {
|
|
177
|
+
panelId: string;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function PanelAdapter({ params }: IDockviewPanelProps<PanelAdapterParams>): React.ReactElement | null {
|
|
181
|
+
const Component = panelRegistry.get(params.panelId);
|
|
182
|
+
|
|
183
|
+
if (!Component) {
|
|
184
|
+
console.warn(`[DockviewWorkspace] No component registered for panel: ${params.panelId}`);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<div data-testid={`panel-${params.panelId}`} className="dockview-panel-content">
|
|
190
|
+
<ErrorBoundary panelName={params.panelId}>
|
|
191
|
+
<div className="error-boundary-wrapper">
|
|
192
|
+
<Component />
|
|
193
|
+
</div>
|
|
194
|
+
</ErrorBoundary>
|
|
195
|
+
</div>
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// Types for Layout Persistence
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
// Re-export SerializedDockview for external use
|
|
204
|
+
export type { SerializedDockview };
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @deprecated Use SerializedDockview from dockview-react instead
|
|
208
|
+
* Kept for backward compatibility during migration
|
|
209
|
+
*/
|
|
210
|
+
export interface WorkspaceLayoutConfig {
|
|
211
|
+
leftSidebar: {
|
|
212
|
+
panels: string[];
|
|
213
|
+
width: number;
|
|
214
|
+
collapsed: boolean;
|
|
215
|
+
activePanel?: string;
|
|
216
|
+
};
|
|
217
|
+
center: {
|
|
218
|
+
panels: string[];
|
|
219
|
+
locked: boolean;
|
|
220
|
+
};
|
|
221
|
+
rightSidebar: {
|
|
222
|
+
panels: string[];
|
|
223
|
+
width: number;
|
|
224
|
+
collapsed: boolean;
|
|
225
|
+
activePanel?: string;
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create default workspace layout using native Dockview format
|
|
231
|
+
* This builds the initial three-region layout programmatically
|
|
232
|
+
*/
|
|
233
|
+
export function createDefaultDockviewLayout(): SerializedDockview {
|
|
234
|
+
// Build the default layout structure that matches what toJSON() produces
|
|
235
|
+
// This creates: [left sidebar] | [center message] | [right sidebar]
|
|
236
|
+
return {
|
|
237
|
+
grid: {
|
|
238
|
+
root: {
|
|
239
|
+
type: 'branch',
|
|
240
|
+
data: [
|
|
241
|
+
// Left sidebar group
|
|
242
|
+
{
|
|
243
|
+
type: 'leaf',
|
|
244
|
+
data: {
|
|
245
|
+
views: LEFT_SIDEBAR_PANELS.map(id => id),
|
|
246
|
+
activeView: LEFT_SIDEBAR_PANELS[0],
|
|
247
|
+
id: 'left-sidebar',
|
|
248
|
+
},
|
|
249
|
+
size: SIDEBAR_WIDTHS.medium,
|
|
250
|
+
},
|
|
251
|
+
// Center (message) group
|
|
252
|
+
{
|
|
253
|
+
type: 'leaf',
|
|
254
|
+
data: {
|
|
255
|
+
views: [PANEL_INVENTORY.MESSAGE],
|
|
256
|
+
activeView: PANEL_INVENTORY.MESSAGE,
|
|
257
|
+
id: 'center',
|
|
258
|
+
hideHeader: true,
|
|
259
|
+
},
|
|
260
|
+
size: 600, // Center takes remaining space
|
|
261
|
+
},
|
|
262
|
+
// Right sidebar group
|
|
263
|
+
{
|
|
264
|
+
type: 'leaf',
|
|
265
|
+
data: {
|
|
266
|
+
views: [...RIGHT_SIDEBAR_PANELS],
|
|
267
|
+
activeView: RIGHT_SIDEBAR_PANELS[0],
|
|
268
|
+
id: 'right-sidebar',
|
|
269
|
+
},
|
|
270
|
+
size: SIDEBAR_WIDTHS.medium,
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
size: 800, // Will be overridden by actual container height
|
|
274
|
+
},
|
|
275
|
+
width: 1200,
|
|
276
|
+
height: 800,
|
|
277
|
+
orientation: 'HORIZONTAL',
|
|
278
|
+
},
|
|
279
|
+
panels: {
|
|
280
|
+
// Left sidebar panels
|
|
281
|
+
...Object.fromEntries(LEFT_SIDEBAR_PANELS.map(id => [id, {
|
|
282
|
+
id,
|
|
283
|
+
contentComponent: 'PanelAdapter',
|
|
284
|
+
title: PANEL_TITLES[id] || id,
|
|
285
|
+
params: { panelId: id },
|
|
286
|
+
}])),
|
|
287
|
+
// Center panel
|
|
288
|
+
[PANEL_INVENTORY.MESSAGE]: {
|
|
289
|
+
id: PANEL_INVENTORY.MESSAGE,
|
|
290
|
+
contentComponent: 'PanelAdapter',
|
|
291
|
+
title: PANEL_TITLES[PANEL_INVENTORY.MESSAGE],
|
|
292
|
+
params: { panelId: PANEL_INVENTORY.MESSAGE },
|
|
293
|
+
},
|
|
294
|
+
// Right sidebar panels
|
|
295
|
+
...Object.fromEntries(RIGHT_SIDEBAR_PANELS.map(id => [id, {
|
|
296
|
+
id,
|
|
297
|
+
contentComponent: 'PanelAdapter',
|
|
298
|
+
title: PANEL_TITLES[id] || id,
|
|
299
|
+
params: { panelId: id },
|
|
300
|
+
}])),
|
|
301
|
+
},
|
|
302
|
+
activeGroup: 'center',
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @deprecated Use createDefaultDockviewLayout instead
|
|
308
|
+
* Kept for backward compatibility
|
|
309
|
+
*/
|
|
310
|
+
export function createWorkspaceLayout(): WorkspaceLayoutConfig {
|
|
311
|
+
return {
|
|
312
|
+
leftSidebar: {
|
|
313
|
+
panels: [...LEFT_SIDEBAR_PANELS],
|
|
314
|
+
width: SIDEBAR_WIDTHS.medium,
|
|
315
|
+
collapsed: false,
|
|
316
|
+
},
|
|
317
|
+
center: {
|
|
318
|
+
panels: [PANEL_INVENTORY.MESSAGE],
|
|
319
|
+
locked: true,
|
|
320
|
+
},
|
|
321
|
+
rightSidebar: {
|
|
322
|
+
panels: [...RIGHT_SIDEBAR_PANELS],
|
|
323
|
+
width: SIDEBAR_WIDTHS.medium,
|
|
324
|
+
collapsed: false,
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// =============================================================================
|
|
330
|
+
// Layout Migration (MSSCI-14188)
|
|
331
|
+
// =============================================================================
|
|
332
|
+
|
|
333
|
+
interface SimplifiedLayoutPanel {
|
|
334
|
+
id: string;
|
|
335
|
+
position?: string;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
interface SimplifiedLayout {
|
|
339
|
+
panels: SimplifiedLayoutPanel[];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Migrate old layout format that had "progress" panel to new format with
|
|
344
|
+
* separate workflow, ac, and todo panels.
|
|
345
|
+
*
|
|
346
|
+
* Story: MSSCI-14188 - Split Progress panel into Workflow, AC, and Todo panels
|
|
347
|
+
*/
|
|
348
|
+
export function migrateLayout(layout: SimplifiedLayout | null | undefined): SimplifiedLayout {
|
|
349
|
+
// Handle null/undefined gracefully
|
|
350
|
+
if (!layout || !layout.panels) {
|
|
351
|
+
return { panels: [] };
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check if layout already has new panels (no migration needed)
|
|
355
|
+
const hasNewPanels = layout.panels.some(
|
|
356
|
+
(p) => p.id === 'workflow' || p.id === 'ac' || p.id === 'todo'
|
|
357
|
+
);
|
|
358
|
+
if (hasNewPanels) {
|
|
359
|
+
// Already migrated, just filter out any stale 'progress' panel
|
|
360
|
+
return {
|
|
361
|
+
panels: layout.panels.filter((p) => p.id !== 'progress'),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check if layout has old 'progress' panel that needs migration
|
|
366
|
+
const progressIndex = layout.panels.findIndex((p) => p.id === 'progress');
|
|
367
|
+
if (progressIndex === -1) {
|
|
368
|
+
// No progress panel to migrate
|
|
369
|
+
return layout;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// Get the position of the old progress panel
|
|
373
|
+
const progressPanel = layout.panels[progressIndex];
|
|
374
|
+
const position = progressPanel.position || 'right';
|
|
375
|
+
|
|
376
|
+
// Build new panels array with progress replaced by workflow, ac, todo
|
|
377
|
+
const newPanels: SimplifiedLayoutPanel[] = [];
|
|
378
|
+
|
|
379
|
+
for (let i = 0; i < layout.panels.length; i++) {
|
|
380
|
+
if (i === progressIndex) {
|
|
381
|
+
// Replace progress with three new panels at the same position
|
|
382
|
+
newPanels.push({ id: 'workflow', position });
|
|
383
|
+
newPanels.push({ id: 'ac', position });
|
|
384
|
+
newPanels.push({ id: 'todo', position });
|
|
385
|
+
} else {
|
|
386
|
+
newPanels.push(layout.panels[i]);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
return { panels: newPanels };
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// =============================================================================
|
|
394
|
+
// DockviewWorkspace Component
|
|
395
|
+
// =============================================================================
|
|
396
|
+
|
|
397
|
+
export interface DockviewWorkspaceProps {
|
|
398
|
+
/** Native Dockview serialized layout (preferred) */
|
|
399
|
+
initialLayout?: SerializedDockview;
|
|
400
|
+
/** Callback when layout changes - receives native Dockview format */
|
|
401
|
+
onLayoutChange?: (layout: SerializedDockview) => void;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function DockviewWorkspace({
|
|
405
|
+
initialLayout,
|
|
406
|
+
onLayoutChange,
|
|
407
|
+
}: DockviewWorkspaceProps): React.ReactElement {
|
|
408
|
+
const apiRef = useRef<DockviewApi | null>(null);
|
|
409
|
+
const [dockviewApi, setDockviewApi] = useState<DockviewApi | null>(null);
|
|
410
|
+
const { isSmall, isBelowMinimum, sidebarWidth } = useResponsiveLayout();
|
|
411
|
+
const [isReady, setIsReady] = useState(false);
|
|
412
|
+
const [closedPanelsList, setClosedPanelsList] = useState<string[]>([]);
|
|
413
|
+
const [showRestoreMenu, setShowRestoreMenu] = useState(false);
|
|
414
|
+
const saveTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
415
|
+
// Panel focus mode — stash/restore layout on /bc CLI events
|
|
416
|
+
useFocusPanel(dockviewApi);
|
|
417
|
+
|
|
418
|
+
// Track if responsive effect should apply - skip on initial load to respect saved collapsed state
|
|
419
|
+
const hasAppliedInitialLayout = useRef(false);
|
|
420
|
+
const previousIsSmall = useRef<boolean | null>(null);
|
|
421
|
+
|
|
422
|
+
// Update closed panels list when panels change
|
|
423
|
+
const updateClosedPanelsList = useCallback(() => {
|
|
424
|
+
setClosedPanelsList(Array.from(closedPanels));
|
|
425
|
+
}, []);
|
|
426
|
+
|
|
427
|
+
// Handle Dockview ready event
|
|
428
|
+
const onReady = useCallback((event: DockviewReadyEvent) => {
|
|
429
|
+
const api = event.api;
|
|
430
|
+
apiRef.current = api;
|
|
431
|
+
dockviewApiRef = api;
|
|
432
|
+
setDockviewApi(api);
|
|
433
|
+
|
|
434
|
+
// Use native fromJSON if we have a saved layout, otherwise build default
|
|
435
|
+
if (initialLayout && initialLayout.grid && initialLayout.panels) {
|
|
436
|
+
// Restore from native Dockview serialized format
|
|
437
|
+
try {
|
|
438
|
+
api.fromJSON(initialLayout);
|
|
439
|
+
|
|
440
|
+
// After restoring, lock the message panel's group and hide its tab bar
|
|
441
|
+
const messagePanel = api.getPanel(PANEL_INVENTORY.MESSAGE);
|
|
442
|
+
if (messagePanel?.group) {
|
|
443
|
+
messagePanel.group.locked = 'no-drop-target';
|
|
444
|
+
messagePanel.group.model.header.hidden = true;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
setIsReady(true);
|
|
448
|
+
return;
|
|
449
|
+
} catch (err) {
|
|
450
|
+
console.warn('[DockviewWorkspace] Failed to restore layout from JSON, building default:', err);
|
|
451
|
+
// Fall through to build default layout
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Build default layout programmatically (for first-time users or failed restore)
|
|
456
|
+
// Add first panel to left sidebar (creates the first group)
|
|
457
|
+
const leftFirstPanel = api.addPanel({
|
|
458
|
+
id: LEFT_SIDEBAR_PANELS[0],
|
|
459
|
+
component: 'PanelAdapter',
|
|
460
|
+
params: { panelId: LEFT_SIDEBAR_PANELS[0] },
|
|
461
|
+
title: PANEL_TITLES[LEFT_SIDEBAR_PANELS[0]],
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// Add remaining left sidebar panels to the same group
|
|
465
|
+
for (let i = 1; i < LEFT_SIDEBAR_PANELS.length; i++) {
|
|
466
|
+
const panelId = LEFT_SIDEBAR_PANELS[i];
|
|
467
|
+
if (panelRegistry.has(panelId)) {
|
|
468
|
+
api.addPanel({
|
|
469
|
+
id: panelId,
|
|
470
|
+
component: 'PanelAdapter',
|
|
471
|
+
params: { panelId },
|
|
472
|
+
position: { referencePanel: leftFirstPanel.id },
|
|
473
|
+
title: PANEL_TITLES[panelId],
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Add message panel to center (creates new group to the right)
|
|
479
|
+
const messagePanel = api.addPanel({
|
|
480
|
+
id: PANEL_INVENTORY.MESSAGE,
|
|
481
|
+
component: 'PanelAdapter',
|
|
482
|
+
params: { panelId: PANEL_INVENTORY.MESSAGE },
|
|
483
|
+
position: { referencePanel: leftFirstPanel.id, direction: 'right' },
|
|
484
|
+
title: PANEL_TITLES[PANEL_INVENTORY.MESSAGE],
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
// Add first right sidebar panel (creates new group to the right of center)
|
|
488
|
+
const rightFirstPanel = api.addPanel({
|
|
489
|
+
id: RIGHT_SIDEBAR_PANELS[0],
|
|
490
|
+
component: 'PanelAdapter',
|
|
491
|
+
params: { panelId: RIGHT_SIDEBAR_PANELS[0] },
|
|
492
|
+
position: { referencePanel: messagePanel.id, direction: 'right' },
|
|
493
|
+
title: PANEL_TITLES[RIGHT_SIDEBAR_PANELS[0]],
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
// Add remaining right sidebar panels to the same group
|
|
497
|
+
for (let i = 1; i < RIGHT_SIDEBAR_PANELS.length; i++) {
|
|
498
|
+
const panelId = RIGHT_SIDEBAR_PANELS[i];
|
|
499
|
+
if (panelRegistry.has(panelId)) {
|
|
500
|
+
api.addPanel({
|
|
501
|
+
id: panelId,
|
|
502
|
+
component: 'PanelAdapter',
|
|
503
|
+
params: { panelId },
|
|
504
|
+
position: { referencePanel: rightFirstPanel.id },
|
|
505
|
+
title: PANEL_TITLES[panelId],
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Lock the center group - MessagePanel cannot be closed or moved
|
|
511
|
+
// Hide the tab bar so users can't accidentally close the message tab
|
|
512
|
+
if (messagePanel?.group) {
|
|
513
|
+
messagePanel.group.locked = 'no-drop-target';
|
|
514
|
+
messagePanel.group.model.header.hidden = true;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Set initial sidebar sizes
|
|
518
|
+
const leftGroup = leftFirstPanel.group;
|
|
519
|
+
const rightGroup = rightFirstPanel.group;
|
|
520
|
+
|
|
521
|
+
if (leftGroup) {
|
|
522
|
+
leftGroup.api.setSize({ width: SIDEBAR_WIDTHS.medium });
|
|
523
|
+
}
|
|
524
|
+
if (rightGroup) {
|
|
525
|
+
rightGroup.api.setSize({ width: SIDEBAR_WIDTHS.medium });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
setIsReady(true);
|
|
529
|
+
}, [initialLayout]);
|
|
530
|
+
|
|
531
|
+
// Handle layout changes for persistence using native toJSON
|
|
532
|
+
const handleLayoutChange = useCallback(() => {
|
|
533
|
+
const api = apiRef.current;
|
|
534
|
+
if (!api || !onLayoutChange) return;
|
|
535
|
+
|
|
536
|
+
// Never save empty layouts — prevents corruption loop
|
|
537
|
+
if (api.panels.length === 0) return;
|
|
538
|
+
|
|
539
|
+
// Debounce saves
|
|
540
|
+
if (saveTimeoutRef.current) {
|
|
541
|
+
clearTimeout(saveTimeoutRef.current);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
saveTimeoutRef.current = setTimeout(() => {
|
|
545
|
+
// Use native Dockview toJSON for complete layout serialization
|
|
546
|
+
const serializedLayout = api.toJSON();
|
|
547
|
+
// Double-check: don't persist if serialization produced empty panels
|
|
548
|
+
if (serializedLayout.panels && Object.keys(serializedLayout.panels).length > 0) {
|
|
549
|
+
onLayoutChange(serializedLayout);
|
|
550
|
+
}
|
|
551
|
+
}, 300);
|
|
552
|
+
}, [onLayoutChange]);
|
|
553
|
+
|
|
554
|
+
// Subscribe to layout changes and track closed panels
|
|
555
|
+
useEffect(() => {
|
|
556
|
+
const api = apiRef.current;
|
|
557
|
+
if (!api || !isReady) return;
|
|
558
|
+
|
|
559
|
+
const disposables = [
|
|
560
|
+
api.onDidLayoutChange(() => handleLayoutChange()),
|
|
561
|
+
api.onDidAddPanel((e) => {
|
|
562
|
+
// Panel restored, remove from closed set
|
|
563
|
+
const panelId = e?.panel?.id;
|
|
564
|
+
if (panelId) {
|
|
565
|
+
closedPanels.delete(panelId);
|
|
566
|
+
}
|
|
567
|
+
updateClosedPanelsList();
|
|
568
|
+
handleLayoutChange();
|
|
569
|
+
}),
|
|
570
|
+
api.onDidRemovePanel((e) => {
|
|
571
|
+
// Track closed panels for restoration
|
|
572
|
+
const panelId = e?.panel?.id;
|
|
573
|
+
if (panelId) {
|
|
574
|
+
closedPanels.add(panelId);
|
|
575
|
+
updateClosedPanelsList();
|
|
576
|
+
}
|
|
577
|
+
handleLayoutChange();
|
|
578
|
+
}),
|
|
579
|
+
];
|
|
580
|
+
|
|
581
|
+
return () => {
|
|
582
|
+
disposables.forEach(d => d.dispose());
|
|
583
|
+
};
|
|
584
|
+
}, [isReady, handleLayoutChange]);
|
|
585
|
+
|
|
586
|
+
// Handle responsive breakpoints - only on viewport changes, not initial load
|
|
587
|
+
useEffect(() => {
|
|
588
|
+
const api = apiRef.current;
|
|
589
|
+
if (!api || !isReady) return;
|
|
590
|
+
|
|
591
|
+
// On first run after layout is ready, just record current state without changing anything
|
|
592
|
+
// This preserves the saved collapsed state from initialLayout
|
|
593
|
+
if (!hasAppliedInitialLayout.current) {
|
|
594
|
+
hasAppliedInitialLayout.current = true;
|
|
595
|
+
previousIsSmall.current = isSmall;
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// Only apply responsive changes when isSmall actually changes
|
|
600
|
+
if (previousIsSmall.current === isSmall) {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
previousIsSmall.current = isSmall;
|
|
604
|
+
|
|
605
|
+
// Find groups by their panels (groups don't have fixed names)
|
|
606
|
+
const gitPanel = api.getPanel(PANEL_INVENTORY.GIT);
|
|
607
|
+
const sprintPanel = api.getPanel(PANEL_INVENTORY.SPRINT);
|
|
608
|
+
const leftGroup = gitPanel?.group;
|
|
609
|
+
const rightGroup = sprintPanel?.group;
|
|
610
|
+
|
|
611
|
+
if (isSmall) {
|
|
612
|
+
// Collapse sidebars at small viewport
|
|
613
|
+
leftGroup?.api.setSize({ width: 0 });
|
|
614
|
+
rightGroup?.api.setSize({ width: 0 });
|
|
615
|
+
} else {
|
|
616
|
+
// Restore sidebars to configured width when viewport expands
|
|
617
|
+
leftGroup?.api.setSize({ width: sidebarWidth });
|
|
618
|
+
rightGroup?.api.setSize({ width: sidebarWidth });
|
|
619
|
+
}
|
|
620
|
+
}, [isSmall, sidebarWidth, isReady]);
|
|
621
|
+
|
|
622
|
+
// Handle restoring a closed panel
|
|
623
|
+
const handleRestorePanel = useCallback((panelId: string) => {
|
|
624
|
+
restorePanel(panelId);
|
|
625
|
+
setShowRestoreMenu(false);
|
|
626
|
+
}, []);
|
|
627
|
+
|
|
628
|
+
// Cleanup on unmount
|
|
629
|
+
useEffect(() => {
|
|
630
|
+
return () => {
|
|
631
|
+
if (saveTimeoutRef.current) {
|
|
632
|
+
clearTimeout(saveTimeoutRef.current);
|
|
633
|
+
}
|
|
634
|
+
dockviewApiRef = null;
|
|
635
|
+
};
|
|
636
|
+
}, []);
|
|
637
|
+
|
|
638
|
+
// Listen for panel toggle commands via WebSocket (View menu integration)
|
|
639
|
+
useEffect(() => {
|
|
640
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
641
|
+
const ws = new WebSocket(`${protocol}//${window.location.host}/ws/settings`);
|
|
642
|
+
|
|
643
|
+
ws.onmessage = (event) => {
|
|
644
|
+
try {
|
|
645
|
+
const data = JSON.parse(event.data);
|
|
646
|
+
if (data.type === 'panel:toggle' && data.panelId) {
|
|
647
|
+
const api = dockviewApiRef;
|
|
648
|
+
if (!api) return;
|
|
649
|
+
const existing = api.getPanel(data.panelId);
|
|
650
|
+
if (existing) {
|
|
651
|
+
existing.api.close();
|
|
652
|
+
} else {
|
|
653
|
+
restorePanel(data.panelId);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
} catch {
|
|
657
|
+
// ignore parse errors
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
|
|
661
|
+
return () => ws.close();
|
|
662
|
+
}, []);
|
|
663
|
+
|
|
664
|
+
// Component map for Dockview
|
|
665
|
+
const components = {
|
|
666
|
+
PanelAdapter,
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// Panel display names for the restore menu
|
|
670
|
+
const panelDisplayNames: Record<string, string> = {
|
|
671
|
+
git: 'Git',
|
|
672
|
+
diffs: 'Diffs',
|
|
673
|
+
debug: 'Debug',
|
|
674
|
+
'audit-log': 'Audit Log',
|
|
675
|
+
sprint: 'Sprint',
|
|
676
|
+
workflow: 'Workflow',
|
|
677
|
+
ac: 'AC',
|
|
678
|
+
todo: 'Todo',
|
|
679
|
+
background: 'Subagents',
|
|
680
|
+
hotspots: 'Hotspots',
|
|
681
|
+
settings: 'Settings',
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
return (
|
|
685
|
+
<div className="cyclist-dockview" data-dockview-group="container">
|
|
686
|
+
{/* Minimum dimension warning */}
|
|
687
|
+
{isBelowMinimum && (
|
|
688
|
+
<div
|
|
689
|
+
data-testid="min-dimension-warning"
|
|
690
|
+
className="min-dimension-warning"
|
|
691
|
+
role="alert"
|
|
692
|
+
>
|
|
693
|
+
Window is below minimum size ({MIN_DIMENSIONS.width}x{MIN_DIMENSIONS.height})
|
|
694
|
+
</div>
|
|
695
|
+
)}
|
|
696
|
+
|
|
697
|
+
{/* Panel restore button - shown when panels are closed */}
|
|
698
|
+
{closedPanelsList.length > 0 && (
|
|
699
|
+
<div className="panel-restore-container">
|
|
700
|
+
<TooltipProvider delayDuration={300}>
|
|
701
|
+
<Tooltip>
|
|
702
|
+
<TooltipTrigger asChild>
|
|
703
|
+
<Button
|
|
704
|
+
variant="outline"
|
|
705
|
+
size="sm"
|
|
706
|
+
className="panel-restore-button"
|
|
707
|
+
onClick={() => setShowRestoreMenu(!showRestoreMenu)}
|
|
708
|
+
aria-expanded={showRestoreMenu}
|
|
709
|
+
aria-haspopup="menu"
|
|
710
|
+
>
|
|
711
|
+
<span className="panel-restore-icon">+</span>
|
|
712
|
+
<span className="panel-restore-count">{closedPanelsList.length}</span>
|
|
713
|
+
</Button>
|
|
714
|
+
</TooltipTrigger>
|
|
715
|
+
<TooltipContent>Restore closed panels</TooltipContent>
|
|
716
|
+
</Tooltip>
|
|
717
|
+
</TooltipProvider>
|
|
718
|
+
|
|
719
|
+
{showRestoreMenu && (
|
|
720
|
+
<div className="panel-restore-menu" role="menu">
|
|
721
|
+
<div className="panel-restore-header">Restore Panel</div>
|
|
722
|
+
{closedPanelsList.map((panelId) => (
|
|
723
|
+
<Button
|
|
724
|
+
variant="ghost"
|
|
725
|
+
key={panelId}
|
|
726
|
+
className="panel-restore-item"
|
|
727
|
+
onClick={() => handleRestorePanel(panelId)}
|
|
728
|
+
role="menuitem"
|
|
729
|
+
>
|
|
730
|
+
{panelDisplayNames[panelId] || panelId}
|
|
731
|
+
</Button>
|
|
732
|
+
))}
|
|
733
|
+
</div>
|
|
734
|
+
)}
|
|
735
|
+
</div>
|
|
736
|
+
)}
|
|
737
|
+
|
|
738
|
+
<DockviewReact
|
|
739
|
+
className="dockview-container"
|
|
740
|
+
onReady={onReady}
|
|
741
|
+
components={components}
|
|
742
|
+
defaultTabComponent={(props) => <DockviewDefaultTab {...props} hideClose />}
|
|
743
|
+
watermarkComponent={() => null}
|
|
744
|
+
/>
|
|
745
|
+
</div>
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export default DockviewWorkspace;
|