navidocs/server/docs/ARCHITECTURE_COMPONENT_DIAGRAM.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

32 KiB

Component Architecture Diagram

Overview Component Hierarchy

┌─────────────────────────────────────────────────────────────────────────┐
│                          NAVIDOCS APPLICATION                            │
└─────────────────────────────────────────────────────────────────────────┘
                                    │
                    ┌───────────────┼───────────────┐
                    │               │               │
        ┌───────────▼──────┐  ┌────▼─────┐  ┌─────▼──────────┐
        │   HomeView.vue   │  │SearchView│  │DocumentView.vue│
        │                  │  │   .vue   │  │                │
        │ ┌──────────────┐ │  │          │  │ ┌────────────┐ │
        │ │ SearchInput  │ │  │          │  │ │CompactNav  │ │
        │ │   Component  │ │  │          │  │ │  Controls  │ │
        │ └──────┬───────┘ │  │          │  │ └─────┬──────┘ │
        │        │         │  │          │  │       │        │
        │        │         │  │          │  │ ┌─────▼──────┐ │
        │  Redirect to     │  │          │  │ │ Search     │ │
        │  SearchView      │  │          │  │ │ Dropdown   │ │
        └──────────────────┘  └──────────┘  │ └─────┬──────┘ │
                                             └───────┼────────┘
                                                     │
                ┌────────────────────────────────────┘
                │
                │  Both use shared components:
                │
                ├─────────────────────────┐
                │                         │
        ┌───────▼─────────┐      ┌───────▼────────┐
        │ SearchResults   │      │SearchResult    │
        │   Component     │──────▶   Card         │
        │  (Container)    │      │  (Individual)  │
        └─────────────────┘      └────────────────┘

Data Flow Architecture

┌────────────────────────────────────────────────────────────────────┐
│                         USER INTERACTION                           │
└────────────────┬───────────────────────────────────────────────────┘
                 │
                 │ 1. Types search query
                 ▼
    ┌────────────────────────────┐
    │  SearchInput.vue Component │
    │  ┌──────────────────────┐  │
    │  │ v-model="searchQuery"│  │
    │  │ @input="handleSearch"│  │
    │  └──────────────────────┘  │
    └────────────┬───────────────┘
                 │
                 │ 2. Emits search event
                 ▼
    ┌─────────────────────────────────┐
    │  useDocumentSearch() Composable │
    │  ┌───────────────────────────┐  │
    │  │ searchWithScope(query)    │  │
    │  │   ↓                       │  │
    │  │ Calls useSearch().search()│  │
    │  └───────────────────────────┘  │
    └────────────┬────────────────────┘
                 │
                 │ 3. HTTP POST /api/search
                 ▼
    ┌─────────────────────────────────┐
    │  Backend: /routes/search.js     │
    │  ┌───────────────────────────┐  │
    │  │ POST /api/search          │  │
    │  │  - Add filters            │  │
    │  │  - Query Meilisearch      │  │
    │  │  - Add grouping metadata  │  │
    │  └───────────────────────────┘  │
    └────────────┬────────────────────┘
                 │
                 │ 4. Meilisearch query
                 ▼
    ┌─────────────────────────────────┐
    │       Meilisearch Engine        │
    │  ┌───────────────────────────┐  │
    │  │ Index: navidocs-pages     │  │
    │  │ Filter by:                │  │
    │  │  - userId/orgId           │  │
    │  │  - documentId (optional)  │  │
    │  │ Return highlighted hits   │  │
    │  └───────────────────────────┘  │
    └────────────┬────────────────────┘
                 │
                 │ 5. Results returned
                 ▼
    ┌─────────────────────────────────┐
    │  useSearchResults() Composable  │
    │  ┌───────────────────────────┐  │
    │  │ groupedByDocument()       │  │
    │  │  - Current doc first      │  │
    │  │  - Other docs grouped     │  │
    │  │  - Limit per group        │  │
    │  └───────────────────────────┘  │
    └────────────┬────────────────────┘
                 │
                 │ 6. Render results
                 ▼
    ┌─────────────────────────────────┐
    │   SearchResults.vue Component   │
    │  ┌───────────────────────────┐  │
    │  │ v-for="group in grouped"  │  │
    │  │   <h3>{{ group.title }}</h3>│
    │  │   <SearchResultCard />    │  │
    │  └───────────────────────────┘  │
    └────────────┬────────────────────┘
                 │
                 │ 7. User clicks result
                 ▼
    ┌─────────────────────────────────┐
    │  Navigate to DocumentView       │
    │  /document/{id}?page=12&q=bilge │
    └─────────────────────────────────┘

Component Communication Pattern

┌──────────────────────────────────────────────────────────────────┐
│                    PROPS DOWN / EVENTS UP                        │
└──────────────────────────────────────────────────────────────────┘

Parent: DocumentView.vue
│
├─ Props ────────┐
│                ▼
│     ┌──────────────────────┐
│     │ CompactNavControls   │
│     │                      │
│     │ Props:               │
│     │  - currentPage       │
│     │  - totalPages        │
│     │  - loading           │
│     │                      │
│     │ Emits:               │
│     │  @prev-page          │
│     │  @next-page          │
│     │  @goto-page          │
│     └──────────────────────┘
│                │
└─ Listens ◀─────┘
   @prev-page="previousPage()"
   @next-page="nextPage()"


Parent: DocumentView.vue
│
├─ Provide ──────┐
│                ▼
│     ┌──────────────────────┐
│     │ SearchDropdown       │
│     │  (Teleported)        │
│     │                      │
│     │ Props:               │
│     │  - isOpen            │
│     │  - style (dynamic)   │
│     │                      │
│     │ Children:            │
│     │  <SearchResults>     │
│     │    <SearchResultCard>│
│     └──────────────────────┘
│                │
└─ Inject ◀──────┘
   closeDropdown()
   navigateToResult()

State Management Flow

┌────────────────────────────────────────────────────────────┐
│              COMPOSITION API STATE (No Pinia)              │
└────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│  DocumentView.vue                                       │
│                                                         │
│  const {                                                │
│    searchQuery,        ◀── Reactive state              │
│    isDropdownOpen,     ◀── Reactive state              │
│    groupedResults,     ◀── Computed from search        │
│    prioritizedResults, ◀── Computed (current doc first)│
│    searchWithScope,    ◀── Async function              │
│    closeDropdown       ◀── Action function             │
│  } = useDocumentSearch(documentId)                      │
│                                                         │
│  └──► useDocumentSearch() ────────────────┐            │
│           │                                │            │
│           │ Composes                       │            │
│           │                                │            │
│           ├──► useSearch() ◀───────────────┼───┐        │
│           │      │                         │   │        │
│           │      └─► results.value         │   │        │
│           │      └─► loading.value         │   │        │
│           │      └─► search(q, options)    │   │        │
│           │                                │   │        │
│           └──► useSearchResults() ◀────────┘   │        │
│                  │                             │        │
│                  └─► groupedByDocument()       │        │
│                  └─► formatSnippet()           │        │
│                                                │        │
│  Each view has its own instance               │        │
│  (No shared global state)                     │        │
│                                                │        │
└────────────────────────────────────────────────┼────────┘
                                                 │
                         ┌───────────────────────┘
                         │
                         │  Shared logic, isolated state
                         │
              ┌──────────▼─────────┐
              │  SearchView.vue    │
              │                    │
              │  const {           │
              │    results,        │ ◀── Different instance
              │    search          │
              │  } = useSearch()   │
              │                    │
              └────────────────────┘

CSS Layout Strategy

┌─────────────────────────────────────────────────────────────────┐
│                    STICKY HEADER LAYOUT                         │
└─────────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────┐
│  .document-header (position: sticky; top: 0)  │ ◀── Always visible
│  ┌─────────────────────────────────────────┐  │
│  │ [←] [→] [1/45]  Boat Manual    [Search]│  │
│  └─────────────────────────────────────────┘  │
│  z-index: 50                                  │
│  backdrop-filter: blur(16px)                  │
└─────────────────┬─────────────────────────────┘
                  │
                  │ Teleport to body
                  │
    ┌─────────────▼──────────────────┐
    │  .search-dropdown              │
    │  (position: fixed)             │
    │  ┌──────────────────────────┐  │
    │  │ This Document (8)        │  │
    │  │  - Page 12: bilge pump..│  │
    │  │  - Page 15: maintenance.│  │
    │  │                          │  │
    │  │ Other Documents (4)      │  │
    │  │  - Engine Manual p8...  │  │
    │  └──────────────────────────┘  │
    │  z-index: 60                   │
    │  top: calc(header + 8px)       │
    └────────────────────────────────┘
                  │
┌─────────────────▼─────────────────────────────┐
│  .pdf-viewer-content                          │
│  scroll-margin-top: 80px                      │
│  ┌─────────────────────────────────────────┐  │
│  │                                         │  │
│  │         PDF Canvas                      │  │
│  │         + Text Layer                    │  │
│  │         + Image Overlays                │  │
│  │                                         │  │
│  │                                         │  │
│  └─────────────────────────────────────────┘  │
└───────────────────────────────────────────────┘

API Request/Response Flow

CLIENT                    SERVER                MEILISEARCH
  │                         │                         │
  │  POST /api/search       │                         │
  ├────────────────────────▶│                         │
  │  {                      │                         │
  │    q: "bilge pump",     │                         │
  │    currentDocumentId:   │                         │
  │      "doc-456",         │                         │
  │    limit: 50            │                         │
  │  }                      │                         │
  │                         │  POST /indexes/search   │
  │                         ├────────────────────────▶│
  │                         │  {                      │
  │                         │    q: "bilge pump",     │
  │                         │    filter: "userId=...  │
  │                         │      OR orgId IN [...]",│
  │                         │    limit: 50,           │
  │                         │    attributesToHighlight│
  │                         │  }                      │
  │                         │                         │
  │                         │  ◀ Search Results       │
  │                         │◀────────────────────────┤
  │                         │  {                      │
  │                         │    hits: [...],         │
  │                         │    processingTimeMs: 8  │
  │                         │  }                      │
  │                         │                         │
  │                         │  Add grouping metadata  │
  │                         │  ┌──────────────────┐   │
  │                         │  │ Group by docId   │   │
  │                         │  │ Mark current doc │   │
  │                         │  │ Calculate counts │   │
  │                         │  └──────────────────┘   │
  │                         │                         │
  │  ◀ Enhanced Response    │                         │
  │◀────────────────────────┤                         │
  │  {                      │                         │
  │    hits: [              │                         │
  │      { ...,             │                         │
  │        _isCurrentDoc:   │                         │
  │          true }         │                         │
  │    ],                   │                         │
  │    grouping: {          │                         │
  │      currentDocument: { │                         │
  │        docId: "...",    │                         │
  │        hitCount: 8      │                         │
  │      },                 │                         │
  │      otherDocuments: {  │                         │
  │        hitCount: 12,    │                         │
  │        documentCount: 3 │                         │
  │      }                  │                         │
  │    }                    │                         │
  │  }                      │                         │
  │                         │                         │
  │  Render grouped results │                         │
  │  in SearchDropdown      │                         │
  │                         │                         │

Event Flow: Search Interaction

TIME: t0 ───────────────────────────────────────────────────────▶

USER ACTION: Types "bilge"
    │
    ├─ @input event
    │     │
    │     └─▶ searchQuery.value = "bilge"
    │         (Vue reactivity)
    │
TIME: t+300ms (debounce)
    │
    ├─ searchWithScope("bilge")
    │     │
    │     ├─▶ isDropdownOpen.value = true
    │     │
    │     └─▶ search("bilge", { limit: 50 })
    │           │
    │           └─▶ POST /api/search
    │                 │
TIME: t+308ms (8ms server)
    │                 │
    │                 └─▶ results.value = [...]
    │                       │
    │                       └─▶ groupedResults (computed)
    │                             │
    │                             └─▶ Dropdown re-renders
    │
TIME: t+350ms (42ms render)
    │
    └─ User sees results
         │
         │ USER ACTION: Clicks result
         │     │
         │     ├─▶ @result-click emitted
         │     │     │
         │     │     └─▶ router.push({
         │     │           name: 'document',
         │     │           params: { id: 'doc-456' },
         │     │           query: { page: 12, q: 'bilge' }
         │     │         })
         │     │
         │     └─▶ isDropdownOpen.value = false
         │
TIME: t+400ms
         │
         └─ DocumentView loads page 12 with highlights

File Structure Tree

/home/setup/navidocs/
├── client/
│   └── src/
│       ├── components/
│       │   ├── search/              ◀── NEW DIRECTORY
│       │   │   ├── SearchResults.vue
│       │   │   ├── SearchResultCard.vue
│       │   │   ├── SearchDropdown.vue
│       │   │   └── SearchInput.vue
│       │   │
│       │   ├── navigation/          ◀── NEW DIRECTORY
│       │   │   ├── CompactNavControls.vue
│       │   │   └── NavTooltip.vue
│       │   │
│       │   └── [existing]/
│       │       ├── ConfirmDialog.vue
│       │       ├── FigureZoom.vue
│       │       ├── TocSidebar.vue
│       │       └── ...
│       │
│       ├── composables/
│       │   ├── useSearch.js         ◀── MODIFIED
│       │   ├── useDocumentSearch.js ◀── NEW
│       │   ├── useSearchResults.js  ◀── NEW
│       │   ├── useAuth.js
│       │   ├── useToast.js
│       │   └── useDocumentImages.js
│       │
│       ├── views/
│       │   ├── DocumentView.vue     ◀── MODIFIED
│       │   ├── SearchView.vue       ◀── MODIFIED
│       │   ├── HomeView.vue         ◀── MODIFIED
│       │   └── ...
│       │
│       └── assets/
│           └── icons/               ◀── NEW (optional)
│               └── nav-icons.svg
│
└── server/
    ├── routes/
    │   └── search.js                ◀── MODIFIED
    │
    ├── services/
    │   └── search.js                (no changes)
    │
    └── docs/
        ├── ARCHITECTURE_VIEWER_IMPROVEMENTS.md
        └── ARCHITECTURE_COMPONENT_DIAGRAM.md  ◀── THIS FILE

Deployment Checklist

┌────────────────────────────────────────────────────────────┐
│                    IMPLEMENTATION PHASES                   │
└────────────────────────────────────────────────────────────┘

PHASE 1: Foundation Components (Week 1)
├─ [ ] Create /components/search/ directory
├─ [ ] Build SearchResults.vue (with tests)
├─ [ ] Build SearchResultCard.vue (with tests)
├─ [ ] Build SearchInput.vue (with tests)
├─ [ ] Build SearchDropdown.vue (with tests)
├─ [ ] Create useDocumentSearch.js composable
├─ [ ] Create useSearchResults.js composable
└─ [ ] Write unit tests (Vitest)

PHASE 2: Document Viewer Integration (Week 2)
├─ [ ] Create CompactNavControls.vue
├─ [ ] Create NavTooltip.vue
├─ [ ] Modify DocumentView.vue
│     ├─ [ ] Add sticky header
│     ├─ [ ] Integrate search dropdown
│     └─ [ ] Add keyboard shortcuts
├─ [ ] Update /routes/search.js API
│     ├─ [ ] Add currentDocumentId support
│     └─ [ ] Add grouping metadata
├─ [ ] Accessibility audit
└─ [ ] Cross-browser testing

PHASE 3: Search View Refactor (Week 3)
├─ [ ] Refactor SearchView.vue
│     ├─ [ ] Use SearchResults component
│     └─ [ ] Remove duplicate code
├─ [ ] Ensure feature parity
│     ├─ [ ] Expand/collapse
│     ├─ [ ] Image previews
│     └─ [ ] Context loading
└─ [ ] Performance benchmarks

PHASE 4: Home View & Polish (Week 4)
├─ [ ] Update HomeView.vue
│     └─ [ ] Use SearchInput component
├─ [ ] Add search suggestions
├─ [ ] Polish animations
├─ [ ] Final QA
├─ [ ] Update documentation
└─ [ ] Deploy to production

Performance Optimization Map

┌────────────────────────────────────────────────────────────┐
│              PERFORMANCE CRITICAL PATHS                    │
└────────────────────────────────────────────────────────────┘

1. Search Input Handling
   ┌─────────────────────────────────────┐
   │ User types → Debounce (300ms)       │
   │           → Single API call         │
   │           → Cache results (5 min)   │
   └─────────────────────────────────────┘
   Target: < 400ms total (input to render)

2. Result Rendering
   ┌─────────────────────────────────────┐
   │ Limit initial: 50 results           │
   │ Virtual scroll: if > 100 results    │
   │ Lazy load images: IntersectionObserver│
   └─────────────────────────────────────┘
   Target: < 50ms first paint

3. Navigation Click
   ┌─────────────────────────────────────┐
   │ Click result → router.push()        │
   │             → Prefetch page data    │
   │             → Scroll to highlight   │
   └─────────────────────────────────────┘
   Target: < 200ms to visible page

4. Dropdown Open/Close
   ┌─────────────────────────────────────┐
   │ CSS transitions only (no JS anim)   │
   │ GPU-accelerated transforms          │
   │ will-change: transform, opacity     │
   └─────────────────────────────────────┘
   Target: 60fps (16ms per frame)

Security & Auth Flow

┌────────────────────────────────────────────────────────────┐
│                 AUTHENTICATION FLOW                        │
└────────────────────────────────────────────────────────────┘

CLIENT                    SERVER                MEILISEARCH
  │                         │                         │
  │  1. Get tenant token    │                         │
  ├────────────────────────▶│                         │
  │  POST /api/search/token │                         │
  │  Authorization: Bearer  │                         │
  │    <JWT>                │                         │
  │                         │                         │
  │                         │  Verify JWT             │
  │                         │  Get user orgs          │
  │                         │  ┌──────────────┐       │
  │                         │  │ SQLite query │       │
  │                         │  │ user_orgs    │       │
  │                         │  └──────────────┘       │
  │                         │                         │
  │                         │  Generate tenant token  │
  │                         ├────────────────────────▶│
  │                         │  {                      │
  │                         │    searchRules: {       │
  │                         │      filter: "orgId..." │
  │                         │    },                   │
  │                         │    expiresAt: ...       │
  │                         │  }                      │
  │                         │                         │
  │  ◀ Tenant token         │                         │
  │◀────────────────────────┤                         │
  │  {                      │                         │
  │    token: "...",        │                         │
  │    expiresAt: "...",    │                         │
  │    indexName: "..."     │                         │
  │  }                      │                         │
  │                         │                         │
  │  2. Search with token   │                         │
  │  POST /api/search       │                         │
  │  (token auto-included)  │                         │
  ├────────────────────────▶│                         │
  │                         │                         │
  │                         │  Verify org access      │
  │                         │  Query Meilisearch      │
  │                         ├────────────────────────▶│
  │                         │  Authorization: Bearer  │
  │                         │    <tenant-token>       │
  │                         │                         │
  │                         │  ◀ Filtered results     │
  │                         │◀────────────────────────┤
  │                         │  (only user's orgs)     │
  │  ◀ Search results       │                         │
  │◀────────────────────────┤                         │
  │                         │                         │

Row-level security: Each hit filtered by userId/orgId
No data leakage: Users only see their own documents

Legend:

  • ◀── Data/event flow
  • ┌──┐ Component/module boundary
  • [ ] Task checkbox
  • ◀── NEW New file to create
  • ◀── MODIFIED Existing file to modify