From 8ece7d31b89b0d340f33335809b391493ed8b2ee Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 2 Apr 2026 17:10:30 +0200 Subject: [PATCH] feat(analytics): add event tracking to 7 core module stores Add analytics events to todo, calendar, contacts, chat, cards, and photos module stores. All mutations (create, update, delete, complete, favorite, archive) now fire the corresponding typed event helpers with automatic module context. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/modules/calendar/stores/events.svelte.ts | 4 ++++ .../web/src/lib/modules/cards/stores/cards.svelte.ts | 3 +++ .../web/src/lib/modules/cards/stores/decks.svelte.ts | 3 +++ .../lib/modules/chat/stores/conversations.svelte.ts | 3 +++ .../web/src/lib/modules/chat/stores/messages.svelte.ts | 2 ++ .../src/lib/modules/contacts/stores/contacts.svelte.ts | 10 +++++++++- .../web/src/lib/modules/photos/stores/albums.svelte.ts | 3 +++ .../web/src/lib/modules/photos/stores/photos.svelte.ts | 3 +++ .../web/src/lib/modules/todo/stores/tasks.svelte.ts | 5 +++++ 9 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/manacore/apps/web/src/lib/modules/calendar/stores/events.svelte.ts b/apps/manacore/apps/web/src/lib/modules/calendar/stores/events.svelte.ts index e7936bfc1..82ebb5fc8 100644 --- a/apps/manacore/apps/web/src/lib/modules/calendar/stores/events.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/calendar/stores/events.svelte.ts @@ -9,6 +9,7 @@ import { db } from '$lib/data/database'; import type { LocalEvent, CalendarEvent } from '../types'; import { toCalendarEvent } from '../queries'; +import { CalendarEvents } from '@manacore/shared-utils/analytics'; let error = $state(null); let draftEvent = $state(null); @@ -54,6 +55,7 @@ export const eventsStore = { }; await db.table('events').add(newLocal); + CalendarEvents.eventCreated(!!input.recurrenceRule); return { success: true, data: toCalendarEvent(newLocal) }; } catch (e) { error = e instanceof Error ? e.message : 'Failed to create event'; @@ -96,6 +98,7 @@ export const eventsStore = { await db.table('events').update(id, localData); const updated = await db.table('events').get(id); if (updated) { + CalendarEvents.eventUpdated(); return { success: true, data: toCalendarEvent(updated) }; } return { success: false, error: 'Event not found' }; @@ -115,6 +118,7 @@ export const eventsStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + CalendarEvents.eventDeleted(); return { success: true }; } catch (e) { error = e instanceof Error ? e.message : 'Failed to delete event'; diff --git a/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts b/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts index c7d8c314a..cac115ea9 100644 --- a/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/cards/stores/cards.svelte.ts @@ -5,6 +5,7 @@ * This store only handles writes to IndexedDB via the unified database. */ +import { CardsEvents } from '@manacore/shared-utils/analytics'; import { cardTable, cardDeckTable } from '../collections'; import { toCard } from '../queries'; import type { LocalCard, Card, CreateCardInput, UpdateCardInput } from '../types'; @@ -40,6 +41,7 @@ export const cardStore = { }); } + CardsEvents.cardCreated(); return toCard(newLocal); } catch (err: any) { error = err.message || 'Failed to create card'; @@ -72,6 +74,7 @@ export const cardStore = { try { const now = new Date().toISOString(); await cardTable.update(id, { deletedAt: now, updatedAt: now }); + CardsEvents.cardDeleted(); // Update deck card count if (deckId) { diff --git a/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts b/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts index acd95136c..0e37cc84e 100644 --- a/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/cards/stores/decks.svelte.ts @@ -5,6 +5,7 @@ * This store only handles writes to IndexedDB via the unified database. */ +import { CardsEvents } from '@manacore/shared-utils/analytics'; import { cardDeckTable, cardTable } from '../collections'; import { toDeck } from '../queries'; import type { LocalDeck } from '../types'; @@ -30,6 +31,7 @@ export const deckStore = { }; await cardDeckTable.add(newLocal); + CardsEvents.deckCreated(); return toDeck(newLocal); } catch (err: any) { error = err.message || 'Failed to create deck'; @@ -69,6 +71,7 @@ export const deckStore = { // Soft-delete the deck await cardDeckTable.update(id, { deletedAt: now, updatedAt: now }); + CardsEvents.deckDeleted(); } catch (err: any) { error = err.message || 'Failed to delete deck'; console.error('Delete deck error:', err); diff --git a/apps/manacore/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts b/apps/manacore/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts index 49c6b8ae9..6139d1892 100644 --- a/apps/manacore/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/chat/stores/conversations.svelte.ts @@ -8,6 +8,7 @@ import { conversationTable, messageTable } from '../collections'; import { toConversation } from '../queries'; import { createArchiveOps } from '@manacore/shared-stores'; +import { ChatEvents } from '@manacore/shared-utils/analytics'; import type { LocalConversation } from '../types'; /** Archive/soft-delete ops for conversations. */ @@ -37,6 +38,7 @@ export const conversationsStore = { isPinned: false, }; await conversationTable.add(newLocal); + ChatEvents.conversationCreated(); return toConversation(newLocal); }, @@ -85,5 +87,6 @@ export const conversationsStore = { for (const msg of msgs) { await messageTable.update(msg.id, { deletedAt: now, updatedAt: now }); } + ChatEvents.conversationDeleted(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/chat/stores/messages.svelte.ts b/apps/manacore/apps/web/src/lib/modules/chat/stores/messages.svelte.ts index 8a2f5dd97..8dd3253ca 100644 --- a/apps/manacore/apps/web/src/lib/modules/chat/stores/messages.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/chat/stores/messages.svelte.ts @@ -7,6 +7,7 @@ import { messageTable, conversationTable } from '../collections'; import { toMessage } from '../queries'; +import { ChatEvents } from '@manacore/shared-utils/analytics'; import type { LocalMessage } from '../types'; export const messagesStore = { @@ -23,6 +24,7 @@ export const messagesStore = { await conversationTable.update(conversationId, { updatedAt: new Date().toISOString(), }); + ChatEvents.messageSent(); return toMessage(newLocal); }, diff --git a/apps/manacore/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts b/apps/manacore/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts index 22a92e15b..61d68e7c7 100644 --- a/apps/manacore/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/contacts/stores/contacts.svelte.ts @@ -8,6 +8,7 @@ import { contactTable } from '../collections'; import { toContact } from '../queries'; import { createArchiveOps } from '@manacore/shared-stores'; +import { ContactsEvents } from '@manacore/shared-utils/analytics'; import type { LocalContact, Contact } from '../types'; /** Archive/soft-delete ops for contacts. */ @@ -42,6 +43,7 @@ export const contactsStore = { }; await contactTable.add(newLocal); + ContactsEvents.contactCreated(); return toContact(newLocal); }, @@ -75,6 +77,7 @@ export const contactsStore = { ...updateData, updatedAt: new Date().toISOString(), }); + ContactsEvents.contactUpdated(); }, async deleteContact(id: string) { @@ -82,6 +85,7 @@ export const contactsStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + ContactsEvents.contactDeleted(); }, async toggleFavorite(id: string) { @@ -92,6 +96,7 @@ export const contactsStore = { isFavorite: !local.isFavorite, updatedAt: new Date().toISOString(), }); + ContactsEvents.contactFavorited(); }, async updateTagIds(id: string, tagIds: string[]) { @@ -101,5 +106,8 @@ export const contactsStore = { }); }, - toggleArchive: (id: string) => contactArchive.toggleArchive(id), + toggleArchive: async (id: string) => { + await contactArchive.toggleArchive(id); + ContactsEvents.contactArchived(); + }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/photos/stores/albums.svelte.ts b/apps/manacore/apps/web/src/lib/modules/photos/stores/albums.svelte.ts index 744cb56f2..9ecd0e70d 100644 --- a/apps/manacore/apps/web/src/lib/modules/photos/stores/albums.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/photos/stores/albums.svelte.ts @@ -5,6 +5,7 @@ * This store handles mutations (create, update, delete, add/remove items). */ +import { PhotosEvents } from '@manacore/shared-utils/analytics'; import { db } from '$lib/data/database'; import type { LocalAlbum, LocalAlbumItem, Album } from '../types'; import { toAlbum } from '../queries'; @@ -25,6 +26,7 @@ export const albumMutations = { updatedAt: now, }; await db.table('albums').add(newLocal); + PhotosEvents.albumCreated(); return toAlbum(newLocal); } catch (e) { console.error('Failed to create album:', e); @@ -59,6 +61,7 @@ export const albumMutations = { await db.table('albumItems').update(item.id, { deletedAt: now, updatedAt: now }); } await db.table('albums').update(id, { deletedAt: now, updatedAt: now }); + PhotosEvents.albumDeleted(); return true; } catch (e) { console.error('Failed to delete album:', e); diff --git a/apps/manacore/apps/web/src/lib/modules/photos/stores/photos.svelte.ts b/apps/manacore/apps/web/src/lib/modules/photos/stores/photos.svelte.ts index e4342866b..0d48b0268 100644 --- a/apps/manacore/apps/web/src/lib/modules/photos/stores/photos.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/photos/stores/photos.svelte.ts @@ -5,6 +5,7 @@ * Favorites are local-first via Dexie. */ +import { PhotosEvents } from '@manacore/shared-utils/analytics'; import { db } from '$lib/data/database'; import type { LocalFavorite, Photo, PhotoFilters, PhotoStats } from '../types'; @@ -156,6 +157,7 @@ export const photoStore = { // Update server-fetched photos in-memory for immediate UI feedback const isFav = !fav; + PhotosEvents.photoFavorited(isFav); photos = photos.map((p) => (p.id === mediaId ? { ...p, isFavorited: isFav } : p)); if (selectedPhoto?.id === mediaId) { selectedPhoto = { ...selectedPhoto, isFavorited: isFav }; @@ -179,6 +181,7 @@ export const photoStore = { photos = photos.filter((p) => p.id !== mediaId); if (selectedPhoto?.id === mediaId) selectedPhoto = null; + PhotosEvents.photoDeleted(); return true; } catch (e) { error = e instanceof Error ? e.message : 'Failed to delete photo'; diff --git a/apps/manacore/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts b/apps/manacore/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts index e0101c528..11a814a2f 100644 --- a/apps/manacore/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/todo/stores/tasks.svelte.ts @@ -8,6 +8,7 @@ import { taskTable } from '../collections'; import { toTask } from '../queries'; import type { LocalTask, TaskPriority, Subtask } from '../types'; +import { TodoEvents } from '@manacore/shared-utils/analytics'; export const tasksStore = { async createTask(data: { @@ -42,6 +43,7 @@ export const tasksStore = { } await taskTable.add(newLocal); + TodoEvents.taskCreated(!!data.dueDate); return toTask(newLocal); }, @@ -67,6 +69,7 @@ export const tasksStore = { ...data, updatedAt: new Date().toISOString(), }); + TodoEvents.taskEdited(); }, async deleteTask(id: string) { @@ -74,6 +77,7 @@ export const tasksStore = { deletedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + TodoEvents.taskDeleted(); }, async completeTask(id: string) { @@ -82,6 +86,7 @@ export const tasksStore = { completedAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }); + TodoEvents.taskCompleted(); }, async uncompleteTask(id: string) {