Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 |
|
|
|
|
|
|
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.vue — role="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.vue — role="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.vue — role="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.vue — SearchBar (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