Polish the Journal Entry Config modal to match the desired section outline, hide alert messaging unless enabled, update the shared draft typing for entry flows, and replace deprecated privacy icons. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
12 KiB
Aether Journals UI Update (2026)
Status: 🚧 Phase 4 Active (Security/Encryption Blockers remain; Journal Entry config rework in progress) Last Updated: 2026-05-05 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 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_crudendpoints (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(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 - 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.
- Standardize Configuration Modals: Refactored Module, Journal, and Entry configuration into a unified tabbed UI.
- Journal Entry Config cleanup: Summary now lives in Metadata; Alert lives in its own Alerts & Messaging section; Privacy Flags is visibility-only; Admin controls are split out and gated to trusted-access and above.
- Shared Flags widget:
AE_Object_Flagsnow shows visible button text and hover titles instead of icon-only controls. - Modal sizing: Entry config modal now expands to viewport height instead of stopping at a fixed 60vh body cap.
- Delete/Remove behavior: Entry config Admin section now uses the real delete helper. Managers/admins see Delete (hard delete); trusted access sees Remove (disable semantics).
- 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.5remto@layer baseinapp.css(safe — utilitypx-*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_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. 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-900with 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. Theflex-1class on the<h3>pushes it right. - Tabs: Center-aligned
btn btn-smwithpreset-filled-primary(active) /preset-tonal-surface(inactive). - Icons: Every tab and primary action should have a Lucide icon for better scannability.
- Button titles: Any button that uses icon+text or icon-only must include a descriptive
titlefor hover clarity. - 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 |
| Warning action (remove/disable) | btn preset-tonal-warning hover:preset-filled-warning-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 onlypreset-*classes for all buttons and interactive elements. variant-form-material— Skeleton v2, removed from all inputs/selects/textareasinput-bordered— non-standard, removed- DaisyUI
modal/modal-box/modal-actionwrapper divs inside Flowbite<Modal>— removed
Dark Mode Rules
- Any
bg-{color}-100dynamic background must have adark: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-900is almost always wrong — usedark:text-gray-100.
3. Dexie LiveQuery Subscriptions
- The Problem: Accessing
liveQueryobservables directly in templates results in[object Object]orundefinedproperty errors. - The Mandate: ALWAYS use the
$prefix (e.g.$lq__obj) when passing or using data from a DexieliveQuery.
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_copyhelper or selective property assignment when syncing "Original" vs "Temporary" state. This ensuresorig_entry_objis a plain JS object, making thehas_unsaved_changescheck stable.
5. Journal Entry Config Layout Notes
The Entry Config modal now follows a stricter section grammar:
Metadatacontains category, tags, summary, archive date, and template.Status & Securitycontains enabled/hidden/priority/sort.Visibility & Audiencecontains only visibility/audience toggles.Alerts & Messagingcontains alert flag + alert message.Adminis gated to trusted access and above, and is the only place for notes plus delete/remove actions.
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.