10 — Interaction Specification (Results Module)
- I1 — Marks Entry Grid (S1)
- I2 — Exam-Subject Detail (S2)
- I3 — Student Results List (S3)
- I4 — Report Card (S4)
- I5 — Grade Summary & Publish (S5)
- I6 — Motion & feedback summary
Precise interactions per screen. Motion defaults per 00-shared/08_Interaction_&_Motion.md; a11y obligations per 00-shared/09_Accessibility_Baseline.md; components
C-*per 00-shared/03_Component_Library.md,R-*per 07_Component_Library.md.
I1 — Marks Entry Grid (S1)
I1.1 Keyboard & focus
- Tab order per row:
marksObtained→grade→remarks→ next row'smarksObtained. - Enter commits the focused cell and saves the row (per-row upsert,
examination.service.ts:147-175); Tab fromremarksmoves to next student's marks field (when grid layout) or commits + moves (card layout). - Esc clears the row's dirty state back to last-saved value.
I1.2 Save choreography
- Row states:
clean → dirty → saving → saved | error(S1 §3). saving: inlineC-ProgressIndicatoron the row only — never a full-screen block (row independence).saved: row turns clean;C-AppSnackbar"Saved — {student}" < 3 s; multiple saves coalesce into one snackbar.error: field showserrorText; row stays dirty; snackbar ≥ 4 s withrequestIdfor 5xx.- "Save all pending": queues dirty rows in order; each still fires its own POST; failures leave that row dirty and continue others.
I1.3 Offline
- Connectivity lost mid-entry →
C-AppOfflineBanner; current edit remains in the field; row enters the offline draft (15_Flutter §Offline). - Reconnect → banner "n unsynced rows" with Sync action; replay in row order; per-row success/failure surfaced (failed rows stay dirty).
I1.4 Published rows
- Rows with server
publishedAt(examination-result.schema.ts:24-25) render locked (fields disabled, "Published" chip). UI-only — server still accepts writes ((planned)lock).
I2 — Exam-Subject Detail (S2)
- Coverage progress animates width 300 ms on load (08_Interaction_&_Motion.md); announces once via
SemanticsService— no repeated announcements on rebuild. - "Enter marks" → push S1; return preserves S1's dirty state (keep cubit alive in the navigator stack).
I3 — Student Results List (S3)
- Pull-to-refresh refetches rows and re-groups; cache is refreshed, not evicted first (renders from old + updates in place).
- Filter chips are instant, client-side, and sticky during session.
- Tapping a group header collapses/expands the group (default expanded).
I4 — Report Card (S4)
- Read-only: no edits, no pull-to-refresh; data is server-computed (
result.service.ts:46-98). - Share action (web share sheet) shares a text summary
(forward-looking); Print/PDF(planned)(Results.md:57). - Stale display: if exam
statusflips topublishedwhile the card is open (in-app eventresults-published), refresh the card and show the "Published" chip without user action.
I5 — Grade Summary & Publish (S5)
- Aggregation runs N subject fetches; show skeleton bars; results stream in as each resolves (partial render).
- Publish button: disabled until coverage = 100 % (advisory); on tap →
C-AppDialogconfirm (B4 warning, no-revert copy) →POST /api/v1/examinations/:id/publish(examination.controller.ts:54-56) → on 2xx replace banner with "Results published on {date}". - On 429
RATE_LIMITED: disable publish, show countdown of the retry window.
I6 — Motion & feedback summary
| Action | Feedback | Duration |
|---|---|---|
| Row save start | inline row spinner (static under reduced motion) | until response |
| Row saved | snackbar success | < 3 s |
| Row rejected | inline error + snackbar (4 s) | ≥ 4 s |
| Publish success | banner + snackbar | banner persists |
| Offline | banner (persists until synced) | persists |
| Coverage change | progress bar 300 ms ease-out | 300 ms |