prjct-cli 0.13.3 → 0.15.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.
Files changed (195) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/bin/prjct +10 -13
  3. package/core/agentic/memory-system/semantic-memories.ts +2 -1
  4. package/core/agentic/plan-mode/plan-mode.ts +2 -1
  5. package/core/agentic/prompt-builder.ts +22 -43
  6. package/core/agentic/services.ts +5 -5
  7. package/core/agentic/smart-context.ts +7 -2
  8. package/core/command-registry/core-commands.ts +54 -29
  9. package/core/command-registry/optional-commands.ts +64 -0
  10. package/core/command-registry/setup-commands.ts +18 -3
  11. package/core/commands/analysis.ts +21 -68
  12. package/core/commands/analytics.ts +247 -213
  13. package/core/commands/base.ts +1 -1
  14. package/core/commands/index.ts +41 -36
  15. package/core/commands/maintenance.ts +300 -31
  16. package/core/commands/planning.ts +233 -22
  17. package/core/commands/setup.ts +3 -8
  18. package/core/commands/shipping.ts +14 -18
  19. package/core/commands/types.ts +8 -6
  20. package/core/commands/workflow.ts +105 -100
  21. package/core/context/generator.ts +317 -0
  22. package/core/context-sync.ts +7 -350
  23. package/core/data/index.ts +13 -32
  24. package/core/data/md-ideas-manager.ts +155 -0
  25. package/core/data/md-queue-manager.ts +4 -3
  26. package/core/data/md-shipped-manager.ts +90 -0
  27. package/core/data/md-state-manager.ts +11 -7
  28. package/core/domain/agent-generator.ts +23 -63
  29. package/core/events/index.ts +143 -0
  30. package/core/index.ts +17 -14
  31. package/core/infrastructure/capability-installer.ts +13 -149
  32. package/core/infrastructure/migrator/project-scanner.ts +2 -1
  33. package/core/infrastructure/path-manager.ts +4 -6
  34. package/core/infrastructure/setup.ts +3 -0
  35. package/core/infrastructure/uuid-migration.ts +750 -0
  36. package/core/outcomes/recorder.ts +2 -1
  37. package/core/plugin/loader.ts +4 -7
  38. package/core/plugin/registry.ts +3 -3
  39. package/core/schemas/index.ts +23 -25
  40. package/core/schemas/state.ts +1 -0
  41. package/core/serializers/ideas-serializer.ts +187 -0
  42. package/core/serializers/index.ts +16 -0
  43. package/core/serializers/shipped-serializer.ts +108 -0
  44. package/core/session/utils.ts +3 -9
  45. package/core/storage/ideas-storage.ts +273 -0
  46. package/core/storage/index.ts +204 -0
  47. package/core/storage/queue-storage.ts +297 -0
  48. package/core/storage/shipped-storage.ts +223 -0
  49. package/core/storage/state-storage.ts +235 -0
  50. package/core/storage/storage-manager.ts +175 -0
  51. package/package.json +1 -1
  52. package/packages/web/app/api/projects/[id]/momentum/route.ts +257 -0
  53. package/packages/web/app/api/sessions/current/route.ts +132 -0
  54. package/packages/web/app/api/sessions/history/route.ts +96 -14
  55. package/packages/web/app/globals.css +5 -0
  56. package/packages/web/app/layout.tsx +2 -0
  57. package/packages/web/app/project/[id]/code/layout.tsx +18 -0
  58. package/packages/web/app/project/[id]/code/page.tsx +408 -0
  59. package/packages/web/app/project/[id]/page.tsx +359 -389
  60. package/packages/web/app/project/[id]/reports/page.tsx +59 -0
  61. package/packages/web/app/project/[id]/reports/print/page.tsx +58 -0
  62. package/packages/web/components/ActivityTimeline/ActivityTimeline.tsx +0 -1
  63. package/packages/web/components/AgentsCard/AgentsCard.tsx +64 -34
  64. package/packages/web/components/AgentsCard/AgentsCard.types.ts +1 -0
  65. package/packages/web/components/AppSidebar/AppSidebar.tsx +135 -11
  66. package/packages/web/components/BentoCard/BentoCard.constants.ts +3 -3
  67. package/packages/web/components/BentoCard/BentoCard.tsx +2 -1
  68. package/packages/web/components/BentoGrid/BentoGrid.tsx +2 -2
  69. package/packages/web/components/BlockersCard/BlockersCard.tsx +65 -57
  70. package/packages/web/components/BlockersCard/BlockersCard.types.ts +1 -0
  71. package/packages/web/components/CommandBar/CommandBar.tsx +67 -0
  72. package/packages/web/components/CommandBar/index.ts +1 -0
  73. package/packages/web/components/DashboardContent/DashboardContent.tsx +35 -5
  74. package/packages/web/components/DateGroup/DateGroup.tsx +1 -1
  75. package/packages/web/components/EmptyState/EmptyState.tsx +39 -21
  76. package/packages/web/components/EmptyState/EmptyState.types.ts +1 -0
  77. package/packages/web/components/EventRow/EventRow.tsx +4 -4
  78. package/packages/web/components/EventRow/EventRow.utils.ts +3 -3
  79. package/packages/web/components/HeroSection/HeroSection.tsx +52 -15
  80. package/packages/web/components/HeroSection/HeroSection.types.ts +4 -4
  81. package/packages/web/components/HeroSection/HeroSection.utils.ts +7 -3
  82. package/packages/web/components/IdeasCard/IdeasCard.tsx +94 -27
  83. package/packages/web/components/IdeasCard/IdeasCard.types.ts +1 -0
  84. package/packages/web/components/MasonryGrid/MasonryGrid.tsx +18 -0
  85. package/packages/web/components/MasonryGrid/index.ts +1 -0
  86. package/packages/web/components/MomentumWidget/MomentumWidget.tsx +119 -0
  87. package/packages/web/components/MomentumWidget/MomentumWidget.types.ts +16 -0
  88. package/packages/web/components/MomentumWidget/index.ts +2 -0
  89. package/packages/web/components/NowCard/NowCard.tsx +81 -56
  90. package/packages/web/components/NowCard/NowCard.types.ts +1 -0
  91. package/packages/web/components/PageHeader/PageHeader.tsx +24 -0
  92. package/packages/web/components/PageHeader/index.ts +1 -0
  93. package/packages/web/components/ProgressRing/ProgressRing.constants.ts +2 -2
  94. package/packages/web/components/ProjectAvatar/ProjectAvatar.tsx +2 -2
  95. package/packages/web/components/ProjectColorDot/ProjectColorDot.tsx +37 -0
  96. package/packages/web/components/ProjectColorDot/index.ts +1 -0
  97. package/packages/web/components/ProjectSelectorModal/ProjectSelectorModal.tsx +104 -0
  98. package/packages/web/components/ProjectSelectorModal/index.ts +1 -0
  99. package/packages/web/components/Providers/Providers.tsx +4 -1
  100. package/packages/web/components/QueueCard/QueueCard.tsx +78 -25
  101. package/packages/web/components/QueueCard/QueueCard.types.ts +1 -0
  102. package/packages/web/components/QueueCard/QueueCard.utils.ts +3 -3
  103. package/packages/web/components/RecoverCard/RecoverCard.tsx +72 -0
  104. package/packages/web/components/RecoverCard/RecoverCard.types.ts +16 -0
  105. package/packages/web/components/RecoverCard/index.ts +2 -0
  106. package/packages/web/components/RoadmapCard/RoadmapCard.tsx +101 -33
  107. package/packages/web/components/RoadmapCard/RoadmapCard.types.ts +1 -0
  108. package/packages/web/components/ShipsCard/ShipsCard.tsx +71 -28
  109. package/packages/web/components/ShipsCard/ShipsCard.types.ts +2 -0
  110. package/packages/web/components/SparklineChart/SparklineChart.tsx +20 -18
  111. package/packages/web/components/StatsMasonry/StatsMasonry.tsx +95 -0
  112. package/packages/web/components/StatsMasonry/index.ts +1 -0
  113. package/packages/web/components/StreakCard/StreakCard.tsx +37 -35
  114. package/packages/web/components/TasksCounter/TasksCounter.tsx +1 -1
  115. package/packages/web/components/TechStackBadges/TechStackBadges.tsx +12 -4
  116. package/packages/web/components/TerminalDock/DockToggleTab.tsx +29 -0
  117. package/packages/web/components/TerminalDock/TerminalDock.tsx +386 -0
  118. package/packages/web/components/TerminalDock/TerminalDockTab.tsx +130 -0
  119. package/packages/web/components/TerminalDock/TerminalTabBar.tsx +142 -0
  120. package/packages/web/components/TerminalDock/index.ts +2 -0
  121. package/packages/web/components/VelocityBadge/VelocityBadge.tsx +8 -3
  122. package/packages/web/components/VelocityCard/VelocityCard.tsx +49 -47
  123. package/packages/web/components/WeeklyReports/PrintableReport.tsx +259 -0
  124. package/packages/web/components/WeeklyReports/ReportPreviewCard.tsx +187 -0
  125. package/packages/web/components/WeeklyReports/WeekCalendar.tsx +288 -0
  126. package/packages/web/components/WeeklyReports/WeeklyReports.tsx +149 -0
  127. package/packages/web/components/WeeklyReports/index.ts +4 -0
  128. package/packages/web/components/WeeklySparkline/WeeklySparkline.tsx +16 -4
  129. package/packages/web/components/WeeklySparkline/WeeklySparkline.types.ts +1 -0
  130. package/packages/web/components/charts/SessionsChart.tsx +6 -3
  131. package/packages/web/components/ui/dialog.tsx +143 -0
  132. package/packages/web/components/ui/drawer.tsx +135 -0
  133. package/packages/web/components/ui/select.tsx +187 -0
  134. package/packages/web/context/GlobalTerminalContext.tsx +538 -0
  135. package/packages/web/lib/commands.ts +81 -0
  136. package/packages/web/lib/generate-week-report.ts +285 -0
  137. package/packages/web/lib/parse-prjct-files.ts +56 -55
  138. package/packages/web/lib/project-colors.ts +58 -0
  139. package/packages/web/lib/projects.ts +58 -5
  140. package/packages/web/lib/services/projects.server.ts +11 -1
  141. package/packages/web/next-env.d.ts +1 -1
  142. package/packages/web/package.json +5 -1
  143. package/templates/commands/analyze.md +39 -3
  144. package/templates/commands/ask.md +58 -3
  145. package/templates/commands/bug.md +117 -26
  146. package/templates/commands/dash.md +95 -158
  147. package/templates/commands/done.md +130 -148
  148. package/templates/commands/feature.md +125 -103
  149. package/templates/commands/git.md +18 -3
  150. package/templates/commands/idea.md +121 -38
  151. package/templates/commands/init.md +124 -20
  152. package/templates/commands/migrate-all.md +63 -28
  153. package/templates/commands/migrate.md +140 -0
  154. package/templates/commands/next.md +115 -5
  155. package/templates/commands/now.md +146 -82
  156. package/templates/commands/pause.md +89 -74
  157. package/templates/commands/redo.md +6 -4
  158. package/templates/commands/resume.md +141 -59
  159. package/templates/commands/setup.md +18 -3
  160. package/templates/commands/ship.md +103 -231
  161. package/templates/commands/spec.md +98 -8
  162. package/templates/commands/suggest.md +22 -2
  163. package/templates/commands/sync.md +192 -203
  164. package/templates/commands/undo.md +6 -4
  165. package/templates/mcp-config.json +20 -1
  166. package/core/data/agents-manager.ts +0 -76
  167. package/core/data/analysis-manager.ts +0 -83
  168. package/core/data/base-manager.ts +0 -156
  169. package/core/data/ideas-manager.ts +0 -81
  170. package/core/data/outcomes-manager.ts +0 -96
  171. package/core/data/project-manager.ts +0 -75
  172. package/core/data/roadmap-manager.ts +0 -118
  173. package/core/data/shipped-manager.ts +0 -65
  174. package/core/data/state-manager.ts +0 -214
  175. package/core/state/index.ts +0 -25
  176. package/core/state/manager.ts +0 -376
  177. package/core/state/types.ts +0 -185
  178. package/core/utils/project-capabilities.ts +0 -156
  179. package/core/view-generator.ts +0 -536
  180. package/packages/web/app/project/[id]/stats/loading.tsx +0 -43
  181. package/packages/web/app/project/[id]/stats/page.tsx +0 -253
  182. package/templates/agent-assignment.md +0 -72
  183. package/templates/analysis/project-analysis.md +0 -78
  184. package/templates/checklists/accessibility.md +0 -33
  185. package/templates/commands/build.md +0 -17
  186. package/templates/commands/decision.md +0 -226
  187. package/templates/commands/fix.md +0 -79
  188. package/templates/commands/help.md +0 -61
  189. package/templates/commands/progress.md +0 -14
  190. package/templates/commands/recap.md +0 -14
  191. package/templates/commands/roadmap.md +0 -52
  192. package/templates/commands/status.md +0 -17
  193. package/templates/commands/task.md +0 -63
  194. package/templates/commands/work.md +0 -44
  195. package/templates/commands/workflow.md +0 -12
@@ -0,0 +1,132 @@
1
+ import { NextResponse } from 'next/server'
2
+ import { promises as fs } from 'fs'
3
+ import { join } from 'path'
4
+ import { homedir } from 'os'
5
+
6
+ export const dynamic = 'force-dynamic'
7
+
8
+ interface Session {
9
+ id: string
10
+ projectId?: string
11
+ task: string
12
+ status: string
13
+ startedAt: string
14
+ timeline?: Array<{ type: string; at: string }>
15
+ context?: {
16
+ prompt?: string
17
+ promptLength?: number
18
+ files?: string[]
19
+ }
20
+ }
21
+
22
+ interface AbandonedSession {
23
+ id: string
24
+ task: string
25
+ projectId: string
26
+ projectName?: string
27
+ startedAt: string
28
+ lastActivity?: string
29
+ hoursAgo: number
30
+ prompt?: string
31
+ }
32
+
33
+ export async function GET(request: Request) {
34
+ try {
35
+ const { searchParams } = new URL(request.url)
36
+ const targetProjectId = searchParams.get('projectId')
37
+
38
+ const globalStorage = join(homedir(), '.prjct-cli', 'projects')
39
+
40
+ let projects: string[]
41
+ try {
42
+ projects = await fs.readdir(globalStorage)
43
+ } catch {
44
+ return NextResponse.json({
45
+ success: true,
46
+ data: {
47
+ currentSession: null,
48
+ abandonedSessions: []
49
+ }
50
+ })
51
+ }
52
+
53
+ const abandonedSessions: AbandonedSession[] = []
54
+ let currentSession: Session | null = null
55
+
56
+ const now = Date.now()
57
+ const ABANDON_THRESHOLD_HOURS = 8
58
+
59
+ for (const projectId of projects) {
60
+ // Skip hidden directories
61
+ if (projectId.startsWith('.')) continue
62
+
63
+ const sessionPath = join(globalStorage, projectId, 'sessions', 'current.json')
64
+
65
+ try {
66
+ const content = await fs.readFile(sessionPath, 'utf-8')
67
+ if (!content.trim()) continue
68
+
69
+ const session: Session = JSON.parse(content)
70
+
71
+ // Skip if not active
72
+ if (session.status !== 'active') continue
73
+
74
+ // Get last activity timestamp
75
+ const lastActivity = session.timeline?.[session.timeline.length - 1]?.at || session.startedAt
76
+ const lastActivityDate = new Date(lastActivity)
77
+ if (isNaN(lastActivityDate.getTime())) continue
78
+
79
+ const hoursAgo = (now - lastActivityDate.getTime()) / (1000 * 60 * 60)
80
+
81
+ // If this is the target project and session is recent, it's the current session
82
+ if (projectId === targetProjectId && hoursAgo < ABANDON_THRESHOLD_HOURS) {
83
+ currentSession = session
84
+ }
85
+ // If session is old (>8 hours), it's considered abandoned
86
+ // Only show abandoned sessions for the target project
87
+ else if (hoursAgo >= ABANDON_THRESHOLD_HOURS && projectId === targetProjectId) {
88
+ // Try to get project name from project.json
89
+ let projectName: string | undefined
90
+ try {
91
+ const projectJsonPath = join(globalStorage, projectId, 'project.json')
92
+ const projectJson = await fs.readFile(projectJsonPath, 'utf-8')
93
+ const projectData = JSON.parse(projectJson)
94
+ projectName = projectData.name
95
+ } catch {
96
+ // Ignore - project name is optional
97
+ }
98
+
99
+ abandonedSessions.push({
100
+ id: session.id,
101
+ task: session.task,
102
+ projectId,
103
+ projectName,
104
+ startedAt: session.startedAt,
105
+ lastActivity,
106
+ hoursAgo: Math.round(hoursAgo),
107
+ prompt: session.context?.prompt
108
+ })
109
+ }
110
+ } catch {
111
+ // Skip projects without valid session files
112
+ }
113
+ }
114
+
115
+ // Sort abandoned sessions by most recent first
116
+ abandonedSessions.sort((a, b) => a.hoursAgo - b.hoursAgo)
117
+
118
+ return NextResponse.json({
119
+ success: true,
120
+ data: {
121
+ currentSession,
122
+ abandonedSessions
123
+ }
124
+ })
125
+ } catch (error) {
126
+ console.error('Sessions current error:', error)
127
+ return NextResponse.json(
128
+ { success: false, error: 'Failed to fetch sessions' },
129
+ { status: 500 }
130
+ )
131
+ }
132
+ }
@@ -16,6 +16,7 @@ interface DailyStats {
16
16
  date: string
17
17
  tasks: number
18
18
  ships: number
19
+ partial: number
19
20
  }
20
21
 
21
22
  export async function GET() {
@@ -37,16 +38,19 @@ export async function GET() {
37
38
  }
38
39
 
39
40
  // Aggregate by date
40
- const dailyMap = new Map<string, { tasks: number; ships: number }>()
41
+ const dailyMap = new Map<string, { tasks: number; ships: number; partial: number }>()
41
42
 
42
- // Calculate date range (last 90 days)
43
+ // Calculate date range (last 30 days)
43
44
  const endDate = new Date()
44
45
  const startDate = new Date()
45
- startDate.setDate(startDate.getDate() - 90)
46
+ startDate.setDate(startDate.getDate() - 30)
46
47
 
47
48
  for (const projectId of projects) {
48
- const contextPath = join(globalStorage, projectId, 'memory', 'context.jsonl')
49
+ // Skip hidden directories
50
+ if (projectId.startsWith('.')) continue
49
51
 
52
+ // Read from memory/context.jsonl (legacy)
53
+ const contextPath = join(globalStorage, projectId, 'memory', 'context.jsonl')
50
54
  try {
51
55
  const content = await fs.readFile(contextPath, 'utf-8')
52
56
  const lines = content.trim().split('\n').filter(Boolean)
@@ -62,12 +66,12 @@ export async function GET() {
62
66
  if (eventDate < startDate || eventDate > endDate) continue
63
67
 
64
68
  const dateKey = eventDate.toISOString().split('T')[0]
65
- const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0 }
69
+ const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
66
70
 
67
71
  const eventType = event.type || event.action
68
72
 
69
73
  // Count tasks completed
70
- if (eventType === 'task_complete' || eventType === 'task_completed') {
74
+ if (eventType === 'task_complete' || eventType === 'task_completed' || eventType === 'session_completed') {
71
75
  current.tasks++
72
76
  }
73
77
 
@@ -76,6 +80,11 @@ export async function GET() {
76
80
  current.ships++
77
81
  }
78
82
 
83
+ // Count partial/abandoned sessions
84
+ if (eventType === 'session_partial' || eventType === 'session_abandoned') {
85
+ current.partial++
86
+ }
87
+
79
88
  dailyMap.set(dateKey, current)
80
89
  } catch {
81
90
  // Skip malformed lines
@@ -84,21 +93,94 @@ export async function GET() {
84
93
  } catch {
85
94
  // Skip projects without context.jsonl
86
95
  }
96
+
97
+ // Read from progress/sessions/{YYYY-MM}/{date}.jsonl (new format)
98
+ const sessionsDir = join(globalStorage, projectId, 'progress', 'sessions')
99
+ try {
100
+ const monthDirs = await fs.readdir(sessionsDir)
101
+ for (const monthDir of monthDirs) {
102
+ // Skip non-directory entries
103
+ if (!monthDir.match(/^\d{4}-\d{2}$/)) continue
104
+
105
+ const monthPath = join(sessionsDir, monthDir)
106
+ try {
107
+ const dayFiles = await fs.readdir(monthPath)
108
+ for (const dayFile of dayFiles) {
109
+ if (!dayFile.endsWith('.jsonl')) continue
110
+
111
+ const dayPath = join(monthPath, dayFile)
112
+ try {
113
+ const content = await fs.readFile(dayPath, 'utf-8')
114
+ const lines = content.trim().split('\n').filter(Boolean)
115
+
116
+ for (const line of lines) {
117
+ try {
118
+ const event: SessionEvent = JSON.parse(line)
119
+ const timestamp = event.ts || event.timestamp
120
+ if (!timestamp) continue
121
+
122
+ const eventDate = new Date(timestamp)
123
+ if (isNaN(eventDate.getTime())) continue
124
+ if (eventDate < startDate || eventDate > endDate) continue
125
+
126
+ const dateKey = eventDate.toISOString().split('T')[0]
127
+ const current = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
128
+
129
+ const eventType = event.type || event.action
130
+
131
+ // Count tasks completed
132
+ if (eventType === 'task_complete' || eventType === 'task_completed') {
133
+ current.tasks++
134
+ }
135
+
136
+ // Count features shipped
137
+ if (eventType === 'feature_ship' || eventType === 'feature_shipped') {
138
+ current.ships++
139
+ }
140
+
141
+ // Count partial/abandoned sessions
142
+ if (eventType === 'session_partial' || eventType === 'session_abandoned') {
143
+ current.partial++
144
+ }
145
+
146
+ dailyMap.set(dateKey, current)
147
+ } catch {
148
+ // Skip malformed lines
149
+ }
150
+ }
151
+ } catch {
152
+ // Skip unreadable files
153
+ }
154
+ }
155
+ } catch {
156
+ // Skip unreadable month directories
157
+ }
158
+ }
159
+ } catch {
160
+ // Skip projects without sessions directory
161
+ }
87
162
  }
88
163
 
89
- // Convert to sorted array
90
- const data: DailyStats[] = Array.from(dailyMap.entries())
91
- .map(([date, stats]) => ({
92
- date,
164
+ // Generate all dates in range (90 days), filling gaps with zeros
165
+ const data: DailyStats[] = []
166
+ const currentDate = new Date(startDate)
167
+ while (currentDate <= endDate) {
168
+ const dateKey = currentDate.toISOString().split('T')[0]
169
+ const stats = dailyMap.get(dateKey) || { tasks: 0, ships: 0, partial: 0 }
170
+ data.push({
171
+ date: dateKey,
93
172
  tasks: stats.tasks,
94
- ships: stats.ships
95
- }))
96
- .sort((a, b) => a.date.localeCompare(b.date))
173
+ ships: stats.ships,
174
+ partial: stats.partial
175
+ })
176
+ currentDate.setDate(currentDate.getDate() + 1)
177
+ }
97
178
 
98
179
  // Calculate totals for summary
99
180
  const totals = {
100
181
  tasks: data.reduce((sum, d) => sum + d.tasks, 0),
101
- ships: data.reduce((sum, d) => sum + d.ships, 0)
182
+ ships: data.reduce((sum, d) => sum + d.ships, 0),
183
+ partial: data.reduce((sum, d) => sum + d.partial, 0)
102
184
  }
103
185
 
104
186
  return NextResponse.json({
@@ -117,6 +117,11 @@
117
117
  * {
118
118
  @apply border-border outline-ring/50;
119
119
  }
120
+ html,
121
+ body {
122
+ overflow-x: hidden;
123
+ max-width: 100vw;
124
+ }
120
125
  body {
121
126
  @apply bg-background text-foreground;
122
127
  /* Prevent pull-to-refresh on mobile */
@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from 'next/font/google'
3
3
  import './globals.css'
4
4
  import { Providers } from '@/components/Providers'
5
5
  import { AppSidebar } from '@/components/AppSidebar'
6
+ import { TerminalDock } from '@/components/TerminalDock'
6
7
 
7
8
  const geistSans = Geist({
8
9
  variable: '--font-geist-sans',
@@ -44,6 +45,7 @@ export default function RootLayout({
44
45
  {children}
45
46
  </main>
46
47
  </div>
48
+ <TerminalDock />
47
49
  </Providers>
48
50
  </body>
49
51
  </html>
@@ -0,0 +1,18 @@
1
+ import type { Metadata } from 'next'
2
+ import { getProject } from '@/lib/services/projects.server'
3
+ import { getProjectEmoji } from '@/lib/project-colors'
4
+
5
+ export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise<Metadata> {
6
+ const { id: projectId } = await params
7
+ const project = await getProject(projectId)
8
+ const projectName = project?.name ?? projectId
9
+ const emoji = getProjectEmoji(projectId)
10
+
11
+ return {
12
+ title: `${emoji} ${projectName} / Code / p.`,
13
+ }
14
+ }
15
+
16
+ export default function CodeLayout({ children }: { children: React.ReactNode }) {
17
+ return children
18
+ }