13 — State Management (Homework Module)
- 1. HomeworkListCubit (list screen, both roles)
- 2. HomeworkDetailCubit
- 3. HomeworkFormCubit (create + edit)
- 4. SubmitCubit (submission form)
- 5. SubmissionsCubit (teacher list)
- 6. GradeCubit (grading sheet)
- 7. Attachment upload (shared mixin)
- 8. Realtime & cross-cutting interplay
- 9. Testing hooks (
00-shared/06 §6)
Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06) backed by
HomeworkRepository(dio) calling the endpoints in 12_API_Mapping.md. Module-wide rules: no optimistic mutations, server-confirmed writes only; submission is a write-once op with a 409 handler; grading is optimistic-free (side effects: events → notifications).
1. HomeworkListCubit (list screen, both roles)
stateDiagram-v2
[*] --> initial
initial --> loading : Load(role)
loading --> loaded : class homework array (dueDate desc)
loading --> error : 5xx / 404
loaded --> loading : Refresh (pull) | ChangeClass(filter)
loaded --> empty : 0 items
error --> loading : Retry
loaded --> loaded : Delete done (row removed server-confirmed)
- State:
{status, items[], classId, role}. items=HomeworkSummary[](doc → model:id, teacherId, classId, subjectId, title, description, attachments, assignedDate, dueDate, status).- Student variant:
classIdfromStudentProfileCubit(Students module, cached 24 h); emitsmyStateper card lazily via per-cardGET /homework/:id/submissionsfilter by ownstudentId(proposed)— OQ-6. - Caching: list cache key
hw:list:{tenant}:{classId}, TTL 5 min, stale-while-revalidate (00-shared/06 §3.3);RefreshIndicatorbypasses. - Events:
Load,Refresh,ChangeClass(classId),Retry,Delete(id)→{status}. Delete is server-confirmed; on 200 remove fromitems.
2. HomeworkDetailCubit
- State:
{status, homework?, mySubmission? (student), submissionsCount? (teacher)}. - Load:
GET /homework/:id→ doc; teacher additionally fetchesGET /homework/:id/submissions(count). Student fetches submissions and filters own row (studentIdfrom profile). - Events:
Load(id),Refresh,EditDone(re-fetch after PATCH),SubmitDone(replacemySubmissionwith server doc). - Late flag derived:
submittedAt > dueDate(client-computed; OQ-1). - No cache (volatile); re-fetch on focus (grade may have landed elsewhere).
3. HomeworkFormCubit (create + edit)
- State:
{mode, form{title, description, classId, subjectId, dueDate, status?}, attachmentUploads: Map<fileKey, UploadState>, status: idle|submitting|error(field?) }. UploadState:{idle, uploading(progress), uploaded(fileId), failed}— one per file; onlyuploadedids enterattachments[]on submit.- Create submit →
POST /homework→ success → clear draft → navigate detail. - Edit submit →
PATCH /homework/:id→ refresh detail. - Errors: 400 →
fieldErrors(mapdetails[].message→ field); 5xx →error(form preserved). Draft persisted locally on abandon(proposed). - No optimistic writes anywhere in this cubit.
4. SubmitCubit (submission form)
stateDiagram-v2
[*] --> idle
idle --> uploading : pick files
uploading --> ready : all uploaded
uploading --> failed : file error (retry per file)
ready --> submitting : Submit
submitting --> done(submittedDoc) : 201
submitting --> duplicate : 409 "Already submitted."
submitting --> error : 5xx / network
duplicate --> done : treat as submitted (fetch doc)
- Events:
AddFile,RetryFile,RemoveFile,Submit(remarks, attachments). - Key decision:
duplicateis a terminal success-like state — navigate to detail with info banner (homework.service.ts:92semantics). - Late-warning computed from
homework.dueDatewhennow > dueDate(banner, never blocks). - No optimistic write;
submittedAtfrom server doc (homework.service.ts:96).
5. SubmissionsCubit (teacher list)
- State:
{status, items[], homeworkTitle, grouped{ungraded[], graded[]}}. - Load:
GET /homework/:id/submissions→ map docs (studentId, submittedAt, status, marks?, remarks?, attachments, gradedAt?); client groups ungraded (status='submitted') / graded (status='graded') and computesisLate. - Events:
Load,Refresh,GradeDone(submissionId, updatedDoc)→ replace item + regroup. - No pagination (array).
6. GradeCubit (grading sheet)
- State:
{status: idle|grading|done|error, current: SubmissionGradeModel?, form{marks, remarks}, isRegrade}. isRegrade = current.status == 'graded'(schema enumhomework-submission.schema.ts:24).- Submit →
PATCH …/grade→ 200 doc →done→ pop with result →SubmissionsCubitapplies (GradeDone). - Optimistic vs server-confirmed: grading is server-confirmed by design — the
row's
marks/status/gradedAtcome only from the 200 payload; no local paint-before- write (eventHomeworkGradedhas side effects: notifications). Rationale:00-shared/07 §9write-with-side-effects rule. - Errors: 404 →
gone(pop + refresh); 400 → field; 5xx → keep form.
7. Attachment upload (shared mixin)
stateDiagram-v2
[*] --> idle
idle --> uploading : POST /files/upload (multipart, progress)
uploading --> uploaded : 201 FileRecord → fileId
uploading --> failed : network / 4xx / 5xx
failed --> uploading : Retry (new upload, no resume — B7)
uploaded --> [*] : referenced in attachments[]
- Progress via dio
onSendProgress;uploadingcount gates the Submit/Assign button (submitEnabled = no uploading && no failed).
8. Realtime & cross-cutting interplay
- WS: no homework topic in
00-shared/07 §8— subscribe tonotification.new(planned); on reconnect re-fetch current screen (00-shared/06 §3.4). ConnectivityCubit: offline → list from cache + banner; submit/grade/create blocked with guidance (no offline write queue defined for homework writes — only uploads retry).AuthCubitsession expiry mid-flow → re-login → screen re-loads (state preserved where safe).
9. Testing hooks (00-shared/06 §6)
- Unit:
SubmitCubit409→duplicate transition;GradeCubitregrade branch;LateBadgederivation (timezone-safe); upload state machine. - Widget: list loading/error/empty; submit states incl. duplicate banner; grade sheet error keeps values; regrade warning visible.