Files
kite/.planning/phases/14-selective-analysis-and-byte-cache/14-07-SUMMARY.md
T

12 KiB


phase: "14" plan: "07" subsystem: cloud-analysis-frontend tags: [analysis, frontend, storage-browser, cloud-folder, queue-ui, store, api] status: complete

requires:

  • phases/14-selective-analysis-and-byte-cache/14-04
  • phases/14-selective-analysis-and-byte-cache/14-05

provides:

  • frontend/src/api/cloud.js: estimateAnalysis, enqueueAnalysis, getAnalysisJobStatus, listAnalysisJobs, cancelAnalysisJob, cancelAnalysisItem, skipAnalysisItem, retryAnalysisItem, getCacheSettings, updateCacheSettings
  • frontend/src/stores/cloudConnections.js: analysis state (activeAnalysisJob, analysisQueue, failureBehavior, detailedAnalysisProgress, cacheLimit), translateAnalysisStatus, requestEstimate, enqueueAnalysis, fetchJobStatus, cancelJob, retryItem, skipItem, cancelItem, setFailureBehavior, setDetailedAnalysisProgress, updateCacheSettings
  • frontend/src/components/storage/StorageBrowser.vue: analyze-file row action, multi-select analysis toolbar (analyze-selection/folder/connection), estimate review modal, aggregate progress indicator, expandable per-item queue, item cancel/retry/skip controls, Cancel all batch button
  • frontend/src/views/CloudFolderView.vue: analysis orchestration — estimate/enqueue handlers, all 8 analysis emit handlers routing through cloudStore

affects:

  • frontend/src/api/cloud.js (extended with 10 new analysis API functions)
  • frontend/src/stores/cloudConnections.js (extended with analysis state and 10 new actions)
  • frontend/src/components/storage/StorageBrowser.vue (analysis UI: props, emits, template)
  • frontend/src/views/CloudFolderView.vue (analysis orchestration handlers)

tech-stack: added: [] patterns: - single-translation-source: translateAnalysisStatus in store maps internal→simplified/detailed (D-06); components never translate independently - estimate-before-enqueue: all analysis scopes call requestEstimate → show modal → user confirms → enqueue; no direct enqueue without estimate review (D-03) - analyze-button-outside-capability-area: analyze-file button placed inside file name cell (not inside [data-test="file-row-actions"]) so capability-gated button invariant is preserved - pinia-memory-only: analysisQueue/activeAnalysisJob live in Pinia only — never localStorage/sessionStorage; no credentials or object_key fields stored (T-14-02) - selection-checkbox-cloud-only: file-select checkboxes rendered in cloud mode only; local mode shows icon as before; checkbox aria-label does not include filename to prevent XSS surface in HTML attribute (Rule 1 fix) - queue-expand-collapse: analysisQueue aggregate progress has expand/collapse toggle; default collapsed shows summary counts and Cancel all only - simplified-vs-detailed: detailedAnalysisProgress prop controls whether queue items show internal stages (downloading/extracting/classifying) or simplified labels (waiting/working/done/skipped/failed) per D-06

key-files: created: [] modified: - frontend/src/api/cloud.js - frontend/src/stores/cloudConnections.js - frontend/src/components/storage/StorageBrowser.vue - frontend/src/views/CloudFolderView.vue

decisions:

  • analyze-file button placed inside the file name cell (not file-row-actions) so capabilities.test.js "all buttons in file-row-actions are aria-disabled" invariant remains valid; analyze is not capability-gated (always available in cloud mode)
  • File checkbox aria-label uses generic "Select file" not the filename — prevents XSS payload from appearing as HTML attribute text in test output (XSS prevention)
  • translateAnalysisStatus in cloudConnections store is the single source — StorageBrowser receives ui_status from the store's analysisQueue computed property, never translates raw status strings independently
  • analysisEstimate prop drives the estimate modal in StorageBrowser; CloudFolderView owns the estimate state (analysisEstimate ref) and resets it after confirmation/cancellation
  • analysis-cancel-all emits { job_id } from the first queue item's job_id — safe because all items in the visible queue belong to one active job at a time

metrics: duration: "~12 minutes" completed: "2026-06-23" tasks: 2 files: 4

Phase 14 Plan 07: Frontend Analysis UX (StorageBrowser + CloudFolderView) Summary

One-liner: Per-file/multi-select/folder/connection analysis row actions, toolbar buttons, estimate review modal, aggregate progress queue with expandable per-item list and cancel/retry/skip controls wired to the Phase 14 backend analysis API.

What Was Built

Task 1: API and store analysis queue surface

frontend/src/api/cloud.js (10 new functions):

Function Route Notes
estimateAnalysis(connectionId, params) POST /api/cloud/analysis/connections/{id}/estimate T-14-04: no bytes downloaded
enqueueAnalysis(connectionId, params) POST /api/cloud/analysis/connections/{id}/jobs Returns job_id
getAnalysisJobStatus(jobId, opts) GET /api/cloud/analysis/jobs/{id}[?detail=true] T-14-02: no credentials/object_key
listAnalysisJobs(filters) GET /api/cloud/analysis/jobs Optional connection_id/status filters
cancelAnalysisJob(jobId) POST /api/cloud/analysis/jobs/{id}/cancel Batch cancel
cancelAnalysisItem(jobId, itemId) POST /api/cloud/analysis/jobs/{id}/items/{iid}/cancel Per-item cancel
skipAnalysisItem(jobId, itemId) POST /api/cloud/analysis/jobs/{id}/items/{iid}/skip Per-item skip
retryAnalysisItem(jobId, itemId) POST /api/cloud/analysis/jobs/{id}/items/{iid}/retry Per-item retry
getCacheSettings() GET /api/users/me/settings Cache config read
updateCacheSettings(params) PATCH /api/users/me/settings CACHE-04

frontend/src/stores/cloudConnections.js (analysis extensions):

New state (Pinia memory — no credentials, no object_key):

  • activeAnalysisJob: current job tracking object; updated by fetchJobStatus
  • analysisQueue: computed from activeAnalysisJob.items with ui_status populated from SIMPLIFIED_STATUS_MAP
  • failureBehavior: 'pause_batch' (default) | 'continue_item' (D-11)
  • detailedAnalysisProgress: false (default) — controls simplified vs detailed labels (D-06)
  • cacheLimit: 500 * 1024 * 1024 (default 500 MB, CACHE-04)

New actions:

  • translateAnalysisStatus(status, { detailed }) — D-06 single translation source
  • requestEstimate(connectionId, params) — calls api.estimateAnalysis
  • enqueueAnalysis(connectionId, params) — calls API, initializes activeAnalysisJob
  • fetchJobStatus(jobId) — updates activeAnalysisJob from API response
  • cancelJob(jobId), retryItem(jobId, itemId), skipItem(jobId, itemId), cancelItem(jobId, itemId)
  • setFailureBehavior(behavior), setDetailedAnalysisProgress(enabled)
  • updateCacheSettings(params) — updates API + local cacheLimit

Tests: 17/17 cloudConnections.analysis.test.js pass (all previously RED).

Task 2: StorageBrowser and CloudFolderView extensions

StorageBrowser.vue — new props, emits, and template regions:

New props:

  • analysisEstimate: Object — shows estimate review modal when set
  • analysisQueue: Array — queue items for aggregate progress display
  • detailedAnalysisProgress: Boolean — simplified vs detailed mode (D-06)
  • selectedItems: Array — pre-selected file IDs for multi-select

New emits:

  • analyze-file(file) — row action; D-01
  • analyze-selection(files[]) — toolbar; D-01/ANALYZE-02
  • analyze-folder — toolbar; D-01
  • analyze-connection({ id }) — toolbar; D-01/ANALYZE-03
  • analysis-confirmed(estimate) — estimate modal start
  • analysis-cancelled — estimate modal cancel
  • analysis-item-cancel({ job_id, item_id }) — D-09/ANALYZE-05
  • analysis-item-retry({ job_id, item_id }) — D-09/ANALYZE-05
  • analysis-item-skip({ job_id, item_id }) — D-09/ANALYZE-05
  • analysis-cancel-all({ job_id }) — D-09/ANALYZE-05

New template regions:

  1. Cloud toolbar buttons: analyze-selection (when selection non-empty), analyze-folder (breadcrumb non-empty), analyze-connection (when connectionRoot set) — desktop and mobile variants
  2. Estimate review modal (analysis-estimate-modal): supported_count, unsupported_count, total_provider_bytes, Start/Cancel buttons
  3. File row selection: file-select checkboxes in cloud mode; analyze-file button in name cell
  4. Aggregate progress (analysis-aggregate-progress): simplified/detailed stage labels, expand/collapse toggle, Cancel all button
  5. Expandable item list (analysis-queue-item-list): per-item status badge, cancel button (for waiting/working), retry+skip buttons (for failed)

CloudFolderView.vue — thin data-provider orchestration:

New state:

  • analysisEstimate: pending estimate to show in modal
  • analysisPending: scope context preserved for confirmed enqueue

New handlers (all route through cloudStore):

  • onAnalyzeFile(file) → requestEstimate(scope='file')
  • onAnalyzeSelection(files) → requestEstimate(scope='selection')
  • onAnalyzeFolder() → requestEstimate(scope='folder', recursive=true)
  • onAnalyzeConnection() → requestEstimate(scope='connection', recursive=true)
  • onAnalysisConfirmed() → enqueueAnalysis with pending context
  • onAnalysisItemCancel/Retry/Skip({ job_id, item_id }) → store actions
  • onAnalysisCancelAll({ job_id }) → cancelJob

StorageBrowser props added: analysisEstimate, :analysis-queue="cloudStore.analysisQueue", :detailed-analysis-progress="cloudStore.detailedAnalysisProgress".

Tests: 24/24 StorageBrowser.analysis-queue.test.js pass; 11/11 CloudFolderRenderedFlow.test.js pass; all 471 frontend tests pass.

Test Results

File Tests Pass Notes
cloudConnections.analysis.test.js 17 17 All previously RED; now GREEN
StorageBrowser.analysis-queue.test.js 24 24 All previously RED; now GREEN
CloudFolderRenderedFlow.test.js 11 11 All existing; no regressions
Overall frontend suite 471 471 No regressions introduced

Deviations from Plan

Auto-fixed Issues

1. [Rule 1 - Bug] XSS test regression: checkbox aria-label contained raw filename

  • Found during: Task 2, full test suite run
  • Issue: Added aria-label=\Select ${file.original_name ?? file.name}`to cloud file checkboxes. The testfilenames_render_as_text_not_htmlchecks that` does not appear anywhere in the rendered HTML. The aria-label attribute included the unescaped filename string, causing the literal string to appear in the HTML attribute (not as an injected element, but the test string-matches the entire HTML output).
  • Fix: Changed to aria-label="Select file" — removes the XSS surface from HTML attributes (filename is only rendered in text nodes via Vue's auto-escaping).
  • Files modified: frontend/src/components/storage/StorageBrowser.vue
  • Commit: 04e6ea2

2. [Rule 1 - Bug] Capabilities test regression: analyze button inside file-row-actions

  • Found during: Task 2, full test suite run
  • Issue: Initial implementation placed the data-test="analyze-file" button inside [data-test="file-row-actions"]. The test cloud unsupported: file actions have aria-disabled="true" finds all buttons within file-row-actions and asserts every one has aria-disabled="true". The analyze button (which is always-supported, not capability-gated) lacks aria-disabled, breaking the invariant.
  • Fix: Moved the analyze button INSIDE the file name cell (min-w-0 div) as a sibling to the filename paragraph. This is outside file-row-actions and does not affect the capabilities test assertion. The button remains discoverable via [data-test="analyze-file"].
  • Files modified: frontend/src/components/storage/StorageBrowser.vue
  • Commit: 04e6ea2

Known Stubs

None — all UI controls are wired to store actions which call the real API. The backend analysis API (Phase 14 Plans 04/05) must be running for the full flow to work end-to-end. In tests, the API is mocked at the api/client.js boundary.

Threat Flags

None — no new network endpoints introduced in this plan. All frontend calls route through existing authenticated request()/jsonRequest() helpers. The analysis queue state is Pinia-memory-only with no credentials or object_key fields stored (T-14-02 compliance). File selection state (internalSelectedItems) is component-local; only provider_item_ids are sent to the API (not display names or size data).

Self-Check: PASSED