diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-07 23:47:35 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-07 23:47:35 +0900 |
| commit | 3cfa0f5c029113a2ada8e8663767a654150ba8c3 (patch) | |
| tree | 0ed8b65c4283079260cadd509e5f68733479a689 | |
| parent | f0635f9beb0ff85bbea2264a1b19160f0beed257 (diff) | |
| download | kioku-3cfa0f5c029113a2ada8e8663767a654150ba8c3.tar.gz kioku-3cfa0f5c029113a2ada8e8663767a654150ba8c3.tar.zst kioku-3cfa0f5c029113a2ada8e8663767a654150ba8c3.zip | |
chore: fix all lint errors and warnings
- Remove unused imports in repositories.ts and sync.test.ts
- Replace non-null assertions with type casts in HomePage.test.tsx
- Use semantic <output> element instead of div with role="status"
- Use semantic <button> element instead of div with role="button"
- Remove useless React fragment in StudyPage.tsx
- Fix useCallback dependencies for handleFlip and handleRating
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| -rw-r--r-- | src/client/components/OfflineBanner.tsx | 5 | ||||
| -rw-r--r-- | src/client/db/repositories.ts | 2 | ||||
| -rw-r--r-- | src/client/pages/HomePage.test.tsx | 14 | ||||
| -rw-r--r-- | src/client/pages/StudyPage.tsx | 135 | ||||
| -rw-r--r-- | src/server/routes/sync.test.ts | 1 |
5 files changed, 77 insertions, 80 deletions
diff --git a/src/client/components/OfflineBanner.tsx b/src/client/components/OfflineBanner.tsx index 357db33..4782fd4 100644 --- a/src/client/components/OfflineBanner.tsx +++ b/src/client/components/OfflineBanner.tsx @@ -8,9 +8,8 @@ export function OfflineBanner() { } return ( - <div + <output data-testid="offline-banner" - role="status" aria-live="polite" style={{ backgroundColor: "#6c757d", @@ -34,6 +33,6 @@ export function OfflineBanner() { </span> )} </span> - </div> + </output> ); } diff --git a/src/client/db/repositories.ts b/src/client/db/repositories.ts index 73abb05..1a03f93 100644 --- a/src/client/db/repositories.ts +++ b/src/client/db/repositories.ts @@ -1,12 +1,10 @@ import { v4 as uuidv4 } from "uuid"; import { CardState, - type CardStateType, db, type LocalCard, type LocalDeck, type LocalReviewLog, - type RatingType, } from "./index"; /** diff --git a/src/client/pages/HomePage.test.tsx b/src/client/pages/HomePage.test.tsx index f75f88d..18c2e76 100644 --- a/src/client/pages/HomePage.test.tsx +++ b/src/client/pages/HomePage.test.tsx @@ -484,7 +484,7 @@ describe("HomePage", () => { }); const editButtons = screen.getAllByRole("button", { name: "Edit" }); - await user.click(editButtons[0]!); + await user.click(editButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); expect(screen.getByRole("heading", { name: "Edit Deck" })).toBeDefined(); @@ -512,7 +512,7 @@ describe("HomePage", () => { }); const editButtons = screen.getAllByRole("button", { name: "Edit" }); - await user.click(editButtons[0]!); + await user.click(editButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -557,7 +557,7 @@ describe("HomePage", () => { // Click Edit on first deck const editButtons = screen.getAllByRole("button", { name: "Edit" }); - await user.click(editButtons[0]!); + await user.click(editButtons.at(0) as HTMLElement); // Update name const nameInput = screen.getByLabelText("Name"); @@ -623,7 +623,7 @@ describe("HomePage", () => { }); const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); - await user.click(deleteButtons[0]!); + await user.click(deleteButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); expect( @@ -652,7 +652,7 @@ describe("HomePage", () => { }); const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); - await user.click(deleteButtons[0]!); + await user.click(deleteButtons.at(0) as HTMLElement); expect(screen.getByRole("dialog")).toBeDefined(); @@ -693,7 +693,7 @@ describe("HomePage", () => { // Click Delete on first deck const deleteButtons = screen.getAllByRole("button", { name: "Delete" }); - await user.click(deleteButtons[0]!); + await user.click(deleteButtons.at(0) as HTMLElement); // Wait for modal to appear await waitFor(() => { @@ -706,7 +706,7 @@ describe("HomePage", () => { const deleteButton = Array.from(dialogButtons).find( (btn) => btn.textContent === "Delete", ); - await user.click(deleteButton!); + await user.click(deleteButton as HTMLElement); // Modal should close await waitFor(() => { diff --git a/src/client/pages/StudyPage.tsx b/src/client/pages/StudyPage.tsx index c6f8665..03cb537 100644 --- a/src/client/pages/StudyPage.tsx +++ b/src/client/pages/StudyPage.tsx @@ -119,62 +119,69 @@ export function StudyPage() { fetchData(); }, [fetchData]); + // biome-ignore lint/correctness/useExhaustiveDependencies: Reset timer when card changes useEffect(() => { cardStartTimeRef.current = Date.now(); }, [currentIndex]); - const handleFlip = () => { + const handleFlip = useCallback(() => { setIsFlipped(true); - }; + }, []); - const handleRating = async (rating: Rating) => { - if (!deckId || isSubmitting) return; + const handleRating = useCallback( + async (rating: Rating) => { + if (!deckId || isSubmitting) return; - const currentCard = cards[currentIndex]; - if (!currentCard) return; + const currentCard = cards[currentIndex]; + if (!currentCard) return; - setIsSubmitting(true); - setError(null); + setIsSubmitting(true); + setError(null); - const durationMs = Date.now() - cardStartTimeRef.current; + const durationMs = Date.now() - cardStartTimeRef.current; - try { - const authHeader = apiClient.getAuthHeader(); - if (!authHeader) { - throw new ApiClientError("Not authenticated", 401); - } + try { + const authHeader = apiClient.getAuthHeader(); + if (!authHeader) { + throw new ApiClientError("Not authenticated", 401); + } - const res = await fetch(`/api/decks/${deckId}/study/${currentCard.id}`, { - method: "POST", - headers: { - ...authHeader, - "Content-Type": "application/json", - }, - body: JSON.stringify({ rating, durationMs }), - }); - - if (!res.ok) { - const errorBody = await res.json().catch(() => ({})); - throw new ApiClientError( - (errorBody as { error?: string }).error || - `Request failed with status ${res.status}`, - res.status, + const res = await fetch( + `/api/decks/${deckId}/study/${currentCard.id}`, + { + method: "POST", + headers: { + ...authHeader, + "Content-Type": "application/json", + }, + body: JSON.stringify({ rating, durationMs }), + }, ); - } - setCompletedCount((prev) => prev + 1); - setIsFlipped(false); - setCurrentIndex((prev) => prev + 1); - } catch (err) { - if (err instanceof ApiClientError) { - setError(err.message); - } else { - setError("Failed to submit review. Please try again."); + if (!res.ok) { + const errorBody = await res.json().catch(() => ({})); + throw new ApiClientError( + (errorBody as { error?: string }).error || + `Request failed with status ${res.status}`, + res.status, + ); + } + + setCompletedCount((prev) => prev + 1); + setIsFlipped(false); + setCurrentIndex((prev) => prev + 1); + } catch (err) { + if (err instanceof ApiClientError) { + setError(err.message); + } else { + setError("Failed to submit review. Please try again."); + } + } finally { + setIsSubmitting(false); } - } finally { - setIsSubmitting(false); - } - }; + }, + [deckId, isSubmitting, cards, currentIndex], + ); const handleKeyDown = useCallback( (e: KeyboardEvent) => { @@ -200,7 +207,7 @@ export function StudyPage() { } } }, - [isFlipped, isSubmitting], + [isFlipped, isSubmitting, handleFlip, handleRating], ); useEffect(() => { @@ -328,21 +335,16 @@ export function StudyPage() { {currentCard && !isSessionComplete && ( <div data-testid="study-card"> - <div + <button + type="button" data-testid="card-container" onClick={!isFlipped ? handleFlip : undefined} - onKeyDown={(e) => { - if (!isFlipped && (e.key === " " || e.key === "Enter")) { - e.preventDefault(); - handleFlip(); - } - }} - role="button" - tabIndex={0} aria-label={ isFlipped ? "Card showing answer" : "Click to reveal answer" } + disabled={isFlipped} style={{ + width: "100%", border: "1px solid #ccc", borderRadius: "8px", padding: "2rem", @@ -354,6 +356,7 @@ export function StudyPage() { cursor: isFlipped ? "default" : "pointer", backgroundColor: isFlipped ? "#f8f9fa" : "white", transition: "background-color 0.2s", + font: "inherit", }} > {!isFlipped ? ( @@ -381,22 +384,20 @@ export function StudyPage() { </p> </> ) : ( - <> - <p - data-testid="card-back" - style={{ - fontSize: "1.25rem", - textAlign: "center", - margin: 0, - whiteSpace: "pre-wrap", - wordBreak: "break-word", - }} - > - {currentCard.back} - </p> - </> + <p + data-testid="card-back" + style={{ + fontSize: "1.25rem", + textAlign: "center", + margin: 0, + whiteSpace: "pre-wrap", + wordBreak: "break-word", + }} + > + {currentCard.back} + </p> )} - </div> + </button> {isFlipped && ( <div diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts index 9deb5ac..e1b389f 100644 --- a/src/server/routes/sync.test.ts +++ b/src/server/routes/sync.test.ts @@ -4,7 +4,6 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import { errorHandler } from "../middleware/index.js"; import type { SyncPullResult, - SyncPushData, SyncPushResult, SyncRepository, } from "../repositories/sync.js"; |
