create-arete-workspace 0.2.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 (180) hide show
  1. package/README.md +77 -0
  2. package/bin/arete.js +156 -0
  3. package/bin/create.js +111 -0
  4. package/lib/install-openclaw.js +50 -0
  5. package/lib/scaffold.js +213 -0
  6. package/lib/setup-wizard.js +88 -0
  7. package/lib/updater.js +130 -0
  8. package/package.json +34 -0
  9. package/packages/gatsaeng-os/README.md +36 -0
  10. package/packages/gatsaeng-os/components.json +23 -0
  11. package/packages/gatsaeng-os/eslint.config.mjs +18 -0
  12. package/packages/gatsaeng-os/next.config.ts +7 -0
  13. package/packages/gatsaeng-os/package.json +59 -0
  14. package/packages/gatsaeng-os/postcss.config.mjs +7 -0
  15. package/packages/gatsaeng-os/public/file.svg +1 -0
  16. package/packages/gatsaeng-os/public/globe.svg +1 -0
  17. package/packages/gatsaeng-os/public/next.svg +1 -0
  18. package/packages/gatsaeng-os/public/vercel.svg +1 -0
  19. package/packages/gatsaeng-os/public/window.svg +1 -0
  20. package/packages/gatsaeng-os/python/api_server.py +248 -0
  21. package/packages/gatsaeng-os/python/briefing.py +145 -0
  22. package/packages/gatsaeng-os/python/config.py +55 -0
  23. package/packages/gatsaeng-os/python/goal_context_agent.py +193 -0
  24. package/packages/gatsaeng-os/python/gyeokguk.py +171 -0
  25. package/packages/gatsaeng-os/python/proactive.py +158 -0
  26. package/packages/gatsaeng-os/python/requirements.txt +11 -0
  27. package/packages/gatsaeng-os/python/run.py +28 -0
  28. package/packages/gatsaeng-os/python/scoring.py +44 -0
  29. package/packages/gatsaeng-os/python/streak.py +70 -0
  30. package/packages/gatsaeng-os/python/telegram_bot.py +331 -0
  31. package/packages/gatsaeng-os/python/timing_engine.py +117 -0
  32. package/packages/gatsaeng-os/python/vault_io.py +423 -0
  33. package/packages/gatsaeng-os/src/app/(dashboard)/areas/[id]/page.tsx +215 -0
  34. package/packages/gatsaeng-os/src/app/(dashboard)/areas/page.tsx +161 -0
  35. package/packages/gatsaeng-os/src/app/(dashboard)/books/[id]/page.tsx +215 -0
  36. package/packages/gatsaeng-os/src/app/(dashboard)/books/page.tsx +268 -0
  37. package/packages/gatsaeng-os/src/app/(dashboard)/calendar/page.tsx +379 -0
  38. package/packages/gatsaeng-os/src/app/(dashboard)/error.tsx +30 -0
  39. package/packages/gatsaeng-os/src/app/(dashboard)/focus/page.tsx +293 -0
  40. package/packages/gatsaeng-os/src/app/(dashboard)/goals/[id]/page.tsx +426 -0
  41. package/packages/gatsaeng-os/src/app/(dashboard)/goals/page.tsx +178 -0
  42. package/packages/gatsaeng-os/src/app/(dashboard)/layout.tsx +29 -0
  43. package/packages/gatsaeng-os/src/app/(dashboard)/notes/[id]/page.tsx +147 -0
  44. package/packages/gatsaeng-os/src/app/(dashboard)/notes/page.tsx +254 -0
  45. package/packages/gatsaeng-os/src/app/(dashboard)/page.tsx +26 -0
  46. package/packages/gatsaeng-os/src/app/(dashboard)/projects/[id]/page.tsx +86 -0
  47. package/packages/gatsaeng-os/src/app/(dashboard)/projects/page.tsx +215 -0
  48. package/packages/gatsaeng-os/src/app/(dashboard)/review/page.tsx +475 -0
  49. package/packages/gatsaeng-os/src/app/(dashboard)/routines/page.tsx +436 -0
  50. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/[id]/page.tsx +210 -0
  51. package/packages/gatsaeng-os/src/app/(dashboard)/tasks/page.tsx +307 -0
  52. package/packages/gatsaeng-os/src/app/(dashboard)/voice/page.tsx +212 -0
  53. package/packages/gatsaeng-os/src/app/api/areas/[id]/route.ts +26 -0
  54. package/packages/gatsaeng-os/src/app/api/areas/route.ts +22 -0
  55. package/packages/gatsaeng-os/src/app/api/auth/login/route.ts +52 -0
  56. package/packages/gatsaeng-os/src/app/api/auth/logout/route.ts +8 -0
  57. package/packages/gatsaeng-os/src/app/api/books/[id]/route.ts +27 -0
  58. package/packages/gatsaeng-os/src/app/api/books/route.ts +20 -0
  59. package/packages/gatsaeng-os/src/app/api/calendar/[id]/route.ts +24 -0
  60. package/packages/gatsaeng-os/src/app/api/calendar/import/route.ts +52 -0
  61. package/packages/gatsaeng-os/src/app/api/calendar/route.ts +37 -0
  62. package/packages/gatsaeng-os/src/app/api/daily/route.ts +51 -0
  63. package/packages/gatsaeng-os/src/app/api/goals/[id]/route.ts +34 -0
  64. package/packages/gatsaeng-os/src/app/api/goals/route.ts +30 -0
  65. package/packages/gatsaeng-os/src/app/api/logs/energy/route.ts +40 -0
  66. package/packages/gatsaeng-os/src/app/api/logs/focus/route.ts +22 -0
  67. package/packages/gatsaeng-os/src/app/api/logs/routine/route.ts +54 -0
  68. package/packages/gatsaeng-os/src/app/api/milestones/[id]/route.ts +26 -0
  69. package/packages/gatsaeng-os/src/app/api/milestones/route.ts +47 -0
  70. package/packages/gatsaeng-os/src/app/api/notes/[id]/route.ts +29 -0
  71. package/packages/gatsaeng-os/src/app/api/notes/route.ts +37 -0
  72. package/packages/gatsaeng-os/src/app/api/profile/route.ts +17 -0
  73. package/packages/gatsaeng-os/src/app/api/projects/[id]/route.ts +27 -0
  74. package/packages/gatsaeng-os/src/app/api/projects/route.ts +25 -0
  75. package/packages/gatsaeng-os/src/app/api/reviews/[id]/route.ts +26 -0
  76. package/packages/gatsaeng-os/src/app/api/reviews/route.ts +29 -0
  77. package/packages/gatsaeng-os/src/app/api/routines/[id]/route.ts +26 -0
  78. package/packages/gatsaeng-os/src/app/api/routines/route.ts +28 -0
  79. package/packages/gatsaeng-os/src/app/api/tasks/[id]/route.ts +28 -0
  80. package/packages/gatsaeng-os/src/app/api/tasks/route.ts +66 -0
  81. package/packages/gatsaeng-os/src/app/api/timing/current/route.ts +63 -0
  82. package/packages/gatsaeng-os/src/app/api/voice/chat/route.ts +50 -0
  83. package/packages/gatsaeng-os/src/app/api/voice/transcribe/route.ts +25 -0
  84. package/packages/gatsaeng-os/src/app/api/voice/tts/route.ts +36 -0
  85. package/packages/gatsaeng-os/src/app/error.tsx +30 -0
  86. package/packages/gatsaeng-os/src/app/favicon.ico +0 -0
  87. package/packages/gatsaeng-os/src/app/globals.css +208 -0
  88. package/packages/gatsaeng-os/src/app/layout.tsx +33 -0
  89. package/packages/gatsaeng-os/src/app/login/page.tsx +87 -0
  90. package/packages/gatsaeng-os/src/app/providers.tsx +27 -0
  91. package/packages/gatsaeng-os/src/components/ErrorBoundary.tsx +46 -0
  92. package/packages/gatsaeng-os/src/components/dashboard/DashboardGrid.tsx +86 -0
  93. package/packages/gatsaeng-os/src/components/dashboard/DdayWidget.tsx +88 -0
  94. package/packages/gatsaeng-os/src/components/dashboard/EnergyTracker.tsx +87 -0
  95. package/packages/gatsaeng-os/src/components/dashboard/FocusTimer.tsx +139 -0
  96. package/packages/gatsaeng-os/src/components/dashboard/GatsaengScore.tsx +30 -0
  97. package/packages/gatsaeng-os/src/components/dashboard/GoalRings.tsx +107 -0
  98. package/packages/gatsaeng-os/src/components/dashboard/ProactiveBar.tsx +98 -0
  99. package/packages/gatsaeng-os/src/components/dashboard/RoutineChecklist.tsx +81 -0
  100. package/packages/gatsaeng-os/src/components/dashboard/TimingWidget.tsx +86 -0
  101. package/packages/gatsaeng-os/src/components/dashboard/WidgetCustomizer.tsx +95 -0
  102. package/packages/gatsaeng-os/src/components/dashboard/WidgetWrapper.tsx +33 -0
  103. package/packages/gatsaeng-os/src/components/dashboard/ZeigarnikPanel.tsx +43 -0
  104. package/packages/gatsaeng-os/src/components/editor/EditorToolbar.tsx +186 -0
  105. package/packages/gatsaeng-os/src/components/editor/TiptapEditor.tsx +114 -0
  106. package/packages/gatsaeng-os/src/components/layout/Header.tsx +47 -0
  107. package/packages/gatsaeng-os/src/components/layout/MobileBottomNav.tsx +122 -0
  108. package/packages/gatsaeng-os/src/components/layout/MobileSidebar.tsx +29 -0
  109. package/packages/gatsaeng-os/src/components/layout/Sidebar.tsx +142 -0
  110. package/packages/gatsaeng-os/src/components/onboarding/OnboardingFlow.tsx +229 -0
  111. package/packages/gatsaeng-os/src/components/onboarding/OnboardingGate.tsx +78 -0
  112. package/packages/gatsaeng-os/src/components/projects/CalendarView.tsx +152 -0
  113. package/packages/gatsaeng-os/src/components/projects/KanbanView.tsx +180 -0
  114. package/packages/gatsaeng-os/src/components/projects/ListView.tsx +82 -0
  115. package/packages/gatsaeng-os/src/components/projects/TableView.tsx +206 -0
  116. package/packages/gatsaeng-os/src/components/projects/TaskCard.tsx +154 -0
  117. package/packages/gatsaeng-os/src/components/projects/TaskForm.tsx +128 -0
  118. package/packages/gatsaeng-os/src/components/projects/ViewSwitcher.tsx +40 -0
  119. package/packages/gatsaeng-os/src/components/search/GlobalSearch.tsx +179 -0
  120. package/packages/gatsaeng-os/src/components/shared/InlineEdit.tsx +77 -0
  121. package/packages/gatsaeng-os/src/components/shared/PinButton.tsx +42 -0
  122. package/packages/gatsaeng-os/src/components/tasks/DDayBadge.tsx +34 -0
  123. package/packages/gatsaeng-os/src/components/ui/badge.tsx +48 -0
  124. package/packages/gatsaeng-os/src/components/ui/button.tsx +64 -0
  125. package/packages/gatsaeng-os/src/components/ui/card.tsx +92 -0
  126. package/packages/gatsaeng-os/src/components/ui/checkbox.tsx +32 -0
  127. package/packages/gatsaeng-os/src/components/ui/command.tsx +184 -0
  128. package/packages/gatsaeng-os/src/components/ui/dialog.tsx +158 -0
  129. package/packages/gatsaeng-os/src/components/ui/input.tsx +21 -0
  130. package/packages/gatsaeng-os/src/components/ui/label.tsx +24 -0
  131. package/packages/gatsaeng-os/src/components/ui/popover.tsx +89 -0
  132. package/packages/gatsaeng-os/src/components/ui/progress.tsx +31 -0
  133. package/packages/gatsaeng-os/src/components/ui/select.tsx +190 -0
  134. package/packages/gatsaeng-os/src/components/ui/sheet.tsx +143 -0
  135. package/packages/gatsaeng-os/src/components/ui/tabs.tsx +91 -0
  136. package/packages/gatsaeng-os/src/components/ui/toggle-group.tsx +83 -0
  137. package/packages/gatsaeng-os/src/components/ui/toggle.tsx +47 -0
  138. package/packages/gatsaeng-os/src/components/ui/tooltip.tsx +57 -0
  139. package/packages/gatsaeng-os/src/hooks/useAreas.ts +53 -0
  140. package/packages/gatsaeng-os/src/hooks/useBooks.ts +62 -0
  141. package/packages/gatsaeng-os/src/hooks/useCalendar.ts +59 -0
  142. package/packages/gatsaeng-os/src/hooks/useDaily.ts +15 -0
  143. package/packages/gatsaeng-os/src/hooks/useGlobalTasks.ts +45 -0
  144. package/packages/gatsaeng-os/src/hooks/useGoals.ts +53 -0
  145. package/packages/gatsaeng-os/src/hooks/useMilestones.ts +75 -0
  146. package/packages/gatsaeng-os/src/hooks/useNotes.ts +65 -0
  147. package/packages/gatsaeng-os/src/hooks/useProjects.ts +102 -0
  148. package/packages/gatsaeng-os/src/hooks/useRoutines.ts +76 -0
  149. package/packages/gatsaeng-os/src/hooks/useTiming.ts +27 -0
  150. package/packages/gatsaeng-os/src/lib/apiFetch.ts +14 -0
  151. package/packages/gatsaeng-os/src/lib/auth.ts +32 -0
  152. package/packages/gatsaeng-os/src/lib/date.ts +7 -0
  153. package/packages/gatsaeng-os/src/lib/editor/markdown.ts +35 -0
  154. package/packages/gatsaeng-os/src/lib/llm-governor.ts +167 -0
  155. package/packages/gatsaeng-os/src/lib/neuroscience/energyCycle.ts +35 -0
  156. package/packages/gatsaeng-os/src/lib/neuroscience/habitStack.ts +22 -0
  157. package/packages/gatsaeng-os/src/lib/neuroscience/scoring.ts +32 -0
  158. package/packages/gatsaeng-os/src/lib/routes.ts +15 -0
  159. package/packages/gatsaeng-os/src/lib/utils.ts +6 -0
  160. package/packages/gatsaeng-os/src/lib/vault/config.ts +29 -0
  161. package/packages/gatsaeng-os/src/lib/vault/frontmatter.ts +84 -0
  162. package/packages/gatsaeng-os/src/lib/vault/index.ts +180 -0
  163. package/packages/gatsaeng-os/src/lib/vault/schemas.ts +274 -0
  164. package/packages/gatsaeng-os/src/middleware.ts +34 -0
  165. package/packages/gatsaeng-os/src/stores/dashboardStore.ts +26 -0
  166. package/packages/gatsaeng-os/src/stores/favoritesStore.ts +47 -0
  167. package/packages/gatsaeng-os/src/stores/timerStore.ts +65 -0
  168. package/packages/gatsaeng-os/src/types/index.ts +320 -0
  169. package/packages/gatsaeng-os/tsconfig.json +34 -0
  170. package/templates/scripts/forge_qa.sh.tmpl +237 -0
  171. package/templates/scripts/forge_ship.sh.tmpl +183 -0
  172. package/templates/scripts/session_indexer.py.tmpl +420 -0
  173. package/templates/scripts/tracer.py.tmpl +266 -0
  174. package/templates/workspace/AGENTS.md.tmpl +190 -0
  175. package/templates/workspace/BOOTSTRAP.md.tmpl +27 -0
  176. package/templates/workspace/HEARTBEAT.md.tmpl +23 -0
  177. package/templates/workspace/MEMORY.md.tmpl +35 -0
  178. package/templates/workspace/SOUL.md.tmpl +258 -0
  179. package/templates/workspace/TOOLS.md.tmpl +28 -0
  180. package/templates/workspace/USER.md.tmpl +43 -0
@@ -0,0 +1,293 @@
1
+ 'use client'
2
+
3
+ import { useState, useEffect, useRef, useCallback } from 'react'
4
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
5
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Badge } from '@/components/ui/badge'
8
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
9
+ import { Timer, Play, Pause, RotateCcw, Clock, Zap, Trophy } from 'lucide-react'
10
+ import { cn } from '@/lib/utils'
11
+ import type { FocusSession, SessionType } from '@/types'
12
+
13
+ const SESSION_PRESETS: { type: SessionType; label: string; minutes: number; color: string }[] = [
14
+ { type: 'pomodoro_25', label: '25min', minutes: 25, color: '#00d4aa' },
15
+ { type: 'focus_90', label: '90min', minutes: 90, color: '#f59e0b' },
16
+ { type: 'deep_work', label: 'Deep Work', minutes: 120, color: '#7c5cbf' },
17
+ ]
18
+
19
+ function formatTime(seconds: number) {
20
+ const m = Math.floor(seconds / 60)
21
+ const s = seconds % 60
22
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
23
+ }
24
+
25
+ export default function FocusPage() {
26
+ const queryClient = useQueryClient()
27
+ const [selectedType, setSelectedType] = useState<SessionType>('pomodoro_25')
28
+ const [isRunning, setIsRunning] = useState(false)
29
+ const [timeLeft, setTimeLeft] = useState(25 * 60)
30
+ const [startedAt, setStartedAt] = useState<string | null>(null)
31
+ const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null)
32
+
33
+ const preset = SESSION_PRESETS.find(p => p.type === selectedType) ?? SESSION_PRESETS[0]
34
+ const totalSeconds = preset.minutes * 60
35
+ const progress = ((totalSeconds - timeLeft) / totalSeconds) * 100
36
+
37
+ const { data: sessions = [] } = useQuery({
38
+ queryKey: ['focus-sessions'],
39
+ queryFn: async (): Promise<FocusSession[]> => {
40
+ const res = await fetch('/api/logs/focus')
41
+ return res.json()
42
+ },
43
+ })
44
+
45
+ const createSession = useMutation({
46
+ mutationFn: async (data: Partial<FocusSession>) => {
47
+ const res = await fetch('/api/logs/focus', {
48
+ method: 'POST',
49
+ headers: { 'Content-Type': 'application/json' },
50
+ body: JSON.stringify(data),
51
+ })
52
+ return res.json()
53
+ },
54
+ onSuccess: () => {
55
+ queryClient.invalidateQueries({ queryKey: ['focus-sessions'] })
56
+ },
57
+ })
58
+
59
+ const saveSession = useCallback((completed: boolean) => {
60
+ if (!startedAt) return
61
+ const elapsed = totalSeconds - timeLeft
62
+ createSession.mutate({
63
+ session_type: selectedType,
64
+ duration_minutes: Math.round(elapsed / 60),
65
+ completed,
66
+ started_at: startedAt,
67
+ date: new Date().toISOString().slice(0, 10),
68
+ })
69
+ }, [startedAt, totalSeconds, timeLeft, selectedType, createSession])
70
+
71
+ useEffect(() => {
72
+ if (isRunning && timeLeft > 0) {
73
+ intervalRef.current = setInterval(() => {
74
+ setTimeLeft(prev => {
75
+ if (prev <= 1) {
76
+ setIsRunning(false)
77
+ return 0
78
+ }
79
+ return prev - 1
80
+ })
81
+ }, 1000)
82
+ }
83
+ return () => {
84
+ if (intervalRef.current) clearInterval(intervalRef.current)
85
+ }
86
+ }, [isRunning, timeLeft])
87
+
88
+ // Auto-save on completion
89
+ useEffect(() => {
90
+ if (timeLeft === 0 && startedAt) {
91
+ saveSession(true)
92
+ setStartedAt(null)
93
+ }
94
+ }, [timeLeft, startedAt, saveSession])
95
+
96
+ const handleStart = () => {
97
+ if (!isRunning && timeLeft === totalSeconds) {
98
+ setStartedAt(new Date().toISOString())
99
+ }
100
+ setIsRunning(true)
101
+ }
102
+
103
+ const handlePause = () => {
104
+ setIsRunning(false)
105
+ }
106
+
107
+ const handleReset = () => {
108
+ if (isRunning || (startedAt && timeLeft < totalSeconds)) {
109
+ saveSession(false)
110
+ }
111
+ setIsRunning(false)
112
+ setTimeLeft(totalSeconds)
113
+ setStartedAt(null)
114
+ }
115
+
116
+ const handleTypeChange = (type: SessionType) => {
117
+ if (isRunning) return
118
+ setSelectedType(type)
119
+ const p = SESSION_PRESETS.find(p => p.type === type)
120
+ if (p) setTimeLeft(p.minutes * 60)
121
+ }
122
+
123
+ // Stats
124
+ const today = new Date().toISOString().slice(0, 10)
125
+ const todaySessions = sessions.filter(s => s.date === today || s.started_at?.startsWith(today))
126
+ const todayMinutes = todaySessions.reduce((sum, s) => sum + (s.duration_minutes ?? 0), 0)
127
+ const completedCount = todaySessions.filter(s => s.completed).length
128
+ const recentSessions = [...sessions]
129
+ .sort((a, b) => (b.started_at ?? '').localeCompare(a.started_at ?? ''))
130
+ .slice(0, 10)
131
+
132
+ return (
133
+ <div className="max-w-3xl mx-auto">
134
+ <div className="flex items-center justify-between mb-6">
135
+ <div>
136
+ <h1 className="text-2xl font-bold text-foreground">포커스</h1>
137
+ <p className="text-sm text-muted-foreground mt-1">Deep Work 타이머로 몰입 세션 기록</p>
138
+ </div>
139
+ </div>
140
+
141
+ {/* Timer */}
142
+ <Card className="mb-6">
143
+ <CardContent className="py-8">
144
+ <div className="flex flex-col items-center">
145
+ {/* Session Type Selector */}
146
+ <div className="flex items-center gap-2 mb-8">
147
+ {SESSION_PRESETS.map(p => (
148
+ <button
149
+ key={p.type}
150
+ onClick={() => handleTypeChange(p.type)}
151
+ disabled={isRunning}
152
+ className={cn(
153
+ 'px-4 py-2 rounded-lg text-sm font-medium transition-colors',
154
+ selectedType === p.type
155
+ ? 'text-black'
156
+ : 'bg-secondary text-muted-foreground hover:text-foreground'
157
+ )}
158
+ style={selectedType === p.type ? { backgroundColor: p.color } : undefined}
159
+ >
160
+ {p.label}
161
+ </button>
162
+ ))}
163
+ </div>
164
+
165
+ {/* Circular Timer */}
166
+ <div className="relative w-64 h-64 mb-8">
167
+ <svg className="w-64 h-64 -rotate-90" viewBox="0 0 256 256">
168
+ <circle cx="128" cy="128" r="112" fill="none" stroke="currentColor" className="text-muted" strokeWidth="6" />
169
+ <circle
170
+ cx="128" cy="128" r="112" fill="none"
171
+ stroke={preset.color}
172
+ strokeWidth="6"
173
+ strokeLinecap="round"
174
+ strokeDasharray={`${2 * Math.PI * 112}`}
175
+ strokeDashoffset={`${2 * Math.PI * 112 * (1 - progress / 100)}`}
176
+ className="transition-all duration-1000"
177
+ />
178
+ </svg>
179
+ <div className="absolute inset-0 flex flex-col items-center justify-center">
180
+ <span className="text-5xl font-bold font-mono text-foreground">
181
+ {formatTime(timeLeft)}
182
+ </span>
183
+ <span className="text-sm text-muted-foreground mt-2">{preset.label}</span>
184
+ </div>
185
+ </div>
186
+
187
+ {/* Controls */}
188
+ <div className="flex items-center gap-4">
189
+ <Button
190
+ variant="outline"
191
+ size="icon"
192
+ className="h-12 w-12 rounded-full"
193
+ onClick={handleReset}
194
+ >
195
+ <RotateCcw className="w-5 h-5" />
196
+ </Button>
197
+ <Button
198
+ size="icon"
199
+ className="h-16 w-16 rounded-full"
200
+ style={{ backgroundColor: preset.color }}
201
+ onClick={isRunning ? handlePause : handleStart}
202
+ >
203
+ {isRunning
204
+ ? <Pause className="w-7 h-7 text-black" />
205
+ : <Play className="w-7 h-7 text-black ml-1" />
206
+ }
207
+ </Button>
208
+ <div className="w-12" /> {/* spacer */}
209
+ </div>
210
+
211
+ {timeLeft === 0 && (
212
+ <div className="mt-6 text-center animate-in fade-in">
213
+ <Trophy className="w-8 h-8 text-gatsaeng-amber mx-auto mb-2" />
214
+ <p className="text-sm font-medium text-gatsaeng-amber">세션 완료!</p>
215
+ </div>
216
+ )}
217
+ </div>
218
+ </CardContent>
219
+ </Card>
220
+
221
+ {/* Today Stats */}
222
+ <div className="grid grid-cols-3 gap-4 mb-6">
223
+ <Card>
224
+ <CardContent className="py-4 text-center">
225
+ <Clock className="w-5 h-5 mx-auto mb-1 text-muted-foreground" />
226
+ <div className="text-2xl font-bold text-foreground">{todayMinutes}</div>
227
+ <div className="text-xs text-muted-foreground">오늘 집중 (분)</div>
228
+ </CardContent>
229
+ </Card>
230
+ <Card>
231
+ <CardContent className="py-4 text-center">
232
+ <Trophy className="w-5 h-5 mx-auto mb-1 text-gatsaeng-amber" />
233
+ <div className="text-2xl font-bold text-foreground">{completedCount}</div>
234
+ <div className="text-xs text-muted-foreground">완료 세션</div>
235
+ </CardContent>
236
+ </Card>
237
+ <Card>
238
+ <CardContent className="py-4 text-center">
239
+ <Zap className="w-5 h-5 mx-auto mb-1 text-gatsaeng-teal" />
240
+ <div className="text-2xl font-bold text-foreground">{todaySessions.length}</div>
241
+ <div className="text-xs text-muted-foreground">총 세션</div>
242
+ </CardContent>
243
+ </Card>
244
+ </div>
245
+
246
+ {/* Recent Sessions */}
247
+ {recentSessions.length > 0 && (
248
+ <Card>
249
+ <CardHeader className="pb-2">
250
+ <CardTitle className="text-sm">최근 세션</CardTitle>
251
+ </CardHeader>
252
+ <CardContent>
253
+ <div className="space-y-2">
254
+ {recentSessions.map(session => {
255
+ const sp = SESSION_PRESETS.find(p => p.type === session.session_type)
256
+ return (
257
+ <div key={session.id} className="flex items-center justify-between py-2 border-b border-border last:border-0">
258
+ <div className="flex items-center gap-3">
259
+ <div
260
+ className="w-2 h-2 rounded-full"
261
+ style={{ backgroundColor: sp?.color ?? '#888' }}
262
+ />
263
+ <div>
264
+ <div className="text-sm font-medium">{sp?.label ?? session.session_type}</div>
265
+ <div className="text-[10px] text-muted-foreground">
266
+ {session.started_at?.slice(0, 16).replace('T', ' ')}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ <div className="flex items-center gap-2">
271
+ <span className="text-sm text-muted-foreground">{session.duration_minutes}분</span>
272
+ <Badge
273
+ variant="outline"
274
+ className={cn(
275
+ 'text-[10px]',
276
+ session.completed
277
+ ? 'border-gatsaeng-teal/30 text-gatsaeng-teal'
278
+ : 'border-gatsaeng-red/30 text-gatsaeng-red'
279
+ )}
280
+ >
281
+ {session.completed ? '완료' : '중단'}
282
+ </Badge>
283
+ </div>
284
+ </div>
285
+ )
286
+ })}
287
+ </div>
288
+ </CardContent>
289
+ </Card>
290
+ )}
291
+ </div>
292
+ )
293
+ }