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

06 — Screen Specifications (Parents Module)

Production-grade specs for each Parents screen: chrome, states, components, micro-interactions, a11y, responsive, dark/light, keyboard, motion. Tokens: Sp = 00-shared/02 §3, Ty = typography §2, El = elevation §5, R = radius §4, Mo = motion §6. Components reference 00-shared/03 + 07_Component_Library.md.


1. Parents List /parents

Chrome: standard shell AppBar (headlineMedium "Guardians", subtitle = count from meta.totalItems); horizontal padding 16 phone / 24 tablet+; AppBar elevation e-1.

Layout (phone):

AppBar "Guardians"                        headlineMedium
AppSearchBar  (client-side filter only — server ignores q, OQ-7)
[AppListTile ×N]                          ParentListTile: AppAvatar 40, name titleMedium,
                                          subtitle "occupation · company",
                                          trailing children-count badge
infinite scroll footer                    "End of list" bodySmall / spinner
FAB "Add guardian"                        FloatingActionButton.extended, Icons.person_add

States:

  • initial → AppSkeleton(list) (8 rows, shimmer m-slow; reduced-motion static tint).
  • success+data → list; RefreshIndicator (bypasses cache).
  • success+empty → AppEmptyState Icons.groups, "No guardians yet", action "Add first guardian".
  • error → AppErrorState(code); 5xx generic + requestId hint; Retry re-fetches page 1.
  • offline → AppOfflineBanner top; stale-while-revalidate from last-good cache (00-shared/06 §3.3).
  • permission (planned) → 403 screen; today any JWT can list (OQ-8).

Pagination: ParentsCubit.loadMore() on scroll end when meta.hasNext (pagination-query.dto.ts:32-39); bottom spinner fade-in m-instant; page increments 1-based; limit 20 default (pagination-query.dto.ts:19).

Motion: row entrance stagger m-entrance (40 ms); FAB hide on scroll-down, show on scroll-up (00-shared/03 D).

A11y: search results announced via live region; row semantics "Guardian , linked children"; focus ring on rows (desktop).

Tablet/desktop: ≥ 840 dp master-detail — list pane 320–360 dp, detail in right pane (see §2); hover row tint m-fast; N key = new guardian; Esc clears search.

Dark/light: token-only (surface, surfaceContainerLow, onSurfaceVariant); children-count badge primaryContainer.

2. Parent Detail /parents/:id

Chrome: AppBar title = display name (from joined User), back to list; body scrolls.

Layout (phone):

Header card                            AppCard e-1
  AppAvatar 64 (User.avatarFileId / initials)
  name            titleLarge
  occupation · company   bodyMedium onSurfaceVariant
  AppInfoRow: annual income, relationship notes, emergency priority, pickup
  actions: [Edit] OutlinedButton → /parents/:id/edit
Section "Linked children"              AppSectionHeader (+ "Link" TextButton)
  LinkedChildCard ×N                   (see component spec §2 of 07)
  empty → AppEmptyState "No linked children — Link a student"

States:

  • loading → AppSkeleton header card + 2 child cards.
  • 404 → AppErrorState "Guardian not found" (parent.service.ts:51) + back button.
  • error → AppErrorState(code) + Retry.
  • children load (GET /parents/:id/students) is a second call (parent.service.ts:69-72): header renders first, children section shows inline AppSkeleton rows; failure in children section only → inline AppErrorState(compact).

Linked children rendering (join, OQ-10): each card fetches GET /students/:id (client-side join). While joining: AppSkeleton inline. On child 404 (deleted) → card shows "student no longer active" state with unlink action still available.

Per-child menu (AppMenu):

  • "Set as primary" → shown when isPrimaryGuardian is false; action = delete old primary link + recreate this one (no link PATCH endpoint — OQ-4); confirm dialog explains the two-step.
  • "Unlink" → AppDialog (§5) → DELETE /parents/link/:linkId → row fade-out m-base + snackbar "Guardian unlinked" (student-parent-link.service.ts:38-41).

Guardian-relationship presentation: RelationshipChip per child + PrimaryGuardianBadge "Primary" (primaryContainer tonal) when isPrimaryGuardian (student-parent-link.schema.ts:27-29).

Motion: hero AppAvatar list→detail m-slow; card entrances m-entrance stagger.

A11y: sections use Semantics(headers:); menu items labeled; badge announced "Primary guardian".

Tablet/desktop: master-detail right pane; edit opens as overlay page in pane.

3. Create / Edit Parent Form /parents/new, /parents/:id/edit

Chrome: full-screen page (00-shared/05 §5 — > 3 fields); AppBar title "Add guardian" / "Edit guardian"; scroll + keyboard avoidance.

Create layout (phone):

User picker (create only)   AppTextField(read-only) + search sheet over GET /users?q=
                            — shows User.displayName + email (users.service.ts:90-115)
occupation                  AppTextField
company                     AppTextField
annualIncome                AppTextField (number, currency locale)
relationshipNotes           AppTextField multiline
emergencyContactPriority    AppTextField (number 0-99)
pickupAuthorization         AppSwitch ListTile "Authorized to pick up child"
                            (default false — parent.schema.ts:28)
CTA "Save guardian"         FilledButton fullWidth h 48

Edit layout: same minus user picker (UpdateParentDto has no userId, update-parent.dto.ts:4-34).

Validation (client, mirrors DTO):

  • userId required + MongoId (create-parent.dto.ts:6-7) — client always passes a picked user's id.
  • occupation/company/relationshipNotes strings (IsString optional).
  • annualIncome / emergencyContactPriority numbers (client: NumberFormat parsing; server has no bounds — annualIncome unbounded, emergencyContactPriority unbounded — client caps 0–9999, propose server Min/Max).
  • pickupAuthorization boolean.
  • Server 400 VALIDATION_ERROR details map to fields (http-exception.filter.ts:104-107).

States:

  • saving → CTA spinner, fields disabled, no double-submit.
  • 409 DUPLICATE_RESOURCE (create) → inline AppBanner(error) "A guardian profile already exists for this user." + button "Open existing profile" → /parents/:id (parent.service.ts:32-34).
  • 404 (edit) → AppErrorState "Parent not found." (parent.service.ts:77).
  • success → pop to detail; snackbar "Guardian saved"; audit event ParentUpdated (event-queue-map.ts:38) or ParentCreated → in-app (event-queue-map.ts:37).

A11y: labels visible + linked; errors announced + focus first invalid (09 §10); autofill hints organization for company (desktop).

Motion: route push m-base; error slide-in m-fast.

Tablet/desktop: centered card ≤ 560 dp; Enter submits; Tab order = visual.

Chrome: AppBottomSheet (half → full height on phone when keyboard); DragHandle; title "Link guardian to student"; scrollable body.

Layout:

Student (if not preselected)   AppSearchBar → result rows (admissionNumber + name)
                               selection replaces field with chosen chip + change
relationship                   AppDropdown 6 values (enum student-parent-link.schema.ts:7-14)
isPrimaryGuardian              AppSwitch "Primary guardian" (default false)
pickupAllowed                  AppSwitch "Allowed to pick up" (default true — schema:33-34)
financialResponsibility        AppSwitch "Financially responsible" (default false)
emergencyPriority              AppTextField number (0-99)
CTA "Link guardian"            FilledButton fullWidth

Warnings (client-side pre-checks over loaded links):

  • Pair already linked → AppBanner(warning) "Already linked — saving creates a duplicate (server allows it, OQ-2)" + disable CTA or "Link anyway".
  • Student already has a primary and isPrimaryGuardian toggled on → AppBanner(warning) "This student already has a primary guardian. Two primaries will exist." (OQ-5).

States: submitting → CTA spinner; 400 → field errors; 404 student → banner (only if server ever enforces — today findById result is discarded, OQ-3); success → sheet pops with link doc → children section refreshes + snackbar "Guardian linked" (lightImpact).

A11y: sheet = dialog semantics; each switch Semantics(toggled:); dropdown role combobox.

Motion: sheet slide-up m-base; warning banner expand AnimatedSize.

Chrome: AppDialog centered card; title "Unlink guardian?"; body: "Remove as a guardian of ? The guardian profile and other links are kept." (04-Modules/Parents.md:58).

  • Primary warning line (error container, error icon) when isPrimaryGuardian: "This child will have no primary guardian after unlinking." (OQ-4).
  • Actions: TextButton Cancel / FilledButton destructive "Unlink".
  • Behavior: server-first (no optimistic — irreversible); heavyImpact on confirm; success → row removed m-base fade + snackbar; 404 → treat-as-removed (student-parent-link.service.ts:40).

6. My Children /my/children (forward-looking)

Chrome: shell AppBar "My children"; body = ChildSwitcherBar + selected child summary.

Composition:

ChildSwitcherBar            horizontal chips (avatar + name), selected primaryContainer
ChildSummaryCard            name, grade/class/section (student.schema.ts:33-39),
                            admissionNumber mono
Section entries             Attendance / Results / Fees (other modules; student.read)
AppBar action               AppAvatar → profile menu → /my/profile

States: loading skeleton; empty AppEmptyState "No linked children"; 404/error → AppErrorState; offline → cached list + banner. Blocker note: depends on (planned) my-profile/children endpoint (OQ-1) — the screen spec is frozen until then; do not build against a client-side workaround.

7. Child Switcher (component)

Spec: ChildSwitcherBar — horizontal SingleChildScrollView of FilterChip-style avatar chips (40 dp, initials/avatar, name below, primaryContainer selected state); ChildSwitcherCubit.selectedChildId drives all child-context modules; switch animates AnimatedSwitcher fade m-fast; a11y chips Semantics(button, toggled); announced "Switched to ".

8. My Profile /my/profile (forward-looking)

Same form chrome as §3 (edit mode) with read-only header (identity from users, relationship per child from links). PATCH /parents/:id on save (parent.service.ts:74-87). Blocks: no self-id resolution (OQ-1) — frozen spec.


Shared micro-interaction & motion notes (all screens)

  • Route transitions m-base; snackbar success < 3 s, error ≥ 4 s (00-shared/03 A).
  • Cards e-1; sheets e-5; dialogs e-4; FAB e-3, r-full.
  • Touch targets ≥ 48 dp; rows ≥ 56 dp (02 §9).
  • Safe areas respected; landscape lists scroll.
  • Reduced motion: fades only, no stagger/shake (08 §4).