# 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)
- [x] Backend cleanup (remove legacy routers).
- [x] Verify frontend uses V3 API (`ae_journals__journal.ts`).

### Phase 2: Rapid Entry (Complete)
- [x] Create `ae_comp__journal_entry_quick_add.svelte`.
- [x] Integrate Quick Add into `+page.svelte`.

### Phase 3: Content Manipulation & Portability (Complete)
- [x] Implement Append/Prepend logic.
- [x] Implement Bulk Export/Import system.
- [x] Establish centralized Export Template engine.

### Phase 4: Polish & Security (ACTIVE)
- [x] Implement Auto-Save toggle and visual status indicators.
- [x] Extract decryption workflow to non-reactive helper.
- [x] **Standardize Configuration Modals:** Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
- [x] **RESOLVED:** Decryption workflow stability (Fixed via dependency isolation).
- [x] **Style Standardization (2026-03-06):** Full Skeleton v4 `preset-*` class pass across all 17 journal components. See style token table in Lessons Learned below.
- [x] **Dark mode fixes:** Entry content hover, journal view section/description background and text colors.
- [x] **Modal close button:** All 3 config modals use `dismissable={false}` + explicit `<X>` button in header snippet for correct right-aligned placement.
- [x] **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)
- `variant-filled-*`, `variant-soft-*`, `variant-ghost-*`, `variant-ringed-*` — Skeleton v2, removed
- `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.
