docs(04): create phase 4 plan (9 plans, 7 waves)

Folders, Sharing, Quotas & Document UX — plans verified (0 blockers,
2 non-blocking warnings). Covers FOLD-01..05, SHARE-01..05, SEC-08/09,
ADMIN-06, DOC-01/02.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
curo1305
2026-05-25 18:20:16 +02:00
parent 752cf987aa
commit 747303246a
14 changed files with 4832 additions and 11 deletions
@@ -0,0 +1,243 @@
---
phase: 04-folders-sharing-quotas-document-ux
plan: 08
type: execute
wave: 6
depends_on:
- "04-07"
files_modified:
- frontend/src/api/client.js
- frontend/src/stores/folders.js
- frontend/src/stores/documents.js
- frontend/src/router/index.js
autonomous: true
requirements:
- FOLD-01
- FOLD-02
- FOLD-03
- FOLD-04
- FOLD-05
- SHARE-01
- SHARE-02
- SHARE-03
- SHARE-04
- SHARE-05
- DOC-01
- DOC-02
must_haves:
truths:
- "All backend API endpoints have matching client.js wrapper functions"
- "useFoldersStore exposes state and actions for folder CRUD and navigation"
- "useDocumentsStore extended with search, sort, folderId, shareDocument, revokeShare, listShares"
- "Vue Router has routes for /folders/:folderId and /shared"
- "Access token injected via existing request() helper — no new auth logic added"
artifacts:
- path: "frontend/src/api/client.js"
provides: "listFolders, createFolder, renameFolder, deleteFolder, moveDocument, createShare, deleteShare, listShares, getSharedWithMe, getMyPreferences, updateMyPreferences, getDocumentContentUrl"
- path: "frontend/src/stores/folders.js"
provides: "useFoldersStore: folders, currentFolderId, breadcrumb, loading, error + folder CRUD actions"
- path: "frontend/src/stores/documents.js"
provides: "extended with folderId, searchQuery, sort, order state + share actions"
- path: "frontend/src/router/index.js"
provides: "/folders/:folderId route + /shared route"
key_links:
- from: "frontend/src/stores/folders.js"
to: "frontend/src/api/client.js"
via: "all folder CRUD actions call api.* functions"
pattern: "import.*api"
- from: "frontend/src/router/index.js"
to: "frontend/src/views/FolderView.vue"
via: "route /folders/:folderId component lazy-loaded"
pattern: "FolderView"
---
<objective>
Wire the frontend data layer: API client functions, Pinia stores, and router routes for all
Phase 4 backend endpoints. This plan creates no UI components — those are in plan 04-09.
Stores and API client are the contracts the UI components depend on.
Purpose: Establish frontend data contracts before writing view and component code.
Output: client.js additions + folders store + extended documents store + new routes.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/phases/04-folders-sharing-quotas-document-ux/04-CONTEXT.md
@.planning/phases/04-folders-sharing-quotas-document-ux/04-PATTERNS.md
@.planning/phases/04-folders-sharing-quotas-document-ux/04-UI-SPEC.md
@frontend/src/api/client.js
@frontend/src/stores/documents.js
@frontend/src/stores/topics.js
@frontend/src/router/index.js
</context>
<interfaces>
From frontend/src/api/client.js: read actual file to confirm request() signature.
Pattern: all exported functions call the internal request() helper which injects
the access token from the auth store and handles 401 refresh.
From frontend/src/stores/topics.js: full defineStore pattern to replicate.
State refs: typed refs, loading, error. Actions: async try/catch/finally pattern.
From frontend/src/router/index.js: read to find the route array and lazy-import
pattern for view components (e.g., () => import('../views/SomeView.vue')).
</interfaces>
<tasks>
<task type="auto">
<name>Task 1: Add Phase 4 API functions to client.js and create folders store</name>
<files>frontend/src/api/client.js, frontend/src/stores/folders.js</files>
<read_first>
frontend/src/api/client.js — read the ENTIRE file; identify the request() helper signature; find the last exported function; note if any listFolders or share functions already exist
frontend/src/stores/topics.js — read ENTIRE file for the exact defineStore + ref + async action pattern
frontend/src/stores/documents.js — read lines 1-30 to confirm existing imports
</read_first>
<action>
Modify frontend/src/api/client.js: APPEND these named export functions after existing functions. Follow the existing named-export style exactly.
Folder API: listFolders(parentId = null) → GET /api/folders with optional ?parent_id= query param; createFolder(name, parentId = null) → POST /api/folders with JSON body {name, parent_id: parentId || null}; getFolder(folderId) → GET /api/folders/{folderId}; renameFolder(folderId, name) → PATCH /api/folders/{folderId} with JSON {name}; deleteFolder(folderId) → DELETE /api/folders/{folderId}; moveDocument(docId, folderId) → PATCH /api/documents/{docId}/folder with JSON {folder_id: folderId || null}.
Share API: createShare(docId, recipientHandle) → POST /api/shares with JSON {document_id: docId, recipient_handle: recipientHandle}; listShares(docId) → GET /api/shares?document_id={docId}; deleteShare(shareId) → DELETE /api/shares/{shareId}; getSharedWithMe() → GET /api/shares/received.
Preference API: getMyPreferences() → GET /api/auth/me/preferences; updateMyPreferences(payload) → PATCH /api/auth/me/preferences with JSON payload.
PDF proxy URL helper: getDocumentContentUrl(docId) → returns the string `/api/documents/${docId}/content` — pure string, no fetch. Used for iframe src or window.open.
For listDocuments (the existing function): read its current signature. If it does not already accept folderId, q, sort, order params (added in plan 04-03), update it to pass these as query params via URLSearchParams, including params only when non-null and non-empty.
Create frontend/src/stores/folders.js as a NEW file. Follow the exact useTopicsStore pattern.
Imports: `import { defineStore } from 'pinia'`, `import { ref } from 'vue'`, `import * as api from '../api/client.js'`.
State refs: folders (ref([])), currentFolderId (ref(null)), breadcrumb (ref([])), loading (ref(false)), error (ref(null)).
Actions:
- fetchFolders(parentId = null): calls api.listFolders(parentId); sets folders.value
- createFolder(name, parentId = null): calls api.createFolder(name, parentId); pushes result to folders.value
- renameFolder(folderId, name): calls api.renameFolder; updates matching entry in folders.value
- deleteFolder(folderId): calls api.deleteFolder; removes from folders.value
- navigateTo(folderId): sets currentFolderId.value = folderId; if folderId is not null, calls api.getFolder(folderId) and sets breadcrumb.value = response.breadcrumb; if folderId is null, sets breadcrumb.value = []
All actions follow the loading/error/finally pattern from topics store.
Export: `export const useFoldersStore = defineStore('folders', () => { ... return { folders, currentFolderId, breadcrumb, loading, error, fetchFolders, createFolder, renameFolder, deleteFolder, navigateTo } })`.
</action>
<verify>
Run: grep -n "listFolders\|createShare\|getMyPreferences\|getDocumentContentUrl\|getSharedWithMe" /Users/nik/Documents/Progamming/document_scanner/frontend/src/api/client.js
Expected: all five function names appear in the grep output.
Also: grep -n "useFoldersStore\|navigateTo\|currentFolderId" /Users/nik/Documents/Progamming/document_scanner/frontend/src/stores/folders.js
Expected: all three identifiers appear.
</verify>
<acceptance_criteria>
- frontend/src/api/client.js contains: listFolders, createFolder, getFolder, renameFolder, deleteFolder, moveDocument, createShare, listShares, deleteShare, getSharedWithMe, getMyPreferences, updateMyPreferences, getDocumentContentUrl (13 new functions — grep confirms each name)
- frontend/src/stores/folders.js exists and exports useFoldersStore
- useFoldersStore returns: folders, currentFolderId, breadcrumb, loading, error, fetchFolders, createFolder, renameFolder, deleteFolder, navigateTo
- listDocuments in client.js accepts folderId, q, sort, order params
- No existing API functions are modified (only appended to)
</acceptance_criteria>
<done>client.js has all Phase 4 API wrappers; folders store is created and exported.</done>
</task>
<task type="auto">
<name>Task 2: Extend documents store with folder/search/share actions + add Vue Router routes</name>
<files>frontend/src/stores/documents.js, frontend/src/router/index.js</files>
<read_first>
frontend/src/stores/documents.js — read the ENTIRE file; identify fetchDocuments signature; find existing actions (upload, remove, etc.); find the return statement at the end to know what to add to it
frontend/src/router/index.js — read the ENTIRE file; identify the routes array; find the lazy import pattern; note auth guard usage (requiresAuth meta, beforeEach hook) to replicate for new routes
</read_first>
<action>
Modify frontend/src/stores/documents.js:
Add new refs (insert after existing state refs, before existing actions):
- currentFolderId: ref(null)
- searchQuery: ref('')
- sortField: ref('date') — 'name' | 'date' | 'size'
- sortOrder: ref('desc') — 'asc' | 'desc'
Add debounced search watcher (RESEARCH.md Pattern 10 — no lodash):
After existing watch declarations (or before return), add:
let _searchTimer = null
watch(searchQuery, (newVal) => {
clearTimeout(_searchTimer)
if (newVal.length < 2) { fetchDocuments({ folderId: currentFolderId.value }); return }
_searchTimer = setTimeout(() => { fetchDocuments({ q: newVal, folderId: currentFolderId.value, sort: sortField.value, order: sortOrder.value }) }, 300)
})
Update fetchDocuments() to accept and pass {folderId, q, sort, order} params to api.listDocuments().
Add new actions (append before return statement):
- shareDocument(docId, recipientHandle): try { return await api.createShare(docId, recipientHandle) } catch (e) { throw e }
- revokeShare(shareId): try { await api.deleteShare(shareId) } catch (e) { throw e }
- listShares(docId): try { return await api.listShares(docId) } catch (e) { throw e }
Add new state/actions to the return statement: currentFolderId, searchQuery, sortField, sortOrder, shareDocument, revokeShare, listShares.
Modify frontend/src/router/index.js:
Add two new route objects to the routes array. Follow the exact lazy-import pattern from existing routes. Apply the same requiresAuth guard as existing protected routes.
Route 1: { path: '/folders/:folderId', name: 'folder', component: () => import('../views/FolderView.vue'), meta: { requiresAuth: true } }
Route 2: { path: '/shared', name: 'shared', component: () => import('../views/SharedView.vue'), meta: { requiresAuth: true } }
Note: FolderView.vue and SharedView.vue do not exist yet (created in plan 04-09). The router import is lazy-loaded so missing files do not cause import errors at router init — only at navigation time.
</action>
<verify>
Run: grep -n "currentFolderId\|searchQuery\|sortField\|shareDocument\|revokeShare" /Users/nik/Documents/Progamming/document_scanner/frontend/src/stores/documents.js
Expected: all five identifiers appear.
Also: grep -n "FolderView\|SharedView\|/folders\|/shared" /Users/nik/Documents/Progamming/document_scanner/frontend/src/router/index.js
Expected: both view names and both paths appear.
</verify>
<acceptance_criteria>
- documents.js contains: currentFolderId ref, searchQuery ref, sortField ref, sortOrder ref
- documents.js contains debounced watch on searchQuery with 300ms timeout and 2-char minimum
- documents.js return statement includes: currentFolderId, searchQuery, sortField, sortOrder, shareDocument, revokeShare, listShares
- router/index.js contains /folders/:folderId route and /shared route
- Both new routes use lazy component import and requiresAuth meta (grep confirms)
- No existing routes modified (only new routes appended)
</acceptance_criteria>
<done>Documents store extended with folder/search/share state; router has two new protected routes.</done>
</task>
</tasks>
<threat_model>
## Trust Boundaries
| Boundary | Description |
|----------|-------------|
| Frontend store → backend API | All requests go through request() helper which injects JWT; no auth logic duplicated in new stores |
| Vue Router → view components | New routes use same requiresAuth guard as existing protected routes |
## STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-04-08-01 | Broken Access Control | /folders/:folderId route without auth guard | mitigate | requiresAuth: true meta applied to both new routes; same router beforeEach guard as existing protected routes |
| T-04-08-02 | Information Disclosure | Access token stored in localStorage via new store | accept | All token handling is in existing auth store + request() helper; folders store and documents store extension do not touch auth state |
| T-04-08-03 | Tampering | Debounced search fires before 2-char minimum | mitigate | searchQuery watch checks newVal.length < 2 before firing API call; short inputs restore full list without API call |
| T-04-SC | Tampering | npm/pip/cargo installs | accept | No new packages installed in this plan |
</threat_model>
<verification>
1. API functions: grep -c "export function" /Users/nik/Documents/Progamming/document_scanner/frontend/src/api/client.js
2. Folders store: grep -n "useFoldersStore\|navigateTo\|breadcrumb" /Users/nik/Documents/Progamming/document_scanner/frontend/src/stores/folders.js
3. Documents store: grep -n "currentFolderId\|shareDocument\|searchQuery" /Users/nik/Documents/Progamming/document_scanner/frontend/src/stores/documents.js
4. Router: grep -n "FolderView\|SharedView" /Users/nik/Documents/Progamming/document_scanner/frontend/src/router/index.js
</verification>
<success_criteria>
- All 13 new API functions exist in client.js (verified by grep)
- useFoldersStore is created and exports folder CRUD actions + navigation state
- documents store return includes all new state/action exports
- Vue Router has /folders/:folderId and /shared routes with requiresAuth guard
</success_criteria>
<output>
Create `.planning/phases/04-folders-sharing-quotas-document-ux/04-08-SUMMARY.md` when done.
</output>