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/CreateDeckModal.tsx | 53 ++++++++++++-------------------
 1 file changed, 21 insertions(+), 32 deletions(-)

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

diff --git a/src/client/components/CreateDeckModal.tsx b/src/client/components/CreateDeckModal.tsx
index 34d46e7..11dc712 100644
--- a/src/client/components/CreateDeckModal.tsx
+++ b/src/client/components/CreateDeckModal.tsx
@@ -1,7 +1,7 @@
-import { useAtomValue } from "jotai";
+import { useAtomValue, useSetAtom } from "jotai";
 import { type FormEvent, useState } from "react";
-import { ApiClientError, apiClient } from "../api";
-import { isOnlineAtom } from "../atoms";
+import { syncActionAtom, userAtom } from "../atoms";
+import { localDeckRepository } from "../db/repositories";
 
 interface CreateDeckModalProps {
 	isOpen: boolean;
@@ -18,7 +18,8 @@ export function CreateDeckModal({
 	const [description, setDescription] = useState("");
 	const [error, setError] = useState<string | null>(null);
 	const [isSubmitting, setIsSubmitting] = useState(false);
-	const isOnline = useAtomValue(isOnlineAtom);
+	const user = useAtomValue(userAtom);
+	const triggerSync = useSetAtom(syncActionAtom);
 
 	const resetForm = () => {
 		setName("");
@@ -33,40 +34,29 @@ export function CreateDeckModal({
 
 	const handleSubmit = async (e: FormEvent) => {
 		e.preventDefault();
+		if (!user) {
+			setError("You must be signed in to create a deck.");
+			return;
+		}
 		setError(null);
 		setIsSubmitting(true);
 
 		try {
-			const res = await apiClient.rpc.api.decks.$post(
-				{
-					json: {
-						name: name.trim(),
-						description: description.trim() || null,
-					},
-				},
-				{
-					headers: apiClient.getAuthHeader(),
-				},
-			);
-
-			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,
-				);
-			}
+			await localDeckRepository.create({
+				userId: user.id,
+				name: name.trim(),
+				description: description.trim() || null,
+				defaultNoteTypeId: null,
+			});
 
 			resetForm();
 			onDeckCreated();
 			onClose();
-		} catch (err) {
-			if (err instanceof ApiClientError) {
-				setError(err.message);
-			} else {
-				setError("Failed to create deck. Please try again.");
-			}
+			// Fire-and-forget: server push will be retried by the sync engine if it
+			// fails (e.g. offline), so we deliberately do not await or surface errors.
+			void triggerSync().catch(() => {});
+		} catch {
+			setError("Failed to create deck. Please try again.");
 		} finally {
 			setIsSubmitting(false);
 		}
@@ -163,8 +153,7 @@ export function CreateDeckModal({
 							</button>
 							<button
 								type="submit"
-								disabled={isSubmitting || !name.trim() || !isOnline}
-								title={!isOnline ? "Reconnect to create a deck" : undefined}
+								disabled={isSubmitting || !name.trim()}
 								className="px-4 py-2 bg-primary hover:bg-primary-dark text-white font-medium rounded-lg transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
 							>
 								{isSubmitting ? "Creating..." : "Create Deck"}
-- 
cgit v1.3.1

