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) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-02 17:10:30 +02:00
parent bfe11d91ed
commit 8ece7d31b8
9 changed files with 35 additions and 1 deletions

View file

@ -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<string | null>(null);
let draftEvent = $state<CalendarEvent | null>(null);
@ -54,6 +55,7 @@ export const eventsStore = {
};
await db.table<LocalEvent>('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<LocalEvent>('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';

View file

@ -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) {

View file

@ -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);

View file

@ -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();
},
};

View file

@ -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);
},

View file

@ -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();
},
};

View file

@ -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);

View file

@ -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';

View file

@ -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) {