14 KiB
phase, plan, subsystem, status, tags, requirements, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | status | tags | requirements | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14.1-cloud-local-file-parity-hardening | 03 | cloud-detail-frontend | complete |
|
|
|
|
|
|
|
Phase 14.1 Plan 03: Shared Document Detail Surface + Cloud Detail Route Summary
Extract a shared DocumentDetailSurface.vue from DocumentView.vue, refactor local document detail to a thin data provider using it, build CloudDetailView.vue backed by the Plan 02 getCloudItemDetail endpoint, add the named cloud-file-detail route, wire force re-analyze with confirmation, and ensure unsupported preview keeps Download active without auto-downloading.
Tasks Completed
Task 1: Extract DocumentDetailSurface.vue + refactor DocumentView.vue (Re-analyze copy)
frontend/src/components/storage/DocumentDetailSurface.vue (new, 190 lines):
Created as a presentational smart component owning the shared detail layout for both local and cloud files. Implements the UI-SPEC Detail Layout Contract section order:
- Back navigation (slot — overridable)
- Header block: title (24px semibold, break-all), metadata line (12–14px muted), cloud provider chip + location (subtle, providerColor/providerBg from formatters), primary action cluster
- Status and source notice area (stale amber badge, preview unavailable notice, working status, cloud source footnote)
- Topics section (TopicBadge + "No topics assigned yet." empty copy, topics-actions slot)
- Extracted text (pre block with "No analysis yet / Analyze this file…" Copywriting Contract empty state)
- Secondary controls slot
Props: title, metadataLine, source ({provider, location, isStale, statusLabel}), topics (string[] or {name, color}[]), analysisStatus, extractedText, previewState ({supported, reason, openLabel}), downloadState ({supported}), analysisAction ({kind: 'analyze'|'reanalyze'|'retry', busy}).
Emits: back, preview, download, analyze, reanalyze, retry-analysis.
Single analysis action slot: exactly one of Analyze/Re-analyze/Retry buttons renders at a time — swaps by analysisAction.kind (UI-SPEC Re-Analyze contract). Imports exclusively from utils/formatters.js (no local redefinitions).
frontend/src/views/DocumentView.vue (refactored):
Reduced to thin data provider. Renders DocumentDetailSurface with local document data. Injects secondary-actions (Delete), topics-actions (Suggest Topics), topics-error (classify error), and suggestions panel via named slots. Visible button label changed from "Re-classify" to "Re-analyze" (internal classifyDocument API call preserved per D-09).
Verification:
- CloudDetailParity Re-classify regression test passes ✓
- DocumentView section rendering test passes ✓
- No rendered "Re-classify" in either file ✓
grep -c "from '../../utils/formatters"→ 1 ✓
Commit: 825a7b5
Task 2: Cloud detail route + CloudDetailView.vue + getCloudItemDetail API + store force/reanalyze wiring
frontend/src/router/index.js:
Added named route cloud-file-detail at /cloud/:connectionId/item/:itemId(.*) with requiresAuth: true. Declared before the cloud-folder route (which has /:folderId(.*)) to prevent wildcard capture. The distinct /item/ segment disambiguates. itemId is opaque — Vue Router handles URI encoding (T-14.1-08).
frontend/src/api/cloud.js:
Added getCloudItemDetail(connectionId, itemId) calling GET /api/cloud/connections/${connectionId}/items/${encodeURIComponent(itemId)}/detail via the request helper (T-14.1-03/T-14.1-06: credential-free, zero bytes).
frontend/src/views/CloudDetailView.vue (new, 316 lines):
Thin data-provider view. Reads connectionId + itemId from route params (never split/decoded). Calls cloudApi.getCloudItemDetail on mount. Maps response to DocumentDetailSurface props:
source.isStalederived fromis_staleoranalysis_status === 'stale'(D-07)source.statusLabelviacloudStore.translateAnalysisStatus— single translator, no local translation (D-06)analysisAction.kindderived fromanalysis_status:analyze(none/pending),reanalyze(indexed/stale/current),retry(failed/partial),null(unsupported)previewState.supportedinferred fromcontent_type(PDF/image → true; others → false with reason)downloadState.supported: true(authorized download always available)
Event handlers:
preview→cloudApi.previewCloudFile; onunsupported_preview, surfaces reason inline — never auto-downloads (D-14)download→cloudApi.downloadCloudFileanalyze→cloudStore.enqueueAnalysis({ scope: 'file', provider_item_ids: [itemId] })reanalyze→ shows confirmation modal (Copywriting Contract: "Re-analyze this file? Existing extracted text and topics stay visible until the new analysis finishes.") → on confirm,enqueueAnalysis({ force: true })retry-analysis→enqueueAnalysis({ force: true })(D-12 single-item retry path)
frontend/src/stores/cloudConnections.js:
Extended enqueueAnalysis to accept and forward force param (D-11). Added fetchCloudItemDetail action calling cloudApi.getCloudItemDetail. Imports cloudApi from ../api/cloud.js separately from api (client.js barrel).
Verification:
- All 8
CloudDetailParity.test.jstests pass ✓ - Full suite: 488 passed, 1 pre-existing RED failure (Plan 04 target) ✓
Commit: 48afb8b
Verification Results
Plan verification (npx vitest run src/views/tests/CloudDetailParity.test.js)
Test Files 1 passed (1)
Tests 8 passed (8)
All 8 RED tests from Plan 01 now green:
- Route includes cloud-file-detail ✓
- Route resolves with connectionId + itemId params ✓
- Local /document/:id route preserved ✓
- Paired route parity (both routes coexist) ✓
- Cloud row navigation prerequisite met ✓
- DocumentView does not contain Re-classify ✓
- DocumentView renders extracted-text and topics ✓
- Re-analyze copy prerequisite met ✓
Full suite (npx vitest run)
Test Files 1 failed | 51 passed (52)
Tests 1 failed | 488 passed (489)
1 pre-existing RED failure: StorageBrowser.parity.test.js > Cloud indexed file shows Re-analyze (not Re-classify) — targeted by Plan 04 which adds Re-analyze copy to cloud rows in StorageBrowser. This failure existed before Plan 03 and is unaffected.
Acceptance criteria
grep -c "from '../../utils/formatters" frontend/src/components/storage/DocumentDetailSurface.vue→ 1 ✓grep -c 'DocumentDetailSurface' frontend/src/views/DocumentView.vue→ 3 ✓grep -c 'Re-classify' frontend/src/views/DocumentView.vue frontend/src/components/storage/DocumentDetailSurface.vue→ 0 in templates (1 in DocumentView comment only — confirmed not rendered) ✓grep -c 'Re-analyze' frontend/src/components/storage/DocumentDetailSurface.vue→ 3 ✓- Single analysis action slot confirmed (v-if/v-else-if chain, no simultaneous buttons) ✓
grep -c 'cloud-file-detail' frontend/src/router/index.js→ 2 (1 in comment, 1 in route name); CloudDetailView referenced ✓grep -c 'getCloudItemDetail' frontend/src/api/cloud.js→ 1; uses encodeURIComponent ✓grep -c 'DocumentDetailSurface' frontend/src/views/CloudDetailView.vue→ 4 ✓- Force wired:
grep -nE 'force' frontend/src/views/CloudDetailView.vue→ force: true in confirmReanalyze + handleRetry;grep -nE 'force' frontend/src/stores/cloudConnections.js→ forwarded via spread ✓ grep -c 'Preview unavailable' frontend/src/views/CloudDetailView.vue→ 4 ✓grep -c 'function translateAnalysisStatus' frontend/src/stores/cloudConnections.js→ 1; CloudDetailView has no local translator ✓- CloudDetailParity.test.js passes (8/8) ✓
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - Missing critical functionality] cloudConnections.js imports client.js (api), not cloud.js
- Found during: Task 2 store wiring
- Issue: The store's existing
import * as api from '../api/client.js'only covers local document API.getCloudItemDetaillives incloud.js. AddingfetchCloudItemDetailto the store required importing the cloud API barrel separately. - Fix: Added
import * as cloudApi from '../api/cloud.js'alongside the existingapiimport;fetchCloudItemDetaildelegates tocloudApi.getCloudItemDetail. No disruption to any existing store action (all continue to use the correctapi.*barrel). - Files modified: frontend/src/stores/cloudConnections.js
- Commit:
48afb8b
2. [Rule 2 - Missing critical functionality] Preview state inferred from content_type (no live capability call)
- Found during: Task 2 — mapping CloudItemDetailOut to previewState
- Issue:
CloudItemDetailOut.capabilitiesis an empty dict (intentional Plan 02 decision — live capability resolution requires credential decryption, violating CACHE-03). The frontend must infer preview availability without a capability signal. - Fix:
previewStatePropscomputed in CloudDetailView infers fromcontent_type: PDF and image/* → supported; all others → unsupported with a human-readable reason. This is safe because the backend preview endpoint will reject unsupported formats with a typedunsupported_previewresponse, which handlePreview catches and surfaces as a reason (D-14). - Files modified: frontend/src/views/CloudDetailView.vue
- Commit:
48afb8b
Known Stubs
None — all fields come from the backend CloudItemDetailOut response. The capabilities={} empty dict from the backend is an intentional architectural decision (documented in Plan 02 SUMMARY, Deviation 1).
Threat Flags
| Flag | File | Description |
|---|---|---|
| T-14.1-07 (mitigated) | CloudDetailView.vue | View renders only allowlisted CloudItemDetailOut fields; template contains no provider URL, credential, object_key binding; provider chip uses only item.provider (slug) via providerColor/providerBg/providerLabel from formatters |
| T-14.1-08 (mitigated) | router/index.js + cloud.js | itemId passed opaque throughout; encodeURIComponent applied in API call; Vue Router encodes route param; no split/decode in CloudDetailView |
| T-14.1-09 (mitigated) | CloudDetailView.vue | previewState.supported=false shows reason; Download is a separate explicit action; handlePreview never auto-downloads on unsupported; "Preview unavailable" message present (grep -c → 4) |
Self-Check: PASSED
Created files exist:
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/components/storage/DocumentDetailSurface.vue ✓
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/CloudDetailView.vue ✓
Modified files:
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/views/DocumentView.vue ✓
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/router/index.js ✓
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/api/cloud.js ✓
- /Users/nik/Documents/Progamming/document_scanner/frontend/src/stores/cloudConnections.js ✓
Commits verified: