diff --git a/apps/mana/apps/web/src/lib/data/database.ts b/apps/mana/apps/web/src/lib/data/database.ts
index a731a31b0..a9a7720d2 100644
--- a/apps/mana/apps/web/src/lib/data/database.ts
+++ b/apps/mana/apps/web/src/lib/data/database.ts
@@ -563,6 +563,17 @@ db.version(23).stores({
userContext: 'id',
});
+// v24 — Wishes module: wishlists with price tracking.
+// wishesItems indexes [listId+order] for the per-list view,
+// status for the active/fulfilled filter tabs.
+// wishesPriceChecks indexes [wishId+checkedAt] for the per-wish
+// price history timeline (reverse range scan).
+db.version(24).stores({
+ wishesItems: 'id, listId, status, priority, category, [listId+order], [status+order]',
+ wishesLists: 'id, order, isArchived',
+ wishesPriceChecks: 'id, wishId, checkedAt, [wishId+checkedAt]',
+});
+
// v25 — Wetter module: saved locations and user preferences.
db.version(25).stores({
wetterLocations: 'id, isDefault, order',
diff --git a/apps/mana/apps/web/src/lib/data/seed-registry.ts b/apps/mana/apps/web/src/lib/data/seed-registry.ts
index 88101b892..56da92c86 100644
--- a/apps/mana/apps/web/src/lib/data/seed-registry.ts
+++ b/apps/mana/apps/web/src/lib/data/seed-registry.ts
@@ -35,6 +35,7 @@ import { MEDITATE_GUEST_SEED } from '$lib/modules/meditate/collections';
import { SLEEP_GUEST_SEED } from '$lib/modules/sleep/collections';
import { MOOD_GUEST_SEED } from '$lib/modules/mood/collections';
import { QUIZ_GUEST_SEED } from '$lib/modules/quiz/collections';
+import { WISHES_GUEST_SEED } from '$lib/modules/wishes/collections';
/**
* Flat list of { tableName, rows } entries. Only modules with non-empty
@@ -74,6 +75,7 @@ register(MEDITATE_GUEST_SEED);
register(SLEEP_GUEST_SEED);
register(MOOD_GUEST_SEED);
register(QUIZ_GUEST_SEED);
+register(WISHES_GUEST_SEED);
/**
* Seed all module guest data into empty tables. Idempotent: tables
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte b/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte
new file mode 100644
index 000000000..ab784cb22
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/ListView.svelte
@@ -0,0 +1,341 @@
+
+
+
+ Wünsche - Mana
+
+
+
+
+
+
+ {activeCount} offen · {fulfilledCount} erfüllt
+ {#if totalCost > 0}
+ · ~{totalCost.toLocaleString('de-DE')} €
+ {/if}
+
+
+
+
+
+ {#if showAdd}
+
+ {/if}
+
+
+
+ {#each [{ key: 'active', label: 'Offen', icon: Star }, { key: 'fulfilled', label: 'Erfüllt', icon: Check }, { key: 'all', label: 'Alle', icon: Archive }] as tab (tab.key)}
+
+ {/each}
+
+
+
+
+ {#if lists.length > 0}
+
+ {/if}
+ {#each lists as list (list.id)}
+
+
+
+
+ {/each}
+ {#if showNewList}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+
+
+
+
+ {#if filtered.length === 0}
+
+
+
+ {filter === 'active' ? 'Noch keine Wünsche' : 'Keine Wünsche in dieser Ansicht'}
+
+
+ {:else}
+
+ {#each filtered as wish (wish.id)}
+
+ {/each}
+
+ {/if}
+
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/collections.ts b/apps/mana/apps/web/src/lib/modules/wishes/collections.ts
new file mode 100644
index 000000000..3154e9c71
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/collections.ts
@@ -0,0 +1,67 @@
+/**
+ * Wishes module — collection accessors and guest seed data.
+ *
+ * Uses prefixed table names in the unified DB: wishesItems, wishesLists, wishesPriceChecks.
+ */
+
+import { db } from '$lib/data/database';
+import type { LocalWish, LocalWishList, LocalPriceCheck } from './types';
+
+// ─── Collection Accessors ──────────────────────────────────
+
+export const wishTable = db.table('wishesItems');
+export const listTable = db.table('wishesLists');
+export const priceCheckTable = db.table('wishesPriceChecks');
+
+// ─── Guest Seed ────────────────────────────────────────────
+
+const DEMO_LIST_ID = 'demo-birthday-wishes';
+
+export const WISHES_GUEST_SEED = {
+ wishesLists: [
+ {
+ id: DEMO_LIST_ID,
+ name: 'Geburtstagsgeschenke',
+ description: 'Ideen für Geburtstag',
+ icon: '🎁',
+ color: '#ec4899',
+ isArchived: false,
+ order: 0,
+ },
+ ],
+ wishesItems: [
+ {
+ id: 'demo-wish-1',
+ title: 'Neue Kopfhörer',
+ description: 'Wireless, aktive Geräuschunterdrückung',
+ listId: DEMO_LIST_ID,
+ priority: 'medium' as const,
+ status: 'active' as const,
+ targetPrice: 150,
+ currency: 'EUR',
+ productUrls: [],
+ imageUrl: null,
+ category: 'Technik',
+ tags: ['audio', 'tech'],
+ notes: [],
+ order: 0,
+ },
+ {
+ id: 'demo-wish-2',
+ title: 'Kochbuch — vegetarische Rezepte',
+ description: 'Am liebsten asiatisch-vegetarisch',
+ listId: DEMO_LIST_ID,
+ priority: 'low' as const,
+ status: 'active' as const,
+ targetPrice: 25,
+ currency: 'EUR',
+ productUrls: [],
+ imageUrl: null,
+ category: 'Bücher',
+ tags: ['books', 'cooking'],
+ notes: [],
+ order: 1,
+ },
+ ],
+ wishesPriceChecks: [] as Record[],
+};
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/index.ts b/apps/mana/apps/web/src/lib/modules/wishes/index.ts
new file mode 100644
index 000000000..f4d1a75e8
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/index.ts
@@ -0,0 +1,38 @@
+/**
+ * Wishes module — barrel exports.
+ */
+
+// Stores
+export { wishesStore } from './stores/wishes.svelte';
+export { listsStore } from './stores/lists.svelte';
+export { priceChecksStore } from './stores/price-checks.svelte';
+
+// Queries
+export {
+ useAllWishes,
+ useAllLists,
+ usePriceChecks,
+ toWish,
+ toWishList,
+ toPriceCheck,
+ filterByStatus,
+ filterByPriority,
+ filterByList,
+ searchWishes,
+ getTotalEstimatedCost,
+} from './queries';
+
+// Collections
+export { wishTable, listTable, priceCheckTable, WISHES_GUEST_SEED } from './collections';
+
+// Types
+export type {
+ LocalWish,
+ LocalWishList,
+ LocalPriceCheck,
+ Wish,
+ WishList,
+ PriceCheck,
+ WishStatus,
+ WishPriority,
+} from './types';
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts b/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts
new file mode 100644
index 000000000..18ca22346
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/module.config.ts
@@ -0,0 +1,10 @@
+import type { ModuleConfig } from '$lib/data/module-registry';
+
+export const wishesModuleConfig: ModuleConfig = {
+ appId: 'wishes',
+ tables: [
+ { name: 'wishesItems', syncName: 'items' },
+ { name: 'wishesLists', syncName: 'lists' },
+ { name: 'wishesPriceChecks', syncName: 'priceChecks' },
+ ],
+};
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/queries.ts b/apps/mana/apps/web/src/lib/modules/wishes/queries.ts
new file mode 100644
index 000000000..6bcd667f3
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/queries.ts
@@ -0,0 +1,129 @@
+/**
+ * Reactive queries & pure helpers for Wishes — uses Dexie liveQuery on the unified DB.
+ */
+
+import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
+import { db } from '$lib/data/database';
+import { decryptRecords } from '$lib/data/crypto';
+import type {
+ LocalWish,
+ LocalWishList,
+ LocalPriceCheck,
+ Wish,
+ WishList,
+ PriceCheck,
+} from './types';
+
+// ─── Type Converters ───────────────────────────────────────
+
+export function toWish(local: LocalWish): Wish {
+ return {
+ id: local.id,
+ title: local.title,
+ description: local.description ?? null,
+ listId: local.listId ?? null,
+ priority: local.priority,
+ targetPrice: local.targetPrice ?? null,
+ currency: local.currency ?? null,
+ productUrls: local.productUrls ?? [],
+ imageUrl: local.imageUrl ?? null,
+ category: local.category ?? null,
+ status: local.status,
+ tags: local.tags ?? [],
+ notes: local.notes ?? [],
+ order: local.order,
+ createdAt: local.createdAt ?? new Date().toISOString(),
+ updatedAt: local.updatedAt ?? new Date().toISOString(),
+ };
+}
+
+export function toWishList(local: LocalWishList): WishList {
+ return {
+ id: local.id,
+ name: local.name,
+ description: local.description ?? null,
+ icon: local.icon ?? null,
+ color: local.color ?? null,
+ isArchived: local.isArchived,
+ order: local.order,
+ createdAt: local.createdAt ?? new Date().toISOString(),
+ updatedAt: local.updatedAt ?? new Date().toISOString(),
+ };
+}
+
+export function toPriceCheck(local: LocalPriceCheck): PriceCheck {
+ return {
+ id: local.id,
+ wishId: local.wishId,
+ url: local.url,
+ price: local.price,
+ currency: local.currency,
+ available: local.available,
+ checkedAt: local.checkedAt,
+ createdAt: local.createdAt ?? new Date().toISOString(),
+ };
+}
+
+// ─── Live Queries ──────────────────────────────────────────
+
+export function useAllWishes() {
+ return useLiveQueryWithDefault(async () => {
+ const locals = await db.table('wishesItems').orderBy('order').toArray();
+ const visible = locals.filter((w) => !w.deletedAt);
+ const decrypted = await decryptRecords('wishesItems', visible);
+ return decrypted.map(toWish);
+ }, [] as Wish[]);
+}
+
+export function useAllLists() {
+ return useLiveQueryWithDefault(async () => {
+ const locals = await db.table('wishesLists').orderBy('order').toArray();
+ const visible = locals.filter((l) => !l.deletedAt && !l.isArchived);
+ return visible.map(toWishList);
+ }, [] as WishList[]);
+}
+
+export function usePriceChecks(wishId: string) {
+ return useLiveQueryWithDefault(async () => {
+ const locals = await db
+ .table('wishesPriceChecks')
+ .where('wishId')
+ .equals(wishId)
+ .toArray();
+ const visible = locals.filter((p) => !p.deletedAt);
+ return visible
+ .sort((a, b) => new Date(b.checkedAt).getTime() - new Date(a.checkedAt).getTime())
+ .map(toPriceCheck);
+ }, [] as PriceCheck[]);
+}
+
+// ─── Pure Filter Functions ────────────────────────────────
+
+export function filterByStatus(wishes: Wish[], status: string): Wish[] {
+ return wishes.filter((w) => w.status === status);
+}
+
+export function filterByPriority(wishes: Wish[], priority: string): Wish[] {
+ return wishes.filter((w) => w.priority === priority);
+}
+
+export function filterByList(wishes: Wish[], listId: string | null): Wish[] {
+ if (listId === null) return wishes.filter((w) => !w.listId);
+ return wishes.filter((w) => w.listId === listId);
+}
+
+export function searchWishes(wishes: Wish[], query: string): Wish[] {
+ if (!query.trim()) return wishes;
+ const q = query.toLowerCase().trim();
+ return wishes.filter(
+ (w) =>
+ w.title.toLowerCase().includes(q) ||
+ w.description?.toLowerCase().includes(q) ||
+ w.category?.toLowerCase().includes(q) ||
+ w.tags.some((t) => t.toLowerCase().includes(q))
+ );
+}
+
+export function getTotalEstimatedCost(wishes: Wish[]): number {
+ return wishes.reduce((sum, w) => sum + (w.targetPrice ?? 0), 0);
+}
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts
new file mode 100644
index 000000000..e98fc1943
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/lists.svelte.ts
@@ -0,0 +1,57 @@
+/**
+ * Wish Lists Store — Mutations Only
+ */
+
+import { listTable } from '../collections';
+import { toWishList } from '../queries';
+import type { LocalWishList } from '../types';
+
+export const listsStore = {
+ async create(data: { name: string; description?: string; icon?: string; color?: string }) {
+ const all = await listTable.toArray();
+ const active = all.filter((l) => !l.deletedAt);
+
+ const newLocal: LocalWishList = {
+ id: crypto.randomUUID(),
+ name: data.name,
+ description: data.description ?? null,
+ icon: data.icon ?? null,
+ color: data.color ?? null,
+ isArchived: false,
+ order: active.length,
+ };
+ await listTable.add(newLocal);
+ return toWishList(newLocal);
+ },
+
+ async update(
+ id: string,
+ data: Partial>
+ ) {
+ await listTable.update(id, {
+ ...data,
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async archive(id: string) {
+ await listTable.update(id, {
+ isArchived: true,
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async delete(id: string) {
+ await listTable.update(id, {
+ deletedAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async reorder(orderedIds: string[]) {
+ const now = new Date().toISOString();
+ for (let i = 0; i < orderedIds.length; i++) {
+ await listTable.update(orderedIds[i], { order: i, updatedAt: now });
+ }
+ },
+};
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts
new file mode 100644
index 000000000..707e2a822
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/price-checks.svelte.ts
@@ -0,0 +1,29 @@
+/**
+ * Price Checks Store — Mutations Only
+ */
+
+import { priceCheckTable } from '../collections';
+import type { LocalPriceCheck } from '../types';
+
+export const priceChecksStore = {
+ async record(data: {
+ wishId: string;
+ url: string;
+ price: number;
+ currency: string;
+ available?: boolean;
+ }) {
+ const now = new Date().toISOString();
+ const newLocal: LocalPriceCheck = {
+ id: crypto.randomUUID(),
+ wishId: data.wishId,
+ url: data.url,
+ price: data.price,
+ currency: data.currency,
+ available: data.available ?? true,
+ checkedAt: now,
+ };
+ await priceCheckTable.add(newLocal);
+ return newLocal;
+ },
+};
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts b/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts
new file mode 100644
index 000000000..02f8a9e5f
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/stores/wishes.svelte.ts
@@ -0,0 +1,142 @@
+/**
+ * Wishes Store — Mutations Only
+ *
+ * All reads are handled by liveQuery hooks in queries.ts.
+ */
+
+import { wishTable } from '../collections';
+import { toWish } from '../queries';
+import type { LocalWish, WishPriority } from '../types';
+import { encryptRecord } from '$lib/data/crypto';
+import { emitDomainEvent } from '$lib/data/events';
+
+export const wishesStore = {
+ async create(data: {
+ title: string;
+ description?: string;
+ listId?: string | null;
+ priority?: WishPriority;
+ targetPrice?: number;
+ currency?: string;
+ productUrls?: string[];
+ imageUrl?: string;
+ category?: string;
+ tags?: string[];
+ }) {
+ const existing = await wishTable.toArray();
+ const active = existing.filter((w) => !w.deletedAt);
+
+ const newLocal: LocalWish = {
+ id: crypto.randomUUID(),
+ title: data.title,
+ description: data.description ?? null,
+ listId: data.listId ?? null,
+ priority: data.priority ?? 'medium',
+ targetPrice: data.targetPrice ?? null,
+ currency: data.currency ?? 'EUR',
+ productUrls: data.productUrls ?? [],
+ imageUrl: data.imageUrl ?? null,
+ category: data.category ?? null,
+ status: 'active',
+ tags: data.tags ?? [],
+ notes: [],
+ order: active.length,
+ };
+
+ const plaintextSnapshot = toWish(newLocal);
+ await encryptRecord('wishesItems', newLocal);
+ await wishTable.add(newLocal);
+ emitDomainEvent('WishCreated', 'wishes', 'wishesItems', newLocal.id, {
+ wishId: newLocal.id,
+ title: data.title,
+ listId: data.listId,
+ });
+ return plaintextSnapshot;
+ },
+
+ async update(
+ id: string,
+ data: Partial<
+ Pick<
+ LocalWish,
+ | 'title'
+ | 'description'
+ | 'priority'
+ | 'status'
+ | 'targetPrice'
+ | 'currency'
+ | 'productUrls'
+ | 'imageUrl'
+ | 'category'
+ | 'tags'
+ | 'listId'
+ >
+ >
+ ) {
+ const diff: Partial = {
+ ...data,
+ updatedAt: new Date().toISOString(),
+ };
+ await encryptRecord('wishesItems', diff);
+ await wishTable.update(id, diff);
+ },
+
+ async fulfill(id: string) {
+ await wishTable.update(id, {
+ status: 'fulfilled',
+ updatedAt: new Date().toISOString(),
+ });
+ emitDomainEvent('WishFulfilled', 'wishes', 'wishesItems', id, { wishId: id });
+ },
+
+ async archive(id: string) {
+ await wishTable.update(id, {
+ status: 'archived',
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async delete(id: string) {
+ await wishTable.update(id, {
+ deletedAt: new Date().toISOString(),
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async addNote(wishId: string, content: string) {
+ const wish = await wishTable.get(wishId);
+ if (!wish) return;
+ const now = new Date().toISOString();
+ const note = { id: crypto.randomUUID(), content, createdAt: now };
+ await wishTable.update(wishId, {
+ notes: [...wish.notes, note],
+ updatedAt: now,
+ });
+ },
+
+ async addProductUrl(wishId: string, url: string) {
+ const wish = await wishTable.get(wishId);
+ if (!wish) return;
+ if (wish.productUrls.includes(url)) return;
+ await wishTable.update(wishId, {
+ productUrls: [...wish.productUrls, url],
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async removeProductUrl(wishId: string, url: string) {
+ const wish = await wishTable.get(wishId);
+ if (!wish) return;
+ await wishTable.update(wishId, {
+ productUrls: wish.productUrls.filter((u) => u !== url),
+ updatedAt: new Date().toISOString(),
+ });
+ },
+
+ async reorder(orderedIds: string[]) {
+ const now = new Date().toISOString();
+ for (let i = 0; i < orderedIds.length; i++) {
+ await wishTable.update(orderedIds[i], { order: i, updatedAt: now });
+ }
+ },
+};
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/tools.ts b/apps/mana/apps/web/src/lib/modules/wishes/tools.ts
new file mode 100644
index 000000000..7c5b24378
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/tools.ts
@@ -0,0 +1,109 @@
+/**
+ * Wishes Tools — LLM-accessible operations for the wishes module.
+ */
+
+import type { ModuleTool } from '$lib/data/tools/types';
+
+export const wishesTools: ModuleTool[] = [
+ {
+ name: 'create_wish',
+ module: 'wishes',
+ description:
+ 'Erstellt einen neuen Wunsch auf der Wunschliste. Nutze dies wenn der Nutzer sich etwas wünscht oder eine Geschenkidee hat.',
+ parameters: [
+ { name: 'title', type: 'string', description: 'Wunsch-Titel', required: true },
+ { name: 'description', type: 'string', description: 'Beschreibung', required: false },
+ {
+ name: 'priority',
+ type: 'string',
+ description: 'Priorität',
+ required: false,
+ enum: ['low', 'medium', 'high'],
+ },
+ {
+ name: 'targetPrice',
+ type: 'number',
+ description: 'Zielpreis / Budget in EUR',
+ required: false,
+ },
+ {
+ name: 'category',
+ type: 'string',
+ description: 'Kategorie (z.B. Technik, Bücher)',
+ required: false,
+ },
+ ],
+ async execute(params) {
+ const { wishesStore } = await import('./stores/wishes.svelte');
+ const wish = await wishesStore.create({
+ title: params.title as string,
+ description: params.description as string | undefined,
+ priority: (params.priority as 'low' | 'medium' | 'high') ?? undefined,
+ targetPrice: params.targetPrice as number | undefined,
+ category: params.category as string | undefined,
+ });
+ return { success: true, data: wish, message: `Wunsch "${wish.title}" erstellt` };
+ },
+ },
+ {
+ name: 'list_wishes',
+ module: 'wishes',
+ description:
+ 'Listet alle Wünsche auf der Wunschliste. Nutze dies wenn der Nutzer nach seinen Wünschen fragt.',
+ parameters: [
+ {
+ name: 'filter',
+ type: 'string',
+ description: 'Nach Status filtern',
+ required: false,
+ enum: ['active', 'fulfilled', 'all'],
+ },
+ ],
+ async execute(params) {
+ const { wishTable } = await import('./collections');
+ const { toWish } = await import('./queries');
+ const { decryptRecords } = await import('$lib/data/crypto');
+ const all = await wishTable.toArray();
+ const active = all.filter((w) => !w.deletedAt);
+ const decrypted = await decryptRecords('wishesItems', active);
+ const wishes = decrypted.map(toWish);
+
+ const filter = (params.filter as string) ?? 'active';
+ let filtered = wishes;
+ if (filter === 'active') filtered = wishes.filter((w) => w.status === 'active');
+ else if (filter === 'fulfilled') filtered = wishes.filter((w) => w.status === 'fulfilled');
+
+ const list = filtered.map((w) => ({
+ id: w.id,
+ title: w.title,
+ priority: w.priority,
+ targetPrice: w.targetPrice,
+ currency: w.currency,
+ category: w.category,
+ status: w.status,
+ }));
+
+ return {
+ success: true,
+ data: list,
+ message:
+ list.length === 0
+ ? `Keine ${filter === 'all' ? '' : filter + 'n'} Wünsche`
+ : `${list.length} Wünsche gefunden`,
+ };
+ },
+ },
+ {
+ name: 'fulfill_wish',
+ module: 'wishes',
+ description: 'Markiert einen Wunsch als erfüllt.',
+ parameters: [
+ { name: 'wishId', type: 'string', description: 'ID des Wunsches', required: true },
+ ],
+ async execute(params) {
+ const { wishesStore } = await import('./stores/wishes.svelte');
+ await wishesStore.fulfill(params.wishId as string);
+ return { success: true, message: 'Wunsch als erfüllt markiert' };
+ },
+ },
+];
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/types.ts b/apps/mana/apps/web/src/lib/modules/wishes/types.ts
new file mode 100644
index 000000000..f0230b15b
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/types.ts
@@ -0,0 +1,86 @@
+/**
+ * Wishes module types for the unified app.
+ */
+
+import type { BaseRecord } from '@mana/local-store';
+
+export interface LocalWish extends BaseRecord {
+ title: string;
+ description?: string | null;
+ listId?: string | null;
+ priority: 'low' | 'medium' | 'high';
+ targetPrice?: number | null;
+ currency?: string | null;
+ productUrls: string[];
+ imageUrl?: string | null;
+ category?: string | null;
+ status: 'active' | 'fulfilled' | 'archived';
+ tags: string[];
+ notes: Array<{ id: string; content: string; createdAt: string }>;
+ order: number;
+}
+
+export interface LocalWishList extends BaseRecord {
+ name: string;
+ description?: string | null;
+ icon?: string | null;
+ color?: string | null;
+ isArchived: boolean;
+ order: number;
+}
+
+export interface LocalPriceCheck extends BaseRecord {
+ wishId: string;
+ url: string;
+ price: number;
+ currency: string;
+ available: boolean;
+ checkedAt: string;
+}
+
+// ─── Public Types (post-decryption, used in UI) ───────────
+
+export interface Wish {
+ id: string;
+ title: string;
+ description?: string | null;
+ listId?: string | null;
+ priority: 'low' | 'medium' | 'high';
+ targetPrice?: number | null;
+ currency?: string | null;
+ productUrls: string[];
+ imageUrl?: string | null;
+ category?: string | null;
+ status: 'active' | 'fulfilled' | 'archived';
+ tags: string[];
+ notes: Array<{ id: string; content: string; createdAt: string }>;
+ order: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface WishList {
+ id: string;
+ name: string;
+ description?: string | null;
+ icon?: string | null;
+ color?: string | null;
+ isArchived: boolean;
+ order: number;
+ createdAt: string;
+ updatedAt: string;
+}
+
+export interface PriceCheck {
+ id: string;
+ wishId: string;
+ url: string;
+ price: number;
+ currency: string;
+ available: boolean;
+ checkedAt: string;
+ createdAt: string;
+}
+
+export type WishStatus = Wish['status'];
+export type WishPriority = Wish['priority'];
diff --git a/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte
new file mode 100644
index 000000000..cb0ea646c
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/modules/wishes/views/DetailView.svelte
@@ -0,0 +1,373 @@
+
+
+
+ {wish?.title ?? 'Wunsch'} - Wünsche - Mana
+
+
+{#if !wish}
+
+
Wunsch nicht gefunden
+
+{:else}
+
+
+
+
+
+ {#if wish.status === 'active'}
+
+ {/if}
+
+
+
+
+
+ {#if editing}
+
+ {:else}
+
+ {/if}
+
+
+
+
+
+ Produkt-Links ({wish.productUrls.length})
+
+
+ {#if wish.productUrls.length > 0}
+
+ {#each wish.productUrls as url}
+ -
+
+ {url}
+
+
+
+ {/each}
+
+ {/if}
+
+
+
+
+
+ {#if priceChecks.value.length > 0}
+
+
Preisverlauf
+
+ {#each priceChecks.value.slice(0, 10) as check (check.id)}
+
+
+ {new Date(check.checkedAt).toLocaleDateString('de-DE')}
+
+
+ {check.price.toLocaleString('de-DE')}
+ {check.currency}
+
+
+ {/each}
+
+
+ {/if}
+
+
+
+
+ Notizen ({wish.notes.length})
+
+
+ {#if wish.notes.length > 0}
+
+ {/if}
+
+
+
+
+
+ {#if wish.tags.length > 0}
+
+ {#each wish.tags as tag}
+
+ {tag}
+
+ {/each}
+
+ {/if}
+
+{/if}
diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte
new file mode 100644
index 000000000..ae9c9d035
--- /dev/null
+++ b/apps/mana/apps/web/src/routes/(app)/wishes/+layout.svelte
@@ -0,0 +1,7 @@
+
+
+{@render children()}
diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte
new file mode 100644
index 000000000..7707c1aac
--- /dev/null
+++ b/apps/mana/apps/web/src/routes/(app)/wishes/+page.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte
new file mode 100644
index 000000000..7f6ecdb83
--- /dev/null
+++ b/apps/mana/apps/web/src/routes/(app)/wishes/[id]/+page.svelte
@@ -0,0 +1,5 @@
+
+
+
diff --git a/packages/shared-ui/src/dnd/types.ts b/packages/shared-ui/src/dnd/types.ts
index d4be73af4..7a1a4b8e2 100644
--- a/packages/shared-ui/src/dnd/types.ts
+++ b/packages/shared-ui/src/dnd/types.ts
@@ -26,7 +26,8 @@ export type DragType =
| 'place'
| 'dream'
| 'journal-entry'
- | 'first';
+ | 'first'
+ | 'wish';
export interface DragPayload> {
type: DragType;