diff --git a/src/lib/pages/SettingsPage.svelte b/src/lib/pages/SettingsPage.svelte index 3c3f5b9..d13362c 100644 --- a/src/lib/pages/SettingsPage.svelte +++ b/src/lib/pages/SettingsPage.svelte @@ -429,6 +429,16 @@ let showNewProfile = $state(false); let showNewTemplate = $state(false); + // Arm-confirm state for profile deletion. Mirrors HistoryPage's inline + // confirm pattern (4-second auto-disarm) — first click on Delete arms, + // second click within the window calls through to the storage layer. + // Replaces the prior one-click splice-into-localStorage path that + // silently drifted localStorage and SQLite apart. + const PROFILE_DELETE_CONFIRM_MS = 4000; + let deleteProfileArmedIndex = $state(-1); + let deleteProfileArmTimer: ReturnType | null = null; + let deleteProfileBusy = $state(false); + // Phase 9c: SettingsGroup native
drives disclosure state per // group; no more centralised openSection. Transcription stays open by // default (matches the prior accordion behaviour where it was the @@ -886,6 +896,10 @@ if (unlisten) unlisten(); if (unlistenLlm) unlistenLlm(); if (unlistenParakeet) unlistenParakeet(); + if (deleteProfileArmTimer) { + clearTimeout(deleteProfileArmTimer); + deleteProfileArmTimer = null; + } }); $effect(() => { @@ -1025,15 +1039,66 @@ editingProfile = profiles.length - 1; } - function deleteProfile(index) { - const name = profiles[index].name; - if (page.activeProfile === name) { - page.activeProfile = "None"; + // Two-step delete. First call arms (UI swaps to Confirm/Cancel pills); + // second call inside the window does the work. Routes through the + // SQLite-backed profilesStore so the canonical profile row + its + // profile_terms cascade are removed; legacy localStorage entry is only + // spliced after the storage call succeeds. The storage layer rejects + // deletion when the profile still has transcripts attached — that + // rejection surfaces as a toast via profilesStore.delete(). + function armDeleteProfile(index) { + deleteProfileArmedIndex = index; + if (deleteProfileArmTimer) clearTimeout(deleteProfileArmTimer); + deleteProfileArmTimer = setTimeout(() => { + deleteProfileArmedIndex = -1; + deleteProfileArmTimer = null; + }, PROFILE_DELETE_CONFIRM_MS); + } + + function disarmDeleteProfile() { + deleteProfileArmedIndex = -1; + if (deleteProfileArmTimer) { + clearTimeout(deleteProfileArmTimer); + deleteProfileArmTimer = null; + } + } + + async function confirmDeleteProfile(index) { + if (deleteProfileBusy) return; + if (index < 0 || index >= profiles.length) { + disarmDeleteProfile(); + return; + } + const name = profiles[index].name; + deleteProfileBusy = true; + try { + // Match the legacy localStorage record to its SQL counterpart by + // name. The two systems were grown in parallel: localStorage uses + // name as the natural key, SQL uses a UUID. Until the data model + // is unified we have to bridge by name. + const sqlProfile = profilesStore.profiles.find((p) => p.name === name); + if (sqlProfile && sqlProfile.id !== DEFAULT_PROFILE_ID) { + const before = profilesStore.profiles.length; + await profilesStore.delete(sqlProfile.id); + // profilesStore.delete catches its own errors and emits a toast; + // we detect failure by checking whether the store actually removed + // the row. If it did not, abandon the legacy splice so the two + // sides stay aligned. + if (profilesStore.profiles.length === before) { + return; + } + } + if (page.activeProfile === name) { + page.activeProfile = "None"; + } + removeProfileTaskList(name); + profiles.splice(index, 1); + saveProfiles(); + editingProfile = -1; + } finally { + deleteProfileBusy = false; + disarmDeleteProfile(); } - removeProfileTaskList(name); - profiles.splice(index, 1); - saveProfiles(); - editingProfile = -1; } function profileWordCount(words) { @@ -1437,10 +1502,23 @@ class="text-[12px] text-text-secondary hover:text-accent opacity-0 group-hover:opacity-100" onclick={() => editingProfile = editingProfile === i ? -1 : i} >{editingProfile === i ? "Close" : "Edit"} - + {#if deleteProfileArmedIndex === i} + + + {:else} + + {/if} {#if editingProfile === i}