diff --git a/openspec/changes/enhance-annotation-features-and-deployment/tasks.md b/openspec/changes/enhance-annotation-features-and-deployment/tasks.md new file mode 100644 index 0000000..1b5e7e5 --- /dev/null +++ b/openspec/changes/enhance-annotation-features-and-deployment/tasks.md @@ -0,0 +1,197 @@ +# Implementation Tasks + +## 1. Setup and Configuration + +- [ ] 1.1 Load JetBrains Mono font from Google Fonts in `src/app/layout.tsx` (add to head with weights 400, 500, 700) +- [ ] 1.2 Update `next.config.js` to enable standalone output mode (`output: 'standalone'`) +- [ ] 1.3 Verify all existing features work (run dev server, test line drawing, label placement, delete tool) + +## 2. API Extensions for Bulk Operations + +- [ ] 2.1 Read existing `src/app/api/annotations/route.ts` DELETE handler +- [ ] 2.2 Add query parameter parsing in DELETE handler (`type` and `all` params from `request.nextUrl.searchParams`) +- [ ] 2.3 Add conditional WHERE clause logic: if `type` param exists, filter by `label_type IN (comma-separated types)`, if `all=true` delete everything, else return 400 error +- [ ] 2.4 Update return value to include count: `{ success: true, deleted: }` using `result.length` from Drizzle delete operation +- [ ] 2.5 Test bulk delete via curl: `curl -X DELETE "http://localhost:3000/api/annotations?type=break_up"` (verify count returned) +- [ ] 2.6 Create new file `src/app/api/health/route.ts` with GET handler returning `{ status: 'ok', timestamp: Date.now() }` +- [ ] 2.7 Add optional database check in health endpoint: if `?check=db` query param exists, attempt simple `SELECT 1` query and return 503 on failure +- [ ] 2.8 Test health endpoint: `curl http://localhost:3000/api/health` (verify 200 response) + +## 3. Hacker Theme - CSS Variables and Tailwind + +- [ ] 3.1 Read current `src/app/globals.css` and note existing CSS variable names +- [ ] 3.2 Replace CSS variable values in `:root` with hacker theme colors: `--background: 120 100% 4%` (#0a0e0a), `--foreground: 120 100% 50%` (#00ff41), `--primary: 120 100% 50%`, `--destructive: 348 100% 50%` (#ff0040), `--border: 120 100% 10%` (#003311), map all other variables to theme equivalents +- [ ] 3.3 Update body font-family in `globals.css` to: `font-family: 'JetBrains Mono', 'Fira Code', 'Courier New', monospace;` +- [ ] 3.4 Add custom selection styling in `globals.css`: `::selection { background: rgba(0, 255, 65, 0.4); color: #00ff41; }` +- [ ] 3.5 Add custom scrollbar styles in `globals.css`: `::-webkit-scrollbar { width: 8px; }`, `::-webkit-scrollbar-track { background: #003311; }`, `::-webkit-scrollbar-thumb { background: #00ff41; border-radius: 4px; }` +- [ ] 3.6 Read `tailwind.config.ts` and locate `theme.extend.colors` object +- [ ] 3.7 Add custom colors to Tailwind config: `matrix: '#00ff41'`, `matrixDim: '#00cc33'`, `matrixDark: '#003311'`, `neonRed: '#ff0040'`, `neonCyan: '#00d4ff'`, `neonYellow: '#ffff00'`, `terminal: '#0a0e0a'`, `terminalLight: '#0d110d'` +- [ ] 3.8 Add custom fontFamily to Tailwind config: `mono: ['JetBrains Mono', 'Fira Code', 'Courier New', 'monospace']` +- [ ] 3.9 Add custom boxShadow to Tailwind config: `'glow-sm': '0 0 8px #00ff41'`, `'glow': '0 0 15px #00ff41, 0 0 30px rgba(0,255,65,0.5)'`, `'glow-lg': '0 0 20px #00ff41, 0 0 40px rgba(0,255,65,0.5)'` +- [ ] 3.10 Add keyframes to Tailwind config: `'glow-pulse': { '0%, 100%': { boxShadow: '0 0 15px #00ff41' }, '50%': { boxShadow: '0 0 30px #00ff41, 0 0 50px rgba(0,255,65,0.5)' } }`, `'flicker': { '0%, 100%': { opacity: '1' }, '50%': { opacity: '0.8' } }` +- [ ] 3.11 Add animations to Tailwind config: `'glow-pulse': 'glow-pulse 2s ease-in-out infinite'`, `'flicker': 'flicker 0.1s ease-in-out'` +- [ ] 3.12 Test theme in browser: verify green text, black background, monospace font on all elements + +## 4. Hacker Theme - Component Styling + +- [ ] 4.1 Read `src/components/Toolbox.tsx` and identify button elements +- [ ] 4.2 Update button hover states in Toolbox to add glow effect: add `hover:shadow-glow` class to all Button components +- [ ] 4.3 Update active tool buttons in Toolbox: when `activeTool` matches, add `animate-glow-pulse` class +- [ ] 4.4 Update input field styling (search input in label list, if exists): add `focus:shadow-glow-sm focus:border-matrix` classes +- [ ] 4.5 Read `src/components/ui/button.tsx` and add destructive variant glow: in destructive variant, change hover shadow to red: `hover:shadow-[0_0_15px_#ff0040]` +- [ ] 4.6 Test button styling: hover over buttons (should glow green), click to activate tool (should pulse), hover delete button (should glow red) + +## 5. Label Management - State and Selection + +- [ ] 5.1 Read `src/app/page.tsx` and locate existing state declarations (activeTool, selectedColor, etc.) +- [ ] 5.2 Add new state in page.tsx: `const [selectedLabelId, setSelectedLabelId] = useState(null)` +- [ ] 5.3 Pass selectedLabelId and setSelectedLabelId as props to CandleChart component +- [ ] 5.4 Read `src/components/CandleChart.tsx` and locate marker creation code (where break_up/break_down markers are added) +- [ ] 5.5 Add onClick handler to marker options: in marker creation config, add `onClick: () => props.onLabelSelect?.(annotation.id)` +- [ ] 5.6 Update CandleChart props interface to accept `selectedLabelId: number | null`, `onLabelSelect: (id: number) => void` +- [ ] 5.7 Wire up onLabelSelect in page.tsx: `onLabelSelect={(id) => setSelectedLabelId(id === selectedLabelId ? null : id)}` +- [ ] 5.8 Add visual highlight for selected marker: when rendering markers, if `annotation.id === selectedLabelId`, set marker size to 1.5x and add glow effect (modify marker shape/color options) +- [ ] 5.9 Test label selection: click on break_up arrow (should highlight), click again (should deselect), click different marker (should switch selection) + +## 6. Label Management - Keyboard Delete + +- [ ] 6.1 Read `src/app/page.tsx` and locate any existing keyboard event handlers +- [ ] 6.2 Add global keyboard handler in page.tsx useEffect: `window.addEventListener('keydown', handleKeyDown)` +- [ ] 6.3 Implement handleKeyDown function: if `e.key === 'Delete' || e.key === 'Backspace'`, check if `selectedLabelId !== null` +- [ ] 6.4 In handleKeyDown, when Delete pressed with selected label: call `fetch('/api/annotations/' + selectedLabelId, { method: 'DELETE' })` +- [ ] 6.5 After successful delete, call chart refresh method and reset `setSelectedLabelId(null)` +- [ ] 6.6 Add cleanup in useEffect: `return () => window.removeEventListener('keydown', handleKeyDown)` +- [ ] 6.7 Test keyboard delete: select a label marker, press Delete key (marker should disappear), verify database updated + +## 7. Label Management - Sidebar List UI Structure + +- [ ] 7.1 Read `src/components/Toolbox.tsx` and locate the export button section (bottom of component) +- [ ] 7.2 Import Collapsible components from shadcn/ui: check if `src/components/ui/collapsible.tsx` exists, if not install via `npx shadcn@latest add collapsible` +- [ ] 7.3 Add state in Toolbox for collapsed state: `const [labelsExpanded, setLabelsExpanded] = useState(true)` +- [ ] 7.4 Add new section above Export button: wrap in Collapsible component with `open={labelsExpanded}` and `onOpenChange={setLabelsExpanded}` +- [ ] 7.5 Create section header button: "Label Annotations (X)" with ChevronDown/ChevronUp icon from lucide-react, clicking toggles `labelsExpanded` +- [ ] 7.6 Inside Collapsible.Content, add container div with `className="max-h-96 overflow-y-auto space-y-2 p-2"` +- [ ] 7.7 Add count display in header: calculate `breakUpCount` and `breakDownCount` from props.annotations filtered by `label_type`, display as "Break Up: X | Break Down: Y" +- [ ] 7.8 Test collapsible: click header (should expand/collapse), verify smooth animation + +## 8. Label Management - Sidebar List Content + +- [ ] 8.1 Update Toolbox props to accept `annotations: Annotation[]`, `selectedLabelId: number | null`, `onLabelSelect: (id: number) => void`, `onLabelDelete: (id: number) => void` +- [ ] 8.2 Pass annotations from page.tsx to Toolbox (retrieve from CandleChart or fetch directly in page.tsx) +- [ ] 8.3 Filter annotations in Toolbox: `const labelAnnotations = annotations.filter(a => a.label_type === 'break_up' || a.label_type === 'break_down')` +- [ ] 8.4 Sort labelAnnotations by timestamp descending: `labelAnnotations.sort((a, b) => b.timestamp - a.timestamp)` +- [ ] 8.5 Map over labelAnnotations to render list items: each item in a div with border, padding, cursor-pointer, onClick calls onLabelSelect +- [ ] 8.6 Format timestamp in each item: `new Date(timestamp * 1000).toLocaleString('en-US', { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })` +- [ ] 8.7 Add colored badge for label type: break_up shows "BREAK UP" in green bg, break_down shows "BREAK DOWN" in red bg +- [ ] 8.8 Add delete button (trash icon) to each item: onClick calls `onLabelDelete(annotation.id)` with stopPropagation to prevent selection +- [ ] 8.9 Highlight selected item: if `annotation.id === selectedLabelId`, add border-matrix and bg-matrix/10 classes +- [ ] 8.10 Handle empty state: if labelAnnotations.length === 0, show message "No labels yet. Click Break Up or Break Down tools to add labels." +- [ ] 8.11 Test list rendering: add several labels, verify they appear sorted by time, click to select, click delete button (should remove) + +## 9. Label Management - Search and Filter + +- [ ] 9.1 Add state for search in Toolbox: `const [searchText, setSearchText] = useState('')` +- [ ] 9.2 Add state for filter in Toolbox: `const [filterType, setFilterType] = useState<'all' | 'break_up' | 'break_down'>('all')` +- [ ] 9.3 Add search input above label list: Input component with placeholder "Search by timestamp...", value={searchText}, onChange={e => setSearchText(e.target.value)} +- [ ] 9.4 Add filter dropdown above label list: Select or dropdown with options "All", "Break Up", "Break Down", value={filterType}, onChange={setFilterType} +- [ ] 9.5 Apply filter to labelAnnotations: if filterType !== 'all', filter array by `a.label_type === filterType` +- [ ] 9.6 Apply search to labelAnnotations: filter by formatted timestamp includes searchText (case-insensitive): `formattedTimestamp.toLowerCase().includes(searchText.toLowerCase())` +- [ ] 9.7 Update count display to show filtered count: "Showing X of Y labels" +- [ ] 9.8 Test search: type "Feb" in search (should filter to February dates), test filter: select "Break Up" (should show only break_up labels) + +## 10. Label Management - Delete All Labels Button + +- [ ] 10.1 Import Dialog components from shadcn/ui: check if `src/components/ui/dialog.tsx` exists, if not install via `npx shadcn@latest add dialog` +- [ ] 10.2 Add state for confirmation dialog: `const [deleteAllDialogOpen, setDeleteAllDialogOpen] = useState(false)` +- [ ] 10.3 Add "Delete All Labels" button below search/filter controls: Button with destructive variant, onClick opens dialog, disabled if labelAnnotations.length === 0 +- [ ] 10.4 Create Dialog component with AlertDialog pattern: AlertDialogTitle "Delete all label annotations?", AlertDialogDescription "This will remove all Break Up and Break Down markers. This cannot be undone." +- [ ] 10.5 Add Cancel button in dialog: onClick closes dialog without action +- [ ] 10.6 Add Confirm button in dialog: destructive variant, onClick calls API DELETE with type=break_up,break_down +- [ ] 10.7 Implement delete all handler: `fetch('/api/annotations?type=break_up,break_down', { method: 'DELETE' })`, on success refresh annotations and close dialog +- [ ] 10.8 Show toast notification after delete: use shadcn toast to display "> SUCCESS: Deleted all labels [X items]" +- [ ] 10.9 Test delete all: add multiple labels, click Delete All Labels, confirm dialog (all should disappear), verify database emptied + +## 11. Toast Feedback System + +- [ ] 11.1 Install shadcn toast if not present: `npx shadcn@latest add toast` +- [ ] 11.2 Read `src/components/ui/toast.tsx` and `src/hooks/use-toast.ts` +- [ ] 11.3 Update toast styling in `toast.tsx`: add `font-mono` class, update border color to use matrix theme colors +- [ ] 11.4 Import and add Toaster component in `src/app/layout.tsx` (add `` in body) +- [ ] 11.5 Create useToast hook wrapper in components that need feedback: import `{ useToast } from '@/hooks/use-toast'` +- [ ] 11.6 Add toast calls after successful operations: delete label → `toast({ title: "> SUCCESS", description: "Deleted label [1 item]" })` +- [ ] 11.7 Add toast calls for errors: catch API errors → `toast({ title: "> ERROR", description: "Failed to delete [code: 500]", variant: "destructive" })` +- [ ] 11.8 Test toast: perform delete operation (should see green success toast), cause error by disconnecting network (should see red error toast) + +## 12. Docker - Dockerfile Creation + +- [ ] 12.1 Create `.dockerignore` file in project root with contents: `node_modules`, `.next`, `.git`, `data/`, `*.md`, `.env*`, `*.log`, `coverage/`, `.DS_Store` +- [ ] 12.2 Create `Dockerfile` in project root: start with `FROM node:18-alpine AS builder` +- [ ] 12.3 Add build stage instructions: `WORKDIR /app`, `COPY package*.json ./`, `RUN npm ci`, `COPY . .`, `RUN npm run build` +- [ ] 12.4 Add production stage: `FROM node:18-alpine`, `WORKDIR /app` +- [ ] 12.5 Create non-root user: `RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001` +- [ ] 12.6 Copy standalone files: `COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./` +- [ ] 12.7 Copy static files: `COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static` +- [ ] 12.8 Copy public files: `COPY --from=builder --chown=nextjs:nodejs /app/public ./public` +- [ ] 12.9 Create data directory: `RUN mkdir -p /app/data && chown nextjs:nodejs /app/data` +- [ ] 12.10 Set environment variables: `ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0` +- [ ] 12.11 Set user and expose port: `USER nextjs`, `EXPOSE 3000` +- [ ] 12.12 Add healthcheck: `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1` +- [ ] 12.13 Set CMD: `CMD ["node", "server.js"]` +- [ ] 12.14 Test Dockerfile: run `docker build -t candle-annotator .` (should build successfully, check image size with `docker images`) + +## 13. Docker - Compose Configuration + +- [ ] 13.1 Create `docker-compose.yml` in project root +- [ ] 13.2 Set compose version: `version: '3.8'` (or use no version for latest) +- [ ] 13.3 Define service: `services: candle-annotator:` +- [ ] 13.4 Add build context: `build: .` +- [ ] 13.5 Add port mapping: `ports: - "3000:3000"` +- [ ] 13.6 Add volume mount: `volumes: - candle-data:/app/data` +- [ ] 13.7 Add environment variables: `environment: - NODE_ENV=production` (optionally use `env_file: .env`) +- [ ] 13.8 Add restart policy: `restart: unless-stopped` +- [ ] 13.9 Define named volume: `volumes: candle-data:` +- [ ] 13.10 Create `.env.example` file with template variables: `NODE_ENV=production`, `PORT=3000`, `DATABASE_PATH=/app/data/candles.db` +- [ ] 13.11 Test compose: run `docker-compose up --build` (should start container and be accessible at http://localhost:3000) +- [ ] 13.12 Test persistence: upload CSV, add annotations, stop container (`docker-compose down`), restart (`docker-compose up`), verify data persists + +## 14. Documentation Updates + +- [ ] 14.1 Read existing `DEPLOYMENT.md` file +- [ ] 14.2 Add "Docker Deployment" section with prerequisites (Docker, docker-compose installed) +- [ ] 14.3 Add build instructions: `docker-compose build` or `docker build -t candle-annotator .` +- [ ] 14.4 Add run instructions: `docker-compose up -d` for detached mode, `docker-compose logs -f` to view logs +- [ ] 14.5 Add environment setup: copy `.env.example` to `.env`, edit variables as needed +- [ ] 14.6 Add volume management: explain data persists in `candle-data` volume, show backup command `docker cp candle-annotator:/app/data/candles.db ./backup.db` +- [ ] 14.7 Add troubleshooting section: port conflicts (change PORT in .env), permission errors (check volume ownership), build failures (clear cache with `docker-compose build --no-cache`) +- [ ] 14.8 Add update procedure: `git pull`, `docker-compose down`, `docker-compose up --build -d` +- [ ] 14.9 Update `README.md` with Docker quickstart: add section "Docker Deployment" linking to DEPLOYMENT.md +- [ ] 14.10 Update `CLAUDE_DESCRIPTION.md` with new features: label management sidebar, hacker theme, Docker support + +## 15. Integration Testing and Validation + +- [ ] 15.1 Test full label workflow: create 10 break_up and 10 break_down labels using chart tools +- [ ] 15.2 Test label selection: click markers on chart (should highlight), verify selection state reflected in sidebar list +- [ ] 15.3 Test sidebar list: verify all 20 labels appear sorted by timestamp, click list item (should highlight marker on chart) +- [ ] 15.4 Test search: type partial timestamp (should filter list), clear search (should show all) +- [ ] 15.5 Test filter: select "Break Up" filter (should show only 10 items), select "Break Down" (should show other 10), select "All" (should show 20) +- [ ] 15.6 Test delete individual label: click trash icon on list item (should remove from list and chart), verify database updated +- [ ] 15.7 Test delete all labels: click "Delete All Labels" button, confirm dialog (all 20 should disappear), verify success toast appears +- [ ] 15.8 Test keyboard delete: create label, click to select, press Delete key (should remove), create another, press Backspace (should remove) +- [ ] 15.9 Test existing line features: draw lines (should still work), select lines (should still work), drag endpoints (should still work), delete lines (should still work) +- [ ] 15.10 Test theme visual appearance: verify monospace font on all text, green color scheme throughout, glow effects on hover, borders and shadows match specs +- [ ] 15.11 Test health endpoint: `curl http://localhost:3000/api/health` (should return 200 with { status: 'ok' }), test with db check `curl http://localhost:3000/api/health?check=db` +- [ ] 15.12 Test Docker deployment: build image, start container, access http://localhost:3000, upload CSV, create annotations, verify persistence after restart +- [ ] 15.13 Test contrast and accessibility: use browser dev tools to check contrast ratios (should meet WCAG AA 4.5:1), test with prefers-reduced-motion (animations should disable) +- [ ] 15.14 Verify no breaking changes: compare current functionality with pre-change state, confirm all original features work identically +- [ ] 15.15 Final smoke test: clean database, fresh Docker build, complete annotation workflow from CSV upload to export, verify output CSV includes all annotations + +## 16. Commit and Cleanup + +- [ ] 16.1 Review all changed files with `git status` and `git diff` +- [ ] 16.2 Commit label management changes: `git add src/app/page.tsx src/components/CandleChart.tsx src/components/Toolbox.tsx` and commit with message "feat: implement label management with sidebar list and search" +- [ ] 16.3 Commit API changes: `git add src/app/api/` and commit with message "feat: add bulk delete API and health endpoint" +- [ ] 16.4 Commit theme changes: `git add src/app/globals.css tailwind.config.ts src/app/layout.tsx src/components/ui/` and commit with message "feat: implement hacker theme with matrix colors and monospace fonts" +- [ ] 16.5 Commit Docker files: `git add Dockerfile docker-compose.yml .dockerignore .env.example next.config.js` and commit with message "feat: add Docker deployment with multi-stage build" +- [ ] 16.6 Commit documentation: `git add README.md DEPLOYMENT.md CLAUDE_DESCRIPTION.md` and commit with message "docs: update deployment and feature documentation" +- [ ] 16.7 Tag release: `git tag v2.0.0 -m "Release: Label management, Docker deployment, hacker theme"` +- [ ] 16.8 Push to remote: `git push origin master --tags`