Module-specific interactions for hierarchy browsing, master-detail, cascading
pickers, and the assignment matrix, layered on shared 00-shared/08 . Covers
phone/tablet/desktop. Every interaction stays within server semantics (no
optimistic writes on deletes/set-current).
Gesture Behaviour Where
Tap navigate rows, toggle tree nodes, open chips/sheets, submit all screens
Tap (row trailing) AppMenu (Edit / Delete / Replace / Remove / Set as current)lists, roster rows
Long-press same as row menu (tree nodes, roster rows) explorer, matrix
Double-tap expand/collapse tree node on the label (first tap = select, second = toggle) (proposed) explorer
Swipe left no destructive swipe — deletes are destructive, keep confirm dialogsall lists
Pull down RefreshIndicator: re-fetch page 1 (lists) / refetch roster + cache (07 §10 )lists, detail, explorer
Pinch none (no media; tree depth ≤ 5 levels) —
Hardware back pop to list; sheet → back closes sheet; root → exit shell all
Edge swipe drawer (phone shell) shell
Input Behaviour
Tab logical order Year → Grade → Section → Class → Subject (forms + explorer)
Enter submits the visible form / confirms dialog primary action
Esc closes sheet/dialog, collapses popovers, cancels selection → focus returns to trigger
N new entity on list screens (year/grade/section/class/subject)
Ctrl+Enter submit form; set-current confirm
Arrows navigate tree nodes (↑/↓ move, →/← expand/collapse), dropdown options
Space toggle selected chip / expand node
Right-click context menu = row AppMenu (same actions)
Hover row/tree-node highlight surfaceVariant after 150 ms; tooltip on conflict icon (500 ms)
Focus visible focus ring on every control (00-shared/09 §6 )
Event Haptic Ref
Create success (year/grade/class/subject/assignment) lightImpact—
409 duplicate surfaced mediumImpact—
Delete confirm (destructive) heavyImpact—
Tree node expand/collapse selectionClick—
Chips / year switcher toggle selectionClick—
Rule disabled when system haptics off; none on desktop 08 §3
Transition Duration/curve Used by
List → detail push m-base slide; Hero on row leading avatar → header card (m-slow)year/grade/class/subject detail
Sheet (add assignment) m-base bottom slide, 2/3 heightmatrix add sheet
Tree expand/collapse AnimatedSize m-base; children stagger m-fastexplorer
Conflict banner in/out slide-down + fade m-base class detail, roster, explorer
Row removal AnimatedList exit m-fast + snackbarlists, roster
Status badge change (set-current) scale pop m-fast on new "Current" pill year list/detail
Chips filter toggle fill/outline swap m-fast lists, explorer
FAB morph (Overview → Subjects tab) AnimatedSwitcher m-fastclass detail
Reduced-motion: all of the above degrade to fades ≤ m-instant; no slide or scale.
Interaction Detail
Cascading picker parent change children reset + disabled with AnimatedSize shrink; new options fade in; selection emits {academicYearId, gradeId, sectionId} or null (07 §6 )
"Add subject" in matrix sheet opens; subject dropdown searchable; already-assigned entries disabled with "Already assigned" caption
Duplicate warn on class name as the user types the name, cache lookup on (year, grade, section, name) shows inline "Duplicate class?" confirm on submit, not on keystroke
Subject form marks live summary line updates per keystroke: "theory 80 + practical 20 = 100 ≤ max 100"; invalid combo disables submit
Set as current server-first: button loading → 200 → badges re-render (old current loses pill with m-fast fade) → snackbar
Remove assignment confirm dialog → server-first → row exit AnimatedList; 404 stale id → treat as removed + refresh
Year switcher change all scoped lists reload (year param), skeleton only in list body, AppBar chip swaps m-fast
Pull-to-refresh cascade list refreshes first, then cache (refreshAll), then downstream consumers re-fetch scoped data (13 §5 )
All presses respond < 100 ms with m-fast ripple.
Every network action shows pending state (button loading / skeleton / spinner).
Submit disabled while pending — no double-submit (anti-double-create: the worst
failure here is a duplicate 409 that actually succeeded on the server).
429 → countdown, never auto-retry.
Errors persist until corrected or dismissed; conflict banners stay until
resolved (not auto-timeout) since they change the meaning of the roster.
studylyon://academics/classes/:id → class master screen directly.
studylyon://academics/explore?yearId= → explorer scoped to a year.
ObjectId paste: admin/dev build accepts pasted MongoId in search fields to jump
to an entity (hidden behind kDebugMode flag).
Roster table (desktop): SelectableText on code/IDs, Ctrl+C works.
Interaction claim Server check
"Set as current" confirms global un-flagging setCurrent loops all isCurrent:true docs (academic-year.service.ts:72-77) — confirmed
Delete of grade shows orphan warning, no cascade remove = bare softDelete (grade.service.ts:58-61), no children checks — confirmed
Replace assignment = delete + create no update endpoint (subject-assignment.controller.ts:19-39) — confirmed
Client duplicate guard only, never claims server rejection no unique index/check on assignments/classes (subject-assignment.schema.ts:22-25, class.service.ts:16-18) — confirmed
Year switcher re-scopes via academicYearId query param by-year / by-class / by-teacher take academicYearId (class.controller.ts:30-35, subject-assignment.controller.ts:24-35) — confirmed
Status free-string treated conservatively schema defaults only (grade.schema.ts:21-22) — confirmed