리펙토링: 코드 최적화, AGENTS.md, README.md 내용 반영

This commit is contained in:
2026-05-26 17:34:40 +09:00
parent 7d4ab86da5
commit 2ebb8dbc32
4 changed files with 62 additions and 58 deletions
+24 -21
View File
@@ -16,6 +16,9 @@ import KatexRenderer from "@/components/math/KatexRenderer";
import { useGameStore } from "@/store/gameStore";
import type { ProblemCount } from "@/store/gameStore";
const MOBILE_BREAKPOINT_QUERY = "(min-width: 640px)";
const MOBILE_KEYBOARD_SPACER_OFFSET = 480;
function parseAnswer(value: string) {
const trimmed = value.trim();
@@ -47,7 +50,6 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
const [answerA, setAnswerA] = useState("");
const [answerB, setAnswerB] = useState("");
const [activeInput, setActiveInput] = useState<ActiveAnswerInput>(0);
const [focusInputOnFormMount, setFocusInputOnFormMount] = useState(false);
const [mobileFocusSpacerHeight, setMobileFocusSpacerHeight] = useState(0);
const [inputError, setInputError] = useState("");
@@ -86,15 +88,17 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
setInputError("");
}, [currentProblem?.key]);
useEffect(() => {
return () => {
scrollTimeoutsRef.current.forEach((timeoutId) => {
window.clearTimeout(timeoutId);
});
viewportResizeCleanupRef.current?.();
};
const clearViewportFocusCleanup = useCallback(() => {
scrollTimeoutsRef.current.forEach((timeoutId) => {
window.clearTimeout(timeoutId);
});
scrollTimeoutsRef.current = [];
viewportResizeCleanupRef.current?.();
viewportResizeCleanupRef.current = null;
}, []);
useEffect(() => clearViewportFocusCleanup, [clearViewportFocusCleanup]);
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
@@ -113,15 +117,18 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
}
function handleNextProblem() {
setFocusInputOnFormMount(true);
flushSync(() => {
nextProblem();
});
answerFormRef.current?.focusInput();
}
const handleInputBlur = useCallback(() => {
setMobileFocusSpacerHeight(0);
}, []);
const scrollKatexToViewportTop = useCallback(() => {
if (window.matchMedia("(min-width: 640px)").matches) return;
if (window.matchMedia(MOBILE_BREAKPOINT_QUERY).matches) return;
function alignKatex() {
const katexElement =
@@ -158,15 +165,13 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
);
setMobileFocusSpacerHeight(
Math.max(window.innerHeight, keyboardHeight + 480),
Math.max(
window.innerHeight,
keyboardHeight + MOBILE_KEYBOARD_SPACER_OFFSET,
),
);
scrollTimeoutsRef.current.forEach((timeoutId) => {
window.clearTimeout(timeoutId);
});
scrollTimeoutsRef.current = [];
viewportResizeCleanupRef.current?.();
viewportResizeCleanupRef.current = null;
clearViewportFocusCleanup();
if (visualViewport) {
let settleTimeoutId: number | null = null;
@@ -197,7 +202,7 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
const timeoutId = window.setTimeout(alignKatex, delay);
scrollTimeoutsRef.current.push(timeoutId);
});
}, []);
}, [clearViewportFocusCleanup]);
if (!session || sessionNeedsInit) {
return (
@@ -272,13 +277,11 @@ export default function PlayClient({ problemCount }: PlayClientProps) {
answerA={answerA}
answerB={answerB}
activeInput={activeInput}
disabled={submitted}
error={inputError}
focusOnMount={focusInputOnFormMount}
onActiveInputChange={setActiveInput}
onAnswerAChange={setAnswerA}
onAnswerBChange={setAnswerB}
onInputBlur={() => setMobileFocusSpacerHeight(0)}
onInputBlur={handleInputBlur}
onInputFocus={scrollKatexToViewportTop}
onSubmit={handleSubmit}
/>