Compare commits
146
Commits
v0.2
...
3514fdcf3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3514fdcf3a | ||
|
|
fac7721dd9 | ||
|
|
d3700e20c4 | ||
|
|
2f00e122d3 | ||
|
|
ed046752e4 | ||
|
|
fd48c5b928 | ||
|
|
0c4f9ccba3 | ||
|
|
40b1572582 | ||
|
|
f92270fda4 | ||
|
|
b1a9f436c4 | ||
|
|
60df8552b5 | ||
|
|
af0de30e93 | ||
|
|
405c7a6610 | ||
|
|
a1d1c3ba2e | ||
|
|
d4b2697109 | ||
|
|
ebc74f4abe | ||
|
|
fcb38c50b1 | ||
|
|
cd5bd82260 | ||
|
|
f09387a9d4 | ||
|
|
25cc2537bb | ||
|
|
e68faf3051 | ||
|
|
e809df9f51 | ||
|
|
a7e55d1bb0 | ||
|
|
60afb028bf | ||
|
|
a77b7324aa | ||
|
|
508d27643b | ||
|
|
af7d45f3c4 | ||
|
|
3e9355f42d | ||
|
|
aaa63c19e4 | ||
|
|
9ad99461bf | ||
|
|
561a40908d | ||
|
|
3351e63458 | ||
|
|
e7e62bbab8 | ||
|
|
fc28e032be | ||
|
|
d959e0cf42 | ||
|
|
33f0498503 | ||
|
|
7ecbec7df9 | ||
|
|
4cd6499a96 | ||
|
|
cad5ef5de2 | ||
|
|
3ddf4da6a8 | ||
|
|
e1ce4cbe14 | ||
|
|
f47e36d93d | ||
|
|
86a4fe0847 | ||
|
|
94a0617b9b | ||
|
|
5c0bc2a3b4 | ||
|
|
7501036300 | ||
|
|
6784d3bdb7 | ||
|
|
88a62da4c4 | ||
|
|
f2411de85e | ||
|
|
8923ed5b3c | ||
|
|
514925bd4c | ||
|
|
451d30208c | ||
|
|
fd6b561899 | ||
|
|
efb596433c | ||
|
|
f01bb181c1 | ||
|
|
c7688a52f3 | ||
|
|
38900e0ee7 | ||
|
|
64ddb50cd6 | ||
|
|
d452dee3f7 | ||
|
|
b00218e5c5 | ||
|
|
d02d5db7c3 | ||
|
|
66a7937bd1 | ||
|
|
aa1d626187 | ||
|
|
46b7ea6c12 | ||
|
|
6d294ea7e6 | ||
|
|
1ec5158c65 | ||
|
|
779b05086b | ||
|
|
9ef7f81b3f | ||
|
|
7bb046ac41 | ||
|
|
dc3e1725da | ||
|
|
9974fca2cb | ||
|
|
ba7f652cde | ||
|
|
792a25fad7 | ||
|
|
70a543a8c1 | ||
|
|
bfa4dc502a | ||
|
|
fe08afd740 | ||
|
|
a6e8a597e7 | ||
|
|
62128730e8 | ||
|
|
805fe44bfb | ||
|
|
2b46f74329 | ||
|
|
eb68facd6c | ||
|
|
692600c755 | ||
|
|
e64980af5f | ||
|
|
c283623903 | ||
|
|
bd5e8f4192 | ||
|
|
4a910549ac | ||
|
|
c85e4abd91 | ||
|
|
057b4999fd | ||
|
|
731b65ecdd | ||
|
|
97c30c3a15 | ||
|
|
70ed1219a9 | ||
|
|
8c80607df9 | ||
|
|
461b56892c | ||
|
|
206f564248 | ||
|
|
043a7817e2 | ||
|
|
760a8d4bcd | ||
|
|
b6526e46f1 | ||
|
|
bb818e0621 | ||
|
|
34be364ca7 | ||
|
|
824d271a42 | ||
|
|
67d3e4bcac | ||
|
|
3ca57dcd0c | ||
|
|
de2efd1664 | ||
|
|
1b3084ddfa | ||
|
|
66d8634b17 | ||
|
|
a7e17d69b4 | ||
|
|
802afebafe | ||
|
|
13b9d83401 | ||
|
|
33264862c1 | ||
|
|
8e5d7a1900 | ||
|
|
b6911fb4ed | ||
|
|
de63e2a3a4 | ||
|
|
b7ae44b3f8 | ||
|
|
06ccc865b9 | ||
|
|
7b1fc6e5cf | ||
|
|
c441fc63e5 | ||
|
|
fccb9c6394 | ||
|
|
3b24058e15 | ||
|
|
8a99230048 | ||
|
|
84c2549c36 | ||
|
|
5287efd34c | ||
|
|
c6c0742267 | ||
|
|
44244335a1 | ||
|
|
bf9af11274 | ||
|
|
6f0ecfa39b | ||
|
|
81c4d52041 | ||
|
|
f61736621e | ||
|
|
e5d6b9ea53 | ||
|
|
c6237ca57f | ||
|
|
e186019066 | ||
|
|
ff33439f0a | ||
|
|
71ba0293a5 | ||
|
|
312a96d2bf | ||
|
|
3127853c1e | ||
|
|
718fb2c2b5 | ||
|
|
0a7273b9fe | ||
|
|
11b91775b6 | ||
|
|
fe54a855b3 | ||
|
|
ea682fdecd | ||
|
|
52b110acef | ||
|
|
09814c28a9 | ||
|
|
a323276e37 | ||
|
|
608acedaf6 | ||
|
|
9150d28fe1 | ||
|
|
0e56c85349 | ||
|
|
123ae5b29b |
No files matched your search
@@ -17,6 +17,8 @@ POSTGRES_PASSWORD=changeme_super
|
|||||||
MINIO_ROOT_USER=minioadmin
|
MINIO_ROOT_USER=minioadmin
|
||||||
MINIO_ROOT_PASSWORD=changeme_minio_root
|
MINIO_ROOT_PASSWORD=changeme_minio_root
|
||||||
MINIO_ENDPOINT=minio:9000
|
MINIO_ENDPOINT=minio:9000
|
||||||
|
# Browser-resolvable endpoint used in presigned URLs.
|
||||||
|
MINIO_PUBLIC_ENDPOINT=localhost:9000
|
||||||
# App-level access key. docker-compose.yml provisions this user and a bucket-scoped policy.
|
# App-level access key. docker-compose.yml provisions this user and a bucket-scoped policy.
|
||||||
MINIO_ACCESS_KEY=docuvault_app
|
MINIO_ACCESS_KEY=docuvault_app
|
||||||
MINIO_SECRET_KEY=changeme_minio_app
|
MINIO_SECRET_KEY=changeme_minio_app
|
||||||
@@ -74,3 +76,19 @@ ONEDRIVE_TENANT_ID=common
|
|||||||
# Backend and frontend URLs — used to construct OAuth callback/redirect URLs
|
# Backend and frontend URLs — used to construct OAuth callback/redirect URLs
|
||||||
BACKEND_URL=http://localhost:8000
|
BACKEND_URL=http://localhost:8000
|
||||||
FRONTEND_URL=http://localhost:5173
|
FRONTEND_URL=http://localhost:5173
|
||||||
|
|
||||||
|
# ── AI Defaults ──────────────────────────────────────────────────────────────
|
||||||
|
DEFAULT_AI_PROVIDER=ollama
|
||||||
|
DEFAULT_AI_MODEL=llama3.2
|
||||||
|
SYSTEM_PROMPT=
|
||||||
|
|
||||||
|
# ── Auth/session durations ───────────────────────────────────────────────────
|
||||||
|
ACCESS_TOKEN_EXPIRE_MINUTES=15
|
||||||
|
REFRESH_TOKEN_EXPIRE_DAYS=30
|
||||||
|
REFRESH_TOKEN_EXPIRE_HOURS=16
|
||||||
|
|
||||||
|
# ── Development logging and observability ────────────────────────────────────
|
||||||
|
LOG_LEVEL=INFO
|
||||||
|
LOG_JSON=true
|
||||||
|
GRAFANA_ADMIN_USER=admin
|
||||||
|
GRAFANA_ADMIN_PASSWORD=CHANGEME-replace-with-strong-password
|
||||||
+24
-4
@@ -14,6 +14,18 @@ Every user's documents — and the credentials they use to store them — are in
|
|||||||
|
|
||||||
See `.planning/MILESTONES.md` and `.planning/milestones/v0.2-ROADMAP.md` for full archive.
|
See `.planning/MILESTONES.md` and `.planning/milestones/v0.2-ROADMAP.md` for full archive.
|
||||||
|
|
||||||
|
## Current Milestone: v0.3 Reimagining Cloud Storage integration
|
||||||
|
|
||||||
|
**Goal:** Make connected cloud storage behave like first-class DocuVault storage: users can browse and manage cloud files as if they were local, analyze all or selected cloud documents, and search them by keywords, sentences, or ideas while minimizing DocuVault-owned local storage.
|
||||||
|
|
||||||
|
**Target features:**
|
||||||
|
- Native-feeling cloud file navigation and actions: open, preview, upload, move, delete, rename, and organize files from connected providers.
|
||||||
|
- Provider setup, health, reconnect, credential lifecycle, and error handling that make cloud connections feel reliable and understandable.
|
||||||
|
- Analyze all cloud documents or a selected subset without requiring manual local upload.
|
||||||
|
- Classify and index cloud documents so they become smart-searchable like locally uploaded documents.
|
||||||
|
- Cache and offload cloud file bytes on demand, similar to a desktop sync client, preserving local-like navigation while minimizing local storage use.
|
||||||
|
- Rework storage architecture where needed: provider capabilities, background jobs, sync/import/index state, conflict handling, and test coverage.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Validated (v0.1 — shipped 2026-06-06)
|
### Validated (v0.1 — shipped 2026-06-06)
|
||||||
@@ -65,7 +77,14 @@ See `.planning/MILESTONES.md` and `.planning/milestones/v0.2-ROADMAP.md` for ful
|
|||||||
- ✓ Dead code deleted (FolderRow.vue, AccountView.vue, stale test files) — v0.2
|
- ✓ Dead code deleted (FolderRow.vue, AccountView.vue, stale test files) — v0.2
|
||||||
- ✓ WHY-only comment policy enforced (CODE-09) — v0.2
|
- ✓ WHY-only comment policy enforced (CODE-09) — v0.2
|
||||||
|
|
||||||
### Active (next milestone — not yet defined)
|
### Active (v0.3 — Reimagining Cloud Storage integration)
|
||||||
|
|
||||||
|
- [ ] Cloud files can be browsed and managed with the same core actions as local files: open, preview, upload, move, delete, and rename.
|
||||||
|
- [ ] Cloud provider setup and maintenance includes health status, reconnect flows, credential lifecycle handling, and useful error states.
|
||||||
|
- [ ] Users can analyze all cloud documents or a selected subset from connected storage.
|
||||||
|
- [ ] Analyzed cloud documents are classified and indexed for smart search by keywords, sentences, and semantic ideas.
|
||||||
|
- [ ] DocuVault minimizes local storage by caching cloud file bytes only when needed and offloading them when safe.
|
||||||
|
- [ ] Storage architecture models provider capabilities, sync/index state, background jobs, and conflict/error handling explicitly.
|
||||||
|
|
||||||
### Out of Scope
|
### Out of Scope
|
||||||
|
|
||||||
@@ -79,7 +98,7 @@ See `.planning/MILESTONES.md` and `.planning/milestones/v0.2-ROADMAP.md` for ful
|
|||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
- **Current state**: v0.2 shipped 2026-06-17. All 4 v0.2 phases complete: stack upgrade + decomposition (Phase 8), admin panel rearchitecture (Phase 9), UX interaction layer (Phase 10), visual design + responsive layout (Phase 11). 277 tests pass. Bundle −81 kB from baseline. App is mobile-responsive, keyboard-navigable, and fully polished. Ready for next milestone definition.
|
- **Current state**: v0.3 in progress — Phase 12 (cloud-resource-foundation) complete 2026-06-19. Provider-neutral CloudResourceAdapter contract, durable CloudItem/CloudFolderState ORM, connection-ID browse API, stale-while-revalidate Celery refresh task, and capability-aware StorageBrowser frontend all shipped. v0.2.0 released. Phase 13 (cloud mutations: create/rename/move/delete) is next.
|
||||||
- **Tech stack**: FastAPI 0.136+ (Python 3.12), SQLAlchemy 2.0 async, Alembic, MinIO SDK; Vue 3 (Options API), Pinia, Vue Router 4, Vite, Tailwind CSS.
|
- **Tech stack**: FastAPI 0.136+ (Python 3.12), SQLAlchemy 2.0 async, Alembic, MinIO SDK; Vue 3 (Options API), Pinia, Vue Router 4, Vite, Tailwind CSS.
|
||||||
- **Code quality**: v0.1 was built feature-first under time pressure. Both backend and frontend contain duplication, inconsistent patterns, and components that grew beyond their original scope. v0.2 addresses this systematically.
|
- **Code quality**: v0.1 was built feature-first under time pressure. Both backend and frontend contain duplication, inconsistent patterns, and components that grew beyond their original scope. v0.2 addresses this systematically.
|
||||||
- **Admin panel**: Standalone /admin/* route subtree with AdminLayout as the route component, AdminSidebar with 5 nav links, and 5 dedicated view components. AdminView.vue and legacy tab components deleted. Admin users redirect to /admin on login; non-admin blocked by to.matched.some() guard.
|
- **Admin panel**: Standalone /admin/* route subtree with AdminLayout as the route component, AdminSidebar with 5 nav links, and 5 dedicated view components. AdminView.vue and legacy tab components deleted. Admin users redirect to /admin on login; non-admin blocked by to.matched.some() guard.
|
||||||
@@ -120,12 +139,13 @@ See `.planning/MILESTONES.md` and `.planning/milestones/v0.2-ROADMAP.md` for ful
|
|||||||
| Teleport + getBoundingClientRect for dropdowns | Viewport-edge clipping eliminated without complex position logic; prerequisite for virtual scrolling | ✓ Shipped Phase 10 (UX-13) |
|
| Teleport + getBoundingClientRect for dropdowns | Viewport-edge clipping eliminated without complex position logic; prerequisite for virtual scrolling | ✓ Shipped Phase 10 (UX-13) |
|
||||||
| Admin routes lazy-loaded | All 5 admin views excluded from initial bundle; −81 kB (−30.6%) improvement | ✓ Shipped Phase 11 (PERF-03) |
|
| Admin routes lazy-loaded | All 5 admin views excluded from initial bundle; −81 kB (−30.6%) improvement | ✓ Shipped Phase 11 (PERF-03) |
|
||||||
| Vite 8 upgrade for npm audit | Closed high-severity esbuild CVE present in Vite 5/6; npm audit now clean | ✓ Shipped Phase 11 (11-07) |
|
| Vite 8 upgrade for npm audit | Closed high-severity esbuild CVE present in Vite 5/6; npm audit now clean | ✓ Shipped Phase 11 (11-07) |
|
||||||
|
| Virtual-local cloud storage model | Users already organize documents in their cloud provider; DocuVault should add local-like actions, analysis, and smart search without forcing full import | — Pending v0.3 |
|
||||||
|
|
||||||
## Evolution
|
## Evolution
|
||||||
|
|
||||||
This document evolves at phase transitions and milestone boundaries.
|
This document evolves at phase transitions and milestone boundaries.
|
||||||
|
|
||||||
Last updated: 2026-06-16
|
Last updated: 2026-06-19
|
||||||
|
|
||||||
**After each phase transition** (via `/gsd-transition`):
|
**After each phase transition** (via `/gsd-transition`):
|
||||||
1. Requirements invalidated? → Move to Out of Scope with reason
|
1. Requirements invalidated? → Move to Out of Scope with reason
|
||||||
@@ -141,4 +161,4 @@ Last updated: 2026-06-16
|
|||||||
4. Update Context with current state
|
4. Update Context with current state
|
||||||
|
|
||||||
---
|
---
|
||||||
*Last updated: 2026-06-17 — after v0.2 milestone*
|
*Last updated: 2026-06-17 — after starting v0.3 milestone*
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
# Requirements: DocuVault v0.3
|
||||||
|
|
||||||
|
**Defined:** 2026-06-17
|
||||||
|
**Core Value:** Every user's documents — and the credentials they use to store them — are inaccessible to everyone except that user, while the platform scales horizontally and supports pluggable storage backends.
|
||||||
|
|
||||||
|
## v0.3 Requirements
|
||||||
|
|
||||||
|
### Connections
|
||||||
|
|
||||||
|
- [x] **CONN-01**: User can connect, reconnect, test, and disconnect each supported cloud provider.
|
||||||
|
- [x] **CONN-02**: User can see connection health and actionable errors for expired, revoked, or invalid credentials.
|
||||||
|
- [x] **CONN-03**: Reconnecting or refreshing credentials invalidates stale provider caches without exposing credentials.
|
||||||
|
- [x] **CONN-04**: User interface reflects the file operations supported by each connected provider.
|
||||||
|
|
||||||
|
### Cloud File Management
|
||||||
|
|
||||||
|
- [x] **CLOUD-01**: User can browse connected cloud files and folders through the shared `StorageBrowser`.
|
||||||
|
- [x] **CLOUD-02**: User can open and preview supported cloud documents through DocuVault authorization.
|
||||||
|
- [x] **CLOUD-03**: User can upload files into the currently viewed cloud folder.
|
||||||
|
- [x] **CLOUD-04**: User can create folders in connected cloud storage where the provider supports it.
|
||||||
|
- [x] **CLOUD-05**: User can rename cloud files and folders where the provider supports it.
|
||||||
|
- [x] **CLOUD-06**: User can move files and folders within the same cloud connection where the provider supports it.
|
||||||
|
- [x] **CLOUD-07**: User can delete cloud files and folders after explicit confirmation.
|
||||||
|
- [x] **CLOUD-08**: User sees unsupported cloud actions disabled with a provider-specific explanation.
|
||||||
|
- [x] **CLOUD-09**: Successful cloud mutations update navigation promptly and produce metadata-only audit events.
|
||||||
|
|
||||||
|
### Cloud Analysis
|
||||||
|
|
||||||
|
- [ ] **ANALYZE-01**: User can request analysis of an individual cloud document.
|
||||||
|
- [ ] **ANALYZE-02**: User can request analysis of selected cloud files or folders.
|
||||||
|
- [ ] **ANALYZE-03**: User can request analysis of an entire cloud connection after reviewing a file-count and byte-size estimate.
|
||||||
|
- [ ] **ANALYZE-04**: User can monitor aggregate and per-item analysis progress through queued, downloading, extracting, classifying, indexed, and failed states.
|
||||||
|
- [ ] **ANALYZE-05**: User can cancel queued analysis work and retry failed cloud documents.
|
||||||
|
- [ ] **ANALYZE-06**: DocuVault does not repeat analysis when the provider item version or etag has not changed.
|
||||||
|
- [ ] **ANALYZE-07**: Analyzing a cloud document never moves, renames, or rewrites the provider-owned file.
|
||||||
|
|
||||||
|
### Smart Search
|
||||||
|
|
||||||
|
- [ ] **SEARCH-01**: User can search local documents and analyzed cloud documents through one search experience.
|
||||||
|
- [ ] **SEARCH-02**: User can find documents using keywords or sentences matched against extracted text.
|
||||||
|
- [ ] **SEARCH-03**: User can find documents using semantic concepts or ideas rather than exact wording.
|
||||||
|
- [ ] **SEARCH-04**: Search results identify local/cloud source, provider, location, topics, and analysis status.
|
||||||
|
- [ ] **SEARCH-05**: User can filter search results by source, provider, folder, topic, and analysis status.
|
||||||
|
- [ ] **SEARCH-06**: Opening a cloud search result hydrates provider bytes only when preview or download requires them.
|
||||||
|
- [ ] **SEARCH-07**: Executing a search query never downloads cloud file bytes.
|
||||||
|
|
||||||
|
### Cache and Storage Minimization
|
||||||
|
|
||||||
|
- [x] **CACHE-01**: Provider-owned file bytes remain the source of truth unless the user explicitly imports a file into local DocuVault storage.
|
||||||
|
- [x] **CACHE-02**: DocuVault persists cloud metadata, extracted text, topics, and semantic search data independently of cached file bytes.
|
||||||
|
- [ ] **CACHE-03**: DocuVault caches cloud file bytes only when required for opening, preview, or analysis.
|
||||||
|
- [ ] **CACHE-04**: Configurable cache limits and safe eviction minimize local storage use without interrupting active work.
|
||||||
|
- [ ] **CACHE-05**: Cached cloud content is isolated by user and connection and is never served without an ownership check.
|
||||||
|
|
||||||
|
### Change Tracking
|
||||||
|
|
||||||
|
- [x] **SYNC-01**: DocuVault records provider item identifiers, parent/location, version or etag, size, and modification time for indexed cloud items.
|
||||||
|
- [ ] **SYNC-02**: DocuVault marks analyzed cloud items stale when their provider version changes.
|
||||||
|
- [ ] **SYNC-03**: Files deleted outside DocuVault are removed from cloud navigation and excluded from search results after refresh.
|
||||||
|
- [ ] **SYNC-04**: Provider refresh jobs are idempotent and retry transient failures with bounded exponential backoff.
|
||||||
|
|
||||||
|
## Future Requirements
|
||||||
|
|
||||||
|
### Extended Cloud Mobility
|
||||||
|
|
||||||
|
- **XFER-01**: User can copy or move files between different cloud providers.
|
||||||
|
- **IMPORT-01**: User can permanently pin or import a cloud file into DocuVault-owned local storage.
|
||||||
|
- **OFFLINE-01**: User can mark folders for persistent offline availability.
|
||||||
|
|
||||||
|
### Sync Clients
|
||||||
|
|
||||||
|
- **CLIENT-01**: User can install a native desktop client for operating-system file integration.
|
||||||
|
- **CLIENT-02**: Native clients can synchronize selected DocuVault and cloud folders.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
| Feature | Reason |
|
||||||
|
|---------|--------|
|
||||||
|
| Cross-provider move/copy | Requires transfer orchestration and conflict semantics beyond the virtual-local single-provider milestone |
|
||||||
|
| Native desktop sync client | v0.3 remains a responsive web application |
|
||||||
|
| Whole-drive offline mirror | Conflicts with the milestone's local-storage minimization goal |
|
||||||
|
| Real-time collaborative editing | Document management and discovery are the current focus |
|
||||||
|
| Automatic whole-cloud analysis without consent | Could create unexpected AI cost, provider load, and privacy impact |
|
||||||
|
| Permanent local pinning/import | Temporary bounded caching is sufficient for v0.3; explicit import remains future scope |
|
||||||
|
| Public unauthenticated links | Existing v0.x privacy boundary remains unchanged |
|
||||||
|
|
||||||
|
## Traceability
|
||||||
|
|
||||||
|
| Requirement | Phase | Status |
|
||||||
|
|-------------|-------|--------|
|
||||||
|
| CONN-01 | Phase 13 | Complete |
|
||||||
|
| CONN-02 | Phase 13 | Complete |
|
||||||
|
| CONN-03 | Phase 13 | Complete |
|
||||||
|
| CONN-04 | Phase 12 | Complete |
|
||||||
|
| CLOUD-01 | Phase 12 | Complete |
|
||||||
|
| CLOUD-02 | Phase 13 | Complete |
|
||||||
|
| CLOUD-03 | Phase 13 | Complete |
|
||||||
|
| CLOUD-04 | Phase 13 | Complete |
|
||||||
|
| CLOUD-05 | Phase 13 | Complete |
|
||||||
|
| CLOUD-06 | Phase 13 | Complete |
|
||||||
|
| CLOUD-07 | Phase 13 | Complete |
|
||||||
|
| CLOUD-08 | Phase 12 | Complete |
|
||||||
|
| CLOUD-09 | Phase 13 | Complete |
|
||||||
|
| ANALYZE-01 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-02 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-03 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-04 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-05 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-06 | Phase 14 | Pending |
|
||||||
|
| ANALYZE-07 | Phase 14 | Pending |
|
||||||
|
| SEARCH-01 | Phase 15 | Pending |
|
||||||
|
| SEARCH-02 | Phase 15 | Pending |
|
||||||
|
| SEARCH-03 | Phase 15 | Pending |
|
||||||
|
| SEARCH-04 | Phase 15 | Pending |
|
||||||
|
| SEARCH-05 | Phase 15 | Pending |
|
||||||
|
| SEARCH-06 | Phase 15 | Pending |
|
||||||
|
| SEARCH-07 | Phase 15 | Pending |
|
||||||
|
| CACHE-01 | Phase 12 | Complete |
|
||||||
|
| CACHE-02 | Phase 12 | Complete |
|
||||||
|
| CACHE-03 | Phase 14 | Pending |
|
||||||
|
| CACHE-04 | Phase 14 | Pending |
|
||||||
|
| CACHE-05 | Phase 14 | Pending |
|
||||||
|
| SYNC-01 | Phase 12 | Complete |
|
||||||
|
| SYNC-02 | Phase 16 | Pending |
|
||||||
|
| SYNC-03 | Phase 16 | Pending |
|
||||||
|
| SYNC-04 | Phase 16 | Pending |
|
||||||
|
|
||||||
|
**Coverage:**
|
||||||
|
|
||||||
|
- v0.3 requirements: 36 total
|
||||||
|
- Mapped to phases: 36
|
||||||
|
- Unmapped: 0
|
||||||
|
|
||||||
|
---
|
||||||
|
*Requirements defined: 2026-06-17*
|
||||||
|
*Last updated: 2026-06-17 after roadmap creation*
|
||||||
+143
-543
@@ -1,561 +1,161 @@
|
|||||||
# DocuVault — v1 Roadmap
|
# DocuVault Roadmap: v0.3 Reimagining Cloud Storage integration
|
||||||
|
|
||||||
_Last updated: 2026-06-02_
|
**Status:** Proposed
|
||||||
|
**Phases:** 12-16
|
||||||
|
**Requirements:** 36
|
||||||
|
**Defined:** 2026-06-17
|
||||||
|
|
||||||
## Mandatory Cross-Cutting Gates (every phase)
|
## Milestone Goal
|
||||||
|
|
||||||
Before any phase is marked complete, all three gates must pass:
|
Make connected cloud storage behave like first-class DocuVault storage: users can navigate and manage provider-owned files as if they were local, selectively analyze them, and find them through unified keyword and semantic search while DocuVault minimizes locally cached file bytes.
|
||||||
|
|
||||||
1. **Test gate** — `pytest -v` passes with zero failures; every new function/endpoint has at least one test; all security invariant tests pass (wrong owner, admin block, token replay)
|
## Mandatory Cross-Cutting Gates
|
||||||
2. **Security gate** — Security agent runs `bandit -r backend/` (zero HIGH), `pip audit` (zero critical/high), `npm audit --audit-level=high` (zero high/critical); admin endpoints verified to never return `password_hash`, `credentials_enc`, or document content; no hardcoded secrets
|
|
||||||
3. **Bug fix rule** — Any bug fix during execution must: (a) target the root cause, (b) change ≤50 lines, (c) include a regression test — no workarounds permitted
|
|
||||||
|
|
||||||
---
|
Before any phase is marked complete:
|
||||||
|
|
||||||
## Phases
|
1. Backend and frontend test suites pass with zero failures; every new function, endpoint, store, and component has focused coverage.
|
||||||
|
2. Security gate verifies user/connection/item ownership, credential secrecy, SSRF defenses, metadata-only audit logs, cache isolation, and no admin document-content access.
|
||||||
|
3. Dependency audits report no high/critical vulnerabilities.
|
||||||
|
4. Provider integration tests cover supported capabilities and explicit unsupported behavior.
|
||||||
|
5. User-facing changes update `AGENTS.md`, relevant README sections, and application patch/minor versions according to project protocol.
|
||||||
|
|
||||||
- [x] **Phase 1: Infrastructure Foundation** — PostgreSQL + MinIO wired into Docker Compose; Alembic migrations running; existing app still works
|
## Phase Summary
|
||||||
- [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
|
|
||||||
- [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)
|
|
||||||
- [x] **Phase 5: Cloud Storage Backends** — Users can connect OneDrive, Google Drive, Nextcloud, or WebDAV as a personal storage backend
|
|
||||||
|
|
||||||
---
|
| Phase | Name | Goal | Requirements |
|
||||||
|
|------:|------|------|--------------|
|
||||||
|
| 12 | 6/6 | Complete | 2026-06-21 |
|
||||||
|
| 13 | 11/11 | Complete | 2026-06-23 |
|
||||||
|
| 14 | Selective Analysis and Byte Cache | Analyze selected cloud scopes through cancellable jobs backed by bounded, private, on-demand byte caching | ANALYZE-01..07, CACHE-03..05 |
|
||||||
|
| 15 | Unified Smart Search | Search local and analyzed cloud documents by exact text or semantic ideas without downloading files during queries | SEARCH-01..07 |
|
||||||
|
| 16 | Change Tracking and Reliability | Detect external provider changes, mark stale indexes, remove deleted items, and harden refresh behavior | SYNC-02..04 |
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
### Phase 1: Infrastructure Foundation
|
### Phase 12: Cloud Resource Foundation
|
||||||
|
|
||||||
**Goal**: PostgreSQL + MinIO are wired into Docker Compose with a complete Alembic-managed schema; all services boot cleanly and the existing single-user document scanner continues to work exactly as before — no user-facing behavior change.
|
**Goal:** DocuVault has a provider-neutral cloud file capability contract and durable per-user cloud item index, and the shared browser can render cloud resources and supported actions without copying full provider files into local storage.
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Nothing (first phase)
|
|
||||||
**Requirements**: STORE-01, STORE-02, STORE-07
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
**Depends on:** Phase 11
|
||||||
|
**Requirements:** CONN-04, CLOUD-01, CLOUD-08, CACHE-01, CACHE-02, SYNC-01
|
||||||
|
|
||||||
1. `docker compose up` starts PostgreSQL, MinIO, and the FastAPI backend with no errors; health checks pass for all three services
|
**Success Criteria:**
|
||||||
2. Running `alembic upgrade head` applies the initial migration cleanly against the fresh PostgreSQL instance with no errors
|
|
||||||
3. The full existing document upload, text extraction, and AI classification workflow completes successfully — no regression in single-user behavior
|
|
||||||
4. MinIO object key schema `{user_id}/{document_id}/{uuid4()}{ext}` is enforced in the model layer; human-readable filenames are stored in the DB column, not in the MinIO key
|
|
||||||
|
|
||||||
**Plans**: 5 plans
|
1. Every supported provider reports normalized capabilities for browse, open, preview, upload, folder creation, rename, move, delete, and change tracking.
|
||||||
|
2. Browsing any connected provider persists normalized, owner-scoped cloud item metadata including provider ID/path, parent, type, size, modification time, and version/etag.
|
||||||
|
3. `StorageBrowser.vue` remains the single file browser and renders cloud actions from capabilities, including disabled actions with provider-specific explanations.
|
||||||
|
4. Provider-owned bytes remain outside DocuVault while metadata, extracted text placeholders, topic links, and semantic-index state can be persisted independently.
|
||||||
|
5. Ownership and admin-negative tests prove cloud connection and cloud item metadata cannot cross user boundaries.
|
||||||
|
|
||||||
- [x] 01-01-PLAN.md — Docker Compose service topology + Postgres init + Pydantic Settings + requirements
|
### Phase 12.1: Fix Nextcloud root listing and sync visibility (INSERTED)
|
||||||
- [x] 01-02-PLAN.md — Wave 0 test scaffolds (xfail/skip stubs) + async pytest fixtures
|
|
||||||
- [x] 01-03-PLAN.md — SQLAlchemy ORM models + async engine + Alembic async migration (incl. alembic upgrade head)
|
**Goal:** Restore visible, truthful, metadata-only cloud browsing by repairing Nextcloud's adapter contract, applying one completeness/freshness contract across all providers, and aligning the shared frontend browser with normalized provider identities.
|
||||||
- [x] 01-04-PLAN.md — StorageBackend ABC + MinIO backend + rewritten async services/storage.py
|
**Requirements:** CONN-04, CLOUD-01, CACHE-01, SYNC-01
|
||||||
- [x] 01-05-PLAN.md — Lifespan + /health + API cutover + Celery worker + walking-skeleton e2e verify
|
**Depends on:** Phase 12
|
||||||
|
**Plans:** 4/4 plans complete
|
||||||
|
**Execution waves:** Wave 1: 12.1-01; Wave 2: 12.1-02 after 01; Wave 3: 12.1-03 after 02; Wave 4: 12.1-04 after 01-03.
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
|
||||||
|
- [x] 12.1-01-PLAN.md
|
||||||
|
- [x] 12.1-02-PLAN.md
|
||||||
|
- [x] 12.1-03-PLAN.md
|
||||||
|
- [x] 12.1-04-PLAN.md
|
||||||
|
- [x] 12.1-01 (Wave 1) — Restore the shared four-provider adapter contract, secure Nextcloud URL/redirect handling, and metadata-only listing behavior.
|
||||||
|
- [x] 12.1-02 (Wave 2; after 12.1-01) — Centralize complete/incomplete reconciliation so incomplete provider results cannot become fresh or delete cached metadata.
|
||||||
|
- [x] 12.1-03 (Wave 3; after 12.1-02) — Align the shared browser and trees with `kind`, opaque `provider_item_id` routing, lineage-based breadcrumbs, and server freshness.
|
||||||
|
- [ ] 12.1-04 (Wave 4; after 12.1-01 through 12.1-03) — Run sanitized two-mode live Nextcloud acceptance, rendered/security/secret gates, and phase closeout.
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
|
||||||
|
1. Nextcloud and generic WebDAV expose one canonical `CloudResourceAdapter.list_folder` implementation, and all four providers pass the same runtime identity/completeness/no-byte/no-mutation contract with provider-specific pagination fixtures.
|
||||||
|
2. Every outbound Nextcloud/WebDAV target, including redirects, is rejected unless each hop is HTTPS, same-origin, and passes URL plus resolved-address SSRF validation; automatic redirects are disabled.
|
||||||
|
3. `complete=False` never marks a folder fresh, advances `last_refreshed_at`, or authorizes unseen-item deletion in synchronous or worker paths; cached rows remain usable with a controlled warning.
|
||||||
|
4. The frontend classifies by `kind`, navigates through named routes/query serialization with opaque `provider_item_id` values, builds breadcrumbs from explicit navigation lineage rather than splitting provider IDs, and renders backend freshness verbatim.
|
||||||
|
5. Live Nextcloud validation first runs a nonblocking sanitized diagnostic; exact names/kinds become a blocking gate only after the owner-confirmed manifest is stored in a tracked, non-secret fixture. Unexpected live names are never printed or persisted.
|
||||||
|
6. Automated backend/frontend/rendered-flow tests, dedicated per-plan security reviews, dependency audits, an executable secret scan, documentation/version updates, and one atomic commit/push per plan pass without exposing credentials or provider bytes.
|
||||||
|
|
||||||
|
### Phase 13: Virtual-Local Cloud Operations
|
||||||
|
|
||||||
|
**Goal:** Users can maintain healthy cloud connections and perform the main file-management workflows in connected storage with the same shared browser interactions used for local files.
|
||||||
|
|
||||||
|
**Depends on:** Phase 12
|
||||||
|
**Requirements:** CONN-01, CONN-02, CONN-03, CLOUD-02, CLOUD-03, CLOUD-04, CLOUD-05, CLOUD-06, CLOUD-07, CLOUD-09
|
||||||
|
**Plans:** 11/11 plans complete
|
||||||
|
**Execution waves:** Wave 0: 13-01 and 13-02 in parallel; Wave 1: 13-03 after 13-01; Wave 2: 13-04 after 13-01 and 13-03; Wave 3: 13-05 after 13-01, 13-03, and 13-04; Wave 4: 13-06 after 13-05; Wave 5: 13-07 and 13-08 in parallel (13-07 after 13-02, 13-04, and 13-06; 13-08 after 13-03, 13-04, and 13-06); Wave 6: 13-09 after 13-08; Wave 7: 13-10 after 13-02, 13-04, 13-07, and 13-09; Wave 8: 13-11 after 13-03 through 13-10.
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
|
||||||
|
- [x] 13-01-PLAN.md — Create red backend and provider-contract suites for reconnect, content, mutations, and audit secrecy.
|
||||||
|
- [x] 13-02-PLAN.md — Create red frontend/store suites for queue, preview, health UX, broader Drive consent, and no-probe-on-navigation.
|
||||||
|
- [x] 13-03-PLAN.md — Build the mutable cloud contract and orchestration seam without breaking centralized reconciliation.
|
||||||
|
- [x] 13-04-PLAN.md — Implement connection-ID reconnect, explicit health test, broader Drive scope handling, and authorized content routes.
|
||||||
|
- [x] 13-05-PLAN.md — Implement backend upload provider mechanics, typed route results, and refreshed-credential handoff.
|
||||||
|
- [x] 13-06-PLAN.md — Complete upload reconcile, freshness, and metadata-only audit follow-through before frontend queue wiring.
|
||||||
|
- [x] 13-07-PLAN.md — Wire the shared browser queue and binary-only preview or download fallback through thin cloud view handlers.
|
||||||
|
- [x] 13-08-PLAN.md — Implement backend create-folder and rename semantics with collision, stale, and stable-identity safeguards.
|
||||||
|
- [x] 13-09-PLAN.md — Implement backend move and delete semantics with same-connection, disclosure, security, and audit safeguards.
|
||||||
|
- [x] 13-10-PLAN.md — Finish shared-browser mutation UX, store-backed health behavior, reconnect copy, and the no-probe invariant.
|
||||||
|
- [x] 13-11-PLAN.md — Run closeout-only docs, versions, full gates, explicit secret scan, and ship-readiness checks.
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
|
||||||
|
1. Users can connect, test, reconnect, and disconnect providers while seeing current health and actionable reauthentication/error states.
|
||||||
|
2. Users can open or preview supported cloud documents and upload files into the current cloud folder through authorized DocuVault endpoints.
|
||||||
|
3. Users can create folders, rename items, move items within one connection, and delete items after confirmation wherever the provider supports each action.
|
||||||
|
4. Successful mutations immediately invalidate affected listings/index entries, refresh the shared browser, and write metadata-only audit events.
|
||||||
|
5. Provider-specific tests verify operation semantics, conflict/error responses, token refresh persistence, SSRF protection, and explicit unsupported capabilities.
|
||||||
|
|
||||||
|
### Phase 14: Selective Analysis and Byte Cache
|
||||||
|
|
||||||
|
**Goal:** Users can analyze individual files, selected scopes, or whole connections through observable and controllable background jobs, while file bytes are hydrated only on demand and evicted safely.
|
||||||
|
|
||||||
|
**Depends on:** Phase 13
|
||||||
|
**Requirements:** ANALYZE-01, ANALYZE-02, ANALYZE-03, ANALYZE-04, ANALYZE-05, ANALYZE-06, ANALYZE-07, CACHE-03, CACHE-04, CACHE-05
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
|
||||||
|
1. Users can analyze one file, a multi-selection, a folder tree, or a whole connection after reviewing recursive file-count and byte-size estimates.
|
||||||
|
2. The UI exposes aggregate and per-item queued, downloading, extracting, classifying, indexed, cancelled, and failed states with retry and queued-work cancellation.
|
||||||
|
3. Analysis jobs are idempotent by cloud item and provider version/etag, and they never mutate the provider-owned file.
|
||||||
|
4. Cloud bytes are cached only for active opening, preview, or analysis work and are evicted by configurable limits without interrupting pinned active jobs.
|
||||||
|
5. Cache ownership, cache-key versioning, cancellation, duplicate-job, failure-retry, and eviction behavior have dedicated tests.
|
||||||
|
|
||||||
|
### Phase 15: Unified Smart Search
|
||||||
|
|
||||||
|
**Goal:** One search experience finds local and analyzed cloud documents by keywords, sentences, or semantic ideas and opens cloud results through on-demand hydration.
|
||||||
|
|
||||||
|
**Depends on:** Phase 14
|
||||||
|
**Requirements:** SEARCH-01, SEARCH-02, SEARCH-03, SEARCH-04, SEARCH-05, SEARCH-06, SEARCH-07
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
|
||||||
|
1. A single query returns authorized local and analyzed cloud results ranked through full-text and semantic relevance.
|
||||||
|
2. Keyword and sentence searches match persisted extracted text, while idea searches use persisted embeddings or equivalent semantic indexes.
|
||||||
|
3. Results clearly show source, provider, cloud location, topics, and current analysis/stale status and can be filtered by those fields.
|
||||||
|
4. Search execution performs no provider file download; only opening or previewing a result may hydrate bytes.
|
||||||
|
5. Search isolation, ranking, filtering, stale-result handling, and no-download-on-query invariants have automated coverage.
|
||||||
|
|
||||||
|
### Phase 16: Change Tracking and Reliability
|
||||||
|
|
||||||
|
**Goal:** DocuVault remains accurate when users or other applications modify connected cloud storage outside DocuVault.
|
||||||
|
|
||||||
|
**Depends on:** Phase 15
|
||||||
|
**Requirements:** SYNC-02, SYNC-03, SYNC-04
|
||||||
|
|
||||||
|
**Success Criteria:**
|
||||||
|
|
||||||
|
1. Provider delta feeds or bounded metadata refreshes detect changed versions and mark previously analyzed items stale until reanalysis.
|
||||||
|
2. Items deleted outside DocuVault disappear from navigation and are excluded from search without deleting unrelated user data.
|
||||||
|
3. Refresh jobs are idempotent, cursor-aware where supported, rate-limited, and retry transient failures with bounded exponential backoff.
|
||||||
|
4. Users can see refresh/stale/error state and deliberately reanalyze changed items.
|
||||||
|
5. End-to-end UAT verifies cloud-as-local operations, selective analysis, cache offload, unified smart search, and external-change recovery across supported provider classes.
|
||||||
|
|
||||||
|
## Coverage
|
||||||
|
|
||||||
|
- v0.3 requirements: 36
|
||||||
|
- Mapped to phases: 36
|
||||||
|
- Unmapped: 0
|
||||||
|
- Duplicate mappings: 0
|
||||||
|
|
||||||
---
|
---
|
||||||
|
*Roadmap proposed: 2026-06-17*
|
||||||
### Phase 2: Users & Authentication
|
|
||||||
|
|
||||||
**Goal**: Users can register, log in (with optional TOTP 2FA), reset their password, and sign out all active sessions; admins can manage user accounts and assign AI providers — all enforced by a complete FastAPI dependency chain.
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 1
|
|
||||||
**Requirements**: AUTH-01, AUTH-02, AUTH-03, AUTH-04, AUTH-05, AUTH-06, AUTH-07, AUTH-08, SEC-01, SEC-02, SEC-03, SEC-05, SEC-06, SEC-07, ADMIN-01, ADMIN-02, ADMIN-03, ADMIN-04, ADMIN-05, ADMIN-07
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. A new user can register with an email and password that passes strength validation; a password from the HaveIBeenPwned list is rejected with a clear error
|
|
||||||
2. A logged-in user can enroll a TOTP authenticator app, receive 8–10 backup codes, explicitly acknowledge them, and thereafter be required to supply a TOTP code (or backup code) on every login — a backup code is invalidated on first use
|
|
||||||
3. A user who forgets their password can receive a reset email, follow the link within 1 hour, set a new password, and is then returned to the TOTP login gate (not auto-logged in)
|
|
||||||
4. A user can trigger "sign out all devices" from account settings; all other active sessions are immediately invalidated and any reuse of a rotated refresh token revokes the entire token family
|
|
||||||
5. An admin user can create, deactivate, and reset a user account, and assign an AI provider and model to that user; admin API endpoints never return document content or credentials_enc (per-user document auth enforcement deferred to Phase 3 per D-07)
|
|
||||||
|
|
||||||
**Plans**: 5 plans
|
|
||||||
|
|
||||||
**Wave 1** — Foundation
|
|
||||||
|
|
||||||
- [x] 02-01-PLAN.md — Auth service layer (Argon2, JWT, refresh tokens, TOTP, backup codes, HIBP, security alert), FastAPI deps, BackupCode model + password_must_change migration
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1 completion)*
|
|
||||||
|
|
||||||
- [x] 02-02-PLAN.md — Register/login (TOTP + backup code paths) + refresh/logout/change-password endpoints + CSP/Origin validation/rate-limit (IP + per-account) + Vue auth store + router guard + Login/Register views
|
|
||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2 completion)*
|
|
||||||
|
|
||||||
- [x] 02-03-PLAN.md — TOTP enrollment + backup codes + password reset + sign-out-all endpoints + AccountView + TotpEnrollment + BackupCodesDisplay + PasswordReset views
|
|
||||||
|
|
||||||
**Wave 4** *(blocked on Wave 3 completion)*
|
|
||||||
|
|
||||||
- [x] 02-04-PLAN.md — Admin backend: user CRUD, quota, AI config endpoints with get_current_admin enforced + tests
|
|
||||||
|
|
||||||
**Wave 5** *(blocked on Wave 4 completion)*
|
|
||||||
|
|
||||||
- [x] 02-05-PLAN.md — Admin panel frontend: AdminView + three tab components + AppSidebar admin link and user identity footer
|
|
||||||
|
|
||||||
**Cross-cutting constraints:**
|
|
||||||
|
|
||||||
- JWT access token in Pinia memory only — never localStorage (Plans 02, 03, 05)
|
|
||||||
- Refresh token httpOnly SameSite=Strict cookie on all token issuance (Plans 02, 03)
|
|
||||||
- Admin endpoints never return document content or credentials_enc (Plans 04, 05)
|
|
||||||
- All auth endpoints rate-limited per-IP and per-account (Plans 02, 03)
|
|
||||||
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 3: Document Migration & Multi-User Isolation
|
|
||||||
|
|
||||||
**Goal**: All existing documents have been migrated from flat-file JSON + filesystem into PostgreSQL + MinIO; all new uploads use the presigned URL flow; per-user isolation is enforced at the DB level; the existing document UI works without regression; the backend is stateless and ready for horizontal scaling.
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 2
|
|
||||||
**Requirements**: STORE-03, STORE-04, STORE-05, STORE-06, STORE-08, SEC-04, DOC-03, DOC-04, DOC-05
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. Every document present before migration is accessible after migration with the same metadata and extracted text; a count reconciliation check confirms zero document loss
|
|
||||||
2. Two concurrent uploads that would together exceed a user's 100 MB quota result in exactly one success and one 413 rejection — the quota never goes over limit
|
|
||||||
3. A document delete atomically decrements the user's recorded quota usage; after deletion the quota reflects the freed bytes
|
|
||||||
4. Requesting a document object key or presigned URL for a document owned by a different user returns 403 — no cross-user object access is possible through any request parameter manipulation; all /api/documents/* endpoints enforce get_current_user and return 403 when the requesting user's role is admin (completing SC5 from Phase 2)
|
|
||||||
5. AI classification for each document uses the provider and model assigned to that user by the admin, not any user-supplied or default value
|
|
||||||
|
|
||||||
**Plans**: 5 plans
|
|
||||||
|
|
||||||
**Wave 1** — Migration + test scaffolds
|
|
||||||
|
|
||||||
- [x] 03-01-PLAN.md — Wave 0 test scaffolds (auth_user/admin_user/MinIO mock fixtures + 19 xfail stubs) + Alembic migration 0003 (null-user cleanup, NOT NULL constraint, topic cleanup, quota reconciliation, ix_topics_user_id) — Complete 2026-05-23
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1)*
|
|
||||||
|
|
||||||
- [ ] 03-02-PLAN.md — Presigned upload backend: StorageBackend ABC + MinIOBackend dual client + generate_presigned_put_url/stat_object + /api/documents/upload-url + /api/documents/{id}/confirm with atomic quota UPDATE + GET /api/auth/me/quota + delete-with-quota + abandoned-upload Celery beat + docker-compose CORS/celery-beat
|
|
||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2)*
|
|
||||||
|
|
||||||
- [x] 03-03-PLAN.md — Auth guards: get_regular_user dep + ownership assertions on every /api/documents/* handler (404 not 403) + admin 403 + real user_id in object_key + namespace-scoped /api/topics/* + POST /api/admin/topics + classifier topic-namespace plumbing
|
|
||||||
|
|
||||||
**Wave 4** *(blocked on Wave 3)*
|
|
||||||
|
|
||||||
- [x] 03-04-PLAN.md — Settings retirement + per-user AI: delete /api/settings + remove load_settings/save_settings + classifier accepts ai_provider/ai_model kwargs + Celery task resolves user.ai_provider via DB + frontend SettingsView placeholder + remove settings store/API — Complete 2026-05-23
|
|
||||||
|
|
||||||
**Wave 5** *(blocked on Wave 4)*
|
|
||||||
|
|
||||||
- [ ] 03-05-PLAN.md — Frontend upload flow + quota bar: 3-step upload action with XHR progress + UploadProgress.vue progress bar and quota rejection error block + QuotaBar.vue + AppSidebar embed + quota state in auth store + human checkpoint
|
|
||||||
|
|
||||||
**Cross-cutting constraints:**
|
|
||||||
|
|
||||||
- Atomic quota UPDATE pattern only lives in Plan 02; never duplicate (CLAUDE.md)
|
|
||||||
- Every /api/documents/* handler injects get_regular_user (Plan 03)
|
|
||||||
- AI provider/model resolved only via Celery task DB lookup (Plan 04)
|
|
||||||
- Browser XHR PUT to MinIO sends NO Authorization header (Plan 05)
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 3 is complete):**
|
|
||||||
|
|
||||||
- [ ] `pytest -v` — zero failures; presigned URL, quota enforcement, ownership isolation, and admin-403 all covered
|
|
||||||
- [ ] Security agent: path traversal check on object key construction; cross-user IDOR tests; quota race condition test
|
|
||||||
- [ ] Bandit + pip audit + npm audit all clean
|
|
||||||
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 4: Folders, Sharing, Quotas & Document UX
|
|
||||||
|
|
||||||
**Goal**: Users have a complete document management experience — organized with folders, shared by handle, warned before they hit quota, able to preview PDFs in-browser, and served by a searchable document list; admins can view the append-only audit log.
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 3
|
|
||||||
**Requirements**: FOLD-01, FOLD-02, FOLD-03, FOLD-04, FOLD-05, SHARE-01, SHARE-02, SHARE-03, SHARE-04, SHARE-05, SEC-08, SEC-09, ADMIN-06, DOC-01, DOC-02
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. A user can create, rename, and delete folders; moving a document between folders preserves its metadata and AI classification; deleting a non-empty folder prompts with the content count before proceeding
|
|
||||||
2. A user can share a document with another user by handle; the recipient sees it appear in a "Shared with me" virtual folder with no storage quota charged against them; the owner can revoke access and the shared entry disappears immediately for the recipient
|
|
||||||
3. The sidebar quota bar displays current usage in MB; it turns amber at 80% and red at 95%; an upload that would exceed the limit is rejected with an error showing current usage, the rejected file size, and a link to storage settings
|
|
||||||
4. Any document in the user's library can be previewed in-browser as a PDF; document bytes are proxied through the app and no presigned URLs are exposed to the browser (native browser PDF rendering via Content-Type header)
|
|
||||||
5. An admin can view the audit log filtered by date range, user, and action type; the log contains no document content, filenames, or extracted text; account deletion triggers cleanup of all user files before DB records are removed
|
|
||||||
|
|
||||||
**Plans**: 9 plans
|
|
||||||
|
|
||||||
**Wave 1** — Test scaffolds + DB migration (parallel)
|
|
||||||
|
|
||||||
- [x] 04-01-PLAN.md — Wave 0 test stubs: test_folders.py + test_shares.py + test_audit.py + proxy stubs in test_documents.py + SEC-08/SEC-09 stubs in test_security.py
|
|
||||||
- [x] 04-02-PLAN.md — Alembic migration 0004 (users.pdf_open_mode, GIN FTS index, audit-logs bucket) + MinIOBackend.put_object_raw()
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1)*
|
|
||||||
|
|
||||||
- [x] 04-03-PLAN.md — Audit service (write_audit_log) + Folders API (FOLD-01..05): POST/GET/PATCH/DELETE /api/folders + PATCH /api/documents/{id}/folder + document list sort/search/is_shared extension
|
|
||||||
- [ ] 04-04-PLAN.md — Shares API (SHARE-01..05): POST/GET /api/shares + GET /api/shares/received + DELETE /api/shares/{id} with IDOR protection
|
|
||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2)*
|
|
||||||
|
|
||||||
- [ ] 04-05-PLAN.md — PDF streaming proxy GET /api/documents/{id}/content with Range header support + PATCH /api/auth/me/preferences (pdf_open_mode)
|
|
||||||
- [ ] 04-06-PLAN.md — Admin audit log API (GET /api/admin/audit-log, CSV export) + Celery beat daily audit export task + celery_app.py beat schedule
|
|
||||||
|
|
||||||
**Wave 4** *(blocked on Wave 3)*
|
|
||||||
|
|
||||||
- [ ] 04-07-PLAN.md — SEC-08/SEC-09 hardening + audit log backfill into auth.py/admin.py/documents.py + CloudConnectionOut Pydantic model + delete-user file cleanup
|
|
||||||
|
|
||||||
**Wave 5** *(blocked on Wave 4)*
|
|
||||||
|
|
||||||
- [ ] 04-08-PLAN.md — Frontend data layer: API client functions + useFoldersStore + documents store extension + Vue Router routes (/folders/:folderId, /shared)
|
|
||||||
|
|
||||||
**Wave 6** *(blocked on Wave 5)*
|
|
||||||
|
|
||||||
- [ ] 04-09-PLAN.md — Frontend UI: all new components (FolderRow, FolderBreadcrumb, FolderDeleteModal, ShareModal, DocumentPreviewModal, SearchBar, SortControls, AuditLogTab) + view wiring (AppSidebar, DocumentCard, HomeView, FolderView, SharedView, SettingsView, AdminView) + human checkpoint
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 4 is complete):**
|
|
||||||
|
|
||||||
- [ ] `pytest -v` — zero failures; folder ownership, share revocation, quota bar, PDF proxy (no presigned URL exposure) all covered
|
|
||||||
- [ ] Security agent: audit log verified to contain zero document content; sharing IDOR tests; PDF proxy verified to not leak presigned URLs or object keys
|
|
||||||
- [ ] Bandit + pip audit + npm audit all clean
|
|
||||||
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 5: Cloud Storage Backends
|
|
||||||
|
|
||||||
**Goal**: Users can connect OneDrive, Google Drive, Nextcloud, or a generic WebDAV server as a personal storage backend; credentials are encrypted with a per-user HKDF-derived key; connection status is visible; local and cloud storage coexist; the `StorageBackend` ABC makes adding further backends straightforward.
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 4
|
|
||||||
**Requirements**: CLOUD-01, CLOUD-02, CLOUD-03, CLOUD-04, CLOUD-05, CLOUD-06, CLOUD-07
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. A user can connect OneDrive, Google Drive, Nextcloud, or a WebDAV endpoint through an OAuth or credential flow; the connection status is displayed as `ACTIVE`, `REQUIRES_REAUTH`, or `ERROR` — never shows raw credentials
|
|
||||||
2. When an OAuth token is revoked externally (simulated `invalid_grant` response), the connection status transitions to `REQUIRES_REAUTH` without a 500 error; the user is shown a re-authentication prompt
|
|
||||||
3. A user can select their connected cloud backend as the default storage destination for new uploads; local MinIO storage remains available as an alternative; existing local documents are unaffected
|
|
||||||
4. A user can disconnect a cloud backend; credentials are permanently deleted from the DB and a subsequent attempt to use that backend returns an appropriate error — no orphaned data remains
|
|
||||||
5. An admin API response for a user's cloud connections returns only `provider, display_name, connected_at, status` — the `credentials_enc` column is never present in any serialized response
|
|
||||||
|
|
||||||
**Plans**: 12 plans (8 original + 3 UAT gap closure + 1 gap closure wave)
|
|
||||||
|
|
||||||
**Wave 1** — Test scaffold + dependencies
|
|
||||||
|
|
||||||
- [x] 05-01-PLAN.md — Wave 0 xfail stubs, conftest cloud fixtures, requirements.txt packages, config.py settings
|
|
||||||
|
|
||||||
**Wave 2** — Shared utilities
|
|
||||||
|
|
||||||
- [x] 05-02-PLAN.md — cloud_utils.py (SSRF + HKDF), cloud_cache.py (TTLCache), storage factory extension
|
|
||||||
|
|
||||||
**Wave 3** — Cloud backends (parallel, both blocked on Wave 2 / Plan 05-02)
|
|
||||||
|
|
||||||
- [x] 05-03-PLAN.md — GoogleDriveBackend + OneDriveBackend (all 7 StorageBackend methods)
|
|
||||||
- [x] 05-04-PLAN.md — NextcloudBackend + WebDAVBackend (all 7 StorageBackend methods)
|
|
||||||
|
|
||||||
**Wave 4** — Cloud API
|
|
||||||
|
|
||||||
- [x] 05-05-PLAN.md — All /api/cloud/* endpoints + /api/users/me/default-storage + main.py router registration
|
|
||||||
|
|
||||||
**Wave 5** — Document routing + full test suite
|
|
||||||
|
|
||||||
- [x] 05-06-PLAN.md — Upload/content proxy cloud routing + all 15 tests promoted to passing
|
|
||||||
|
|
||||||
**Wave 6** — Frontend settings UI
|
|
||||||
|
|
||||||
- [x] 05-07-PLAN.md — cloudConnections store + API client + SettingsView 3-tab + SettingsCloudTab + CloudCredentialModal
|
|
||||||
|
|
||||||
**Wave 7** — Frontend sidebar (human checkpoint)
|
|
||||||
|
|
||||||
- [x] 05-08-PLAN.md — AppSidebar cloud section + CloudProviderTreeItem + CloudFolderTreeItem + human checkpoint
|
|
||||||
|
|
||||||
**Wave 8** — UAT gap closure (parallel, all independent)
|
|
||||||
|
|
||||||
- [x] 05-09-PLAN.md — Cloud document open/re-analyze/edit: authenticated fetch+Blob URL, cloud-aware Celery task, PATCH /api/documents/{id}
|
|
||||||
- [x] 05-10-PLAN.md — OAuth initiate fix (JSON response), Nextcloud custom endpoint edit round-trip, Edit button on ERROR rows, confirmation text overflow
|
|
||||||
- [x] 05-11-PLAN.md — Admin hard-delete with password confirmation: UserDeleteConfirm backend model + inline frontend panel
|
|
||||||
|
|
||||||
**Wave 9** — Post-UAT gap closure
|
|
||||||
|
|
||||||
- [x] 05-12-PLAN.md — OAuth 400 preflight (unconfigured creds), 502 cloud fallback, upload hint in CloudStorageView, celery-worker volume mount
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 5 is complete):**
|
|
||||||
|
|
||||||
- [x] `pytest -v` — zero failures; SSRF prevention on WebDAV/Nextcloud user-supplied URLs; credential encryption/decryption round-trip; admin response never exposes `credentials_enc`; OAuth invalid_grant handling
|
|
||||||
- [x] Security agent: SSRF allowlist verification; credential key derivation correctness; connection status never leaks raw credential values
|
|
||||||
- [x] Bandit + pip audit + npm audit all clean
|
|
||||||
- [x] UAT gaps resolved and re-tested (05-09, 05-10, 05-11, 05-12)
|
|
||||||
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 6: Performance & Production Hardening
|
|
||||||
|
|
||||||
**Goal**: The application is ready for production deployment — observable, load-tested, and hardened; response times meet SLA targets under concurrent load; all auth and document endpoints are rate-limited; structured logging and distributed tracing are in place; the Docker image runs as a non-root user with a read-only filesystem.
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 5
|
|
||||||
**Requirements**: TBD
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. All API endpoints respond within defined latency targets (p50/p95/p99) under a realistic load test (e.g., 50 concurrent users, 5-minute soak)
|
|
||||||
2. Structured JSON logging (correlation IDs, user ID, request latency) is emitted to stdout; a local log aggregation stack (Loki or similar) captures and queries them
|
|
||||||
3. All auth endpoints (login, register, password reset, TOTP) enforce per-IP and per-account rate limits that cannot be bypassed by header manipulation
|
|
||||||
4. Container hardening is complete: non-root user, read-only root filesystem, dropped Linux capabilities; `docker scout` or equivalent reports zero critical CVEs
|
|
||||||
5. A runbook documents all environment variables, startup/shutdown procedures, backup strategy, and on-call escalation path; the app can be stood up from scratch using only the runbook
|
|
||||||
|
|
||||||
**Plans**: 6 plans (4 waves)
|
|
||||||
|
|
||||||
**Wave 0** — Test scaffolds + package verification
|
|
||||||
|
|
||||||
- [x] 06-01-PLAN.md — Nyquist Wave 0: xfail stubs (test_logging.py, test_rate_limiting.py), Locust skeleton, package legitimacy checkpoint (D-01, D-04, D-11, D-12)
|
|
||||||
|
|
||||||
**Wave 1** *(blocked on Wave 0 completion)*
|
|
||||||
|
|
||||||
- [x] 06-02-PLAN.md — structlog JSON logging + CorrelationIDMiddleware + Loki/Promtail/Grafana Docker Compose stack (D-01, D-02, D-03)
|
|
||||||
- [x] 06-03-PLAN.md — Locust locustfile.py with JWT auth + SLA gate listener (D-04, D-05, D-06)
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1 completion)*
|
|
||||||
|
|
||||||
- [x] 06-04-PLAN.md — Multi-stage Dockerfile + read_only/tmpfs/cap_drop on backend + celery-worker (D-07, D-08, D-09)
|
|
||||||
- [x] 06-05-PLAN.md — Trusted-proxy get_client_ip body + per-account rate limiter on document/cloud endpoints (D-11, D-12, D-13)
|
|
||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2 completion)*
|
|
||||||
|
|
||||||
- [x] 06-06-PLAN.md — docker scout CVE gate + RUNBOOK.md (D-10, D-14)
|
|
||||||
|
|
||||||
**Cross-cutting constraints:**
|
|
||||||
|
|
||||||
- get_client_ip lives ONLY in backend/deps/utils.py — replace body in-place, no new function (Plans 05)
|
|
||||||
- celery-beat intentionally excluded from read_only: true (Plan 04)
|
|
||||||
- locust must NOT be in requirements.txt — use requirements-dev.txt (Plan 03)
|
|
||||||
- CorrelationIDMiddleware registered LAST in main.py — Starlette reverse order (Plan 02)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 6.1: Close v1.0 audit gaps: SHARE-02/STORE-06/ADMIN-06
|
|
||||||
|
|
||||||
**Goal**: Close three v1.0 requirements that remain unimplemented — atomic quota decrement on document delete (STORE-06), "Shared with me" virtual folder without recipient quota charge (SHARE-02), and admin audit log viewer with date/user/action type filters (ADMIN-06).
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 6
|
|
||||||
**Requirements**: STORE-06, SHARE-02, ADMIN-06
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. Deleting a document atomically decrements the owning user's quota; after deletion the quota reflects the freed bytes with no race condition under concurrent deletes
|
|
||||||
2. A user who receives a shared document sees it appear in a "Shared with me" virtual folder; the recipient's quota usage is not charged for the shared document's storage
|
|
||||||
3. An admin can view the audit log filtered independently by date range, user, and action type; filtered results contain no document content, filenames, or extracted text
|
|
||||||
|
|
||||||
**Plans**: 2 plans
|
|
||||||
|
|
||||||
**Wave 1** — Test promotion (parallel)
|
|
||||||
|
|
||||||
- [x] 06.1-01-PLAN.md — Promote test_shares.py stubs to real tests + second_auth_user fixture (SHARE-01..05)
|
|
||||||
- [x] 06.1-02-PLAN.md — Promote test_audit.py stubs to real tests (ADMIN-06)
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 6.1 is complete):**
|
|
||||||
|
|
||||||
- [ ] `pytest -v` — zero failures; all 7 share tests + 4 audit log tests passing
|
|
||||||
- [ ] Security agent: bandit + pip audit + npm audit all clean
|
|
||||||
- [ ] STORE-06 confirmed: `test_delete_decrements_quota` passes under `INTEGRATION=1`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 6.2: Close v1 sharing + cloud-delete + CSV export gaps
|
|
||||||
|
|
||||||
**Goal**: Close remaining v1 gaps — sharing edge cases (SHARE-03/SHARE-05), cloud document deletion propagation to the remote backend, and CSV export + daily export UI for the admin audit log (ADMIN-06).
|
|
||||||
**Mode:** mvp
|
|
||||||
**Depends on**: Phase 6.1
|
|
||||||
**Requirements**: SHARE-03, SHARE-05, ADMIN-06
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. Documents shared with others display a "Shared" badge in the owner's list view (reads doc.is_shared, not doc.share_count)
|
|
||||||
2. Owner can set permission to "view" or "edit" when creating a share and toggle it per-recipient afterward; PATCH /api/shares/{id} enforces IDOR protection (404 on wrong owner)
|
|
||||||
3. Deleting a cloud document propagates the delete to the cloud provider; failure shows a warning modal with "Remove from app" fallback; ?remove_only=true removes only the DB record; cloud docs never affect quota on delete
|
|
||||||
4. Admin can download filtered audit log CSV via fetch+Blob (not window.location.href); audit log entries show user handles instead of raw UUIDs; user filter accepts handles (not UUIDs)
|
|
||||||
5. Admin can list and download Celery-generated daily audit export files from a new section in the Audit Log tab
|
|
||||||
|
|
||||||
**Plans**: 4 plans
|
|
||||||
|
|
||||||
**Wave 0** — Test stubs
|
|
||||||
|
|
||||||
- [x] 06.2-01-PLAN.md — 11 xfail stubs across test_shares.py, test_documents.py, test_audit.py
|
|
||||||
|
|
||||||
**Wave 1** — Feature slices (parallel)
|
|
||||||
|
|
||||||
- [x] 06.2-02-PLAN.md — SHARE-05 badge fix + SHARE-03 permission control (backend PATCH + frontend dropdown + toggle)
|
|
||||||
- [x] 06.2-03-PLAN.md — Cloud-delete propagation + structured error response + remove_only path + DocumentView warning modal
|
|
||||||
|
|
||||||
**Wave 2** — Audit log enrichment
|
|
||||||
|
|
||||||
- [x] 06.2-04-PLAN.md — Audit handle JOIN + user_handle filter + CSV fetch+Blob fix + daily-export list + download endpoints + AuditLogTab UI
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 6.2 is complete):**
|
|
||||||
|
|
||||||
- [x] `pytest -v` — 344 passed, 1 pre-existing unrelated failure (test_extract_docx missing module)
|
|
||||||
- [x] Security agent: bandit + pip audit + npm audit all clean (SECURITY.md threats_open: 0)
|
|
||||||
- [x] IDOR on PATCH /api/shares/{id}: test_share_patch_idor passes
|
|
||||||
- [x] Date regex validation confirmed: GET /api/admin/audit-log/daily-exports/invalid-date returns 404
|
|
||||||
- [x] window.location.href removed from AuditLogTab.vue confirmed by grep
|
|
||||||
|
|
||||||
**Status: ✓ Complete (2026-06-01)**
|
|
||||||
|
|
||||||
### Phase 7: Redo and optimize LLM integration
|
|
||||||
|
|
||||||
**Goal:** A fully refactored, production-reliable AI provider layer: AI provider settings live in a new `system_settings` DB table (replacing env-only config), API keys encrypted at rest via HKDF/AES-GCM (same pattern as cloud credentials), all OpenAI-compatible providers (Groq, xAI, DeepSeek, OpenRouter, Gemini-compat, Mistral-compat, Ollama, LMStudio) handled by a single `GenericOpenAIProvider` class, Anthropic uses native `output_config.format.type="json_schema"` structured output, JSON-mode enforced everywhere with `parse_classification()` as fallback, Celery exponential-backoff retry (30s/90s/270s) replaces silent failure on classification errors, per-provider context window size with smart 60/40 truncation replaces the global `MAX_AI_CHARS`, the singleton `_client` pattern restores httpx connection-pool reuse, an admin AI Providers panel allows interactive configuration plus test-connection, and a "Re-analyze" button on the document card re-queues failed classifications.
|
|
||||||
**Mode:** standard
|
|
||||||
**Depends on:** Phase 6.2
|
|
||||||
**Requirements**: D-01..D-18 (CONTEXT.md decisions; no REQ-IDs yet mapped — phase introduces new infrastructure)
|
|
||||||
|
|
||||||
**Success Criteria** (what must be TRUE):
|
|
||||||
|
|
||||||
1. AI provider settings live in `system_settings`; admin can view, edit, and atomically activate any provider via `PUT /api/admin/ai-config`; `GET /api/admin/ai-config` never returns `api_key_enc` or any decrypted key value
|
|
||||||
2. A document whose classification raises an exception is automatically retried by Celery at 30 s, 90 s, and 270 s; after the third failure the document's status remains `classification_failed` and no further retries occur
|
|
||||||
3. All OpenAI-compatible providers route through `GenericOpenAIProvider`; classify/suggest pass `response_format={"type":"json_object"}` when `supports_json_mode` is True (Gemini preset is False and falls back to `parse_classification()`); Anthropic uses `output_config.format.type="json_schema"` with a constrained schema
|
|
||||||
4. `MAX_AI_CHARS` no longer exists in `openai_provider.py`, `anthropic_provider.py`, or `classifier.py`; each provider truncates input text using its own `context_chars` value via a 60% head + 40% tail strategy
|
|
||||||
5. A failed document shows a red "Classification failed" badge and a "Re-analyze" button on the document card; clicking the button calls `POST /api/documents/{id}/classify`, which sets the document to `processing` and re-queues the Celery task
|
|
||||||
|
|
||||||
**Plans**: 5 plans (5 waves)
|
|
||||||
|
|
||||||
**Wave 1** — Foundation: migration, ORM model, encryption helpers, Wave 0 test stubs
|
|
||||||
|
|
||||||
- [x] 07-01-PLAN.md — Alembic migration 0005 (system_settings table) + SystemSettings ORM model + services/ai_config.py (HKDF helpers + load_provider_config + env seed on startup) + Wave 0 xfail stubs for D-01..D-16 (test_ai_providers.py, test_ai_config.py, test_admin_ai_config.py, test_document_tasks.py)
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1)* — Provider layer: ProviderConfig, GenericOpenAIProvider, singleton client fix, registry
|
|
||||||
|
|
||||||
- [x] 07-02-PLAN.md — ProviderConfig Pydantic model + PROVIDER_DEFAULTS + SUPPORTS_JSON_MODE + GenericOpenAIProvider(OpenAIProvider) + OpenAIProvider singleton refactor + ollama/lmstudio context_chars + MAX_AI_CHARS removal from openai_provider.py + classifier.py + registry-based ai/__init__.py get_provider(config: ProviderConfig) + anthropic>=0.95.0 pin
|
|
||||||
|
|
||||||
**Wave 3** *(blocked on Wave 2)* — Anthropic + Classifier wiring
|
|
||||||
|
|
||||||
- [x] 07-03-PLAN.md — AnthropicProvider singleton + output_config json_schema + smart truncation + MAX_AI_CHARS removal + classifier.classify_document driven by load_provider_config (no inline _settings dict) + ai_config stub removed in favor of real ProviderConfig + load_provider_config_by_id helper
|
|
||||||
|
|
||||||
**Wave 4** *(blocked on Wave 3)* — Celery retry harness + Re-queue endpoint
|
|
||||||
|
|
||||||
- [x] 07-04-PLAN.md — Celery extract_and_classify decorator gains bind=True + max_retries=3 + _ClassificationError sentinel + 30/90/270 backoff + _mark_classification_failed on exhaustion + POST /api/documents/{id}/classify changes to re-queue Celery (sets status=processing, calls .delay()) — promotes test_retry_backoff, test_exhaustion_sets_failed_status, test_reclassify_requeues_celery
|
|
||||||
|
|
||||||
**Wave 5** *(blocked on Wave 4)* — Admin UI + Frontend badge
|
|
||||||
|
|
||||||
- [x] 07-05-PLAN.md — Admin AI-config backend (GET/PUT/test-connection with whitelist + audit logging + atomic is_active flip) + frontend API client helpers (getAiConfig/saveAiConfig/testAiConnection) + AdminAiConfigTab.vue global System AI Providers section ABOVE existing per-user table + DocumentCard.vue classification_failed badge + Re-analyze button + human checkpoint UAT
|
|
||||||
|
|
||||||
**Cross-cutting constraints:**
|
|
||||||
|
|
||||||
- `api_key_enc` is never returned by any admin endpoint — enforced by `_ai_config_to_dict()` whitelist (Plan 05)
|
|
||||||
- HKDF domain separation: AI settings use `info=b"ai-provider-settings"`, cloud credentials use `info=b"cloud-credentials"` (Plan 01)
|
|
||||||
- `is_active` flip is atomic: single `UPDATE SET is_active = (provider_id = $target)` — never read-then-write (Plan 05)
|
|
||||||
- Provider clients are stored as `self._client` in `__init__` — never recreated per call (Plans 02, 03)
|
|
||||||
- `MAX_AI_CHARS` removal must cover all three locations: openai_provider.py L5, anthropic_provider.py L5, classifier.py L28 (Plans 02 + 03)
|
|
||||||
- Celery `self.retry()` must be raised from the outer sync task body, never inside `asyncio.run()` (Plan 04 — Pitfall 3)
|
|
||||||
- `extra_hosts: ["host.docker.internal:host-gateway"]` already present in docker-compose.yml — D-14 is already done; no docker-compose changes required (RESEARCH.md)
|
|
||||||
- AdminAiConfigTab.vue per-user assignment table is preserved untouched; the new global system section is added ABOVE it (Plan 05 — Pitfall 6)
|
|
||||||
|
|
||||||
**Phase gates (must pass before Phase 7 is complete):**
|
|
||||||
|
|
||||||
- [x] `pytest -v` — zero failures; D-01/D-03/D-05/D-06/D-07/D-09/D-10/D-11/D-12/D-13/D-16 covered by promoted tests (347 passed, 1 pre-existing failure test_extract_docx missing module)
|
|
||||||
- [x] Security agent: bandit -r backend/ (zero HIGH), npm audit --audit-level=high (2 moderate esbuild/vite dev-only — no high/critical); pip-audit not runnable locally (Python 3.9 vs 3.12 requirements), inherited clean gate from Phase 6.2
|
|
||||||
- [x] `_ai_config_to_dict()` confirmed to never include `api_key_enc` (test_get_never_returns_key passes; whitelist at admin.py:62)
|
|
||||||
- [x] HKDF domain separation verified: test_encrypt_api_key_domain_isolation passes (test_ai_config.py:21)
|
|
||||||
- [x] Atomic `is_active` flip verified: test_set_active_provider_atomic passes (test_admin_ai_config.py:77)
|
|
||||||
- [x] Human checkpoint UAT approves admin panel + DocumentCard badge end-to-end (07-UAT.md: 11/11 passed 2026-06-05)
|
|
||||||
|
|
||||||
**UI hint**: yes
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 7.1: Security: session revocation on privilege change (CR-01..03) (INSERTED)
|
|
||||||
|
|
||||||
**Goal**: Fix the three missing session-revocation calls in `backend/api/auth.py`: `change_password`, `enable_totp`, and `disable_totp` must all call `revoke_all_refresh_tokens()` (excluding the current session). Add `sessions_revoked` to their response shapes and a frontend toast when other sessions are terminated.
|
|
||||||
**Mode:** quick
|
|
||||||
**Depends on**: Phase 7
|
|
||||||
**Requirements**: CR-01, CR-02, CR-03
|
|
||||||
|
|
||||||
**Plans**: 2 plans
|
|
||||||
|
|
||||||
**Wave 1** — Backend: service + API changes
|
|
||||||
|
|
||||||
- [ ] 07.1-01-PLAN.md — Add skip_token_hash param to revoke_all_refresh_tokens + wire revoke into change_password, enable_totp, disable_totp with sessions_revoked response + audit log
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1)*
|
|
||||||
|
|
||||||
- [ ] 07.1-02-PLAN.md — Tests (3 new test_*_revokes_other_sessions) + frontend toast in SettingsAccountTab.vue + TotpEnrollment.vue
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 7.2: Security — JTI Claim + Redis Access-Token Revocation (INSERTED)
|
|
||||||
|
|
||||||
**Goal**: Add a `jti` (JWT ID) claim to every issued access token. In `get_current_user`, check `redis.get("jti_revoked:{jti}")` and raise 401 if set. Add `revoke_access_token(jti, ttl)` helper called from `change_password`, `enable_totp`, `disable_totp`, and admin account deactivation. Closes the 15-minute window where a revoked session's live access token remains valid.
|
|
||||||
**Mode:** quick
|
|
||||||
**Depends on**: Phase 7.1
|
|
||||||
**Requirements**: Tracked in `.planning/codebase/CONCERNS.md` §"No JTI Claim and No JTI Revocation in Redis"
|
|
||||||
|
|
||||||
**Status:** Not planned yet
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 7.3: Security — ES256 Algorithm Upgrade (INSERTED)
|
|
||||||
|
|
||||||
**Goal**: Replace HS256 with ES256 (ECDSA P-256) for JWT signing. Generate a P-256 key pair; store private key in `JWT_PRIVATE_KEY` env var, public key in `JWT_PUBLIC_KEY`. Update `create_access_token` and all `decode_*` functions. Rotate all active refresh tokens on first boot after deploy. A leaked public key cannot forge tokens.
|
|
||||||
**Mode:** quick
|
|
||||||
**Depends on**: Phase 7.2
|
|
||||||
**Requirements**: Tracked in `.planning/codebase/CONCERNS.md` §"JWT Algorithm Downgrade: HS256 Instead of ES256"
|
|
||||||
|
|
||||||
**Plans**: 3 plans
|
|
||||||
|
|
||||||
**Wave 0** — Test scaffolds (no production code)
|
|
||||||
|
|
||||||
- [x] 07.3-01-PLAN.md — Wave 0 xfail stubs: test_auth_es256.py (9 stubs covering ES256-01..05 + RM-01..03 + CFG-01 satellite) + extend test_settings_has_jwt_config for refresh_token_expire_hours
|
|
||||||
|
|
||||||
**Wave 1** *(blocked on Wave 0)* — ES256 core + startup rotation
|
|
||||||
|
|
||||||
- [x] 07.3-02-PLAN.md — config.py jwt_private_key/jwt_public_key/refresh_token_expire_hours + services/auth.py 4 sites to ES256 + main.py _rotate_tokens_on_algorithm_change lifespan hook + docker-compose JWT_PRIVATE_KEY/JWT_PUBLIC_KEY + README key-gen snippet + .env.example + version bump 0.1.2
|
|
||||||
|
|
||||||
**Wave 2** *(blocked on Wave 1)* — "Remember me" 16h/30d TTL split
|
|
||||||
|
|
||||||
- [x] 07.3-03-PLAN.md — create_refresh_token remember_me param + LoginRequest.remember_me + _set_refresh_cookie max_age conditional + LoginView.vue "Stay signed in for 30 days" checkbox + stores/auth.js + api/client.js forwarding + human checkpoint
|
|
||||||
|
|
||||||
**Status:** Complete (2026-06-06)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Phase 7.4: Security — Token Fingerprinting / Token Binding (INSERTED)
|
|
||||||
|
|
||||||
**Goal**: Add a `fgp` (fingerprint) claim = `hmac(key, User-Agent + Accept-Language)[:16]` to every issued access token. In `get_current_user`, recompute the fingerprint from the request headers and compare with `hmac.compare_digest`. Limits replay of stolen access tokens to the original device/browser context.
|
|
||||||
**Mode:** quick
|
|
||||||
**Depends on**: Phase 7.3
|
|
||||||
**Requirements**: Tracked in `.planning/codebase/CONCERNS.md` §"No Token Fingerprint / Token Binding"
|
|
||||||
|
|
||||||
**Plans**: 2 plans
|
|
||||||
|
|
||||||
**Wave 0** — Test scaffolds (no production code)
|
|
||||||
|
|
||||||
- [x] 07.4-01-PLAN.md — Wave 0 xfail stubs: test_auth_fgp.py (4 stubs covering FGP-01..04)
|
|
||||||
|
|
||||||
**Wave 1** *(blocked on Wave 0)* — Production implementation + test promotion
|
|
||||||
|
|
||||||
- [x] 07.4-02-PLAN.md — _compute_fgp helper + create_access_token fgp claim + get_current_user fgp validation + login/refresh call site updates + promote all 4 FGP tests
|
|
||||||
|
|
||||||
**Status:** Complete (2026-06-06)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Milestones
|
|
||||||
|
|
||||||
- ✅ **v0.2 — UI Overhaul and Optimization** — Phases 8–11 (shipped 2026-06-17)
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>✅ v0.2 — UI Overhaul and Optimization (Phases 8–11) — SHIPPED 2026-06-17</summary>
|
|
||||||
|
|
||||||
- [x] Phase 8: Stack Upgrade & Backend Decomposition (8/8 plans) — completed 2026-06-12
|
|
||||||
- [x] Phase 9: Admin Panel Rearchitecture (5/5 plans) — completed 2026-06-13
|
|
||||||
- [x] Phase 10: UX & Interaction (13/13 plans) — completed 2026-06-16
|
|
||||||
- [x] Phase 11: Visual Design, Responsive Layout & Cleanup (7/7 plans) — completed 2026-06-17
|
|
||||||
|
|
||||||
Full archive: `.planning/milestones/v0.2-ROADMAP.md`
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## v0.2 + v0.1 Progress Table
|
|
||||||
|
|
||||||
| Phase | Milestone | Plans Complete | Status | Completed |
|
|
||||||
|-------|-----------|----------------|--------|-----------|
|
|
||||||
| 8. Stack Upgrade & Backend Decomposition | v0.2 | 8/8 | Complete | 2026-06-12 |
|
|
||||||
| 9. Admin Panel Rearchitecture | v0.2 | 5/5 | Complete | 2026-06-13 |
|
|
||||||
| 10. UX & Interaction | v0.2 | 13/13 | Complete | 2026-06-16 |
|
|
||||||
| 11. Visual Design, Responsive Layout & Cleanup | v0.2 | 7/7 | Complete | 2026-06-17 |
|
|
||||||
| 1. Infrastructure Foundation | v0.1 | 5/5 | Complete | 2026-05-22 |
|
|
||||||
| 2. Users & Authentication | v0.1 | 6/6 | Complete | 2026-06-01 |
|
|
||||||
| 3. Document Migration & Multi-User Isolation | v0.1 | 5/5 | Complete | 2026-05-25 |
|
|
||||||
| 4. Folders, Sharing, Quotas & Document UX | v0.1 | 9/9 | Complete | 2026-05-28 |
|
|
||||||
| 5. Cloud Storage Backends | v0.1 | 12/12 | Complete | 2026-05-30 |
|
|
||||||
| 6. Performance & Production Hardening | v0.1 | 6/6 | Complete | 2026-05-30 |
|
|
||||||
| 6.1. Close v1.0 audit gaps | v0.1 | 2/2 | Complete | 2026-05-30 |
|
|
||||||
| 6.2. Close v1 sharing + cloud-delete + CSV export gaps | v0.1 | 5/5 | Complete | 2026-05-31 |
|
|
||||||
| 7. Redo and optimize LLM integration | v0.1 | 5/5 | Complete | 2026-06-05 |
|
|
||||||
| 7.1. Security: session revocation on privilege change | v0.1 | 2/2 | Complete | 2026-06-08 |
|
|
||||||
| 7.2. Security: JTI claim + Redis access-token revocation | v0.1 | 3/3 | Complete | 2026-06-05 |
|
|
||||||
| 7.3. Security: ES256 algorithm upgrade | v0.1 | 3/3 | Complete | 2026-06-06 |
|
|
||||||
| 7.4. Security: token fingerprinting / token binding | v0.1 | 2/2 | Complete | 2026-06-06 |
|
|
||||||
+81
-25
@@ -1,47 +1,67 @@
|
|||||||
---
|
---
|
||||||
gsd_state_version: 1.0
|
gsd_state_version: 1.0
|
||||||
milestone: v0.2
|
milestone: v0.3
|
||||||
milestone_name: UI Overhaul and Optimization
|
milestone_name: Reimagining Cloud Storage integration
|
||||||
current_phase: 11
|
current_phase: 13
|
||||||
|
current_phase_name: virtual-local-cloud-operations
|
||||||
status: complete
|
status: complete
|
||||||
last_updated: "2026-06-17"
|
stopped_at: Phase 14 context gathered
|
||||||
last_activity: 2026-06-17 -- v0.2 milestone archived
|
last_updated: "2026-06-23T12:04:31.598Z"
|
||||||
|
last_activity: 2026-06-23
|
||||||
|
last_activity_desc: Phase 13 complete, v0.3.0 shipped
|
||||||
progress:
|
progress:
|
||||||
total_phases: 4
|
total_phases: 6
|
||||||
completed_phases: 4
|
completed_phases: 3
|
||||||
total_plans: 33
|
total_plans: 21
|
||||||
completed_plans: 33
|
completed_plans: 21
|
||||||
percent: 100
|
percent: 50
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
|
|
||||||
**Project:** DocuVault
|
**Project:** DocuVault
|
||||||
**Status:** v0.2 milestone complete — ready for next milestone
|
**Status:** Phase 13 complete — ready to begin Phase 14
|
||||||
**Last Updated:** 2026-06-17
|
**Last Updated:** 2026-06-23
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Milestone v0.2 shipped 2026-06-17. All 4 phases complete, 40/40 requirements satisfied.
|
Phase: 13 (virtual-local-cloud-operations) — COMPLETE
|
||||||
Next action: `/gsd:new-milestone` to define v0.3
|
Plan: 11 of 11
|
||||||
|
Status: Phase 13 complete. Next: Phase 14 (Selective Analysis and Byte Cache)
|
||||||
|
Last activity: 2026-06-23 — Phase 13 complete, v0.3.0 shipped
|
||||||
|
|
||||||
## Phase Status
|
## Phase Status
|
||||||
|
|
||||||
| Phase | Requirements | Status |
|
| Phase | Requirements | Status |
|
||||||
|-------|-------------|--------|
|
|-------|-------------|--------|
|
||||||
| 8. Stack Upgrade & Backend Decomposition | PERF-01, CODE-01, CODE-02, CODE-03, CODE-04, CODE-08 | **Complete (8/8 plans)** |
|
| 12. Cloud Resource Foundation | CONN-04, CLOUD-01, CLOUD-08, CACHE-01, CACHE-02, SYNC-01 | **Complete** |
|
||||||
| 9. Admin Panel Rearchitecture | ADMIN-08..12, CODE-06, CODE-09 | **Complete (5/5 plans)** |
|
| 12.1 Fix Nextcloud Root Listing and Sync Visibility | CONN-04, CLOUD-01, CACHE-01, SYNC-01 | **Complete** |
|
||||||
| 10. UX & Interaction | UX-01..14, CODE-05 | **Complete (13/13 plans)** |
|
| 13. Virtual-Local Cloud Operations | CONN-01..03, CLOUD-02..07, CLOUD-09 | **Complete** |
|
||||||
| 11. Visual Design, Responsive Layout & Cleanup | VISUAL-01..04, RESP-01..05, CODE-07, PERF-02, PERF-03 | **Complete (7/7 plans)** |
|
| 14. Selective Analysis and Byte Cache | ANALYZE-01..07, CACHE-03..05 | **Not started** |
|
||||||
|
| 15. Unified Smart Search | SEARCH-01..07 | **Not started** |
|
||||||
|
| 16. Change Tracking and Reliability | SYNC-02..04 | **Not started** |
|
||||||
|
|
||||||
## Performance Metrics
|
## Performance Metrics
|
||||||
|
|
||||||
| Metric | Value |
|
| Metric | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Phases complete | 4 / 4 |
|
| Phases complete | 3 / 6 |
|
||||||
| Requirements satisfied | 40 / 40 |
|
| Requirements satisfied | 17 / 36 |
|
||||||
| Plans complete | 33 / 33 |
|
| Plans complete | 21 / 21 |
|
||||||
| Tests at close | 277 |
|
| Tests at milestone start | 277 |
|
||||||
|
| Phase 12.1 P01 | 823s | 4 tasks | 14 files |
|
||||||
|
| Phase 12.1 P02 | 2100s | 3 tasks | 13 files |
|
||||||
|
| Phase 12.1 P03 | 677s | 4 tasks | 11 files |
|
||||||
|
| Phase 12.1 P04 | 15m | 4 tasks | 10 files |
|
||||||
|
| Phase 13 P02 | 30m | 2 tasks | 6 files |
|
||||||
|
| Phase 13 P03 | 134s | 2 tasks | 8 files |
|
||||||
|
| Phase 13 P04 | 180m | 2 tasks | 7 files |
|
||||||
|
| Phase 13 P06 | 5m | 2 tasks | 3 files |
|
||||||
|
| Phase 13 P07 | 30m | 2 tasks | 5 files |
|
||||||
|
| Phase 13 P08 | 20m | 2 tasks | 2 files |
|
||||||
|
| Phase 13 P09 | 30m | 2 tasks | 3 files |
|
||||||
|
| Phase 13 P10 | 25m | 2 tasks | 6 files |
|
||||||
|
| Phase 13 P11 | 20m | 2 tasks | 6 files |
|
||||||
|
|
||||||
## Accumulated Context
|
## Accumulated Context
|
||||||
|
|
||||||
@@ -60,12 +80,18 @@ Next action: `/gsd:new-milestone` to define v0.3
|
|||||||
| to.matched.some() for requiresAdmin guard | Vue Router 4 does not inherit meta to children; direct to.meta check is a security regression |
|
| to.matched.some() for requiresAdmin guard | Vue Router 4 does not inherit meta to children; direct to.meta check is a security regression |
|
||||||
| FastAPI 0.128+ empty-path sub-router restriction | `@router.get("")` on sub-router with empty include prefix fails; register root routes on parent aggregator |
|
| FastAPI 0.128+ empty-path sub-router restriction | `@router.get("")` on sub-router with empty include prefix fails; register root routes on parent aggregator |
|
||||||
| Vite 6 upgrade resolves 2 CVEs | CVE-2026-39363/39364 closed; npm audit now clean |
|
| Vite 6 upgrade resolves 2 CVEs | CVE-2026-39363/39364 closed; npm audit now clean |
|
||||||
|
| Phase 13 mutation JSONResponse | Mutations return JSONResponse (not HTTPException) so kind/reason appear at response top level |
|
||||||
|
| Phase 13 cloud_operations.py orchestration | All mutation logic routes through services/cloud_operations.py — never inline in routers |
|
||||||
|
| testCloudConnection explicit-only | Never called as navigation side effect (D-13); only from user action or post-failure retest |
|
||||||
|
| v0.3.0 version bump | Phase 13 completion warrants minor version bump per CLAUDE.md versioning protocol |
|
||||||
|
|
||||||
### Roadmap Evolution
|
### Roadmap Evolution
|
||||||
|
|
||||||
- v0.1 completed: all 7 foundation phases + security hardening (2026-06-06)
|
- v0.1 completed: all 7 foundation phases + security hardening (2026-06-06)
|
||||||
- v0.2 completed: UI overhaul, admin panel rearchitecture, responsive layout, codebase quality (2026-06-17)
|
- v0.2 completed: UI overhaul, admin panel rearchitecture, responsive layout, codebase quality (2026-06-17)
|
||||||
- v0.2 archived to `.planning/milestones/v0.2-ROADMAP.md`
|
- v0.2 archived to `.planning/milestones/v0.2-ROADMAP.md`
|
||||||
|
- v0.3 in progress: Phase 12 (cloud resource foundation) complete 2026-06-21; Phase 12.1 (Nextcloud fix) complete; Phase 13 (virtual-local cloud operations) complete 2026-06-23 — v0.3.0 shipped
|
||||||
|
- Phase 14 (selective analysis and byte cache) is next
|
||||||
|
|
||||||
### Open Questions
|
### Open Questions
|
||||||
|
|
||||||
@@ -77,11 +103,41 @@ None.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
|
**Stopped at:** Phase 14 context gathered
|
||||||
|
|
||||||
_Updated at each phase transition._
|
_Updated at each phase transition._
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Last session | 2026-06-17 — v0.2 milestone archived |
|
| Last session | 2026-06-23T12:04:31.592Z |
|
||||||
| Next action | /gsd:new-milestone to define v0.3 |
|
| Next action | Begin Phase 14 (Selective Analysis and Byte Cache) |
|
||||||
| Pending decisions | None |
|
| Pending decisions | None |
|
||||||
| Resume file | None |
|
| Resume file | .planning/phases/14-selective-analysis-and-byte-cache/14-CONTEXT.md |
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
- [Phase 12]: Remove NextcloudBackend.list_folder override — inherit canonical WebDAVBackend method
|
||||||
|
- [Phase 12]: OneDrive nextLink restricted to graph.microsoft.com host
|
||||||
|
- [Phase 12]: apply_listing_and_finalize is the single freshness gate — callers must not set refresh_state=fresh independently after list_folder
|
||||||
|
- [Phase 12.1 P03]: Named route objects used for all cloud folder navigation — Vue Router handles opaque ref encoding
|
||||||
|
- [Phase 12.1 P03]: Breadcrumb lineage maintained as explicit visited-node list — never reconstructed from provider_item_id
|
||||||
|
- [Phase 12.1 P03]: provider_item_id is canonical navigation reference; DocuVault id is row identity for Vue keys and metadata only
|
||||||
|
- [Phase 13 P03]: Backend-typed bodies required; frontend never guesses
|
||||||
|
- [Phase 13 P04]: file-open must call openCloudFile API; window.open() to provider URL is forbidden (D-02/T-13-07)
|
||||||
|
- [Phase 13 P10]: connectionHealth store translation is single source for browser compact status and Settings diagnostics (D-12)
|
||||||
|
- [Phase 13 P10]: testCloudConnection never called as side effect of folder browse navigation (D-13)
|
||||||
|
- [Phase 13 P04]: D-17: Google Drive OAuth uses drive scope (not drive.file) for Phase 13 full-access mutations
|
||||||
|
- [Phase 13 P07]: D-18: Preview is binary-only (PDF/images); Office/Workspace formats use typed unsupported_preview fallback to authorized download endpoint
|
||||||
|
- [Phase 13 P03]: Phase 13 mutation errors use JSONResponse (not HTTPException) so kind/reason appear at top level of response body, not nested under detail
|
||||||
|
- [Phase 13 P06]: upload_mutated folder state code: upload success marks parent folder as warning/upload_mutated for reconcile-before-return
|
||||||
|
- [Phase 13 P07]: CloudFolderView uses api.* barrel imports so vi.mock intercepts correctly
|
||||||
|
- [Phase 13 P07]: UploadProgress suppressed in cloud mode (v-if) — cloud queue dialogs replace it per D-03/D-04
|
||||||
|
- [Phase 13 P07]: StorageBrowser emits upload-queue-resolve with typed action; CloudFolderView handles all five resolution paths (keep_both/replace/skip/retry/cancel_all)
|
||||||
|
- [Phase 13 P08]: Rename collision surfaced to user (no auto-retry) — user chose name explicitly
|
||||||
|
- [Phase 13 P08]: Create-folder bounded retry up to 5 attempts with keep_both_name counter suffix D-05/D-06
|
||||||
|
- [Phase 13 P08]: Stale guard for create-folder and rename calls update_folder_state before returning typed stale body D-07
|
||||||
|
- [Phase 13 P08]: Reconcile-before-return for create-folder and rename: upsert_cloud_item + update_folder_state T-13-26
|
||||||
|
- [Phase 13 P09]: Move with descendant-chain walk + self-check + cross-connection check before provider submission
|
||||||
|
- [Phase 13 P09]: Stale move guard stops mutation, refreshes source folder state, returns typed stale result
|
||||||
|
- [Phase 13 P09]: Delete audit metadata: only kind/provider_item_id/display_name/is_folder — no bytes or credentials
|
||||||
|
- [Phase 13 P11]: Version bump to v0.3.0 on Phase 13 completion (minor bump for full phase per CLAUDE.md protocol)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Debug: Phase 12 Cloud Schema Cold Start
|
||||||
|
|
||||||
|
**Status:** root cause found
|
||||||
|
**Date:** 2026-06-19
|
||||||
|
**UAT tests:** 1, 2
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- `GET /api/cloud/connections` raises `psycopg.errors.UndefinedColumn` for `cloud_connections.display_name_override`.
|
||||||
|
- `POST /api/cloud/connections/webdav` reaches `_upsert_cloud_connection` and raises the same error, preventing Nextcloud account connection.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The running application code and ORM are at Phase 12, but the live PostgreSQL schema remains at Alembic revision `0005`.
|
||||||
|
|
||||||
|
Live evidence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ docker compose run --rm backend alembic current
|
||||||
|
0005
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration `backend/migrations/versions/0006_cloud_resource_foundation.py` correctly adds `cloud_connections.display_name_override` and the Phase 12 cloud metadata tables. The ORM correctly maps that column. The defect is that `docker-compose.yml` has no migration service and the backend command starts Uvicorn directly. Backend, Celery worker, and Celery beat depend on PostgreSQL health, but none depends on `alembic upgrade head` completing. Therefore `docker compose up` can run new application code against an old persistent database.
|
||||||
|
|
||||||
|
## Why Automated Tests Missed It
|
||||||
|
|
||||||
|
- Most backend tests build schema directly from `Base.metadata.create_all`, which validates the final ORM shape but bypasses Alembic history and deployment ordering.
|
||||||
|
- Existing Alembic tests target early migrations and do not exercise an existing PostgreSQL database upgraded from `0005` to `head`.
|
||||||
|
- Phase verification checked migration file structure and model parity, not a real cold-start Compose upgrade path.
|
||||||
|
|
||||||
|
## Files Involved
|
||||||
|
|
||||||
|
- `docker-compose.yml` — no one-shot migration service; backend/workers start after DB health only.
|
||||||
|
- `backend/migrations/versions/0006_cloud_resource_foundation.py` — contains the required column and tables but was not applied.
|
||||||
|
- `backend/db/models.py` — queries `display_name_override`, exposing schema drift immediately.
|
||||||
|
- `backend/tests/test_alembic.py` — lacks a 0005-to-head PostgreSQL upgrade regression.
|
||||||
|
- `.planning/phases/12-cloud-resource-foundation/12-VALIDATION.md` — cold-start test expected migration completion, but execution evidence did not actually verify Compose migration orchestration.
|
||||||
|
|
||||||
|
## Required Fix Direction
|
||||||
|
|
||||||
|
1. Add a one-shot Compose migration service that runs `alembic upgrade head` with `DATABASE_MIGRATE_URL` after PostgreSQL becomes healthy.
|
||||||
|
2. Make backend, Celery worker, and Celery beat wait for that migration service to complete successfully.
|
||||||
|
3. Add a PostgreSQL/Compose regression proving an existing revision `0005` advances to `0006/head` before the API starts, and assert `display_name_override`, `cloud_items`, `cloud_item_topics`, and `cloud_folder_states` exist.
|
||||||
|
4. Document the migration lifecycle and recovery command in README/RUNBOOK.
|
||||||
|
5. For the currently running environment, apply `alembic upgrade head` and restart application processes before resuming UAT.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Both UAT blockers share this root cause. No evidence currently indicates a separate Nextcloud credential or WebDAV defect.
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Debug: Phase 12 Cloud Schema Cold Start
|
||||||
|
|
||||||
|
**Status:** root cause found
|
||||||
|
**Date:** 2026-06-19
|
||||||
|
**UAT tests:** 1, 2
|
||||||
|
|
||||||
|
## Symptoms
|
||||||
|
|
||||||
|
- `GET /api/cloud/connections` raises `psycopg.errors.UndefinedColumn` for `cloud_connections.display_name_override`.
|
||||||
|
- `POST /api/cloud/connections/webdav` reaches `_upsert_cloud_connection` and raises the same error, preventing Nextcloud account connection.
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
The running application code and ORM are at Phase 12, but the live PostgreSQL schema remains at Alembic revision `0005`.
|
||||||
|
|
||||||
|
Live evidence:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ docker compose run --rm backend alembic current
|
||||||
|
0005
|
||||||
|
```
|
||||||
|
|
||||||
|
Migration `backend/migrations/versions/0006_cloud_resource_foundation.py` correctly adds `cloud_connections.display_name_override` and the Phase 12 cloud metadata tables. The ORM correctly maps that column. The defect is that `docker-compose.yml` has no migration service and the backend command starts Uvicorn directly. Backend, Celery worker, and Celery beat depend on PostgreSQL health, but none depends on `alembic upgrade head` completing. Therefore `docker compose up` can run new application code against an old persistent database.
|
||||||
|
|
||||||
|
## Why Automated Tests Missed It
|
||||||
|
|
||||||
|
- Most backend tests build schema directly from `Base.metadata.create_all`, which validates the final ORM shape but bypasses Alembic history and deployment ordering.
|
||||||
|
- Existing Alembic tests target early migrations and do not exercise an existing PostgreSQL database upgraded from `0005` to `head`.
|
||||||
|
- Phase verification checked migration file structure and model parity, not a real cold-start Compose upgrade path.
|
||||||
|
|
||||||
|
## Files Involved
|
||||||
|
|
||||||
|
- `docker-compose.yml` — no one-shot migration service; backend/workers start after DB health only.
|
||||||
|
- `backend/migrations/versions/0006_cloud_resource_foundation.py` — contains the required column and tables but was not applied.
|
||||||
|
- `backend/db/models.py` — queries `display_name_override`, exposing schema drift immediately.
|
||||||
|
- `backend/tests/test_alembic.py` — lacks a 0005-to-head PostgreSQL upgrade regression.
|
||||||
|
- `.planning/phases/12-cloud-resource-foundation/12-VALIDATION.md` — cold-start test expected migration completion, but execution evidence did not actually verify Compose migration orchestration.
|
||||||
|
|
||||||
|
## Required Fix Direction
|
||||||
|
|
||||||
|
1. Add a one-shot Compose migration service that runs `alembic upgrade head` with `DATABASE_MIGRATE_URL` after PostgreSQL becomes healthy.
|
||||||
|
2. Make backend, Celery worker, and Celery beat wait for that migration service to complete successfully.
|
||||||
|
3. Add a PostgreSQL/Compose regression proving an existing revision `0005` advances to `0006/head` before the API starts, and assert `display_name_override`, `cloud_items`, `cloud_item_topics`, and `cloud_folder_states` exist.
|
||||||
|
4. Document the migration lifecycle and recovery command in README/RUNBOOK.
|
||||||
|
5. For the currently running environment, apply `alembic upgrade head` and restart application processes before resuming UAT.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
Both UAT blockers share this root cause. No evidence currently indicates a separate Nextcloud credential or WebDAV defect.
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,393 @@
|
|||||||
|
---
|
||||||
|
phase: 10-ux-interaction
|
||||||
|
plan: 13
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- frontend/src/components/ui/TreeItem.vue
|
||||||
|
- frontend/src/components/storage/StorageBrowser.vue
|
||||||
|
- frontend/src/App.vue
|
||||||
|
- frontend/src/components/documents/SearchBar.vue
|
||||||
|
- frontend/src/components/layout/OsDragOverlay.vue
|
||||||
|
- frontend/src/__tests__/keyboard.test.js
|
||||||
|
- frontend/src/components/ui/__tests__/TreeItem.test.js
|
||||||
|
- frontend/src/components/storage/__tests__/StorageBrowser.showSearch.test.js
|
||||||
|
autonomous: true
|
||||||
|
requirements: [UX-02, UX-03, UX-05, UX-06, UX-07, UX-08, UX-09]
|
||||||
|
gap_closure: true
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Sidebar Cloud/Folder sections show animated shimmer rows while async data loads — no 'Loading…' text visible"
|
||||||
|
- "Admin routes (/admin/*) render with no AppSidebar — AdminLayout is the sole layout component"
|
||||||
|
- "Pressing '/', 'U', or 'N' in the file manager dispatches to the actual FileManagerView instance — not a RouterView proxy"
|
||||||
|
- "Pressing Escape in the search input clears the field and preserves focus — no browser-native blur"
|
||||||
|
- "Dropping an OS file on the drag overlay triggers upload — capture-phase window listener fires before folder-row handlers"
|
||||||
|
- "Search bar and sort controls are visible at the root of both local and cloud file browsers"
|
||||||
|
artifacts:
|
||||||
|
- path: "frontend/src/components/ui/TreeItem.vue"
|
||||||
|
provides: "Three animate-pulse shimmer rows in the v-if='loading' branch"
|
||||||
|
contains: "animate-pulse"
|
||||||
|
- path: "frontend/src/components/storage/StorageBrowser.vue"
|
||||||
|
provides: "showSearch computed returns true for mode === 'local' OR mode === 'cloud'"
|
||||||
|
- path: "frontend/src/App.vue"
|
||||||
|
provides: "Third template branch for admin routes (no AppSidebar); getFileManagerInstance() via matched.find for keyboard dispatch"
|
||||||
|
- path: "frontend/src/components/documents/SearchBar.vue"
|
||||||
|
provides: "@keydown.escape.prevent.stop suppresses browser native blur on type='search'"
|
||||||
|
- path: "frontend/src/components/layout/OsDragOverlay.vue"
|
||||||
|
provides: "window drop listener registered with capture=true; removeEventListener also passes true"
|
||||||
|
key_links:
|
||||||
|
- from: "frontend/src/App.vue (onKeydown)"
|
||||||
|
to: "FileManagerView.focusSearch / triggerUpload / startNewFolder"
|
||||||
|
via: "router.currentRoute.value.matched.find(r => r.instances?.default)?.instances?.default"
|
||||||
|
pattern: "instances\\.default"
|
||||||
|
- from: "frontend/src/components/layout/OsDragOverlay.vue"
|
||||||
|
to: "window drop event"
|
||||||
|
via: "addEventListener('drop', handler, true)"
|
||||||
|
pattern: "addEventListener.*drop.*true"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Close all 6 UAT gaps from 10-UAT.md that block Phase 10 sign-off: sidebar shimmer (Gap 1), search-at-root visibility (Gap 2), admin sidebar bleed (Gap 3), keyboard shortcuts broken by RouterView proxy (Gap 4 — covers tests 11, 12, 13), Escape-breaks-search (Gap 5), and OS drag-drop not uploading (Gap 6).
|
||||||
|
|
||||||
|
Purpose: Phase 10 is recorded complete in ROADMAP.md but UAT (10-UAT.md) shows 9 issues with 6 distinct root causes diagnosed. These targeted fixes close all 6 causes with minimal code change, restoring full UAT pass.
|
||||||
|
|
||||||
|
Output: Five production files modified, three test files added. No new dependencies. No regressions.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/nik/.claude/get-shit-done/workflows/execute-plan.md
|
||||||
|
@/Users/nik/.claude/get-shit-done/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@/Users/nik/Documents/Progamming/document_scanner/.planning/PROJECT.md
|
||||||
|
@/Users/nik/Documents/Progamming/document_scanner/.planning/ROADMAP.md
|
||||||
|
@/Users/nik/Documents/Progamming/document_scanner/.planning/STATE.md
|
||||||
|
@/Users/nik/Documents/Progamming/document_scanner/.planning/phases/10-ux-interaction/10-UAT.md
|
||||||
|
|
||||||
|
<interfaces>
|
||||||
|
<!-- Key contracts the executor needs. Extracted from codebase. No codebase exploration needed. -->
|
||||||
|
|
||||||
|
From frontend/src/App.vue (current full state — relevant sections):
|
||||||
|
```html
|
||||||
|
<!-- Template — only two branches today: -->
|
||||||
|
<AuthLayout v-if="route.meta.layout === 'auth'" />
|
||||||
|
<div v-else class="flex h-screen overflow-hidden">
|
||||||
|
<AppSidebar />
|
||||||
|
<main class="flex-1 overflow-y-auto">
|
||||||
|
<router-view ref="routeViewRef" /> <!-- resolves to RouterView proxy, NOT FileManagerView -->
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
<ToastContainer />
|
||||||
|
<OsDragOverlay @files-dropped="onOsFilesDropped" />
|
||||||
|
```
|
||||||
|
```js
|
||||||
|
// script setup
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
|
const routeViewRef = ref(null) // ← remove this
|
||||||
|
|
||||||
|
function onOsFilesDropped(files) {
|
||||||
|
routeViewRef.value?.handleOsDrop?.(files) // ← fix to use getFileManagerInstance()
|
||||||
|
}
|
||||||
|
function onKeydown(e) {
|
||||||
|
// guard: INPUT/TEXTAREA/SELECT/contentEditable + dialog check omitted here
|
||||||
|
routeViewRef.value?.focusSearch?.() // line 37 ← fix
|
||||||
|
routeViewRef.value?.clearSearch?.() // line 40 ← fix
|
||||||
|
routeViewRef.value?.triggerUpload?.() // line 43 ← fix
|
||||||
|
routeViewRef.value?.startNewFolder?.() // line 46 ← fix
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/router/index.js — admin route meta:
|
||||||
|
```js
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
component: () => import('../layouts/AdminLayout.vue'),
|
||||||
|
meta: { requiresAdmin: true }, // only on parent; children resolved via matched.some()
|
||||||
|
children: [/* AdminOverviewView, AdminUsersView, AdminQuotasView, AdminAiView, AdminAuditView */]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/components/ui/TreeItem.vue — loading branch to replace (lines 48-52):
|
||||||
|
```html
|
||||||
|
<div
|
||||||
|
v-if="loading"
|
||||||
|
class="text-xs text-gray-400 py-1"
|
||||||
|
:style="{ paddingLeft: `${(depth + 1) * 12 + 8}px` }"
|
||||||
|
>
|
||||||
|
Loading…
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Shimmer pattern to replicate (from AppSidebar.vue lines 60-64):
|
||||||
|
```html
|
||||||
|
<div v-if="loadingRoots" class="pl-7 py-1 space-y-1">
|
||||||
|
<div v-for="n in 3" :key="`sk-f-${n}`" class="flex items-center gap-2 py-1">
|
||||||
|
<div class="w-4 h-4 bg-gray-100 rounded animate-pulse shrink-0"></div>
|
||||||
|
<div class="h-3 bg-gray-100 rounded animate-pulse" :style="{ width: (50 + n * 15) + 'px' }"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/components/storage/StorageBrowser.vue line 287:
|
||||||
|
```js
|
||||||
|
const showSearch = computed(() => props.mode === 'local' && props.breadcrumb.length > 0)
|
||||||
|
// TARGET STATE:
|
||||||
|
const showSearch = computed(() => props.mode === 'local' || props.mode === 'cloud')
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/components/documents/SearchBar.vue line 11:
|
||||||
|
```html
|
||||||
|
@keydown.escape="emit('update:modelValue', '')"
|
||||||
|
<!-- TARGET STATE: -->
|
||||||
|
@keydown.escape.prevent.stop="emit('update:modelValue', '')"
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/components/layout/OsDragOverlay.vue (Options API — lines 52-63):
|
||||||
|
```js
|
||||||
|
mounted() {
|
||||||
|
window.addEventListener('dragenter', this.onDragEnter)
|
||||||
|
window.addEventListener('dragleave', this.onDragLeave)
|
||||||
|
window.addEventListener('dragover', this.onDragOver)
|
||||||
|
window.addEventListener('drop', this.onDrop) // ← bubble phase; must become capture
|
||||||
|
},
|
||||||
|
beforeUnmount() {
|
||||||
|
window.removeEventListener('dragenter', this.onDragEnter)
|
||||||
|
window.removeEventListener('dragleave', this.onDragLeave)
|
||||||
|
window.removeEventListener('dragover', this.onDragOver)
|
||||||
|
window.removeEventListener('drop', this.onDrop) // ← must match with true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/views/FileManagerView.vue defineExpose (lines 190-196):
|
||||||
|
```js
|
||||||
|
// These ARE correct — all methods are exposed. The problem is App.vue never reaches this instance.
|
||||||
|
defineExpose({
|
||||||
|
focusSearch: () => browserRef.value?.focusSearch?.(),
|
||||||
|
triggerUpload: () => browserRef.value?.triggerUpload?.(),
|
||||||
|
startNewFolder: () => browserRef.value?.startNewFolder?.(),
|
||||||
|
clearSearch: () => browserRef.value?.clearSearch?.(),
|
||||||
|
handleOsDrop: (files) => onFilesSelected({ files, autoClassify: true }),
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
From frontend/src/__tests__/keyboard.test.js (existing structure):
|
||||||
|
Uses mountFileManager() helper, vi.mock('../api/client.js', ...), vi.mock('../stores/auth.js', ...),
|
||||||
|
vi.mock('../stores/topics.js', ...), setActivePinia(createPinia()), createRouter+createMemoryHistory.
|
||||||
|
</interfaces>
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Sidebar shimmer rows (TreeItem.vue) and search-at-root (StorageBrowser.vue)</name>
|
||||||
|
<files>
|
||||||
|
frontend/src/components/ui/TreeItem.vue
|
||||||
|
frontend/src/components/storage/StorageBrowser.vue
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
TreeItem.vue — replace the v-if="loading" div (lines 48-52) with a shimmer block. The replacement wraps three shimmer rows in a single container div. Apply the indent :style on the container (same binding the removed div had: `{ paddingLeft: \`\${(depth + 1) * 12 + 8}px\` }`). The container also gets `class="py-1 space-y-1"`. Each of the three inner rows uses `class="flex items-center gap-2 py-1"` and contains two children:
|
||||||
|
- Icon placeholder: `class="w-4 h-4 bg-gray-100 rounded animate-pulse shrink-0"`
|
||||||
|
- Text placeholder: `class="h-3 bg-gray-100 rounded animate-pulse"` with `:style="{ width: (50 + n * 15) + 'px' }"`
|
||||||
|
Use `v-for="n in 3"` with `:key="\`sk-t-\${n}\`"` on the inner row div.
|
||||||
|
Do not change any other branch (v-else-if loadError, v-else-if children.length===0, slot children) or any script section.
|
||||||
|
|
||||||
|
StorageBrowser.vue — change line 287 only. Change:
|
||||||
|
`const showSearch = computed(() => props.mode === 'local' && props.breadcrumb.length > 0)`
|
||||||
|
to:
|
||||||
|
`const showSearch = computed(() => props.mode === 'local' || props.mode === 'cloud')`
|
||||||
|
No template changes needed — SearchBar and SortControls already share v-if="showSearch".
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/frontend && grep -c "animate-pulse" src/components/ui/TreeItem.vue && grep "showSearch" src/components/storage/StorageBrowser.vue | grep "cloud"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `grep -c "animate-pulse" src/components/ui/TreeItem.vue` returns at least 2
|
||||||
|
- `grep -c "Loading" src/components/ui/TreeItem.vue` returns 0
|
||||||
|
- `grep "showSearch" src/components/storage/StorageBrowser.vue` contains `props.mode === 'cloud'`
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Admin sidebar bleed and keyboard instance resolution (App.vue)</name>
|
||||||
|
<files>frontend/src/App.vue</files>
|
||||||
|
<action>
|
||||||
|
Two root causes, one file. Apply both changes together.
|
||||||
|
|
||||||
|
CHANGE A — Admin sidebar bleed (Gap 3):
|
||||||
|
Insert a v-else-if branch in the template between the auth branch and the user-layout div. The new branch condition is `route.matched.some(r => r.meta.requiresAdmin)`. When true it renders only `<router-view />` — no AppSidebar, no main wrapper. The complete corrected template root (inside the component root, excluding ToastContainer and OsDragOverlay which stay unchanged):
|
||||||
|
|
||||||
|
1. `<AuthLayout v-if="route.meta.layout === 'auth'" />`
|
||||||
|
2. `<router-view v-else-if="route.matched.some(r => r.meta.requiresAdmin)" />`
|
||||||
|
3. `<div v-else class="flex h-screen overflow-hidden">` ... AppSidebar + main + router-view (no ref attribute) ... `</div>`
|
||||||
|
|
||||||
|
Note: the router-view in branch 3 must NOT carry `ref="routeViewRef"` — refs on router-view resolve to the RouterView proxy (same root cause as Gap 4). Remove the ref attribute.
|
||||||
|
|
||||||
|
CHANGE B — Keyboard instance resolution (Gap 4, also fixes tests 12 and 13):
|
||||||
|
The `routeViewRef` ref and its usage must be replaced with a direct instance lookup:
|
||||||
|
1. Add `useRouter` to the `vue-router` import: `import { useRoute, useRouter } from 'vue-router'`
|
||||||
|
2. Add `const router = useRouter()` after `const route = useRoute()`
|
||||||
|
3. Add helper: `function getFileManagerInstance() { return router.currentRoute.value.matched.find(r => r.instances?.default)?.instances?.default ?? null }`
|
||||||
|
4. Remove `const routeViewRef = ref(null)` — no longer used
|
||||||
|
5. Remove `ref` from the `ref` import if it is now unused (check — ref was only used for routeViewRef)
|
||||||
|
6. Replace all `routeViewRef.value?.X?.()` calls in onKeydown with `getFileManagerInstance()?.X?.()`
|
||||||
|
7. Replace `routeViewRef.value?.handleOsDrop?.(files)` in onOsFilesDropped with `getFileManagerInstance()?.handleOsDrop?.(files)`
|
||||||
|
|
||||||
|
After both changes, `ref` from vue should be removed from the import line if nothing else uses it (check first — if ToastContainer or OsDragOverlay use a local ref, keep it; otherwise remove to avoid lint warnings).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/frontend && grep "routeViewRef" src/App.vue; echo "exit:$?"; grep "instances.default" src/App.vue; grep "requiresAdmin" src/App.vue</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `grep "routeViewRef" src/App.vue` returns no output (fully removed)
|
||||||
|
- `grep "instances.default" src/App.vue` returns the getFileManagerInstance() line
|
||||||
|
- `grep "requiresAdmin" src/App.vue` returns the v-else-if template branch
|
||||||
|
- `grep "useRouter" src/App.vue` appears in import and instantiation
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Escape modifier (SearchBar.vue) and capture-phase drop (OsDragOverlay.vue)</name>
|
||||||
|
<files>
|
||||||
|
frontend/src/components/documents/SearchBar.vue
|
||||||
|
frontend/src/components/layout/OsDragOverlay.vue
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
SearchBar.vue — one character change on line 11. Add `.prevent.stop` modifiers to `@keydown.escape`:
|
||||||
|
Before: `@keydown.escape="emit('update:modelValue', '')"`
|
||||||
|
After: `@keydown.escape.prevent.stop="emit('update:modelValue', '')"`
|
||||||
|
`.prevent` stops the browser's native clear+blur behavior on type="search" inputs. `.stop` stops bubbling to App.vue's global keydown handler (which would call clearSearch() a second time). No other changes.
|
||||||
|
|
||||||
|
OsDragOverlay.vue — two lines change in the Options API lifecycle hooks:
|
||||||
|
In mounted(): change `window.addEventListener('drop', this.onDrop)` to `window.addEventListener('drop', this.onDrop, true)`
|
||||||
|
In beforeUnmount(): change `window.removeEventListener('drop', this.onDrop)` to `window.removeEventListener('drop', this.onDrop, true)`
|
||||||
|
|
||||||
|
The third argument `true` registers the listener in capture phase, meaning it runs before any bubble-phase handlers on child elements (including `@drop.prevent` on folder rows in StorageBrowser.vue). The removeEventListener MUST also pass `true` — EventTarget tracks bubble-phase and capture-phase registrations separately, so without `true` the cleanup call would fail silently and the listener would leak.
|
||||||
|
|
||||||
|
Do not change dragenter, dragleave, or dragover listeners — those remain in bubble phase and work correctly.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/frontend && grep "keydown.escape" src/components/documents/SearchBar.vue && grep -n "addEventListener.*drop\|removeEventListener.*drop" src/components/layout/OsDragOverlay.vue</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `grep "keydown.escape" src/components/documents/SearchBar.vue` shows `.prevent.stop` in the modifier chain
|
||||||
|
- `grep "addEventListener.*drop" src/components/layout/OsDragOverlay.vue` shows `true` as third argument
|
||||||
|
- `grep "removeEventListener.*drop" src/components/layout/OsDragOverlay.vue` shows `true` as third argument
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>Task 4: Regression tests for all 6 gaps</name>
|
||||||
|
<files>
|
||||||
|
frontend/src/__tests__/keyboard.test.js
|
||||||
|
frontend/src/components/ui/__tests__/TreeItem.test.js
|
||||||
|
frontend/src/components/storage/__tests__/StorageBrowser.showSearch.test.js
|
||||||
|
</files>
|
||||||
|
<behavior>
|
||||||
|
TreeItem shimmer:
|
||||||
|
- When loading=true and expanded=true → rendered HTML contains elements with class "animate-pulse"
|
||||||
|
- When loading=true and expanded=true → text "Loading" does not appear in rendered HTML
|
||||||
|
|
||||||
|
StorageBrowser showSearch:
|
||||||
|
- mode='local', breadcrumb=[] → showSearch is true (root-level local, previously false)
|
||||||
|
- mode='local', breadcrumb=[{name:'Folder'}] → showSearch is true (non-root local)
|
||||||
|
- mode='cloud', breadcrumb=[] → showSearch is true (cloud root, previously always false)
|
||||||
|
- mode='shared' → showSearch is false
|
||||||
|
|
||||||
|
App.vue keyboard dispatch:
|
||||||
|
- matched.find(r => r.instances?.default)?.instances?.default on a mounted FileManagerView router returns an object with focusSearch defined
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
keyboard.test.js — append a new describe block at the end of the existing file (do not modify or move existing describes):
|
||||||
|
|
||||||
|
"Gap 4: getFileManagerInstance resolves to actual component, not RouterView proxy" — using the existing mountFileManager() helper and makeRouter(), after mounting FileManagerView and flushing promises, access `router.currentRoute.value.matched.find(r => r.instances?.default)?.instances?.default` and assert that it is not null and has a `focusSearch` property. This proves the resolution mechanism the refactored App.vue uses is correct. Import `useRouter` from vue-router at the top if not already imported — check existing imports first.
|
||||||
|
|
||||||
|
TreeItem.test.js — create at `frontend/src/components/ui/__tests__/TreeItem.test.js`:
|
||||||
|
- Import: `describe, it, expect, vi` from vitest; `mount, flushPromises` from @vue/test-utils; TreeItem from the component
|
||||||
|
- Mock AppIcon: `vi.mock('../AppIcon.vue', () => ({ default: { template: '<span/>' } }))`
|
||||||
|
- Helper: `mountExpanded(overrides = {})` — mounts TreeItem with `label="Test"`, `loadChildren: vi.fn().mockResolvedValue([])`, `expandable: true` and spreads overrides. After mount, call `w.vm.toggleExpand()` via `w.vm.$.setupState.toggleExpand()` (or trigger the expand button click) and flush promises to reach expanded state with children loaded.
|
||||||
|
- For the loading=true test: mount with a loadChildren that returns a promise that never resolves (`new Promise(() => {})`). Click the expand button to trigger load. Before flushing, assert the HTML. The loading ref will be true while the promise is pending.
|
||||||
|
- describe "Gap 1: sidebar shimmer rows": two tests as per behavior block above.
|
||||||
|
- describe "Gap 1 regression: non-loading branches unchanged": one test asserting that with loadChildren resolving to [] and expanded, the "Empty" text appears (sibling v-else-if branch guard).
|
||||||
|
|
||||||
|
StorageBrowser.showSearch.test.js — create at `frontend/src/components/storage/__tests__/StorageBrowser.showSearch.test.js`:
|
||||||
|
- Heavy stub setup: all child components that StorageBrowser imports must be stubbed (SearchBar, SortControls, DocumentCard, FolderRow, EmptyState, AppIcon, BreadcrumbBar, DropZone, etc.) — use `stubs: { SearchBar: true, SortControls: true, ... }` in global mount options or individual vi.mock calls.
|
||||||
|
- Mock all API calls used on mount (listDocuments, listFolders, etc.) via vi.mock('../../../api/client.js', ...).
|
||||||
|
- After mount with given props, access `w.vm.showSearch` directly as a computed.
|
||||||
|
- Props for mount: at minimum `mode`, `breadcrumb`, `documents: []`, `folders: []`, `topicColorFn: () => '#000'`, `loading: false`.
|
||||||
|
- Four it() blocks as per behavior block above.
|
||||||
|
- Wrap in `describe("Gap 2: showSearch visible at root for local and cloud modes", ...)`.
|
||||||
|
|
||||||
|
All new tests run as part of `npm run test -- --run` without additional flags.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /Users/nik/Documents/Progamming/document_scanner/frontend && npm run test -- --reporter=verbose --run 2>&1 | grep -E "(PASS|FAIL|SKIP| ✓ | × | ✗ )" | tail -50</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- All new describe blocks appear in output with passing test indicators
|
||||||
|
- `npm run test -- --run` exits 0 with zero failures
|
||||||
|
- `grep -c "animate-pulse" src/components/ui/__tests__/TreeItem.test.js` returns at least 1
|
||||||
|
- `grep -c "showSearch" src/components/storage/__tests__/StorageBrowser.showSearch.test.js` returns at least 4
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| OS file drag → window drop (capture) | Files arrive via browser DataTransfer API — browser-enforced; no network boundary crossed; payload handled only client-side before going through existing authenticated upload flow |
|
||||||
|
| Admin route layout selection | Template branch selection is display-only — backend enforces admin access via get_current_admin dep on every admin endpoint; changing template rendering creates no auth regression |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-10gc-01 | Tampering | OsDragOverlay capture-phase drop handler | accept | Handler reads only e.dataTransfer.files (browser-controlled), resets overlay state, emits to parent — no direct server interaction; upload goes through existing authenticated API path unchanged |
|
||||||
|
| T-10gc-02 | Spoofing | router.currentRoute.value.matched.find().instances.default | accept | Read-only Vue internals lookup; no user-supplied data flows through this path; cannot be spoofed via URL manipulation — Vue populates instances after component mount, not from route params |
|
||||||
|
| T-10gc-03 | Elevation of Privilege | App.vue v-else-if admin branch | accept | Branch adds a missing layout guard (removes AppSidebar for admin); it does not grant or deny route access — navigation guard in router/index.js is unchanged and continues to enforce requiresAdmin check |
|
||||||
|
| T-10gc-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed — all fixes are template/script edits to existing files |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
Manual UAT re-run checklist (all must pass before marking complete):
|
||||||
|
|
||||||
|
Gap 1 — Sidebar shimmer: Expand a cloud provider tree item in the sidebar while data loads. Animated shimmer rows appear; "Loading…" text is absent.
|
||||||
|
|
||||||
|
Gap 2 — Search at root: Navigate to / (file manager root, no folder entered). Search bar and sort controls are visible in the content area header.
|
||||||
|
|
||||||
|
Gap 3 — Admin sidebar: Navigate to /admin/users in a logged-in admin session. Only the AdminLayout and AdminSidebar render — AppSidebar (user nav) is absent.
|
||||||
|
|
||||||
|
Gap 4 — Keyboard shortcuts: With the file manager at root, press '/'. Search bar receives cursor focus. Press 'U'. File picker dialog opens. Press 'N'. New folder inline input appears.
|
||||||
|
|
||||||
|
Gap 5 — Escape behavior: Type text into the search bar. Press Escape. Field clears. Cursor remains in the field. Type again immediately — new characters appear (no re-click required).
|
||||||
|
|
||||||
|
Gap 6 — OS drag-drop: Drag a file from Finder/Explorer over the browser window. Overlay appears. Release the file. Upload begins (progress visible or toast appears).
|
||||||
|
|
||||||
|
Automated gate:
|
||||||
|
`cd frontend && npm run test -- --run` — exits 0, zero failures, no skipped tests regressed.
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- All 6 UAT gaps confirmed closed by manual re-run of the 6 scenarios above
|
||||||
|
- `npm run test -- --run` exits 0 with zero failures across the full suite (baseline: 211 passing)
|
||||||
|
- Five production files modified with surgical changes only — no refactors, no feature additions
|
||||||
|
- Three new test files committed with passing tests covering each gap
|
||||||
|
- `grep -c "Loading" frontend/src/components/ui/TreeItem.vue` returns 0
|
||||||
|
- `grep "showSearch" frontend/src/components/storage/StorageBrowser.vue` matches the `|| props.mode === 'cloud'` form
|
||||||
|
- `grep "routeViewRef" frontend/src/App.vue` returns empty
|
||||||
|
- `grep "requiresAdmin" frontend/src/App.vue` returns the v-else-if template branch line
|
||||||
|
- `grep "addEventListener.*drop.*true" frontend/src/components/layout/OsDragOverlay.vue` returns a match
|
||||||
|
- `grep "removeEventListener.*drop.*true" frontend/src/components/layout/OsDragOverlay.vue` returns a match
|
||||||
|
- `grep "prevent.stop" frontend/src/components/documents/SearchBar.vue` returns the escape handler line
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `/Users/nik/Documents/Progamming/document_scanner/.planning/phases/10-ux-interaction/10-13-SUMMARY.md` when done.
|
||||||
|
</output>
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+42
@@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 1
|
||||||
|
type: execute
|
||||||
|
wave: 0
|
||||||
|
depends_on: [10-complete]
|
||||||
|
requirements: [PERF-02]
|
||||||
|
files_modified:
|
||||||
|
- frontend/vite.config.js
|
||||||
|
- .planning/phases/11-visual-design-responsive-layout-cleanup/11-RESEARCH.md
|
||||||
|
- .planning/perf/phase11-baseline.html
|
||||||
|
- .planning/perf/phase11-baseline-summary.md
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-01 — Bundle Baseline & UI Audit
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Capture the Phase 11 pre-optimization bundle baseline before any lazy-loading or visual cleanup begins, then record a targeted audit of the visual/responsive issues Phase 11 will address.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Wire existing `rollup-plugin-visualizer` into `frontend/vite.config.js` behind an opt-in environment flag such as `ANALYZE=true`. The dependency already exists in `frontend/package.json`.
|
||||||
|
2. Run a production build with analysis enabled and write the baseline report to `.planning/perf/phase11-baseline.html`.
|
||||||
|
3. Add `.planning/perf/phase11-baseline-summary.md` with bundle size, largest chunks, route/component observations, and the exact command used.
|
||||||
|
4. Update `11-RESEARCH.md` if execution discovers facts that differ from the refresh research.
|
||||||
|
5. Audit the frontend for Phase 11 targets:
|
||||||
|
- synchronous non-critical route imports
|
||||||
|
- responsive sidebar/admin sidebar gaps
|
||||||
|
- tables or grids that overflow below `sm`/`md`
|
||||||
|
- modal overflow below 640px
|
||||||
|
- inconsistent form, hover, focus, active, spacing, and typography patterns
|
||||||
|
- unreferenced files and imports
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Baseline bundle report exists before any Phase 11 optimization commits.
|
||||||
|
- `vite.config.js` does not generate analyzer output unless explicitly requested.
|
||||||
|
- `11-RESEARCH.md` remains accurate after the baseline build.
|
||||||
|
- Audit notes are concrete enough that plans 11-02 through 11-06 can execute without rediscovering scope.
|
||||||
|
- `cd frontend && npm run build` succeeds with and without analysis enabled.
|
||||||
File renamed without changes.
+43
@@ -0,0 +1,43 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 2
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: [11-01]
|
||||||
|
requirements: [PERF-03]
|
||||||
|
files_modified:
|
||||||
|
- frontend/src/router/index.js
|
||||||
|
- frontend/src/router/__tests__/router.guard.test.js
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-02 — Lazy-Load Non-Critical Routes
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Satisfy PERF-03 by lazy-loading every route component that is not needed for the initial render, while preserving auth/admin guard behavior.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Keep `FileManagerView` synchronous for `/` as the critical first authenticated surface unless the baseline report shows a strong reason to split it. Document this in `router/index.js` near the import.
|
||||||
|
2. Replace synchronous imports in `frontend/src/router/index.js` for non-initial routes:
|
||||||
|
- `TopicsView`
|
||||||
|
- `DocumentView`
|
||||||
|
- `SettingsView`
|
||||||
|
- `CloudStorageView`
|
||||||
|
- `CloudFolderView`
|
||||||
|
3. Keep `/folders/:folderId` on the same `FileManagerView` component for behavior parity with `/`; it is already in the initial chunk because `/` uses the same component.
|
||||||
|
4. Preserve existing lazy auth/admin/shared route imports.
|
||||||
|
5. Extend router tests so guard behavior is verified with lazy route components:
|
||||||
|
- non-admin cannot enter `/admin/*`
|
||||||
|
- admin redirects away from non-admin routes
|
||||||
|
- refresh-before-guard still runs when access token is absent
|
||||||
|
- `/topics`, `/document/:id`, `/settings`, `/cloud`, and `/cloud/:provider/:folderId` still resolve
|
||||||
|
6. Build once and confirm route chunks are emitted.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- `rg "import .*View" frontend/src/router/index.js` only returns `FileManagerView` unless a new initial-render route is explicitly justified.
|
||||||
|
- Admin child routes remain lazy-loaded.
|
||||||
|
- Router guard tests pass.
|
||||||
|
- `cd frontend && npm run build` succeeds and emits split route chunks.
|
||||||
File renamed without changes.
+52
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 3
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on: [11-02]
|
||||||
|
requirements: [RESP-01, RESP-02, RESP-03, RESP-05]
|
||||||
|
files_modified:
|
||||||
|
- frontend/src/App.vue
|
||||||
|
- frontend/src/layouts/AdminLayout.vue
|
||||||
|
- frontend/src/components/layout/AppSidebar.vue
|
||||||
|
- frontend/src/components/admin/AdminSidebar.vue
|
||||||
|
- frontend/src/components/storage/StorageBrowser.vue
|
||||||
|
- frontend/src/__tests__/keyboard.test.js
|
||||||
|
- frontend/src/components/storage/__tests__/StorageBrowser.skeleton.test.js
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-03 — Responsive Shells & Storage Rows
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Make both user and admin layouts usable below `lg`, and make storage rows fit smaller viewports without losing core actions.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Use layout-local drawer refs, not a new Pinia store:
|
||||||
|
- `App.vue` owns user drawer state.
|
||||||
|
- `AdminLayout.vue` owns admin drawer state.
|
||||||
|
- Watch route changes in each layout root and close the drawer after navigation.
|
||||||
|
- Do not put drawer state in `AppSidebar.vue` or `AdminSidebar.vue`.
|
||||||
|
2. Add a mobile-only header with a hamburger button for the user layout.
|
||||||
|
3. Hide `AppSidebar` below `lg`; open it in a slide-in overlay drawer with backdrop tap, route-change close, and `translate-x-0` / `-translate-x-full` transition.
|
||||||
|
4. Apply the same responsive shell behavior to `AdminLayout` and `AdminSidebar`.
|
||||||
|
5. Update `StorageBrowser` row/grid classes so:
|
||||||
|
- Size column hides below `md`
|
||||||
|
- Modified column hides below `sm`
|
||||||
|
- icon, name, and actions remain visible
|
||||||
|
- grid templates do not reserve hidden column widths on mobile
|
||||||
|
6. Ensure inline icon action buttons have at least `36px` touch targets below `md`.
|
||||||
|
7. Add or update tests for drawer open/close, route-change close, admin drawer behavior, responsive column classes, and touch target classes.
|
||||||
|
8. Verify with browser screenshots or Playwright at 375px, 768px, 1024px, and desktop width.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- User sidebar is hidden below 1024px and accessible through a hamburger drawer.
|
||||||
|
- Admin sidebar has matching mobile behavior.
|
||||||
|
- Drawer closes on backdrop tap and navigation tap.
|
||||||
|
- Storage rows satisfy RESP-02 without horizontal overflow at 375px.
|
||||||
|
- Icon actions satisfy RESP-03.
|
||||||
|
- Drawer state is owned only by `App.vue` and `AdminLayout.vue`.
|
||||||
|
- Frontend tests and build pass.
|
||||||
File renamed without changes.
+40
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 4
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on: [11-03]
|
||||||
|
requirements: [VISUAL-02, RESP-04]
|
||||||
|
files_modified:
|
||||||
|
- frontend/tailwind.config.js
|
||||||
|
- frontend/src/components/**/*.vue
|
||||||
|
- frontend/src/views/**/*.vue
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-04 — Forms Baseline & Mobile-Safe Modals
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Normalize form controls through `@tailwindcss/forms` and make every modal scroll safely on mobile viewports.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Confirm `@tailwindcss/forms` remains installed and active in `tailwind.config.js`; it is already wired today, so this should be a verification step unless execution finds drift.
|
||||||
|
2. Audit inputs, selects, textareas, checkboxes, and radio buttons for conflicting per-component browser-reset styles.
|
||||||
|
3. Normalize form classes to the smallest consistent Tailwind pattern already used by the app.
|
||||||
|
4. Update modal shells so content below 640px is scrollable and never exceeds viewport height:
|
||||||
|
- `ShareModal.vue`: centered panel gets mobile `max-h` and `overflow-y-auto`.
|
||||||
|
- `CloudCredentialModal.vue`: tall WebDAV/Nextcloud form gets mobile `max-h` and `overflow-y-auto`.
|
||||||
|
- `FolderDeleteModal.vue`: adopt the same mobile-safe panel pattern.
|
||||||
|
- `DocumentPreviewModal.vue`: preserve full-screen preview but verify header/content sizing at narrow widths.
|
||||||
|
- any auth/account confirmation modal-like surfaces found in the audit
|
||||||
|
5. Add focused tests or DOM assertions for mobile-safe modal classes and form baseline coverage.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- Forms plugin is active and relied on consistently.
|
||||||
|
- No modal content overflows a 375x667 viewport.
|
||||||
|
- No modal text or action row is clipped below 640px.
|
||||||
|
- The desktop modal appearance remains behaviorally unchanged.
|
||||||
|
- `npm run test -- --run` and `npm run build` pass.
|
||||||
File renamed without changes.
+46
@@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 5
|
||||||
|
type: execute
|
||||||
|
wave: 4
|
||||||
|
depends_on: [11-04]
|
||||||
|
requirements: [VISUAL-01, VISUAL-03, VISUAL-04]
|
||||||
|
files_modified:
|
||||||
|
- frontend/src/components/**/*.vue
|
||||||
|
- frontend/src/views/**/*.vue
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-05 — Visual Consistency Pass
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Make spacing, typography, hover, focus-visible, and active states consistent across the frontend without introducing a component-library rewrite.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Preserve legitimate data-driven inline styles:
|
||||||
|
- topic color swatches
|
||||||
|
- quota/progress widths
|
||||||
|
- Teleport dropdown coordinates
|
||||||
|
- tree indentation
|
||||||
|
2. Remove arbitrary spacing and decorative inline styles unless they are data-driven layout values with no Tailwind equivalent.
|
||||||
|
3. Normalize typography to this app scale:
|
||||||
|
- page title: `text-2xl font-semibold`
|
||||||
|
- section title: `text-lg font-semibold`
|
||||||
|
- panel/table heading: `text-sm font-semibold`
|
||||||
|
- body: `text-sm`
|
||||||
|
- caption/metadata: `text-xs`
|
||||||
|
4. Replace generic `focus:ring-*` only patterns on interactive elements with this keyboard focus convention: `focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-1`.
|
||||||
|
5. Ensure buttons, links, card rows, table rows, menu items, and icon actions have coherent hover and active states.
|
||||||
|
6. Avoid broad palette changes; preserve the current DocuVault identity while removing one-off visual drift.
|
||||||
|
7. Add focused tests or static checks for the agreed invariants where practical.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- No unnecessary `px-[...]`, margin `style=`, or one-off typography overrides remain.
|
||||||
|
- Interactive elements have hover plus keyboard-visible focus states.
|
||||||
|
- Typography reads as one app scale, not per-component choices.
|
||||||
|
- Data-driven inline styles remain where they carry runtime values.
|
||||||
|
- Visual changes are behavior-preserving.
|
||||||
|
- Frontend tests and build pass.
|
||||||
File renamed without changes.
+45
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
phase: 11-visual-design-responsive-layout-cleanup
|
||||||
|
plan: 6
|
||||||
|
type: execute
|
||||||
|
wave: 5
|
||||||
|
depends_on: [11-05]
|
||||||
|
requirements: [CODE-07, PERF-02]
|
||||||
|
files_modified:
|
||||||
|
- frontend/src/**/*
|
||||||
|
- .planning/perf/phase11-final.html
|
||||||
|
- .planning/perf/phase11-final-summary.md
|
||||||
|
- .planning/phases/11-visual-design-responsive-layout-cleanup/11-VERIFICATION.md
|
||||||
|
autonomous: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan 11-06 — Dead-Code Cleanup & Final Measurement
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Delete unreferenced frontend code and capture the final bundle report after all Phase 11 optimizations are complete.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
1. Run dead-code searches for unreferenced components, stores, helpers, imports, and old route views.
|
||||||
|
2. Explicitly classify these known suspects before deleting or retaining:
|
||||||
|
- `frontend/src/views/AccountView.vue` (`/account` currently redirects to `/settings`)
|
||||||
|
- `frontend/src/components/admin/__tests__/AdminAiConfigTab.test.js`
|
||||||
|
- `frontend/src/components/admin/__tests__/AdminQuotasTab.test.js`
|
||||||
|
- `frontend/src/components/admin/__tests__/AdminUsersTab.test.js`
|
||||||
|
3. Confirm `HomeView.vue`, `FolderView.vue`, and `AdminView.vue` remain absent.
|
||||||
|
4. Delete files with no active route and no active import in the same commit as their references are removed.
|
||||||
|
5. Remove unused imports and stale tests that target deleted files; keep behavior tests that still protect live surfaces.
|
||||||
|
6. Run the final analyzer build and write `.planning/perf/phase11-final.html`.
|
||||||
|
7. Add `.planning/perf/phase11-final-summary.md` comparing baseline vs final bundle size, chunk count, and largest chunks.
|
||||||
|
8. Produce `11-VERIFICATION.md` mapping every Phase 11 requirement to code/test/build evidence.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- CODE-07 is satisfied: no dead files, unused route components, unused stores, or unused imports remain.
|
||||||
|
- Known suspects are each documented as deleted or intentionally retained.
|
||||||
|
- Final bundle report and summary are committed under `.planning/perf/`.
|
||||||
|
- Baseline and final reports are both present.
|
||||||
|
- `cd frontend && npm run test -- --run` passes.
|
||||||
|
- `cd frontend && npm run build` passes.
|
||||||
|
- Phase 11 verification maps VISUAL-01..04, RESP-01..05, CODE-07, PERF-02, and PERF-03 to concrete evidence.
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user