From c1cb543875edee1aa9cc160a78b610e06ea139e7 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sat, 14 Feb 2026 23:05:43 +0900
Subject: feat(study): add edit button to study session cards

Allow editing note content directly from the study screen via a pen
icon button or the E key shortcut. Keyboard shortcuts are disabled
while the edit modal is open to prevent accidental card flips/ratings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---
 src/client/pages/StudyPage.tsx | 59 ++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 57 insertions(+), 2 deletions(-)

(limited to 'src/client/pages/StudyPage.tsx')

diff --git a/src/client/pages/StudyPage.tsx b/src/client/pages/StudyPage.tsx
index 33fd290..c8eb603 100644
--- a/src/client/pages/StudyPage.tsx
+++ b/src/client/pages/StudyPage.tsx
@@ -2,6 +2,7 @@ import {
 	faCheck,
 	faChevronLeft,
 	faCircleCheck,
+	faPen,
 	faRotateLeft,
 } from "@fortawesome/free-solid-svg-icons";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -17,6 +18,7 @@ import {
 import { Link, useLocation, useParams } from "wouter";
 import { ApiClientError, apiClient } from "../api";
 import { studyDataAtomFamily } from "../atoms";
+import { EditNoteModal } from "../components/EditNoteModal";
 import { ErrorBoundary } from "../components/ErrorBoundary";
 import { queryClient } from "../queryClient";
 import { renderCard } from "../utils/templateRenderer";
@@ -60,6 +62,7 @@ function StudySession({
 		null,
 	);
 	const pendingReviewRef = useRef<PendingReview | null>(null);
+	const [editingNoteId, setEditingNoteId] = useState<string | null>(null);
 
 	// Keep ref in sync with state for cleanup effect
 	useEffect(() => {
@@ -174,6 +177,14 @@ function StudySession({
 	const handleKeyDown = useCallback(
 		(e: KeyboardEvent) => {
 			if (isSubmitting) return;
+			if (editingNoteId) return;
+
+			// Edit: E key to open edit modal
+			if (e.key === "e" && cards[currentIndex]) {
+				e.preventDefault();
+				setEditingNoteId(cards[currentIndex].noteId);
+				return;
+			}
 
 			// Undo: Ctrl+Z / Cmd+Z anytime, or z when card is not flipped
 			if (
@@ -206,7 +217,16 @@ function StudySession({
 				}
 			}
 		},
-		[isFlipped, isSubmitting, handleFlip, handleRating, handleUndo],
+		[
+			isFlipped,
+			isSubmitting,
+			editingNoteId,
+			cards,
+			currentIndex,
+			handleFlip,
+			handleRating,
+			handleUndo,
+		],
 	);
 
 	useEffect(() => {
@@ -395,11 +415,37 @@ function StudySession({
 								: "bg-ivory/50"
 						}`}
 					>
+						{/* Edit button */}
+						{/* biome-ignore lint/a11y/useSemanticElements: Cannot nest <button> inside parent <button>, using span with role="button" instead */}
+						<span
+							role="button"
+							tabIndex={0}
+							data-testid="edit-card-button"
+							onClick={(e) => {
+								e.stopPropagation();
+								setEditingNoteId(currentCard.noteId);
+							}}
+							onKeyDown={(e) => {
+								if (e.key === "Enter" || e.key === " ") {
+									e.stopPropagation();
+									e.preventDefault();
+									setEditingNoteId(currentCard.noteId);
+								}
+							}}
+							className="absolute top-3 right-3 w-8 h-8 flex items-center justify-center text-muted hover:text-slate transition-colors rounded-lg hover:bg-ivory"
+							aria-label="Edit card"
+						>
+							<FontAwesomeIcon
+								icon={faPen}
+								className="w-3.5 h-3.5"
+								aria-hidden="true"
+							/>
+						</span>
 						{/* New card badge */}
 						{currentCard.state === 0 && (
 							<span
 								data-testid="new-card-badge"
-								className="absolute top-3 right-3 bg-primary/10 text-primary text-xs font-medium px-2 py-0.5 rounded-full"
+								className="absolute top-3 right-10 bg-primary/10 text-primary text-xs font-medium px-2 py-0.5 rounded-full"
 							>
 								New
 							</span>
@@ -456,6 +502,15 @@ function StudySession({
 					)}
 				</div>
 			)}
+
+			{/* Edit Note Modal */}
+			<EditNoteModal
+				isOpen={!!editingNoteId}
+				deckId={deckId}
+				noteId={editingNoteId}
+				onClose={() => setEditingNoteId(null)}
+				onNoteUpdated={() => setEditingNoteId(null)}
+			/>
 		</div>
 	);
 }
-- 
cgit v1.3-1-g0d28

