candle-annotator/openspec/changes/archive/2026-02-12-enhance-annotation-features-and-deployment/tasks.md

19 KiB

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: <count> } 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<number | null>(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 <Toaster /> 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