Files
kite/.planning/phases/04-folders-sharing-quotas-document-ux/04-09-SUMMARY.md
T

166 lines
11 KiB
Markdown

---
phase: 04-folders-sharing-quotas-document-ux
plan: "09"
subsystem: frontend-ui
status: complete
completed_at: "2026-05-25"
duration_minutes: 35
tags: [vue, components, folders, sharing, search, sort, audit-log, pdf-preview, tailwind]
dependency_graph:
requires:
- "04-08" # API client + stores data layer
provides:
- "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"
affects:
- "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"
tech_stack:
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"
key_files:
created:
- "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"
modified:
- "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"
decisions:
- "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"
metrics:
duration_minutes: 35
tasks_completed: 2
files_created: 10
files_modified: 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.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
### Commits exist:
- 3672157 — feat(phase-4-09): create new components — FOUND
- a3f5fc2 — feat(phase-4-09): wire components into views — FOUND