Files
kite/.planning/phases/14.1-cloud-local-file-parity-hardening/14.1-03-SUMMARY.md
T

14 KiB
Raw Blame History

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
frontend
shared-surface
cloud-detail
force-reanalyze
parity
route
CLOUD-02
ANALYZE-01
ANALYZE-05
ANALYZE-06
CACHE-03
requires provides affects
14.1-01 (RED tests — CloudDetailParity.test.js must pass)
14.1-02 (backend cloud detail endpoint + force field + retry route)
frontend/src/components/storage/DocumentDetailSurface.vue
frontend/src/views/CloudDetailView.vue
frontend/src/router/index.js (cloud-file-detail route added)
frontend/src/api/cloud.js (getCloudItemDetail added)
frontend/src/stores/cloudConnections.js (force param + fetchCloudItemDetail)
frontend/src/components/storage/StorageBrowser.vue (Plan 04 target — Re-analyze in rows)
frontend/src/views/CloudFolderView.vue (Plan 04 target — row click navigates to cloud-file-detail)
added patterns
Shared detail surface pattern (DocumentDetailSurface as layout owner; DocumentView + CloudDetailView as thin data providers)
Single analysis action slot (swaps Analyze/Re-analyze/Retry by kind — never accumulates)
Opaque provider itemId in route params — encodeURIComponent in API, never decoded/split in frontend
Force re-analyze with confirmation dialog (Copywriting Contract destructive copy)
Preview unavailable keeps Download active, no auto-download (D-14)
created modified
frontend/src/components/storage/DocumentDetailSurface.vue
frontend/src/views/CloudDetailView.vue
frontend/src/views/DocumentView.vue
frontend/src/router/index.js
frontend/src/api/cloud.js
frontend/src/stores/cloudConnections.js
DocumentDetailSurface is a presentational smart component with named slots for secondary-actions, topics-actions, topics-error, suggestions, and secondary-controls
Topics prop accepts both string arrays (cloud) and object arrays with {name, color} (local) — normalized in computed
DocumentView analysis action is always 'reanalyze' kind (local files are always analyzed); busy flag controlled by classifying ref
Cloud route path uses /item/ segment before itemId to disambiguate from /cloud-folder /:folderId(.*) wildcard
cloudConnections.js imports cloudApi (cloud.js) separately from api (client.js) for getCloudItemDetail — no local redefinition
Re-analyze confirmation modal shown before any force=true enqueue; cancel keeps existing analysis data visible
duration completed_date tasks_completed files_created files_modified frontend_tests_passing test_suite_delta
5m 2026-06-26 2 2 4 488 +0 new failures (1 pre-existing Plan 01 RED test for Plan 04 target)

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:

  1. Back navigation (slot — overridable)
  2. Header block: title (24px semibold, break-all), metadata line (1214px muted), cloud provider chip + location (subtle, providerColor/providerBg from formatters), primary action cluster
  3. Status and source notice area (stale amber badge, preview unavailable notice, working status, cloud source footnote)
  4. Topics section (TopicBadge + "No topics assigned yet." empty copy, topics-actions slot)
  5. Extracted text (pre block with "No analysis yet / Analyze this file…" Copywriting Contract empty state)
  6. 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.isStale derived from is_stale or analysis_status === 'stale' (D-07)
  • source.statusLabel via cloudStore.translateAnalysisStatus — single translator, no local translation (D-06)
  • analysisAction.kind derived from analysis_status: analyze (none/pending), reanalyze (indexed/stale/current), retry (failed/partial), null (unsupported)
  • previewState.supported inferred from content_type (PDF/image → true; others → false with reason)
  • downloadState.supported: true (authorized download always available)

Event handlers:

  • previewcloudApi.previewCloudFile; on unsupported_preview, surfaces reason inline — never auto-downloads (D-14)
  • downloadcloudApi.downloadCloudFile
  • analyzecloudStore.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-analysisenqueueAnalysis({ 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.js tests 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. getCloudItemDetail lives in cloud.js. Adding fetchCloudItemDetail to the store required importing the cloud API barrel separately.
  • Fix: Added import * as cloudApi from '../api/cloud.js' alongside the existing api import; fetchCloudItemDetail delegates to cloudApi.getCloudItemDetail. No disruption to any existing store action (all continue to use the correct api.* 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.capabilities is 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: previewStateProps computed in CloudDetailView infers from content_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 typed unsupported_preview response, 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:

  • 825a7b5 (Task 1) present in git log ✓
  • 48afb8b (Task 2) present in git log ✓