@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,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ModeSwitch Component
|
|
3
|
+
*
|
|
4
|
+
* A 3-way toggle for Plan/Manual/Accept permission modes with sliding highlight animation.
|
|
5
|
+
* Story MSSCI-12773 - ModeSwitch Component
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - Three mode options: Plan, Manual, Accept
|
|
9
|
+
* - Sliding highlight animation on mode change
|
|
10
|
+
* - Keyboard accessible (arrow keys via Radix ToggleGroup, Cmd+1/2/3 shortcuts)
|
|
11
|
+
* - ARIA radiogroup semantics provided by Radix ToggleGroup
|
|
12
|
+
* - Color-coded modes: Plan (teal), Manual (gray), Accept (purple)
|
|
13
|
+
* - Tooltips with mode descriptions via Radix Tooltip
|
|
14
|
+
*
|
|
15
|
+
* Refactored to use shadcn ToggleGroup primitive (replaces custom roving tabindex,
|
|
16
|
+
* manual keyboard handling, and manual ARIA management).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import React, { useState, useRef, useCallback, useEffect } from 'react';
|
|
20
|
+
|
|
21
|
+
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
|
22
|
+
import {
|
|
23
|
+
Tooltip,
|
|
24
|
+
TooltipTrigger,
|
|
25
|
+
TooltipContent,
|
|
26
|
+
TooltipProvider,
|
|
27
|
+
} from '@/components/ui/tooltip';
|
|
28
|
+
import { cn } from '@/lib/utils';
|
|
29
|
+
|
|
30
|
+
import './ModeSwitch.css';
|
|
31
|
+
|
|
32
|
+
// =============================================================================
|
|
33
|
+
// Types and Constants
|
|
34
|
+
// =============================================================================
|
|
35
|
+
|
|
36
|
+
export type Mode = 'plan' | 'manual' | 'accept';
|
|
37
|
+
|
|
38
|
+
export const MODES: Mode[] = ['plan', 'manual', 'accept'];
|
|
39
|
+
|
|
40
|
+
export const MODE_LABELS: Record<Mode, string> = {
|
|
41
|
+
plan: 'Plan',
|
|
42
|
+
manual: 'Manual',
|
|
43
|
+
accept: 'Accept',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const MODE_DESCRIPTIONS: Record<Mode, string> = {
|
|
47
|
+
plan: 'Read-only exploration mode',
|
|
48
|
+
manual: 'Ask for permission before actions',
|
|
49
|
+
accept: 'Auto-accept file edits',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Mode Mapping (UI <-> Claude CLI)
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/** Map UI mode names to Claude CLI mode names */
|
|
57
|
+
export const MODE_TO_CLAUDE: Record<Mode, string> = {
|
|
58
|
+
plan: 'plan',
|
|
59
|
+
manual: 'default',
|
|
60
|
+
accept: 'acceptEdits',
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** Map Claude CLI mode names back to UI mode names */
|
|
64
|
+
export const CLAUDE_TO_MODE: Record<string, Mode> = {
|
|
65
|
+
plan: 'plan',
|
|
66
|
+
default: 'manual',
|
|
67
|
+
acceptEdits: 'accept',
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// =============================================================================
|
|
71
|
+
// Keyboard Shortcuts (AC6)
|
|
72
|
+
// =============================================================================
|
|
73
|
+
|
|
74
|
+
/** Cmd+1/2/3 shortcuts to switch modes */
|
|
75
|
+
export const MODE_SHORTCUTS: Record<string, Mode> = {
|
|
76
|
+
'1': 'plan',
|
|
77
|
+
'2': 'manual',
|
|
78
|
+
'3': 'accept',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// =============================================================================
|
|
82
|
+
// Tooltip Support (AC7)
|
|
83
|
+
// =============================================================================
|
|
84
|
+
|
|
85
|
+
/** Flag indicating tooltips are enabled via Radix Tooltip */
|
|
86
|
+
export const TOOLTIP_ENABLED = true;
|
|
87
|
+
|
|
88
|
+
export interface ModeSwitchProps {
|
|
89
|
+
/** Current active mode */
|
|
90
|
+
mode?: Mode;
|
|
91
|
+
/** Default mode if uncontrolled */
|
|
92
|
+
defaultMode?: Mode;
|
|
93
|
+
/** Callback when mode changes */
|
|
94
|
+
onModeChange?: (mode: Mode) => void;
|
|
95
|
+
/** Additional CSS class */
|
|
96
|
+
className?: string;
|
|
97
|
+
/** Disable the component */
|
|
98
|
+
disabled?: boolean;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// =============================================================================
|
|
102
|
+
// Utility Functions
|
|
103
|
+
// =============================================================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Get the default mode (manual)
|
|
107
|
+
*/
|
|
108
|
+
export function getDefaultMode(): Mode {
|
|
109
|
+
return 'manual';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// =============================================================================
|
|
113
|
+
// useModeSwitch Hook
|
|
114
|
+
// =============================================================================
|
|
115
|
+
|
|
116
|
+
interface UseModesSwitchResult {
|
|
117
|
+
mode: Mode;
|
|
118
|
+
setMode: (mode: Mode) => void;
|
|
119
|
+
nextMode: () => void;
|
|
120
|
+
prevMode: () => void;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function useModeSwitch(initialMode: Mode = 'manual'): UseModesSwitchResult {
|
|
124
|
+
const [mode, setModeState] = useState<Mode>(initialMode);
|
|
125
|
+
|
|
126
|
+
const setMode = useCallback((newMode: Mode) => {
|
|
127
|
+
if (MODES.includes(newMode)) {
|
|
128
|
+
setModeState(newMode);
|
|
129
|
+
}
|
|
130
|
+
}, []);
|
|
131
|
+
|
|
132
|
+
const nextMode = useCallback(() => {
|
|
133
|
+
const currentIndex = MODES.indexOf(mode);
|
|
134
|
+
const nextIndex = (currentIndex + 1) % MODES.length;
|
|
135
|
+
setModeState(MODES[nextIndex]);
|
|
136
|
+
}, [mode]);
|
|
137
|
+
|
|
138
|
+
const prevMode = useCallback(() => {
|
|
139
|
+
const currentIndex = MODES.indexOf(mode);
|
|
140
|
+
const prevIndex = (currentIndex - 1 + MODES.length) % MODES.length;
|
|
141
|
+
setModeState(MODES[prevIndex]);
|
|
142
|
+
}, [mode]);
|
|
143
|
+
|
|
144
|
+
return { mode, setMode, nextMode, prevMode };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// =============================================================================
|
|
148
|
+
// useModeSwitchShortcuts Hook (AC6)
|
|
149
|
+
// =============================================================================
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Hook to register Cmd+1/2/3 keyboard shortcuts for mode switching
|
|
153
|
+
*/
|
|
154
|
+
export function useModeSwitchShortcuts(onModeChange: (mode: Mode) => void): void {
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const handler = (e: globalThis.KeyboardEvent) => {
|
|
157
|
+
// Check for Cmd (Mac) or Ctrl (Windows/Linux)
|
|
158
|
+
if ((e.metaKey || e.ctrlKey) && MODE_SHORTCUTS[e.key]) {
|
|
159
|
+
e.preventDefault();
|
|
160
|
+
onModeChange(MODE_SHORTCUTS[e.key]);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
document.addEventListener('keydown', handler);
|
|
165
|
+
return () => document.removeEventListener('keydown', handler);
|
|
166
|
+
}, [onModeChange]);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// useModeSync Hook (Backend Integration via WebSocket)
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
interface UseModeSyncResult {
|
|
174
|
+
mode: Mode;
|
|
175
|
+
setMode: (mode: Mode) => void;
|
|
176
|
+
isLoading: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Hook to sync UI mode state with Claude backend via WebSocket
|
|
181
|
+
* Migrated from IPC to WebSocket for unified communication
|
|
182
|
+
*/
|
|
183
|
+
export function useModeSync(): UseModeSyncResult {
|
|
184
|
+
const [mode, setModeState] = useState<Mode>('manual');
|
|
185
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
186
|
+
const wsRef = useRef<WebSocket | null>(null);
|
|
187
|
+
|
|
188
|
+
// Connect to WebSocket and sync mode
|
|
189
|
+
useEffect(() => {
|
|
190
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
191
|
+
const wsUrl = `${protocol}//${window.location.host}/ws/claude`;
|
|
192
|
+
const ws = new WebSocket(wsUrl);
|
|
193
|
+
wsRef.current = ws;
|
|
194
|
+
|
|
195
|
+
ws.onopen = () => {
|
|
196
|
+
// Request current mode from server
|
|
197
|
+
ws.send(JSON.stringify({ type: 'getMode' }));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
ws.onmessage = (event) => {
|
|
201
|
+
try {
|
|
202
|
+
const data = JSON.parse(event.data);
|
|
203
|
+
if (data.type === 'mode' && data.mode) {
|
|
204
|
+
const uiMode = CLAUDE_TO_MODE[data.mode] || 'manual';
|
|
205
|
+
setModeState(uiMode);
|
|
206
|
+
setIsLoading(false);
|
|
207
|
+
console.log('[ModeSwitch] Mode synced:', data.mode, '->', uiMode);
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
console.error('[ModeSwitch] Failed to parse mode response:', err);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
ws.onerror = () => {
|
|
215
|
+
setIsLoading(false);
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// Timeout fallback if server doesn't respond
|
|
219
|
+
const timeout = setTimeout(() => {
|
|
220
|
+
if (isLoading) {
|
|
221
|
+
setIsLoading(false);
|
|
222
|
+
}
|
|
223
|
+
}, 2000);
|
|
224
|
+
|
|
225
|
+
return () => {
|
|
226
|
+
clearTimeout(timeout);
|
|
227
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
228
|
+
ws.close();
|
|
229
|
+
}
|
|
230
|
+
wsRef.current = null;
|
|
231
|
+
};
|
|
232
|
+
}, []);
|
|
233
|
+
|
|
234
|
+
// Set mode on Claude backend via WebSocket
|
|
235
|
+
const setMode = useCallback((newMode: Mode) => {
|
|
236
|
+
const claudeMode = MODE_TO_CLAUDE[newMode];
|
|
237
|
+
|
|
238
|
+
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
|
239
|
+
wsRef.current.send(JSON.stringify({ type: 'setMode', mode: claudeMode }));
|
|
240
|
+
setModeState(newMode);
|
|
241
|
+
console.log('[ModeSwitch] Mode set to:', newMode, '->', claudeMode);
|
|
242
|
+
} else {
|
|
243
|
+
// WebSocket not connected, just update local state
|
|
244
|
+
setModeState(newMode);
|
|
245
|
+
console.warn('[ModeSwitch] WebSocket not connected, mode set locally only');
|
|
246
|
+
}
|
|
247
|
+
}, []);
|
|
248
|
+
|
|
249
|
+
return { mode, setMode, isLoading };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// =============================================================================
|
|
253
|
+
// ModeSwitch Component
|
|
254
|
+
// =============================================================================
|
|
255
|
+
|
|
256
|
+
export function ModeSwitch({
|
|
257
|
+
mode: controlledMode,
|
|
258
|
+
defaultMode = 'manual',
|
|
259
|
+
onModeChange,
|
|
260
|
+
className = '',
|
|
261
|
+
disabled = false,
|
|
262
|
+
}: ModeSwitchProps): React.ReactElement {
|
|
263
|
+
// Support both controlled and uncontrolled usage
|
|
264
|
+
const [internalMode, setInternalMode] = useState<Mode>(defaultMode);
|
|
265
|
+
const [reducedMotion, setReducedMotion] = useState(false);
|
|
266
|
+
const mode = controlledMode ?? internalMode;
|
|
267
|
+
|
|
268
|
+
// Check for reduced motion preference
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
271
|
+
setReducedMotion(mediaQuery.matches);
|
|
272
|
+
|
|
273
|
+
const listener = (e: MediaQueryListEvent) => {
|
|
274
|
+
setReducedMotion(e.matches);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
mediaQuery.addEventListener('change', listener);
|
|
278
|
+
return () => mediaQuery.removeEventListener('change', listener);
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
281
|
+
// Calculate highlight position based on current mode
|
|
282
|
+
const modeIndex = MODES.indexOf(mode);
|
|
283
|
+
|
|
284
|
+
const handleModeChange = useCallback((newMode: Mode) => {
|
|
285
|
+
if (disabled) return;
|
|
286
|
+
if (!controlledMode) {
|
|
287
|
+
setInternalMode(newMode);
|
|
288
|
+
}
|
|
289
|
+
onModeChange?.(newMode);
|
|
290
|
+
}, [controlledMode, disabled, onModeChange]);
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Radix ToggleGroup fires onValueChange with the new value string.
|
|
294
|
+
* When the user clicks the already-selected item, value is '' (deselect);
|
|
295
|
+
* we ignore that to enforce "always one selected".
|
|
296
|
+
*/
|
|
297
|
+
const handleValueChange = useCallback(
|
|
298
|
+
(value: string) => {
|
|
299
|
+
if (!value) return; // prevent deselect
|
|
300
|
+
handleModeChange(value as Mode);
|
|
301
|
+
},
|
|
302
|
+
[handleModeChange],
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<div
|
|
307
|
+
className={cn(
|
|
308
|
+
'mode-switch',
|
|
309
|
+
disabled && 'mode-switch--disabled',
|
|
310
|
+
reducedMotion && 'reduced-motion',
|
|
311
|
+
className,
|
|
312
|
+
)}
|
|
313
|
+
data-testid="mode-switch"
|
|
314
|
+
style={reducedMotion ? { transition: 'none' } : undefined}
|
|
315
|
+
>
|
|
316
|
+
{/* Sliding highlight */}
|
|
317
|
+
<div
|
|
318
|
+
className="mode-switch__highlight"
|
|
319
|
+
data-testid="mode-highlight"
|
|
320
|
+
style={{
|
|
321
|
+
transform: `translateX(${modeIndex * 100}%)`,
|
|
322
|
+
transition: reducedMotion ? 'none' : undefined,
|
|
323
|
+
}}
|
|
324
|
+
data-mode={mode}
|
|
325
|
+
/>
|
|
326
|
+
|
|
327
|
+
{/* Mode options via shadcn ToggleGroup */}
|
|
328
|
+
<TooltipProvider delayDuration={400}>
|
|
329
|
+
<ToggleGroup
|
|
330
|
+
type="single"
|
|
331
|
+
value={mode}
|
|
332
|
+
onValueChange={handleValueChange}
|
|
333
|
+
disabled={disabled}
|
|
334
|
+
className="relative z-[1] gap-0"
|
|
335
|
+
aria-label="Permission mode"
|
|
336
|
+
>
|
|
337
|
+
{MODES.map((m) => {
|
|
338
|
+
const isActive = mode === m;
|
|
339
|
+
return (
|
|
340
|
+
<Tooltip key={m}>
|
|
341
|
+
<TooltipTrigger asChild>
|
|
342
|
+
<ToggleGroupItem
|
|
343
|
+
value={m}
|
|
344
|
+
data-mode={m}
|
|
345
|
+
data-testid={`mode-${m}`}
|
|
346
|
+
aria-label={`${MODE_LABELS[m]} mode: ${MODE_DESCRIPTIONS[m]}`}
|
|
347
|
+
className={cn(
|
|
348
|
+
'mode-option',
|
|
349
|
+
isActive && 'active',
|
|
350
|
+
)}
|
|
351
|
+
>
|
|
352
|
+
{MODE_LABELS[m]}
|
|
353
|
+
</ToggleGroupItem>
|
|
354
|
+
</TooltipTrigger>
|
|
355
|
+
<TooltipContent side="bottom">
|
|
356
|
+
{MODE_DESCRIPTIONS[m]}
|
|
357
|
+
</TooltipContent>
|
|
358
|
+
</Tooltip>
|
|
359
|
+
);
|
|
360
|
+
})}
|
|
361
|
+
</ToggleGroup>
|
|
362
|
+
</TooltipProvider>
|
|
363
|
+
|
|
364
|
+
{/* Screen reader announcement */}
|
|
365
|
+
<div className="visually-hidden" role="status" aria-live="polite">
|
|
366
|
+
{`${MODE_LABELS[mode]} mode selected`}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export default ModeSwitch;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PersonaHeader Component
|
|
3
|
+
*
|
|
4
|
+
* Displays the current agent persona with portrait, character name, theme, role.
|
|
5
|
+
* Story MSSCI-12700 - PersonaHeader Component
|
|
6
|
+
* Story 72-3 - Catchphrase and Badge Improvements
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Portrait image display with adjacent role badge
|
|
10
|
+
* - Character name display with prominent styling
|
|
11
|
+
* - Random catchphrase from theme
|
|
12
|
+
* - Color-coded role badge matching CLI statusbar
|
|
13
|
+
* - Real-time updates via IPC subscriptions
|
|
14
|
+
* - Graceful handling of missing data
|
|
15
|
+
* - Accessible with ARIA labels
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
19
|
+
import { Badge } from '@/components/ui/badge';
|
|
20
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
21
|
+
import { usePersona } from '../hooks/usePersona';
|
|
22
|
+
import { useColorScheme } from '../hooks/useColorScheme';
|
|
23
|
+
import { AgentPopup } from './AgentPopup';
|
|
24
|
+
import TandemPortrait from './TandemPortrait';
|
|
25
|
+
|
|
26
|
+
// Agent colors matching CLI statusbar (statusline.sh)
|
|
27
|
+
const AGENT_COLORS: Record<string, string> = {
|
|
28
|
+
pm: '#a78bfa', // Purple - strategic
|
|
29
|
+
sm: '#60a5fa', // Blue - coordination
|
|
30
|
+
dev: '#4ade80', // Green - building
|
|
31
|
+
tea: '#2dd4bf', // Teal - testing
|
|
32
|
+
reviewer: '#f87171', // Red - critical eye
|
|
33
|
+
architect: '#fb923c', // Orange - design
|
|
34
|
+
devops: '#22d3ee', // Cyan - infrastructure
|
|
35
|
+
'ux-designer': '#f0abfc', // Pink - design
|
|
36
|
+
'tech-writer': '#e5e5e5', // White/light gray - documentation
|
|
37
|
+
orchestrator: '#e879f9', // Magenta - coordination
|
|
38
|
+
ba: '#a3e635', // Lime - discovery
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Abbreviated role names for compact badge display
|
|
42
|
+
const AGENT_ABBREV: Record<string, string> = {
|
|
43
|
+
pm: 'PM',
|
|
44
|
+
sm: 'SM',
|
|
45
|
+
dev: 'DEV',
|
|
46
|
+
tea: 'TEA',
|
|
47
|
+
reviewer: 'REV',
|
|
48
|
+
architect: 'ARC',
|
|
49
|
+
devops: 'OPS',
|
|
50
|
+
'ux-designer': 'UX',
|
|
51
|
+
'tech-writer': 'TW',
|
|
52
|
+
orchestrator: 'ORC',
|
|
53
|
+
ba: 'BA',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Convert kebab-case theme name to Title Case (e.g., "princess-bride" -> "Princess Bride")
|
|
57
|
+
function humanizeTheme(theme: string): string {
|
|
58
|
+
return theme
|
|
59
|
+
.split('-')
|
|
60
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
61
|
+
.join(' ');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export default function PersonaHeader(): React.ReactElement {
|
|
65
|
+
const { persona, isStreaming } = usePersona();
|
|
66
|
+
const colorScheme = useColorScheme();
|
|
67
|
+
const [portraitError, setPortraitError] = useState(false);
|
|
68
|
+
const [isPopupOpen, setIsPopupOpen] = useState(false);
|
|
69
|
+
const [isCompact, setIsCompact] = useState(false);
|
|
70
|
+
|
|
71
|
+
const character = persona?.character || 'Agent';
|
|
72
|
+
const theme = persona?.theme || 'default';
|
|
73
|
+
const role = persona?.role || 'agent';
|
|
74
|
+
const slug = persona?.slug;
|
|
75
|
+
const quote = persona?.quote;
|
|
76
|
+
const tandemAgent = persona?.tandemAgent;
|
|
77
|
+
|
|
78
|
+
// Observation pulse: one-shot animation on primary portrait when backseat starts thinking
|
|
79
|
+
const [observationPulse, setObservationPulse] = useState(false);
|
|
80
|
+
const prevThinkingRef = useRef(false);
|
|
81
|
+
const portraitRef = useRef<HTMLDivElement>(null);
|
|
82
|
+
|
|
83
|
+
useEffect(() => {
|
|
84
|
+
const wasThinking = prevThinkingRef.current;
|
|
85
|
+
const isThinking = tandemAgent?.isThinking ?? false;
|
|
86
|
+
prevThinkingRef.current = isThinking;
|
|
87
|
+
|
|
88
|
+
if (!wasThinking && isThinking) {
|
|
89
|
+
setObservationPulse(true);
|
|
90
|
+
}
|
|
91
|
+
}, [tandemAgent?.isThinking]);
|
|
92
|
+
|
|
93
|
+
const handlePulseEnd = useCallback(() => {
|
|
94
|
+
setObservationPulse(false);
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
const handleOpenPopup = useCallback(() => {
|
|
98
|
+
setIsPopupOpen(true);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
const handleClosePopup = useCallback(() => {
|
|
102
|
+
setIsPopupOpen(false);
|
|
103
|
+
}, []);
|
|
104
|
+
|
|
105
|
+
// Get role color, fallback to magenta
|
|
106
|
+
const roleColor = AGENT_COLORS[role] || '#e879f9';
|
|
107
|
+
|
|
108
|
+
// Don't render if no persona data
|
|
109
|
+
if (!persona?.character) {
|
|
110
|
+
return <div className="persona-header empty" data-testid="persona-header" />;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return (
|
|
114
|
+
<>
|
|
115
|
+
<TooltipProvider delayDuration={300}>
|
|
116
|
+
<div
|
|
117
|
+
className={`persona-header clickable${isCompact ? ' compact' : ''}`}
|
|
118
|
+
data-testid="persona-header"
|
|
119
|
+
role="button"
|
|
120
|
+
tabIndex={0}
|
|
121
|
+
aria-label="Current agent persona - click to view team"
|
|
122
|
+
aria-live="polite"
|
|
123
|
+
onClick={handleOpenPopup}
|
|
124
|
+
onKeyDown={(e) => e.key === 'Enter' && handleOpenPopup()}
|
|
125
|
+
>
|
|
126
|
+
<div className="persona-portrait-group">
|
|
127
|
+
<div
|
|
128
|
+
className={`persona-portrait${isStreaming ? ' avatar-thinking' : ''}${observationPulse ? ' avatar-observation-pulse' : ''}`}
|
|
129
|
+
data-testid="persona-portrait"
|
|
130
|
+
ref={portraitRef}
|
|
131
|
+
onAnimationEnd={handlePulseEnd}
|
|
132
|
+
>
|
|
133
|
+
{slug && theme && !portraitError ? (
|
|
134
|
+
<img
|
|
135
|
+
src={`/portraits/${theme}/medium/${slug}.png`}
|
|
136
|
+
alt={character}
|
|
137
|
+
className="portrait-image"
|
|
138
|
+
onError={() => setPortraitError(true)}
|
|
139
|
+
/>
|
|
140
|
+
) : (
|
|
141
|
+
<span className="portrait-fallback">🤖</span>
|
|
142
|
+
)}
|
|
143
|
+
</div>
|
|
144
|
+
{tandemAgent && (
|
|
145
|
+
<TandemPortrait
|
|
146
|
+
character={tandemAgent.character}
|
|
147
|
+
role={tandemAgent.role}
|
|
148
|
+
slug={tandemAgent.slug}
|
|
149
|
+
theme={tandemAgent.theme}
|
|
150
|
+
isActive={true}
|
|
151
|
+
isThinking={tandemAgent.isThinking}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
{tandemAgent && (
|
|
155
|
+
<span className="visually-hidden" role="status" aria-live="polite" data-testid="tandem-sr-status">
|
|
156
|
+
{tandemAgent.isThinking
|
|
157
|
+
? `${tandemAgent.character} is thinking`
|
|
158
|
+
: `${tandemAgent.character} observing`}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
<div className="persona-info">
|
|
163
|
+
<div className="persona-name-row">
|
|
164
|
+
<Tooltip>
|
|
165
|
+
<TooltipTrigger asChild>
|
|
166
|
+
<Badge
|
|
167
|
+
variant="default"
|
|
168
|
+
className="persona-role"
|
|
169
|
+
data-testid="persona-role"
|
|
170
|
+
style={{ backgroundColor: roleColor }}
|
|
171
|
+
>
|
|
172
|
+
{AGENT_ABBREV[role] || role}
|
|
173
|
+
</Badge>
|
|
174
|
+
</TooltipTrigger>
|
|
175
|
+
<TooltipContent>{role}</TooltipContent>
|
|
176
|
+
</Tooltip>
|
|
177
|
+
<Tooltip>
|
|
178
|
+
<TooltipTrigger asChild>
|
|
179
|
+
<span
|
|
180
|
+
className="persona-character"
|
|
181
|
+
data-testid="persona-character"
|
|
182
|
+
>
|
|
183
|
+
{character}
|
|
184
|
+
</span>
|
|
185
|
+
</TooltipTrigger>
|
|
186
|
+
<TooltipContent>{character}</TooltipContent>
|
|
187
|
+
</Tooltip>
|
|
188
|
+
<Tooltip>
|
|
189
|
+
<TooltipTrigger asChild>
|
|
190
|
+
<span
|
|
191
|
+
className="persona-theme"
|
|
192
|
+
data-testid="persona-theme"
|
|
193
|
+
>
|
|
194
|
+
{humanizeTheme(theme)}
|
|
195
|
+
</span>
|
|
196
|
+
</TooltipTrigger>
|
|
197
|
+
<TooltipContent>{`Theme: ${theme}`}</TooltipContent>
|
|
198
|
+
</Tooltip>
|
|
199
|
+
</div>
|
|
200
|
+
{quote && (
|
|
201
|
+
<Tooltip>
|
|
202
|
+
<TooltipTrigger asChild>
|
|
203
|
+
<span
|
|
204
|
+
className="persona-catchphrase"
|
|
205
|
+
data-testid="persona-catchphrase"
|
|
206
|
+
>
|
|
207
|
+
"{quote}"
|
|
208
|
+
</span>
|
|
209
|
+
</TooltipTrigger>
|
|
210
|
+
<TooltipContent>{quote}</TooltipContent>
|
|
211
|
+
</Tooltip>
|
|
212
|
+
)}
|
|
213
|
+
</div>
|
|
214
|
+
<img
|
|
215
|
+
src={tandemAgent
|
|
216
|
+
? `/portraits/${theme}/medium/cyclist-tandem.png`
|
|
217
|
+
: (colorScheme === 'dark' ? '/images/cyclist-dark.png' : '/images/cyclist-light.png')}
|
|
218
|
+
alt="Cyclist"
|
|
219
|
+
className="persona-branding"
|
|
220
|
+
/>
|
|
221
|
+
<button
|
|
222
|
+
className="persona-collapse-toggle"
|
|
223
|
+
onClick={(e) => {
|
|
224
|
+
e.stopPropagation();
|
|
225
|
+
setIsCompact(!isCompact);
|
|
226
|
+
}}
|
|
227
|
+
aria-label={isCompact ? 'Expand header' : 'Collapse header'}
|
|
228
|
+
>
|
|
229
|
+
{isCompact ? '▼' : '▲'}
|
|
230
|
+
</button>
|
|
231
|
+
</div>
|
|
232
|
+
</TooltipProvider>
|
|
233
|
+
|
|
234
|
+
<AgentPopup
|
|
235
|
+
isOpen={isPopupOpen}
|
|
236
|
+
onClose={handleClosePopup}
|
|
237
|
+
currentRole={role}
|
|
238
|
+
currentTheme={theme}
|
|
239
|
+
/>
|
|
240
|
+
</>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProjectInfoBar Component
|
|
3
|
+
*
|
|
4
|
+
* Slim info bar displaying the project directory name.
|
|
5
|
+
* Sits between PersonaHeader and Dockview tabs in BikeRack mode.
|
|
6
|
+
* Story 110-6: Project directory indicator in TUI header
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useEffect } from 'react';
|
|
10
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
|
11
|
+
|
|
12
|
+
interface ProjectInfo {
|
|
13
|
+
name: string;
|
|
14
|
+
path: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export default function ProjectInfoBar(): React.ReactElement {
|
|
18
|
+
const [info, setInfo] = useState<ProjectInfo | null>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
fetch('/api/project-info')
|
|
22
|
+
.then(res => res.ok ? res.json() : null)
|
|
23
|
+
.then(data => { if (data) setInfo(data); })
|
|
24
|
+
.catch(() => {});
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
if (!info) {
|
|
28
|
+
return <div className="project-info-bar" data-testid="project-info-bar" />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<TooltipProvider delayDuration={300}>
|
|
33
|
+
<div className="project-info-bar" data-testid="project-info-bar">
|
|
34
|
+
<Tooltip>
|
|
35
|
+
<TooltipTrigger asChild>
|
|
36
|
+
<span className="project-info-dir" data-testid="project-info-dir">
|
|
37
|
+
{info.name}
|
|
38
|
+
</span>
|
|
39
|
+
</TooltipTrigger>
|
|
40
|
+
<TooltipContent>{info.path}</TooltipContent>
|
|
41
|
+
</Tooltip>
|
|
42
|
+
</div>
|
|
43
|
+
</TooltipProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|