navidocs/server/docs/TECHNICAL_DECISIONS_SUMMARY.md
Danny Stocker 58b344aa31 FINAL: P0 blockers fixed + Joe Trader + ignore binaries
Fixed:
- Price: €800K-€1.5M, Sunseeker added
- Agent 1: Joe Trader persona + actual sale ads research
- Ignored meilisearch binary + data/ (too large for GitHub)
- SESSION_DEBUG_BLOCKERS.md created

Ready for Session 1 launch.

🤖 Generated with Claude Code
2025-11-13 01:29:59 +01:00

360 lines
16 KiB
Markdown

# Technical Decisions Summary
**Project**: Document Viewer Search Improvements
**Date**: 2025-10-21
**Status**: Approved Design
## Key Decisions Matrix
### 1. Component Architecture
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Search Results Component** | Unified `SearchResults.vue` shared by DocumentView and SearchView | DRY principle, consistent UX, easier maintenance | Separate components per view (rejected: code duplication) |
| **Result Card** | Extract to `SearchResultCard.vue` | Reusable across dropdown and full-page views | Inline template (rejected: hard to test) |
| **Dropdown Container** | Separate `SearchDropdown.vue` with Teleport | Clean separation of concerns, portal for z-index | Inline in DocumentView (rejected: messy code) |
| **Navigation Controls** | `CompactNavControls.vue` component | Encapsulates nav logic, reusable | Mix with DocumentView (rejected: poor separation) |
**Decision**: Component-based architecture with clear responsibilities
---
### 2. State Management
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Global State** | NO global store (Pinia/Vuex) | Search is ephemeral, component-scoped state sufficient | Pinia store (rejected: unnecessary complexity) |
| **Composables** | `useDocumentSearch.js`, `useSearchResults.js` | Composition API pattern, code reuse without coupling | Mixin-based (rejected: Vue 3 deprecates mixins) |
| **Search Caching** | In-memory Map with 5-min TTL | Fast repeat queries, automatic cleanup | localStorage (rejected: not sensitive data) |
| **Result Grouping** | Computed property in composable | Reactive, declarative, no manual updates | Manual state management (rejected: error-prone) |
**Decision**: Composition API with reactive composables, no global store
---
### 3. API Design
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Document Scoping** | Add `currentDocumentId` param to existing `/api/search` | Backward compatible, single endpoint | New `/api/search/scoped` endpoint (rejected: fragmentation) |
| **Grouping Metadata** | Return `grouping` object in response | Frontend can display counts without recalculation | Client-side grouping only (rejected: missed optimization) |
| **Result Prioritization** | Backend adds `_isCurrentDoc` flag | Single source of truth, less client logic | Client-only sorting (rejected: duplicates logic) |
| **Pagination** | Existing `limit`/`offset` params | Already implemented, works well | Cursor-based (rejected: overkill) |
**Decision**: Extend existing API with minimal changes, maintain backward compatibility
**API Contract**:
```json
POST /api/search
Request: {
"q": "query",
"currentDocumentId": "doc-123", // NEW (optional)
"limit": 50
}
Response: {
"hits": [...],
"grouping": { // NEW
"currentDocument": { "hitCount": 8 },
"otherDocuments": { "hitCount": 12, "documentCount": 3 }
}
}
```
---
### 4. CSS/Positioning Strategy
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Navigation Bar** | `position: sticky; top: 0` | Smooth scroll behavior, no JS needed | `position: fixed` (rejected: layout jumps) |
| **Search Dropdown** | `position: fixed` + Teleport to body | Overlays content, no z-index issues | Absolute position (rejected: overflow issues) |
| **Backdrop Blur** | CSS `backdrop-filter: blur(16px)` | Native browser support, GPU accelerated | JS blur library (rejected: performance cost) |
| **Transitions** | CSS transitions only | 60fps, hardware accelerated | JS animation libraries (rejected: bundle size) |
| **Mobile Layout** | Full-screen modal (< 768px), dropdown (> 768px) | Better touch UX, more space on mobile | Same dropdown everywhere (rejected: poor mobile UX) |
**Decision**: Modern CSS features (sticky, backdrop-filter) with progressive enhancement
**Browser Support**:
- Chrome/Edge 90+
- Firefox 88+
- Safari 14+
- All features have 95%+ browser support as of 2025
---
### 5. Search UX Pattern
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **DocumentView Search** | Dropdown results | Non-intrusive, keeps context | Full-page (rejected: disrupts reading), Sidebar (rejected: screen space) |
| **Result Grouping** | "This Document" first, then "Other Documents" | Users prioritize current doc 80% of time | Flat list (rejected: poor scannability) |
| **Debounce Delay** | 300ms | Balance between responsiveness and API load | 500ms (rejected: feels slow), 100ms (rejected: too many requests) |
| **Results Per Group** | 5 current doc, 5 other docs, "Show more" button | Compact, shows variety, infinite scroll option | Show all (rejected: overwhelming), 3 per group (rejected: too few) |
| **Keyboard Navigation** | Arrow keys, Enter, Escape | Power user efficiency | Mouse-only (rejected: poor accessibility) |
**Decision**: Google-like dropdown with intelligent grouping
**UX Flow**:
```
User types → 300ms debounce → API call → Dropdown renders
"This Document (8)" ← Always first
Page 12: ...
Page 15: ...
[5 results max]
"Other Documents (4)" ← Collapsed by default on mobile
Engine Manual p8: ...
[Show 8 more]
Click result → Navigate to page → Dropdown closes
```
---
### 6. Performance Optimizations
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Search Debouncing** | `useDebounceFn` from @vueuse/core | Battle-tested, TypeScript support | Custom debounce (rejected: reinventing wheel) |
| **Result Caching** | 5-minute in-memory cache | Repeat searches instant | IndexedDB (rejected: overkill), No cache (rejected: poor UX) |
| **Virtual Scrolling** | Only if > 100 results | Most searches return < 50 results | Always virtual scroll (rejected: complexity), Never (rejected: performance) |
| **Image Lazy Loading** | `IntersectionObserver` API | Native, no library needed | Eager loading (rejected: slow), Library (rejected: bundle size) |
| **Bundle Splitting** | Code split search components | Faster initial load | Single bundle (rejected: larger initial load) |
**Decision**: Pragmatic optimizations based on real-world usage
**Performance Targets**:
- Search input API response: < 100ms (p90)
- API response Dropdown render: < 50ms
- Dropdown open/close: 60fps (16ms per frame)
- Total (type to see results): < 400ms
---
### 7. Accessibility
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **ARIA Roles** | `role="combobox"`, `role="listbox"`, `role="option"` | WCAG 2.1 AA compliance | No ARIA (rejected: fails screen readers) |
| **Keyboard Navigation** | Full keyboard support (Tab, Arrow, Enter, Escape) | Power users, accessibility requirement | Mouse-only (rejected: excludes users) |
| **Focus Management** | Trap focus in dropdown, restore on close | Prevents keyboard users getting lost | No focus trap (rejected: poor UX) |
| **Color Contrast** | 7:1 for text, 4.5:1 for highlights (AAA) | Readable for low vision users | 4.5:1 everywhere (rejected: not AAA) |
| **Screen Reader** | Live regions for result count announcements | Blind users know search completed | Silent (rejected: no feedback) |
**Decision**: WCAG 2.1 AAA where feasible, AA minimum
**ARIA Structure**:
```html
<div role="combobox" aria-expanded="true" aria-owns="results-listbox">
<input role="searchbox" aria-label="Search documents" />
</div>
<ul id="results-listbox" role="listbox" aria-label="Search results">
<li role="option" aria-selected="false">...</li>
</ul>
<div aria-live="polite">8 results found</div>
```
---
### 8. Testing Strategy
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Unit Tests** | Vitest | Fast, Vue-native, Vite integration | Jest (rejected: slower setup) |
| **Component Tests** | @vue/test-utils | Official Vue testing library | Testing Library (rejected: different philosophy) |
| **E2E Tests** | Playwright | Modern, reliable, cross-browser | Cypress (rejected: fewer browsers), Selenium (rejected: slow) |
| **Coverage Target** | 80% for new code | Pragmatic balance | 100% (rejected: diminishing returns), None (rejected: risky) |
| **CI Integration** | GitHub Actions on PR | Automated, free for OSS | Manual testing (rejected: error-prone) |
**Decision**: Comprehensive testing with pragmatic coverage goals
**Test Pyramid**:
```
/\
/ \ E2E (10 tests)
/____\ - Search flow
/ \ - Navigation
/________\ - Mobile UX
/ \ Integration (30 tests)
/__________\ - Component interactions
- Composable logic
/ \ Unit (60 tests)
/______________\ - SearchResults.vue
- useDocumentSearch.js
- Result grouping logic
```
---
### 9. Security
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **XSS Prevention** | Vue auto-escaping + `v-html` only for Meilisearch responses | Meilisearch sanitizes HTML | DOMPurify library (rejected: Meilisearch already safe) |
| **Query Sanitization** | 200 char limit, strip `<>` characters | Prevent injection attacks | No sanitization (rejected: vulnerable) |
| **Row-Level Security** | Existing tenant tokens + org filtering | Already implemented | Client-side filtering only (rejected: data leak) |
| **Rate Limiting** | Debounce (300ms) + server-side rate limit | Prevent abuse | No limit (rejected: DoS vulnerable) |
**Decision**: Leverage existing security model, minimal additional code
---
### 10. Migration & Rollout
| Decision | Chosen Approach | Rationale | Alternatives Considered |
|----------|----------------|-----------|------------------------|
| **Rollout Strategy** | Phased: DocumentView SearchView HomeView | Test each integration point | Big bang (rejected: risky), Feature flag (rejected: complexity) |
| **Backward Compatibility** | Keep old components until full rollout | Safe rollback | Delete old code immediately (rejected: no rollback) |
| **Database Migrations** | None needed | No schema changes | Add search history table (rejected: Phase 2 feature) |
| **Deployment** | Standard CI/CD pipeline | No special process | Blue-green deployment (rejected: overkill for this change) |
**Decision**: Low-risk phased rollout with rollback capability
**Timeline**:
```
Week 1: Foundation components (can test in isolation)
Week 2: DocumentView integration (feature works end-to-end)
Week 3: SearchView refactor (remove duplication)
Week 4: HomeView polish + final QA
```
---
## Decision Ownership
| Area | Owner | Reviewer | Status |
|------|-------|----------|--------|
| Component Architecture | Tech Lead | Senior Engineer | Approved |
| API Design | Backend Lead | Tech Lead | Approved |
| UX Design | Product Designer | UX Lead | Approved |
| Performance | Tech Lead | DevOps | Approved |
| Accessibility | Frontend Lead | Accessibility Specialist | Approved |
| Testing | QA Lead | Tech Lead | Approved |
---
## Trade-offs Acknowledged
### 1. No Pinia/Vuex Store
**Trade-off**: Each view has its own search instance
**Benefit**: Simpler code, no global state pollution
**Cost**: Can't share search results between views
**Mitigation**: HTTP cache makes repeat queries fast anyway
### 2. Component-based vs. Renderless
**Trade-off**: `SearchResults.vue` has markup (not renderless)
**Benefit**: Easier to understand, faster to implement
**Cost**: Less flexible for advanced customization
**Mitigation**: Props allow sufficient customization for our needs
### 3. CSS Sticky vs. Fixed
**Trade-off**: Sticky doesn't overlay content
**Benefit**: Smoother scroll behavior, no layout shift
**Cost**: Scrolls out of view on long documents
**Mitigation**: Users rarely scroll far with search open
### 4. Dropdown vs. Modal
**Trade-off**: Dropdown has limited space
**Benefit**: Non-intrusive, keeps context
**Cost**: May truncate results on small screens
**Mitigation**: Full-screen modal on mobile (< 768px)
---
## Success Metrics (KPIs)
| Metric | Baseline | Target | Measurement |
|--------|----------|--------|-------------|
| Search response time | 150ms (p90) | < 100ms (p90) | Server logs |
| Time to first result | N/A (no search) | < 400ms | Lighthouse |
| Search success rate | 60% (estimate) | 80% | Analytics |
| Keyboard navigation usage | 0% | 15% | Event tracking |
| Accessibility violations | Unknown | 0 (axe-core) | CI pipeline |
| Bundle size increase | 0 KB | < 50 KB (gzipped) | Webpack stats |
| Test coverage | 0% (new code) | 80% | Vitest report |
---
## Risk Assessment
| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|-----------|
| **Performance degradation** | Low | High | Benchmarks before/after, load testing |
| **Accessibility issues** | Medium | High | Automated testing (axe-core), manual audit |
| **Cross-browser bugs** | Medium | Medium | Playwright tests on Chrome, Firefox, Safari |
| **Mobile UX problems** | Low | Medium | Responsive design from day 1, mobile testing |
| **API breaking changes** | Low | High | Backward compatible API, versioning |
| **Code duplication** | Medium | Low | Shared component library, code review |
**Overall Risk**: **LOW** (well-defined scope, incremental rollout)
---
## Open Questions (Deferred to Phase 2)
1. **Search History**: Store recent searches in localStorage?
- Decision: Phase 2 feature
- Reasoning: Nice-to-have, not critical for MVP
2. **Search Suggestions**: Auto-complete while typing?
- Decision: Phase 2 feature
- Reasoning: Requires additional Meilisearch config
3. **Advanced Filters**: Filter by document type, date, etc.?
- Decision: Phase 2 feature
- Reasoning: SearchView already has basic filters
4. **Search Analytics**: Track queries for insights?
- Decision: Phase 2 feature
- Reasoning: Privacy considerations, analytics setup
5. **Offline Search**: IndexedDB for offline mode?
- Decision: Phase 2 feature (PWA enhancement)
- Reasoning: Requires service worker, complex sync
---
## Final Recommendation
**APPROVED** for implementation with the following conditions:
1. **Phased rollout**: Start with DocumentView, validate before continuing
2. **Test coverage**: 80% minimum for new code
3. **Performance**: Maintain < 100ms search latency (p90)
4. **Accessibility**: Zero violations in axe-core audit
5. **Rollback plan**: Keep old components until full deployment
**Next Steps**:
1. Create GitHub issues for each phase
2. Assign to frontend engineer
3. Schedule design review after Phase 1
4. Schedule QA after Phase 3
5. Deploy to production after Phase 4
---
**Document Version**: 1.0
**Last Updated**: 2025-10-21
**Status**: Ready for Implementation
---
## Appendix: Technology Stack
| Layer | Technology | Version | Purpose |
|-------|-----------|---------|---------|
| **Frontend Framework** | Vue 3 | 3.5.0 | UI components |
| **Composition API** | @vueuse/core | Latest | Utilities (debounce, etc.) |
| **Search Engine** | Meilisearch | 0.41.0 | Full-text search |
| **HTTP Client** | Fetch API | Native | API calls |
| **Router** | Vue Router | 4.4.0 | Navigation |
| **Build Tool** | Vite | 5.0.0 | Bundler |
| **CSS Framework** | Tailwind CSS | 3.4.0 | Styling |
| **PDF Rendering** | PDF.js | 4.0.0 | Document viewer |
| **Testing** | Vitest + Playwright | Latest | Unit + E2E tests |
| **Backend** | Express.js | Latest | API server |
| **Database** | SQLite | Latest | Metadata storage |