prjct-cli 0.11.5 → 0.12.1
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/CHANGELOG.md +58 -0
- package/README.md +81 -25
- package/bin/dev.js +1 -1
- package/bin/generate-views.js +209 -0
- package/bin/migrate-to-json.js +742 -0
- package/bin/prjct +5 -5
- package/bin/serve.js +226 -50
- package/core/__tests__/agentic/{memory-system.test.js → memory-system.test.ts} +12 -23
- package/core/__tests__/agentic/{plan-mode.test.js → plan-mode.test.ts} +26 -24
- package/core/__tests__/agentic/{prompt-builder.test.js → prompt-builder.test.ts} +3 -8
- package/core/__tests__/utils/{date-helper.test.js → date-helper.test.ts} +19 -30
- package/core/__tests__/utils/{output.test.js → output.test.ts} +12 -24
- package/core/agentic/agent-router.ts +137 -0
- package/core/agentic/chain-of-thought.ts +228 -0
- package/core/agentic/command-executor/command-executor.ts +384 -0
- package/core/agentic/command-executor/index.ts +16 -0
- package/core/agentic/command-executor/status-signal.ts +38 -0
- package/core/agentic/command-executor/types.ts +79 -0
- package/core/agentic/command-executor.ts +8 -0
- package/core/agentic/{context-builder.js → context-builder.ts} +92 -81
- package/core/agentic/context-filter.ts +365 -0
- package/core/agentic/ground-truth/index.ts +76 -0
- package/core/agentic/ground-truth/types.ts +33 -0
- package/core/agentic/ground-truth/utils.ts +48 -0
- package/core/agentic/ground-truth/verifiers/analyze.ts +54 -0
- package/core/agentic/ground-truth/verifiers/done.ts +75 -0
- package/core/agentic/ground-truth/verifiers/feature.ts +70 -0
- package/core/agentic/ground-truth/verifiers/index.ts +37 -0
- package/core/agentic/ground-truth/verifiers/init.ts +52 -0
- package/core/agentic/ground-truth/verifiers/now.ts +57 -0
- package/core/agentic/ground-truth/verifiers/ship.ts +85 -0
- package/core/agentic/ground-truth/verifiers/spec.ts +45 -0
- package/core/agentic/ground-truth/verifiers/sync.ts +47 -0
- package/core/agentic/ground-truth/verifiers.ts +6 -0
- package/core/agentic/ground-truth.ts +8 -0
- package/core/agentic/loop-detector/error-analysis.ts +97 -0
- package/core/agentic/loop-detector/hallucination.ts +71 -0
- package/core/agentic/loop-detector/index.ts +41 -0
- package/core/agentic/loop-detector/loop-detector.ts +222 -0
- package/core/agentic/loop-detector/types.ts +66 -0
- package/core/agentic/loop-detector.ts +8 -0
- package/core/agentic/memory-system/history.ts +53 -0
- package/core/agentic/memory-system/index.ts +192 -0
- package/core/agentic/memory-system/patterns.ts +156 -0
- package/core/agentic/memory-system/semantic-memories.ts +277 -0
- package/core/agentic/memory-system/session.ts +21 -0
- package/core/agentic/memory-system/types.ts +159 -0
- package/core/agentic/memory-system.ts +8 -0
- package/core/agentic/parallel-tools.ts +165 -0
- package/core/agentic/plan-mode/approval.ts +57 -0
- package/core/agentic/plan-mode/constants.ts +44 -0
- package/core/agentic/plan-mode/index.ts +28 -0
- package/core/agentic/plan-mode/plan-mode.ts +406 -0
- package/core/agentic/plan-mode/types.ts +193 -0
- package/core/agentic/plan-mode.ts +8 -0
- package/core/agentic/prompt-builder.ts +566 -0
- package/core/agentic/response-templates.ts +164 -0
- package/core/agentic/semantic-compression.ts +273 -0
- package/core/agentic/services.ts +206 -0
- package/core/agentic/smart-context.ts +476 -0
- package/core/agentic/{template-loader.js → template-loader.ts} +27 -16
- package/core/agentic/think-blocks.ts +202 -0
- package/core/agentic/tool-registry.ts +119 -0
- package/core/agentic/validation-rules.ts +313 -0
- package/core/agents/index.ts +28 -0
- package/core/agents/performance.ts +444 -0
- package/core/agents/types.ts +126 -0
- package/core/bus/{index.js → index.ts} +57 -61
- package/core/command-registry/categories.ts +23 -0
- package/core/command-registry/commands.ts +15 -0
- package/core/command-registry/core-commands.ts +319 -0
- package/core/command-registry/index.ts +158 -0
- package/core/command-registry/optional-commands.ts +119 -0
- package/core/command-registry/setup-commands.ts +53 -0
- package/core/command-registry/types.ts +59 -0
- package/core/command-registry.ts +9 -0
- package/core/commands/analysis.ts +298 -0
- package/core/commands/analytics.ts +288 -0
- package/core/commands/base.ts +273 -0
- package/core/commands/index.ts +211 -0
- package/core/commands/maintenance.ts +226 -0
- package/core/commands/planning.ts +311 -0
- package/core/commands/setup.ts +309 -0
- package/core/commands/shipping.ts +188 -0
- package/core/commands/types.ts +183 -0
- package/core/commands/workflow.ts +226 -0
- package/core/commands.ts +11 -0
- package/core/constants/formats.ts +187 -0
- package/core/constants/index.ts +7 -0
- package/core/{context-sync.js → context-sync.ts} +59 -26
- package/core/data/agents-manager.ts +76 -0
- package/core/data/analysis-manager.ts +83 -0
- package/core/data/base-manager.ts +156 -0
- package/core/data/ideas-manager.ts +81 -0
- package/core/data/index.ts +32 -0
- package/core/data/outcomes-manager.ts +96 -0
- package/core/data/project-manager.ts +75 -0
- package/core/data/roadmap-manager.ts +118 -0
- package/core/data/shipped-manager.ts +65 -0
- package/core/data/state-manager.ts +214 -0
- package/core/domain/{agent-generator.js → agent-generator.ts} +77 -57
- package/core/domain/{agent-loader.js → agent-loader.ts} +65 -56
- package/core/domain/{agent-matcher.js → agent-matcher.ts} +51 -24
- package/core/domain/{agent-validator.js → agent-validator.ts} +70 -37
- package/core/domain/{analyzer.js → analyzer.ts} +91 -85
- package/core/domain/{architect-session.js → architect-session.ts} +49 -34
- package/core/domain/{architecture-generator.js → architecture-generator.ts} +25 -13
- package/core/domain/{context-estimator.js → context-estimator.ts} +57 -36
- package/core/domain/{product-standards.js → product-standards.ts} +40 -26
- package/core/domain/{smart-cache.js → smart-cache.ts} +39 -30
- package/core/domain/{snapshot-manager.js → snapshot-manager.ts} +103 -100
- package/core/domain/{task-analyzer.js → task-analyzer.ts} +82 -43
- package/core/domain/task-stack/index.ts +19 -0
- package/core/domain/task-stack/parser.ts +86 -0
- package/core/domain/task-stack/storage.ts +123 -0
- package/core/domain/task-stack/task-stack.ts +340 -0
- package/core/domain/task-stack/types.ts +51 -0
- package/core/domain/task-stack.ts +8 -0
- package/core/{index.js → index.ts} +61 -18
- package/core/infrastructure/{agent-detector.js → agent-detector.ts} +55 -19
- package/core/infrastructure/agents/{claude-agent.js → claude-agent.ts} +61 -21
- package/core/infrastructure/{author-detector.js → author-detector.ts} +42 -49
- package/core/infrastructure/{capability-installer.js → capability-installer.ts} +51 -27
- package/core/infrastructure/{command-installer.js → command-installer/command-installer.ts} +43 -144
- package/core/infrastructure/command-installer/global-config.ts +106 -0
- package/core/infrastructure/command-installer/index.ts +25 -0
- package/core/infrastructure/command-installer/types.ts +41 -0
- package/core/infrastructure/command-installer.ts +8 -0
- package/core/infrastructure/{config-manager.js → config-manager.ts} +60 -80
- package/core/infrastructure/{editors-config.js → editors-config.ts} +33 -31
- package/core/infrastructure/legacy-installer-detector/cleanup.ts +216 -0
- package/core/infrastructure/legacy-installer-detector/detection.ts +95 -0
- package/core/infrastructure/legacy-installer-detector/index.ts +171 -0
- package/core/infrastructure/legacy-installer-detector/migration.ts +87 -0
- package/core/infrastructure/legacy-installer-detector/types.ts +42 -0
- package/core/infrastructure/legacy-installer-detector.ts +7 -0
- package/core/infrastructure/migrator/file-operations.ts +125 -0
- package/core/infrastructure/migrator/index.ts +288 -0
- package/core/infrastructure/migrator/project-scanner.ts +89 -0
- package/core/infrastructure/migrator/reports.ts +117 -0
- package/core/infrastructure/migrator/types.ts +124 -0
- package/core/infrastructure/migrator/validation.ts +94 -0
- package/core/infrastructure/migrator/version-migration.ts +117 -0
- package/core/infrastructure/migrator.ts +10 -0
- package/core/infrastructure/{path-manager.js → path-manager.ts} +51 -91
- package/core/infrastructure/session-manager/index.ts +23 -0
- package/core/infrastructure/session-manager/migration.ts +88 -0
- package/core/infrastructure/session-manager/session-manager.ts +307 -0
- package/core/infrastructure/session-manager/types.ts +45 -0
- package/core/infrastructure/session-manager.ts +8 -0
- package/core/infrastructure/{setup.js → setup.ts} +29 -21
- package/core/infrastructure/{update-checker.js → update-checker.ts} +40 -18
- package/core/outcomes/analyzer.ts +333 -0
- package/core/outcomes/index.ts +34 -0
- package/core/outcomes/recorder.ts +194 -0
- package/core/outcomes/types.ts +145 -0
- package/core/plugin/{hooks.js → hooks.ts} +56 -58
- package/core/plugin/{index.js → index.ts} +19 -8
- package/core/plugin/{loader.js → loader.ts} +87 -69
- package/core/plugin/{registry.js → registry.ts} +49 -45
- package/core/plugins/{webhook.js → webhook.ts} +43 -27
- package/core/schemas/agents.ts +27 -0
- package/core/schemas/analysis.ts +41 -0
- package/core/schemas/ideas.ts +83 -0
- package/core/schemas/index.ts +73 -0
- package/core/schemas/outcomes.ts +22 -0
- package/core/schemas/project.ts +26 -0
- package/core/schemas/roadmap.ts +90 -0
- package/core/schemas/shipped.ts +82 -0
- package/core/schemas/state.ts +107 -0
- package/core/session/index.ts +17 -0
- package/core/session/{metrics.js → metrics.ts} +64 -46
- package/core/session/{index.js → session-manager.ts} +51 -117
- package/core/session/types.ts +29 -0
- package/core/session/utils.ts +57 -0
- package/core/state/index.ts +25 -0
- package/core/state/manager.ts +376 -0
- package/core/state/types.ts +185 -0
- package/core/tsconfig.json +22 -0
- package/core/types/index.ts +506 -0
- package/core/utils/{animations.js → animations.ts} +74 -28
- package/core/utils/{branding.js → branding.ts} +29 -4
- package/core/utils/{date-helper.js → date-helper.ts} +31 -74
- package/core/utils/file-helper.ts +262 -0
- package/core/utils/{jsonl-helper.js → jsonl-helper.ts} +71 -107
- package/core/utils/{logger.js → logger.ts} +24 -12
- package/core/utils/{output.js → output.ts} +25 -13
- package/core/utils/{project-capabilities.js → project-capabilities.ts} +31 -18
- package/core/utils/{session-helper.js → session-helper.ts} +79 -66
- package/core/utils/{version.js → version.ts} +23 -31
- package/core/view-generator.ts +536 -0
- package/package.json +23 -17
- package/packages/shared/.turbo/turbo-build.log +14 -0
- package/packages/shared/dist/index.d.ts +8 -613
- package/packages/shared/dist/index.d.ts.map +1 -0
- package/packages/shared/dist/index.js +4110 -118
- package/packages/shared/dist/schemas.d.ts +408 -0
- package/packages/shared/dist/schemas.d.ts.map +1 -0
- package/packages/shared/dist/types.d.ts +144 -0
- package/packages/shared/dist/types.d.ts.map +1 -0
- package/packages/shared/dist/unified.d.ts +139 -0
- package/packages/shared/dist/unified.d.ts.map +1 -0
- package/packages/shared/dist/utils.d.ts +60 -0
- package/packages/shared/dist/utils.d.ts.map +1 -0
- package/packages/shared/package.json +4 -4
- package/packages/shared/src/index.ts +1 -0
- package/packages/shared/src/unified.ts +174 -0
- package/packages/web/app/api/claude/sessions/route.ts +1 -1
- package/packages/web/app/api/claude/status/route.ts +1 -1
- package/packages/web/app/api/migrate/route.ts +46 -0
- package/packages/web/app/api/projects/[id]/route.ts +1 -1
- package/packages/web/app/api/projects/[id]/stats/route.ts +30 -2
- package/packages/web/app/api/projects/[id]/status/route.ts +1 -1
- package/packages/web/app/api/projects/route.ts +1 -1
- package/packages/web/app/api/settings/route.ts +97 -0
- package/packages/web/app/api/v2/projects/[id]/unified/route.ts +57 -0
- package/packages/web/app/globals.css +38 -0
- package/packages/web/app/layout.tsx +10 -2
- package/packages/web/app/page.tsx +9 -224
- package/packages/web/app/project/[id]/page.tsx +191 -63
- package/packages/web/app/project/[id]/stats/loading.tsx +43 -0
- package/packages/web/app/project/[id]/stats/page.tsx +204 -163
- package/packages/web/app/settings/page.tsx +222 -2
- package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +2 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +50 -0
- package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +8 -0
- package/packages/web/components/ActivityTimeline/hooks/index.ts +2 -0
- package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +9 -0
- package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +23 -0
- package/packages/web/components/ActivityTimeline/index.ts +2 -0
- package/packages/web/components/AgentsCard/AgentsCard.tsx +63 -0
- package/packages/web/components/AgentsCard/AgentsCard.types.ts +13 -0
- package/packages/web/components/AgentsCard/index.ts +2 -0
- package/packages/web/components/AppSidebar/AppSidebar.tsx +190 -0
- package/packages/web/components/AppSidebar/index.ts +1 -0
- package/packages/web/components/BackLink/BackLink.tsx +18 -0
- package/packages/web/components/BackLink/BackLink.types.ts +5 -0
- package/packages/web/components/BackLink/index.ts +2 -0
- package/packages/web/components/BentoCard/BentoCard.constants.ts +16 -0
- package/packages/web/components/BentoCard/BentoCard.tsx +47 -0
- package/packages/web/components/BentoCard/BentoCard.types.ts +15 -0
- package/packages/web/components/BentoCard/index.ts +2 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +9 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +18 -0
- package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +5 -0
- package/packages/web/components/BentoCardSkeleton/index.ts +2 -0
- package/packages/web/components/{stats → BentoGrid}/BentoGrid.tsx +4 -8
- package/packages/web/components/BentoGrid/BentoGrid.types.ts +4 -0
- package/packages/web/components/BentoGrid/index.ts +2 -0
- package/packages/web/components/CommandButton/index.ts +1 -0
- package/packages/web/components/ConnectionStatus/index.ts +1 -0
- package/packages/web/components/DashboardContent/DashboardContent.tsx +254 -0
- package/packages/web/components/DashboardContent/index.ts +1 -0
- package/packages/web/components/DateGroup/DateGroup.tsx +18 -0
- package/packages/web/components/DateGroup/DateGroup.types.ts +6 -0
- package/packages/web/components/DateGroup/DateGroup.utils.ts +11 -0
- package/packages/web/components/DateGroup/index.ts +2 -0
- package/packages/web/components/{stats → EmptyState}/EmptyState.tsx +1 -10
- package/packages/web/components/EmptyState/EmptyState.types.ts +10 -0
- package/packages/web/components/EmptyState/index.ts +2 -0
- package/packages/web/components/EventRow/EventRow.constants.ts +10 -0
- package/packages/web/components/EventRow/EventRow.tsx +49 -0
- package/packages/web/components/EventRow/EventRow.types.ts +7 -0
- package/packages/web/components/EventRow/EventRow.utils.ts +49 -0
- package/packages/web/components/EventRow/index.ts +2 -0
- package/packages/web/components/ExpandButton/ExpandButton.tsx +18 -0
- package/packages/web/components/ExpandButton/ExpandButton.types.ts +6 -0
- package/packages/web/components/ExpandButton/index.ts +2 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +14 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +5 -0
- package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +13 -0
- package/packages/web/components/HealthGradientBackground/index.ts +2 -0
- package/packages/web/components/HeroSection/HeroSection.tsx +55 -0
- package/packages/web/components/HeroSection/HeroSection.types.ts +14 -0
- package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -0
- package/packages/web/components/HeroSection/hooks/index.ts +2 -0
- package/packages/web/components/HeroSection/hooks/useCountUp.ts +45 -0
- package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +18 -0
- package/packages/web/components/HeroSection/index.ts +2 -0
- package/packages/web/components/{stats → IdeasCard}/IdeasCard.tsx +3 -14
- package/packages/web/components/IdeasCard/IdeasCard.types.ts +9 -0
- package/packages/web/components/IdeasCard/index.ts +2 -0
- package/packages/web/components/InsightMessage/InsightMessage.tsx +9 -0
- package/packages/web/components/InsightMessage/InsightMessage.types.ts +3 -0
- package/packages/web/components/InsightMessage/index.ts +2 -0
- package/packages/web/components/Logo/index.ts +1 -0
- package/packages/web/components/MarkdownContent/index.ts +1 -0
- package/packages/web/components/NowCard/NowCard.tsx +93 -0
- package/packages/web/components/NowCard/NowCard.types.ts +15 -0
- package/packages/web/components/NowCard/index.ts +2 -0
- package/packages/web/components/ProgressRing/ProgressRing.constants.ts +20 -0
- package/packages/web/components/{stats → ProgressRing}/ProgressRing.tsx +4 -27
- package/packages/web/components/ProgressRing/ProgressRing.types.ts +11 -0
- package/packages/web/components/ProgressRing/index.ts +2 -0
- package/packages/web/components/ProjectAvatar/index.ts +1 -0
- package/packages/web/components/Providers/index.ts +1 -0
- package/packages/web/components/QueueCard/QueueCard.tsx +72 -0
- package/packages/web/components/QueueCard/QueueCard.types.ts +11 -0
- package/packages/web/components/QueueCard/QueueCard.utils.ts +12 -0
- package/packages/web/components/QueueCard/index.ts +2 -0
- package/packages/web/components/{stats → RoadmapCard}/RoadmapCard.tsx +3 -23
- package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +15 -0
- package/packages/web/components/RoadmapCard/index.ts +2 -0
- package/packages/web/components/{stats → ShipsCard}/ShipsCard.tsx +4 -22
- package/packages/web/components/ShipsCard/ShipsCard.types.ts +12 -0
- package/packages/web/components/ShipsCard/ShipsCard.utils.ts +4 -0
- package/packages/web/components/ShipsCard/index.ts +2 -0
- package/packages/web/components/{stats → SparklineChart}/SparklineChart.tsx +1 -7
- package/packages/web/components/SparklineChart/SparklineChart.types.ts +6 -0
- package/packages/web/components/SparklineChart/index.ts +2 -0
- package/packages/web/components/StreakCard/StreakCard.constants.ts +2 -0
- package/packages/web/components/{stats → StreakCard}/StreakCard.tsx +5 -11
- package/packages/web/components/StreakCard/StreakCard.types.ts +4 -0
- package/packages/web/components/StreakCard/index.ts +2 -0
- package/packages/web/components/TasksCounter/TasksCounter.tsx +14 -0
- package/packages/web/components/TasksCounter/TasksCounter.types.ts +3 -0
- package/packages/web/components/TasksCounter/index.ts +2 -0
- package/packages/web/components/TechStackBadges/index.ts +1 -0
- package/packages/web/components/{TerminalTab.tsx → TerminalTabs/TerminalTab.tsx} +11 -0
- package/packages/web/components/{TerminalTabs.tsx → TerminalTabs/TerminalTabs.tsx} +29 -28
- package/packages/web/components/TerminalTabs/index.ts +1 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.tsx +27 -0
- package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +3 -0
- package/packages/web/components/VelocityBadge/index.ts +2 -0
- package/packages/web/components/VelocityCard/VelocityCard.tsx +71 -0
- package/packages/web/components/VelocityCard/VelocityCard.types.ts +7 -0
- package/packages/web/components/VelocityCard/index.ts +2 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +13 -0
- package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +3 -0
- package/packages/web/components/WeeklySparkline/index.ts +2 -0
- package/packages/web/components/ui/input.tsx +21 -0
- package/packages/web/context/TerminalTabsContext.tsx +46 -1
- package/packages/web/hooks/useClaudeTerminal.ts +71 -21
- package/packages/web/hooks/useProjectStats.ts +55 -0
- package/packages/web/hooks/useProjects.ts +6 -6
- package/packages/web/lib/actions/projects.ts +15 -0
- package/packages/web/lib/json-loader.ts +630 -0
- package/packages/web/lib/services/index.ts +9 -0
- package/packages/web/lib/services/migration.server.ts +600 -0
- package/packages/web/lib/services/projects.server.ts +52 -0
- package/packages/web/lib/services/stats.server.ts +264 -0
- package/packages/web/lib/unified-loader.ts +396 -0
- package/packages/web/package.json +10 -7
- package/packages/web/server.ts +58 -8
- package/templates/commands/done.md +76 -32
- package/templates/commands/feature.md +121 -47
- package/templates/commands/idea.md +81 -8
- package/templates/commands/now.md +41 -17
- package/templates/commands/ship.md +64 -25
- package/templates/commands/sync.md +28 -3
- package/core/agentic/agent-router.js +0 -140
- package/core/agentic/chain-of-thought.js +0 -578
- package/core/agentic/command-executor.js +0 -417
- package/core/agentic/context-filter.js +0 -354
- package/core/agentic/ground-truth.js +0 -591
- package/core/agentic/loop-detector.js +0 -406
- package/core/agentic/memory-system.js +0 -845
- package/core/agentic/parallel-tools.js +0 -366
- package/core/agentic/plan-mode.js +0 -572
- package/core/agentic/prompt-builder.js +0 -352
- package/core/agentic/response-templates.js +0 -290
- package/core/agentic/semantic-compression.js +0 -517
- package/core/agentic/think-blocks.js +0 -657
- package/core/agentic/tool-registry.js +0 -184
- package/core/agentic/validation-rules.js +0 -380
- package/core/command-registry.js +0 -698
- package/core/commands.js +0 -2237
- package/core/domain/task-stack.js +0 -497
- package/core/infrastructure/legacy-installer-detector.js +0 -546
- package/core/infrastructure/migrator.js +0 -796
- package/core/infrastructure/session-manager.js +0 -390
- package/core/utils/file-helper.js +0 -329
- package/packages/web/app/api/projects/[id]/delete/route.ts +0 -21
- package/packages/web/app/api/stats/route.ts +0 -38
- package/packages/web/components/AppSidebar.tsx +0 -113
- package/packages/web/components/stats/ActivityTimeline.tsx +0 -201
- package/packages/web/components/stats/AgentsCard.tsx +0 -56
- package/packages/web/components/stats/BentoCard.tsx +0 -88
- package/packages/web/components/stats/HeroSection.tsx +0 -172
- package/packages/web/components/stats/NowCard.tsx +0 -71
- package/packages/web/components/stats/QueueCard.tsx +0 -58
- package/packages/web/components/stats/VelocityCard.tsx +0 -60
- package/packages/web/components/stats/index.ts +0 -17
- package/packages/web/hooks/useStats.ts +0 -28
- /package/packages/web/components/{CommandButton.tsx → CommandButton/CommandButton.tsx} +0 -0
- /package/packages/web/components/{ConnectionStatus.tsx → ConnectionStatus/ConnectionStatus.tsx} +0 -0
- /package/packages/web/components/{Logo.tsx → Logo/Logo.tsx} +0 -0
- /package/packages/web/components/{MarkdownContent.tsx → MarkdownContent/MarkdownContent.tsx} +0 -0
- /package/packages/web/components/{ProjectAvatar.tsx → ProjectAvatar/ProjectAvatar.tsx} +0 -0
- /package/packages/web/components/{providers.tsx → Providers/Providers.tsx} +0 -0
- /package/packages/web/components/{TechStackBadges.tsx → TechStackBadges/TechStackBadges.tsx} +0 -0
|
@@ -35,6 +35,7 @@ export function TerminalTab({ session, projectDir, isActive }: TerminalTabProps)
|
|
|
35
35
|
disconnect,
|
|
36
36
|
sendInput,
|
|
37
37
|
focusTerminal,
|
|
38
|
+
fit,
|
|
38
39
|
} = useClaudeTerminal({
|
|
39
40
|
sessionId: session.id,
|
|
40
41
|
projectDir,
|
|
@@ -57,6 +58,16 @@ export function TerminalTab({ session, projectDir, isActive }: TerminalTabProps)
|
|
|
57
58
|
}
|
|
58
59
|
}, []) // Empty deps - run only on mount
|
|
59
60
|
|
|
61
|
+
// Re-fit terminal when tab becomes active (fixes resize issues when tab was hidden)
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (isActive && hasInitializedRef.current) {
|
|
64
|
+
// Use requestAnimationFrame to ensure container is fully visible
|
|
65
|
+
requestAnimationFrame(() => {
|
|
66
|
+
fit()
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
}, [isActive, fit])
|
|
70
|
+
|
|
60
71
|
// Register sendInput and focusTerminal for this session
|
|
61
72
|
useEffect(() => {
|
|
62
73
|
registerSendInput(session.id, sendInput)
|
|
@@ -90,8 +90,8 @@ export function TerminalTabs({ projectDir }: TerminalTabsProps) {
|
|
|
90
90
|
|
|
91
91
|
return (
|
|
92
92
|
<div className="flex flex-col h-full">
|
|
93
|
-
{/* Tab bar */}
|
|
94
|
-
<div className="flex items-center gap-1 px-2 py-1 bg-card border-b border-border min-h-[40px]">
|
|
93
|
+
{/* Tab bar - scrollable on mobile */}
|
|
94
|
+
<div className="flex items-center gap-1 px-2 py-1.5 bg-card border-b border-border min-h-[44px] md:min-h-[40px] overflow-x-auto scrollbar-hide">
|
|
95
95
|
{sessions.map(session => (
|
|
96
96
|
<div
|
|
97
97
|
key={session.id}
|
|
@@ -101,22 +101,23 @@ export function TerminalTabs({ projectDir }: TerminalTabsProps) {
|
|
|
101
101
|
tabIndex={0}
|
|
102
102
|
onKeyDown={(e) => e.key === 'Enter' && setActiveSession(session.id)}
|
|
103
103
|
className={cn(
|
|
104
|
-
'flex items-center gap-2 px-3 py-1.5 rounded-md text-sm transition-colors cursor-pointer',
|
|
105
|
-
'hover:bg-muted',
|
|
104
|
+
'flex items-center gap-2 px-3 py-2 md:py-1.5 rounded-md text-sm transition-colors cursor-pointer shrink-0',
|
|
105
|
+
'hover:bg-muted active:bg-muted/80',
|
|
106
|
+
'min-h-[36px] md:min-h-0', // Touch-friendly height on mobile
|
|
106
107
|
session.id === activeSessionId
|
|
107
108
|
? 'bg-muted text-foreground'
|
|
108
109
|
: 'text-muted-foreground'
|
|
109
110
|
)}
|
|
110
111
|
>
|
|
111
112
|
{session.isLoading ? (
|
|
112
|
-
<Loader2 className="w-3.5 h-3.5 animate-spin" />
|
|
113
|
+
<Loader2 className="w-3.5 h-3.5 animate-spin shrink-0" />
|
|
113
114
|
) : session.isConnected ? (
|
|
114
|
-
<span className="relative flex h-2 w-2">
|
|
115
|
+
<span className="relative flex h-2.5 w-2.5 shrink-0">
|
|
115
116
|
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
|
|
116
|
-
<span className="relative inline-flex rounded-full h-2 w-2 bg-green-500" />
|
|
117
|
+
<span className="relative inline-flex rounded-full h-2.5 w-2.5 bg-green-500" />
|
|
117
118
|
</span>
|
|
118
119
|
) : (
|
|
119
|
-
<span className="h-2 w-2 rounded-full bg-muted-foreground/50" />
|
|
120
|
+
<span className="h-2.5 w-2.5 rounded-full bg-muted-foreground/50 shrink-0" />
|
|
120
121
|
)}
|
|
121
122
|
{editingSessionId === session.id ? (
|
|
122
123
|
<input
|
|
@@ -130,41 +131,41 @@ export function TerminalTabs({ projectDir }: TerminalTabsProps) {
|
|
|
130
131
|
className="w-[100px] bg-background border border-border rounded px-1 py-0.5 text-sm outline-none focus:ring-1 focus:ring-ring"
|
|
131
132
|
/>
|
|
132
133
|
) : (
|
|
133
|
-
<span className="truncate max-w-[100px]">{session.label}</span>
|
|
134
|
+
<span className="truncate max-w-[80px] md:max-w-[100px]">{session.label}</span>
|
|
134
135
|
)}
|
|
135
136
|
<button
|
|
136
137
|
onClick={(e) => handleCloseTab(session.id, e)}
|
|
137
|
-
className="p-0.5 rounded hover:bg-background/50 text-muted-foreground hover:text-foreground"
|
|
138
|
+
className="p-1 md:p-0.5 rounded hover:bg-background/50 text-muted-foreground hover:text-foreground shrink-0"
|
|
138
139
|
>
|
|
139
|
-
<X className="w-3 h-3" />
|
|
140
|
+
<X className="w-4 h-4 md:w-3 md:h-3" />
|
|
140
141
|
</button>
|
|
141
142
|
</div>
|
|
142
143
|
))}
|
|
143
144
|
|
|
144
|
-
{/* New tab button */}
|
|
145
|
+
{/* New tab button - larger touch target on mobile */}
|
|
145
146
|
<Button
|
|
146
147
|
variant="ghost"
|
|
147
148
|
size="icon"
|
|
148
|
-
className="h-7 w-7 ml-1"
|
|
149
|
+
className="h-9 w-9 md:h-7 md:w-7 ml-1 shrink-0"
|
|
149
150
|
onClick={createSession}
|
|
150
151
|
>
|
|
151
|
-
<Plus className="w-4 h-4" />
|
|
152
|
+
<Plus className="w-5 h-5 md:w-4 md:h-4" />
|
|
152
153
|
</Button>
|
|
153
154
|
</div>
|
|
154
155
|
|
|
155
156
|
{/* Terminal area */}
|
|
156
157
|
<div className="flex-1 relative">
|
|
157
158
|
{hasNoSessions ? (
|
|
158
|
-
<div className="absolute inset-0 flex items-center justify-center bg-background">
|
|
159
|
-
<div className="text-center">
|
|
160
|
-
<div className="w-16 h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-4">
|
|
161
|
-
<TerminalIcon className="w-8 h-8 text-muted-foreground" />
|
|
159
|
+
<div className="absolute inset-0 flex items-center justify-center bg-background p-4">
|
|
160
|
+
<div className="text-center max-w-xs">
|
|
161
|
+
<div className="w-14 h-14 md:w-16 md:h-16 rounded-full bg-muted flex items-center justify-center mx-auto mb-3 md:mb-4">
|
|
162
|
+
<TerminalIcon className="w-7 h-7 md:w-8 md:h-8 text-muted-foreground" />
|
|
162
163
|
</div>
|
|
163
|
-
<h2 className="text-lg font-medium mb-2">No active sessions</h2>
|
|
164
|
-
<p className="text-muted-foreground text-sm mb-4">
|
|
165
|
-
|
|
164
|
+
<h2 className="text-base md:text-lg font-medium mb-1.5 md:mb-2">No active sessions</h2>
|
|
165
|
+
<p className="text-muted-foreground text-sm mb-3 md:mb-4">
|
|
166
|
+
Tap + to create a new terminal
|
|
166
167
|
</p>
|
|
167
|
-
<Button onClick={createSession}>
|
|
168
|
+
<Button onClick={createSession} className="min-h-[44px]">
|
|
168
169
|
<Plus className="w-4 h-4 mr-2" />
|
|
169
170
|
New Terminal
|
|
170
171
|
</Button>
|
|
@@ -182,23 +183,23 @@ export function TerminalTabs({ projectDir }: TerminalTabsProps) {
|
|
|
182
183
|
)}
|
|
183
184
|
</div>
|
|
184
185
|
|
|
185
|
-
{/* Close confirmation dialog */}
|
|
186
|
+
{/* Close confirmation dialog - responsive */}
|
|
186
187
|
<AlertDialog open={!!sessionToClose} onOpenChange={(open: boolean) => !open && setSessionToClose(null)}>
|
|
187
|
-
<AlertDialogContent>
|
|
188
|
+
<AlertDialogContent className="max-w-[calc(100vw-2rem)] sm:max-w-lg">
|
|
188
189
|
<AlertDialogHeader>
|
|
189
190
|
<AlertDialogTitle className="flex items-center gap-2">
|
|
190
|
-
<AlertTriangle className="w-5 h-5 text-destructive" />
|
|
191
|
+
<AlertTriangle className="w-5 h-5 text-destructive shrink-0" />
|
|
191
192
|
Close Terminal?
|
|
192
193
|
</AlertDialogTitle>
|
|
193
194
|
<AlertDialogDescription>
|
|
194
195
|
This terminal has an active session. Closing it will terminate the connection.
|
|
195
196
|
</AlertDialogDescription>
|
|
196
197
|
</AlertDialogHeader>
|
|
197
|
-
<AlertDialogFooter>
|
|
198
|
-
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
198
|
+
<AlertDialogFooter className="flex-col sm:flex-row gap-2">
|
|
199
|
+
<AlertDialogCancel className="w-full sm:w-auto">Cancel</AlertDialogCancel>
|
|
199
200
|
<AlertDialogAction
|
|
200
201
|
onClick={handleConfirmClose}
|
|
201
|
-
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
|
202
|
+
className="bg-destructive text-destructive-foreground hover:bg-destructive/90 w-full sm:w-auto"
|
|
202
203
|
>
|
|
203
204
|
Close Terminal
|
|
204
205
|
</AlertDialogAction>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { TerminalTabs } from './TerminalTabs'
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { TrendingUp, TrendingDown } from 'lucide-react'
|
|
2
|
+
import { cn } from '@/lib/utils'
|
|
3
|
+
import type { VelocityBadgeProps } from './VelocityBadge.types'
|
|
4
|
+
|
|
5
|
+
export function VelocityBadge({ change }: VelocityBadgeProps) {
|
|
6
|
+
if (change === 0) return null
|
|
7
|
+
|
|
8
|
+
const isPositive = change >= 0
|
|
9
|
+
const Icon = isPositive ? TrendingUp : TrendingDown
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex items-center gap-2 mt-2">
|
|
13
|
+
<span
|
|
14
|
+
className={cn(
|
|
15
|
+
'inline-flex items-center gap-1 text-xs sm:text-sm font-medium px-2 py-0.5 rounded-md',
|
|
16
|
+
isPositive
|
|
17
|
+
? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
|
|
18
|
+
: 'bg-muted text-muted-foreground'
|
|
19
|
+
)}
|
|
20
|
+
>
|
|
21
|
+
<Icon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
|
22
|
+
{isPositive ? '+' : ''}{change}%
|
|
23
|
+
</span>
|
|
24
|
+
<span className="text-xs sm:text-sm text-muted-foreground">vs last week</span>
|
|
25
|
+
</div>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { BentoCard } from '@/components/BentoCard'
|
|
2
|
+
import { SparklineChart } from '@/components/SparklineChart'
|
|
3
|
+
import { Zap, TrendingUp, TrendingDown, Target } from 'lucide-react'
|
|
4
|
+
import { cn } from '@/lib/utils'
|
|
5
|
+
import type { VelocityCardProps } from './VelocityCard.types'
|
|
6
|
+
|
|
7
|
+
export function VelocityCard({
|
|
8
|
+
tasksPerDay,
|
|
9
|
+
weeklyData = [],
|
|
10
|
+
change = 0,
|
|
11
|
+
estimateAccuracy,
|
|
12
|
+
className,
|
|
13
|
+
}: VelocityCardProps) {
|
|
14
|
+
return (
|
|
15
|
+
<BentoCard
|
|
16
|
+
size="1x1"
|
|
17
|
+
title="Velocity"
|
|
18
|
+
icon={Zap}
|
|
19
|
+
className={className}
|
|
20
|
+
>
|
|
21
|
+
<div className="flex flex-col h-full justify-between">
|
|
22
|
+
<div>
|
|
23
|
+
<p className="text-3xl font-bold tabular-nums">{tasksPerDay}</p>
|
|
24
|
+
<p className="text-xs text-muted-foreground">tasks/day</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{weeklyData.length > 0 && (
|
|
28
|
+
<div className="mt-2">
|
|
29
|
+
<SparklineChart data={weeklyData} height={28} />
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
|
|
33
|
+
<div className="flex items-center justify-between mt-2">
|
|
34
|
+
{change !== 0 && (
|
|
35
|
+
<div className="flex items-center gap-1">
|
|
36
|
+
{change >= 0 ? (
|
|
37
|
+
<TrendingUp className="h-3 w-3 text-emerald-500" />
|
|
38
|
+
) : (
|
|
39
|
+
<TrendingDown className="h-3 w-3 text-muted-foreground" />
|
|
40
|
+
)}
|
|
41
|
+
<span
|
|
42
|
+
className={cn(
|
|
43
|
+
'text-xs font-medium',
|
|
44
|
+
change >= 0 ? 'text-emerald-600 dark:text-emerald-400' : 'text-muted-foreground'
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{change >= 0 ? '+' : ''}{change}%
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
)}
|
|
51
|
+
|
|
52
|
+
{estimateAccuracy !== undefined && estimateAccuracy > 0 && (
|
|
53
|
+
<div className="flex items-center gap-1">
|
|
54
|
+
<Target className="h-3 w-3 text-muted-foreground" />
|
|
55
|
+
<span
|
|
56
|
+
className={cn(
|
|
57
|
+
'text-xs font-medium',
|
|
58
|
+
estimateAccuracy >= 70 ? 'text-emerald-600 dark:text-emerald-400' :
|
|
59
|
+
estimateAccuracy >= 40 ? 'text-amber-600 dark:text-amber-400' :
|
|
60
|
+
'text-muted-foreground'
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{estimateAccuracy}% acc
|
|
64
|
+
</span>
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</BentoCard>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SparklineChart } from '@/components/SparklineChart'
|
|
2
|
+
import type { WeeklySparklineProps } from './WeeklySparkline.types'
|
|
3
|
+
|
|
4
|
+
export function WeeklySparkline({ data }: WeeklySparklineProps) {
|
|
5
|
+
return (
|
|
6
|
+
<div className="w-full sm:w-32">
|
|
7
|
+
<p className="text-[10px] uppercase tracking-wider text-muted-foreground mb-1 md:text-right">
|
|
8
|
+
7-day activity
|
|
9
|
+
</p>
|
|
10
|
+
<SparklineChart data={data} height={40} />
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "@/lib/utils"
|
|
4
|
+
|
|
5
|
+
function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
|
6
|
+
return (
|
|
7
|
+
<input
|
|
8
|
+
type={type}
|
|
9
|
+
data-slot="input"
|
|
10
|
+
className={cn(
|
|
11
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
12
|
+
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
13
|
+
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
14
|
+
className
|
|
15
|
+
)}
|
|
16
|
+
{...props}
|
|
17
|
+
/>
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export { Input }
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Terminal Tabs Context - Manage multiple terminal sessions
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createContext, useContext, useCallback, useState, useRef, ReactNode } from 'react'
|
|
7
|
+
import { createContext, useContext, useCallback, useState, useRef, useEffect, ReactNode } from 'react'
|
|
8
8
|
|
|
9
9
|
export interface TerminalSession {
|
|
10
10
|
id: string
|
|
@@ -108,6 +108,51 @@ export function TerminalTabsProvider({ children, projectId }: { children: ReactN
|
|
|
108
108
|
}
|
|
109
109
|
}, [activeSessionId, sessions])
|
|
110
110
|
|
|
111
|
+
// Check if any session is connected
|
|
112
|
+
const hasConnectedSessions = sessions.some(s => s.isConnected)
|
|
113
|
+
|
|
114
|
+
// Prevent page unload when terminal sessions are active
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
if (!hasConnectedSessions) return
|
|
117
|
+
|
|
118
|
+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
119
|
+
e.preventDefault()
|
|
120
|
+
// Modern browsers require returnValue to be set
|
|
121
|
+
e.returnValue = 'You have active terminal sessions. Are you sure you want to leave?'
|
|
122
|
+
return e.returnValue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
126
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
127
|
+
}, [hasConnectedSessions])
|
|
128
|
+
|
|
129
|
+
// Prevent back/forward navigation when terminal sessions are active
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
if (!hasConnectedSessions) return
|
|
132
|
+
|
|
133
|
+
// Push a state to history so we can intercept back navigation
|
|
134
|
+
const currentPath = window.location.pathname + window.location.search
|
|
135
|
+
window.history.pushState({ terminalActive: true }, '', currentPath)
|
|
136
|
+
|
|
137
|
+
const handlePopState = (e: PopStateEvent) => {
|
|
138
|
+
// User pressed back/forward
|
|
139
|
+
const confirmLeave = window.confirm(
|
|
140
|
+
'You have active terminal sessions. Leaving will terminate them. Are you sure?'
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
if (!confirmLeave) {
|
|
144
|
+
// Cancel navigation by pushing state back
|
|
145
|
+
window.history.pushState({ terminalActive: true }, '', currentPath)
|
|
146
|
+
}
|
|
147
|
+
// If confirmed, let the navigation happen naturally
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
window.addEventListener('popstate', handlePopState)
|
|
151
|
+
return () => {
|
|
152
|
+
window.removeEventListener('popstate', handlePopState)
|
|
153
|
+
}
|
|
154
|
+
}, [hasConnectedSessions])
|
|
155
|
+
|
|
111
156
|
return (
|
|
112
157
|
<TerminalTabsContext.Provider value={{
|
|
113
158
|
sessions,
|
|
@@ -89,6 +89,9 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
89
89
|
const reconnectAttemptsRef = useRef(0)
|
|
90
90
|
const intentionalDisconnectRef = useRef(false)
|
|
91
91
|
const currentSessionIdRef = useRef<string | null>(null)
|
|
92
|
+
const resizeObserverRef = useRef<ResizeObserver | null>(null)
|
|
93
|
+
const outputBufferRef = useRef<string[]>([])
|
|
94
|
+
const flushScheduledRef = useRef(false)
|
|
92
95
|
|
|
93
96
|
const [isConnected, setIsConnected] = useState(false)
|
|
94
97
|
const [isLoading, setIsLoading] = useState(false)
|
|
@@ -122,7 +125,10 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
122
125
|
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
|
|
123
126
|
fontSize: 14,
|
|
124
127
|
lineHeight: 1.2,
|
|
125
|
-
theme: initialTheme
|
|
128
|
+
theme: initialTheme,
|
|
129
|
+
// Performance optimizations
|
|
130
|
+
scrollback: 5000,
|
|
131
|
+
smoothScrollDuration: 0, // Instant scroll for better performance
|
|
126
132
|
})
|
|
127
133
|
|
|
128
134
|
const fitAddon = new FitAddon()
|
|
@@ -138,26 +144,27 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
138
144
|
fitAddonRef.current = fitAddon
|
|
139
145
|
containerRef.current = container
|
|
140
146
|
|
|
141
|
-
//
|
|
142
|
-
const
|
|
143
|
-
if (fitAddonRef.current) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
147
|
+
// Use ResizeObserver for better resize handling (works with container, not just window)
|
|
148
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
149
|
+
if (fitAddonRef.current && containerRef.current) {
|
|
150
|
+
// Only fit if container has dimensions (not hidden)
|
|
151
|
+
if (containerRef.current.offsetWidth > 0 && containerRef.current.offsetHeight > 0) {
|
|
152
|
+
fitAddonRef.current.fit()
|
|
153
|
+
|
|
154
|
+
// Send resize to server
|
|
155
|
+
if (wsRef.current?.readyState === WebSocket.OPEN && terminalRef.current) {
|
|
156
|
+
wsRef.current.send(JSON.stringify({
|
|
157
|
+
type: 'resize',
|
|
158
|
+
cols: terminalRef.current.cols,
|
|
159
|
+
rows: terminalRef.current.rows
|
|
160
|
+
}))
|
|
161
|
+
}
|
|
153
162
|
}
|
|
154
163
|
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
window.addEventListener('resize', handleResize)
|
|
164
|
+
})
|
|
158
165
|
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
resizeObserver.observe(container)
|
|
167
|
+
resizeObserverRef.current = resizeObserver
|
|
161
168
|
}, [])
|
|
162
169
|
|
|
163
170
|
// Clear reconnect timeout
|
|
@@ -238,13 +245,28 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
238
245
|
}
|
|
239
246
|
}
|
|
240
247
|
|
|
248
|
+
// Flush buffered output using requestAnimationFrame for smooth rendering
|
|
249
|
+
const flushOutput = () => {
|
|
250
|
+
if (outputBufferRef.current.length > 0 && terminalRef.current) {
|
|
251
|
+
const combined = outputBufferRef.current.join('')
|
|
252
|
+
terminalRef.current.write(combined)
|
|
253
|
+
outputBufferRef.current = []
|
|
254
|
+
}
|
|
255
|
+
flushScheduledRef.current = false
|
|
256
|
+
}
|
|
257
|
+
|
|
241
258
|
ws.onmessage = (event) => {
|
|
242
259
|
try {
|
|
243
260
|
const message = JSON.parse(event.data)
|
|
244
261
|
|
|
245
262
|
switch (message.type) {
|
|
246
263
|
case 'output':
|
|
247
|
-
|
|
264
|
+
// Buffer output and flush on next animation frame for smooth rendering
|
|
265
|
+
outputBufferRef.current.push(message.data)
|
|
266
|
+
if (!flushScheduledRef.current) {
|
|
267
|
+
flushScheduledRef.current = true
|
|
268
|
+
requestAnimationFrame(flushOutput)
|
|
269
|
+
}
|
|
248
270
|
break
|
|
249
271
|
|
|
250
272
|
case 'connected':
|
|
@@ -261,8 +283,12 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
261
283
|
break
|
|
262
284
|
}
|
|
263
285
|
} catch {
|
|
264
|
-
// Raw data, write directly
|
|
265
|
-
|
|
286
|
+
// Raw data, write directly (also buffered)
|
|
287
|
+
outputBufferRef.current.push(event.data)
|
|
288
|
+
if (!flushScheduledRef.current) {
|
|
289
|
+
flushScheduledRef.current = true
|
|
290
|
+
requestAnimationFrame(flushOutput)
|
|
291
|
+
}
|
|
266
292
|
}
|
|
267
293
|
}
|
|
268
294
|
|
|
@@ -347,6 +373,25 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
347
373
|
}
|
|
348
374
|
}, [])
|
|
349
375
|
|
|
376
|
+
// Fit terminal to container (useful when tab becomes visible)
|
|
377
|
+
const fit = useCallback(() => {
|
|
378
|
+
if (fitAddonRef.current && containerRef.current) {
|
|
379
|
+
// Only fit if container has dimensions (not hidden)
|
|
380
|
+
if (containerRef.current.offsetWidth > 0 && containerRef.current.offsetHeight > 0) {
|
|
381
|
+
fitAddonRef.current.fit()
|
|
382
|
+
|
|
383
|
+
// Send resize to server
|
|
384
|
+
if (wsRef.current?.readyState === WebSocket.OPEN && terminalRef.current) {
|
|
385
|
+
wsRef.current.send(JSON.stringify({
|
|
386
|
+
type: 'resize',
|
|
387
|
+
cols: terminalRef.current.cols,
|
|
388
|
+
rows: terminalRef.current.rows
|
|
389
|
+
}))
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}, [])
|
|
394
|
+
|
|
350
395
|
// Cleanup on unmount - empty deps to run only on unmount
|
|
351
396
|
useEffect(() => {
|
|
352
397
|
return () => {
|
|
@@ -357,6 +402,10 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
357
402
|
if (wsRef.current) {
|
|
358
403
|
wsRef.current.close()
|
|
359
404
|
}
|
|
405
|
+
// Cleanup ResizeObserver
|
|
406
|
+
if (resizeObserverRef.current) {
|
|
407
|
+
resizeObserverRef.current.disconnect()
|
|
408
|
+
}
|
|
360
409
|
}
|
|
361
410
|
}, []) // Empty deps - run cleanup only on unmount
|
|
362
411
|
|
|
@@ -366,6 +415,7 @@ export function useClaudeTerminal(options: UseClaudeTerminalOptions) {
|
|
|
366
415
|
disconnect,
|
|
367
416
|
sendInput,
|
|
368
417
|
focusTerminal,
|
|
418
|
+
fit,
|
|
369
419
|
isConnected,
|
|
370
420
|
isLoading,
|
|
371
421
|
isReconnecting,
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import { useQuery } from '@tanstack/react-query'
|
|
4
4
|
import type { ProjectStats, RawProjectFiles } from '@/lib/parse-prjct-files'
|
|
5
|
+
import type {
|
|
6
|
+
UnifiedApiResponse,
|
|
7
|
+
ProjectState,
|
|
8
|
+
OutcomeSummary,
|
|
9
|
+
AgentPerformance,
|
|
10
|
+
ProjectInsights,
|
|
11
|
+
} from '@prjct/shared'
|
|
5
12
|
|
|
6
13
|
interface ProjectStatsResponse {
|
|
7
14
|
success: boolean
|
|
@@ -15,6 +22,7 @@ interface ProjectStatsData {
|
|
|
15
22
|
raw: RawProjectFiles
|
|
16
23
|
}
|
|
17
24
|
|
|
25
|
+
// Legacy fetch
|
|
18
26
|
async function fetchProjectStats(projectId: string): Promise<ProjectStatsData> {
|
|
19
27
|
const res = await fetch(`/api/projects/${projectId}/stats`, {
|
|
20
28
|
cache: 'no-store',
|
|
@@ -36,3 +44,50 @@ export function useProjectStats(projectId: string) {
|
|
|
36
44
|
enabled: !!projectId,
|
|
37
45
|
})
|
|
38
46
|
}
|
|
47
|
+
|
|
48
|
+
// ============== Unified API ==============
|
|
49
|
+
|
|
50
|
+
interface UnifiedProjectData {
|
|
51
|
+
state: ProjectState | null
|
|
52
|
+
outcomes: OutcomeSummary | null
|
|
53
|
+
agentPerformance: AgentPerformance[]
|
|
54
|
+
insights: ProjectInsights
|
|
55
|
+
legacyFallback: boolean
|
|
56
|
+
// Legacy data when fallback
|
|
57
|
+
legacyData?: ProjectStats
|
|
58
|
+
legacyRaw?: RawProjectFiles
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function fetchUnifiedProjectData(projectId: string): Promise<UnifiedProjectData> {
|
|
62
|
+
const res = await fetch(`/api/v2/projects/${projectId}/unified`, {
|
|
63
|
+
cache: 'no-store',
|
|
64
|
+
})
|
|
65
|
+
if (!res.ok) throw new Error('Failed to fetch unified project data')
|
|
66
|
+
const json: UnifiedApiResponse = await res.json()
|
|
67
|
+
if (!json.success) {
|
|
68
|
+
throw new Error('Failed to fetch unified project data')
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
state: json.state,
|
|
72
|
+
outcomes: json.outcomes,
|
|
73
|
+
agentPerformance: json.agentPerformance,
|
|
74
|
+
insights: json.insights,
|
|
75
|
+
legacyFallback: json.legacyFallback,
|
|
76
|
+
legacyData: json.legacyData as ProjectStats | undefined,
|
|
77
|
+
legacyRaw: json.legacyRaw as RawProjectFiles | undefined,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Hook for fetching unified project data from v2 API.
|
|
83
|
+
* Falls back to legacy data if unified state doesn't exist.
|
|
84
|
+
*/
|
|
85
|
+
export function useUnifiedProjectStats(projectId: string) {
|
|
86
|
+
return useQuery({
|
|
87
|
+
queryKey: ['project-unified', projectId],
|
|
88
|
+
queryFn: () => fetchUnifiedProjectData(projectId),
|
|
89
|
+
staleTime: 30_000, // Cache 30s
|
|
90
|
+
refetchOnWindowFocus: true,
|
|
91
|
+
enabled: !!projectId,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
|
|
4
4
|
import { queryPresets } from '@/lib/query-config'
|
|
5
|
+
import { deleteProject as deleteProjectAction } from '@/lib/actions/projects'
|
|
5
6
|
|
|
6
7
|
export interface Project {
|
|
7
8
|
id: string
|
|
@@ -53,18 +54,17 @@ export function useProject(projectId: string | null) {
|
|
|
53
54
|
})
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
// Hook for deleting a project
|
|
57
|
+
// Hook for deleting a project (uses Server Action)
|
|
57
58
|
export function useDeleteProject() {
|
|
58
59
|
const queryClient = useQueryClient()
|
|
59
60
|
|
|
60
61
|
return useMutation({
|
|
61
62
|
mutationFn: async (projectId: string) => {
|
|
62
|
-
const
|
|
63
|
-
if (!
|
|
64
|
-
|
|
65
|
-
throw new Error(error.error || 'Failed to delete project')
|
|
63
|
+
const result = await deleteProjectAction(projectId)
|
|
64
|
+
if (!result.success) {
|
|
65
|
+
throw new Error(result.error || 'Failed to delete project')
|
|
66
66
|
}
|
|
67
|
-
return
|
|
67
|
+
return result
|
|
68
68
|
},
|
|
69
69
|
onSuccess: () => {
|
|
70
70
|
queryClient.invalidateQueries({ queryKey: ['projects'] })
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use server'
|
|
2
|
+
|
|
3
|
+
import { revalidatePath } from 'next/cache'
|
|
4
|
+
import { moveToTrash } from '@/lib/projects'
|
|
5
|
+
|
|
6
|
+
export async function deleteProject(projectId: string) {
|
|
7
|
+
try {
|
|
8
|
+
const result = await moveToTrash(projectId)
|
|
9
|
+
revalidatePath('/')
|
|
10
|
+
return { success: true, ...result }
|
|
11
|
+
} catch (error) {
|
|
12
|
+
const message = error instanceof Error ? error.message : 'Failed to delete project'
|
|
13
|
+
return { success: false, error: message }
|
|
14
|
+
}
|
|
15
|
+
}
|