Files
claudetools/imported-conversations/auto-builder/bcc2f2d7-2a47-410b-b948-6a9d0f44c92c/tool-results/toolu_01LbtDKWDuPEaWfzsJ3c9zm3.txt
Mike Swanson 75ce1c2fd5 feat: Add Sequential Thinking to Code Review + Frontend Validation
Enhanced code review and frontend validation with intelligent triggers:

Code Review Agent Enhancement:
- Added Sequential Thinking MCP integration for complex issues
- Triggers on 2+ rejections or 3+ critical issues
- New escalation format with root cause analysis
- Comprehensive solution strategies with trade-off evaluation
- Educational feedback to break rejection cycles
- Files: .claude/agents/code-review.md (+308 lines)
- Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md

Frontend Design Skill Enhancement:
- Automatic invocation for ANY UI change
- Comprehensive validation checklist (200+ checkpoints)
- 8 validation categories (visual, interactive, responsive, a11y, etc.)
- 3 validation levels (quick, standard, comprehensive)
- Integration with code review workflow
- Files: .claude/skills/frontend-design/SKILL.md (+120 lines)
- Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines)

Settings Optimization:
- Repaired .claude/settings.local.json (fixed m365 pattern)
- Reduced permissions from 49 to 33 (33% reduction)
- Removed duplicates, sorted alphabetically
- Created SETTINGS_PERMISSIONS.md documentation

Checkpoint Command Enhancement:
- Dual checkpoint system (git + database)
- Saves session context to API for cross-machine recall
- Includes git metadata in database context
- Files: .claude/commands/checkpoint.md (+139 lines)

Decision Rationale:
- Sequential Thinking MCP breaks rejection cycles by identifying root causes
- Automatic frontend validation catches UI issues before code review
- Dual checkpoints enable complete project memory across machines
- Settings optimization improves maintainability

Total: 1,200+ lines of documentation and enhancements

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 16:23:52 -07:00

453 lines
22 KiB
Plaintext

1→import { useEffect, useState } from 'react'
2→import { useProjectStore } from '../stores/projectStore'
3→import { useAgentStore } from '../stores/agentStore'
4→import { useToastStore } from '../components/Toast'
5→import {
6→ FolderOpen,
7→ CheckCircle,
8→ Clock,
9→ AlertTriangle,
10→ Activity,
11→ Trash2,
12→ Pencil,
13→ Check,
14→ Palette,
15→ X,
16→} from 'lucide-react'
17→import * as AlertDialog from '@radix-ui/react-alert-dialog'
18→import * as Dialog from '@radix-ui/react-dialog'
19→
20→interface TaskStats {
21→ completed: number
22→ inProgress: number
23→ blocked: number
24→ backlog: number
25→ qa: number
26→}
27→
28→export default function Dashboard() {
29→ const { currentProject, fetchProjects, deleteProject, updateProject } = useProjectStore()
30→ const { activeSessions, fetchActiveSessions } = useAgentStore()
31→ const { addToast } = useToastStore()
32→ const [showDeleteDialog, setShowDeleteDialog] = useState(false)
33→ const [isDeleting, setIsDeleting] = useState(false)
34→ const [showEditDialog, setShowEditDialog] = useState(false)
35→ const [editName, setEditName] = useState('')
36→ const [editDescription, setEditDescription] = useState('')
37→ const [editDesignMode, setEditDesignMode] = useState<'mvp' | 'finished'>('mvp')
38→ const [isSaving, setIsSaving] = useState(false)
39→ const [saveSuccess, setSaveSuccess] = useState(false)
40→ const [taskStats, setTaskStats] = useState<TaskStats>({
41→ completed: 0,
42→ inProgress: 0,
43→ blocked: 0,
44→ backlog: 0,
45→ qa: 0,
46→ })
47→
48→ useEffect(() => {
49→ fetchProjects()
50→ fetchActiveSessions()
51→ }, [fetchProjects, fetchActiveSessions])
52→
53→ // Fetch task statistics when current project changes
54→ useEffect(() => {
55→ const fetchTaskStats = async () => {
56→ if (!currentProject) return
57→
58→ try {
59→ const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}/tasks`)
60→ if (!response.ok) return
61→
62→ const tasks = await response.json()
63→ const stats: TaskStats = {
64→ completed: 0,
65→ inProgress: 0,
66→ blocked: 0,
67→ backlog: 0,
68→ qa: 0,
69→ }
70→
71→ tasks.forEach((task: { status: string }) => {
72→ switch (task.status) {
73→ case 'done':
74→ stats.completed++
75→ break
76→ case 'in_progress':
77→ stats.inProgress++
78→ break
79→ case 'blocked':
80→ stats.blocked++
81→ break
82→ case 'backlog':
83→ stats.backlog++
84→ break
85→ case 'qa':
86→ stats.qa++
87→ break
88→ }
89→ })
90→
91→ setTaskStats(stats)
92→ } catch (error) {
93→ console.error('Failed to fetch task stats:', error)
94→ }
95→ }
96→
97→ fetchTaskStats()
98→ }, [currentProject])
99→
100→ const handleDeleteProject = async () => {
101→ if (!currentProject) return
102→ setIsDeleting(true)
103→ try {
104→ await deleteProject(currentProject.id)
105→ setShowDeleteDialog(false)
106→ } catch (error) {
107→ console.error('Failed to delete project:', error)
108→ addToast('error', 'Delete Failed', `Failed to delete project "${currentProject.name}". Please try again.`)
109→ } finally {
110→ setIsDeleting(false)
111→ }
112→ }
113→
114→ const handleOpenEditDialog = () => {
115→ if (currentProject) {
116→ setEditName(currentProject.name)
117→ setEditDescription(currentProject.description || '')
118→ setEditDesignMode((currentProject.design_mode as 'mvp' | 'finished') || 'mvp')
119→ setSaveSuccess(false)
120→ setShowEditDialog(true)
121→ }
122→ }
123→
124→ const handleSaveProject = async () => {
125→ if (!currentProject) return
126→ setIsSaving(true)
127→ setSaveSuccess(false)
128→ try {
129→ const response = await fetch(`http://localhost:8000/api/projects/${currentProject.id}`, {
130→ method: 'PUT',
131→ headers: { 'Content-Type': 'application/json' },
132→ body: JSON.stringify({ name: editName, description: editDescription, design_mode: editDesignMode }),
133→ })
134→ if (!response.ok) {
135→ const errorData = await response.json().catch(() => ({}))
136→ throw new Error(errorData.detail || 'Failed to update project')
137→ }
138→ const updated = await response.json()
139→ updateProject(currentProject.id, updated)
140→ setSaveSuccess(true)
141→ setTimeout(() => {
142→ setShowEditDialog(false)
143→ setSaveSuccess(false)
144→ }, 1000)
145→ } catch (error) {
146→ console.error('Failed to save project:', error)
147→ const message = error instanceof Error ? error.message : 'Failed to save project'
148→ addToast('error', 'Save Failed', message)
149→ } finally {
150→ setIsSaving(false)
151→ }
152→ }
153→
154→ if (!currentProject) {
155→ return (
156→ <div className="flex flex-col items-center justify-center h-full text-center">
157→ <FolderOpen size={64} className="text-slate-600 mb-4" />
158→ <h2 className="text-2xl font-semibold text-slate-400">
159→ No Project Selected
160→ </h2>
161→ <p className="text-slate-500 mt-2">
162→ Select a project from the sidebar or create a new one to get started.
163→ </p>
164→ </div>
165→ )
166→ }
167→
168→ const stats = [
169→ {
170→ label: 'Tasks Completed',
171→ value: taskStats.completed,
172→ icon: CheckCircle,
173→ color: 'text-green-400',
174→ },
175→ {
176→ label: 'Tasks In Progress',
177→ value: taskStats.inProgress,
178→ icon: Clock,
179→ color: 'text-accent',
180→ },
181→ {
182→ label: 'Blocked Tasks',
183→ value: taskStats.blocked,
184→ icon: AlertTriangle,
185→ color: 'text-warning',
186→ },
187→ {
188→ label: 'Active Agents',
189→ value: activeSessions.filter((s) => s.status === 'running').length,
190→ icon: Activity,
191→ color: 'text-primary-400',
192→ },
193→ ]
194→
195→ return (
196→ <div className="space-y-6">
197→ {/* Project Header */}
198→ <div className="flex items-start justify-between">
199→ <div>
200→ <div className="flex items-center gap-3">
201→ <h1 className="text-3xl font-bold">{currentProject.name}</h1>
202→ {/* Design Mode Badge */}
203→ <span
204→ className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-full text-xs font-medium ${
205→ currentProject.design_mode === 'finished'
206→ ? 'bg-accent/20 text-accent border border-accent/30'
207→ : 'bg-amber-500/20 text-amber-400 border border-amber-500/30'
208→ }`}
209→ >
210→ <Palette size={12} />
211→ {currentProject.design_mode === 'finished' ? 'Finished Mode' : 'MVP Mode'}
212→ </span>
213→ </div>
214→ {currentProject.description && (
215→ <p className="text-slate-400 mt-1">{currentProject.description}</p>
216→ )}
217→ </div>
218→ <div className="flex items-center gap-2">
219→ {/* Edit Button and Dialog */}
220→ <Dialog.Root open={showEditDialog} onOpenChange={setShowEditDialog}>
221→ <Dialog.Trigger asChild>
222→ <button
223→ onClick={handleOpenEditDialog}
224→ className="flex items-center gap-2 px-3 py-2 text-sm text-slate-400 hover:text-accent hover:bg-accent/10 rounded-lg transition-colors"
225→ title="Edit Project Settings"
226→ >
227→ <Pencil size={16} />
228→ <span>Edit</span>
229→ </button>
230→ </Dialog.Trigger>
231→ <Dialog.Portal>
232→ <Dialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
233→ <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-slate-800 border border-slate-700 rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
234→ <div className="flex items-center justify-between">
235→ <Dialog.Title className="text-xl font-semibold text-white">
236→ Edit Project Settings
237→ </Dialog.Title>
238→ <Dialog.Close asChild>
239→ <button className="p-1 text-slate-400 hover:text-white hover:bg-slate-700 rounded transition-colors" aria-label="Close">
240→ <X size={20} />
241→ </button>
242→ </Dialog.Close>
243→ </div>
244→ <Dialog.Description className="mt-2 text-slate-400">
245→ Update your project name, description, and design mode.
246→ </Dialog.Description>
247→ <div className="mt-4 space-y-4">
248→ <div>
249→ <label className="block text-sm font-medium text-slate-300 mb-1">Project Name</label>
250→ <input
251→ type="text"
252→ value={editName}
253→ onChange={(e) => setEditName(e.target.value)}
254→ className="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
255→ placeholder="Enter project name"
256→ />
257→ </div>
258→ <div>
259→ <label className="block text-sm font-medium text-slate-300 mb-1">Description</label>
260→ <textarea
261→ value={editDescription}
262→ onChange={(e) => setEditDescription(e.target.value)}
263→ rows={3}
264→ className="w-full px-3 py-2 bg-slate-900 border border-slate-700 rounded-lg text-white placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent resize-none"
265→ placeholder="Enter project description"
266→ />
267→ </div>
268→ <div>
269→ <label className="block text-sm font-medium text-slate-300 mb-2">Design Mode</label>
270→ <div className="grid grid-cols-2 gap-2">
271→ <button
272→ type="button"
273→ onClick={() => setEditDesignMode('mvp')}
274→ className={`p-3 rounded-lg border text-left transition-colors ${
275→ editDesignMode === 'mvp'
276→ ? 'border-amber-500 bg-amber-500/10'
277→ : 'border-slate-700 hover:border-slate-600'
278→ }`}
279→ >
280→ <p className="font-medium text-amber-400">MVP</p>
281→ <p className="text-xs text-slate-400">Functional drafts OK</p>
282→ </button>
283→ <button
284→ type="button"
285→ onClick={() => setEditDesignMode('finished')}
286→ className={`p-3 rounded-lg border text-left transition-colors ${
287→ editDesignMode === 'finished'
288→ ? 'border-accent bg-accent/10'
289→ : 'border-slate-700 hover:border-slate-600'
290→ }`}
291→ >
292→ <p className="font-medium text-accent">Finished</p>
293→ <p className="text-xs text-slate-400">Polished required</p>
294→ </button>
295→ </div>
296→ </div>
297→ </div>
298→ <div className="mt-6 flex justify-end gap-3">
299→ <Dialog.Close asChild>
300→ <button className="px-4 py-2 text-sm text-slate-300 hover:bg-slate-700 rounded-lg transition-colors">
301→ Cancel
302→ </button>
303→ </Dialog.Close>
304→ <button
305→ onClick={handleSaveProject}
306→ disabled={isSaving || !editName.trim()}
307→ className="flex items-center gap-2 px-4 py-2 text-sm bg-accent hover:bg-accent/80 text-white rounded-lg transition-colors disabled:opacity-50"
308→ >
309→ {saveSuccess ? (
310→ <>
311→ <Check size={16} />
312→ Saved!
313→ </>
314→ ) : isSaving ? (
315→ 'Saving...'
316→ ) : (
317→ 'Save Changes'
318→ )}
319→ </button>
320→ </div>
321→ </Dialog.Content>
322→ </Dialog.Portal>
323→ </Dialog.Root>
324→
325→ {/* Delete Button and Dialog */}
326→ <AlertDialog.Root open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>
327→ <AlertDialog.Trigger asChild>
328→ <button
329→ className="flex items-center gap-2 px-3 py-2 text-sm text-slate-400 hover:text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
330→ title="Delete Project"
331→ >
332→ <Trash2 size={16} />
333→ <span>Delete</span>
334→ </button>
335→ </AlertDialog.Trigger>
336→ <AlertDialog.Portal>
337→ <AlertDialog.Overlay className="fixed inset-0 bg-black/50 z-50" />
338→ <AlertDialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-slate-800 border border-slate-700 rounded-lg p-4 md:p-6 w-[calc(100%-2rem)] max-w-md max-h-[calc(100vh-2rem)] overflow-y-auto z-50 shadow-xl">
339→ <AlertDialog.Title className="text-xl font-semibold text-white">
340→ Delete Project
341→ </AlertDialog.Title>
342→ <AlertDialog.Description className="mt-3 text-slate-400">
343→ Are you sure you want to delete <span className="font-semibold text-white">{currentProject.name}</span>?
344→ This action cannot be undone. All project data, tasks, and history will be permanently deleted.
345→ </AlertDialog.Description>
346→ <div className="mt-6 flex justify-end gap-3">
347→ <AlertDialog.Cancel asChild>
348→ <button className="px-4 py-2 text-sm text-slate-300 hover:bg-slate-700 rounded-lg transition-colors">
349→ Cancel
350→ </button>
351→ </AlertDialog.Cancel>
352→ <AlertDialog.Action asChild>
353→ <button
354→ onClick={handleDeleteProject}
355→ disabled={isDeleting}
356→ className="px-4 py-2 text-sm bg-destructive hover:bg-destructive/80 text-white rounded-lg transition-colors disabled:opacity-50"
357→ >
358→ {isDeleting ? 'Deleting...' : 'Delete Project'}
359→ </button>
360→ </AlertDialog.Action>
361→ </div>
362→ </AlertDialog.Content>
363→ </AlertDialog.Portal>
364→ </AlertDialog.Root>
365→ </div>
366→ </div>
367→
368→ {/* Stats Grid */}
369→ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
370→ {stats.map((stat) => {
371→ const Icon = stat.icon
372→ return (
373→ <div
374→ key={stat.label}
375→ className="bg-slate-800 rounded-lg p-4 border border-slate-700"
376→ >
377→ <div className="flex items-center gap-3">
378→ <Icon className={stat.color} size={24} />
379→ <div>
380→ <p className="text-sm text-slate-400">{stat.label}</p>
381→ <p className="text-2xl font-bold">{stat.value}</p>
382→ </div>
383→ </div>
384→ </div>
385→ )
386→ })}
387→ </div>
388→
389→ {/* Phase Progress */}
390→ <div className="bg-slate-800 rounded-lg p-6 border border-slate-700">
391→ <h2 className="text-lg font-semibold mb-4">Phase Progress</h2>
392→ <div className="flex items-center gap-4">
393→ <div className="flex-1">
394→ <div className="flex justify-between text-sm mb-2">
395→ <span>Phase {currentProject.current_phase}</span>
396→ <span>{currentProject.total_phases} total</span>
397→ </div>
398→ <div className="w-full h-3 bg-slate-700 rounded-full overflow-hidden">
399→ <div
400→ className="h-full bg-accent transition-all duration-500"
401→ style={{
402→ width: `${(currentProject.current_phase / currentProject.total_phases) * 100}%`,
403→ }}
404→ />
405→ </div>
406→ </div>
407→ <span className="text-2xl font-bold">
408→ {Math.round((currentProject.current_phase / currentProject.total_phases) * 100)}%
409→ </span>
410→ </div>
411→ </div>
412→
413→ {/* Recent Activity */}
414→ <div className="bg-slate-800 rounded-lg p-6 border border-slate-700">
415→ <h2 className="text-lg font-semibold mb-4">Recent Activity</h2>
416→ <div className="space-y-3">
417→ {activeSessions.length === 0 ? (
418→ <p className="text-slate-500 italic">No recent activity</p>
419→ ) : (
420→ activeSessions.slice(0, 5).map((session) => (
421→ <div
422→ key={session.id}
423→ className="flex items-center justify-between py-2 border-b border-slate-700 last:border-0"
424→ >
425→ <div className="flex items-center gap-3">
426→ <div
427→ className={`w-2 h-2 rounded-full ${
428→ session.status === 'running'
429→ ? 'bg-accent animate-pulse'
430→ : session.status === 'completed'
431→ ? 'bg-green-400'
432→ : session.status === 'failed'
433→ ? 'bg-error'
434→ : 'bg-slate-500'
435→ }`}
436→ />
437→ <span className="capitalize">{session.agent_type.replace('_', ' ')} Agent</span>
438→ </div>
439→ <span className="text-sm text-slate-400">{session.status}</span>
440→ </div>
441→ ))
442→ )}
443→ </div>
444→ </div>
445→ </div>
446→ )
447→}
448→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>