From c1ecdcad9c505ffcc2a965885fd456d21aafc094 Mon Sep 17 00:00:00 2001 From: elpatron Date: Mon, 1 Jun 2026 09:39:51 +0200 Subject: [PATCH] Fix live journal hang on empty new logbooks. Fast-path today's entry creation, add init timeout, defer auto-position GPS, and migrate logbook keys when the server returns a different id. Co-authored-by: Cursor --- client/src/components/LiveLogView.tsx | 60 ++++++++++++++++++++++----- client/src/services/logbook.ts | 4 ++ client/src/services/quickEventLog.ts | 25 +++++++---- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/client/src/components/LiveLogView.tsx b/client/src/components/LiveLogView.tsx index d04c0f0..8ad4601 100644 --- a/client/src/components/LiveLogView.tsx +++ b/client/src/components/LiveLogView.tsx @@ -87,8 +87,26 @@ type LiveModal = const AUTO_POSITION_INTERVAL_MS = 3 * 60 * 60 * 1000 const AUTO_POSITION_CHECK_MS = 60_000 +const AUTO_POSITION_START_DELAY_MS = 3000 +const LIVE_LOG_INIT_TIMEOUT_MS = 25_000 const UNDO_TIMEOUT_MS = 5000 +function withTimeout(promise: Promise, ms: number, message: string): Promise { + return new Promise((resolve, reject) => { + const timer = window.setTimeout(() => reject(new Error(message)), ms) + promise.then( + (value) => { + window.clearTimeout(timer) + resolve(value) + }, + (err) => { + window.clearTimeout(timer) + reject(err) + } + ) + }) +} + function hapticPulse() { navigator.vibrate?.(40) } @@ -186,13 +204,27 @@ export default function LiveLogView({ const seq = ++initSeqRef.current setLoading(true) setError(null) + setEntryId(null) + setEvents([]) + setYachtSails([]) + + if (!logbookId.trim()) { + setError(t('logs.live_load_error')) + setLoading(false) + return + } + try { - const id = await findOrCreateTodayEntry(logbookId) + const id = await withTimeout( + findOrCreateTodayEntry(logbookId), + LIVE_LOG_INIT_TIMEOUT_MS, + t('logs.live_load_error') + ) if (seq !== initSeqRef.current) return setEntryId(id) - const masterKey = await getLogbookKey(logbookId) || getActiveMasterKey() - if (masterKey) { + const logbookKey = await getLogbookKey(logbookId) + if (logbookKey) { const yacht = await db.yachts.get(logbookId) if (yacht) { try { @@ -200,7 +232,7 @@ export default function LiveLogView({ yacht.encryptedData, yacht.iv, yacht.tag, - masterKey + logbookKey ) if (decrypted?.sails && Array.isArray(decrypted.sails)) { setYachtSails(decrypted.sails as string[]) @@ -216,18 +248,18 @@ export default function LiveLogView({ if (loaded) { applyLoadedEntry(loaded) } else { - throw new Error(i18n.t('logs.live_load_error')) + throw new Error(t('logs.live_load_error')) } } catch (err: unknown) { if (seq !== initSeqRef.current) return console.error('Failed to init live log:', err) - setError(err instanceof Error ? err.message : i18n.t('logs.live_load_error')) + setError(err instanceof Error ? err.message : t('logs.live_load_error')) } finally { if (seq === initSeqRef.current) { setLoading(false) } } - }, [logbookId, applyLoadedEntry]) + }, [logbookId, applyLoadedEntry, t]) useEffect(() => { void runInit() @@ -263,7 +295,7 @@ export default function LiveLogView({ autoPositionBusyRef.current = true try { - const coords = await getCurrentPosition() + const coords = await getCurrentPosition(8000) await appendQuickEvent(logbookId, entryId, { gpsLat: coords.lat, gpsLng: coords.lng, @@ -277,8 +309,16 @@ export default function LiveLogView({ } } - const interval = window.setInterval(() => void maybeAutoPosition(), AUTO_POSITION_CHECK_MS) - return () => window.clearInterval(interval) + let intervalRef: number | undefined + const startTimer = window.setTimeout(() => { + void maybeAutoPosition() + intervalRef = window.setInterval(() => void maybeAutoPosition(), AUTO_POSITION_CHECK_MS) + }, AUTO_POSITION_START_DELAY_MS) + + return () => { + window.clearTimeout(startTimer) + if (intervalRef !== undefined) window.clearInterval(intervalRef) + } }, [entryId, loading, logbookId, refreshEntry, busy]) const runQuickAction = async ( diff --git a/client/src/services/logbook.ts b/client/src/services/logbook.ts index 6f76c7a..cf46296 100644 --- a/client/src/services/logbook.ts +++ b/client/src/services/logbook.ts @@ -214,6 +214,10 @@ export async function createLogbook(title: string): Promise { if (response.ok) { const serverLb = await response.json() + if (serverLb.id !== localId) { + await saveLogbookKey(serverLb.id, logbookKey) + await db.logbookKeys.delete(localId) + } await db.logbooks.put({ id: serverLb.id, encryptedTitle: serverLb.encryptedTitle, diff --git a/client/src/services/quickEventLog.ts b/client/src/services/quickEventLog.ts index d85a88e..060bd11 100644 --- a/client/src/services/quickEventLog.ts +++ b/client/src/services/quickEventLog.ts @@ -158,10 +158,12 @@ export async function createTodayEntry(logbookId: string): Promise { const localEntries = await db.entries.where({ logbookId }).toArray() const decryptedEntries: Array = [] - for (const entry of localEntries) { - const decrypted = await tryDecryptEntryPayload(entry, masterKey) - if (decrypted) { - decryptedEntries.push(decrypted as LogEntryTankSource & TravelDaySortable) + if (localEntries.length > 0) { + for (const entry of localEntries) { + const decrypted = await tryDecryptEntryPayload(entry, masterKey) + if (decrypted) { + decryptedEntries.push(decrypted as LogEntryTankSource & TravelDaySortable) + } } } @@ -211,10 +213,19 @@ export async function createTodayEntry(logbookId: string): Promise { } export async function findOrCreateTodayEntry(logbookId: string): Promise { - await ensureLogbookKey(logbookId) - const existing = await findTodayEntryId(logbookId) + const id = logbookId.trim() + if (!id) throw new Error('Logbook id required') + + await ensureLogbookKey(id) + + const entryCount = await db.entries.where({ logbookId: id }).count() + if (entryCount === 0) { + return createTodayEntry(id) + } + + const existing = await findTodayEntryId(id) if (existing) return existing - return createTodayEntry(logbookId) + return createTodayEntry(id) } export interface AppendQuickEventResult {