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>
350 lines
23 KiB
Markdown
350 lines
23 KiB
Markdown
---
|
|
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>
|