GET /books/:id (library.controller.ts:43-47); 404 → AppEmptyState "Book not found" + back (library.service.ts:80)
Availability
availableCopies > 0 → issue enabled; 0 → disabled + copy "All copies are on loan" (issue is a server 409 too, library.service.ts:109-111)
Status copy
damaged/lost (book.schema.ts:7-12) → issue disabled + warning badge (no endpoint transitions to these — OQ-4)
Edit
PATCH flow (S4); after save, detail re-fetches
Delete
menu → AppDialog confirm → DELETE /books/:id (library.controller.ts:55-59); 409 "Cannot delete book with active borrow records." (library.service.ts:99-101) → dialog explains active loans; 200 → snackbar + pop to catalog (soft delete, library.service.ts:103)
Edit copies
copy delta adjusts availableCopies (floored at 0) (library.service.ts:87-90) — surface "available will change by same amount" note
Active loans
not exposed by any endpoint — skip section (planned)
a11y
status badges read as full sentences ("3 of 5 copies available"); actions grouped
Motion
hero transition from list row (cover/title) m-base(00-shared/08)
create: availableCopies = totalCopies (library.service.ts:41-46); edit: server shifts availableCopies by the delta, floor 0 (library.service.ts:87-90) — show the projected availability inline
409 duplicate ISBN (library.service.ts:37-39) → inline under ISBN: "A book with this ISBN already exists" + link to the existing book if client has it in cache
Save
create POST /books → 201 → push detail; edit PATCH /books/:id → 200 → pop with updated doc; no optimistic writes
Draft
unsaved-changes guard on back; draft persist (proposed)
a11y
every field labelFor-linked; first invalid field focused on 400
Active = GET /books/borrows/:studentId/active (library.controller.ts:79-83); History = GET /books/borrows/:studentId (library.controller.ts:73-77) — includes returned + any active; not paginated (library.service.ts:195-201); client shows first N with "load more" (proposed)
Populate
repo populates bookId (borrow-record.repository.ts:31,42) → title/author render; studentId is not populated — student identity comes from the profile context (OQ-9)
Due date
prominent on active cards; overdueTag when active && now > dueDate (derived, OQ-2); fine preview ceil(days)×5 shown but marked "estimate"
days overdue = ceil((now − dueDate)/day); fine preview = days × 5 — client mirrors server rulelibrary.service.ts:211-217; server recomputes anyway (:159)
Override
fineAmount? editable @Min(0) (return-book.dto.ts:9-13) — server prefers dto.fineAmount ?? calculateFine(dueDate) (library.service.ts:159); keep override only for staff (hide for student/parent — they can't return anyway)
Submit
POST /books/return {borrowRecordId, fineAmount?, notes?} (library.controller.ts:67-71)
200
record status=RETURNED, returnedAt=now, fineAmount, fineStatus = pending if fine > 0 else unset (library.service.ts:161-169); book availableCopies+1, status available (library.service.ts:172-178)
409
"Book was not actively borrowed." (library.service.ts:155-157) → refresh record, banner "Already returned"
┌───────────────────────────────────────────┐
│ ← Overdue [refresh] │
│ ⚠ Derived from client data — server │
│ overdue scan planned │
│ [Card] Student · Book Title │
│ due 12 Jul · 15 days late · fine 75 │
│ [Return] │
└───────────────────────────────────────────┘
derived by filtering active borrows where now > dueDate (per-student active lists; no list-all endpoint — OQ-2). Server findOverdue() exists but is unused (borrow-record.repository.ts:34-43); a dedicated endpoint + scheduled scan is (planned) (IMPLEMENTATION_PLAN.md:228)
Honesty
persistent info banner: "Derived from client data — server overdue scan planned"
Rows
student (from profile state), book title, dueDate, days late, fine preview ceil(days)×5, [Return] shortcut → S6
Empty
"Nothing overdue"
Refresh
re-fetches the underlying active lists
Permission
staff only (books.issue/books.return); hidden otherwise