747303246a
Folders, Sharing, Quotas & Document UX — plans verified (0 blockers, 2 non-blocking warnings). Covers FOLD-01..05, SHARE-01..05, SEC-08/09, ADMIN-06, DOC-01/02. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 KiB
23 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-folders-sharing-quotas-document-ux | 09 | execute | 7 |
|
|
false |
|
|
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.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_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 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"
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.
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.
- 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)
All new components created following UI-SPEC; accessibility contracts met; CSV export uses window.location.href.
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.
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
Type "approved" after verifying all 11 checkpoints pass. Describe any issues for the executor to fix before approving.
<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> |
<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>