Files
OSIT-AE-App-Svelte/documentation/PROJECT__AE_UI_Journals_module_update_2026.md

10 KiB

Aether Journals UI Update (2026)

Status: 🚧 Phase 4 Active (Security/Encryption Blockers remain; Style pass complete) Last Updated: 2026-03-06 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

  1. V3 API Verification: Ensure all CRUD operations utilize the generic api_crud_v3 endpoints (Verified).
  2. Quick Add UI: Implement a specialized interface for rapid, friction-free entry creation.
  3. Append/Prepend UI: Allow users to quickly add text to the beginning or end of existing entries without full edit mode.
  4. Interop & Portability: Robust import/export logic for Markdown/HTML (Nextcloud Notes compatibility).
  5. 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_entry attached 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.
  • Standardize Configuration Modals: Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
  • RESOLVED: Decryption workflow stability (Fixed via dependency isolation).
  • Style Standardization (2026-03-06): Full Skeleton v4 preset-* class pass across all 17 journal components. See style token table in Lessons Learned below.
  • Dark mode fixes: Entry content hover, journal view section/description background and text colors.
  • Modal close button: All 3 config modals use dismissable={false} + explicit <X> button in header snippet for correct right-aligned placement.
  • Global select padding: Added padding-inline: 0.5rem to @layer base in app.css (safe — utility px-* classes override it where intentional).
  • Solidify E2EE passcode system for Journals and Entries.
  • 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 and the new Configuration Modals, we encountered critical browser hangs caused by infinite reactivity loops. Here is how we resolved them:

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. This is CRITICAL when initializing local state from props inside an effect.

2. Standardized Modal UI ("Aether Orange") & Style Token Conventions

We have established a unified design language for configuration interfaces and all Journals UI. Use these as the module template.

Modal Chrome

  • Header/Footer: bg-orange-100 dark:bg-orange-900 with consistent orange borders.
  • Close button: Always use dismissable={false} on the <Modal> and add an explicit <button> with <X> inside the {#snippet header()} so placement is fully in our control. The flex-1 class on the <h3> pushes it right.
  • Tabs: Center-aligned btn btn-sm with preset-filled-primary (active) / preset-tonal-surface (inactive).
  • Icons: Every tab and primary action should have a Lucide icon for better scannability.
  • Explicit Persistence: Follow "Edit working copy → Save Changes" pattern to prevent accidental store/API churn.

Skeleton v4 Style Token Reference (Journals = canonical example)

Intent Class
Primary CTA (save, create, open) btn preset-filled-primary
Neutral / cancel / close btn preset-tonal-surface
Secondary action btn preset-tonal-secondary
Success (confirmed save) btn preset-filled-success
Warning (caution action) btn preset-tonal-warning hover:preset-filled-warning-500
Error / danger (delete, force reset) btn preset-tonal-error hover:preset-filled-error-500
Active tab preset-filled-primary
Inactive tab preset-tonal-surface
Icon button btn-icon btn-icon-sm preset-tonal-surface
Input (base) input
Input (small) input input-sm
Select (base) select
Select (small) select select-sm
Textarea textarea
Badge (info/neutral) badge preset-tonal-surface
Badge (success) badge preset-tonal-success
Badge (error) badge preset-tonal-error

Removed Patterns (never use)

  • All variant-* classes are now fully removed from the codebase. Use only preset-* classes for all buttons and interactive elements.
  • variant-form-material — Skeleton v2, removed from all inputs/selects/textareas
  • input-bordered — non-standard, removed
  • DaisyUI modal / modal-box / modal-action wrapper divs inside Flowbite <Modal> — removed

Dark Mode Rules

  • Any bg-{color}-100 dynamic background must have a dark:bg-gray-800 (or similar) override — light shades are unreadable in dark mode.
  • Hover states on content areas need both light and dark variants: hover:bg-blue-100 dark:hover:bg-blue-950.
  • Text locked to dark:text-gray-900 is almost always wrong — use dark:text-gray-100.

3. Dexie LiveQuery Subscriptions

  • The Problem: Accessing liveQuery observables directly in templates results in [object Object] or undefined property errors.
  • The Mandate: ALWAYS use the $ prefix (e.g. $lq__obj) when passing or using data from a Dexie liveQuery.

4. 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.

  1. Decrypting content triggers a change in tmp_entry_obj.content.
  2. The Auto-Save effect sees this as a manual user edit and saves to the database.
  3. Dexie LiveQuery detects the DB update and refreshes the object.
  4. The Sync effect resets the entry to its encrypted state (from DB).
  5. The Auto-Decryption effect fires again, starting the loop over.

What we tried:

  • is_processing flags: Attempted to block reactivity during decryption.
  • untrack(): Attempted to isolate store updates.
  • Reference Sync: Attempted to update orig_entry_obj simultaneously 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:

  1. Native Svelte 5 State: Refactor journals_sess from a Svelte 4 Writable to a class using $state.
  2. Logic Extraction: Move decryption logic into a non-reactive class/helper to isolate side effects from the UI render cycle.
  3. Hash Comparison: Use content hashes for change detection instead of string comparisons to avoid whitespace/normalization loops.