---
phase: 04-folders-sharing-quotas-document-ux
plan: 09
type: execute
wave: 7
depends_on:
  - "04-08"
files_modified:
  - frontend/src/views/FolderView.vue
  - frontend/src/views/SharedView.vue
  - frontend/src/views/SettingsView.vue
  - frontend/src/views/DocumentView.vue
  - frontend/src/views/HomeView.vue
  - frontend/src/views/AdminView.vue
  - frontend/src/components/folders/FolderRow.vue
  - frontend/src/components/folders/FolderBreadcrumb.vue
  - frontend/src/components/folders/FolderDeleteModal.vue
  - frontend/src/components/sharing/ShareModal.vue
  - frontend/src/components/documents/DocumentPreviewModal.vue
  - frontend/src/components/documents/SearchBar.vue
  - frontend/src/components/documents/SortControls.vue
  - frontend/src/components/admin/AuditLogTab.vue
  - frontend/src/components/layout/AppSidebar.vue
  - frontend/src/components/documents/DocumentCard.vue
  - frontend/src/api/client.js
autonomous: false
requirements:
  - FOLD-01
  - FOLD-02
  - FOLD-03
  - FOLD-04
  - FOLD-05
  - SHARE-01
  - SHARE-02
  - SHARE-03
  - SHARE-04
  - SHARE-05
  - ADMIN-06
  - DOC-01
  - DOC-02

must_haves:
  truths:
    - "Folders section visible in AppSidebar above topics; top-level folders clickable to navigate"
    - "Shared with me entry in AppSidebar above folders; shows count badge when non-empty"
    - "Document list shows SearchBar and SortControls above; sort persists until changed"
    - "DocumentCard shows share button on hover; shared indicator pill when doc.share_count > 0"
    - "Share modal: handle input, Share button, current recipient list with Revoke button"
    - "FolderView renders sub-folders as FolderRow components + breadcrumb + document list"
    - "FolderDeleteModal shows document count and requires explicit confirmation before delete"
    - "PDF documents open in DocumentPreviewModal (in_app mode) or new tab (new_tab mode)"
    - "SettingsView Document Preferences card shows pdf_open_mode radio; auto-saves on change"
    - "Admin AuditLog tab is accessible from AdminView; filters and CSV export work"
  artifacts:
    - path: "frontend/src/views/FolderView.vue"
      provides: "Folder contents: sub-folder rows + breadcrumb + document list + new subfolder inline input"
    - path: "frontend/src/views/SharedView.vue"
      provides: "Shared with me virtual folder view"
    - path: "frontend/src/components/folders/FolderRow.vue"
      provides: "Folder row with three-dot menu (rename inline, delete folder) per UI-SPEC"
    - path: "frontend/src/components/folders/FolderBreadcrumb.vue"
      provides: "Breadcrumb nav with truncation at depth > 4; each segment clickable"
    - path: "frontend/src/components/folders/FolderDeleteModal.vue"
      provides: "Destructive confirmation modal with document count"
    - path: "frontend/src/components/sharing/ShareModal.vue"
      provides: "Share by handle + current recipients list + revoke"
    - path: "frontend/src/components/documents/DocumentPreviewModal.vue"
      provides: "In-app PDF preview via iframe"
    - path: "frontend/src/components/documents/SearchBar.vue"
      provides: "Debounced search input; clears on Escape"
    - path: "frontend/src/components/documents/SortControls.vue"
      provides: "Name / Date / Size sort toggle buttons"
    - path: "frontend/src/components/admin/AuditLogTab.vue"
      provides: "Paginated audit log table with date/user/action filters + CSV export button"
  key_links:
    - from: "frontend/src/views/FolderView.vue"
      to: "frontend/src/stores/folders.js"
      via: "useFoldersStore for folder data and navigation"
      pattern: "useFoldersStore"
    - from: "frontend/src/components/sharing/ShareModal.vue"
      to: "frontend/src/stores/documents.js"
      via: "shareDocument, revokeShare, listShares actions"
      pattern: "useDocumentsStore"
    - from: "frontend/src/components/layout/AppSidebar.vue"
      to: "frontend/src/stores/folders.js"
      via: "top-level folder list + currentFolderId for active state"
      pattern: "useFoldersStore"
---

<objective>
Build all Phase 4 Vue components and wire them into the existing views. This is the final
frontend plan — all stores, API client, and routes are established by plan 04-08.

Purpose: Deliver the complete document management UX visible to users and admins.
Output: 10 new components + 6 modified views following 04-UI-SPEC.md exactly.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/phases/04-folders-sharing-quotas-document-ux/04-UI-SPEC.md
@.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md
@.planning/phases/04-folders-sharing-quotas-document-ux/04-PATTERNS.md
@frontend/src/components/layout/AppSidebar.vue
@frontend/src/components/documents/DocumentCard.vue
@frontend/src/views/HomeView.vue
@frontend/src/views/AdminView.vue
@frontend/src/views/SettingsView.vue
@frontend/src/views/DocumentView.vue
@frontend/src/components/admin/AdminUsersTab.vue
</context>

<interfaces>
From 04-UI-SPEC.md — exact Tailwind classes for each new component are specified.
Executor must read 04-UI-SPEC.md fully before writing any component template.

Key design tokens from 04-UI-SPEC.md:
- Modal overlay: `fixed inset-0 bg-black/40 flex items-center justify-center z-50`
- Modal panel: `bg-white rounded-2xl shadow-xl p-6 max-w-md w-full mx-4`
- Primary button: `bg-indigo-600 hover:bg-indigo-700 text-white text-sm px-4 py-2 rounded-lg`
- Destructive button: `bg-red-600 hover:bg-red-700 text-white text-sm font-semibold px-4 py-2 rounded-lg min-h-[44px]`
- FolderRow: `flex items-center gap-3 px-4 py-3 bg-white border border-gray-200 rounded-xl hover:border-indigo-300 hover:shadow-sm transition-all cursor-pointer`
- Share badge (SHARE-05): `bg-indigo-50 text-indigo-600 text-xs font-medium px-2 py-1 rounded-full`
- Shared with me icon: `bg-purple-50 text-purple-500` (only purple UI element)
- Section label: `text-xs font-semibold text-gray-400 uppercase tracking-wider mb-1`
- Accessibility: all icon-only buttons have aria-label; modals have role="dialog" aria-modal="true"

Copywriting (from 04-UI-SPEC.md):
- Share modal title: "Share document"
- Handle input placeholder: "Enter username handle"
- Share submit: "Share document"
- Empty share state: "Not shared with anyone yet."
- User not found: "User not found. Check the handle and try again."
- Non-empty folder delete body: "This folder contains {N} document{s}. Deleting it will permanently delete all documents inside. This cannot be undone."
- Folder delete confirm: "Delete folder and documents"
- Folder delete dismiss: "Keep folder"
- Search placeholder: "Search documents..."
- PDF pref section heading: "Document Preferences"
- PDF open modes: "Open documents in-app" / "Open documents in new tab"
- Audit log tab label: "Audit Log"
- Sort labels: "Name" / "Date" / "Size"
</interfaces>

<tasks>

<task type="auto">
  <name>Task 1: Create new components (FolderRow, FolderBreadcrumb, FolderDeleteModal, ShareModal, DocumentPreviewModal, SearchBar, SortControls, AuditLogTab)</name>
  <files>
    frontend/src/components/folders/FolderRow.vue,
    frontend/src/components/folders/FolderBreadcrumb.vue,
    frontend/src/components/folders/FolderDeleteModal.vue,
    frontend/src/components/sharing/ShareModal.vue,
    frontend/src/components/documents/DocumentPreviewModal.vue,
    frontend/src/components/documents/SearchBar.vue,
    frontend/src/components/documents/SortControls.vue,
    frontend/src/components/admin/AuditLogTab.vue
  </files>
  <read_first>
    frontend/src/components/admin/AdminUsersTab.vue — read the ENTIRE file; extract the form pattern (reactive form state, loading/error refs, onMounted fetch, table with filters, action buttons, inline confirm pattern); use this as the template for ShareModal and AuditLogTab
    frontend/src/components/ui/ConfirmBlock.vue — read fully for the confirm/cancel button pattern
    frontend/src/components/documents/DocumentCard.vue — read for the card layout pattern and SVG icon style
    frontend/src/views/AdminView.vue — read to understand how tabs are currently implemented; extract the tab switch pattern
  </read_first>
  <action>
    Create each component file in the exact directory specified. All use Vue 3 Options API with Composition API (script setup) — follow the same pattern as existing components (read the existing files to confirm which style is used and replicate it exactly).

    FolderRow.vue (per UI-SPEC FolderRow section):
    - Props: folder {id, name, doc_count}, onNavigate (function), onRename (function), onDelete (function)
    - Template: flex container with folder icon (bg-gray-100), folder name + doc count, three-dot menu button
    - Three-dot menu: dropdown with "Rename" and "Delete folder" items; closes on outside click (use @click.outside or a boolean ref + document listener)
    - Rename mode: toggles to inline input pre-filled with current name; Enter=save, Escape=cancel, empty=rejected with inline error
    - No modal for rename — inline text input replaces the name display

    FolderBreadcrumb.vue (per UI-SPEC FolderBreadcrumb section):
    - Props: segments Array of {id, name}
    - Emits: navigate(folderId) — folderId is null for root
    - Computed: visibleSegments — if segments.length > 4, return [segments[0], {id: null, name: '...'}, ...segments.slice(-2)]; otherwise return segments as-is
    - Template: nav with aria-label="Folder navigation", ol list structure (accessibility), separator chevron SVG between segments
    - All segments except the last are clickable (emit navigate); last segment is non-clickable (current folder)
    - Wrap with nav aria-label="Folder navigation" and ol per UI-SPEC accessibility contract

    FolderDeleteModal.vue (per UI-SPEC FolderDeleteModal section):
    - Props: folder {id, name, doc_count}, onConfirm (function), onCancel (function)
    - Template: fixed overlay, warning icon (red exclamation), heading "Delete folder?", body text with doc_count interpolated, two buttons: "Keep folder" (neutral) and "Delete folder and documents" (red, min-h-[44px])
    - Emits: confirm, cancel
    - Accessibility: role="dialog" aria-modal="true" aria-labelledby="modal-title-id"

    ShareModal.vue (per UI-SPEC ShareModal section):
    - Props: doc {id, filename}
    - Emits: close
    - Script state: handle ref(''), submitting ref(false), error ref(null), shares ref([])
    - onMounted: load current shares via useDocumentsStore().listShares(props.doc.id); set shares.value
    - submitShare(): calls store.shareDocument(props.doc.id, handle.value); clears handle on success; updates shares list; shows error for 404/409
    - revokeShare(shareId): calls store.revokeShare(shareId); removes from shares array optimistically; on error re-adds
    - Template: overlay, panel, title "Share document", handle input + Share document button, separator, recipient list OR empty state, close X button
    - Error messages per UI-SPEC copywriting (User not found, already shared)
    - Accessibility: role="dialog" aria-modal="true"; close button aria-label="Close"

    DocumentPreviewModal.vue (per UI-SPEC DocumentPreviewModal section):
    - Props: doc {id, filename}
    - Emits: close
    - Script: proxyUrl computed = `/api/documents/${props.doc.id}/content`
    - Template: fixed overlay (bg-black/60 z-50 flex flex-col), header bar with doc name + close button, flex-1 iframe filling the remaining height
    - Close on overlay click (check event.target === overlay element) and Escape key (keydown listener on mounted/unmounted)
    - Accessibility: role="dialog" aria-modal="true" aria-label="Document preview"

    SearchBar.vue (per UI-SPEC SearchBar section):
    - Props: modelValue (String), placeholder defaults to "Search documents..."
    - Emits: update:modelValue
    - Template: input type="search", role="search" on wrapper div, aria-label="Search documents"
    - Clears on Escape key (@keydown.escape)
    - Width: w-56

    SortControls.vue (per UI-SPEC SortControls section):
    - Props: sort ('name'|'date'|'size'), order ('asc'|'desc')
    - Emits: change({sort, order})
    - Template: three text buttons; active sort has `text-indigo-600 font-semibold` class; direction indicator appended (arrow up/down unicode or ↑/↓ text)
    - Clicking the already-active sort toggles order; clicking a different sort switches to it with desc order
    - aria-pressed="true" on active button; aria-label includes direction (e.g., "Sort by name, ascending")

    AuditLogTab.vue (per UI-SPEC AuditLogTab section):
    - Script state: entries ref([]), total ref(0), page ref(1), loading ref(false), filters reactive({start:'', end:'', user_id:'', event_type:''})
    - onMounted: fetchLog()
    - fetchLog(): calls api.adminListAuditLog({...filters, page: page.value}) from api/client.js (add this function — GET /api/admin/audit-log with query params)
    - exportCsv(): sets window.location.href to `/api/admin/audit-log/export?format=csv&${new URLSearchParams(activeFilters)}` — triggers browser download
    - Template: filter bar (date inputs, user dropdown placeholder, event_type dropdown, Apply button, Export CSV button), paginated table (Timestamp | User | Action Type | IP Address), Previous/Next pagination, empty state
    - Action type pill badge colors per UI-SPEC: auth=bg-blue-50 text-blue-600, document=bg-gray-100 text-gray-600, folder/share=bg-purple-50 text-purple-600, admin=bg-amber-50 text-amber-700
    - Table headers with scope="col"; timestamp cell uses font-mono text-xs

    Also add adminListAuditLog function to frontend/src/api/client.js (append): GET /api/admin/audit-log with query params start, end, user_id, event_type, page, per_page.
  </action>
  <verify>
    Run: find /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/folders /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/sharing -name "*.vue" 2>/dev/null

    Expected: FolderRow.vue, FolderBreadcrumb.vue, FolderDeleteModal.vue appear; ShareModal.vue appears.

    Also: find /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/documents /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/admin -name "*.vue" | sort

    Expected: DocumentPreviewModal.vue, SearchBar.vue, SortControls.vue, AuditLogTab.vue appear.
  </verify>
  <acceptance_criteria>
    - All 8 component files exist in their respective directories (verify with find)
    - FolderBreadcrumb.vue has nav aria-label="Folder navigation" and ol list structure (grep: `aria-label.*Folder navigation` and `ol` in template)
    - ShareModal.vue has role="dialog" and aria-modal="true" (grep: `role="dialog"` and `aria-modal="true"`)
    - FolderDeleteModal.vue contains "Keep folder" and "Delete folder and documents" button text (grep)
    - DocumentPreviewModal.vue contains iframe with :src bound to proxyUrl (grep: `iframe` and `proxyUrl`)
    - SearchBar.vue has @keydown.escape handler (grep: `keydown.escape` or `keydown.esc`)
    - SortControls.vue has aria-pressed (grep: `aria-pressed`)
    - AuditLogTab.vue has window.location.href for CSV export (grep: `window.location.href`)
    - frontend/src/api/client.js contains adminListAuditLog function (grep)
  </acceptance_criteria>
  <done>All new components created following UI-SPEC; accessibility contracts met; CSV export uses window.location.href.</done>
</task>

<task type="checkpoint:human-verify" gate="blocking">
  <what-built>
    All frontend views and components for Phase 4:
    - AppSidebar.vue extended with "Shared with me" entry (purple icon, count badge) and Folders section with "New folder" CTA; folder links navigate to /folders/:id
    - DocumentCard.vue extended with share button (hover, opacity-0 group-hover:opacity-100) and shared indicator pill
    - HomeView.vue wired with SearchBar + SortControls above document list; folder-aware fetchDocuments
    - FolderView.vue: breadcrumb + sub-folder FolderRow list + document list + inline new-folder input
    - SharedView.vue: filtered document list from GET /api/shares/received; empty state
    - DocumentView.vue: PDF click triggers DocumentPreviewModal (in_app) or window.open (new_tab) based on pdf_open_mode preference
    - SettingsView.vue: Document Preferences card with radio buttons auto-saved via PATCH /api/auth/me/preferences
    - AdminView.vue: AuditLog tab added alongside existing tabs

    This checkpoint also triggers human verification of all visual and interactive Phase 4 features before the phase is marked complete.
  </what-built>
  <how-to-verify>
    Before this checkpoint is reached, the executor MUST complete the following modifications:

    AppSidebar.vue: read the entire file; add "Shared with me" entry (above folders section, inline inbox icon, purple bg-purple-50 text-purple-500 icon container, count badge when sharedCount > 0); add Folders section below (section label "FOLDERS", "New folder" button, top-level folder list from useFoldersStore); import useFoldersStore; add sharedCount computed using getSharedWithMe() or a dedicated ref.

    DocumentCard.vue: read the entire file; add `group` class to outer container; add share button (opacity-0 group-hover:opacity-100 transition-opacity, aria-label="Share document", min-h-[44px] min-w-[44px]); add ShareModal component import and v-if=showShareModal; add shared indicator pill below metadata line if doc.share_count > 0.

    HomeView.vue: read the entire file; add SearchBar component above document list with v-model=docsStore.searchQuery; add SortControls with sort=docsStore.sortField, order=docsStore.sortOrder, @change handler; add useFoldersStore; update onMounted to also call foldersStore.fetchFolders(null).

    FolderView.vue: create new file; uses useFoldersStore and useDocumentsStore; on route param folderId change, calls foldersStore.navigateTo(folderId) and docsStore.fetchDocuments({folderId}); renders FolderBreadcrumb + FolderRow list + document list + inline new-folder input.

    SharedView.vue: create new file; on mounted calls api.getSharedWithMe(); renders document list using same DocumentCard layout but with owner handle shown; empty state per UI-SPEC copywriting.

    DocumentView.vue: read the entire file; find where PDF documents would be clicked or previewed; add logic — if current_user.pdf_open_mode === 'in_app', show DocumentPreviewModal; if 'new_tab', call window.open(getDocumentContentUrl(doc.id), '_blank'); load preference via api.getMyPreferences() on mount.

    SettingsView.vue: read the entire file; add Document Preferences section after existing sections; radio group with v-model=pdfOpenMode; watch pdfOpenMode to auto-call api.updateMyPreferences.

    AdminView.vue: read the entire file; add "Audit Log" tab button alongside existing tabs; add AuditLogTab component conditional display.

    AFTER completing all view modifications, start docker compose up and navigate to the application:

    1. Navigate to the home page — verify Folders section and "Shared with me" appear in sidebar
    2. Create a folder via sidebar "New folder" — verify it appears in the folder list
    3. Navigate into the folder — verify FolderView loads with breadcrumb showing the folder name
    4. Upload a document (use existing upload flow) — verify it appears in the current folder
    5. Click the share button on a DocumentCard — verify ShareModal opens with handle input and empty state
    6. In ShareModal, type a non-existent handle — verify "User not found" error appears
    7. Delete a non-empty folder — verify FolderDeleteModal shows the document count and correct copy
    8. Navigate to Settings — verify Document Preferences card with radio buttons appears
    9. Navigate to Admin — verify Audit Log tab appears; click Apply filters — verify table loads
    10. Click Export CSV in audit log — verify file downloads
    11. Open a PDF document (set preference to in_app first) — verify DocumentPreviewModal with iframe appears
  </how-to-verify>
  <resume-signal>Type "approved" after verifying all 11 checkpoints pass. Describe any issues for the executor to fix before approving.</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Browser → /api/documents/{id}/content | All document content access goes through proxy; iframe src is the proxy URL, not a presigned URL |
| Share modal → shares API | Recipient handle is user-supplied text; backend validates existence; no autocomplete/search that could enumerate users |
| Admin audit log export | window.location.href triggers a download authenticated by the existing httpOnly cookie; no token in URL |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-04-09-01 | Information Disclosure | iframe src reveals presigned URL in browser | mitigate | DocumentPreviewModal iframe src = `/api/documents/${docId}/content` (the proxy URL, never presigned); Content-Disposition: inline drives rendering |
| T-04-09-02 | Information Disclosure | Share modal autocomplete reveals user handles | mitigate | D-04: exact handle input only; no autocomplete API; 404 response reveals only that the handle does not exist |
| T-04-09-03 | Broken Access Control | Audit log tab visible to regular users in AdminView | mitigate | AuditLogTab only visible inside AdminView which is already guarded by admin-only route; backend enforces get_current_admin |
| T-04-09-04 | Information Disclosure | window.location.href for CSV export embeds sensitive params in URL | accept | Params are only filter values (dates, event types, user IDs) — not auth tokens; the request is authenticated via httpOnly cookie already set |
| T-04-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan |
</threat_model>

<verification>
1. Component existence: find frontend/src/components/folders frontend/src/components/sharing -name "*.vue"
2. Security: grep -rn "presigned" frontend/src/components/documents/DocumentPreviewModal.vue — expect zero matches
3. Accessibility: grep -n "role=\"dialog\"\|aria-modal" frontend/src/components/sharing/ShareModal.vue frontend/src/components/folders/FolderDeleteModal.vue
4. Share IDOR prevention: grep -n "aria-label" frontend/src/components/documents/DocumentCard.vue — share button must have aria-label
5. Human checkpoint: all 11 scenarios verified by the developer in a running instance
</verification>

<success_criteria>
- All 10 new component files exist
- AppSidebar has "Shared with me" (purple icon) and Folders section
- DocumentCard has share button (hover-reveal) and shared indicator pill
- DocumentPreviewModal uses proxy URL (/api/documents/{id}/content), not presigned URL
- AuditLogTab is accessible only inside AdminView (protected route)
- Developer approves all 11 manual verification checkpoints
</success_criteria>

<output>
Create `.planning/phases/04-folders-sharing-quotas-document-ux/04-09-SUMMARY.md` when done.
</output>
