Files
kite/.planning/milestones/v0.2-phases/08-stack-upgrade-backend-decomposition/08-07-PLAN.md
T
curo1305andClaude Sonnet 4.6 123ae5b29b chore: archive v0.2 phase directories to milestones/v0.2-phases/
Moves phases 08–11 execution artifacts from .planning/phases/ to
.planning/milestones/v0.2-phases/ to keep .planning/phases/ clean
for the next milestone.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-17 14:34:52 +02:00

25 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, tags, must_haves
phase plan type wave depends_on files_modified autonomous requirements tags must_haves
08-stack-upgrade-backend-decomposition 07 execute 2
08-03
frontend/src/api/utils.js
frontend/src/api/documents.js
frontend/src/api/auth.js
frontend/src/api/admin.js
frontend/src/api/folders.js
frontend/src/api/shares.js
frontend/src/api/cloud.js
frontend/src/api/topics.js
frontend/src/api/client.js
true
CODE-04
CODE-08
frontend-decomposition
api-client
barrel-reexport
truths artifacts key_links
frontend/src/api/utils.js exists, exports request(), exports fetchWithRetry() — the consolidated 401-retry helper
Each of documents.js / auth.js / admin.js / folders.js / shares.js / cloud.js / topics.js exists and imports `request` from './utils.js'
fetchWithRetry() consolidates the 3 blob-download patterns (adminExportAuditLogCsv, adminDownloadDailyExport, fetchDocumentContent) into one helper
client.js is reduced to a barrel re-export: `export * from './documents.js'` etc., plus `export { fetchWithRetry, request } from './utils.js'`
Every function name from the old client.js is exported by exactly one domain module (no duplication, no missing names)
Zero consumer files (35+) under frontend/src/stores/ frontend/src/components/ frontend/src/views/ are modified — all `import * as api from '../api/client.js'` and `import { name } from '../api/client.js'` continue to resolve
Frontend test suite passes
path provides contains
frontend/src/api/utils.js HTTP transport: request() with bearer injection + 401-refresh-retry, fetchWithRetry() for raw Response endpoints export async function request
path provides contains
frontend/src/api/documents.js Document domain functions including fetchDocumentContent (now using fetchWithRetry) export function listDocuments
path provides contains
frontend/src/api/auth.js Auth domain functions: login, register, refreshToken, logout, totp*, password*, preferences, quota export function login
path provides contains
frontend/src/api/admin.js Admin domain functions including blob-download admin endpoints export function adminListUsers
path provides contains
frontend/src/api/folders.js Folder domain functions export function listFolders
path provides contains
frontend/src/api/shares.js Share domain functions export function createShare
path provides contains
frontend/src/api/cloud.js Cloud connection domain functions including initiateOAuth export function listCloudConnections
path provides contains
frontend/src/api/topics.js Topic domain functions export function listTopics
path provides contains
frontend/src/api/client.js Barrel re-export — preserves zero-change consumer contract export * from './documents.js'
from to via pattern
frontend/src/api/client.js all 7 domain modules + utils.js export * from export * from './(documents|auth|admin|folders|shares|cloud|topics).js'
from to via pattern
each domain module frontend/src/api/utils.js import { request } from './utils.js' import { request
from to via pattern
blob-download functions (admin.js, documents.js) frontend/src/api/utils.js fetchWithRetry import { fetchWithRetry } from './utils.js' import {.*fetchWithRetry
Decompose the 635-line `frontend/src/api/client.js` monolith into 7 domain modules + `utils.js` (HTTP transport + 401-retry consolidation) per locked D-12/D-13/D-14. `client.js` is reduced to a barrel re-export so the 35+ consumer files do not require any edits. The 3 blob-download 401-retry copy-paste patterns are consolidated into a single `fetchWithRetry()` helper in `utils.js`.

Purpose: CODE-04 (decomposition) + CODE-08 (single definition of the auth+retry pattern instead of 3 copies).

Output: 1 new utils.js, 7 new domain modules, client.js reduced to ~12 lines.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-CONTEXT.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-RESEARCH.md @.planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md @frontend/src/api/client.js @CLAUDE.md Function to domain map (locked per RESEARCH.md §"Frontend API Client Decomposition" and PATTERNS.md §"frontend/src/api/*"):

utils.js: request(path, options) — moved from client.js lines 11-57 to break circular dep fetchWithRetry(url, options) — new consolidator for blob-download 401-retry

documents.js (consumers: stores/documents.js, DocumentCard.vue, DocumentView.vue, DocumentPreviewModal.vue, etc): listDocuments, getDocument, deleteDocument, deleteDocumentRemoveOnly, classifyDocument, getUploadUrl, confirmUpload, uploadToCloud, fetchDocumentContent (refactored to use fetchWithRetry), getDocumentContentUrl

auth.js (consumers: stores/auth.js, SettingsAccountTab.vue, LoginView.vue, etc): login, register, refreshToken, logout, logoutAll, getMe, changePassword, totpSetup, totpEnable, totpDisable, passwordResetRequest, passwordResetConfirm, getMyPreferences, updateMyPreferences, getMyQuota

admin.js (consumers: AdminUsersTab.vue, AdminQuotasTab.vue, AdminAiConfigTab.vue, AuditLogTab.vue, etc): adminListUsers, adminCreateUser, adminDeactivateUser, adminReactivateUser, adminResetUserPassword, adminGetUserQuota, adminUpdateQuota, adminUpdateAiConfig, adminDeleteUser, getAiConfig, saveAiConfig, testAiConnection, getAiModels, adminListAuditLog, adminExportAuditLogCsv (refactored to use fetchWithRetry), adminListDailyExports, adminDownloadDailyExport (refactored to use fetchWithRetry)

folders.js (consumers: stores/folders.js, FolderTreeItem.vue, AppSidebar.vue): listFolders, createFolder, getFolder, renameFolder, deleteFolder, moveDocument

shares.js (consumers: SharedView.vue, ShareModal.vue): createShare, updateSharePermission, listShares, deleteShare, getSharedWithMe

cloud.js (consumers: stores/cloudConnections.js, SettingsCloudTab.vue, CloudCredentialModal.vue, CloudProviderTreeItem.vue, CloudFolderTreeItem.vue): listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage, getCloudFolders, initiateOAuth, getConnectionConfig

topics.js (consumers: stores/topics.js, SearchableModelSelect.vue is unrelated): listTopics, createTopic, updateTopic, deleteTopic, suggestTopics

client.js (after refactor — barrel only): export * from './documents.js' export * from './auth.js' export * from './admin.js' export * from './folders.js' export * from './shares.js' export * from './cloud.js' export * from './topics.js' export { fetchWithRetry, request } from './utils.js'

Critical: All function names across these 8 files are unique — no export * collisions (RESEARCH.md §Pitfall 5 confirms).

Task 1: Create utils.js with request() and fetchWithRetry() frontend/src/api/utils.js - frontend/src/api/client.js lines 11-57 (current `request` function — copy verbatim) - frontend/src/api/client.js lines 428-471 (adminExportAuditLogCsv blob pattern) - frontend/src/api/client.js lines 492-529 (adminDownloadDailyExport blob pattern) - frontend/src/api/client.js lines 552-581 (fetchDocumentContent blob pattern) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/api/utils.js" (full code template for both functions) Create `frontend/src/api/utils.js`. Add a top-of-file JSDoc comment explaining: "HTTP transport + 401-retry consolidator. `request()` moved from client.js to break circular dependency (domain modules import request from here; client.js re-exports from domain modules). `fetchWithRetry()` consolidates 3 blob-download patterns. Security: Bearer from authStore memory only (CLAUDE.md)."

(A) Export async function request(path, options = {}) — copy the body verbatim from client.js lines 11-57. Keep the lazy await import('../stores/auth.js') to avoid the Pinia bootstrap cycle. Keep the noRefreshPaths array as a local const inside the function (or as a module-level const above the function — either works, but match the original pattern from client.js).

(B) Export async function fetchWithRetry(url, options = {}, _retry = false) per PATTERNS.md §"frontend/src/api/utils.js" template. The function: lazy-imports useAuthStore, copies headers, injects Authorization: Bearer ${authStore.accessToken} if present, calls fetch(url, { ...options, headers, credentials: 'include' }), on 401-and-not-already-retried calls authStore.refresh() and recurses with _retry=true, on refresh failure clears authStore.accessToken + authStore.user and throws 'Session expired', otherwise returns the raw Response (caller decides how to consume it — text(), blob(), etc.).

Do NOT modify client.js in this task — task 9 owns the barrel rewrite. cd frontend && node -e "import('./src/api/utils.js').then(m => { if (typeof m.request !== 'function') throw new Error('request missing'); if (typeof m.fetchWithRetry !== 'function') throw new Error('fetchWithRetry missing'); console.log('ok'); }).catch(e => { console.error(e); process.exit(1); })" <acceptance_criteria> - File frontend/src/api/utils.js exists - grep -c "export async function request" frontend/src/api/utils.js returns 1 - grep -c "export async function fetchWithRetry" frontend/src/api/utils.js returns 1 - grep -c "await import('../stores/auth.js')" frontend/src/api/utils.js returns at least 1 (lazy import preserved) - grep -c "noRefreshPaths" frontend/src/api/utils.js returns at least 1 - grep -c "_retry" frontend/src/api/utils.js returns at least 2 (request uses options._retry, fetchWithRetry uses positional _retry) </acceptance_criteria> utils.js exists with both helpers, identifiable by export grep, module loads without error.

Task 2: Create domain modules documents.js, auth.js, topics.js frontend/src/api/documents.js, frontend/src/api/auth.js, frontend/src/api/topics.js - frontend/src/api/client.js (full file — confirm exact function bodies and signatures for each function listed in the function-to-domain map in <interfaces>) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/api/documents.js" and §"frontend/src/api/auth.js" For each file, write a small header docstring naming the domain (e.g., "Document API — listing, fetching, uploading, content streaming"). Add `import { request, fetchWithRetry } from './utils.js'` for documents.js (because fetchDocumentContent uses fetchWithRetry); for auth.js and topics.js use only `import { request } from './utils.js'`.

(A) documents.js: move these functions VERBATIM from client.js (preserve every parameter, every default value, every URL path, every body shape): listDocuments, getDocument, deleteDocument, deleteDocumentRemoveOnly, classifyDocument, getUploadUrl, confirmUpload, uploadToCloud, getDocumentContentUrl. Then refactor fetchDocumentContent to use fetchWithRetry: the new body becomes export async function fetchDocumentContent(docId, options = {}) { const res = await fetchWithRetry('/api/documents/' + docId + '/content', options); if (!res.ok) throw new Error('Failed to fetch document content: ' + res.status); return res; } — preserving the public contract (returns raw Response, throws on non-ok).

(B) auth.js: move these functions VERBATIM: login, register, refreshToken, logout, logoutAll, getMe, changePassword, totpSetup, totpEnable, totpDisable, passwordResetRequest, passwordResetConfirm, getMyPreferences, updateMyPreferences, getMyQuota.

(C) topics.js: move these functions VERBATIM: listTopics, createTopic, updateTopic, deleteTopic, suggestTopics.

Do NOT remove the functions from client.js yet — task 9 owns the barrel rewrite and deletion of the original bodies. This task only adds new files. cd frontend && node -e "Promise.all([import('./src/api/documents.js'), import('./src/api/auth.js'), import('./src/api/topics.js')]).then(([d, a, t]) => { ['listDocuments','getDocument','deleteDocument','classifyDocument','getUploadUrl','fetchDocumentContent'].forEach(n => { if (typeof d[n] !== 'function') throw new Error('documents.' + n + ' missing'); }); ['login','register','refreshToken','logout','getMe','changePassword','totpSetup','passwordResetRequest','getMyQuota'].forEach(n => { if (typeof a[n] !== 'function') throw new Error('auth.' + n + ' missing'); }); ['listTopics','createTopic','suggestTopics'].forEach(n => { if (typeof t[n] !== 'function') throw new Error('topics.' + n + ' missing'); }); console.log('ok'); }).catch(e => { console.error(e); process.exit(1); })" <acceptance_criteria> - Files exist: frontend/src/api/documents.js, frontend/src/api/auth.js, frontend/src/api/topics.js - grep -c "^export function listDocuments\\|^export async function fetchDocumentContent" frontend/src/api/documents.js returns at least 2 - grep -c "import { request, fetchWithRetry } from './utils.js'" frontend/src/api/documents.js returns 1 - grep -c "import { request } from './utils.js'" frontend/src/api/auth.js returns 1 - grep -c "import { request } from './utils.js'" frontend/src/api/topics.js returns 1 - grep -c "^export function login\\|^export function register" frontend/src/api/auth.js returns at least 2 - The node -e import check above prints ok (all named exports resolvable) </acceptance_criteria> Three domain files exist with all expected exports; verified by node import.

Task 3: Create domain modules admin.js, folders.js, shares.js, cloud.js frontend/src/api/admin.js, frontend/src/api/folders.js, frontend/src/api/shares.js, frontend/src/api/cloud.js - frontend/src/api/client.js (full file — confirm exact function bodies and signatures) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/api/admin.js" (blob refactor pattern using fetchWithRetry) For each file, add a header docstring naming the domain. Import statements: - admin.js: `import { request, fetchWithRetry } from './utils.js'` (audit-log CSV + daily-export use fetchWithRetry) - folders.js, shares.js, cloud.js: `import { request } from './utils.js'`

(A) admin.js: move these functions VERBATIM from client.js: adminListUsers, adminCreateUser, adminDeactivateUser, adminReactivateUser, adminResetUserPassword, adminGetUserQuota, adminUpdateQuota, adminUpdateAiConfig, adminDeleteUser, getAiConfig, saveAiConfig, testAiConnection, getAiModels, adminListAuditLog, adminListDailyExports. Then refactor adminExportAuditLogCsv and adminDownloadDailyExport to use fetchWithRetry (per PATTERNS.md §"frontend/src/api/admin.js"). The new adminExportAuditLogCsv(params = {}) body: build the URLSearchParams exactly as before, call const res = await fetchWithRetry('/api/admin/audit-log/export?' + searchParams), on !res.ok throw 'Export failed: ' + res.status, then call res.text(), create the Blob, trigger the download via temporary anchor click, revokeObjectURL on a setTimeout. The new adminDownloadDailyExport(date) body: const res = await fetchWithRetry('/api/admin/audit-log/daily-exports/' + date), on !res.ok throw, then download the blob the same way. Drop the _retry boilerplate — fetchWithRetry handles it.

(B) folders.js: move these functions VERBATIM: listFolders, createFolder, getFolder, renameFolder, deleteFolder, moveDocument.

(C) shares.js: move these functions VERBATIM: createShare, updateSharePermission, listShares, deleteShare, getSharedWithMe.

(D) cloud.js: move these functions VERBATIM: listCloudConnections, disconnectCloud, connectWebDav, updateDefaultStorage, getCloudFolders, initiateOAuth, getConnectionConfig.

Do NOT remove the functions from client.js yet — task 4 owns the barrel rewrite. cd frontend && node -e "Promise.all([import('./src/api/admin.js'), import('./src/api/folders.js'), import('./src/api/shares.js'), import('./src/api/cloud.js')]).then(([ad, fo, sh, cl]) => { ['adminListUsers','adminCreateUser','adminUpdateQuota','getAiConfig','saveAiConfig','testAiConnection','adminListAuditLog','adminExportAuditLogCsv','adminDownloadDailyExport'].forEach(n => { if (typeof ad[n] !== 'function') throw new Error('admin.' + n + ' missing'); }); ['listFolders','createFolder','moveDocument'].forEach(n => { if (typeof fo[n] !== 'function') throw new Error('folders.' + n + ' missing'); }); ['createShare','updateSharePermission','getSharedWithMe'].forEach(n => { if (typeof sh[n] !== 'function') throw new Error('shares.' + n + ' missing'); }); ['listCloudConnections','initiateOAuth','getConnectionConfig'].forEach(n => { if (typeof cl[n] !== 'function') throw new Error('cloud.' + n + ' missing'); }); console.log('ok'); }).catch(e => { console.error(e); process.exit(1); })" <acceptance_criteria> - Files exist: frontend/src/api/admin.js, frontend/src/api/folders.js, frontend/src/api/shares.js, frontend/src/api/cloud.js - grep -c "import { request, fetchWithRetry } from './utils.js'" frontend/src/api/admin.js returns 1 - grep -c "import { request } from './utils.js'" frontend/src/api/folders.js returns 1 - grep -c "import { request } from './utils.js'" frontend/src/api/shares.js returns 1 - grep -c "import { request } from './utils.js'" frontend/src/api/cloud.js returns 1 - grep -c "fetchWithRetry" frontend/src/api/admin.js returns at least 2 (one each for adminExportAuditLogCsv and adminDownloadDailyExport) - grep -c "_retry" frontend/src/api/admin.js returns 0 (boilerplate dropped — fetchWithRetry owns retry) - grep -c "^export function initiateOAuth" frontend/src/api/cloud.js returns 1 (named import is used by SettingsCloudTab.vue) - The node -e import check above prints ok </acceptance_criteria> Four domain files exist with all expected exports; blob-download functions use fetchWithRetry; node import succeeds.

Task 4: Rewrite client.js as barrel re-export and run frontend tests frontend/src/api/client.js - frontend/src/api/client.js (the current 635-line file — you are about to replace its contents) - .planning/phases/08-stack-upgrade-backend-decomposition/08-PATTERNS.md §"frontend/src/api/client.js (transport + barrel, request-response) — MODIFIED" (the full final-state template) Replace the ENTIRE contents of `frontend/src/api/client.js` with this final form (preserves all previous exports via `export *`):

/**

  • API client — barrel re-export.
  • The HTTP transport (request) and 401-retry consolidator (fetchWithRetry) live in utils.js
  • to avoid the circular import that would arise if domain modules imported request from here
  • while this file re-exported from those same domain modules.
  • All 35+ consumer files continue using one of:
  • import * as api from '...api/client.js' — namespace pattern
  • import { funcName } from '...api/client.js' — named import pattern
  • without any changes. */ export * from './documents.js' export * from './auth.js' export * from './admin.js' export * from './folders.js' export * from './shares.js' export * from './cloud.js' export * from './topics.js' export { fetchWithRetry, request } from './utils.js'

After writing the new file contents, run cd frontend && npm test to confirm the test suite passes. The frontend tests use the import { ... } from '../api/client.js' and import * as api from '../api/client.js' patterns — both must resolve every previously-exported name through the barrel. If any test fails with TypeError: ... is not a function or similar, the symptom is a missing or misspelled export in one of the domain modules — diagnose by re-checking the function-to-domain map.

After tests pass, smoke the dev build: cd frontend && npm run build (Vite production build). Vite will fail loudly if export * from produces ambiguous names or unresolved modules. If the build fails, fix and rerun. cd frontend && wc -l src/api/client.js && npm test 2>&1 | tail -20 && npm run build 2>&1 | tail -10 <acceptance_criteria> - wc -l < frontend/src/api/client.js returns less than 25 (was 635; barrel is ~15-20 lines) - grep -c "^export \\* from " frontend/src/api/client.js returns 7 - grep -c "export { fetchWithRetry, request } from './utils.js'" frontend/src/api/client.js returns 1 - grep -c "async function request" frontend/src/api/client.js returns 0 (request moved to utils.js) - grep -c "noRefreshPaths" frontend/src/api/client.js returns 0 (lived inside request, now in utils.js) - cd frontend && npm test exits 0 - cd frontend && npm run build exits 0 - grep -rn "from '../api/client.js'\\|from '../../api/client.js'\\|from '../../../api/client.js'" frontend/src/stores/ frontend/src/components/ frontend/src/views/ 2>/dev/null | wc -l returns same count as before this plan (35+ files; none modified) - No file under frontend/src/stores/, frontend/src/components/, or frontend/src/views/ is listed in git diff for this plan </acceptance_criteria> client.js is a thin barrel; frontend tests pass; production build succeeds; zero consumer files modified.

<threat_model>

Trust Boundaries

Boundary Description
Browser → backend API All requests carry Bearer token from authStore memory (never localStorage per CLAUDE.md)
Domain module → utils.js request In-process import; no untrusted data crosses
fetchWithRetry → authStore.refresh on 401 Refresh flow uses httpOnly cookie; on failure the in-memory access token is cleared

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-08-07-01 Spoofing Bearer token injection mitigate request body copied verbatim from client.js — preserves if (authStore.accessToken) headers['Authorization'] = ... and lazy import of authStore
T-08-07-02 Tampering Circular import producing undefined exports mitigate request lives in utils.js; client.js never re-exports request from itself; domain modules import from utils.js (PATTERNS.md §"Frontend domain module import line")
T-08-07-03 Repudiation 401-refresh-retry loop mitigate _retry flag is preserved in both request and fetchWithRetry; recursion is bounded
T-08-07-04 Information Disclosure Token stored in JS storage mitigate CLAUDE.md rule preserved: token from authStore.accessToken (memory only), never read from localStorage/sessionStorage
T-08-07-05 Tampering export * name collision mitigate RESEARCH.md §Pitfall 5 verified all current client.js function names are unique; acceptance criterion runs frontend tests + production build which would fail on collision
T-08-07-06 Denial of Service Consumer files break mitigate Barrel re-export preserves every named export; acceptance criterion verifies zero consumer files in stores/components/views were modified
T-08-07-SC Supply Chain No new npm packages accept This plan installs zero new packages
</threat_model>
- `cd frontend && npm test` — zero failures - `cd frontend && npm run build` — succeeds - `wc -l frontend/src/api/client.js` — under 25 lines - `grep -rn "import.*from '../api/client.js'\\|import.*from '../../api/client.js'\\|import.*from '../../../api/client.js'" frontend/src/stores/ frontend/src/components/ frontend/src/views/` returns the same line count as before this plan - `git diff --stat HEAD frontend/src/stores/ frontend/src/components/ frontend/src/views/` returns no entries

<success_criteria>

  • 8 new files in frontend/src/api/ (utils.js + 7 domain modules)
  • client.js reduced to ~15 lines
  • Frontend tests + production build pass
  • Zero consumer files modified (the entire point of the barrel pattern)
  • fetchWithRetry consolidates 3 blob-download patterns (CODE-08) </success_criteria>
Create `.planning/phases/08-stack-upgrade-backend-decomposition/08-07-SUMMARY.md` when done. Include: (a) line counts before/after for client.js and the 8 new files, (b) list of all consumer files (stores + components + views) confirmed untouched, (c) confirmation that the 3 old blob-download _retry boilerplate blocks were consolidated into the single `fetchWithRetry` helper.