Skipping a card is not a mistake
Scrolling through a deck without answering recorded a 0% attempt and listed every card under "Missed items" with its answer revealed. Only answered cards count now: the score is correct/answered, the attempt stores the answered count, an empty run records nothing, and the cards scrolled past wait under "Not answered (n)" - listed as prompts with "not counted as mistakes" - and "Answer the rest (n)" starts a catch-up run of just those cards (same restart path as retry-missed, so history and the SRS still see the answers). "Aced all" requires nothing left waiting too. phase2 pins the flow: all-skipped records no attempt and no mistake list; one-of-two reads "You got 1 of 1 right" + "Not answered (1)" with attempts:1, bestScore:100; the catch-up run records the remaining answer. Exam mode keeps its own rule: in a timed exam an unanswered question is a miss. Version 0.6.5, SW v87.
This commit is contained in:
parent
855ce82836
commit
98537cee64
7 changed files with 161 additions and 20 deletions
|
|
@ -9,7 +9,16 @@ import type { Activity, Deck } from "@/lib/schemas";
|
|||
import { downloadDeck } from "@/lib/storage";
|
||||
import { useIsPhone } from "@/lib/use-phone";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { BoltIcon, DownloadIcon, FlameIcon, HomeIcon, RefreshIcon, SnowflakeIcon, SparklesIcon } from "@/components/icons";
|
||||
import {
|
||||
BoltIcon,
|
||||
DownloadIcon,
|
||||
FlameIcon,
|
||||
HistoryIcon,
|
||||
HomeIcon,
|
||||
RefreshIcon,
|
||||
SnowflakeIcon,
|
||||
SparklesIcon,
|
||||
} from "@/components/icons";
|
||||
import { Button, Chip } from "@/components/ui";
|
||||
import { AskButton } from "@/components/chat/AskButton";
|
||||
|
||||
|
|
@ -62,9 +71,11 @@ export function ResultsScreen({
|
|||
total,
|
||||
correct,
|
||||
missed,
|
||||
unanswered,
|
||||
results,
|
||||
retryMode,
|
||||
onRetryMissed,
|
||||
onStudyUnanswered,
|
||||
onRestart,
|
||||
onRegenerate,
|
||||
regenerating,
|
||||
|
|
@ -73,12 +84,17 @@ export function ResultsScreen({
|
|||
deck: Deck;
|
||||
xp: number;
|
||||
bestStreak: number;
|
||||
/** Cards that were answered (right or wrong). */
|
||||
total: number;
|
||||
correct: number;
|
||||
/** Answered, but wrong. */
|
||||
missed: Activity[];
|
||||
/** Scrolled past without answering — waiting, not wrong. */
|
||||
unanswered: Activity[];
|
||||
results: Record<string, AttemptResult>;
|
||||
retryMode: boolean;
|
||||
onRetryMissed: () => void;
|
||||
onStudyUnanswered: () => void;
|
||||
onRestart: () => void;
|
||||
onRegenerate: () => void;
|
||||
regenerating: boolean;
|
||||
|
|
@ -90,7 +106,7 @@ export function ResultsScreen({
|
|||
/* Phones: the score and XP count up; desktop jumps straight in. */
|
||||
const percentShown = useCountUp(percent, !isPhone);
|
||||
const xpShown = useCountUp(xp, !isPhone);
|
||||
const perfect = missed.length === 0 && total > 0;
|
||||
const perfect = missed.length === 0 && unanswered.length === 0 && total > 0;
|
||||
const tone = percent >= 80 ? "emerald" : percent >= 50 ? "frost" : "amber";
|
||||
|
||||
return (
|
||||
|
|
@ -107,11 +123,13 @@ export function ResultsScreen({
|
|||
<p className="text-sm text-frost-600 dark:text-frost-300">
|
||||
{perfect
|
||||
? t("results.acedAll", { n: total, title: deck.title })
|
||||
: t("results.gotOf", {
|
||||
correct,
|
||||
total,
|
||||
retry: retryMode ? t("results.retrySuffix") : "",
|
||||
})}
|
||||
: total === 0
|
||||
? t("results.noneAnswered")
|
||||
: t("results.gotOf", {
|
||||
correct,
|
||||
total,
|
||||
retry: retryMode ? t("results.retrySuffix") : "",
|
||||
})}
|
||||
</p>
|
||||
|
||||
<div className="mt-1 flex flex-wrap justify-center gap-2">
|
||||
|
|
@ -164,21 +182,45 @@ export function ResultsScreen({
|
|||
</div>
|
||||
) : null}
|
||||
|
||||
{unanswered.length > 0 ? (
|
||||
<div className="card space-y-3 p-5">
|
||||
<h2 className="flex items-center gap-2 text-base font-bold text-frost-950 dark:text-frost-50">
|
||||
<HistoryIcon className="size-4" /> {t("results.unanswered", { n: unanswered.length })}
|
||||
</h2>
|
||||
<p className="text-xs leading-relaxed text-frost-600 dark:text-frost-300">{t("results.unansweredNote")}</p>
|
||||
<ul className="space-y-2">
|
||||
{unanswered.map((activity) => (
|
||||
<li
|
||||
key={activity.id}
|
||||
className="rounded-2xl border border-white/50 bg-white/45 p-3 text-sm font-semibold text-frost-950 backdrop-blur-xl dark:border-white/10 dark:bg-white/5 dark:text-frost-50"
|
||||
>
|
||||
{promptFor(activity)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{missed.length > 0 ? (
|
||||
<Button size="lg" onClick={onRetryMissed}>
|
||||
<RefreshIcon className="size-5" /> {t("results.retryMissed", { n: missed.length })}
|
||||
</Button>
|
||||
) : (
|
||||
) : null}
|
||||
{unanswered.length > 0 ? (
|
||||
<Button size="lg" variant={missed.length > 0 ? "secondary" : "primary"} onClick={onStudyUnanswered}>
|
||||
<RefreshIcon className="size-5" /> {t("results.retryUnanswered", { n: unanswered.length })}
|
||||
</Button>
|
||||
) : null}
|
||||
{missed.length === 0 && unanswered.length === 0 ? (
|
||||
<Button size="lg" variant="secondary" onClick={onRestart}>
|
||||
<RefreshIcon className="size-5" /> {t("results.studyAgain")}
|
||||
</Button>
|
||||
)}
|
||||
{missed.length > 0 ? (
|
||||
) : (
|
||||
<Button size="lg" variant="secondary" onClick={onRestart}>
|
||||
<RefreshIcon className="size-5" /> {t("results.studyWhole")}
|
||||
</Button>
|
||||
) : null}
|
||||
)}
|
||||
<Button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue