chore(phase-4): UAT complete — Phase 4 marked done, sidebar collapse, duplicate-folder fix
UAT: 14/15 passed. Bug fixed: folders/rootFolders array alias in fetchFolders caused duplicate folder row on creation (rootFolders = [...list] breaks the shared reference). Sidebar: Folders section now has a collapse/expand chevron, collapsed by default. State: Phase 4 complete, Phase 5 (Cloud Storage Backends) is next. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,8 +16,8 @@ Before any phase is marked complete, all three gates must pass:
|
||||
|
||||
- [x] **Phase 1: Infrastructure Foundation** — PostgreSQL + MinIO wired into Docker Compose; Alembic migrations running; existing app still works
|
||||
- [x] **Phase 2: Users & Authentication** — Full auth flow end-to-end (register, login, TOTP, backup codes, password reset, sign-out-all) with admin panel for user management
|
||||
- [ ] **Phase 3: Document Migration & Multi-User Isolation** — All documents in PostgreSQL + MinIO; per-user isolation enforced; existing UI still works
|
||||
- [ ] **Phase 4: Folders, Sharing, Quotas & Document UX** — Full document management UX (folders, sharing, quota bar, PDF preview, search, audit log)
|
||||
- [x] **Phase 3: Document Migration & Multi-User Isolation** — All documents in PostgreSQL + MinIO; per-user isolation enforced; existing UI still works
|
||||
- [x] **Phase 4: Folders, Sharing, Quotas & Document UX** — Full document management UX (folders, sharing, quota bar, PDF preview, search, audit log)
|
||||
- [ ] **Phase 5: Cloud Storage Backends** — Users can connect OneDrive, Google Drive, Nextcloud, or WebDAV as a personal storage backend
|
||||
|
||||
---
|
||||
@@ -205,6 +205,6 @@ Before any phase is marked complete, all three gates must pass:
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Infrastructure Foundation | 5/5 | Complete | 2026-05-22 |
|
||||
| 2. Users & Authentication | 5/5 | Complete | 2026-05-22 |
|
||||
| 3. Document Migration & Multi-User Isolation | 0/5 | Not started | - |
|
||||
| 4. Folders, Sharing, Quotas & Document UX | 0/9 | Not started | - |
|
||||
| 3. Document Migration & Multi-User Isolation | 5/5 | Complete | 2026-05-25 |
|
||||
| 4. Folders, Sharing, Quotas & Document UX | 9/9 | Complete | 2026-05-28 |
|
||||
| 5. Cloud Storage Backends | 0/? | Not started | - |
|
||||
|
||||
+11
-11
@@ -16,9 +16,9 @@ progress:
|
||||
# Project State
|
||||
|
||||
**Project:** DocuVault
|
||||
**Status:** Phase 3 Complete — Ready to begin Phase 4
|
||||
**Current Phase:** 4
|
||||
**Last Updated:** 2026-05-25
|
||||
**Status:** Phase 4 Complete — Ready to begin Phase 5
|
||||
**Current Phase:** 5
|
||||
**Last Updated:** 2026-05-28
|
||||
|
||||
## Phase Status
|
||||
|
||||
@@ -26,15 +26,15 @@ progress:
|
||||
|---|---|---|
|
||||
| 1 | Infrastructure Foundation | ✓ Complete |
|
||||
| 2 | Users & Authentication | ✓ Complete (5/5 plans) |
|
||||
| 3 | Document Migration & Multi-User Isolation | ✓ Complete (5/5 plans, 10/10 UAT, security gate passed) |
|
||||
| 4 | Folders, Sharing, Quotas & Document UX | In Progress (7/9 plans complete) |
|
||||
| 3 | Document Migration & Multi-User Isolation | ✓ Complete (5/5 plans, UAT passed, security gate passed) |
|
||||
| 4 | Folders, Sharing, Quotas & Document UX | ✓ Complete (9/9 plans, UAT 14/15 passed, 1 bug fixed) |
|
||||
| 5 | Cloud Storage Backends | Not Started |
|
||||
|
||||
## Current Position
|
||||
|
||||
**Phase:** 04-folders-sharing-quotas-document-ux — In progress
|
||||
**Plan:** 7/9 — Wave 0 scaffolds (04-01), migration 0004 + put_object_raw (04-02), Folders API + audit helper (04-03), Sharing API (04-04), Streaming proxy + preferences (04-05), Quota enforcement (04-06), Audit log backfill + SEC-08/SEC-09 (04-07)
|
||||
**Progress:** ██████░░░░ 60% (3/5 phases complete)
|
||||
**Phase:** 05-cloud-storage-backends — Not started
|
||||
**Plan:** 0/TBD
|
||||
**Progress:** ████████░░ 80% (4/5 phases complete)
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -160,7 +160,7 @@ _Updated at each phase transition._
|
||||
| Last session | 2026-05-25 — Plan 04-02 executed: migration 0004 (pdf_open_mode, GIN FTS index, audit-logs bucket) + MinIOBackend.put_object_raw(); 122 tests pass |
|
||||
| Last session | 2026-05-25 — Plan 04-03 executed: write_audit_log() helper (flush-not-commit, never-raises) + FOLD-01..05 folder API + document sort/FTS/move; 122 pass, 0 new failures |
|
||||
| Last session | 2026-05-25 — Plan 04-04 executed: Sharing API (SHARE-01..05) — grant/list/received/revoke with IDOR protection; 7 xfailed, zero new failures |
|
||||
| Last session | 2026-05-25 — Plan 04-07 executed: audit log backfill (D-13, 8 auth + 2 doc + 5 admin events), SEC-08 CloudConnectionOut, SEC-09 delete-user MinIO cleanup; 92 passed, 1 pre-existing failure |
|
||||
| Next action | Continue execution: run plan 04-08 (frontend integration) |
|
||||
| Last session | 2026-05-28 — Phase 4 UAT complete (14/15 passed, 1 bug found + fixed: duplicate folder on creation); sidebar collapsible folder tree added; Phase 4 marked complete |
|
||||
| Next action | Begin Phase 5: Cloud Storage Backends — run /gsd:discuss-phase 5 |
|
||||
| Pending decisions | None |
|
||||
| Resume file | `.planning/phases/04-folders-sharing-quotas-document-ux/04-07-SUMMARY.md` |
|
||||
| Resume file | `.planning/phases/04-folders-sharing-quotas-document-ux/04-UAT.md` |
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
---
|
||||
status: complete
|
||||
phase: 04-folders-sharing-quotas-document-ux
|
||||
source: 04-01-SUMMARY.md, 04-02-SUMMARY.md, 04-03-SUMMARY.md, 04-04-SUMMARY.md, 04-05-SUMMARY.md, 04-06-SUMMARY.md, 04-07-SUMMARY.md, 04-08-SUMMARY.md, 04-09-SUMMARY.md
|
||||
started: 2026-05-28T00:00:00Z
|
||||
updated: 2026-05-28T17:30:00Z
|
||||
completed: 2026-05-28T17:30:00Z
|
||||
---
|
||||
|
||||
## Current Test
|
||||
|
||||
[testing complete]
|
||||
|
||||
## Tests
|
||||
|
||||
### 1. Cold Start Smoke Test
|
||||
expected: Kill any running containers (docker compose down). Start fresh with docker compose up. PostgreSQL, MinIO, and FastAPI all start cleanly. Migration 0004 runs without errors (adds users.pdf_open_mode column, GIN FTS index, creates audit-logs MinIO bucket). Frontend dev server starts. Opening http://localhost:5173 shows the file manager (not a login issue or blank page) — or the login page if not authenticated.
|
||||
result: pass
|
||||
|
||||
### 2. File Manager as Home
|
||||
expected: Log in and navigate to http://localhost:5173/. The page renders the FileManagerView — a column-layout file browser showing "No folders yet" or a list of existing folders/documents. There is NO separate document card grid from the old HomeView. The URL stays at / (not redirected to /home or /folders).
|
||||
result: pass
|
||||
reported: "Saw file manager; login/logout works; can navigate through folders."
|
||||
|
||||
### 3. Create a Folder
|
||||
expected: From the file manager root (/), click "New folder". An inline input row appears in the file list. Type a name (e.g. "Work") and press Enter or click Save. The new folder row appears immediately in the list with an amber folder icon and the name you typed. No page reload.
|
||||
result: issue
|
||||
reported: "First it shows it twice and after reloading it shows it correctly once."
|
||||
severity: major
|
||||
fix: fetchFolders(null) assigned folders and rootFolders the same array reference; createFolder pushed to both, causing double-render. Fixed by spreading rootFolders = [...list] to break the alias.
|
||||
|
||||
### 4. Navigate into a Folder (breadcrumb)
|
||||
expected: Click the "Work" folder row. The URL changes to /folders/{id}. The breadcrumb at the top updates to show "Home > Work". The document area shows "This folder is empty" (or any docs moved there). Clicking "Home" in the breadcrumb returns to / and shows the root folder list again.
|
||||
result: pass
|
||||
|
||||
### 5. Upload a File into a Folder
|
||||
expected: Navigate into the "Work" folder. The DropZone is visible at the top of the content area. Drop or select a file. The upload progress bar appears and completes. After confirm, the file appears as a document row inside the "Work" folder — NOT in the root.
|
||||
result: pass
|
||||
|
||||
### 6. Move a Document to a Folder (hover action)
|
||||
expected: Hover over a document row. A folder-icon button appears. Click it. A dropdown lists available folders and "Root (no folder)". Click "Work". The document moves to "Work" folder.
|
||||
result: pass
|
||||
|
||||
### 7. Drag a Document onto a Folder
|
||||
expected: Drag a document row onto a folder row. The folder highlights amber. Drop it. The document disappears from the current list and appears inside the target folder.
|
||||
result: pass
|
||||
|
||||
### 8. Rename a Folder
|
||||
expected: Hover over a folder row and click the pencil icon. The name becomes an inline input. Edit and press Enter. The folder row updates immediately.
|
||||
result: pass
|
||||
|
||||
### 9. Delete a Folder
|
||||
expected: Hover over a folder row and click the trash icon. A confirmation modal appears. Click "Delete". The folder disappears from the list.
|
||||
result: pass
|
||||
|
||||
### 10. Share a Document
|
||||
expected: Hover over a document row and click the share icon. A ShareModal opens with a handle input. Type a handle and click Share. The user appears in the shared list.
|
||||
result: pass
|
||||
|
||||
### 11. Shared With Me View
|
||||
expected: Click "Shared with me" in the sidebar. The URL changes to /shared. Documents shared by others appear (or "Nothing shared with you yet").
|
||||
result: pass
|
||||
|
||||
### 12. Search Within a Folder
|
||||
expected: Navigate into a folder with 2+ documents. Type 2+ characters. The list filters. Clearing or pressing Escape restores the full list.
|
||||
result: pass
|
||||
|
||||
### 13. Sort Controls
|
||||
expected: In a folder with multiple documents, click "Size". Documents re-sort. Clicking again reverses direction. Clicking "Name" switches to alphabetical.
|
||||
result: pass
|
||||
|
||||
### 14. PDF In-App Preview
|
||||
expected: Open a PDF document. It renders inline via /api/documents/{id}/content proxy — not a presigned MinIO URL.
|
||||
result: pass
|
||||
|
||||
### 15. Audit Log in Admin Panel
|
||||
expected: Log in as admin. Navigate to Admin panel. Click "Audit Log" tab. A paginated table of events appears with filters and CSV export. No passwords, document content, or credentials visible.
|
||||
result: pass
|
||||
|
||||
## Summary
|
||||
|
||||
total: 15
|
||||
passed: 14
|
||||
issues: 1
|
||||
skipped: 0
|
||||
blocked: 0
|
||||
pending: 0
|
||||
|
||||
## Gaps
|
||||
|
||||
- truth: "Creating a folder shows it once immediately, correct on reload"
|
||||
status: fixed
|
||||
reason: "User reported: first it shows it twice and after reloading it shows it correctly once"
|
||||
severity: major
|
||||
test: 3
|
||||
fix: "folders and rootFolders shared array reference after fetchFolders(null); fixed by rootFolders.value = [...list]"
|
||||
@@ -43,11 +43,26 @@
|
||||
|
||||
<!-- Folders root + collapsible tree -->
|
||||
<div class="mt-3">
|
||||
<!-- "Folders" is the root entry — clicking navigates to the root folder view -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<!-- Expand/collapse chevron -->
|
||||
<button
|
||||
@click="foldersExpanded = !foldersExpanded"
|
||||
class="p-1 rounded hover:bg-gray-100 text-gray-400 hover:text-gray-600 transition-colors shrink-0"
|
||||
:title="foldersExpanded ? 'Collapse folders' : 'Expand folders'"
|
||||
>
|
||||
<svg
|
||||
class="w-3 h-3 transition-transform duration-150"
|
||||
:class="foldersExpanded ? 'rotate-90' : ''"
|
||||
fill="none" stroke="currentColor" viewBox="0 0 24 24"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- "Folders" navigates to root file manager -->
|
||||
<router-link
|
||||
to="/"
|
||||
class="nav-link flex-1"
|
||||
class="nav-link flex-1 min-w-0"
|
||||
:class="{ 'nav-link-active': $route.path === '/' || $route.path.startsWith('/folders/') }"
|
||||
>
|
||||
<svg class="w-4 h-4 mr-2 shrink-0 text-amber-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -56,6 +71,7 @@
|
||||
</svg>
|
||||
Folders
|
||||
</router-link>
|
||||
|
||||
<button
|
||||
@click="startNewFolder"
|
||||
class="text-xs text-indigo-600 hover:underline shrink-0 mr-1"
|
||||
@@ -65,30 +81,33 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Inline new root folder input -->
|
||||
<div v-if="showNewFolderInput" class="px-3 mb-2 mt-1">
|
||||
<input
|
||||
v-model="newFolderName"
|
||||
type="text"
|
||||
placeholder="Folder name"
|
||||
class="block w-full border border-gray-300 rounded-lg px-2 py-1 text-xs focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
@keydown.enter="submitNewFolder"
|
||||
@keydown.escape="cancelNewFolder"
|
||||
autofocus
|
||||
/>
|
||||
<p v-if="newFolderError" class="text-red-500 text-xs mt-1">{{ newFolderError }}</p>
|
||||
</div>
|
||||
<!-- Collapsible content -->
|
||||
<template v-if="foldersExpanded">
|
||||
<!-- Inline new root folder input -->
|
||||
<div v-if="showNewFolderInput" class="px-3 mb-2 mt-1">
|
||||
<input
|
||||
v-model="newFolderName"
|
||||
type="text"
|
||||
placeholder="Folder name"
|
||||
class="block w-full border border-gray-300 rounded-lg px-2 py-1 text-xs focus:outline-none focus:ring-2 focus:ring-indigo-500"
|
||||
@keydown.enter="submitNewFolder"
|
||||
@keydown.escape="cancelNewFolder"
|
||||
autofocus
|
||||
/>
|
||||
<p v-if="newFolderError" class="text-red-500 text-xs mt-1">{{ newFolderError }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Sub-folders tree (indented under Folders) -->
|
||||
<div v-if="loadingRoots" class="pl-7 py-1 text-xs text-gray-400">Loading…</div>
|
||||
<div v-else-if="foldersStore.rootFolders.length === 0 && !showNewFolderInput"
|
||||
class="pl-7 py-1 text-xs text-gray-400">No folders yet</div>
|
||||
<FolderTreeItem
|
||||
v-for="folder in foldersStore.rootFolders"
|
||||
:key="folder.id"
|
||||
:folder="folder"
|
||||
:depth="1"
|
||||
/>
|
||||
<!-- Sub-folders tree -->
|
||||
<div v-if="loadingRoots" class="pl-7 py-1 text-xs text-gray-400">Loading…</div>
|
||||
<div v-else-if="foldersStore.rootFolders.length === 0 && !showNewFolderInput"
|
||||
class="pl-7 py-1 text-xs text-gray-400">No folders yet</div>
|
||||
<FolderTreeItem
|
||||
v-for="folder in foldersStore.rootFolders"
|
||||
:key="folder.id"
|
||||
:folder="folder"
|
||||
:depth="1"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Topics list -->
|
||||
@@ -186,6 +205,7 @@ const showNewFolderInput = ref(false)
|
||||
const newFolderName = ref('')
|
||||
const newFolderError = ref('')
|
||||
const loadingRoots = ref(true)
|
||||
const foldersExpanded = ref(false)
|
||||
|
||||
watch(() => foldersStore.treeVersion, () => foldersStore.fetchRootFolders())
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export const useFoldersStore = defineStore('folders', () => {
|
||||
const data = await api.listFolders(parentId)
|
||||
const list = data.items ?? data
|
||||
folders.value = list
|
||||
if (parentId === null) rootFolders.value = list
|
||||
if (parentId === null) rootFolders.value = [...list]
|
||||
} catch (e) {
|
||||
error.value = e.message || 'Failed to load folders'
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user