Files
kite/.planning/phases/03-document-migration-multi-user-isolation/03-05-SUMMARY.md
T
curo1305 254e756cb8 docs(03-05): complete frontend presigned upload + quota bar plan
- 03-05-SUMMARY.md: plan summary covering 3-step XHR upload, QuotaBar.vue, UploadProgress error block, STORE-03/04/05 completed
- STATE.md: advance to plan 5 checkpoint pending; add 5 key decisions from plan 03-05
2026-05-23 22:02:03 +02:00

9.2 KiB
Raw Blame History

phase, plan, subsystem, tags, requires, provides, affects, tech-stack, key-files, key-decisions, requirements-completed, duration, completed
phase plan subsystem tags requires provides affects tech-stack key-files key-decisions requirements-completed duration completed
03-document-migration-multi-user-isolation 05 ui,frontend
vue3
pinia
xhr
minio
presigned-upload
quota
progressbar
tailwind
phase provides
03-04 getMyQuota, getUploadUrl, confirmUpload in api/client.js; per-user AI classification wired
phase provides
03-03 per-user document/topic isolation; /api/documents/{id}/confirm endpoint (HTTP 413 on quota exceeded)
Three-step presigned upload flow (getUploadUrl → XHR PUT to MinIO → confirmUpload) with XHR progress events mapped to 0100 visual range
uploadProgress reactive map in documents store (keyed by filename+timestamp, T-03-25)
authStore.quota ref({used_bytes, limit_bytes}) + fetchQuota() action updated after every upload/delete
QuotaBar.vue sidebar widget: skeleton loading, color thresholds <80% indigo-500 / 80-95% amber-500 / >=95% red-500
UploadProgress.vue: aria progressbar per row, percentage label, inline 413 quota rejection error block with role=alert
api/client.js request() attaches .status and .payload to thrown errors (structured 413 detection)
Legacy uploadDocument multipart function removed from api/client.js
03-verify
added patterns
XHR PUT presigned upload: bare XMLHttpRequest with no Authorization header (T-03-22 — presigned URL is self-authenticating)
Progress mapping: XHR 0100 → visual range 590 via 5 + Math.round(pct * 0.85)
uploadProgress map key: file.name__Date.now() composite to prevent race conditions on duplicate filenames (T-03-25)
Structured 413 handling: request() detects typeof body.detail === 'object', attaches err.payload = body.detail
fetchQuota: silent catch in auth store (QuotaBar owns error display via v-if !loadFailed)
QuotaBar v-if !loadFailed: hides entire DOM on fetch error (UI-SPEC Loading and Error States)
created modified
frontend/src/components/layout/QuotaBar.vue
frontend/src/api/client.js
frontend/src/stores/auth.js
frontend/src/stores/documents.js
frontend/src/components/upload/UploadProgress.vue
frontend/src/components/layout/AppSidebar.vue
Plain <a href='/settings'> used in UploadProgress quota error block instead of <router-link> to avoid import dependency in an upload component (plan note: 'avoid router-link import complexity')
uploadProgress map entries are NOT deleted after upload completes — parent component (DropZone) is responsible for clearing; this keeps the bar/error visible until user dismisses the row
QuotaBar.vue owns loadFailed state; authStore.fetchQuota() silently ignores errors — division of responsibility per UI-SPEC
Frontend build warning about mixed dynamic/static import of auth.js is pre-existing (api/client.js uses lazy import to break circular dep) — not introduced by this plan
STORE-03
STORE-04
STORE-05
7min 2026-05-23

Phase 03 Plan 05: Frontend Presigned Upload + Quota Bar Summary

Three-step presigned upload (XHR PUT to MinIO with progress events) + sidebar QuotaBar + inline 413 quota rejection error block wired to authStore.quota reactive state

Performance

  • Duration: 7 min
  • Started: 2026-05-23T18:42:50Z
  • Completed: 2026-05-23T18:49:24Z
  • Tasks: 2 (+ 1 checkpoint awaiting human verification)
  • Files modified: 5 modified, 1 created

Accomplishments

  • 3-step upload flow (STORE-03): stores/documents.js replaces the legacy api.uploadDocument() multipart call with: (1) api.getUploadUrl() → (2) uploadToMinIO() XHR PUT with xhr.upload.addEventListener('progress', ...) → (3) api.confirmUpload(). Progress mapped 0%→5%→90%→92%→100% per UI-SPEC. No Authorization header on MinIO PUT (T-03-22). uploadProgress ref map keyed by ${file.name}__${Date.now()} (T-03-25).

  • Quota state in auth store (STORE-04): stores/auth.js adds quota = ref({used_bytes:0, limit_bytes:0}) and fetchQuota() async action. Called after every successful upload and every document delete. Silent catch keeps last-known values intact on error.

  • QuotaBar.vue sidebar widget (STORE-04): New component at frontend/src/components/layout/QuotaBar.vue. onMounted triggers fetchQuota. Computed pct/barColor/labelColor/label from authStore.quota. Color thresholds: bg-indigo-500 (<80%), bg-amber-500 (80-95%), bg-red-500 (>=95%). Skeleton loading state; v-if hides entirely on loadFailed. Embedded in AppSidebar between </nav> and footer div.

  • Quota rejection error block (STORE-05): UploadProgress.vue renders inline red error block (role="alert") when item.quotaError is set — populated from err.payload on 413. Shows rejected_bytes, used_bytes, limit_bytes from server response (T-03-23 — never from client-side file.size). "Manage storage →" uses plain <a href="/settings">.

  • api/client.js structured 413: request() extended — if (!res.ok) block detects typeof body.detail === 'object', attaches err.status = res.status and err.payload = body.detail. Legacy uploadDocument() multipart function removed.

Task Commits

  1. Task 1: Refactor stores + api/client + UploadProgresseb18428 (feat)
  2. Task 2: Create QuotaBar.vue + embed in AppSidebar23c568a (feat)

Files Created/Modified

  • frontend/src/api/client.js — Extended request() error handling; removed uploadDocument legacy multipart
  • frontend/src/stores/auth.js — Added quota ref + fetchQuota action
  • frontend/src/stores/documents.js — 3-step upload with XHR, uploadProgress map, fetchQuota calls
  • frontend/src/components/upload/UploadProgress.vue — Progress bar per row, quota rejection error block
  • frontend/src/components/layout/QuotaBar.vue — New: sidebar quota widget
  • frontend/src/components/layout/AppSidebar.vue — Import + embed QuotaBar

Decisions Made

  • Plain <a href="/settings"> in quota error block rather than <router-link> — avoids router-link import dependency in an upload component that may not have router context in all test scenarios. Functional equivalence for the Phase 3 use case.
  • uploadProgress entries not cleared by the store — DropZone/parent component owns row lifecycle and clears on dismiss. Keeps progress bar/error visible until user action.

Deviations from Plan

None — plan executed exactly as written. All implementation notes from the plan prompt were followed precisely.

UI-SPEC Deviations

Element UI-SPEC Implemented Reason
Manage storage → link <router-link to="/settings"> or plain <a> Plain <a href="/settings"> Plan explicitly notes "use plain <a href='/settings'> to avoid router-link import complexity"
Upload progress track bg bg-gray-100 bg-gray-100 Matches
QuotaBar track bg bg-gray-200 bg-gray-200 Matches

No functional UI-SPEC deviations.

Issues Encountered

None.

Known Stubs

None — all data flows are wired to real API responses. QuotaBar reads from authStore.quota which is populated by /api/auth/me/quota. UploadProgress quota error block reads from 413 response body (not client-side file.size).

Threat Flags

No new threat surface introduced — this plan modifies only frontend JavaScript files. All security mitigations from the threat register implemented:

Threat ID Status
T-03-22 MITIGATED — uploadToMinIO helper uses bare XMLHttpRequest with NO Authorization header
T-03-23 MITIGATED — Quota rejection error block populates used_bytes/limit_bytes/rejected_bytes from 413 response body only
T-03-25 MITIGATED — uploadProgress key = ${file.name}__${Date.now()} composite
T-03-26 MITIGATED — authStore.fetchQuota wraps in try/catch; QuotaBar hides on loadFailed

Next Phase Readiness

  • Phase 3 is now feature-complete pending human checkpoint (Task 3) approval
  • Task 3 human checkpoint requires: docker compose running, dev server at localhost:5173, logged-in non-admin user with quota row
  • After checkpoint approval: Phase 3 verification (/gsd:verify-work 3) can begin
  • Phase 4 (folder navigation, PDF preview, sharing) has no blockers from Phase 3

Self-Check: PASSED

  • frontend/src/components/layout/QuotaBar.vue exists
  • frontend/src/api/client.js contains getMyQuota (grep count: 1) and does NOT contain uploadDocument
  • frontend/src/stores/documents.js contains uploadToMinIO, XMLHttpRequest, getUploadUrl, confirmUpload, fetchQuota
  • frontend/src/stores/auth.js contains fetchQuota (count: 2)
  • frontend/src/components/upload/UploadProgress.vue contains role="progressbar" and Not enough storage
  • frontend/src/components/layout/QuotaBar.vue contains role="progressbar" and useAuthStore
  • frontend/src/components/layout/AppSidebar.vue contains <QuotaBar and QuotaBar from './QuotaBar.vue'
  • Frontend build exits 0 (npm run build — verified)
  • No legacy uploadDocument references in frontend/src/ (grep clean)
  • Task 1 commit eb18428 exists
  • Task 2 commit 23c568a exists

Phase: 03-document-migration-multi-user-isolation Completed: 2026-05-23