Smart Review: instant loading feedback, no more dead-feeling tap

Reported: tapping Review due could sit for many seconds with no loading
screen. The flow itself is local and fast (counts are hydrated on the
library; <1.5s even at 6x CPU throttle with 3,000 answers), so the
visible fault was missing feedback: /review is a dynamic route and
until its payload arrived nothing on screen changed.

- LinkLoadingScreen: the review card links render a full-screen
  loading screen from the link's own useLinkStatus pending state, the
  instant the tap happens (measured 58 ms with the route request held
  open). The links also prefetch their payload.
- Route loading boundaries (loading.tsx) for /review, /stats and
  /exam, sharing a RouteLoading screen (app icon + spinner +
  localized message); ReviewClient's bare skeleton replaced with it.
- Health probes: per-key 30s cache + in-flight sharing in use-health.ts
  (every page mounts its own gate; a session now fires one probe
  instead of one per navigation). Manual re-check still forces.
- Czech fix: exam heading 'Zkouškové režim' -> 'Zkouškový režim'.

Verified by the rebuilt suites (the /tmp harness was wiped overnight):
review-loading 15/15 (stalled-request loading screen at 58 ms, session
arrival after release, fast normal path, mistakes path, end-to-end
grading, probe cache) and navigation 78/78 (tab bar, labels 360/390
EN/CS, routing, deep links, wizard loading lock). Lint + build clean.
SW freezmo-v30.
This commit is contained in:
Martin Dvořák 2026-10-11 14:35:16 +00:00
commit 2103f9fc69
11 changed files with 150 additions and 17 deletions

View file

@ -69,7 +69,10 @@ no database, no data leaving your machine except the requests you send to Ollama
IndexedDB log, and each activity gets a light **SM-2 schedule** — correct answers come back after
1 day, then 3, then interval × ease; wrong ones after ten minutes. The Library shows a **Smart
review** card with the due count, a **Mistakes** count (activities whose latest answer was wrong —
they clear themselves once answered correctly) and today's **daily goal** progress. A **Progress**
they clear themselves once answered correctly) and today's **daily goal** progress. Tapping
either one shows a **loading screen instantly** (the link reports its own pending state, the
route has a loading boundary, and the payload is prefetched), so even a slow first request never
looks like a dead tap. A **Progress**
tab has the day streak, a five-week calendar, accuracy per subject and per deck and
the weakest topics. **Exam mode** (its own tab) takes one or more decks, a question count and an
optional timer, hides hints and shows a per-deck/per-topic breakdown at the end. **Daily
@ -1044,6 +1047,24 @@ Things I chose while building, with the reasoning:
Back from every tab and the ⋯ sheet) plus the wizard suite (92 checks, including the
loading-lock and abort-on-leave cases) and the other standing suites.
44. **Smart Review must never look like a dead tap.** The reported symptom was a tap on *Review
due* sitting for many seconds with no feedback. The flow itself is local and fast (the counts
are already hydrated on the library; measurements show <1.5s even at 6× CPU throttle with
3,000 answers), so the visible fault was the missing feedback: `/review` is a dynamic route
(search params), and until its payload arrived the screen simply didn't change. Three layers
now cover it: the card links **prefetch** their payload; the link's own **`useLinkStatus`**
pending state renders a full-screen loading screen (`LinkLoadingScreen`) the instant the tap
happens — measured at **58 ms** even with the route request held open; and `loading.tsx`
boundaries cover the render/stream phase (review, progress and exam). The client screen's own
bare skeleton was replaced with the same loading screen. While in there, health probes got a
per-key 30-second cache plus in-flight sharing (`use-health.ts`): every page mounts its own
gate and each probe can sit for seconds while Ollama answers, so a session now fires **one**
probe instead of one per navigation (verified). Also fixed a Czech typo on the exam heading
(“Zkouškové režim” → “Zkouškový režim”). Verified by the review-loading suite (15 checks:
stalled-request loading screen, session arrival after release, the normal fast path, the
mistakes path, end-to-end grading, and the probe cache) and the rebuilt navigation suite
(78 checks).
36. **The mobile declutter pass reshapes, it doesn't fork.** Phones (≤639px) get a 3-step deck
flow and a 2-step homework flow that share the exact state and handlers with the desktop
cards (one `UploadScreen` / one `HomeworkInput`, two layouts switched by `useIsPhone()`);