diff --git a/documentation/AE__Permissions_and_Security.md b/documentation/AE__Permissions_and_Security.md index b9141d34..150532c1 100644 --- a/documentation/AE__Permissions_and_Security.md +++ b/documentation/AE__Permissions_and_Security.md @@ -191,7 +191,7 @@ This pattern lives in `ae_comp__badge_obj_li.svelte` — move to `ae_utils` if n - Entry configuration admin controls are gated to `trusted_access` and above. - `manager_access` and `administrator_access` see the Delete action, which performs a hard delete. - `trusted_access` users see Remove instead, which follows disable semantics rather than a hard delete. -- The Admin section is the place for staff notes, enabled/default access state, and destructive entry actions; visibility/privacy flags remain separate. +- The Admin section is the place for staff notes, enabled/default access state, and destructive entry actions; the template toggle belongs in Metadata, while visibility/audience flags remain separate. ### Events — Badges diff --git a/documentation/PROJECT__AE_UI_Journals_module_update_2026.md b/documentation/PROJECT__AE_UI_Journals_module_update_2026.md index a34cdbf7..c97730c6 100644 --- a/documentation/PROJECT__AE_UI_Journals_module_update_2026.md +++ b/documentation/PROJECT__AE_UI_Journals_module_update_2026.md @@ -151,9 +151,9 @@ Svelte 5 state is backed by Proxies. ### 5. Journal Entry Config Layout Notes The Entry Config modal now follows a stricter section grammar: -* `Metadata` contains category, tags, summary, and archive date. +* `Metadata` contains category, tags, summary, archive date, and template. * `Status & Security` contains enabled/hidden/priority/sort. -* `Privacy Flags` contains only visibility/audience toggles. +* `Visibility & Audience` contains only visibility/audience toggles. * `Alerts & Messaging` contains alert flag + alert message. * `Admin` is gated to trusted access and above, and is the only place for notes plus delete/remove actions. diff --git a/src/lib/ae_elements/AE_AITools.svelte b/src/lib/ae_elements/AE_AITools.svelte index 33649c45..491ddbeb 100644 --- a/src/lib/ae_elements/AE_AITools.svelte +++ b/src/lib/ae_elements/AE_AITools.svelte @@ -24,8 +24,8 @@ import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.s interface Props { // Core Props - content: string; // The text to summarize/analyze - summary: string; // The result (bindable) + content: string | null | undefined; // The text to summarize/analyze + summary: string | null | undefined; // The result (bindable) // Configuration (Bindable for global settings persistence) model?: string; diff --git a/src/lib/ae_elements/AE_Object_Flags.svelte b/src/lib/ae_elements/AE_Object_Flags.svelte index b29cd15a..cc15e80d 100644 --- a/src/lib/ae_elements/AE_Object_Flags.svelte +++ b/src/lib/ae_elements/AE_Object_Flags.svelte @@ -6,7 +6,7 @@ */ import { Siren, - Fingerprint, + FingerprintPattern, Globe, BookHeart, BriefcaseBusiness, @@ -14,11 +14,11 @@ import { Settings } from '@lucide/svelte'; import { ae_loc } from '$lib/stores/ae_stores'; -import type { ae_JournalEntry } from '$lib/types/ae_types'; +import type { ae_JournalEntryDraft } from '$lib/types/ae_types'; interface Props { // The object containing the flags (bindable) - obj: ae_JournalEntry; + obj: ae_JournalEntryDraft; // Visibility configuration (optional overrides) show_labels?: boolean; @@ -109,8 +109,8 @@ function toggle_template() { onclick={toggle_private} class="btn btn-sm flex items-center gap-2 px-3 transition preset-tonal-secondary hover:preset-filled-secondary-500" title="Toggle private or encrypted visibility"> - - Private + + Private or Encrypt {/if} diff --git a/src/lib/elements/element_editor_codemirror.svelte b/src/lib/elements/element_editor_codemirror.svelte index 050e57e5..eed4433e 100644 --- a/src/lib/elements/element_editor_codemirror.svelte +++ b/src/lib/elements/element_editor_codemirror.svelte @@ -13,7 +13,7 @@ import { ensure_CodeMirror_modules } from './codemirror_modules'; // Icons (Standardized to Lucide where possible, or FontAwesome placeholders) import { Bold, Code, Italic, List } from '@lucide/svelte'; interface Props { - content?: string; + content?: string | null; new_content?: string; placeholder?: string; theme_mode?: 'light' | 'dark'; diff --git a/src/lib/types/ae_types.ts b/src/lib/types/ae_types.ts index fc22b857..a8f291d3 100644 --- a/src/lib/types/ae_types.ts +++ b/src/lib/types/ae_types.ts @@ -230,6 +230,30 @@ export interface ae_JournalEntry extends ae_BaseObj { file_count?: number; } +export type ae_JournalEntryDraft = Omit< + Partial, + | 'journal_entry_id' + | 'journal_id' + | 'name' + | 'summary' + | 'content' + | 'content_md_html' + | 'content_encrypted' + | 'history' + | 'history_encrypted' +> & { + journal_entry_id?: string; + journal_id?: string; + name?: string | null; + summary?: string | null; + content?: string | null; + content_md_html?: string | null; + content_encrypted?: string | null; + history?: string | null; + history_encrypted?: string | null; + [key: string]: unknown; +}; + /** * Person - A human entity */ diff --git a/src/routes/journals/ae_comp__journal_entry_ai_tools.svelte b/src/routes/journals/ae_comp__journal_entry_ai_tools.svelte index 9295792e..ba9dd782 100644 --- a/src/routes/journals/ae_comp__journal_entry_ai_tools.svelte +++ b/src/routes/journals/ae_comp__journal_entry_ai_tools.svelte @@ -9,7 +9,7 @@ import { journals_loc } from '$lib/ae_journals/ae_journals_stores'; interface Props { content: string; - summary: string; // Bindable + summary: string | null | undefined; // Bindable on_save: () => void; log_lvl?: number; } diff --git a/src/routes/journals/ae_comp__journal_entry_editor.svelte b/src/routes/journals/ae_comp__journal_entry_editor.svelte index b8365f8d..e642b84a 100644 --- a/src/routes/journals/ae_comp__journal_entry_editor.svelte +++ b/src/routes/journals/ae_comp__journal_entry_editor.svelte @@ -8,17 +8,16 @@ import { LockKeyhole, RefreshCcw, Save } from '@lucide/svelte'; import { ae_loc } from '$lib/stores/ae_stores'; import { journals_loc } from '$lib/ae_journals/ae_journals_stores'; import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte'; -import type { ae_JournalEntry, ae_Journal } from '$lib/types/ae_types'; - -type JournalEntryDraft = Partial & { - content?: string | false; - [key: string]: unknown; -}; +import type { + ae_JournalEntry, + ae_Journal, + ae_JournalEntryDraft +} from '$lib/types/ae_types'; interface Props { entry: ae_JournalEntry; journal: ae_Journal; - tmp_entry_obj: JournalEntryDraft; // Bindable + tmp_entry_obj: ae_JournalEntryDraft; // Bindable editor_view?: unknown; // Bindable has_changed: boolean; updated_idb: boolean; diff --git a/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte b/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte index 98725240..70f6bb3d 100644 --- a/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte +++ b/src/routes/journals/ae_comp__journal_entry_obj_id_view.svelte @@ -31,6 +31,7 @@ import AE_Comp_Journal_Entry_AiTools from './ae_comp__journal_entry_ai_tools.sve import AE_Comp_Journal_Entry_ObjFileLi from './ae_comp__journal_entry_obj_file_li.svelte'; import AE_Comp_Modal_Journal_Entry_Append from './ae_comp__modal_journal_entry_append.svelte'; import AE_Comp_Modal_Journal_Entry_Config from './ae_comp__modal_journal_entry_config.svelte'; +import type { ae_JournalEntryDraft } from '$lib/types/ae_types'; // Icons import { CircleAlert, CircleX, LoaderCircle } from '@lucide/svelte'; @@ -53,8 +54,8 @@ let { // *** State let editor_view: any = $state(); -let orig_entry_obj: key_val | null = $state(null); -let tmp_entry_obj: key_val = $state({}); +let orig_entry_obj: ae_JournalEntryDraft | null = $state(null); +let tmp_entry_obj: ae_JournalEntryDraft = $state({}); let save_status: 'saved' | 'unsaved' | 'saving' = $state('saved'); let decryption_error: string | null = $state(null); let auto_save_timer: ReturnType; @@ -65,7 +66,7 @@ let show_config_modal = $state(false); function deep_copy(obj: any) { if (!obj) return null; try { - const copy: key_val = {}; + const copy: ae_JournalEntryDraft = {}; for (const key in obj) { const val = obj[key]; if (val instanceof Date) { @@ -240,9 +241,12 @@ async function run_decryption_workflow() { } // SUCCESS - const content = result.content || ''; + const content = + typeof result.content === 'string' + ? result.content + : await (result.content ?? ''); tmp_entry_obj.content = content; - tmp_entry_obj.content_md_html = handle_marked(content); + tmp_entry_obj.content_md_html = await handle_marked(content); tmp_entry_obj.content_encrypted = null; tmp_entry_obj.history_encrypted = null; @@ -303,8 +307,12 @@ async function update_journal_entry(fields_kv?: key_val) { if (!fields_kv) { if (tmp_entry_obj.private) { if (tmp_entry_obj.content) { + const content_to_encrypt = + typeof tmp_entry_obj.content === 'string' + ? tmp_entry_obj.content + : ''; data_kv.content_encrypted = await ae_util.encrypt_wrapper( - tmp_entry_obj.content, + content_to_encrypt, decrypt_key ); data_kv.content = null; @@ -316,9 +324,15 @@ async function update_journal_entry(fields_kv?: key_val) { } try { + const journal_entry_id = $lq__journal_entry_obj?.journal_entry_id; + if (!journal_entry_id) { + console.error('Journal entry ID missing for update.'); + return; + } + await journals_func.update_ae_obj__journal_entry({ api_cfg: $ae_api, - journal_entry_id: $lq__journal_entry_obj?.journal_entry_id, + journal_entry_id, data_kv: data_kv, log_lvl: 1 }); @@ -468,7 +482,11 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto'); ? 'ring-primary-500/40 ring-2 ring-inset' : ''}"> update_journal_entry()} {log_lvl} /> @@ -509,7 +527,6 @@ let modal_mode: 'append' | 'prepend' | 'auto' = $state('auto'); update_journal_entry()} diff --git a/src/routes/journals/ae_comp__journal_entry_obj_li.svelte b/src/routes/journals/ae_comp__journal_entry_obj_li.svelte index 5578685a..20fe7bb8 100644 --- a/src/routes/journals/ae_comp__journal_entry_obj_li.svelte +++ b/src/routes/journals/ae_comp__journal_entry_obj_li.svelte @@ -52,8 +52,9 @@ import { } from '$lib/ae_journals/ae_journals_stores'; import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import AeCompModalJournalEntryAppend from './ae_comp__modal_journal_entry_append.svelte'; +import type { ae_JournalEntryDraft } from '$lib/types/ae_types'; -let tmp_entry_obj: key_val = $state({}); +let tmp_entry_obj: ae_JournalEntryDraft = $state({}); // Derived state for modal visibility // We cast to boolean for the prop, but we need to handle the close event to clear the store ID diff --git a/src/routes/journals/ae_comp__modal_journal_entry_append.svelte b/src/routes/journals/ae_comp__modal_journal_entry_append.svelte index ff5143be..7aa3b54e 100644 --- a/src/routes/journals/ae_comp__modal_journal_entry_append.svelte +++ b/src/routes/journals/ae_comp__modal_journal_entry_append.svelte @@ -5,10 +5,11 @@ import { ae_util } from '$lib/ae_utils/ae_utils'; import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import { ae_api } from '$lib/stores/ae_stores'; import type { key_val } from '$lib/stores/ae_stores'; +import type { ae_JournalEntryDraft } from '$lib/types/ae_types'; interface Props { open: boolean; - journal_entry: key_val; + journal_entry: ae_JournalEntryDraft; journal_config: key_val; // The cfg_json from the journal object mode?: 'append' | 'prepend' | 'auto'; on_close: () => void; @@ -26,7 +27,7 @@ let { log_lvl = 0 }: Props = $props(); // Local State -let tmp_entry_obj: key_val = $state({}); +let tmp_entry_obj: ae_JournalEntryDraft = $state({}); // Header Options let add_timestamp_header: boolean = $state(true); @@ -50,7 +51,8 @@ $effect(() => { }); async function handle_save() { - let current_entry_content = tmp_entry_obj?.content || ''; + let current_entry_content = + typeof tmp_entry_obj?.content === 'string' ? tmp_entry_obj.content : ''; let add_content = ''; let new_content = current_entry_content; @@ -109,11 +111,16 @@ async function handle_save() { new_content = new_content.trim() + '\n'; let data_kv = { content: new_content }; + const journal_entry_id = tmp_entry_obj.journal_entry_id; + if (!journal_entry_id) { + console.error('Journal entry ID missing for append save.'); + return; + } try { let update_result = await journals_func.update_ae_obj__journal_entry({ api_cfg: $ae_api, - journal_entry_id: tmp_entry_obj?.journal_entry_id, + journal_entry_id, data_kv: data_kv, log_lvl: log_lvl }); diff --git a/src/routes/journals/ae_comp__modal_journal_entry_config.svelte b/src/routes/journals/ae_comp__modal_journal_entry_config.svelte index bf2bfbf6..fa255485 100644 --- a/src/routes/journals/ae_comp__modal_journal_entry_config.svelte +++ b/src/routes/journals/ae_comp__modal_journal_entry_config.svelte @@ -7,13 +7,11 @@ import { ArrowDownToLine, ArrowUpToLine, Check, - CircleAlert, CodeXml, Copy, FileDown, - Fingerprint, + FingerprintPattern, Minus, - Plus, RefreshCcw, Settings, Shapes, @@ -29,13 +27,16 @@ import { ae_loc, ae_api } from '$lib/stores/ae_stores'; import { journals_func } from '$lib/ae_journals/ae_journals_functions'; import AE_Comp_Editor_CodeMirror from '$lib/elements/element_editor_codemirror.svelte'; import AE_Object_Flags from '$lib/ae_elements/AE_Object_Flags.svelte'; -import type { ae_Journal, ae_JournalEntry } from '$lib/types/ae_types'; +import type { + ae_Journal, + ae_JournalEntryDraft +} from '$lib/types/ae_types'; interface Props { log_lvl?: number; show?: boolean; journal: ae_Journal; - tmp_entry_obj: ae_JournalEntry; // Bindable + tmp_entry_obj: ae_JournalEntryDraft; // Bindable on_save: () => void; on_force_reset?: () => void; on_show_export?: () => void; @@ -77,6 +78,12 @@ function tab_button_class(is_active: boolean): string { async function handle_update_entry() { try { // WHITELISTED BASE TABLE COLUMNS ONLY + const journal_entry_id = tmp_entry_obj.journal_entry_id; + if (!journal_entry_id) { + console.error('Journal entry ID missing for update.'); + return; + } + const data_kv = { name: tmp_entry_obj.name, short_name: tmp_entry_obj.short_name, @@ -86,6 +93,7 @@ async function handle_update_entry() { type_code: tmp_entry_obj.type_code, topic_code: tmp_entry_obj.topic_code, tags: tmp_entry_obj.tags, + passcode_hash: tmp_entry_obj.passcode_hash, private: tmp_entry_obj.private, public: tmp_entry_obj.public, personal: tmp_entry_obj.personal, @@ -105,7 +113,7 @@ async function handle_update_entry() { await journals_func.update_ae_obj__journal_entry({ api_cfg: $ae_api, - journal_entry_id: tmp_entry_obj.journal_entry_id, + journal_entry_id, data_kv: data_kv, log_lvl: log_lvl }); @@ -119,6 +127,12 @@ async function handle_admin_delete_action() { const delete_method = can_delete ? 'delete' : 'disable'; const action_label = can_delete ? 'delete' : 'remove'; const confirm_label = can_delete ? 'delete' : 'remove'; + const journal_entry_id = tmp_entry_obj.journal_entry_id; + + if (!journal_entry_id) { + console.error('Journal entry ID missing for delete action.'); + return; + } if (!confirm(`Are you sure you want to ${confirm_label} this journal entry?`)) { return; @@ -127,7 +141,7 @@ async function handle_admin_delete_action() { try { await journals_func.delete_ae_obj_id__journal_entry({ api_cfg: $ae_api, - journal_entry_id: tmp_entry_obj.journal_entry_id, + journal_entry_id, method: delete_method, log_lvl }); @@ -149,7 +163,7 @@ async function handle_admin_delete_action() { size="lg" class="relative mx-auto flex h-[calc(100dvh-2rem)] max-h-[calc(100dvh-2rem)] w-full flex-col rounded-xl border border-surface-200-800 bg-surface-50-900 text-surface-950-50 shadow-xl" headerClass="flex w-full flex-row items-center justify-between gap-2 rounded-t-xl border-b border-surface-200-800 bg-surface-100-900 p-4" - footerClass="flex w-full flex-row items-center justify-center gap-2 rounded-b-xl border-t border-surface-200-800 bg-surface-100-900 p-4"> + footerClass="mt-auto flex w-full shrink-0 flex-row items-center justify-center gap-2 rounded-b-xl border-t border-surface-200-800 bg-surface-100-900 p-4"> {#snippet header()}

@@ -338,6 +352,25 @@ async function handle_admin_delete_action() { }} class="input" /> + + {#if !journal?.cfg_json?.hide_btn_template} + + {/if} @@ -346,118 +379,27 @@ async function handle_admin_delete_action() {

- Status & Security + Status

- - - -
-
- Sort Order - Manual list position -
-
-
{/if} - {#if $ae_loc.trusted_access || $ae_loc.manager_access || $ae_loc.administrator_access} -
-

- - Admin -

+
+ + + Admin + +

- Trusted access and above only. Notes are for staff - use; managers and admins see Delete while trusted - access sees Remove. + Trusted access and above only. Notes are for staff use; managers and admins see Delete while trusted access sees Remove.