Micro-interactions and state transitions per surface. Motion tokens from
00-shared/08 ; every interaction below is server-confirm (no optimistic mutations) —
devices and evidence are irreversible.
Devices, Logs, hub status: AppRefreshIndicator re-fetches; Mo.m-fast settle;
refresh bypasses cache (00-shared/06 §3.1 ).
During refresh the previous content stays visible (no skeleton flash).
Trigger Behavior
"Sync now" button → spinner, disabled; row pulse; result snackbar "Sync complete · N punches" / "Sync failed"
Retry same button after failure; copy "will retry (4×, exponential 2000 ms)" (blueprint RETRIES.md:38)
Dead-letter failure banner + "View in Bull-Board" link; banner persists until resolved
Motion: Mo.m-base for running; result swap Mo.m-fast fade.
Badge color swap 200 ms; offline → tile tint errorContainer (07 §10 ).
No animation loops; reduced-motion = opacity cross-fade only.
Step advance only on confirm; back allowed to step 1 (re-select student).
Capture failure: inline error + "Retry capture"; device offline blocks step 2 with
banner (no spinner hanging).
Duplicate enrollment: destructive-style confirm ("Overwrite existing template?").
Motion: Mo.m-base horizontal slide between steps; progress fill 200 ms.
Action Interaction
Open tap → detail (master-detail pane on ≥ 840 px)
Menu AppMenu: Detail / Disable (devices); Open / (logs)
Disable AppDialog confirm → 200 → row removed + AppSnackbar; no undo (irreversible)
Filter chips toggle → list refetch; active chip announced
Verdict card transitions: AnimatedSwitcher fade 200 ms; mismatch = warning banner
timeline stays visible (evidence-first, 09 §2 ).
"Edit attendance" hop → attendance screen; back returns with verdict refreshed.
Offline: verify blocked (evidence must be live) — banner + disabled CTA.
Case UI
Empty devices AppEmptyState + inline CTA "Register device"
Empty logs (filter) "No punches for this filter" + "Clear filters" button
403 permission screen, not error
429 (ingest path) countdown, no auto-retry (machine path should avoid — OQ-5)
Queue unavailable "queue unavailable" chip on hub; never crash
Session expiry global overlay (00-shared/07 )
.next sequence; .done submits register form; first invalid field focused on submit.
Advanced (config JSON) disclosure: keyboard-openable, focus inside editor.
Live regions: sync result, verdict, badge changes, queue counts.
All tap targets ≥ 48 dp; timeline nodes individually focusable.
Monospace rawData reader: Semantics.label "raw device payload, read-only".
No interaction implies real-time; sync copy references 15-min cadence.
No destructive action without confirm; no undo promises.
Every status change is server-confirmed before UI updates.
Reduced-motion honored app-wide (00-shared/08 §2 ).