diff --git a/.planning/STATE.md b/.planning/STATE.md
index ac89fb5..d434cf1 100644
--- a/.planning/STATE.md
+++ b/.planning/STATE.md
@@ -2,9 +2,9 @@
gsd_state_version: 1.0
milestone: v1.0
milestone_name: milestone
-current_phase: 3
-status: executing
-last_updated: "2026-05-24T19:21:17.122Z"
+current_phase: 4
+status: ready
+last_updated: "2026-05-25T00:00:00Z"
progress:
total_phases: 5
completed_phases: 3
@@ -16,9 +16,9 @@ progress:
# Project State
**Project:** DocuVault
-**Status:** Phase 3 In Progress — Plan 05 Tasks 1-2 Complete (awaiting human checkpoint)
-**Current Phase:** 3
-**Last Updated:** 2026-05-23
+**Status:** Phase 3 Complete — Ready to begin Phase 4
+**Current Phase:** 4
+**Last Updated:** 2026-05-25
## Phase Status
@@ -26,15 +26,15 @@ progress:
|---|---|---|
| 1 | Infrastructure Foundation | ✓ Complete |
| 2 | Users & Authentication | ✓ Complete (5/5 plans) |
-| 3 | Document Migration & Multi-User Isolation | In Progress (5/5 plans — checkpoint pending) |
+| 3 | Document Migration & Multi-User Isolation | ✓ Complete (5/5 plans, 10/10 UAT, security gate passed) |
| 4 | Folders, Sharing, Quotas & Document UX | Not Started |
| 5 | Cloud Storage Backends | Not Started |
## Current Position
-**Phase:** 03-document-migration-multi-user-isolation — In Progress
-**Plan:** 5/5 tasks 1-2 done; Task 3 checkpoint awaiting human verification
-**Progress:** ████░░░░░░ 57% (2/5 phases complete, 14/15 plans committed; Phase 3 checkpoint pending)
+**Phase:** 04-folders-sharing-quotas-document-ux — Ready to start
+**Plan:** 0/N — awaiting /gsd:discuss-phase 4
+**Progress:** ██████░░░░ 60% (3/5 phases complete)
## Performance Metrics
@@ -109,6 +109,22 @@ progress:
- Verify cloud SDK minor versions on PyPI before Phase 5 pinning
+### Workflow Changes (2026-05-25)
+
+Two mandatory cross-cutting gates added to all phases going forward:
+
+**1. Test gate** — every plan must leave `pytest -v` passing with zero failures. Every new function/endpoint/component requires at least one test. All security-invariant negative tests (wrong owner, admin block, token replay) must exist and pass.
+
+**2. Security gate** — a security agent runs after every plan execution and is a blocking requirement before phase advancement. It:
+- Runs `bandit -r backend/`, `pip audit`, `npm audit --audit-level=high`
+- Checks for path traversal, IDOR, SSRF, timing attacks, mass assignment, token replay
+- Verifies admin endpoints never return `password_hash`, `credentials_enc`, or document content
+- Fixes issues directly (full edit access) rather than deferring
+
+**3. Bug fix rule** — all fixes: root cause only, ≤50 lines, regression test required, no workarounds.
+
+See CLAUDE.md "Testing Protocol" and "Security Protocol" sections for full detail.
+
### Blockers
None.
@@ -119,7 +135,8 @@ _Updated at each phase transition._
| Field | Value |
|---|---|
-| Last session | 2026-05-23 — Executed Plan 03-05 (3-step XHR upload, QuotaBar, UploadProgress error block) |
-| Next action | Human checkpoint Task 3: test upload/quota/413 flow in browser; type "approved" or describe failures |
+| Last session | 2026-05-25 — Phase 3 UAT complete (10/10); security gate passed (3 fixes: bandit B324, Referrer-Policy, IDOR on /topics/suggest); test fix for test_lmstudio.py import |
+| Last session | 2026-05-25 — Phase 4 context gathered (4 areas: folder nav, sharing, PDF proxy, audit log) |
+| Next action | Run `/gsd:plan-phase 4` to create execution plan |
| Pending decisions | None |
-| Resume file | `.planning/phases/03-document-migration-multi-user-isolation/03-05-SUMMARY.md` |
+| Resume file | `.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md` |
diff --git a/.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md b/.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md
new file mode 100644
index 0000000..5049d47
--- /dev/null
+++ b/.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md
@@ -0,0 +1,154 @@
+# Phase 4: Folders, Sharing, Quotas & Document UX - Context
+
+**Gathered:** 2026-05-25
+**Status:** Ready for planning
+
+
+## Phase Boundary
+
+Deliver a complete document management experience on top of the Phase 3 multi-user foundation: folder organization (create/rename/delete/move, breadcrumb navigation, sort), document sharing by exact handle with immediate revocation, quota feedback (already-built QuotaBar, upload rejection with detailed error), PDF in-browser preview proxied through the app with a per-user open-mode preference, full-text search via PostgreSQL tsvector, and an admin audit log viewer with export and daily automated CSV backups.
+
+This phase does NOT include cloud storage backend connectivity (Phase 5). The QuotaBar component and upload rejection flow are already built (Phase 3) — Phase 4 wires the remaining document UX features around them.
+
+
+
+
+## Implementation Decisions
+
+### Folder Navigation Layout
+
+- **D-01:** Hybrid layout — AppSidebar shows top-level folders only (no nested expansion in sidebar). Once inside a folder, the main content area shows sub-folder rows + breadcrumb navigation. Top-level folders in sidebar are clickable to navigate directly.
+- **D-02:** Unlimited nesting depth — no API or UI cap. The `Folder.parent_id` self-referential FK is the authoritative constraint. Breadcrumbs truncate long paths with an ellipsis when path depth > N segments.
+- **D-03:** Non-empty folder delete shows a warning modal that includes the document count (e.g., "This folder contains 5 documents. Deleting it will permanently delete all documents inside."). If the user confirms, cascade-delete all documents (MinIO objects + DB rows + quota decrements) and the folder. If the user cancels, no action taken. Documents are NOT moved to root — they are destroyed with the folder.
+
+### Sharing
+
+- **D-04:** Exact handle input — user types the recipient's handle (no `@` prefix required by API, but UI may accept it). No autocomplete or user search endpoint needed. API returns 404 if handle not found; UI shows a clear "User not found" error.
+- **D-05:** Share button is on the DocumentCard (inline icon button). Clicking opens a sharing modal containing: (a) a text input for the recipient handle, (b) a "Share" submit button, (c) a list of current recipients with their permission level and a "Revoke" button per row.
+- **D-06:** "Shared with me" is a fixed virtual folder entry in AppSidebar, rendered above the user's own folder list. It is not stored as a real folder — the backend filters by `shares.recipient_id = current_user.id`. Shared documents count zero bytes against the recipient's quota (SHARE-02).
+- **D-07:** Share permission is `view` only for Phase 4 (SHARE-03 — owner controls permission level; only `view` implemented; `edit` deferred).
+
+### PDF Proxy & Preview
+
+- **D-08:** Streaming proxy endpoint: `GET /api/documents/{id}/content` retrieves bytes from MinIO via the storage backend and returns them via FastAPI `StreamingResponse`. Supports `Range` headers for partial content (large file performance). `Content-Disposition: inline` with correct `Content-Type` from the document's `content_type` column. No presigned URL is ever generated or exposed to the browser (DOC-02 privacy model).
+- **D-09:** Native browser PDF rendering — no PDF.js library. DOC-02 specifies PDF.js but the user explicitly chose native rendering (intent over literal spec). Content-Type header drives browser rendering. Zero frontend dependencies added.
+- **D-10:** Per-user PDF open preference stored in DB. New column `users.pdf_open_mode` (String, default `'in_app'`, allowed values: `'in_app'` | `'new_tab'`). Exposed via `PATCH /api/me/preferences` endpoint. Setting is shown in SettingsView for all users (not admin-only).
+ - `in_app`: PDF opens in a modal/overlay containing `
+
+
+## Canonical References
+
+**Downstream agents MUST read these before planning or implementing.**
+
+### Requirements
+- `.planning/REQUIREMENTS.md` — FOLD-01 through FOLD-05 (folder CRUD, breadcrumbs, sort, search), SHARE-01 through SHARE-05 (sharing by handle, virtual folder, revocation, indicator), DOC-01 (document metadata view), DOC-02 (PDF proxy, no presigned URLs), SEC-08 (credentials_enc excluded), SEC-09 (account deletion cleanup), ADMIN-06 (audit log viewer, metadata only)
+
+### Roadmap & Success Criteria
+- `.planning/ROADMAP.md` — Phase 4 goal and all 5 success criteria (especially SC2: sharing with quota enforcement on recipient, SC4: PDF bytes proxied with no presigned URL exposure, SC5: admin audit log with no document content)
+
+### Architecture Constraints
+- `CLAUDE.md` — Key Architectural Rules: bytes never through API (DOC-02 is the deliberate exception — proxy endpoint), MinIO key schema, atomic quota UPDATE, ownership assertion pattern, admin endpoints never return document content
+
+### Prior Phase Decisions
+- `.planning/phases/03-document-migration-multi-user-isolation/03-CONTEXT.md` — D-07 (atomic quota UPDATE pattern), D-16 (ownership assertion: 404 not 403 for cross-user access), D-17 (topic namespace model — context for how virtual folder pattern differs)
+
+### ORM Schema (all relevant models already exist)
+- `backend/db/models.py` — `Folder` (parent_id self-FK, user_id, name), `Document` (folder_id FK nullable), `Share` (document_id, owner_id, recipient_id, permission), `AuditLog` (user_id, actor_id, event_type, resource_id, ip_address, metadata_ JSONB), `User` (add pdf_open_mode column in Phase 4 migration)
+
+### Frontend Existing Components
+- `frontend/src/components/layout/AppSidebar.vue` — extend with Folders section + "Shared with me" entry
+- `frontend/src/components/documents/DocumentCard.vue` — add share button
+- `frontend/src/components/layout/QuotaBar.vue` — already complete (Phase 3)
+- `frontend/src/stores/documents.js` — extend with folder/search/sharing actions
+- `frontend/src/api/client.js` — add folder, share, search, PDF proxy, preferences API calls
+
+
+
+
+## Existing Code Insights
+
+### Reusable Assets
+- `backend/deps/auth.py` — `get_current_user` (regular user) and `get_current_admin` (admin only) — inject into all new folder/share/audit endpoints
+- `backend/db/models.py` — `Folder`, `Share`, `AuditLog` ORM models are complete; `Document.folder_id` FK exists; no new tables needed for Phase 4 except adding `users.pdf_open_mode` column
+- `backend/storage/minio_backend.py` — `MinIOBackend.get_object()` exists (or add it); streaming proxy reads from MinIO and passes bytes to `StreamingResponse`
+- `backend/celery_app.py` — Celery beat schedule: add daily audit log export task alongside existing abandoned-upload cleanup
+- `frontend/src/components/layout/QuotaBar.vue` — already implemented with amber/red thresholds; no changes needed
+- `frontend/src/components/upload/DropZone.vue` — upload 413 rejection error with payload already implemented
+
+### Established Patterns
+- **Ownership assertion** — `doc.user_id == current_user.id` → raise `HTTPException(404)` (D-16 from Phase 3); same pattern applies to folder endpoints
+- **Atomic quota UPDATE** — `UPDATE quotas SET used_bytes = ... WHERE ... RETURNING used_bytes`; use same pattern for quota decrement on folder cascade-delete
+- **`get_regular_user` dep (403 for admin)** — enforced on all document/folder/share endpoints; admin role cannot read document content
+- **`write_audit_log()` helper** — new shared function in `backend/services/audit.py` (or `backend/db/audit.py`); called inline in handlers after successful operation
+- **`asyncio.to_thread()`** — for MinIO sync SDK calls in streaming proxy (established pattern from Phase 1)
+- **Pydantic response models** — explicit whitelist pattern (`_user_to_dict()` equivalent) already established for admin responses; extend for CloudConnection serializers (SEC-08)
+
+### Integration Points
+- `backend/api/documents.py` — add `GET /api/documents/{id}/content` streaming proxy; add `q` query param to list endpoint for tsvector search; add audit log calls to upload/delete/reclassify handlers
+- `backend/api/` — add `backend/api/folders.py` (folder CRUD + move), `backend/api/shares.py` (share grant/revoke/list), `backend/api/audit.py` (admin audit log viewer + export)
+- `backend/api/auth.py` + `backend/api/admin.py` — add `write_audit_log()` calls to auth events and admin user management actions (Phase 4 back-fills audit writing for previously unlogged operations)
+- `backend/migrations/versions/0004_phase4_*.py` — add `users.pdf_open_mode` column; add `tsvector` GIN index on `documents.extracted_text`; create `audit-logs` MinIO bucket in migration post-DDL step
+- `frontend/src/stores/documents.js` — add `fetchFolder(folderId)`, `createFolder()`, `moveDocument()`, `shareDocument()`, `revokeShare()`, `searchDocuments(q)` actions
+- `frontend/src/views/HomeView.vue` — wire folder navigation state (current folder ID), breadcrumb component, folder rows in list
+- `frontend/src/views/SettingsView.vue` — add PDF open mode toggle (currently a static placeholder per Phase 3 Risk 6 decision)
+
+### Constraints from Prior Phases
+- MinIO key schema `{user_id}/{document_id}/{uuid4()}{ext}` is locked — proxy endpoint uses stored `object_key`, never reconstructs it
+- `get_regular_user` returns 403 for admin role on document endpoints — streaming proxy must also use `get_regular_user`
+- Cross-user doc access returns 404 not 403 (D-16) — same rule applies to folder and share access attempts
+- `documents.user_id` is NOT NULL (Phase 3 migration) — no null-user guards needed
+
+
+
+
+## Specific Ideas
+
+- Folder delete warning modal: shows count string like "This folder contains 5 documents. Deleting it will permanently delete all documents inside. This cannot be undone." — two buttons: "Delete folder and documents" (destructive, red) / "Cancel"
+- Share modal layout: handle input at top → "Share" button → separator → list of current shares with handle + permission badge + "Revoke" button per row. Empty state: "Not shared with anyone yet."
+- "Shared with me" sidebar entry: fixed position above user-created folders, shows inbox-like icon, displays document count badge when non-empty
+- PDF open mode setting in SettingsView: simple toggle/radio in a "Document Preferences" card section ("Open documents in-app" / "Open documents in new tab")
+- Audit log CSV filename convention: `audit-logs/YYYY-MM-DD.csv` in MinIO `audit-logs` bucket
+- tsvector search: `documents.extracted_text` GIN index; query via `plainto_tsquery('english', :q)` for natural-language matching (simpler than `to_tsquery` which requires operators)
+- Breadcrumb truncation: show first segment + "..." + last 2 segments when depth > 4
+
+
+
+
+## Deferred Ideas
+
+- **Per-day document scan quota tiers** — User wants subscription tiers based on number of AI scans per day rather than (or in addition to) storage bytes. New billing/subscription model capability — separate milestone/phase. Does not affect Phase 4 implementation.
+- **Share permission levels beyond `view`** — SHARE-03 says owner controls permission level; only `view` is implemented in Phase 4. `edit` and `comment` permissions deferred to a future iteration.
+- **Presigned GET URLs for non-PDF document download** — Phase 3 deferred this; Phase 4 adds the proxy endpoint for PDFs. For other formats (DOCX, TXT), streaming proxy via the same endpoint is the approach; no direct presigned URLs exposed.
+- **Audit log row archival/deletion policy** — Daily CSV backup to MinIO manages growth by providing an export, but DB rows are not auto-deleted in Phase 4. Long-term archival/TTL policy deferred.
+
+
+
+---
+
+*Phase: 4-Folders-Sharing-Quotas-Document-UX*
+*Context gathered: 2026-05-25*
diff --git a/.planning/phases/04-folders-sharing-quotas-document-ux/04-DISCUSSION-LOG.md b/.planning/phases/04-folders-sharing-quotas-document-ux/04-DISCUSSION-LOG.md
new file mode 100644
index 0000000..daf83e9
--- /dev/null
+++ b/.planning/phases/04-folders-sharing-quotas-document-ux/04-DISCUSSION-LOG.md
@@ -0,0 +1,142 @@
+# Phase 4: Folders, Sharing, Quotas & Document UX - Discussion Log
+
+> **Audit trail only.** Do not use as input to planning, research, or execution agents.
+> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
+
+**Date:** 2026-05-25
+**Phase:** 04-folders-sharing-quotas-document-ux
+**Areas discussed:** Folder navigation layout, Sharing discovery UX, PDF proxy & PDF.js approach, Audit log event catalog
+
+---
+
+## Folder navigation layout
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Sidebar tree | Collapsible full tree in AppSidebar; always visible; click to navigate | |
+| Main area drill-down | Folders as cards in main area; no sidebar tree | |
+| Hybrid: top-level in sidebar + breadcrumb in main | Sidebar shows top-level folders only; sub-folder rows + breadcrumbs in main area | ✓ |
+
+**User's choice:** Hybrid — top-level folders in sidebar, breadcrumb + sub-folder rows in main content area
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Unlimited nesting | No API/UI cap; breadcrumbs truncate with ellipsis | ✓ |
+| 2 levels max | API enforces depth limit; simpler breadcrumbs | |
+| You decide | Defer to planner | |
+
+**User's choice:** Unlimited nesting
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Move docs to root before deleting | Documents survive, lose folder assignment | |
+| Cascade delete — docs + folder deleted together | Warning shows count; user confirms | ✓ |
+| Block delete if non-empty | 409 response; user must empty manually first | |
+
+**User's choice:** Show warning modal with document count; user confirms → cascade-delete documents and folder
+**Notes:** User explicitly said "Let the user accept or decline" — warning with confirm/cancel. Confirmed that delete means permanent removal of documents (not move to root) when clarified.
+
+---
+
+## Sharing discovery UX
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Exact handle input | Type full handle; no suggestions; no search endpoint | ✓ |
+| Live autocomplete | Type-to-search dropdown; requires GET /api/users/search | |
+
+**User's choice:** Exact handle input — no autocomplete, no search endpoint
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Inline button on DocumentCard + modal | Share icon on card opens modal with handle input + share list | ✓ |
+| Document detail view action | Sharing only from DocumentView, not card | |
+
+**User's choice:** Share button on DocumentCard, opens modal
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Fixed entry in AppSidebar above user folders | Always visible, virtual folder concept | ✓ |
+| Separate route / dedicated view | Accessible via /shared; not in main sidebar | |
+| Filtered view on main document list | Toggle/tab on HomeView | |
+
+**User's choice:** Fixed "Shared with me" entry in AppSidebar above user folders
+
+---
+
+## PDF proxy & PDF.js approach
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Streaming proxy endpoint | GET /api/documents/{id}/content; StreamingResponse; no presigned URL exposed | ✓ |
+| Server-side redirect with signed URL | 302 redirect to short-lived presigned URL | |
+
+**User's choice:** Streaming proxy endpoint
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| pdfjs-dist npm package — Vue component | Bundled PDF.js; ~800KB gzip; canvas rendering | |
+| PDF.js pre-built viewer via iframe | Static HTML viewer served from backend | |
+| Native browser PDF rendering | Content-Disposition: inline; zero dependencies | ✓ |
+
+**User's choice:** Native browser PDF rendering
+
+**Notes:** DOC-02 literally specifies PDF.js. User was informed of the discrepancy and chose to prioritize intent (privacy model) over the literal requirement. Claude flagged this before the user confirmed.
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| In-app modal/panel | iframe in modal overlay | |
+| New browser tab | window.open() | |
+| You decide | Defer to planner | |
+
+**User's choice (follow-up):** Per-user preference setting — users choose whether PDFs open in-app (iframe modal) or in a new browser tab
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| localStorage — per-browser | No backend work; simple | |
+| DB-backed — synced across devices | users.pdf_open_mode column + PATCH /api/me/preferences | ✓ |
+
+**User's choice:** DB-backed preference (synced across all user devices)
+
+---
+
+## Audit log event catalog
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Auth events | Login, logout, password change, TOTP, backup codes | ✓ |
+| Document operations | Upload (no filename), delete, reclassify | ✓ |
+| Folder & share operations | Folder create/delete, share grant/revoke | ✓ |
+| Admin actions | User create, deactivate, quota change, AI config | ✓ |
+
+**User's choice:** All 4 categories
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Inline in each API handler | Explicit per-handler; shared write_audit_log() helper | ✓ |
+| FastAPI middleware | Centralized but hard to add contextual data | |
+| Celery background task | Async but adds latency and complexity | |
+
+**User's choice:** Inline in each handler with shared helper
+
+| Option | Description | Selected |
+|--------|-------------|----------|
+| Filters only — paginated table | Date range, user, action type; paginated | |
+| Filters + export (CSV/JSON) | Same + download button | ✓ |
+| You decide | Defer to planner | |
+
+**User's choice:** Filters + export + daily automated CSV backup to MinIO `audit-logs` bucket
+**Notes:** User requested daily automatic CSV backup in addition to manual export, specifically to manage log file size over time. Backup stored in MinIO (not local filesystem) for persistence.
+
+---
+
+## Claude's Discretion
+
+- Search UX placement (search bar in existing document list vs. dedicated route) — decided inline filter as standard approach per Phase 4 domain
+- Breadcrumb truncation threshold — N > 4 segments shows first + "..." + last 2
+
+## Deferred Ideas
+
+- **Per-day document scan quota tiers** — User wants subscription tiers based on daily AI scan count, not just storage. Business model idea: "having a local storage quota might get some subscribers but connecting your google drive defeats the purpose." Recommended as a separate billing/subscription milestone.
+- **Share permission levels beyond `view`** — `edit`/`comment` permission deferred.
+- **Audit log row archival/TTL policy** — Daily CSV backup provides export; DB row deletion policy deferred.