Per-screen Cubits (Flutter/bloc; proposal, 00-shared/06 ) + the module-wide
guardian-link cache that admin screens share. Backed by ParentsRepository (dio)
calling the endpoints in 12_API_Mapping.md .
One ParentsStore (Cubit-injected singleton within the module scope) holds:
List<ParentRef> listPage, meta, and a per-parent Map<parentId, List<Link>> links.
Invariant: any mutation (link create/delete) invalidates the affected parent's links
cache so every screen re-fetches the children section on next open.
No persistence for links (server truth; small data); list uses last-good cache per
00-shared/06 §3.3 (TTL 5 min; RefreshIndicator bypasses).
Screen Cubit Events → State
Parents list ParentsCubitLoad, Refresh, LoadMore, Retry → {initial, loading, loaded(items, meta), empty, error(code), loadingMore}
Parent detail ParentDetailCubitLoad(id), Refresh, Retry → {initial, loading, loaded(parent, links, students), notFound, error(code)}
Create/Edit form ParentFormCubitInit(existing?), SetField, Submit(form) → {idle, dirty, saving, saved(parentId), duplicate(existingId), validation(errors), error(code)}
Link sheet LinkCubitInit(context) (parentId+studentId known), PickStudent, SetRelationship, TogglePrimary/Pickup/Financial, Submit → {idle, searching, ready, saving, linked(link), warnDuplicate, warnSecondPrimary, error}
Unlink UnlinkCubitConfirm(linkId) → {idle, confirming, done, notFound, error}
My children (forward-looking) ParentChildrenCubitLoad() → {initial, loading, loaded(children), empty, error}
Child switcher ChildSwitcherCubitSwitchChild(id) → {children, selectedChildId}
My profile (forward-looking) MyProfileCubitLoad(), Submit(form) → {initial, loading, loaded(profile), saving, saved, error}
class ParentRef {
final String id; String? userId;
String? occupation, company, relationshipNotes;
num? annualIncome; int emergencyContactPriority;
bool pickupAuthorization;
DateTime createdAt, updatedAt;
}
class Link {
final String id; String studentId, parentId;
RelationshipType relationship; // mother|father|guardian|grandparent|relative|foster_parent
bool isPrimaryGuardian, financialResponsibility, pickupAllowed;
int emergencyPriority;
}
class StudentRef { final String id; String admissionNumber; String? rollNumber; String? gradeId, sectionId, classId; String status; }
UI event Cubit method Repository call
list open / pull load() / refresh()repo.parents(page, limit)
scroll end loadMore()repo.parents(page+1)
row open detail.load(id)repo.parent(id) + repo.parentLinks(id) + per-child repo.student(id)
create submit form.submit()repo.createParent(dto)
edit submit form.submit()repo.updateParent(id, dto)
delete detail.remove(id) (menu)repo.deleteParent(id)
link submit link.submit()repo.linkParent(studentId, linkDto)
student guardians embed studentsGuardians.load(studentId)repo.studentLinks(studentId) + per-parent repo.parent(id)
unlink confirm unlink.confirm(linkId)repo.unlink(linkId)
set primary link.switchPrimary(link)repo.unlink(oldPrimary) + repo.linkParent(studentId, newDto) (OQ-4)
child switch switcher.switch(id)— (local state)
Parents list: last-good cache sl:cache:parents:{tenant}:{page}; RefreshIndicator
bypasses; infinite-scroll appends.
Detail: no cache — always fetch on open; children section re-fetches after any
link mutation (invariant §1).
My children (forward-looking): last-good cache 5 min + banner; child switcher
selection persisted in memory only (per session).
No WS surface today. ParentCreated → in-app notification job
(event-queue-map.ts:37); when the notification center + WS ship (planned), a
parent.linked push invalidates the detail cache and nudges refresh
(00-shared/06 §3.4).
Action Error State →
create 409 duplicate(existingId) → banner + open existing
any 404 notFound → AppErrorState (detail) or treat-as-removed (unlink)
link duplicate pre-check warnDuplicate → warning + block submit (OQ-2)
link second primary warnSecondPrimary → warning, submit allowed (OQ-5)
any 429 rateLimited → countdown
any 401 session expiry flow (global)
any 5xx error(code) → AppErrorState + requestId
Pure-Dart cubits; unit-test: pagination mixin (append/refresh/meta), primary-switch
two-step sequencing, duplicate-link warning logic, 404 mapping.
Widget tests: list 3 states; detail children skeleton→loaded/empty; form
duplicate/validation; link sheet warnings; switcher selection.
ConnectivityCubit gates all writes offline; reads serve cache + banner.
AuthCubit session expiry → re-login; module state discarded (no cross-login
persistence).
Role changes (RBAC (planned)) rebuild route visibility; ParentsCubit survives only
under parent.read.
TenantContext (from AuthCubit) implicit in every repository call — never stored
client-side per record.