From 023d0fcfce575030ee503c5f60df8c28dba7ab07 Mon Sep 17 00:00:00 2001
From: nsfisis <nsfisis@gmail.com>
Date: Sat, 2 May 2026 11:46:13 +0900
Subject: feat(decks): make deck CRUD work fully offline-first
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Create / Edit / Delete deck modals now write through localDeckRepository
and fire-and-forget syncActionAtom so the change is pushed when the
network is up. EditDeckModal reads its note-type list from the
local-first noteTypesAtom instead of fetching, and the "reconnect to..."
guards on the submit buttons are gone — the user can keep working while
offline.

Soft-delete intentionally does NOT cascade to notes/cards, matching the
server's existing deck.softDelete: the deck disappears from listings and
its children become unreachable that way.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 src/client/components/DeleteDeckModal.tsx | 29 +++++++++++++----------------
 1 file changed, 13 insertions(+), 16 deletions(-)

(limited to 'src/client/components/DeleteDeckModal.tsx')

diff --git a/src/client/components/DeleteDeckModal.tsx b/src/client/components/DeleteDeckModal.tsx
index 954431e..5e166fc 100644
--- a/src/client/components/DeleteDeckModal.tsx
+++ b/src/client/components/DeleteDeckModal.tsx
@@ -1,7 +1,7 @@
-import { useAtomValue } from "jotai";
+import { useSetAtom } from "jotai";
 import { useState } from "react";
-import { ApiClientError, apiClient } from "../api";
-import { isOnlineAtom } from "../atoms";
+import { syncActionAtom } from "../atoms";
+import { localDeckRepository } from "../db/repositories";
 
 interface Deck {
 	id: string;
@@ -23,7 +23,7 @@ export function DeleteDeckModal({
 }: DeleteDeckModalProps) {
 	const [error, setError] = useState<string | null>(null);
 	const [isDeleting, setIsDeleting] = useState(false);
-	const isOnline = useAtomValue(isOnlineAtom);
+	const triggerSync = useSetAtom(syncActionAtom);
 
 	const handleClose = () => {
 		setError(null);
@@ -37,19 +37,17 @@ export function DeleteDeckModal({
 		setIsDeleting(true);
 
 		try {
-			const res = await apiClient.rpc.api.decks[":id"].$delete({
-				param: { id: deck.id },
-			});
-			await apiClient.handleResponse(res);
+			const deleted = await localDeckRepository.delete(deck.id);
+			if (!deleted) {
+				setError("Deck not found.");
+				return;
+			}
 
 			onDeckDeleted();
 			onClose();
-		} catch (err) {
-			if (err instanceof ApiClientError) {
-				setError(err.message);
-			} else {
-				setError("Failed to delete deck. Please try again.");
-			}
+			void triggerSync().catch(() => {});
+		} catch {
+			setError("Failed to delete deck. Please try again.");
 		} finally {
 			setIsDeleting(false);
 		}
@@ -132,8 +130,7 @@ export function DeleteDeckModal({
 						<button
 							type="button"
 							onClick={handleDelete}
-							disabled={isDeleting || !isOnline}
-							title={!isOnline ? "Reconnect to delete" : undefined}
+							disabled={isDeleting}
 							className="px-4 py-2 bg-error hover:bg-error/90 text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed min-w-[100px]"
 						>
 							{isDeleting ? "Deleting..." : "Delete"}
-- 
cgit v1.3.1

