--- 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" --- 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. @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md @.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 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')). Task 1: Add Phase 4 API functions to client.js and create folders store frontend/src/api/client.js, frontend/src/stores/folders.js 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 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 } })`. 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. - 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) client.js has all Phase 4 API wrappers; folders store is created and exported. Task 2: Extend documents store with folder/search/share actions + add Vue Router routes frontend/src/stores/documents.js, frontend/src/router/index.js 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 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. 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. - 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) Documents store extended with folder/search/share state; router has two new protected routes. ## 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 | 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 - 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 Create `.planning/phases/04-folders-sharing-quotas-document-ux/04-08-SUMMARY.md` when done.