439 lines
18 KiB
Markdown
439 lines
18 KiB
Markdown
# 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.
|