6.6 KiB
Aether Journals UI Update (2026)
Status: 🚧 Stuck on Phase 4 (Security/Encryption Blockers) Last Updated: 2026-01-14 Primary Agent: Frontend SvelteKit Agent
1. Project Overview
This document outlines the modernization of the Journals module UI in the SvelteKit frontend (aether_app_sveltekit). The primary goals are to fully leverage the generic V3 API architecture and introduce high-velocity productivity features for journal management.
Context: The backend transition to the generic api_crud_v3 router is complete. Custom legacy routers have been removed. The frontend must now fully align with this pattern and provide a frictionless user experience.
2. Core Objectives
🎯 Primary Goals
- V3 API Verification: Ensure all CRUD operations utilize the generic
api_crud_v3endpoints (Verified). - Quick Add UI: Implement a specialized interface for rapid, friction-free entry creation.
- Append/Prepend UI: Allow users to quickly add text to the beginning or end of existing entries without full edit mode.
- Interop & Portability: Robust import/export logic for Markdown/HTML (Nextcloud Notes compatibility).
- Security Hardening: Review and harden client-side encryption logic (BLOCKED).
3. Technical Architecture
Backend (Completed)
- Router:
api_crud_v3(Generic) - Definitions:
app/ae_obj_types_def.py->app/object_definitions/journals.py - Endpoints:
/v3/crud/journal/...and/v3/crud/journal_entry/...
Frontend (In Progress)
- State Management:
src/lib/ae_journals/ae_journals_stores.ts - Local Storage: Dexie.js (
db_journals) - API Client:
src/lib/api/api.ts->get_ae_obj_v3 - Export Engine: Centralized templates in
src/lib/ae_journals/ae_journals_export_templates.ts.
4. Feature Specifications
⚡ Quick Add (Complete)
- Component:
src/routes/journals/ae_comp__journal_entry_quick_add.svelte - Behavior: Creates a new
journal_entryattached to the active journal without leaving the list view.
📝 Append / Prepend (Complete)
- Interaction: Fast text injection via
AeCompModalJournalEntryAppend. - Logic: Updates entry content without full editor state overhead.
🔄 Interop (Markdown/HTML) (Complete)
- Goal: Bulk export/import for data portability.
- Templates: Standard, Personal Log, Amazon Vine.
5. Implementation Plan
Phase 1: Foundation (Done)
- Backend cleanup (remove legacy routers).
- Verify frontend uses V3 API (
ae_journals__journal.ts).
Phase 2: Rapid Entry (Complete)
- Create
ae_comp__journal_entry_quick_add.svelte. - Integrate Quick Add into
+page.svelte.
Phase 3: Content Manipulation & Portability (Complete)
- Implement Append/Prepend logic.
- Implement Bulk Export/Import system.
- Establish centralized Export Template engine.
Phase 4: Polish & Security (ACTIVE)
- Implement Auto-Save toggle and visual status indicators.
- Extract decryption workflow to non-reactive helper.
- Solidify E2EE passcode system for Journals and Entries.
- RESOLVED: Decryption workflow stability (Fixed via dependency isolation).
- Audit encryption flow for Quick Added and Imported entries.
- Integrate Outbound Email sharing.
🧠 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_statusortmp_entry_obj.contentto 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_copyhelper or selective property assignment when syncing "Original" vs "Temporary" state. This ensuresorig_entry_objis a plain JS object, making thehas_unsaved_changescheck 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_processingboolean flag. If any async workflow is active, block others from starting and prevent thehas_unsaved_changesderived rune from reportingtrue.
4. Comparison Normalization
- The Problem: Trivial differences (e.g.,
nullvs""or trailing whitespace) would trigger "unsaved changes" and fire the save loop. - The Fix: Use a
normalize()function in thehas_unsaved_changesderived rune to trim strings and treatnull/undefinedas 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.
- Decrypting content triggers a change in
tmp_entry_obj.content. - The Auto-Save effect sees this as a manual user edit and saves to the database.
- Dexie LiveQuery detects the DB update and refreshes the object.
- The Sync effect resets the entry to its encrypted state (from DB).
- The Auto-Decryption effect fires again, starting the loop over.
What we tried:
is_processingflags: Attempted to block reactivity during decryption.untrack(): Attempted to isolate store updates.- Reference Sync: Attempted to update
orig_entry_objsimultaneously with decryption to fool the change detector. - Direct Store Updates: Switching from property assignment to
journals_sess.update()to fix Svelte 5 notification failures.
Future Fix Ideas:
- Native Svelte 5 State: Refactor
journals_sessfrom a Svelte 4Writableto a class using$state. - Logic Extraction: Move decryption logic into a non-reactive class/helper to isolate side effects from the UI render cycle.
- Hash Comparison: Use content hashes for change detection instead of string comparisons to avoid whitespace/normalization loops.