From fd7aac62f99037d1b05cfaa18ffa5dda6dbf87cd Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 19 Aug 2025 12:02:14 +0000
Subject: [PATCH 1/3] Initial plan
From d0c8fedea9239541c89944e598c52437b68fbdac Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Tue, 19 Aug 2025 12:10:40 +0000
Subject: [PATCH 2/3] Add auto-advance on correct answers when auto-submit is
enabled
Co-authored-by: bradleyayers <105820+bradleyayers@users.noreply.github.com>
---
.../ui/QuizDeckHanziToPinyinQuestion.tsx | 18 +++++++++++-------
.../ui/QuizDeckOneCorrectPairQuestion.tsx | 18 +++++++++++-------
2 files changed, 22 insertions(+), 14 deletions(-)
diff --git a/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx b/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
index a7df4a17cb..c10791c7dd 100644
--- a/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
+++ b/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
@@ -34,6 +34,7 @@ import { Text, View } from "react-native";
import Reanimated, { FadeIn, FadeOut } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import type { DeepReadonly } from "ts-essentials";
+import { Delay } from "./Delay";
import { HanziWordRefText } from "./HanziWordRefText";
import { IconImage } from "./IconImage";
import { PinyinOptionButton } from "./PinyinOptionButton";
@@ -115,13 +116,16 @@ export function QuizDeckHanziToPinyinQuestion({
`}
>
{grade.correct ? (
-
-
- Nice!
-
+ <>
+
+
+ Nice!
+
+ {autoCheck ? : null}
+ >
) : (
<>
diff --git a/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx b/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
index b0b79a7318..e140ba524f 100644
--- a/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
+++ b/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
@@ -26,6 +26,7 @@ import type { ReactNode } from "react";
import { useState } from "react";
import { Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
+import { Delay } from "./Delay";
import { HanziWordRefText } from "./HanziWordRefText";
import { IconImage } from "./IconImage";
import { NewSkillModal } from "./NewSkillModal";
@@ -121,13 +122,16 @@ export function QuizDeckOneCorrectPairQuestion({
`}
>
{isCorrect ? (
-
-
- Nice!
-
+ <>
+
+
+ Nice!
+
+ {autoCheck ? : null}
+ >
) : (
<>
From 37a15f639b8ca43a5dd69b29e2e55766ce01baa3 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 20 Aug 2025 05:02:47 +0000
Subject: [PATCH 3/3] Refactor toast to quiz deck level for immediate
auto-advance
Co-authored-by: bradleyayers <105820+bradleyayers@users.noreply.github.com>
---
projects/app/src/client/ui/QuizDeck.tsx | 66 ++++++++++
.../ui/QuizDeckHanziToPinyinQuestion.tsx | 124 +-----------------
.../ui/QuizDeckOneCorrectPairQuestion.tsx | 118 +----------------
3 files changed, 79 insertions(+), 229 deletions(-)
diff --git a/projects/app/src/client/ui/QuizDeck.tsx b/projects/app/src/client/ui/QuizDeck.tsx
index b9cae0f70d..ff14c61d1a 100644
--- a/projects/app/src/client/ui/QuizDeck.tsx
+++ b/projects/app/src/client/ui/QuizDeck.tsx
@@ -1,3 +1,7 @@
+import {
+ autoCheckUserSetting,
+ useUserSetting,
+} from "@/client/hooks/useUserSetting";
import { useEventCallback } from "@/client/hooks/useEventCallback";
import { usePrefetchImages } from "@/client/hooks/usePrefetchImages";
import { useQuizProgress } from "@/client/hooks/useQuizProgress";
@@ -30,9 +34,12 @@ import React, { useEffect, useId, useRef, useState } from "react";
import { Animated as RnAnimated, Text, View } from "react-native";
import Reanimated, { FadeIn } from "react-native-reanimated";
import { CloseButton } from "./CloseButton";
+import { Delay } from "./Delay";
+import { IconImage } from "./IconImage";
import { usePostHog } from "./PostHogProvider";
import { QuizDeckHanziToPinyinQuestion } from "./QuizDeckHanziToPinyinQuestion";
import { QuizDeckOneCorrectPairQuestion } from "./QuizDeckOneCorrectPairQuestion";
+import { QuizDeckToastContainer } from "./QuizDeckToastContainer";
import { QuizProgressBar } from "./QuizProgressBar";
import { QuizQueueButton } from "./QuizQueueButton";
import { RectButton } from "./RectButton";
@@ -55,6 +62,9 @@ export const QuizDeck = ({ className }: { className?: string }) => {
const queryClient = useQueryClient();
const postHog = usePostHog();
+ const autoCheck =
+ useUserSetting(autoCheckUserSetting).value?.enabled ?? false;
+
const query = nextQuizQuestionQuery(r, id);
// The following is a bit convoluted but allows prefetching the next question
@@ -67,6 +77,10 @@ export const QuizDeck = ({ className }: { className?: string }) => {
const reviewQueue = nextQuestionQuery.data?.reviewQueue ?? null;
const [question, setQuestion] = useState();
+ const [toastState, setToastState] = useState<{
+ correct: boolean;
+ show: boolean;
+ } | null>(null);
useEffect(() => {
if (question == null && nextQuestion != null) {
@@ -90,6 +104,8 @@ export const QuizDeck = ({ className }: { className?: string }) => {
const handleNext = useEventCallback(() => {
// Clear the current question so that we swap to the next question.
setQuestion(undefined);
+ // Clear the toast
+ setToastState(null);
});
const handleRating = useEventCallback(
@@ -107,6 +123,15 @@ export const QuizDeck = ({ className }: { className?: string }) => {
playSuccessSound();
}
+ // Show the toast
+ setToastState({ correct: success, show: true });
+
+ // If auto-check is enabled and the answer is correct, advance immediately
+ if (autoCheck && success) {
+ // Clear the current question immediately to start transition
+ setQuestion(undefined);
+ }
+
const now = Date.now();
void (async () => {
@@ -289,6 +314,47 @@ export const QuizDeck = ({ className }: { className?: string }) => {
+
+ {/* Toast shown at deck level */}
+ {toastState?.show === true ? (
+
+
+ {toastState.correct ? (
+ <>
+
+
+ Nice!
+
+ {
+ setToastState(null);
+ }}
+ />
+ >
+ ) : (
+
+
+ Incorrect
+
+ )}
+
+
+ ) : null}
);
};
diff --git a/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx b/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
index c10791c7dd..e594dd219f 100644
--- a/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
+++ b/projects/app/src/client/ui/QuizDeckHanziToPinyinQuestion.tsx
@@ -9,7 +9,6 @@ import type {
PinyinPronunciation,
UnsavedSkillRating,
} from "@/data/model";
-import { SkillKind } from "@/data/model";
import type {
PinyinSyllableSuggestion,
PinyinSyllableSuggestions,
@@ -19,11 +18,9 @@ import {
pinyinSyllableSuggestions,
} from "@/data/pinyin";
import { hanziToPinyinQuestionMistakes } from "@/data/questions/hanziWordToPinyin";
-import type { HanziWordSkill, Skill } from "@/data/rizzleSchema";
import {
computeSkillRating,
hanziWordFromSkill,
- skillKindFromSkill,
} from "@/data/skills";
import { hanziFromHanziWord } from "@/dictionary/dictionary";
import { nonNullable } from "@pinyinly/lib/invariant";
@@ -34,11 +31,7 @@ import { Text, View } from "react-native";
import Reanimated, { FadeIn, FadeOut } from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import type { DeepReadonly } from "ts-essentials";
-import { Delay } from "./Delay";
-import { HanziWordRefText } from "./HanziWordRefText";
-import { IconImage } from "./IconImage";
import { PinyinOptionButton } from "./PinyinOptionButton";
-import { Pylymark } from "./Pylymark";
import { QuizDeckToastContainer } from "./QuizDeckToastContainer";
import { QuizFlagText } from "./QuizFlagText";
import { QuizSubmitButton, QuizSubmitButtonState } from "./QuizSubmitButton";
@@ -97,6 +90,11 @@ export function QuizDeckHanziToPinyinQuestion({
expectedAnswer: nonNullable(answers[0]),
});
onRating(skillRatings, mistakes);
+
+ // If auto-check is enabled and the answer is correct, advance immediately
+ if (autoCheck && correct) {
+ onNext();
+ }
} else {
onNext();
}
@@ -104,49 +102,7 @@ export function QuizDeckHanziToPinyinQuestion({
return (
- {grade.correct ? (
- <>
-
-
- Nice!
-
- {autoCheck ? : null}
- >
- ) : (
- <>
-
-
- Incorrect
-
-
- Correct answer:
-
-
-
-
-
- >
- )}
-
- )
- }
+ toast={null}
submitButton={
);
-const SkillAnswerText = ({
- skill,
-}: {
- skill: Skill;
- includeAlternatives?: boolean;
- hideA?: boolean;
- hideB?: boolean;
- small?: boolean;
-}) => {
- switch (skillKindFromSkill(skill)) {
- case SkillKind.Deprecated_EnglishToRadical:
- case SkillKind.Deprecated_PinyinToRadical:
- case SkillKind.Deprecated_RadicalToEnglish:
- case SkillKind.Deprecated_RadicalToPinyin:
- case SkillKind.Deprecated:
- case SkillKind.GlossToHanziWord:
- case SkillKind.ImageToHanziWord:
- case SkillKind.PinyinFinalAssociation:
- case SkillKind.PinyinInitialAssociation:
- case SkillKind.PinyinToHanziWord: {
- throw new Error(
- `ShowSkillAnswer not implemented for ${skillKindFromSkill(skill)}`,
- );
- }
- case SkillKind.HanziWordToGloss: {
- skill = skill as HanziWordSkill;
- return ;
- }
- case SkillKind.HanziWordToPinyinTyped:
- case SkillKind.HanziWordToPinyinFinal:
- case SkillKind.HanziWordToPinyinInitial:
- case SkillKind.HanziWordToPinyinTone: {
- skill = skill as HanziWordSkill;
- return ;
- }
- }
-};
-
-const HanziWordToGlossSkillAnswerText = ({
- skill,
-}: {
- skill: HanziWordSkill;
-}) => {
- const hanziWord = hanziWordFromSkill(skill);
-
- return (
- <>
-
-
-
- >
- );
-};
-
-const HanziWordToPinyinSkillAnswerText = ({
- skill,
-}: {
- skill: HanziWordSkill;
-}) => {
- const hanziWord = hanziWordFromSkill(skill);
-
- return (
-
-
-
- );
-};
-
const Skeleton = ({
children,
toast,
diff --git a/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx b/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
index e140ba524f..98e6111628 100644
--- a/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
+++ b/projects/app/src/client/ui/QuizDeckOneCorrectPairQuestion.tsx
@@ -9,16 +9,13 @@ import type {
OneCorrectPairQuestionChoice,
UnsavedSkillRating,
} from "@/data/model";
-import { QuestionFlagKind, SkillKind } from "@/data/model";
+import { QuestionFlagKind } from "@/data/model";
import {
oneCorrectPairChoiceText,
oneCorrectPairQuestionMistakes,
} from "@/data/questions/oneCorrectPair";
-import type { HanziWordSkill, Skill } from "@/data/rizzleSchema";
import {
computeSkillRating,
- hanziWordFromSkill,
- skillKindFromSkill,
} from "@/data/skills";
import { longestTextByGraphemes } from "@/util/unicode";
import { invariant } from "@pinyinly/lib/invariant";
@@ -26,11 +23,7 @@ import type { ReactNode } from "react";
import { useState } from "react";
import { Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
-import { Delay } from "./Delay";
-import { HanziWordRefText } from "./HanziWordRefText";
-import { IconImage } from "./IconImage";
import { NewSkillModal } from "./NewSkillModal";
-import { Pylymark } from "./Pylymark";
import { QuizDeckToastContainer } from "./QuizDeckToastContainer";
import { QuizFlagText } from "./QuizFlagText";
import { QuizSubmitButton, QuizSubmitButtonState } from "./QuizSubmitButton";
@@ -95,6 +88,11 @@ export function QuizDeckOneCorrectPairQuestion({
setIsCorrect(isCorrect);
onRating(skillRatings, mistakes);
+
+ // If auto-check is enabled and the answer is correct, advance immediately
+ if (autoCheck && isCorrect) {
+ onNext();
+ }
};
const groupAFontSize = textAnswerButtonFontSize(
@@ -110,49 +108,7 @@ export function QuizDeckOneCorrectPairQuestion({
return (
- {isCorrect ? (
- <>
-
-
- Nice!
-
- {autoCheck ? : null}
- >
- ) : (
- <>
-
-
- Incorrect
-
-
- Correct answer:
-
-
-
-
-
- >
- )}
-
- )
- }
+ toast={null}
submitButton={
{
- switch (skillKindFromSkill(skill)) {
- case SkillKind.Deprecated_EnglishToRadical:
- case SkillKind.Deprecated_PinyinToRadical:
- case SkillKind.Deprecated_RadicalToEnglish:
- case SkillKind.Deprecated_RadicalToPinyin:
- case SkillKind.Deprecated:
- case SkillKind.GlossToHanziWord:
- case SkillKind.ImageToHanziWord:
- case SkillKind.PinyinFinalAssociation:
- case SkillKind.PinyinInitialAssociation:
- case SkillKind.PinyinToHanziWord: {
- throw new Error(
- `ShowSkillAnswer not implemented for ${skillKindFromSkill(skill)}`,
- );
- }
- case SkillKind.HanziWordToGloss: {
- skill = skill as HanziWordSkill;
- return ;
- }
- case SkillKind.HanziWordToPinyinTyped:
- case SkillKind.HanziWordToPinyinFinal:
- case SkillKind.HanziWordToPinyinInitial:
- case SkillKind.HanziWordToPinyinTone: {
- skill = skill as HanziWordSkill;
- return ;
- }
- }
-};
-
-const HanziWordToGlossSkillAnswerText = ({
- skill,
-}: {
- skill: HanziWordSkill;
-}) => {
- const hanziWord = hanziWordFromSkill(skill);
-
- return (
- <>
-
-
-
- >
- );
-};
-
-const HanziWordToPinyinSkillAnswerText = ({
- skill,
-}: {
- skill: HanziWordSkill;
-}) => {
- const hanziWord = hanziWordFromSkill(skill);
-
- return (
-
-
-
- );
-};
-
const Skeleton = ({
children,
toast,