feat(analytics): add event tracking to remaining 12 module stores

Add analytics events to inventar, storage, memoro, mukke, presi,
moodlit, picture, calc, citycorners, and zitare stores. Also adds
new event helpers for calc, inventar, moodlit, and citycorners.

All 31 module store files now have analytics instrumentation,
up from 4 at the start of this session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-02 17:17:07 +02:00
parent 5a7bc5e10d
commit e7ae444b18
19 changed files with 116 additions and 3 deletions

View file

@ -3,6 +3,7 @@
*/
import { db } from '$lib/data/database';
import { CalcEvents } from '@manacore/shared-utils/analytics';
import type { LocalCalculation } from '../types';
import type { CreateCalculationInput } from '@calc/shared';
@ -17,6 +18,7 @@ export const calculationsStore = {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
CalcEvents.calculationAdded();
},
async deleteCalculation(id: string) {
@ -33,5 +35,6 @@ export const calculationsStore = {
await Promise.all(
active.map((c) => db.table('calculations').update(c.id, { deletedAt: now, updatedAt: now }))
);
CalcEvents.historyCleared();
},
};

View file

@ -3,6 +3,7 @@
*/
import { db } from '$lib/data/database';
import { CalcEvents } from '@manacore/shared-utils/analytics';
import type { LocalSavedFormula } from '../types';
import type { CreateFormulaInput, UpdateFormulaInput } from '@calc/shared';
@ -17,6 +18,7 @@ export const savedFormulasStore = {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
CalcEvents.formulaSaved();
},
async updateFormula(id: string, input: UpdateFormulaInput) {
@ -31,5 +33,6 @@ export const savedFormulasStore = {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
CalcEvents.formulaDeleted();
},
};

View file

@ -6,6 +6,7 @@
*/
import { db } from '$lib/data/database';
import { CityCornersEvents } from '@manacore/shared-utils/analytics';
import type { LocalFavorite } from '../types';
let loading = $state(false);
@ -30,6 +31,7 @@ export const favoritesStore = {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
CityCornersEvents.favoriteToggled(false);
} else {
const newFav: LocalFavorite = {
id: crypto.randomUUID(),
@ -38,6 +40,7 @@ export const favoritesStore = {
updatedAt: new Date().toISOString(),
};
await db.table('ccFavorites').add(newFav);
CityCornersEvents.favoriteToggled(true);
}
} catch (err) {
console.error('Failed to toggle favorite:', err);

View file

@ -8,6 +8,7 @@
import { invCategoryTable } from '../collections';
import { toCategory } from '../queries';
import type { LocalCategory } from '../types';
import { InventarEvents } from '@manacore/shared-utils/analytics';
export const categoriesStore = {
async create(data: { name: string; icon?: string; color?: string; parentId?: string }) {
@ -24,6 +25,7 @@ export const categoriesStore = {
order: siblings.length,
};
await invCategoryTable.add(newLocal);
InventarEvents.categoryCreated();
return toCategory(newLocal);
},
@ -47,5 +49,6 @@ export const categoriesStore = {
for (const deleteId of idsToDelete) {
await invCategoryTable.update(deleteId, { deletedAt: now, updatedAt: now });
}
InventarEvents.categoryDeleted();
},
};

View file

@ -8,6 +8,7 @@
import { invCollectionTable } from '../collections';
import { toCollection } from '../queries';
import type { LocalCollection } from '../types';
import { InventarEvents } from '@manacore/shared-utils/analytics';
export const collectionsStore = {
async create(data: {
@ -32,6 +33,7 @@ export const collectionsStore = {
itemCount: 0,
};
await invCollectionTable.add(newLocal);
InventarEvents.collectionCreated();
return toCollection(newLocal);
},
@ -50,6 +52,7 @@ export const collectionsStore = {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
InventarEvents.collectionDeleted();
},
async reorder(orderedIds: string[]) {

View file

@ -9,6 +9,7 @@ import { invItemTable } from '../collections';
import { toItem } from '../queries';
import type { LocalItem } from '../types';
import type { ItemStatus } from '../queries';
import { InventarEvents } from '@manacore/shared-utils/analytics';
export const itemsStore = {
async create(data: {
@ -45,6 +46,7 @@ export const itemsStore = {
order: collectionItems.length,
};
await invItemTable.add(newLocal);
InventarEvents.itemCreated();
return toItem(newLocal);
},
@ -69,6 +71,7 @@ export const itemsStore = {
...data,
updatedAt: new Date().toISOString(),
});
InventarEvents.itemUpdated();
},
async delete(id: string) {
@ -76,6 +79,7 @@ export const itemsStore = {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
InventarEvents.itemDeleted();
},
async deleteByCollection(collectionId: string) {

View file

@ -8,6 +8,7 @@
import { invLocationTable } from '../collections';
import { toLocation } from '../queries';
import type { LocalLocation } from '../types';
import { InventarEvents } from '@manacore/shared-utils/analytics';
function buildPath(locations: LocalLocation[], parentId?: string): string {
if (!parentId) return '';
@ -41,6 +42,7 @@ export const locationsStore = {
order: siblings.length,
};
await invLocationTable.add(newLocal);
InventarEvents.locationCreated();
return toLocation(newLocal);
},
@ -64,5 +66,6 @@ export const locationsStore = {
for (const deleteId of idsToDelete) {
await invLocationTable.update(deleteId, { deletedAt: now, updatedAt: now });
}
InventarEvents.locationDeleted();
},
};

View file

@ -7,6 +7,7 @@
import { memoryTable } from '../collections';
import { toMemory } from '../queries';
import { MemoroEvents } from '@manacore/shared-utils/analytics';
import type { LocalMemory } from '../types';
export const memoriesStore = {
@ -19,6 +20,7 @@ export const memoriesStore = {
content: data.content ?? null,
};
await memoryTable.add(newLocal);
MemoroEvents.memoCreated();
return toMemory(newLocal);
},
@ -34,5 +36,6 @@ export const memoriesStore = {
async delete(id: string) {
const now = new Date().toISOString();
await memoryTable.update(id, { deletedAt: now, updatedAt: now });
MemoroEvents.memoDeleted();
},
};

View file

@ -8,6 +8,7 @@
import { memoTable } from '../collections';
import { toMemo } from '../queries';
import { createArchiveOps } from '@manacore/shared-stores';
import { MemoroEvents } from '@manacore/shared-utils/analytics';
import type { LocalMemo } from '../types';
/** Archive/soft-delete ops for memos. */
@ -37,6 +38,7 @@ export const memosStore = {
language: data.language ?? null,
};
await memoTable.add(newLocal);
MemoroEvents.memoCreated();
return toMemo(newLocal);
},
@ -72,5 +74,8 @@ export const memosStore = {
},
/** Soft-delete a memo. */
delete: (id: string) => memoArchive.softDelete(id),
async delete(id: string) {
await memoArchive.softDelete(id);
MemoroEvents.memoDeleted();
},
};

View file

@ -3,6 +3,7 @@
*/
import { db } from '$lib/data/database';
import { MoodlitEvents } from '@manacore/shared-utils/analytics';
import type { LocalMood } from '../types';
import type { Mood, MoodSettings } from '../types';
@ -87,6 +88,7 @@ function createMoodsStore() {
favoriteIds = [...favoriteIds, moodId];
}
persist();
MoodlitEvents.moodFavorited();
},
updateSettings(updates: Partial<MoodSettings>) {
@ -105,6 +107,7 @@ function createMoodsStore() {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
MoodlitEvents.moodCreated();
},
async deleteMood(id: string) {
@ -112,6 +115,7 @@ function createMoodsStore() {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
MoodlitEvents.moodDeleted();
},
};
}

View file

@ -3,6 +3,7 @@
*/
import { db } from '$lib/data/database';
import { MoodlitEvents } from '@manacore/shared-utils/analytics';
import type { LocalSequence } from '../types';
import type { MoodSequence } from '../types';
@ -137,6 +138,7 @@ function createSequencesStore() {
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
MoodlitEvents.sequenceCreated();
},
async deleteSequence(id: string) {
@ -144,6 +146,7 @@ function createSequencesStore() {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
MoodlitEvents.sequenceDeleted();
},
};
}

View file

@ -6,6 +6,7 @@
*/
import { songTable } from '../collections';
import { MukkeEvents } from '@manacore/shared-utils/analytics';
import type { LocalSong } from '../types';
export const libraryStore = {
@ -13,10 +14,12 @@ export const libraryStore = {
async toggleFavorite(id: string) {
const local = await songTable.get(id);
if (local) {
const newState = !local.favorite;
await songTable.update(id, {
favorite: !local.favorite,
favorite: newState,
updatedAt: new Date().toISOString(),
});
MukkeEvents.songFavorited(newState);
}
},
@ -29,6 +32,7 @@ export const libraryStore = {
lastPlayedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
MukkeEvents.songPlayed();
}
},
@ -52,6 +56,7 @@ export const libraryStore = {
async delete(id: string) {
const now = new Date().toISOString();
await songTable.update(id, { deletedAt: now, updatedAt: now });
MukkeEvents.songDeleted();
},
/** Insert a song (e.g., after upload). */

View file

@ -7,6 +7,7 @@
import { mukkePlaylistTable, playlistSongTable } from '../collections';
import { toPlaylist } from '../queries';
import { MukkeEvents } from '@manacore/shared-utils/analytics';
import type { LocalPlaylist, LocalPlaylistSong } from '../types';
export const playlistsStore = {
@ -19,6 +20,7 @@ export const playlistsStore = {
coverArtPath: null,
};
await mukkePlaylistTable.add(newLocal);
MukkeEvents.playlistCreated();
return toPlaylist(newLocal);
},
@ -39,6 +41,7 @@ export const playlistsStore = {
for (const ps of allPS) {
await playlistSongTable.update(ps.id, { deletedAt: now, updatedAt: now });
}
MukkeEvents.playlistDeleted();
},
/** Add a song to a playlist. */

View file

@ -7,6 +7,7 @@
import { mukkeProjectTable } from '../collections';
import { toProject } from '../queries';
import { MukkeEvents } from '@manacore/shared-utils/analytics';
import type { LocalProject } from '../types';
export const projectsStore = {
@ -19,6 +20,7 @@ export const projectsStore = {
songId: data.songId ?? null,
};
await mukkeProjectTable.add(newLocal);
MukkeEvents.projectCreated();
return toProject(newLocal);
},
@ -34,5 +36,6 @@ export const projectsStore = {
async delete(id: string) {
const now = new Date().toISOString();
await mukkeProjectTable.update(id, { deletedAt: now, updatedAt: now });
MukkeEvents.projectDeleted();
},
};

View file

@ -8,6 +8,7 @@
import { db } from '$lib/data/database';
import { createArchiveOps, toggleField } from '@manacore/shared-stores';
import { PictureEvents, trackEvent } from '@manacore/shared-utils/analytics';
import type { LocalImage } from '../types';
const imageTable = () => db.table<LocalImage>('images');
@ -42,6 +43,7 @@ export const imagesStore = {
error = null;
try {
await toggleField(imageTable(), id, 'isFavorite');
PictureEvents.imageFavorited();
return { success: true };
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to toggle favorite';
@ -52,5 +54,8 @@ export const imagesStore = {
// Archive ops (delegated to shared factory)
archiveImage: (id: string) => imageArchive.archive(id),
restoreImage: (id: string) => imageArchive.unarchive(id),
deleteImage: (id: string) => imageArchive.softDelete(id),
async deleteImage(id: string) {
await imageArchive.softDelete(id);
trackEvent('image_deleted', { module: 'picture' });
},
};

View file

@ -7,6 +7,7 @@
import { presiDeckTable, slideTable } from '../collections';
import { toDeck, toSlide } from '../queries';
import { PresiEvents } from '@manacore/shared-utils/analytics';
import type {
LocalDeck,
LocalSlide,
@ -34,6 +35,7 @@ function createDecksStore() {
isPublic: false,
};
await presiDeckTable.add(newLocal);
PresiEvents.deckCreated();
return toDeck(newLocal);
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to create deck';
@ -75,6 +77,7 @@ function createDecksStore() {
}
// Soft-delete the deck
await presiDeckTable.update(id, { deletedAt: now, updatedAt: now });
PresiEvents.deckDeleted();
return true;
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to delete deck';
@ -96,6 +99,7 @@ function createDecksStore() {
content: dto.content,
};
await slideTable.add(newLocal);
PresiEvents.slideCreated();
return toSlide(newLocal);
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to create slide';
@ -127,6 +131,7 @@ function createDecksStore() {
try {
const now = new Date().toISOString();
await slideTable.update(id, { deletedAt: now, updatedAt: now });
PresiEvents.slideDeleted();
return true;
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to delete slide';

View file

@ -10,6 +10,7 @@ import { fileTable, storageFolderTable } from '../collections';
import { toFile, toFolder } from '../queries';
import type { StorageFile, StorageFolder } from '../queries';
import type { LocalFile, LocalFolder } from '../types';
import { StorageEvents } from '@manacore/shared-utils/analytics';
let viewMode = $state<'grid' | 'list'>('grid');
let selectedFileIds = $state<Set<string>>(new Set());
@ -134,6 +135,7 @@ export const filesStore = {
isFavorite: newFav,
updatedAt: new Date().toISOString(),
});
StorageEvents.fileFavorited(newFav);
return newFav;
}
return false;
@ -147,6 +149,7 @@ export const filesStore = {
isFavorite: newFav,
updatedAt: new Date().toISOString(),
});
StorageEvents.folderFavorited(newFav);
return newFav;
}
return false;
@ -157,6 +160,7 @@ export const filesStore = {
isDeleted: true,
updatedAt: new Date().toISOString(),
});
StorageEvents.fileDeleted();
},
async deleteFolder(id: string) {
@ -164,6 +168,7 @@ export const filesStore = {
isDeleted: true,
updatedAt: new Date().toISOString(),
});
StorageEvents.folderDeleted();
},
async restoreFile(id: string) {

View file

@ -5,6 +5,7 @@
*/
import { db } from '$lib/data/database';
import { ZitareEvents } from '@manacore/shared-utils/analytics';
import type { LocalQuoteList } from '../types';
import { toQuoteList, type QuoteList } from '../queries';
@ -28,6 +29,7 @@ export const listsStore = {
updatedAt: now,
};
await db.table<LocalQuoteList>('zitareLists').add(newLocal);
ZitareEvents.listCreated();
return toQuoteList(newLocal);
} catch {
return null;
@ -56,6 +58,7 @@ export const listsStore = {
deletedAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
});
ZitareEvents.listDeleted();
return true;
} catch {
return false;

View file

@ -166,6 +166,10 @@ const track = {
subscription: createModuleTracker('subscription'),
memoro: createModuleTracker('memoro'),
app: createModuleTracker('app'),
calc: createModuleTracker('calc'),
inventar: createModuleTracker('inventar'),
moodlit: createModuleTracker('moodlit'),
citycorners: createModuleTracker('citycorners'),
};
/**
@ -546,3 +550,46 @@ export const AppEvents = {
settingsOpened: () => track.app('settings_opened'),
shareClicked: (platform: string) => track.app('share_clicked', { platform }),
};
/**
* Calc App Events
*/
export const CalcEvents = {
calculationAdded: () => track.calc('calculation_added'),
historyCleared: () => track.calc('history_cleared'),
formulaSaved: () => track.calc('formula_saved'),
formulaDeleted: () => track.calc('formula_deleted'),
};
/**
* Inventar App Events
*/
export const InventarEvents = {
itemCreated: () => track.inventar('item_created'),
itemUpdated: () => track.inventar('item_updated'),
itemDeleted: () => track.inventar('item_deleted'),
collectionCreated: () => track.inventar('collection_created'),
collectionDeleted: () => track.inventar('collection_deleted'),
categoryCreated: () => track.inventar('category_created'),
categoryDeleted: () => track.inventar('category_deleted'),
locationCreated: () => track.inventar('location_created'),
locationDeleted: () => track.inventar('location_deleted'),
};
/**
* Moodlit App Events
*/
export const MoodlitEvents = {
moodCreated: () => track.moodlit('mood_created'),
moodDeleted: () => track.moodlit('mood_deleted'),
moodFavorited: () => track.moodlit('mood_favorited'),
sequenceCreated: () => track.moodlit('sequence_created'),
sequenceDeleted: () => track.moodlit('sequence_deleted'),
};
/**
* CityCorners App Events
*/
export const CityCornersEvents = {
favoriteToggled: (favorited: boolean) => track.citycorners('favorite_toggled', { favorited }),
};