navidocs/client/tests/LibraryView-Issues.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

21 KiB

LibraryView Component - Issues & Recommendations

Component: /home/setup/navidocs/client/src/views/LibraryView.vue Analysis Date: 2025-10-23 Version: 1.0


Executive Summary

The LibraryView component is well-structured and follows Vue 3 best practices, but several issues were identified during test documentation creation. This document categorizes issues by severity and provides actionable recommendations.


Critical Issues (Must Fix)

1. No API Integration

Current State:

  • All data is hardcoded in the component template
  • No API calls on component mount
  • No dynamic data loading

Impact:

  • Component shows static data only
  • Cannot scale to production use
  • No real-time updates

Recommendation:

<script setup>
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()
const loading = ref(true)
const error = ref(null)
const essentialDocuments = ref([])
const categories = ref([])
const recentActivity = ref([])

const roles = [
  // ... existing roles
]

const currentRole = ref('owner')

async function fetchLibraryData() {
  try {
    loading.value = true
    const response = await fetch('/api/vessels/lilian-i/documents/essential')
    const data = await response.json()
    essentialDocuments.value = data.essentialDocuments
    // ... populate other data
  } catch (err) {
    error.value = err.message
    console.error('Failed to fetch library data:', err)
  } finally {
    loading.value = false
  }
}

onMounted(() => {
  fetchLibraryData()
})
</script>

2. Missing Accessibility Attributes

Current State:

  • Category cards use @click but are <div> elements
  • No ARIA labels on icon-only buttons
  • No keyboard navigation support for category cards
  • No screen reader announcements for role changes

Impact:

  • Fails WCAG 2.1 Level AA compliance
  • Unusable for keyboard-only users
  • Poor screen reader experience

Recommendation:

A. Convert category cards to semantic buttons:

<button
  class="category-card group"
  @click="navigateToCategory('legal')"
  :aria-label="`Navigate to Legal & Compliance category, 8 documents`"
>
  <!-- card content -->
</button>

B. Add ARIA to role switcher:

<div
  class="glass rounded-xl p-1 flex items-center gap-1"
  role="group"
  aria-label="Select user role"
>
  <button
    v-for="role in roles"
    :key="role.id"
    @click="currentRole = role.id"
    :aria-pressed="currentRole === role.id"
    :aria-label="`Switch to ${role.label} role`"
    :class="[
      'px-4 py-2 rounded-lg text-sm font-medium transition-all duration-200',
      currentRole === role.id
        ? 'bg-gradient-to-r from-primary-500 to-secondary-500 text-white shadow-md'
        : 'text-white/70 hover:text-white hover:bg-white/10'
    ]"
  >
    <svg class="w-4 h-4 inline-block mr-1.5" aria-hidden="true" fill="none" stroke="currentColor" viewBox="0 0 24 24">
      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" :d="role.icon" />
    </svg>
    {{ role.label }}
  </button>
</div>

C. Add live region for role changes:

<div
  class="sr-only"
  role="status"
  aria-live="polite"
>
  {{ currentRole === 'owner' ? 'Owner view selected' : '' }}
  {{ currentRole === 'captain' ? 'Captain view selected' : '' }}
  {{ currentRole === 'manager' ? 'Manager view selected' : '' }}
  {{ currentRole === 'crew' ? 'Crew view selected' : '' }}
</div>

D. Add keyboard support:

<div
  class="category-card group"
  role="button"
  tabindex="0"
  @click="navigateToCategory('legal')"
  @keydown.enter="navigateToCategory('legal')"
  @keydown.space.prevent="navigateToCategory('legal')"
>
  <!-- card content -->
</div>

3. Incomplete Router Integration

Current State:

  • navigateToCategory() only logs to console
  • No actual route navigation implemented
  • Category detail pages don't exist

Impact:

  • Users cannot drill into categories
  • Click events lead nowhere
  • Poor user experience

Recommendation:

A. Update router configuration:

// /home/setup/navidocs/client/src/router.js
{
  path: '/library/category/:categoryId',
  name: 'library-category',
  component: () => import('./views/LibraryCategoryView.vue')
}

B. Update navigation function:

<script setup>
function navigateToCategory(category) {
  router.push({
    name: 'library-category',
    params: { categoryId: category }
  })
}
</script>

C. Create LibraryCategoryView.vue: Create a new view at /home/setup/navidocs/client/src/views/LibraryCategoryView.vue to display filtered documents.


Major Issues (Should Fix)

4. No State Persistence

Current State:

  • Role selection resets on page refresh
  • No localStorage or session storage
  • User preferences lost

Impact:

  • Poor UX - users must reselect role every visit
  • No continuity between sessions

Recommendation:

A. Add localStorage persistence:

<script setup>
import { ref, watch, onMounted } from 'vue'

const currentRole = ref(localStorage.getItem('libraryRole') || 'owner')

watch(currentRole, (newRole) => {
  localStorage.setItem('libraryRole', newRole)
  console.log('Role saved to localStorage:', newRole)
})

onMounted(() => {
  const savedRole = localStorage.getItem('libraryRole')
  if (savedRole) {
    currentRole.value = savedRole
    console.log('Restored role from localStorage:', savedRole)
  }
})
</script>

B. Add Pinia store (better approach):

// /home/setup/navidocs/client/src/stores/library.js
import { defineStore } from 'pinia'

export const useLibraryStore = defineStore('library', {
  state: () => ({
    currentRole: localStorage.getItem('libraryRole') || 'owner',
    pinnedDocuments: JSON.parse(localStorage.getItem('pinnedDocuments') || '[]')
  }),
  actions: {
    setRole(role) {
      this.currentRole = role
      localStorage.setItem('libraryRole', role)
    },
    togglePin(docId) {
      const index = this.pinnedDocuments.indexOf(docId)
      if (index > -1) {
        this.pinnedDocuments.splice(index, 1)
      } else {
        this.pinnedDocuments.push(docId)
      }
      localStorage.setItem('pinnedDocuments', JSON.stringify(this.pinnedDocuments))
    }
  }
})

5. Pin Functionality Not Implemented

Current State:

  • "Pin Document" button does nothing
  • Bookmark icons not interactive
  • No visual feedback on click

Impact:

  • Misleading UI - buttons appear functional but aren't
  • Incomplete feature

Recommendation:

A. Add pin handler:

<script setup>
const pinnedDocs = ref(new Set(['doc_insurance_2025'])) // Default pins

function togglePin(docId) {
  if (pinnedDocs.value.has(docId)) {
    pinnedDocs.value.delete(docId)
    console.log('Unpinned:', docId)
    // TODO: Call API to unpin
  } else {
    pinnedDocs.value.add(docId)
    console.log('Pinned:', docId)
    // TODO: Call API to pin
  }
}

function isPinned(docId) {
  return pinnedDocs.value.has(docId)
}
</script>

B. Update template:

<button
  @click.stop="togglePin('doc_insurance_2025')"
  :class="[
    'p-1 rounded-lg transition-all',
    isPinned('doc_insurance_2025')
      ? 'text-pink-400'
      : 'text-white/40 hover:text-pink-300 hover:bg-white/10'
  ]"
  :aria-label="isPinned('doc_insurance_2025') ? 'Unpin document' : 'Pin document'"
>
  <svg class="w-5 h-5" :fill="isPinned('doc_insurance_2025') ? 'currentColor' : 'none'" viewBox="0 0 20 20">
    <path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-2.5L5 18V4z" />
  </svg>
</button>

6. No Loading States

Current State:

  • No loading indicators
  • No skeleton screens
  • Instant content or nothing

Impact:

  • Confusing during data fetch
  • Appears broken on slow connections

Recommendation:

A. Add loading state:

<template>
  <div class="min-h-screen">
    <!-- Loading skeleton -->
    <div v-if="loading" class="max-w-7xl mx-auto px-6 py-8">
      <div class="skeleton h-64 mb-8"></div>
      <div class="grid grid-cols-3 gap-4">
        <div class="skeleton h-48"></div>
        <div class="skeleton h-48"></div>
        <div class="skeleton h-48"></div>
      </div>
    </div>

    <!-- Actual content -->
    <div v-else>
      <!-- existing content -->
    </div>
  </div>
</template>

B. Use existing skeleton styles: The main.css already has .skeleton class with shimmer animation.


7. No Error Handling

Current State:

  • No error states
  • No error messages
  • No retry mechanism

Impact:

  • Silent failures confuse users
  • No way to recover from errors

Recommendation:

<template>
  <div v-if="error" class="max-w-7xl mx-auto px-6 py-8">
    <div class="glass rounded-2xl p-8 text-center">
      <svg class="w-16 h-16 text-red-400 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
      </svg>
      <h2 class="text-2xl font-bold text-white mb-2">Failed to Load Library</h2>
      <p class="text-white/70 mb-6">{{ error }}</p>
      <button @click="fetchLibraryData" class="btn btn-primary">
        Try Again
      </button>
    </div>
  </div>
</template>

<script setup>
const error = ref(null)

async function fetchLibraryData() {
  try {
    error.value = null
    loading.value = true
    // ... fetch logic
  } catch (err) {
    error.value = err.message || 'An unexpected error occurred'
    console.error('Library fetch error:', err)
  } finally {
    loading.value = false
  }
}
</script>

Minor Issues (Nice to Have)

8. Role Switcher Doesn't Filter Content

Current State:

  • Changing role updates state but shows same content
  • No role-based filtering

Impact:

  • Feature appears broken
  • No value to switching roles

Recommendation:

<script setup>
import { computed } from 'vue'

const visibleDocuments = computed(() => {
  if (currentRole.value === 'crew') {
    // Crew sees limited documents
    return essentialDocuments.value.filter(doc =>
      ['safety', 'operations'].includes(doc.category)
    )
  }
  if (currentRole.value === 'captain') {
    // Captain sees all essential docs
    return essentialDocuments.value
  }
  // Owner and Manager see everything
  return allDocuments.value
})
</script>

<template>
  <div v-for="doc in visibleDocuments" :key="doc.id">
    <!-- document card -->
  </div>
</template>

9. Static Document Counts

Current State:

  • "29 documents" is hardcoded
  • Category counts are static

Impact:

  • Inaccurate as data changes
  • Requires code updates for new docs

Recommendation:

<script setup>
const totalDocuments = computed(() => {
  return categories.value.reduce((sum, cat) => sum + cat.documentCount, 0)
})

const vesselName = ref('LILIAN I')
</script>

<template>
<p class="text-xs text-white/70">{{ vesselName }} - {{ totalDocuments }} documents</p>
</template>

10. Missing Document Click Handlers

Current State:

  • Essential document cards not clickable
  • No way to open/view documents

Impact:

  • Users can't access documents
  • Cards look interactive but aren't

Recommendation:

<div
  class="essential-doc-card group cursor-pointer"
  @click="openDocument('doc_insurance_2025')"
>
  <!-- card content -->
</div>

<script setup>
function openDocument(docId) {
  router.push({
    name: 'document',
    params: { id: docId }
  })
}
</script>

11. No Search Functionality

Current State:

  • No search bar in library view
  • Users must scroll to find documents

Impact:

  • Poor UX for large libraries
  • Difficult to find specific docs

Recommendation:

Add search bar to header:

<div class="flex items-center justify-between mb-8">
  <div>
    <h2 class="text-2xl font-bold text-white">Essential Documents</h2>
  </div>
  <div class="search-bar w-64">
    <input
      type="text"
      v-model="searchQuery"
      placeholder="Search documents..."
      class="search-input"
    />
  </div>
</div>

12. No Pagination or Virtual Scrolling

Current State:

  • Renders all items at once
  • No pagination

Impact:

  • Performance issues with large datasets (>100 documents)
  • Long scroll on large libraries

Recommendation:

For now, acceptable since only 29 documents. Consider adding pagination when document count exceeds 50.


Code Quality Issues

13. Missing Props Validation

Recommendation: If component accepts props in the future, add PropTypes:

<script setup>
defineProps({
  vesselId: {
    type: String,
    required: true
  },
  initialRole: {
    type: String,
    default: 'owner',
    validator: (value) => ['owner', 'captain', 'manager', 'crew'].includes(value)
  }
})
</script>

14. No TypeScript Support

Recommendation: Consider migrating to TypeScript for better type safety:

// types.ts
export interface Document {
  id: string
  title: string
  description: string
  type: string
  status: 'active' | 'expired' | 'pending'
  fileType: string
  expiryDate?: string
  isPinned: boolean
}

export interface Category {
  id: string
  name: string
  description: string
  documentCount: number
  color: string
  icon: string
}

15. Large Component File

Current State:

  • 533 lines in single file
  • Could be split into smaller components

Recommendation:

Extract subcomponents:

  • EssentialDocumentCard.vue
  • CategoryCard.vue
  • ActivityItem.vue
  • RoleSwitcher.vue

Example:

<!-- components/EssentialDocumentCard.vue -->
<template>
  <div class="essential-doc-card group" @click="handleClick">
    <div class="flex items-start gap-3">
      <div :class="['w-12 h-12 rounded-xl flex items-center justify-center', iconColorClass]">
        <slot name="icon"></slot>
      </div>
      <div class="flex-1">
        <h3 class="font-bold text-white mb-1">{{ title }}</h3>
        <p class="text-xs text-white/70 mb-2">{{ description }}</p>
      </div>
    </div>
    <slot name="badge"></slot>
  </div>
</template>

<script setup>
defineProps({
  title: String,
  description: String,
  iconColorClass: String
})

defineEmits(['click'])
</script>

Security Issues

16. No Input Sanitization

Current State:

  • If API returns HTML in titles/descriptions, could lead to XSS

Recommendation: Vue 3 automatically escapes text content, so this is mostly safe. However, if using v-html in future:

<!-- Bad -->
<div v-html="userContent"></div>

<!-- Good -->
<div>{{ sanitize(userContent) }}</div>

<script setup>
import DOMPurify from 'dompurify'

function sanitize(html) {
  return DOMPurify.sanitize(html)
}
</script>

17. No Authentication Check

Current State:

  • No auth guard on route
  • Anyone can access library view

Recommendation:

Add route meta:

// router.js
{
  path: '/library',
  name: 'library',
  component: () => import('./views/LibraryView.vue'),
  meta: { requiresAuth: true }
}

Component-level check:

<script setup>
import { onMounted } from 'vue'
import { useRouter } from 'vue-router'

const router = useRouter()

onMounted(() => {
  const token = localStorage.getItem('accessToken')
  if (!token) {
    router.push({ name: 'login', query: { redirect: '/library' } })
  }
})
</script>

Performance Issues

18. No Memoization

Current State:

  • Role filtering logic would run on every render

Recommendation:

<script setup>
import { computed } from 'vue'

// Instead of filtering in template
const filteredDocuments = computed(() => {
  return documents.value.filter(doc =>
    doc.roles.includes(currentRole.value)
  )
})
</script>

19. No Image Optimization

Current State:

  • Using inline SVGs (good)
  • No image lazy loading if photos are added

Recommendation: If adding raster images:

<img
  :src="doc.thumbnail"
  loading="lazy"
  :alt="doc.title"
  class="w-full h-48 object-cover"
/>

Testing Gaps

20. No Unit Tests

Recommendation:

Create unit tests with Vitest:

// LibraryView.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import LibraryView from '@/views/LibraryView.vue'

describe('LibraryView', () => {
  it('renders with default role', () => {
    const wrapper = mount(LibraryView)
    expect(wrapper.find('.text-xl').text()).toBe('Document Library')
  })

  it('changes role on button click', async () => {
    const wrapper = mount(LibraryView)
    await wrapper.findAll('button')[1].trigger('click') // Click Captain
    expect(wrapper.vm.currentRole).toBe('captain')
  })

  it('navigates on category click', async () => {
    const wrapper = mount(LibraryView)
    const spy = vi.spyOn(wrapper.vm.router, 'push')
    await wrapper.find('.category-card').trigger('click')
    expect(spy).toHaveBeenCalled()
  })
})

21. No E2E Tests

Recommendation:

Create Playwright tests:

// tests/e2e/library.spec.js
import { test, expect } from '@playwright/test'

test.describe('LibraryView', () => {
  test('loads and displays all sections', async ({ page }) => {
    await page.goto('http://localhost:5173/library')

    await expect(page.locator('h1')).toContainText('Document Library')
    await expect(page.locator('text=Essential Documents')).toBeVisible()
    await expect(page.locator('text=Browse by Category')).toBeVisible()
    await expect(page.locator('.essential-doc-card')).toHaveCount(3)
    await expect(page.locator('.category-card')).toHaveCount(8)
  })

  test('role switcher works', async ({ page }) => {
    await page.goto('http://localhost:5173/library')

    await page.click('text=Captain')
    await expect(page.locator('button:has-text("Captain")')).toHaveCSS(
      'background-image',
      /linear-gradient/
    )
  })

  test('category navigation works', async ({ page }) => {
    await page.goto('http://localhost:5173/library')

    await page.click('text=Legal & Compliance')
    // Verify navigation or console log
  })
})

Documentation Gaps

22. Missing Component Documentation

Recommendation:

Add JSDoc comments:

<script setup>
/**
 * LibraryView Component
 *
 * Displays the document library for a vessel, including:
 * - Essential documents (insurance, registration, manuals)
 * - Category browser for organized document access
 * - Role-based filtering (owner, captain, manager, crew)
 * - Recent activity timeline
 *
 * @component
 * @example
 * <LibraryView />
 */

/**
 * Available user roles for filtering documents
 * @type {Array<{id: string, label: string, icon: string}>}
 */
const roles = [
  // ...
]

/**
 * Currently selected role
 * @type {Ref<string>}
 */
const currentRole = ref('owner')

/**
 * Navigate to a specific document category
 * @param {string} category - The category ID to navigate to
 */
function navigateToCategory(category) {
  console.log('Navigate to category:', category)
}
</script>

Priority Matrix

Priority Issue # Issue Name Effort Impact
🔴 P0 1 No API Integration High Critical
🔴 P0 2 Missing Accessibility Medium Critical
🔴 P0 3 Incomplete Router Integration Medium Critical
🟡 P1 4 No State Persistence Low High
🟡 P1 5 Pin Functionality Not Implemented Medium High
🟡 P1 6 No Loading States Low High
🟡 P1 7 No Error Handling Low High
🟢 P2 8 Role Switcher Doesn't Filter Medium Medium
🟢 P2 10 Missing Document Click Handlers Low Medium
🟢 P2 20 No Unit Tests High Medium
🟢 P2 21 No E2E Tests Medium Medium
🔵 P3 9 Static Document Counts Low Low
🔵 P3 11 No Search Functionality High Low
🔵 P3 15 Large Component File Medium Low

Next Actions

Immediate (Week 1)

  1. Implement API integration (#1)
  2. Add accessibility attributes (#2)
  3. Complete router navigation (#3)
  4. Add loading and error states (#6, #7)

Short-term (Week 2-3)

  1. Implement pin functionality (#5)
  2. Add state persistence (#4)
  3. Add role-based filtering (#8)
  4. Make document cards clickable (#10)

Medium-term (Month 1)

  1. Write unit tests (#20)
  2. Write E2E tests (#21)
  3. Extract subcomponents (#15)
  4. Add search functionality (#11)

Long-term (Quarter 1)

  1. Add TypeScript support (#14)
  2. Performance optimizations (#18, #19)
  3. Complete documentation (#22)
  4. Security audit (#16, #17)

Conclusion

The LibraryView component has a solid foundation with excellent styling and user interface design. However, it currently functions as a static prototype and requires significant work to become production-ready.

Critical blockers for production:

  • API integration
  • Accessibility compliance
  • Router navigation
  • Error handling

Estimated effort to production-ready:

  • Development: 2-3 weeks
  • Testing: 1 week
  • Total: 3-4 weeks

Document Version: 1.0 Last Updated: 2025-10-23 Next Review: 2025-11-06