Files

11 KiB

phase, plan, subsystem, status, completed_at, duration_minutes, tags, dependency_graph, tech_stack, key_files, decisions, metrics
phase plan subsystem status completed_at duration_minutes tags dependency_graph tech_stack key_files decisions metrics
04-folders-sharing-quotas-document-ux 09 frontend-ui complete 2026-05-25 35
vue
components
folders
sharing
search
sort
audit-log
pdf-preview
tailwind
requires provides affects
04-08
FolderView.vue — folder contents view with breadcrumb + sub-folder rows + document list
SharedView.vue — shared-with-me virtual folder
FolderRow.vue — folder row with inline rename and delete confirmation modal trigger
FolderBreadcrumb.vue — truncated breadcrumb nav (depth > 4 → ellipsis)
FolderDeleteModal.vue — destructive confirmation modal with document count
ShareModal.vue — share by handle + revoke access list
DocumentPreviewModal.vue — in-app PDF preview via proxy iframe
SearchBar.vue — debounced search input; Escape clears
SortControls.vue — Name/Date/Size toggle buttons with aria-pressed + direction indicator
AuditLogTab.vue — paginated audit log table with filters + CSV export
AppSidebar.vue extended with Shared-with-me entry and Folders section
DocumentCard.vue extended with share button (hover) and shared indicator pill
frontend/src/views/HomeView.vue
frontend/src/views/AdminView.vue
frontend/src/views/SettingsView.vue
frontend/src/views/DocumentView.vue
frontend/src/components/layout/AppSidebar.vue
frontend/src/components/documents/DocumentCard.vue
frontend/src/api/client.js
added patterns
Composition API with <script setup> — all components follow existing project style
iframe with proxy URL for PDF preview (never presigned URL) — T-04-09-01 mitigation
Optimistic UI for share revoke — re-adds row on API failure
Debounce pattern for search lives in documents store (300ms, min 2 chars)
window.location.href for CSV export — authenticated via existing httpOnly cookie
Document listener pattern for outside-click close in FolderRow dropdown
created modified
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/views/FolderView.vue
frontend/src/views/SharedView.vue
frontend/src/components/layout/AppSidebar.vue
frontend/src/components/documents/DocumentCard.vue
frontend/src/views/HomeView.vue
frontend/src/views/DocumentView.vue
frontend/src/views/SettingsView.vue
frontend/src/views/AdminView.vue
frontend/src/api/client.js
DocumentPreviewModal uses /api/documents/{id}/content proxy URL — never presigned; satisfies T-04-09-01
ShareModal exact-handle input only (no autocomplete) — prevents user enumeration (T-04-09-02)
AuditLogTab only visible inside AdminView which is already admin-role-guarded (T-04-09-03)
CSV export via window.location.href with filter params only — no auth token in URL (T-04-09-04 accepted)
Optimistic share revoke (immediate UI removal, re-add on error) — low latency UX with safe rollback
FolderRow outside-click closes menu via document event listener removed on unmount
SettingsView auto-saves pdf_open_mode on radio change via watch() — no manual save button needed
duration_minutes tasks_completed files_created files_modified
35 2 10 7

Phase 4 Plan 09: Vue UI — Folders, Sharing, Search, Sort, Audit Log, PDF Preview Summary

All Phase 4 frontend components created and wired into existing views. 10 new files + 7 modified files delivering the complete document management UX: folder navigation with breadcrumb, share-by-handle modal, in-app PDF preview iframe, debounced search, sort controls, and an admin audit log tab with CSV export.

Tasks Completed

Task Name Commit Key Files
1 Create new components (8 files + api client update) 3672157 FolderRow, FolderBreadcrumb, FolderDeleteModal, ShareModal, DocumentPreviewModal, SearchBar, SortControls, AuditLogTab
2 Modify existing views and components a3f5fc2 AppSidebar, DocumentCard, HomeView, FolderView (new), SharedView (new), DocumentView, SettingsView, AdminView

What Was Built

New Components

FolderRow.vue — Flex row with folder icon, name, doc count, three-dot dropdown menu (Rename/Delete). Inline rename replaces name text with focused input (Enter saves, Escape cancels, empty name rejected). Outside click closes dropdown via document event listener removed on unmount.

FolderBreadcrumb.vue<nav aria-label="Folder navigation"> with <ol> list structure. Computed visibleSegments applies truncation: when depth > 4, shows first segment + ellipsis + last 2 segments. All segments except last clickable (emit navigate); last segment is current folder (non-clickable).

FolderDeleteModal.vuerole="dialog" aria-modal="true". Warning icon, "Delete folder?" heading, body with interpolated doc_count (singular/plural). Two buttons: "Keep folder" (neutral) and "Delete folder and documents" (red, min-h-[44px]).

ShareModal.vuerole="dialog" aria-modal="true". Loads current shares on mount via useDocumentsStore().listShares(). Handle input + "Share document" button. 404 → "User not found. Check the handle and try again." 409 → "This document is already shared with that user." Recipients list with "Remove access" button; optimistic removal re-adds row on error. Close button aria-label="Close".

DocumentPreviewModal.vuerole="dialog" aria-modal="true" aria-label="Document preview". proxyUrl computed as /api/documents/${doc.id}/content (never presigned). Full-viewport overlay bg-black/60, header bar with filename + close, flex-1 iframe. Escape key via document listener removed on unmount. Overlay click checks event.target === overlayRef.value.

SearchBar.vue<div role="search">, <input type="search" aria-label="Search documents">, @keydown.escape clears modelValue. Width w-56.

SortControls.vue — Three buttons (Name/Date/Size). Active: text-indigo-600 font-semibold bg-indigo-50. aria-pressed="true" on active. aria-label includes direction. Clicking active toggles order; clicking different sort switches with desc default.

AuditLogTab.vue — Filter bar (date range, user input, event_type select, Apply/Export). Table with <th scope="col"> headers, action-type pill badges (auth=blue, document=gray, folder/share=purple, admin=amber), font-mono text-xs timestamps. Previous/Next pagination. window.location.href for CSV export (query params only, no auth tokens).

Modified Files

AppSidebar.vue — "Shared with me" router-link (purple icon bg-purple-50 text-purple-500, count badge when sharedCount > 0). Folders section below with section label, "New folder" inline input (toggle), top-level folder list from useFoldersStore. Count loaded on mount via getSharedWithMe().

DocumentCard.vue — Added group class to outer div. Share button opacity-0 group-hover:opacity-100 transition-opacity min-h-[44px] min-w-[44px] with aria-label="Share document". ShareModal v-if triggered on button click (stops propagation). Shared indicator pill bg-indigo-50 text-indigo-600 text-xs font-medium px-2 py-1 rounded-full when doc.share_count > 0.

HomeView.vueSearchBar (v-model → docsStore.searchQuery) + SortControls above document list. handleSortChange updates store fields and re-fetches. foldersStore.fetchFolders(null) added to onMounted.

FolderView.vue (new) — Full folder contents view: FolderBreadcrumb + FolderRow list + inline new-subfolder input + document list with SearchBar/SortControls. Watches route.params.folderId to re-load on navigation. FolderDeleteModal shown when folderToDelete is set.

SharedView.vue (new) — Fetches /api/shares/received on mount. Document-card layout with owner_handle displayed. Empty state: "No documents shared with you yet. When someone shares a document with you, it will appear here."

DocumentView.vue — Added isPdf computed (checks mime_type or filename extension). openPdf() shows DocumentPreviewModal when pdfOpenMode === 'in_app'; otherwise window.open(getDocumentContentUrl(doc.id), '_blank'). Loads user preferences on mount via api.getMyPreferences().

SettingsView.vue — "Document Preferences" section with two radio buttons (in_app / new_tab). watch(pdfOpenMode) auto-saves via api.updateMyPreferences() on every change. Save feedback shown for 3 seconds.

AdminView.vue — Added { id: 'audit', label: 'Audit Log' } to tabs array. <AuditLogTab v-if="activeTab === 'audit'" /> added to tab content.

api/client.js — Added adminListAuditLog({ start, end, user_id, event_type, page, per_page }) — GET /api/admin/audit-log with URLSearchParams.

Deviations from Plan

None — plan executed exactly as written. All UI-SPEC classes, copywriting, and accessibility contracts applied as specified.

Known Stubs

None — all components are fully wired to real stores and API functions. FolderRow.doc_count may be 0 if the backend does not return this field; this is a backend data shape issue, not a frontend stub.

Threat Surface Scan

No new security-relevant surface introduced beyond what the threat model already covers:

  • T-04-09-01: DocumentPreviewModal verified to use proxy URL only (no presigned)
  • T-04-09-02: ShareModal uses exact-handle input only, no autocomplete API
  • T-04-09-03: AuditLogTab only reachable inside AdminView (admin-guarded route)
  • T-04-09-04: CSV export URL contains only filter params (dates, event types, user IDs) — accepted

Self-Check: PASSED

Files exist:

  • frontend/src/components/folders/FolderRow.vue — FOUND
  • frontend/src/components/folders/FolderBreadcrumb.vue — FOUND
  • frontend/src/components/folders/FolderDeleteModal.vue — FOUND
  • frontend/src/components/sharing/ShareModal.vue — FOUND
  • frontend/src/components/documents/DocumentPreviewModal.vue — FOUND
  • frontend/src/components/documents/SearchBar.vue — FOUND
  • frontend/src/components/documents/SortControls.vue — FOUND
  • frontend/src/components/admin/AuditLogTab.vue — FOUND
  • frontend/src/views/FolderView.vue — FOUND
  • frontend/src/views/SharedView.vue — FOUND

Commits exist:

  • 3672157 — feat(phase-4-09): create new components — FOUND
  • a3f5fc2 — feat(phase-4-09): wire components into views — FOUND