# Design: Annotation Features and Deployment Enhancement ## Context ### Current State The candle annotator has a working annotation system with: - Line drawing with color selection, visual feedback, selection, and endpoint dragging (Phase 1-4 complete) - Label markers (break_up/break_down) added by clicking candles - Delete tool that removes annotations by clicking near them - SQLite database storing all annotations with geometry and color - Next.js 16 app with lightweight-charts for visualization - shadcn/ui components with dark slate theme - Local development workflow only ### Constraints - **No breaking changes**: Existing functionality must remain intact - **No new dependencies**: Use existing Next.js, React, Tailwind, shadcn/ui stack - **Single-user application**: No authentication or multi-user considerations - **SQLite database**: Must maintain compatibility with existing schema - **Lightweight-charts integration**: Chart library API is fixed, work within SVG overlay pattern - **Performance**: Chart rendering must maintain 60fps during annotation interactions ### Stakeholders - **Primary user**: Marko (developer/trader creating training data) - **Deployment target**: Server environment with Docker support - **Implementation**: Haiku model must be able to follow the implementation plan ## Goals / Non-Goals **Goals:** 1. Enable efficient bulk annotation management (delete all labels, delete all lines) 2. Provide visibility into all annotations via sidebar list with search/filter 3. Enable one-click production deployment via Docker 4. Transform UI to distinctive hacker/terminal aesthetic while maintaining usability 5. Create implementation plan detailed enough for Haiku model to execute **Non-Goals:** 1. Multi-user support or authentication 2. Undo/redo functionality (can be added later) 3. Import/export of individual annotations (CSV export already exists) 4. Real-time collaboration features 5. Mobile responsive design (desktop-focused tool) 6. Internationalization (English only) ## Decisions ### Decision 1: Label Selection Architecture **Choice**: Use separate state management for label selection parallel to existing line selection **Why?** - Lines and labels are fundamentally different (geometry vs point markers) - Existing line selection in `SvgOverlay.tsx` works well, don't disturb it - Labels rendered by `CandleChart.tsx` using lightweight-charts markers API - Parallel state (`selectedLabelId` alongside `selectedLineId`) keeps concerns separated **Alternatives Considered:** - **Unified selection system**: Single `selectedAnnotationId` for both lines and labels - ❌ Rejected: Would require refactoring working line selection code, higher risk - ❌ Different selection behaviors (lines: click geometry, labels: click marker) - **Event bus pattern**: Pub/sub for selection events - ❌ Rejected: Over-engineering for single-user app, adds complexity **Implementation:** - Add `selectedLabelId` state in `page.tsx` - Pass `selectedLabelId` and `setSelectedLabelId` to `CandleChart.tsx` - Make markers clickable via `onClick` handler in marker creation - Highlight selected marker with custom scale and color properties - Deselect on Escape key (global keyboard handler in page component) ### Decision 2: Label List UI Structure **Choice**: Collapsible section within existing Toolbox sidebar with virtualized scrolling **Why?** - Keeps all controls in one place (consistency) - Toolbox already has vertical space for expansion - Users may have hundreds/thousands of labels, need efficient rendering - No need for new layout areas or floating panels **Alternatives Considered:** - **Separate right sidebar**: New panel on right side of chart - ❌ Rejected: Takes screen real estate from chart, awkward horizontal layout - **Modal/overlay**: Floating panel over chart - ❌ Rejected: Blocks chart view, poor UX for browsing annotations while viewing data - **Bottom panel**: Horizontal list below chart - ❌ Rejected: Vertical list more natural for timestamped items, less vertical space for chart **Implementation:** - Add `` section in Toolbox below tool buttons, above Export - Section header: "Label Annotations (X)" with expand/collapse icon - When expanded: search input, filter dropdown, scrollable list (max-height: 400px) - Each list item: formatted timestamp, colored badge, delete button - Click item to select/highlight on chart - Use CSS `overflow-y: auto` with custom scrollbar styling (no virtualization needed initially) - If performance issues with 1000+ items, add `react-window` in future iteration ### Decision 3: API Design for Bulk Operations **Choice**: Extend existing DELETE `/api/annotations` with query parameters **Why?** - RESTful approach: DELETE resource with filters - No new endpoints to maintain - Backwards compatible (no query params = existing single-delete behavior still works via `/api/annotations/[id]`) - Simple to implement and test **Query Parameter Schema:** ``` DELETE /api/annotations?type=line // Delete all lines DELETE /api/annotations?type=break_up // Delete all break_up labels DELETE /api/annotations?type=break_down // Delete all break_down labels DELETE /api/annotations?type=break_up,break_down // Delete all labels DELETE /api/annotations?all=true // Delete everything (nuclear option) ``` **Alternatives Considered:** - **POST /api/annotations/bulk-delete**: Separate endpoint - ❌ Rejected: Not RESTful (POST for delete), adds complexity - **DELETE with request body**: `{ types: ['break_up', 'break_down'] }` - ❌ Rejected: DELETE with body is controversial in HTTP semantics, some proxies strip bodies - **Separate endpoints per type**: `/api/annotations/lines`, `/api/annotations/labels` - ❌ Rejected: More endpoints to maintain, inconsistent with current design **Implementation:** - Modify `src/app/api/annotations/route.ts` DELETE handler - Parse query params: `const { type, all } = request.nextUrl.searchParams` - Use Drizzle ORM with conditional WHERE clause - Return `{ success: true, deleted: }` for confirmation UI ### Decision 4: Docker Strategy - Standalone Output **Choice**: Next.js standalone output mode with multi-stage build **Why?** - Minimal production bundle (~50MB for Next.js runtime vs ~500MB with full node_modules) - Fast container builds (cached layers for dependencies) - Official Next.js recommendation for containerization - Better-sqlite3 works in standalone mode (native module properly bundled) **Dockerfile Structure:** ```dockerfile # Stage 1: Build FROM node:18-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build # Stage 2: Production FROM node:18-alpine WORKDIR /app RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001 COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/public ./public USER nextjs EXPOSE 3000 CMD ["node", "server.js"] ``` **Alternatives Considered:** - **Full node_modules copy**: Copy entire node_modules to production - ❌ Rejected: 5-10x larger images, slower deployments, unnecessary dev dependencies - **Distroless base images**: Google distroless/nodejs - ❌ Rejected: No shell for debugging, alpine is small enough, better-sqlite3 compatibility unclear - **Docker layer caching with pnpm/yarn**: Different package manager - ❌ Rejected: Project uses npm, no reason to change, npm v7+ has similar caching **Database Persistence:** - Mount `/app/data` as Docker volume - SQLite file at `/app/data/candles.db` - Named volume in docker-compose: `candle-data:/app/data` ### Decision 5: Theme Implementation - CSS Variables + Tailwind Extension **Choice**: Replace existing CSS variables in `globals.css` with hacker theme values, extend Tailwind config with custom colors and animations **Why?** - Minimal refactoring: existing shadcn/ui components already use CSS variables - Tailwind extension provides utility classes for new theme features (glow effects, animations) - No component rewrites needed, just style overrides - Easy to toggle/revert if needed (swap CSS variable values) **Color Mapping Strategy:** ```css /* globals.css - override existing variables */ :root { --background: 120 100% 4%; /* #0a0e0a - very dark green */ --foreground: 120 100% 50%; /* #00ff41 - matrix green */ --primary: 120 100% 50%; /* #00ff41 */ --destructive: 348 100% 50%; /* #ff0040 - neon red */ --border: 120 100% 10%; /* #003311 - dim green */ /* ... map all existing variables to hacker theme equivalents */ } ``` **Tailwind Extension:** ```typescript // tailwind.config.ts - extend existing config theme: { extend: { colors: { matrix: '#00ff41', matrixDim: '#00cc33', matrixDark: '#003311', neonRed: '#ff0040', neonCyan: '#00d4ff', // ... keep existing shadcn colors }, fontFamily: { mono: ['JetBrains Mono', 'Fira Code', 'Courier New', 'monospace'], }, boxShadow: { '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)', }, keyframes: { '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' }, }, }, animation: { 'glow-pulse': 'glow-pulse 2s ease-in-out infinite', 'flicker': 'flicker 0.1s ease-in-out', }, }, } ``` **Alternatives Considered:** - **Complete rewrite with different UI library**: Styled-components, Emotion - ❌ Rejected: Massive effort, no benefit, Tailwind + shadcn works well - **Theme toggle system**: Support multiple themes with switcher - ❌ Rejected: Not in requirements, adds complexity, hacker theme is the goal - **CSS-in-JS for glow effects**: Inline styles or styled-jsx - ❌ Rejected: Inconsistent with existing Tailwind approach, harder to maintain **Font Loading:** - Add JetBrains Mono via Google Fonts in `layout.tsx` `` - Fallback chain: JetBrains Mono → Fira Code → Courier New → system monospace - Set `font-family: var(--font-mono)` on body element ### Decision 6: Feedback Messages - Toast Component **Choice**: Create reusable `Toast` component using shadcn/ui toast primitive, styled for terminal aesthetic **Why?** - Consistent with existing shadcn/ui architecture - Already have toast primitive in `components/ui/` (likely from shadcn init) - Position: top-center, auto-dismiss after 4s - Terminal-style formatting: `> SUCCESS: Operation completed [3 items]` **Implementation:** ```typescript // src/components/ui/toast.tsx - style overrides // Add terminal-style formatting className="font-mono border-matrix bg-terminal text-matrix" // Usage in components: import { useToast } from '@/hooks/use-toast' const { toast } = useToast() toast({ title: "> SUCCESS", description: "Deleted all labels [15 items]", variant: "default", // green }) toast({ title: "> ERROR", description: "Failed to delete annotations [code: 500]", variant: "destructive", // red }) ``` **Alternatives Considered:** - **Custom notification system**: Build from scratch - ❌ Rejected: Reinventing the wheel, shadcn toast is battle-tested - **Browser alert()**: Native dialogs - ❌ Rejected: Ugly, blocks UI, no styling, poor UX - **Console.log only**: No visual feedback - ❌ Rejected: User won't see feedback, poor UX ## Risks / Trade-offs ### Risk 1: Theme Accessibility **Risk**: Neon green on black may strain eyes during extended use, contrast issues for colorblind users **Mitigation:** - Ensure WCAG AA contrast ratios (4.5:1 for text, 3:1 for UI elements) - Use #00ff41 which has sufficient contrast on #0a0e0a background - Provide option to revert theme in future (environment variable: `THEME=classic`) - Include prefers-reduced-motion support to disable animations **Trade-off**: Distinctive aesthetic vs universal accessibility (accepting some users may prefer different theme) ### Risk 2: Label List Performance with Large Datasets **Risk**: 10,000+ labels may cause slow rendering and sluggish scrolling in sidebar list **Mitigation:** - Set reasonable max-height (400px) for scrollable area - If performance issues arise, add pagination or react-window virtualization in follow-up - Monitor performance during development with test data (1000, 5000, 10000 labels) **Trade-off**: Simple initial implementation vs premature optimization (YAGNI principle - add virtualization only if needed) ### Risk 3: Docker Image Size for Better-SQLite3 **Risk**: Native SQLite module may require additional build dependencies in alpine, increasing image size **Mitigation:** - Node:18-alpine includes necessary build tools (python, make, g++) - Better-sqlite3 compiles during npm install in build stage - Final image only includes compiled binary, not build tools - Test build locally: `docker build --progress=plain .` to verify size (<200MB target) **Trade-off**: Alpine convenience vs potential build complexity (alpine is still best choice despite native modules) ### Risk 4: Breaking Existing Line Selection **Risk**: Adding label selection state and keyboard handlers may interfere with existing line selection logic **Mitigation:** - Keep line and label selection completely separate (different state variables) - Keyboard handler checks both states: if label selected, delete label; if line selected, delete line - Thoroughly test all existing line operations (draw, select, drag, delete) after changes - Use TypeScript strict mode to catch state management issues **Trade-off**: Duplicate code for selection logic vs refactoring working code (choose stability) ### Risk 5: Next.js Standalone Mode Compatibility **Risk**: Standalone output may not include all necessary files (static assets, environment configs) **Mitigation:** - Follow official Next.js documentation for standalone mode - Explicitly copy `.next/static/` and `public/` directories in Dockerfile - Test docker build locally before committing - Verify all pages, API routes, and static assets work in container **Trade-off**: Smaller image size vs potential missing files (standalone is well-tested by community) ### Risk 6: Confirmation Dialog UX **Risk**: Users may accidentally confirm bulk delete operations **Mitigation:** - Use clear, explicit confirmation messages: "Delete all X annotations? This cannot be undone." - Require deliberate click on "Confirm" button (not Enter key, no auto-focus) - Show count of items to be deleted in confirmation message - Use destructive button styling (red) to signal danger - No undo system (out of scope), but users can re-annotate from CSV data **Trade-off**: Safety vs speed (prioritize safety for destructive operations) ## Migration Plan ### Phase 1: Label Management (No Deployment Impact) 1. Add `selectedLabelId` state and selection handlers in `CandleChart.tsx` 2. Build label list UI in `Toolbox.tsx` (collapsible section) 3. Implement search/filter functionality 4. Add keyboard delete handler for labels 5. Test with existing database, no schema changes 6. **Deployment**: None (dev only), commit after testing ### Phase 2: API Extensions (Backwards Compatible) 1. Modify `DELETE /api/annotations` to accept query params 2. Add bulk delete logic with Drizzle ORM 3. Create `GET /api/health` endpoint 4. Test bulk operations with Postman/curl 5. **Deployment**: None (API changes backwards compatible), commit after testing ### Phase 3: Hacker Theme (Visual Only) 1. Load JetBrains Mono font in `layout.tsx` 2. Update `globals.css` with new CSS variables 3. Extend `tailwind.config.ts` with custom colors, shadows, animations 4. Apply theme classes to existing components (Toolbox, buttons, inputs) 5. Test visual appearance and contrast ratios 6. **Deployment**: None (CSS/styling only), commit after visual review ### Phase 4: Docker Setup (New Deployment Path) 1. Update `next.config.js` to enable standalone output 2. Create `Dockerfile` with multi-stage build 3. Create `docker-compose.yml` with volume configuration 4. Create `.dockerignore` and `.env.example` 5. Test local build: `docker-compose up --build` 6. Verify database persistence across container restarts 7. Update `DEPLOYMENT.md` with Docker instructions 8. **Deployment**: Push image to registry, deploy to production server ### Rollback Strategy - **Label Management**: No rollback needed (additive feature, disable by not using) - **API Extensions**: Backwards compatible, no rollback needed - **Hacker Theme**: Revert `globals.css` and `tailwind.config.ts` commits - **Docker**: Rollback to direct Node.js deployment, use existing dev workflow ### Testing Checklist - [ ] All existing line drawing features work unchanged - [ ] Label selection highlights correct marker on chart - [ ] Label list displays all annotations with correct formatting - [ ] Search/filter correctly narrows label list - [ ] Delete individual label removes from chart and list - [ ] Delete all labels removes all markers after confirmation - [ ] Bulk delete API endpoints return correct counts - [ ] Health check endpoint returns 200 - [ ] Theme maintains WCAG AA contrast ratios - [ ] All text uses monospace font - [ ] Glow effects appear on hover/active states - [ ] Docker container starts successfully - [ ] Database persists across container restarts - [ ] Application accessible on http://localhost:3000 in container ## Open Questions 1. **Should we add label editing (change type)?** - Not in current scope, but consider for future - Would require modal dialog to change break_up ↔ break_down 2. **Should label list show price information?** - Specs don't mention it, but could be useful - Decision: Show timestamp and type only initially, add price in follow-up if requested 3. **Should we limit the number of labels displayed in sidebar?** - Pagination vs infinite scroll vs show all - Decision: Show all initially, add pagination only if performance issues arise 4. **Should Docker image include sample CSV data?** - Useful for testing, but increases image size - Decision: No, keep image minimal, provide sample CSV in repo documentation 5. **Should we add keyboard shortcuts for tool switching (e.g., 'L' for line tool)?** - Not in requirements, but could enhance UX - Decision: Out of scope for this change, consider in future UX iteration These questions do not block implementation - proceed with spec-defined behavior and defer enhancements.