feat: create comprehensive design document

Technical decisions with rationale:
- Label selection: parallel state to avoid disturbing line logic
- Label list UI: collapsible section in Toolbox with search/filter
- API design: extend DELETE with query params for bulk operations
- Docker: standalone output with multi-stage build
- Theme: CSS variables + Tailwind extension strategy
- Feedback: Toast component with terminal formatting

Includes risk mitigation, migration plan, and testing checklist
This commit is contained in:
Marko Djordjevic 2026-02-12 14:49:26 +01:00
parent c41eb622fe
commit ce96895bc3

View file

@ -0,0 +1,439 @@
# 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 `<Collapsible>` 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: <count> }` 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` `<head>`
- 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.