--- created: 2026-09-05 status: complete requirements: - REQ-SYSTEM-PAGE-STORAGE-OVERVIEW-PARALLEL-SCAN-002 system_design: - ../../specs/system-page/system-design/storage-analysis-progress.md legacy_specs: [] --- # Implementation Plan: Progressive Storage Analysis ## Overview Reduce scan time with bounded filesystem concurrency. Then make overview reads nonblocking and stream coarse progress to the Storage page. The order protects the cache contract. The scanner lands first, the backend state contract follows, and the web work consumes that contract. ## Scope ### In scope - Bounded, context-aware byte measurement for read-only storage analysis. - Nonblocking overview reads with stale-while-revalidate behavior. - Coarse progress notifications with a polling fallback. - First-scan partial results and atomic replacement of older snapshots. - Scan duration, cache lifetime, and next-refresh details. - Desktop and phone access to the timing disclosure. - Focused public operator documentation. ### Out of scope - Changes to destructive cleanup or quarantine rules. - A persistent overview snapshot across backend restarts. - A user-configurable scan worker count or cache lifetime. - File-level progress events or exact time-remaining estimates. - Remote-executor disk analysis. - Automatic scans when no client reads the Storage overview. ## Technical approach ### Bounded filesystem measurement Add `apps/backend/internal/system/storage/filescan`. Its shared limiter will permit four active directory partitions across workspace, Go-cache, and temporary-artifact analysis. Partition each root by immediate children. Preserve provider-specific symlink rejection, marker exclusion, warning order, and path ownership. Inject the limiter through `storageOverview` provider construction. Keep destructive provider paths on their current serial code. ### Progressive cache and API Extend `OverviewCache` with process-local analysis state and a nonblocking read. Keep `Refresh` as the tracked manual path and join every concurrent request to one scan generation. Add source progress callbacks to `storageOverview`. Keep the last successful `Summary` atomic and store cold-scan partial results separately. Extend `GET /api/v1/system/storage` with nullable first-scan snapshot fields and an `analysis` object. Include scan timestamps, duration, cache lifetime, refresh time, source progress, and a partial summary. Publish `system.storage.analysis.updated` with generation and state only. Add the event constant, gateway action, subscription, and frontend revision field. ### Web state and presentation Update storage API types and the Zustand system slice for analysis state and revision ordering. Refactor `useStorageMaintenance` so WebSocket revisions reload only the overview. Poll every 1.5 seconds while analysis is active and stop in a terminal state. Schedule one read at `refresh_due_at` while the Storage page remains mounted. Preserve request generation checks and terminal Analyze refresh behavior. Update `StorageOverviewCard` to show cold-scan source progress and counted-so-far totals. Keep a stale completed snapshot visible during revalidation. Add an information icon beside the completed snapshot time. Reuse `StorageSettingHelp` interaction and permit structured content for the three timing values. ### Documentation and observability Update `docs/public/operations.md` with the asynchronous scan, 15-minute cache, and Analyze behavior. Add structured scan completion logs and storage scan metrics. Do not include task paths in logs or metrics. ## Tests | Acceptance criteria | Evidence | | --- | --- | | `AC-SYSTEM-PAGE-STORAGE-OVERVIEW-PARALLEL-SCAN-002.1`, `.10` | Cache and handler tests block the provider and prove immediate, single-flight reads. | | `.2`, `.3` | Provider, cache, hook, and card tests publish sources progressively and mark partial totals. | | `.4`, `.9` | Cache tests keep the last snapshot through expired and failed refreshes. | | `.5` | Event, hook, and fake-timer tests cover WebSocket refresh and the 1.5-second fallback. | | `.6`, `.7` | Component and Playwright tests cover timing values, focus, hover, tap, and touch size. | | `.8` | Hook fake-timer tests request at `refresh_due_at`. Analyze tests retain forced refresh. | The filesystem scanner tests will cover exact bytes, no-follow behavior, deterministic warnings, cancellation, and a four-worker ceiling. The provider tests will use barriers instead of elapsed-time assertions. They will prove that two partitions enter before either partition is released. ## E2E tests Update `apps/web/e2e/tests/system/storage-maintenance.spec.ts` for progressive cold-scan values, stale refresh, and pointer or keyboard timing disclosure. Update `apps/web/e2e/tests/system/mobile-storage-maintenance.spec.ts` for tap disclosure, a 44-pixel trigger, progressive values, and no horizontal overflow. Both flows map to `AC-SYSTEM-PAGE-STORAGE-OVERVIEW-PARALLEL-SCAN-002.2` through `.8`. ## Mobile design contract Desktop and phone users enter through `/settings/system/storage`. Both receive the same snapshot, progress, and timing data. The existing Storage page and `StorageSettingHelp` are the nearest shipped examples. They provide the one-column card flow and the hover, focus, and pinned-tap help interaction. Storage analysis remains an inline card in the page scroll. No drawer or full-height surface is necessary because the timing content contains three short values. The information trigger will use the existing 44-pixel phone hit area and compact desktop size. The page keeps one scroll owner and no horizontal overflow. Business state, progress derivation, and refresh scheduling remain shared across viewports. Only the responsive trigger size differs. ## Work orders - [x] [Task 01: Build bounded filesystem scanner](task-01-bounded-filesystem-scanner.md) - [x] [Task 02: Add progressive overview state](task-02-progressive-overview-state.md) - [x] [Task 03: Render live analysis details](task-03-live-analysis-ui.md) - [x] [Task 04: Prove progressive storage behavior](task-04-storage-progress-e2e.md) ## Dependency order 1. Task 01 creates the scan and progress primitives. 2. Task 02 uses those primitives for the cache, API, events, and observability. 3. Task 03 consumes the backend contract and updates public operator documentation. 4. Task 04 proves the integrated desktop and phone behavior. All tasks are sequential. They share contracts, fixtures, and storage test files. ## Verification results - Task 01: `go test -race ./internal/system/storage/filescan ./internal/system/storage/workspaces ./internal/system/storage/gocache ./internal/system/storage/tempartifacts ./internal/backendapp` passed (853 tests). - Task 01 benchmark: `go test ./internal/system/storage/filescan -run '^$' -bench 'BenchmarkMeasureTrees' -benchmem` passed at 81,604 ns/op and 11,367 B/op. - Task 02: `go test -race ./internal/system/storage ./internal/backendapp ./internal/gateway/websocket` passed (1,423 tests). - Task 03: the focused web suite passed (46 tests), the extracted terminal-refresh suite passed (2 tests), typecheck, lint, i18n checks, and public-doc validation passed. - Task 04: `make build-web`, `make build-backend`, Chromium storage E2E (8 tests), and mobile Chromium storage E2E (6 tests) passed. - Changed-file Go lint passed with `golangci-lint run ./... --new-from-rev=481359ddb282fd7d3e8c9cdff99da6f9bdd24c7c --timeout=5m`. - Specification lint passed with `python3 scripts/lint-spec-files.py --all`. ## Risks - Parallel directory reads can increase contention on rotating disks. The shared four-walker limit bounds this risk, and timing evidence will record regressions. - Partial results can look final. The API separates them from completed snapshots, and the UI marks aggregates as counted so far. - Late progress can corrupt a new scan. Generation checks must guard every progress and completion write. - Broadcast events can disclose storage facts. The event carries only generation and state. - A missed event can leave stale UI. The active-scan poll provides recovery. - A page timer can drift after sleep. The timer callback reads the server state instead of assuming that a scan started.