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 (Library Module)

Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) backed by LibraryRepository (dio) calling the endpoints in 12_API_Mapping.md. Module-wide rules: no optimistic mutations, server-confirmed writes only; availability and fine amounts are server-authoritative; overdue is client-derived (OQ-2).


1. CatalogCubit (S1 — list + search + filters)

stateDiagram-v2
    [*] --> initial
    initial --> loading : Load(page 1)
    loading --> loaded : BookDoc[] + meta
    loading --> error : 5xx / offline(no cache)
    loaded --> loading : Refresh | Search(q) | LoadMore
    loaded --> empty : 0 items
    error --> loading : Retry
    loaded --> loaded : Filter(chips) — in-memory only
  • State: {status, books[], meta, query, filters{category?, status?, availableOnly?}}.
  • Load: GET /books?page&limit&q (library.controller.ts:33-41); meta.hasNext gates LoadMore (library.service.ts:75).
  • Search: debounced q → server regex (title/author/ISBN, book.repository.ts:26-30).
  • Filters are client-side only (OQ-5): applied to books[] before render, never sent as params.
  • Caching: key lib:catalog:{tenant}:{q}:{page}, TTL 5 min, stale-while-revalidate (00-shared/06 §3.3); pull-to-refresh bypasses.
  • Events: Load, Refresh, Search(q), LoadMore, ChangeFilters, Retry.

2. BookDetailCubit (S2)

  • State: {status, book?}; book = BookDoc model (id, title, author, isbn, publisher?, category?, edition?, totalCopies, availableCopies, status, shelfLocation?, description?).
  • Load: GET /books/:id (library.controller.ts:43-47); 404 → notFound terminal-ish state (empty + back).
  • Derived: isAvailable = availableCopies > 0, canIssue = isAvailable && status == available.
  • Events: Load(id), Refresh (focus), IssueDone/EditDone/DeleteDone (re-fetch or pop).
  • No cache (volatile) — re-fetch on focus (availability changes elsewhere).

3. BookFormCubit (S3 — create + edit)

  • State: {mode, form{title, author, isbn, publisher?, category?, edition?, totalCopies, shelfLocation?, description?}, copiesProjection, status: idle|submitting|error(field?)}.
  • Create submit → POST /books (library.controller.ts:27-31); edit → PATCH /books/:id (:49-53). Success → emit done with server doc; navigate.
  • Copies projection: create available = total (library.service.ts:41-46); edit available = max(0, available + Δ) (:87-90).
  • Errors: 409 duplicate ISBN → fieldErrors.isbn (server message verbatim); 400 → map details[].message → field; 5xx → error (form preserved).
  • No optimistic writes. Draft persist (proposed).

4. IssueCubit (S4)

stateDiagram-v2
    [*] --> idle
    idle --> ready : student selected && dueDate valid
    ready --> submitting : Confirm
    submitting --> success(record) : 201
    submitting --> noCopies : 409 (library.service.ts:109-111)
    submitting --> capReached : 409 (library.service.ts:116-120)
    submitting --> error : 5xx / network / 404
    noCopies --> ready : refresh book (re-check)
    capReached --> ready : refresh student borrows
    error --> ready : Retry
  • State: {status, book, studentId?, dueDate?, notes?, errorKind?}.
  • Submit: POST /books/issue {bookId, studentId, dueDate, notes?} (issue-book.dto.ts:4-21, library.controller.ts:61-65).
  • noCopies/capReached are banner states — re-read the book/borrows and return to ready; never auto-resubmit.
  • Client guard: due date must be future (server has no min — issue-book.dto.ts:14-15).
  • Double-tap guard: submitting blocks re-entry (server has no idempotency — QA-2).

5. BorrowsCubit (S5 — active + history)

  • State: {status, active[], history[], studentId}.
  • Load: GET /books/borrows/:studentId/active (library.controller.ts:79-83) and GET /books/borrows/:studentId (:73-77) — not paginated (library.service.ts:195-201); client shows first N + "load more" (proposed).
  • bookId is populated (borrow-record.repository.ts:31,42); studentId identity comes from Students profile state (OQ-9).
  • Derived per record: isOverdue = status=='active' && now > dueDate, daysOverdue = ceil((now − dueDate)/day), finePreview = daysOverdue × 5 (mirrors library.service.ts:211-217; labeled "estimate").
  • Events: Load(studentId), Refresh, ReturnedDone(recordId) (replace doc), FinePaidDone(recordId) (replace doc).
  • Caching: active 1 min TTL; history 5 min; focus refresh bypasses.

6. ReturnCubit (S6)

  • State: {status, record?, fineAmount?, fineStatus?, notes?, derived{daysOverdue, computedFine}}.
  • Prefill: fineAmount = computedFine (client mirror of server rule); staff may edit (@Min(0)return-book.dto.ts:9-13).
  • Submit → POST /books/return {borrowRecordId, fineAmount?, notes?} (library.controller.ts:67-71) → server doc (server recomputes fine when no override — library.service.ts:159).
  • notActive state on 409 (library.service.ts:155-157) → banner + refresh record.
  • If returned fineAmount > 0 → offer pay: POST /books/fines/:id/pay (library.controller.ts:85-89) → fineStatus: PAID (library.service.ts:203-209).

7. OverdueCubit (S7)

  • State: {status, rows[]} where row = {student, book, dueDate, daysLate, finePreview}.
  • Derived: aggregates active borrows from BorrowsCubit-loaded data (per-student); no server endpoint (OQ-2). Persistent UI notice: "derived — server scan planned".
  • Refresh re-pulls the underlying active lists.
  • Server-side sweep (planned) (IMPLEMENTATION_PLAN.md:228) will replace this.

8. FinePayCubit (inline)

  • {status, recordId?, fineAmount?}; Pay → confirm dialog → endpoint → PAID doc.
  • Server doesn't guard state (OQ-6): cubit only exposes pay when fineStatus == 'pending' and fineAmount > 0.

9. Repository layer

LibraryRepository (dio): methods map 1:1 to the endpoints in 12_API_Mapping.md; wraps the envelope, unwraps data/meta, throws typed ApiException{code,message,details,requestId}; injects auth token + x-request-id. Models (Book, BorrowRecord) parse the raw docs; enums kept as string constants mirroring book.schema.ts:7-12, borrow-record.schema.ts:7-18.

10. Cross-cubit refresh rules

Write doneRefresh
Book created/updated/deletedCatalogCubit + BookDetailCubit
Issue succeededBookDetailCubit (availability) + BorrowsCubit (student)
Return succeededBookDetailCubit + BorrowsCubit + OverdueCubit
Fine paidBorrowsCubit (record chip)

All refreshes are server-confirmed re-fetches; no local doc mutation except replacing with the server response.