From 980a5e996c0a024ca6682a1e8f67b8cc866a03a2 Mon Sep 17 00:00:00 2001 From: Till JS Date: Tue, 7 Apr 2026 14:22:17 +0200 Subject: [PATCH] feat(dreams): symbol library with detail view, meaning, mood stats MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a Symbols view to the Dreams module — the long-term differentiator that lets users build a personal symbol vocabulary instead of relying on generic dream-dictionary entries. - New view-mode tabs (Träume / Symbole) at the top of the Dreams view - SymbolsView: wordcloud-style list of all symbols sized by frequency, with name search and inline color dots - SymbolDetailView: editable name + personal meaning + color picker, mood distribution bars, co-occurring symbols, and chronological list of all dreams that reference the symbol - dreamsStore.updateSymbol: rename propagates to all referencing dreams, collisions auto-merge with the existing symbol - dreamsStore.deleteSymbol: removes the symbol from all dreams - dreamsStore.mergeSymbols: rewrites references and sums counts - New query helpers: getDreamsWithSymbol, getMoodDistribution, getCooccurringSymbols Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/modules/dreams/ListView.svelte | 498 ++++++++++-------- .../apps/web/src/lib/modules/dreams/index.ts | 3 + .../web/src/lib/modules/dreams/queries.ts | 41 ++ .../modules/dreams/stores/dreams.svelte.ts | 87 +++ .../dreams/views/SymbolDetailView.svelte | 462 ++++++++++++++++ .../modules/dreams/views/SymbolsView.svelte | 150 ++++++ 6 files changed, 1018 insertions(+), 223 deletions(-) create mode 100644 apps/mana/apps/web/src/lib/modules/dreams/views/SymbolDetailView.svelte create mode 100644 apps/mana/apps/web/src/lib/modules/dreams/views/SymbolsView.svelte diff --git a/apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte b/apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte index 7e8318e29..4058b15b5 100644 --- a/apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte @@ -15,9 +15,13 @@ import type { ViewProps } from '$lib/app-registry'; import { ContextMenu, type ContextMenuItem } from '@mana/shared-ui'; import { PencilSimple, PushPin, Trash } from '@mana/shared-icons'; + import SymbolsView from './views/SymbolsView.svelte'; let { navigate, goBack, params }: ViewProps = $props(); + type ViewMode = 'list' | 'symbols'; + let viewMode = $state('list'); + let dreams$ = useAllDreams(); let dreams = $derived(dreams$.value); @@ -165,256 +169,275 @@
- -
e.preventDefault()} class="quick-add"> - 🌙 - -
+ +
+ + +
- - {#if insights.total > 0} -
- {insights.total} Träume - {#if insights.lucidCount > 0} - ✨ {insights.lucidCount} Klarträume - {/if} - {#each insights.topSymbols as sym} - - {/each} - {#if symbolFilter} - - {/if} -
- {/if} + {#if viewMode === 'symbols'} + + {:else} + +
e.preventDefault()} class="quick-add"> + 🌙 + +
- - {#if dreams.length > 0} -
- - - - -
- {/if} - - - {#if dreams.length > 5} - - {/if} - - -
- {#each grouped as group (group.label)} -
{group.label}
- {#each group.dreams as dream (dream.id)} - {#if editingId === dream.id} - -
{ - if (e.key === 'Escape') saveEdit(); - }} + + {#if insights.total > 0} +
+ {insights.total} Träume + {#if insights.lucidCount > 0} + ✨ {insights.lucidCount} Klarträume + {/if} + {#each insights.topSymbols as sym} + + {/each} + {#if symbolFilter} + + {/if} +
+ {/if} -
-
- {#each MOODS as mood} - - {/each} -
-
+ + {#if dreams.length > 0} +
+ + + + +
+ {/if} -
- - - -
+ + {#if dreams.length > 5} + + {/if} -
-
- Schlafqualität -
- {#each [1, 2, 3, 4, 5] as q} + +
+ {#each grouped as group (group.label)} +
{group.label}
+ {#each group.dreams as dream (dream.id)} + {#if editingId === dream.id} + +
{ + if (e.key === 'Escape') saveEdit(); + }} + > + + + + +
+
+ {#each MOODS as mood} {/each}
-
-
-
- - -
-
- {:else} - -
startEdit(dream)} - onkeydown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { - e.preventDefault(); - startEdit(dream); - } - }} - oncontextmenu={(e) => handleItemContextMenu(e, dream)} - > - {#if dream.mood} - - {:else} - - {/if} - -
-
- {dream.title || 'Traum ohne Titel'} - {#if dream.isLucid}{/if} - {#if dream.isRecurring}{/if} - {#if dream.isPinned}📌{/if} - {#if dream.isPrivate}🔒{/if} -
- {#if dream.content} -

{dream.content.split('\n')[0]}

- {/if} -
- {formatDreamDate(dream.dreamDate)} - {#if dream.symbols.length > 0} - · - - {#each dream.symbols.slice(0, 3) as sym} +
+
+ Schlafqualität +
+ {#each [1, 2, 3, 4, 5] as q} {/each} - - {/if} +
+
+
+ + +
+
+ +
+ +
-
- {/if} + {:else} + +
startEdit(dream)} + onkeydown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + startEdit(dream); + } + }} + oncontextmenu={(e) => handleItemContextMenu(e, dream)} + > + {#if dream.mood} + + {:else} + + {/if} + +
+
+ {dream.title || 'Traum ohne Titel'} + {#if dream.isLucid}{/if} + {#if dream.isRecurring}{/if} + {#if dream.isPinned}📌{/if} + {#if dream.isPrivate}🔒{/if} +
+ {#if dream.content} +

{dream.content.split('\n')[0]}

+ {/if} +
+ {formatDreamDate(dream.dreamDate)} + {#if dream.symbols.length > 0} + · + + {#each dream.symbols.slice(0, 3) as sym} + + {/each} + + {/if} +
+
+
+ {/if} + {/each} {/each} - {/each} - {#if filtered.length === 0 && dreams.length > 0} -

Keine Treffer

+ {#if filtered.length === 0 && dreams.length > 0} +

Keine Treffer

+ {/if} +
+ + {#if dreams.length === 0} +

Tippe oben, um deinen ersten Traum festzuhalten.

{/if} -
- {#if dreams.length === 0} -

Tippe oben, um deinen ersten Traum festzuhalten.

+ (ctxMenu = { ...ctxMenu, visible: false, dream: null })} + /> {/if} - - (ctxMenu = { ...ctxMenu, visible: false, dream: null })} - />
diff --git a/apps/mana/apps/web/src/lib/modules/dreams/views/SymbolsView.svelte b/apps/mana/apps/web/src/lib/modules/dreams/views/SymbolsView.svelte new file mode 100644 index 000000000..acb8dff1b --- /dev/null +++ b/apps/mana/apps/web/src/lib/modules/dreams/views/SymbolsView.svelte @@ -0,0 +1,150 @@ + + + +{#if selectedSymbolId} + (selectedSymbolId = null)} /> +{:else} +
+ {#if symbols.length > 5} + + {/if} + + {#if filtered.length === 0} +

+ {symbols.length === 0 + ? 'Noch keine Symbole. Füge Symbole zu deinen Träumen hinzu, um sie hier zu sehen.' + : 'Keine Treffer'} +

+ {:else} +
+ {#each filtered as sym (sym.id)} + + {/each} +
+ {/if} +
+{/if} + +