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:
parent
c41eb622fe
commit
ce96895bc3
1 changed files with 439 additions and 0 deletions
|
|
@ -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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue