prjct-cli 0.18.2 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/CHANGELOG.md +82 -0
  2. package/CLAUDE.md +74 -211
  3. package/core/agentic/prompt-builder.ts +3 -7
  4. package/core/command-registry/optional-commands.ts +0 -20
  5. package/core/infrastructure/command-installer/command-installer.ts +8 -1
  6. package/core/infrastructure/command-installer/global-config.ts +31 -1
  7. package/core/infrastructure/command-installer/index.ts +1 -1
  8. package/core/infrastructure/setup.ts +3 -0
  9. package/package.json +3 -17
  10. package/templates/agentic/agents/uxui.md +210 -0
  11. package/templates/commands/bug.md +219 -41
  12. package/templates/commands/done.md +57 -258
  13. package/templates/commands/feature.md +368 -80
  14. package/templates/commands/now.md +72 -277
  15. package/templates/commands/ship.md +167 -246
  16. package/templates/commands/sync.md +62 -3
  17. package/templates/commands/test.md +160 -20
  18. package/templates/global/CLAUDE.md +40 -205
  19. package/templates/global/docs/agents.md +88 -0
  20. package/templates/global/docs/architecture.md +103 -0
  21. package/templates/global/docs/commands.md +98 -0
  22. package/templates/global/docs/validation.md +95 -0
  23. package/bin/dev.js +0 -216
  24. package/bin/serve.js +0 -361
  25. package/packages/web/README.md +0 -36
  26. package/packages/web/app/api/claude/sessions/route.ts +0 -44
  27. package/packages/web/app/api/claude/status/route.ts +0 -34
  28. package/packages/web/app/api/projects/[id]/icon/route.ts +0 -33
  29. package/packages/web/app/api/projects/[id]/momentum/route.ts +0 -257
  30. package/packages/web/app/api/projects/[id]/route.ts +0 -29
  31. package/packages/web/app/api/projects/[id]/stats/route.ts +0 -41
  32. package/packages/web/app/api/projects/[id]/status/route.ts +0 -21
  33. package/packages/web/app/api/projects/route.ts +0 -16
  34. package/packages/web/app/api/sessions/current/route.ts +0 -132
  35. package/packages/web/app/api/sessions/history/route.ts +0 -204
  36. package/packages/web/app/error.tsx +0 -34
  37. package/packages/web/app/favicon.ico +0 -0
  38. package/packages/web/app/globals.css +0 -198
  39. package/packages/web/app/layout.tsx +0 -53
  40. package/packages/web/app/loading.tsx +0 -7
  41. package/packages/web/app/not-found.tsx +0 -25
  42. package/packages/web/app/page.tsx +0 -12
  43. package/packages/web/app/project/[id]/code/layout.tsx +0 -18
  44. package/packages/web/app/project/[id]/code/page.tsx +0 -408
  45. package/packages/web/app/project/[id]/error.tsx +0 -41
  46. package/packages/web/app/project/[id]/loading.tsx +0 -9
  47. package/packages/web/app/project/[id]/not-found.tsx +0 -27
  48. package/packages/web/app/project/[id]/page.tsx +0 -384
  49. package/packages/web/app/project/[id]/reports/page.tsx +0 -59
  50. package/packages/web/app/project/[id]/reports/print/page.tsx +0 -58
  51. package/packages/web/app/sessions/page.tsx +0 -165
  52. package/packages/web/app/settings/page.tsx +0 -151
  53. package/packages/web/components/ActivityTimeline/ActivityTimeline.constants.ts +0 -2
  54. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -49
  55. package/packages/web/components/ActivityTimeline/ActivityTimeline.types.ts +0 -8
  56. package/packages/web/components/ActivityTimeline/hooks/index.ts +0 -2
  57. package/packages/web/components/ActivityTimeline/hooks/useExpandable.ts +0 -9
  58. package/packages/web/components/ActivityTimeline/hooks/useGroupedEvents.ts +0 -23
  59. package/packages/web/components/ActivityTimeline/index.ts +0 -2
  60. package/packages/web/components/AgentsCard/AgentsCard.tsx +0 -93
  61. package/packages/web/components/AgentsCard/AgentsCard.types.ts +0 -14
  62. package/packages/web/components/AgentsCard/index.ts +0 -2
  63. package/packages/web/components/AppSidebar/AppSidebar.tsx +0 -316
  64. package/packages/web/components/AppSidebar/index.ts +0 -1
  65. package/packages/web/components/BackLink/BackLink.tsx +0 -18
  66. package/packages/web/components/BackLink/BackLink.types.ts +0 -5
  67. package/packages/web/components/BackLink/index.ts +0 -2
  68. package/packages/web/components/BentoCard/BentoCard.constants.ts +0 -16
  69. package/packages/web/components/BentoCard/BentoCard.tsx +0 -48
  70. package/packages/web/components/BentoCard/BentoCard.types.ts +0 -15
  71. package/packages/web/components/BentoCard/index.ts +0 -2
  72. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.constants.ts +0 -9
  73. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.tsx +0 -18
  74. package/packages/web/components/BentoCardSkeleton/BentoCardSkeleton.types.ts +0 -5
  75. package/packages/web/components/BentoCardSkeleton/index.ts +0 -2
  76. package/packages/web/components/BentoGrid/BentoGrid.tsx +0 -18
  77. package/packages/web/components/BentoGrid/BentoGrid.types.ts +0 -4
  78. package/packages/web/components/BentoGrid/index.ts +0 -2
  79. package/packages/web/components/BlockersCard/BlockersCard.tsx +0 -75
  80. package/packages/web/components/BlockersCard/BlockersCard.types.ts +0 -12
  81. package/packages/web/components/BlockersCard/index.ts +0 -2
  82. package/packages/web/components/CommandBar/CommandBar.tsx +0 -67
  83. package/packages/web/components/CommandBar/index.ts +0 -1
  84. package/packages/web/components/CommandButton/CommandButton.tsx +0 -46
  85. package/packages/web/components/CommandButton/index.ts +0 -1
  86. package/packages/web/components/ConnectionStatus/ConnectionStatus.tsx +0 -29
  87. package/packages/web/components/ConnectionStatus/index.ts +0 -1
  88. package/packages/web/components/DashboardContent/DashboardContent.tsx +0 -284
  89. package/packages/web/components/DashboardContent/index.ts +0 -1
  90. package/packages/web/components/DateGroup/DateGroup.tsx +0 -18
  91. package/packages/web/components/DateGroup/DateGroup.types.ts +0 -6
  92. package/packages/web/components/DateGroup/DateGroup.utils.ts +0 -11
  93. package/packages/web/components/DateGroup/index.ts +0 -2
  94. package/packages/web/components/EmptyState/EmptyState.tsx +0 -76
  95. package/packages/web/components/EmptyState/EmptyState.types.ts +0 -11
  96. package/packages/web/components/EmptyState/index.ts +0 -2
  97. package/packages/web/components/EventRow/EventRow.constants.ts +0 -10
  98. package/packages/web/components/EventRow/EventRow.tsx +0 -49
  99. package/packages/web/components/EventRow/EventRow.types.ts +0 -7
  100. package/packages/web/components/EventRow/EventRow.utils.ts +0 -49
  101. package/packages/web/components/EventRow/index.ts +0 -2
  102. package/packages/web/components/ExpandButton/ExpandButton.tsx +0 -18
  103. package/packages/web/components/ExpandButton/ExpandButton.types.ts +0 -6
  104. package/packages/web/components/ExpandButton/index.ts +0 -2
  105. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.tsx +0 -14
  106. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.types.ts +0 -5
  107. package/packages/web/components/HealthGradientBackground/HealthGradientBackground.utils.ts +0 -13
  108. package/packages/web/components/HealthGradientBackground/index.ts +0 -2
  109. package/packages/web/components/HeroSection/HeroSection.tsx +0 -92
  110. package/packages/web/components/HeroSection/HeroSection.types.ts +0 -14
  111. package/packages/web/components/HeroSection/HeroSection.utils.ts +0 -11
  112. package/packages/web/components/HeroSection/hooks/index.ts +0 -2
  113. package/packages/web/components/HeroSection/hooks/useCountUp.ts +0 -45
  114. package/packages/web/components/HeroSection/hooks/useWeeklyActivity.ts +0 -18
  115. package/packages/web/components/HeroSection/index.ts +0 -2
  116. package/packages/web/components/IdeasCard/IdeasCard.tsx +0 -115
  117. package/packages/web/components/IdeasCard/IdeasCard.types.ts +0 -10
  118. package/packages/web/components/IdeasCard/index.ts +0 -2
  119. package/packages/web/components/InsightMessage/InsightMessage.tsx +0 -9
  120. package/packages/web/components/InsightMessage/InsightMessage.types.ts +0 -3
  121. package/packages/web/components/InsightMessage/index.ts +0 -2
  122. package/packages/web/components/Logo/Logo.tsx +0 -65
  123. package/packages/web/components/Logo/index.ts +0 -1
  124. package/packages/web/components/MarkdownContent/MarkdownContent.tsx +0 -123
  125. package/packages/web/components/MarkdownContent/index.ts +0 -1
  126. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +0 -18
  127. package/packages/web/components/MasonryGrid/index.ts +0 -1
  128. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +0 -119
  129. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +0 -16
  130. package/packages/web/components/MomentumWidget/index.ts +0 -2
  131. package/packages/web/components/NowCard/NowCard.tsx +0 -118
  132. package/packages/web/components/NowCard/NowCard.types.ts +0 -16
  133. package/packages/web/components/NowCard/index.ts +0 -2
  134. package/packages/web/components/PageHeader/PageHeader.tsx +0 -24
  135. package/packages/web/components/PageHeader/index.ts +0 -1
  136. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +0 -20
  137. package/packages/web/components/ProgressRing/ProgressRing.tsx +0 -51
  138. package/packages/web/components/ProgressRing/ProgressRing.types.ts +0 -11
  139. package/packages/web/components/ProgressRing/index.ts +0 -2
  140. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +0 -54
  141. package/packages/web/components/ProjectAvatar/index.ts +0 -1
  142. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +0 -37
  143. package/packages/web/components/ProjectColorDot/index.ts +0 -1
  144. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +0 -104
  145. package/packages/web/components/ProjectSelectorModal/index.ts +0 -1
  146. package/packages/web/components/Providers/Providers.tsx +0 -48
  147. package/packages/web/components/Providers/index.ts +0 -1
  148. package/packages/web/components/QueueCard/QueueCard.tsx +0 -125
  149. package/packages/web/components/QueueCard/QueueCard.types.ts +0 -12
  150. package/packages/web/components/QueueCard/QueueCard.utils.ts +0 -12
  151. package/packages/web/components/QueueCard/index.ts +0 -2
  152. package/packages/web/components/RecoverCard/RecoverCard.tsx +0 -72
  153. package/packages/web/components/RecoverCard/RecoverCard.types.ts +0 -16
  154. package/packages/web/components/RecoverCard/index.ts +0 -2
  155. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +0 -145
  156. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +0 -16
  157. package/packages/web/components/RoadmapCard/index.ts +0 -2
  158. package/packages/web/components/ShipsCard/ShipsCard.tsx +0 -95
  159. package/packages/web/components/ShipsCard/ShipsCard.types.ts +0 -14
  160. package/packages/web/components/ShipsCard/ShipsCard.utils.ts +0 -4
  161. package/packages/web/components/ShipsCard/index.ts +0 -2
  162. package/packages/web/components/SparklineChart/SparklineChart.tsx +0 -40
  163. package/packages/web/components/SparklineChart/SparklineChart.types.ts +0 -6
  164. package/packages/web/components/SparklineChart/index.ts +0 -2
  165. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +0 -95
  166. package/packages/web/components/StatsMasonry/index.ts +0 -1
  167. package/packages/web/components/StreakCard/StreakCard.constants.ts +0 -2
  168. package/packages/web/components/StreakCard/StreakCard.tsx +0 -55
  169. package/packages/web/components/StreakCard/StreakCard.types.ts +0 -4
  170. package/packages/web/components/StreakCard/index.ts +0 -2
  171. package/packages/web/components/TasksCounter/TasksCounter.tsx +0 -14
  172. package/packages/web/components/TasksCounter/TasksCounter.types.ts +0 -3
  173. package/packages/web/components/TasksCounter/index.ts +0 -2
  174. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +0 -28
  175. package/packages/web/components/TechStackBadges/index.ts +0 -1
  176. package/packages/web/components/TerminalDock/DockToggleTab.tsx +0 -29
  177. package/packages/web/components/TerminalDock/TerminalDock.tsx +0 -386
  178. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +0 -130
  179. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +0 -142
  180. package/packages/web/components/TerminalDock/index.ts +0 -2
  181. package/packages/web/components/TerminalTabs/TerminalTab.tsx +0 -95
  182. package/packages/web/components/TerminalTabs/TerminalTabs.tsx +0 -211
  183. package/packages/web/components/TerminalTabs/index.ts +0 -1
  184. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +0 -32
  185. package/packages/web/components/VelocityBadge/VelocityBadge.types.ts +0 -3
  186. package/packages/web/components/VelocityBadge/index.ts +0 -2
  187. package/packages/web/components/VelocityCard/VelocityCard.tsx +0 -73
  188. package/packages/web/components/VelocityCard/VelocityCard.types.ts +0 -7
  189. package/packages/web/components/VelocityCard/index.ts +0 -2
  190. package/packages/web/components/WeeklyReports/PrintableReport.tsx +0 -259
  191. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +0 -187
  192. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +0 -288
  193. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +0 -149
  194. package/packages/web/components/WeeklyReports/index.ts +0 -4
  195. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +0 -25
  196. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +0 -4
  197. package/packages/web/components/WeeklySparkline/index.ts +0 -2
  198. package/packages/web/components/charts/SessionsChart.tsx +0 -175
  199. package/packages/web/components/ui/alert-dialog.tsx +0 -157
  200. package/packages/web/components/ui/badge.tsx +0 -46
  201. package/packages/web/components/ui/button.tsx +0 -60
  202. package/packages/web/components/ui/card.tsx +0 -92
  203. package/packages/web/components/ui/chart.tsx +0 -385
  204. package/packages/web/components/ui/dialog.tsx +0 -143
  205. package/packages/web/components/ui/drawer.tsx +0 -135
  206. package/packages/web/components/ui/dropdown-menu.tsx +0 -257
  207. package/packages/web/components/ui/input.tsx +0 -21
  208. package/packages/web/components/ui/scroll-area.tsx +0 -58
  209. package/packages/web/components/ui/select.tsx +0 -187
  210. package/packages/web/components/ui/sheet.tsx +0 -139
  211. package/packages/web/components/ui/tabs.tsx +0 -66
  212. package/packages/web/components/ui/tooltip.tsx +0 -61
  213. package/packages/web/components.json +0 -22
  214. package/packages/web/context/GlobalTerminalContext.tsx +0 -538
  215. package/packages/web/context/TerminalContext.tsx +0 -45
  216. package/packages/web/context/TerminalTabsContext.tsx +0 -181
  217. package/packages/web/eslint.config.mjs +0 -18
  218. package/packages/web/hooks/useClaudeTerminal.ts +0 -425
  219. package/packages/web/hooks/useProjectStats.ts +0 -93
  220. package/packages/web/hooks/useProjects.ts +0 -73
  221. package/packages/web/lib/actions/projects.ts +0 -15
  222. package/packages/web/lib/commands.ts +0 -81
  223. package/packages/web/lib/format.ts +0 -23
  224. package/packages/web/lib/generate-week-report.ts +0 -285
  225. package/packages/web/lib/parse-prjct-files.ts +0 -1123
  226. package/packages/web/lib/project-colors.ts +0 -58
  227. package/packages/web/lib/projects.ts +0 -506
  228. package/packages/web/lib/pty.ts +0 -101
  229. package/packages/web/lib/query-config.ts +0 -44
  230. package/packages/web/lib/services/index.ts +0 -9
  231. package/packages/web/lib/services/projects.server.ts +0 -66
  232. package/packages/web/lib/services/stats.server.ts +0 -562
  233. package/packages/web/lib/unified-loader.ts +0 -396
  234. package/packages/web/lib/utils.ts +0 -6
  235. package/packages/web/next-env.d.ts +0 -6
  236. package/packages/web/next.config.ts +0 -7
  237. package/packages/web/package.json +0 -57
  238. package/packages/web/postcss.config.mjs +0 -7
  239. package/packages/web/public/file.svg +0 -1
  240. package/packages/web/public/globe.svg +0 -1
  241. package/packages/web/public/next.svg +0 -1
  242. package/packages/web/public/vercel.svg +0 -1
  243. package/packages/web/public/window.svg +0 -1
  244. package/packages/web/server.ts +0 -312
  245. package/packages/web/tsconfig.json +0 -34
  246. package/templates/commands/serve.md +0 -121
@@ -1,384 +0,0 @@
1
- import { notFound } from 'next/navigation'
2
- import type { Metadata } from 'next'
3
- import {
4
- getStats,
5
- getInsightMessage,
6
- calculateStreak,
7
- getVelocityChange,
8
- getWeeklyVelocityData,
9
- type StatsResult
10
- } from '@/lib/services/stats.server'
11
- import { getProject } from '@/lib/services/projects.server'
12
- import { getProjectEmoji } from '@/lib/project-colors'
13
- import type { TimelineEvent } from '@/lib/parse-prjct-files'
14
-
15
- import { HeroSection } from '@/components/HeroSection'
16
- import { StatsMasonry } from '@/components/StatsMasonry'
17
-
18
- // Dynamic metadata for browser tab title
19
- export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
20
- const { id: projectId } = await params
21
- const project = await getProject(projectId)
22
- const projectName = project?.name ?? projectId
23
- const emoji = getProjectEmoji(projectId)
24
-
25
- return {
26
- title: `${emoji} ${projectName} / p.`,
27
- }
28
- }
29
-
30
- // Types for normalized component data
31
- interface NormalizedCurrentTask {
32
- task: string
33
- startedAt?: string
34
- agent?: string
35
- estimatedDuration?: string
36
- pausedAt?: string
37
- pauseReason?: string
38
- }
39
-
40
- interface NormalizedQueueItem {
41
- task: string
42
- priority?: 'low' | 'medium' | 'high' | 'critical' | number
43
- estimatedDuration?: string
44
- }
45
-
46
- interface NormalizedShip {
47
- name: string
48
- date: string
49
- duration?: string
50
- }
51
-
52
- interface NormalizedIdea {
53
- title: string
54
- impact?: string
55
- }
56
-
57
- interface NormalizedAgent {
58
- name: string
59
- description?: string
60
- successRate?: number
61
- tasksCompleted?: number
62
- bestFor?: string[]
63
- }
64
-
65
- interface NormalizedRoadmap {
66
- phases: Array<{
67
- name: string
68
- progress: number
69
- features?: Array<{ name: string; status: string }>
70
- }>
71
- progress: number
72
- }
73
-
74
- // Data normalization functions
75
- function normalizeCurrentTask(stats: StatsResult): NormalizedCurrentTask | null {
76
- if (stats.state?.currentTask) {
77
- return {
78
- task: stats.state.currentTask.description,
79
- startedAt: stats.state.currentTask.startedAt,
80
- // Simplified - removed legacy fields not in new schema
81
- }
82
- }
83
- return stats.legacyStats?.currentTask ?? null
84
- }
85
-
86
- function normalizeQueue(stats: StatsResult): NormalizedQueueItem[] {
87
- if (stats.queue?.tasks) {
88
- return stats.queue.tasks
89
- .filter(t => !t.completed)
90
- .map(q => ({
91
- task: q.description,
92
- priority: q.priority,
93
- }))
94
- }
95
- return stats.legacyStats?.queue ?? []
96
- }
97
-
98
- function normalizeRoadmap(stats: StatsResult): NormalizedRoadmap | null {
99
- const features = stats.roadmap?.features ?? []
100
- if (features.length > 0) {
101
- const completed = features.filter(f =>
102
- f.status === 'shipped' || f.status === 'completed'
103
- ).length
104
-
105
- return {
106
- phases: features.map(f => ({
107
- name: f.name,
108
- progress: f.status === 'shipped' || f.status === 'completed' ? 100 :
109
- f.status === 'active' ? 50 : 0,
110
- features: f.tasks.map(t => ({
111
- name: t.description,
112
- status: t.completed ? 'completed' : 'pending'
113
- }))
114
- })),
115
- progress: Math.round((completed / features.length) * 100)
116
- }
117
- }
118
- return stats.legacyStats?.roadmap ?? null
119
- }
120
-
121
- function normalizeShipped(stats: StatsResult): NormalizedShip[] {
122
- // Try new format first
123
- const items = stats.shipped?.items ?? []
124
- if (items.length > 0) {
125
- return items.map(s => ({
126
- name: s.name,
127
- date: s.shippedAt || s.date || new Date().toISOString(),
128
- }))
129
- }
130
- // Fallback to legacy
131
- return (stats.legacyStats?.shipped ?? []).map(s => ({
132
- name: s.name,
133
- date: s.date,
134
- duration: s.time,
135
- }))
136
- }
137
-
138
- function normalizeIdeas(stats: StatsResult): NormalizedIdea[] {
139
- // Try new format first
140
- const ideas = stats.ideas?.ideas ?? []
141
- if (ideas.length > 0) {
142
- return ideas
143
- .filter(i => i.status === 'pending')
144
- .map(i => ({
145
- title: i.text,
146
- impact: i.priority?.toUpperCase() || 'MEDIUM'
147
- }))
148
- }
149
- // Fallback to legacy
150
- return (stats.legacyStats?.ideas?.pending ?? []).map(i => ({
151
- title: i.title,
152
- impact: i.impact?.toUpperCase() || 'MEDIUM'
153
- }))
154
- }
155
-
156
- function normalizeAgents(stats: StatsResult): NormalizedAgent[] {
157
- // Try new format first
158
- if (stats.agents.length > 0) {
159
- return stats.agents.map(a => ({
160
- name: a.name,
161
- description: a.description,
162
- successRate: a.successRate,
163
- tasksCompleted: a.tasksCompleted,
164
- bestFor: a.bestFor,
165
- }))
166
- }
167
- // Fallback to legacy
168
- return (stats.legacyStats?.agents ?? []).map(a => ({
169
- name: a.name,
170
- description: a.role,
171
- bestFor: a.whenToUse,
172
- }))
173
- }
174
-
175
- function normalizeTimeline(stats: StatsResult): TimelineEvent[] {
176
- if (stats.metrics?.recentActivity?.length) {
177
- return stats.metrics.recentActivity.map(a => ({
178
- ts: a.timestamp,
179
- type: a.action || a.type || 'task_completed',
180
- task: a.description || '',
181
- }))
182
- }
183
- return stats.legacyStats?.timeline ?? []
184
- }
185
-
186
- function getVelocity(stats: StatsResult): number {
187
- if (stats.metrics?.velocity?.tasksPerDay) {
188
- return stats.metrics.velocity.tasksPerDay
189
- }
190
- return stats.legacyStats?.metrics?.velocity?.tasksPerDay ?? 0
191
- }
192
-
193
- function getTotalShips(stats: StatsResult): number {
194
- // Use shipped.md items count (legacyStats.shipped) as source of truth
195
- return stats.shipped?.items?.length ?? stats.legacyStats?.shipped?.length ?? 0
196
- }
197
-
198
- function getCompletionRate(stats: StatsResult): number {
199
- const completed = stats.legacyStats?.shipped?.length ?? 0
200
- const pending = stats.legacyStats?.queue?.length ?? 0
201
- const total = completed + pending
202
- if (total === 0) return 0
203
- return Math.round((completed / total) * 100)
204
- }
205
-
206
- // Calculate streak from legacy timeline
207
- function calculateStreakFromTimeline(stats: StatsResult): number {
208
- const timeline = stats.legacyStats?.timeline ?? []
209
- if (timeline.length === 0) return 0
210
-
211
- const activityDates = new Set(
212
- timeline.map(e => e.ts?.split('T')[0]).filter(Boolean)
213
- )
214
-
215
- const today = new Date()
216
- today.setHours(0, 0, 0, 0)
217
-
218
- const days = Array.from({ length: 30 }, (_, i) => {
219
- const date = new Date(today)
220
- date.setDate(date.getDate() - i)
221
- return date.toISOString().split('T')[0]
222
- })
223
-
224
- const firstGapIndex = days.findIndex(date => !activityDates.has(date))
225
-
226
- if (firstGapIndex === 0) {
227
- const yesterdayHasActivity = activityDates.has(days[1])
228
- if (!yesterdayHasActivity) return 0
229
- const remainingDays = days.slice(1)
230
- const gapFromYesterday = remainingDays.findIndex(date => !activityDates.has(date))
231
- return gapFromYesterday === -1 ? remainingDays.length : gapFromYesterday
232
- }
233
-
234
- return firstGapIndex === -1 ? days.length : firstGapIndex
235
- }
236
-
237
- // Get weekly velocity data from legacy timeline
238
- function getWeeklyDataFromTimeline(stats: StatsResult): number[] {
239
- const timeline = stats.legacyStats?.timeline ?? []
240
- if (timeline.length === 0) return []
241
-
242
- const today = new Date()
243
-
244
- return Array.from({ length: 7 }, (_, i) => {
245
- const date = new Date(today)
246
- date.setDate(date.getDate() - (6 - i))
247
- const dateStr = date.toISOString().split('T')[0]
248
-
249
- return timeline.filter(e => e.ts?.startsWith(dateStr)).length
250
- })
251
- }
252
-
253
- interface PageProps {
254
- params: Promise<{ id: string }>
255
- }
256
-
257
- export default async function ProjectStatsPage({ params }: PageProps) {
258
- const { id: projectId } = await params
259
-
260
- // Fetch data directly on server - no API calls
261
- const [project, stats] = await Promise.all([
262
- getProject(projectId),
263
- getStats(projectId)
264
- ])
265
-
266
- if (!stats.hasData) {
267
- notFound()
268
- }
269
-
270
- // Compute derived values using service functions
271
- // Use legacy timeline for streak/weekly if metrics is empty
272
- const streak = stats.metrics?.recentActivity?.length
273
- ? calculateStreak(stats.metrics)
274
- : calculateStreakFromTimeline(stats)
275
- const velocity = getVelocity(stats)
276
- const velocityChange = getVelocityChange(velocity)
277
- const insightMessage = getInsightMessage(stats, streak)
278
- const weeklyVelocityData = stats.metrics?.recentActivity?.length
279
- ? getWeeklyVelocityData(stats.metrics)
280
- : getWeeklyDataFromTimeline(stats)
281
-
282
- // Normalize data for components
283
- const currentTask = normalizeCurrentTask(stats)
284
- const queue = normalizeQueue(stats)
285
- const roadmap = normalizeRoadmap(stats)
286
- const shipped = normalizeShipped(stats)
287
- const ideas = normalizeIdeas(stats)
288
- const agents = normalizeAgents(stats)
289
- const timeline = normalizeTimeline(stats)
290
-
291
- // DRY: Use counts from getProject() - same source as dashboard
292
- const totalShips = project?.shippedCount ?? 0
293
- const completionRate = project?.completionRate ?? 0
294
-
295
- // Extract insights
296
- const { estimateAccuracy, blockers } = stats.insights
297
-
298
- return (
299
- <div className="flex h-full flex-col p-4 md:p-6 overflow-auto overflow-x-hidden">
300
- {/* Mobile: Add padding for hamburger menu */}
301
- <div className="pl-10 md:pl-0">
302
- <HeroSection
303
- projectId={projectId}
304
- projectName={project?.name ?? projectId}
305
- projectVersion={project?.version}
306
- totalShips={totalShips}
307
- completionRate={completionRate}
308
- streak={streak}
309
- insightMessage={insightMessage}
310
- timeline={timeline}
311
- />
312
- </div>
313
-
314
- {/* Quick Stats Bar */}
315
- <div className="grid grid-cols-2 sm:grid-cols-4 gap-2 sm:gap-3 mt-4 mb-6">
316
- <div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
317
- <div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
318
- <svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
319
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 3l14 9-14 9V3z" />
320
- </svg>
321
- </div>
322
- <div className="min-w-0">
323
- <div className="text-xl sm:text-2xl font-bold tabular-nums">{totalShips}</div>
324
- <div className="text-xs text-muted-foreground truncate">Shipped</div>
325
- </div>
326
- </div>
327
- <div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
328
- <div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
329
- <svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
330
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
331
- </svg>
332
- </div>
333
- <div className="min-w-0">
334
- <div className="text-xl sm:text-2xl font-bold tabular-nums">{queue.length}</div>
335
- <div className="text-xs text-muted-foreground truncate">Queue</div>
336
- </div>
337
- </div>
338
- <div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
339
- <div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
340
- <svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
341
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 18.657A8 8 0 016.343 7.343S7 9 9 10c0-2 .5-5 2.986-7C14 5 16.09 5.777 17.656 7.343A7.975 7.975 0 0120 13a7.975 7.975 0 01-2.343 5.657z" />
342
- </svg>
343
- </div>
344
- <div className="min-w-0">
345
- <div className="text-xl sm:text-2xl font-bold tabular-nums">{streak}</div>
346
- <div className="text-xs text-muted-foreground truncate">Streak</div>
347
- </div>
348
- </div>
349
- <div className="bg-card border rounded-lg p-2 sm:p-3 flex items-center gap-2 sm:gap-3">
350
- <div className="h-8 w-8 sm:h-10 sm:w-10 rounded-lg bg-muted flex items-center justify-center shrink-0">
351
- <svg className="h-4 w-4 sm:h-5 sm:w-5 text-muted-foreground" fill="none" viewBox="0 0 24 24" stroke="currentColor">
352
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
353
- </svg>
354
- </div>
355
- <div className="min-w-0">
356
- <div className="text-xl sm:text-2xl font-bold tabular-nums">{ideas.length}</div>
357
- <div className="text-xs text-muted-foreground truncate">Ideas</div>
358
- </div>
359
- </div>
360
- </div>
361
-
362
- {/* Main Content - Masonry Layout */}
363
- <StatsMasonry
364
- projectId={projectId}
365
- currentTask={currentTask}
366
- velocity={velocity}
367
- weeklyVelocityData={weeklyVelocityData}
368
- velocityChange={velocityChange}
369
- estimateAccuracy={estimateAccuracy}
370
- roadmap={roadmap}
371
- queue={queue}
372
- shipped={shipped}
373
- totalShips={totalShips}
374
- streak={streak}
375
- blockers={blockers}
376
- ideas={ideas}
377
- agents={agents}
378
- timeline={timeline}
379
- />
380
-
381
- <div className="h-4" />
382
- </div>
383
- )
384
- }
@@ -1,59 +0,0 @@
1
- import { notFound } from 'next/navigation'
2
- import Link from 'next/link'
3
- import type { Metadata } from 'next'
4
- import { ArrowLeft } from 'lucide-react'
5
- import { getStats } from '@/lib/services/stats.server'
6
- import { getProject } from '@/lib/services/projects.server'
7
- import { getProjectEmoji } from '@/lib/project-colors'
8
- import { WeeklyReports } from '@/components/WeeklyReports'
9
-
10
- export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
11
- const { id: projectId } = await params
12
- const project = await getProject(projectId)
13
- const projectName = project?.name ?? projectId
14
- const emoji = getProjectEmoji(projectId)
15
-
16
- return {
17
- title: `${emoji} ${projectName} / Reports / p.`,
18
- }
19
- }
20
-
21
- interface PageProps {
22
- params: Promise<{ id: string }>
23
- }
24
-
25
- export default async function ReportsPage({ params }: PageProps) {
26
- const { id: projectId } = await params
27
-
28
- const [project, stats] = await Promise.all([
29
- getProject(projectId),
30
- getStats(projectId)
31
- ])
32
-
33
- if (!stats.hasData) {
34
- notFound()
35
- }
36
-
37
- const projectName = project?.name ?? projectId
38
-
39
- return (
40
- <div className="flex h-full flex-col p-4 md:p-6 overflow-auto">
41
- {/* Header with back navigation */}
42
- <div className="pl-10 md:pl-0 mb-6">
43
- <Link
44
- href={`/project/${projectId}`}
45
- className="inline-flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4"
46
- >
47
- <ArrowLeft className="h-4 w-4" />
48
- Back to {projectName}
49
- </Link>
50
- <h1 className="text-3xl font-bold">Weekly Reports</h1>
51
- </div>
52
-
53
- <div className="pl-10 md:pl-0">
54
- <WeeklyReports stats={stats} projectName={projectName} />
55
- </div>
56
- <div className="h-4" />
57
- </div>
58
- )
59
- }
@@ -1,58 +0,0 @@
1
- import type { Metadata } from 'next'
2
- import { getStats } from '@/lib/services/stats.server'
3
- import { getProject } from '@/lib/projects'
4
- import { getProjectEmoji } from '@/lib/project-colors'
5
- import { PrintableReport } from '@/components/WeeklyReports/PrintableReport'
6
-
7
- export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
8
- const { id: projectId } = await params
9
- const project = await getProject(projectId)
10
- const projectName = project?.name ?? projectId
11
- const emoji = getProjectEmoji(projectId)
12
-
13
- return {
14
- title: `${emoji} ${projectName} / Print / p.`,
15
- }
16
- }
17
-
18
- interface Props {
19
- params: Promise<{ id: string }>
20
- searchParams: Promise<{ weeks?: string; year?: string }>
21
- }
22
-
23
- export default async function PrintReportPage({ params, searchParams }: Props) {
24
- const { id } = await params
25
- const { weeks, year } = await searchParams
26
-
27
- const project = await getProject(id)
28
- if (!project) {
29
- return <div className="p-8 text-center">Project not found</div>
30
- }
31
-
32
- const stats = await getStats(id)
33
-
34
- // Parse weeks from query params (comma-separated)
35
- const selectedWeeks = weeks
36
- ? weeks.split(',').map(Number).filter(n => !isNaN(n))
37
- : [getCurrentWeek()]
38
-
39
- const selectedYear = year ? parseInt(year) : new Date().getFullYear()
40
-
41
- return (
42
- <PrintableReport
43
- stats={stats}
44
- projectName={project.name}
45
- selectedWeeks={selectedWeeks}
46
- year={selectedYear}
47
- />
48
- )
49
- }
50
-
51
- function getCurrentWeek(): number {
52
- const now = new Date()
53
- const d = new Date(Date.UTC(now.getFullYear(), now.getMonth(), now.getDate()))
54
- const dayNum = d.getUTCDay() || 7
55
- d.setUTCDate(d.getUTCDate() + 4 - dayNum)
56
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1))
57
- return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1) / 7)
58
- }
@@ -1,165 +0,0 @@
1
- 'use client'
2
-
3
- import { useQuery } from '@tanstack/react-query'
4
- import { Clock, Target, CheckCircle, PauseCircle, GitCommit, FileCode } from 'lucide-react'
5
-
6
- function formatDuration(ms: number): string {
7
- const hours = Math.floor(ms / 3600000)
8
- const minutes = Math.floor((ms % 3600000) / 60000)
9
- if (hours > 0) return `${hours}h ${minutes}m`
10
- return `${minutes}m`
11
- }
12
-
13
- function getRelativeTime(dateString: string): string {
14
- const date = new Date(dateString)
15
- const now = new Date()
16
- const diff = now.getTime() - date.getTime()
17
- const minutes = Math.floor(diff / 60000)
18
- const hours = Math.floor(diff / 3600000)
19
- const days = Math.floor(diff / 86400000)
20
-
21
- if (minutes < 1) return 'just now'
22
- if (minutes < 60) return `${minutes}m ago`
23
- if (hours < 24) return `${hours}h ago`
24
- return `${days}d ago`
25
- }
26
-
27
- export default function Sessions() {
28
- const { data: projects } = useQuery({
29
- queryKey: ['projects'],
30
- queryFn: async () => {
31
- const res = await fetch('/api/projects')
32
- const json = await res.json()
33
- return json.data || []
34
- }
35
- })
36
-
37
- // Get sessions from first project (for demo)
38
- const projectId = projects?.[0]?.id
39
-
40
- const { data: sessions, isLoading } = useQuery({
41
- queryKey: ['sessions', projectId],
42
- queryFn: async () => {
43
- if (!projectId) return []
44
- const res = await fetch(`/api/sessions?projectId=${projectId}`)
45
- const json = await res.json()
46
- return json.data || []
47
- },
48
- enabled: !!projectId
49
- })
50
-
51
- if (isLoading) {
52
- return (
53
- <div className="flex items-center justify-center h-full">
54
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary" />
55
- </div>
56
- )
57
- }
58
-
59
- return (
60
- <div className="p-6 h-full overflow-auto">
61
- <header className="mb-6">
62
- <h1 className="text-2xl font-bold flex items-center gap-2">
63
- <Clock className="w-6 h-6" />
64
- Sessions
65
- </h1>
66
- <p className="text-muted-foreground mt-1">
67
- Your work session history
68
- </p>
69
- </header>
70
-
71
- {!sessions?.length ? (
72
- <div className="border border-dashed border-border rounded-lg p-8 text-center">
73
- <Clock className="w-12 h-12 text-muted-foreground mx-auto mb-4" />
74
- <p className="text-muted-foreground">
75
- No sessions yet. Start one with <code className="bg-muted px-2 py-1 rounded">/p:now</code>
76
- </p>
77
- </div>
78
- ) : (
79
- <div className="space-y-4">
80
- {sessions.map((session: Session) => (
81
- <SessionCard key={session.id} session={session} />
82
- ))}
83
- </div>
84
- )}
85
- </div>
86
- )
87
- }
88
-
89
- interface Session {
90
- id: string
91
- task: string
92
- status: 'active' | 'paused' | 'completed'
93
- startedAt: string
94
- completedAt?: string
95
- duration: number
96
- metrics: {
97
- filesChanged: number
98
- linesAdded: number
99
- linesRemoved: number
100
- commits: number
101
- }
102
- }
103
-
104
- function SessionCard({ session }: { session: Session }) {
105
- const statusConfig = {
106
- active: {
107
- icon: Target,
108
- color: 'text-green-500',
109
- bg: 'bg-green-500/10',
110
- label: 'Active'
111
- },
112
- paused: {
113
- icon: PauseCircle,
114
- color: 'text-yellow-500',
115
- bg: 'bg-yellow-500/10',
116
- label: 'Paused'
117
- },
118
- completed: {
119
- icon: CheckCircle,
120
- color: 'text-blue-500',
121
- bg: 'bg-blue-500/10',
122
- label: 'Completed'
123
- }
124
- }
125
-
126
- const status = statusConfig[session.status]
127
- const StatusIcon = status.icon
128
-
129
- return (
130
- <div className="bg-card border border-border rounded-lg p-4">
131
- <div className="flex items-start justify-between mb-3">
132
- <div className="flex-1">
133
- <h3 className="font-medium mb-1">{session.task}</h3>
134
- <div className="flex items-center gap-3 text-sm text-muted-foreground">
135
- <span>{getRelativeTime(session.startedAt)}</span>
136
- <span>•</span>
137
- <span>{formatDuration(session.duration)}</span>
138
- </div>
139
- </div>
140
-
141
- <div className={`flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-medium ${status.bg} ${status.color}`}>
142
- <StatusIcon className="w-3 h-3" />
143
- {status.label}
144
- </div>
145
- </div>
146
-
147
- <div className="flex items-center gap-4 text-sm text-muted-foreground">
148
- <span className="flex items-center gap-1">
149
- <FileCode className="w-3.5 h-3.5" />
150
- {session.metrics.filesChanged} files
151
- </span>
152
- <span className="flex items-center gap-1 text-green-500">
153
- +{session.metrics.linesAdded}
154
- </span>
155
- <span className="flex items-center gap-1 text-red-500">
156
- -{session.metrics.linesRemoved}
157
- </span>
158
- <span className="flex items-center gap-1">
159
- <GitCommit className="w-3.5 h-3.5" />
160
- {session.metrics.commits} commits
161
- </span>
162
- </div>
163
- </div>
164
- )
165
- }