Skip to content

Commit

Permalink
Add guardrails for duplicate/excess answers in no-guess-queue mode.
Browse files Browse the repository at this point in the history
These guardrails exist for guess-queue mode but were missing from
no-guess-queue mode. This change brings parity between the two modes.

Note that:

1. This is not a firm guarantee against (unintended) duplicate answers,
since two submissions made at ~the same time won't detect the presence
of the other submission in time. Only a read-modify-write transaction
would prevent this.

2. If duplicate answers _are_ submitted, clearing one answer in
no-guess-queue mode will mark the puzzle as incorrect and clear the
answer, while leaving a correct guess in place. This is an inconsistent
state. The workaround is to clear all copies of the answer and resubmit
it.
  • Loading branch information
jpd236 committed Jan 5, 2025
1 parent a3855d7 commit 1b1208c
Showing 1 changed file with 42 additions and 7 deletions.
49 changes: 42 additions & 7 deletions imports/client/components/PuzzlePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,11 @@ const PuzzlePageMetadata = ({
{" Answer"}
</Button>
{/* eslint-disable-next-line @typescript-eslint/no-use-before-define */}
<PuzzleAnswerModal ref={answerModalRef} puzzle={puzzle} />
<PuzzleAnswerModal
ref={answerModalRef}
puzzle={puzzle}
guesses={guesses}
/>
</>
);
}
Expand Down Expand Up @@ -1643,12 +1647,16 @@ const PuzzleAnswerModal = React.forwardRef(
(
{
puzzle,
guesses,
}: {
puzzle: PuzzleType;
guesses: GuessType[];
},
forwardedRef: React.Ref<PuzzleAnswerModalHandle>,
) => {
const [answer, setAnswer] = useState<string>("");
const [confirmingSubmit, setConfirmingSubmit] = useState<boolean>(false);
const [confirmationMessage, setConfirmationMessage] = useState<string>("");
const [submitState, setSubmitState] = useState<PuzzleAnswerSubmitState>(
PuzzleAnswerSubmitState.IDLE,
);
Expand All @@ -1675,14 +1683,41 @@ const PuzzleAnswerModal = React.forwardRef(
const onAnswerChange: NonNullable<FormControlProps["onChange"]> =
useCallback((e) => {
setAnswer(e.currentTarget.value);
setConfirmingSubmit(false);
}, []);

const onDismissError = useCallback(() => {
setSubmitState(PuzzleAnswerSubmitState.IDLE);
setSubmitError("");
}, []);

const solvedness = useMemo(() => {
return computeSolvedness(puzzle);
}, [puzzle]);

const onSubmit = useCallback(() => {
const strippedAnswer = answer.replaceAll(/\s/g, "");
const repeatAnswer = guesses.find((g) => {
return (
g.state === "correct" &&
g.guess.replaceAll(/\s/g, "") === strippedAnswer
);
});
if ((repeatAnswer || solvedness !== "unsolved") && !confirmingSubmit) {
const repeatAnswerStr = repeatAnswer
? "This answer has already been submitted. "
: "";
const solvednessStr = {
solved: "This puzzle has already been solved. ",
noAnswers:
"This puzzle does not expect any answers to be submitted. ",
unsolved: "",
}[solvedness];
const msg = `${solvednessStr} ${repeatAnswerStr} Are you sure you want to submit this answer?`;
setConfirmationMessage(msg);
setConfirmingSubmit(true);
return;
}
setSubmitState(PuzzleAnswerSubmitState.SUBMITTING);
setSubmitError("");
addPuzzleAnswer.call(
Expand All @@ -1699,20 +1734,17 @@ const PuzzleAnswerModal = React.forwardRef(
setSubmitState(PuzzleAnswerSubmitState.IDLE);
hide();
}
setConfirmingSubmit(false);
},
);
}, [puzzle._id, answer, hide]);
}, [puzzle._id, confirmingSubmit, guesses, solvedness, answer, hide]);

return (
<ModalForm
ref={formRef}
title={`Submit answer to ${puzzle.title}`}
onSubmit={onSubmit}
submitLabel={
submitState === PuzzleAnswerSubmitState.SUBMITTING
? "Confirm Submit"
: "Submit"
}
submitLabel={confirmingSubmit ? "Confirm Submit" : "Submit"}
>
<FormGroup as={Row} className="mb-3">
<FormLabel column xs={3} htmlFor="jr-puzzle-answer">
Expand All @@ -1730,6 +1762,9 @@ const PuzzleAnswerModal = React.forwardRef(
</Col>
</FormGroup>

{confirmingSubmit ? (
<Alert variant="warning">{confirmationMessage}</Alert>
) : null}
{submitState === PuzzleAnswerSubmitState.FAILED ? (
<Alert variant="danger" dismissible onClose={onDismissError}>
{submitError ||
Expand Down

0 comments on commit 1b1208c

Please sign in to comment.