diff --git a/apps/manacore/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts b/apps/manacore/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts index 3033368dd..0a68afb90 100644 --- a/apps/manacore/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/calc/stores/calculations.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts b/apps/manacore/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts index 58fcc2db0..0c3aa7953 100644 --- a/apps/manacore/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/calc/stores/saved-formulas.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts b/apps/manacore/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts index fb33a490e..70af0bee1 100644 --- a/apps/manacore/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/citycorners/stores/favorites.svelte.ts @@ -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); diff --git a/apps/manacore/apps/web/src/lib/modules/inventar/stores/categories.svelte.ts b/apps/manacore/apps/web/src/lib/modules/inventar/stores/categories.svelte.ts index 469cb9ac5..0e276ad4d 100644 --- a/apps/manacore/apps/web/src/lib/modules/inventar/stores/categories.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/inventar/stores/categories.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/inventar/stores/collections.svelte.ts b/apps/manacore/apps/web/src/lib/modules/inventar/stores/collections.svelte.ts index 09b652f42..d3f7be52e 100644 --- a/apps/manacore/apps/web/src/lib/modules/inventar/stores/collections.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/inventar/stores/collections.svelte.ts @@ -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[]) { diff --git a/apps/manacore/apps/web/src/lib/modules/inventar/stores/items.svelte.ts b/apps/manacore/apps/web/src/lib/modules/inventar/stores/items.svelte.ts index facda3e0b..630550a74 100644 --- a/apps/manacore/apps/web/src/lib/modules/inventar/stores/items.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/inventar/stores/items.svelte.ts @@ -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) { diff --git a/apps/manacore/apps/web/src/lib/modules/inventar/stores/locations.svelte.ts b/apps/manacore/apps/web/src/lib/modules/inventar/stores/locations.svelte.ts index c359bab42..7e0831ec9 100644 --- a/apps/manacore/apps/web/src/lib/modules/inventar/stores/locations.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/inventar/stores/locations.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts b/apps/manacore/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts index 55f716822..700c29a45 100644 --- a/apps/manacore/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/memoro/stores/memories.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts b/apps/manacore/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts index 1df1cae1c..bd6d8d12d 100644 --- a/apps/manacore/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/memoro/stores/memos.svelte.ts @@ -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(); + }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts b/apps/manacore/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts index 4f5502c5a..2f93ad1c5 100644 --- a/apps/manacore/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/moodlit/stores/moods.svelte.ts @@ -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) { @@ -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(); }, }; } diff --git a/apps/manacore/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts b/apps/manacore/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts index 6fed5caef..8b011ac1a 100644 --- a/apps/manacore/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/moodlit/stores/sequences.svelte.ts @@ -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(); }, }; } diff --git a/apps/manacore/apps/web/src/lib/modules/mukke/stores/library.svelte.ts b/apps/manacore/apps/web/src/lib/modules/mukke/stores/library.svelte.ts index 5644917e2..8d547c5fc 100644 --- a/apps/manacore/apps/web/src/lib/modules/mukke/stores/library.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/mukke/stores/library.svelte.ts @@ -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). */ diff --git a/apps/manacore/apps/web/src/lib/modules/mukke/stores/playlists.svelte.ts b/apps/manacore/apps/web/src/lib/modules/mukke/stores/playlists.svelte.ts index 5e20a2893..742fb2bc9 100644 --- a/apps/manacore/apps/web/src/lib/modules/mukke/stores/playlists.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/mukke/stores/playlists.svelte.ts @@ -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. */ diff --git a/apps/manacore/apps/web/src/lib/modules/mukke/stores/projects.svelte.ts b/apps/manacore/apps/web/src/lib/modules/mukke/stores/projects.svelte.ts index 9f898629d..5c08fb459 100644 --- a/apps/manacore/apps/web/src/lib/modules/mukke/stores/projects.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/mukke/stores/projects.svelte.ts @@ -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(); }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/picture/stores/images.svelte.ts b/apps/manacore/apps/web/src/lib/modules/picture/stores/images.svelte.ts index 59368ea9b..15617129f 100644 --- a/apps/manacore/apps/web/src/lib/modules/picture/stores/images.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/picture/stores/images.svelte.ts @@ -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('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' }); + }, }; diff --git a/apps/manacore/apps/web/src/lib/modules/presi/stores/decks.svelte.ts b/apps/manacore/apps/web/src/lib/modules/presi/stores/decks.svelte.ts index 283a447ea..2ab42ee56 100644 --- a/apps/manacore/apps/web/src/lib/modules/presi/stores/decks.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/presi/stores/decks.svelte.ts @@ -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'; diff --git a/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts b/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts index 063b1b946..1b239d023 100644 --- a/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/storage/stores/files.svelte.ts @@ -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>(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) { diff --git a/apps/manacore/apps/web/src/lib/modules/zitare/stores/lists.svelte.ts b/apps/manacore/apps/web/src/lib/modules/zitare/stores/lists.svelte.ts index 41b26ba84..07020a8d6 100644 --- a/apps/manacore/apps/web/src/lib/modules/zitare/stores/lists.svelte.ts +++ b/apps/manacore/apps/web/src/lib/modules/zitare/stores/lists.svelte.ts @@ -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('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; diff --git a/packages/shared-utils/src/analytics.ts b/packages/shared-utils/src/analytics.ts index 06c93ffa4..93c05fa65 100644 --- a/packages/shared-utils/src/analytics.ts +++ b/packages/shared-utils/src/analytics.ts @@ -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 }), +};