swageswagehandshape practice

Failure modes

Every fault this project has a named, deliberate answer for — not a promise nothing ever goes wrong, a record of what happens when it does.

#FaultContract
F1Camera permission deniedNon-camera flashcard mode: reference diagram + text description + manual "I made this shape" advance, keyboard-operable. No dead end.
F2No camera hardwareSame fallback as F1, distinct copy naming the actual cause.
F3GPU (WebGL2) delegate rejected, including no WebGL at allMediaPipe's CPU delegate is pure WASM and needs no WebGL, so these are one branch, not two rungs. Catch the rejection, retry delegate:'CPU'. Label: "Running on CPU — same result, about 15 frames a second instead of 35." Never "reduced accuracy" (it isn't), never "identical" (frame rate visibly differs).
F4Model fetch fails mid-loadLoud on-page error + retry, never a stuck spinner. Flashcard mode needs no model and stays available.
F5Cache bug — .task re-downloads every visit (GCS caps freshness at max-age=3600)Fixed at the fetch layer: manual fetch() + caches.open()/.put(), pass the ArrayBuffer via modelAssetBuffer, not modelAssetPath. Verified by a second-visit network capture showing ~0 model bytes.
F6Two hands in frame with numHands:1MediaPipe may flicker its choice. Absorbed by the stability window; copy asks for one hand.
F7Low-confidence predictionA tick's effective label is null below threshold — never silently guesses.
F8Hand exits frame mid-holdreset() — a gap always breaks a streak. No partial credit across a gap.
F9prefers-reduced-motionFeedback becomes an instant static state change. No autoplaying motion.
F10localStorage unavailablePractice works in-session; UI says progress will not persist. Never throws.
F11Classifier wrong on a genuinely confusable letterNot a bug. Normal miss; hint references the published confusion pair ("commonly mixed up with M — check thumb position"), never phrased as the user being wrong.
F12J or Z requestedStructurally impossible — never offered. /docs/limitations states why.