Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

13 — State Management (Reports Module)

Cubit/BloC layout for the Reports module per 00-shared/06 (recommendation, not an implemented decision). One Cubit per screen cluster; polling owned by a single engine. Includes the job state machine.


1. Cubits

ReportCatalogCubit (S1)

  • State: CatalogState { templates: ReportTemplate[] } — static; loads once; (forward-looking) fetches server-supported types.
  • No async beyond init.

GenerateReportCubit (S2)

  • State: GenerateState { type, params, submitting, fieldErrors }.
  • Events: SelectType, UpdateParam, Submit.
  • Submit: validate (client presence rule 08 §3.1) → POST /reports/generate → success: emit jobCreated(jobId) → router to S4 (reports.controller.ts:14-18).
  • Server 400 maps to fieldErrors; 429/5xx to snackbar (12 §client table).

ReportJobListCubit (S3)

  • State: JobListState { jobs: LocalJobEntry[], filter, loading }.
  • LocalJobEntry = {jobId, type, status, createdAt, params?, source} from the local history store (12 E4 gap) — refreshed by polling visible rows.
  • Events: LoadHistory, Refresh, Filter, Retry(jobId).

ReportJobDetailCubit (S4) — owns the polling engine

  • State: JobDetailState { job?, status, elapsed, pollPaused, error? }.
  • Events: Open(jobId), PollTick, Retry, CheckAgain (manual after pause).
  • Polling engine (10 §1): Timer.periodic(2s) while status non-terminal; GET → map doc; terminal → cancel timer; network/5xx → pause + emit pollPaused; 404 → terminal not-found.
  • Retry: re-POST params (reports.service.ts:31-43) → pushReplacement detail of new job.

ReportResultCubit (S5)

  • State: ResultState { job, renderModel, exporting }.
  • Pure mapping from job.result per type (06 §S5 renderers, reports.service.ts:96-160).
  • Export (planned): no-op/disabled today; future: E3 download → share sheet.

2. Job state machine (server-authoritative)

Statuses from report-job.schema.ts:13-18; transitions applied by report-job.repository.ts:21-47 (markProcessing/markCompleted/markFailed) and the initial queued default (report-job.schema.ts:28-29). The Cubit mirrors, never predicts.

stateDiagram-v2
    [*] --> queued: job created (POST /reports/generate)\nreports.service.ts:31-43
    queued --> processing: worker executes\n(reports.service.ts:59, markProcessing)
    processing --> completed: result stored\n(markCompleted, repo :27-38)
    processing --> failed: error stored\n(markFailed, repo :40-47)
    queued --> failed: worker error path\n(reports.service.ts:79-82)
    failed --> queued: user Retry = NEW job POST\n(new jobId, old stays failed)
    completed --> [*]: result readable via GET\n(reports.service.ts:46-50)
    failed --> [*]

Notes:

  • queued → failed without processing is possible (executor throws before markProcessing completes — reports.service.ts:59-82); UI must not assume processing was seen.
  • No processing → queued requeue exists in code — a crashed worker leaves a job stuck in processing (QA item, 14 §5).
  • Retry is never a status transition on the same doc — always a new job.

3. Data persistence

StoreKeyTTLPurpose
local historyreports.localHistorycap 50 entriesS3 list (E4 gap)
last-good job docreports.job.{jobId}24 hS4 offline render
last-good resultreports.result.{jobId}24 hS5 offline render

Server remains source of truth; caches are read-only mirrors.

4. Loading / streaming / realtime

ScreenLoadingStreamingRealtime
S1none (static)
S2submit spinner
S3skeleton rowspoll visible rows on refresh
S4full skeletonpoll 2 s (the module's realtime)
S5render on doc— (terminal)

No WS topic for reports (00-shared/07 §8 has none) — (forward-looking): push notification on ReportGenerated (04-Modules/Reports.md:33).