diff options
| author | Claude <noreply@anthropic.com> | 2026-02-08 11:29:22 +0000 |
|---|---|---|
| committer | Claude <noreply@anthropic.com> | 2026-02-08 11:29:22 +0000 |
| commit | 6d53e63d9f3fd81125d0f61e9701ecd262318875 (patch) | |
| tree | 0f068896360b01e19e787b4e23da912c4e3518ee /src | |
| parent | e17c87441d9beff9c1241cbe3ba71c402a7c0c3f (diff) | |
| download | kioku-6d53e63d9f3fd81125d0f61e9701ecd262318875.tar.gz kioku-6d53e63d9f3fd81125d0f61e9701ecd262318875.tar.zst kioku-6d53e63d9f3fd81125d0f61e9701ecd262318875.zip | |
fix(deck): change review card limit to 80 and simplify deck detail stats
Review card limit is reduced from 100 to 80 across deck list, deck detail,
and study routes. Deck detail page now shows only total card count and due
card count (with budget limits applied), matching the deck list and study
screen numbers.
https://claude.ai/code/session_01NAj4waQhwSSXV9EbgioX2j
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/pages/DeckDetailPage.test.tsx | 29 | ||||
| -rw-r--r-- | src/client/pages/DeckDetailPage.tsx | 52 | ||||
| -rw-r--r-- | src/server/routes/decks.ts | 18 | ||||
| -rw-r--r-- | src/server/routes/study.test.ts | 2 | ||||
| -rw-r--r-- | src/server/routes/study.ts | 2 |
5 files changed, 30 insertions, 73 deletions
diff --git a/src/client/pages/DeckDetailPage.test.tsx b/src/client/pages/DeckDetailPage.test.tsx index 815dff1..3c741ad 100644 --- a/src/client/pages/DeckDetailPage.test.tsx +++ b/src/client/pages/DeckDetailPage.test.tsx @@ -258,33 +258,16 @@ describe("DeckDetailPage", () => { ); }); - it("displays card counts by state", () => { + it("displays due card count from deck data", () => { renderWithProviders({ - initialDeck: mockDeck, + initialDeck: { ...mockDeck, dueCardCount: 5 }, initialCards: mockCards, }); - // New cards (state=0, but card-1 is not due yet, so 0) - const newLabel = screen.getByText("New"); - expect(newLabel).toBeDefined(); - const newContainer = newLabel.parentElement; - expect(newContainer?.querySelector(".text-info")?.textContent).toBe("0"); - - // Learning cards (state=1 or 3, none in mockCards) - const learningLabel = screen.getByText("Learning"); - expect(learningLabel).toBeDefined(); - const learningContainer = learningLabel.parentElement; - expect(learningContainer?.querySelector(".text-warning")?.textContent).toBe( - "0", - ); - - // Review cards (state=2, card-2 is due now) - const reviewLabel = screen.getByText("Review"); - expect(reviewLabel).toBeDefined(); - const reviewContainer = reviewLabel.parentElement; - expect(reviewContainer?.querySelector(".text-success")?.textContent).toBe( - "1", - ); + const dueLabel = screen.getByText("Due"); + expect(dueLabel).toBeDefined(); + const dueContainer = dueLabel.parentElement; + expect(dueContainer?.querySelector(".text-primary")?.textContent).toBe("5"); }); it("does not display card list (cards are hidden)", () => { diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx index 6bc89ba..d717d60 100644 --- a/src/client/pages/DeckDetailPage.tsx +++ b/src/client/pages/DeckDetailPage.tsx @@ -7,7 +7,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useAtomValue } from "jotai"; import { Suspense } from "react"; import { Link, useParams } from "wouter"; -import { getEndOfStudyDayBoundary } from "../../shared/date"; import { cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms"; import { ErrorBoundary } from "../components/ErrorBoundary"; import { LoadingSpinner } from "../components/LoadingSpinner"; @@ -25,52 +24,21 @@ function DeckHeader({ deckId }: { deckId: string }) { ); } -// CardState values from FSRS -const CardState = { - New: 0, - Learning: 1, - Review: 2, - Relearning: 3, -} as const; - function DeckStats({ deckId }: { deckId: string }) { + const { data: deck } = useAtomValue(deckByIdAtomFamily(deckId)); const { data: cards } = useAtomValue(cardsByDeckAtomFamily(deckId)); - // Count cards due today (study day boundary is 3:00 AM) - const boundary = getEndOfStudyDayBoundary(); - const dueCards = cards.filter((card) => new Date(card.due) < boundary); - - // Count by card state - const newCards = dueCards.filter((card) => card.state === CardState.New); - const learningCards = dueCards.filter( - (card) => - card.state === CardState.Learning || card.state === CardState.Relearning, - ); - const reviewCards = dueCards.filter( - (card) => card.state === CardState.Review, - ); - return ( <div className="bg-white rounded-xl border border-border/50 p-6 mb-6"> - <div className="grid grid-cols-4 gap-4"> + <div className="grid grid-cols-2 gap-4"> <div> <p className="text-sm text-muted mb-1">Total</p> <p className="text-2xl font-semibold text-ink">{cards.length}</p> </div> <div> - <p className="text-sm text-muted mb-1">New</p> - <p className="text-2xl font-semibold text-info">{newCards.length}</p> - </div> - <div> - <p className="text-sm text-muted mb-1">Learning</p> - <p className="text-2xl font-semibold text-warning"> - {learningCards.length} - </p> - </div> - <div> - <p className="text-sm text-muted mb-1">Review</p> - <p className="text-2xl font-semibold text-success"> - {reviewCards.length} + <p className="text-sm text-muted mb-1">Due</p> + <p className="text-2xl font-semibold text-primary"> + {deck.dueCardCount} </p> </div> </div> @@ -100,7 +68,7 @@ function DeckContent({ deckId }: { deckId: string }) { <Suspense fallback={ <div className="bg-white rounded-xl border border-border/50 p-6 mb-6"> - <div className="grid grid-cols-4 gap-4"> + <div className="grid grid-cols-2 gap-4"> <div> <div className="h-4 w-12 bg-muted/20 rounded animate-pulse mb-1" /> <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> @@ -109,14 +77,6 @@ function DeckContent({ deckId }: { deckId: string }) { <div className="h-4 w-12 bg-muted/20 rounded animate-pulse mb-1" /> <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> </div> - <div> - <div className="h-4 w-16 bg-muted/20 rounded animate-pulse mb-1" /> - <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> - </div> - <div> - <div className="h-4 w-14 bg-muted/20 rounded animate-pulse mb-1" /> - <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> - </div> </div> </div> } diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts index 6a24c66..d73aa0c 100644 --- a/src/server/routes/decks.ts +++ b/src/server/routes/decks.ts @@ -22,7 +22,7 @@ const deckIdParamSchema = z.object({ id: z.uuid(), }); -const REVIEW_CARDS_LIMIT = 100; +const REVIEW_CARDS_LIMIT = 80; export function createDecksRouter(deps: DeckDependencies) { const { deckRepo, cardRepo, reviewLogRepo } = deps; @@ -82,7 +82,21 @@ export function createDecksRouter(deps: DeckDependencies) { throw Errors.notFound("Deck not found", "DECK_NOT_FOUND"); } - return c.json({ deck }, 200); + const now = new Date(); + const [dueNewCards, dueReviewCards, reviewedNewCards] = await Promise.all( + [ + cardRepo.countDueNewCards(deck.id, now), + cardRepo.countDueReviewCards(deck.id, now), + reviewLogRepo.countTodayNewCardReviews(deck.id, now), + ], + ); + + const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards); + const newCardsToStudy = Math.min(dueNewCards, newCardBudget); + const reviewCardsToStudy = Math.min(dueReviewCards, REVIEW_CARDS_LIMIT); + const dueCardCount = newCardsToStudy + reviewCardsToStudy; + + return c.json({ deck: { ...deck, dueCardCount } }, 200); }) .put( "/:id", diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts index a58ea0d..514d966 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -198,7 +198,7 @@ describe("GET /api/decks/:deckId/study", () => { expect(mockCardRepo.findDueReviewCardsForStudy).toHaveBeenCalledWith( DECK_ID, expect.any(Date), - 100, + 80, ); }); diff --git a/src/server/routes/study.ts b/src/server/routes/study.ts index efd450c..d05f3ca 100644 --- a/src/server/routes/study.ts +++ b/src/server/routes/study.ts @@ -62,7 +62,7 @@ export function createStudyRouter(deps: StudyDependencies) { // Fetch new cards (limited) and review cards separately const [newCards, reviewCards] = await Promise.all([ cardRepo.findDueNewCardsForStudy(deckId, now, newCardBudget), - cardRepo.findDueReviewCardsForStudy(deckId, now, 100), + cardRepo.findDueReviewCardsForStudy(deckId, now, 80), ]); return c.json({ cards: [...newCards, ...reviewCards] }, 200); |
