diff --git a/documentation/AE_UI_Journals_module_update_2026.md b/documentation/AE_UI_Journals_module_update_2026.md index f8262a72..065c3f89 100644 --- a/documentation/AE_UI_Journals_module_update_2026.md +++ b/documentation/AE_UI_Journals_module_update_2026.md @@ -68,16 +68,41 @@ This document outlines the modernization of the Journals module UI in the Svelte - [x] Implement Bulk Export/Import system. - [x] Establish centralized Export Template engine. -### Phase 4: Polish & Security (ACTIVE BLOCKER) +### Phase 4: Polish & Security (ACTIVE) - [x] Implement Auto-Save toggle and visual status indicators. +- [x] Extract decryption workflow to non-reactive helper. - [ ] Solidify E2EE passcode system for Journals and Entries. -- [ ] **BLOCKER:** Decryption workflow is currently unstable due to Svelte 5 reactivity loops. +- [x] **RESOLVED:** Decryption workflow stability (Fixed via dependency isolation). - [ ] Audit encryption flow for Quick Added and Imported entries. - [ ] Integrate Outbound Email sharing. --- -## ⚠️ Technical Blocker: The "Decryption-Sync" Loop +## 🧠 Lessons Learned: Solving the Svelte 5 Reactivity Hang + +During the implementation of the Privacy/Decryption toggle, we encountered a critical browser hang caused by an infinite reactivity loop. Here is how we resolved it and the patterns we should follow in the future: + +### 1. Rigorous Dependency Isolation (`untrack`) +Svelte 5 runes (`$effect`, `$derived`) automatically track **every** reactive variable read inside them. +* **The Problem:** An effect would read `save_status` or `tmp_entry_obj.content` to decide if it should sync, but the act of syncing would update those same variables, re-triggering the effect. +* **The Fix:** Wrap any "check-only" state or store reads in `untrack(() => ... )`. This allows the effect to use the value without becoming a dependency of it. + +### 2. Manual Deep Copying vs. Proxies +Svelte 5 state is backed by Proxies. +* **The Problem:** Using `JSON.parse(JSON.stringify(proxy))` can sometimes trigger unexpected behavior or loops when used inside a reactive context. +* **The Fix:** Implement a manual `deep_copy` helper or selective property assignment when syncing "Original" vs "Temporary" state. This ensures `orig_entry_obj` is a plain JS object, making the `has_unsaved_changes` check stable. + +### 3. Concurrency Locking (`is_processing`) +* **The Problem:** Decryption (Async) and Auto-Save (Debounced Async) can fire nearly simultaneously. +* **The Fix:** Use a simple `is_processing` boolean flag. If any async workflow is active, block others from starting and prevent the `has_unsaved_changes` derived rune from reporting `true`. + +### 4. Comparison Normalization +* **The Problem:** Trivial differences (e.g., `null` vs `""` or trailing whitespace) would trigger "unsaved changes" and fire the save loop. +* **The Fix:** Use a `normalize()` function in the `has_unsaved_changes` derived rune to trim strings and treat `null/undefined` as empty strings during comparison. + +--- + +## ⚠️ Technical Blocker: The "Decryption-Sync" Loop (Resolved) ### The Issue The component is suffering from a **Reactive Feedback Loop** between decryption, auto-save, and background IDB refreshes. diff --git a/src/routes/journals/+page.svelte b/src/routes/journals/+page.svelte index 1d6d89c1..ebf63730 100644 --- a/src/routes/journals/+page.svelte +++ b/src/routes/journals/+page.svelte @@ -8,11 +8,11 @@ import { goto } from '$app/navigation'; // *** Icons - import { - BookPlus, SquareLibrary, Wrench, - FileUp, Loader2, Sparkles + import { + BookPlus, SquareLibrary, Wrench, + FileUp, Loader2, Sparkles } from '@lucide/svelte'; - + // *** Libraries & Stores import { liveQuery } from 'dexie'; import { Modal } from 'flowbite-svelte'; @@ -85,15 +85,13 @@
@@ -108,9 +106,9 @@