feat(analytics): add Analytics Maturity metric to ManaScore and custom event tracking to 4 apps

Add new "Analytics Maturity" extended metric to ManaScore schema with 5 checks:
pageViewTracking, customEvents, authTracking, landingTracking, publicDashboard.
Populate analytics data across all 20 audit files. Document the metric in about.md.

Add app-specific Umami custom event helpers and integrate tracking into:
- ManaCore: 13 events (nav, onboarding, dashboard widgets, credits, settings)
- Presi: 12 events (deck/slide CRUD, presentation start/exit, sharing)
- Zitare: 9 events (quotes, favorites, categories, search, lists, language)
- Mukke: 12 events (upload, library, playlists, projects, editor export)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-03-25 09:20:59 +01:00
parent f0233b8d31
commit d2264f5360
46 changed files with 314 additions and 4 deletions

View file

@ -2,6 +2,7 @@
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { decksStore } from '$lib/stores/decks.svelte';
import { PresiEvents } from '@manacore/shared-utils/analytics';
import { PageHeader, ContextMenu, type ContextMenuItem } from '@manacore/shared-ui';
import {
Plus,
@ -74,6 +75,7 @@
});
if (deck) {
PresiEvents.deckCreated();
showCreateModal = false;
newDeckTitle = '';
newDeckDescription = '';
@ -90,6 +92,7 @@
async function handleDelete() {
if (!deckToDelete) return;
await decksStore.deleteDeck(deckToDelete.id);
PresiEvents.deckDeleted();
showDeleteModal = false;
deckToDelete = null;
}

View file

@ -4,6 +4,7 @@
import { goto } from '$app/navigation';
import { browser } from '$app/environment';
import { decksStore } from '$lib/stores/decks.svelte';
import { PresiEvents } from '@manacore/shared-utils/analytics';
import { shareApi } from '$lib/api/client';
import type { ShareLink } from '$lib/api/client';
import type { Slide, SlideContent } from '@presi/shared';
@ -93,8 +94,10 @@
if (editingSlide) {
await decksStore.updateSlide(editingSlide.id, { content });
PresiEvents.slideEdited();
} else {
await decksStore.createSlide(deckId, { content });
PresiEvents.slideCreated();
}
isSaving = false;
@ -109,6 +112,7 @@
async function handleDeleteSlide() {
if (!slideToDelete) return;
await decksStore.deleteSlide(slideToDelete.id);
PresiEvents.slideDeleted();
showDeleteModal = false;
slideToDelete = null;
}
@ -135,6 +139,7 @@
// Update order values
newSlides.forEach((s, i) => (s.order = i + 1));
await decksStore.reorderSlides(newSlides);
PresiEvents.slideReordered(direction);
}
function addBulletPoint() {
@ -170,6 +175,7 @@
try {
const newShare = await shareApi.createShare(deckId);
shareLinks = [newShare, ...shareLinks];
PresiEvents.shareLinkCreated();
} catch (e) {
console.error('Failed to create share link:', e);
} finally {
@ -181,6 +187,7 @@
try {
await shareApi.deleteShare(shareId);
shareLinks = shareLinks.filter((s) => s.id !== shareId);
PresiEvents.shareLinkDeleted();
} catch (e) {
console.error('Failed to delete share link:', e);
}
@ -195,6 +202,7 @@
const url = getShareUrl(share.shareCode);
try {
await navigator.clipboard.writeText(url);
PresiEvents.shareLinkCopied();
copiedLinkId = share.id;
setTimeout(() => {
copiedLinkId = null;

View file

@ -3,6 +3,7 @@
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { decksStore } from '$lib/stores/decks.svelte';
import { PresiEvents } from '@manacore/shared-utils/analytics';
import type { Slide } from '@presi/shared';
import {
X,
@ -28,8 +29,12 @@
const deckId = $page.params.id as string;
let maxSlideReached = $state(0);
onMount(() => {
decksStore.loadDeck(deckId);
decksStore.loadDeck(deckId).then(() => {
PresiEvents.presentationStarted(decksStore.currentSlides.length);
});
// Keyboard navigation
window.addEventListener('keydown', handleKeydown);
@ -92,6 +97,7 @@
function nextSlide() {
if (currentSlideIndex < decksStore.currentSlides.length - 1) {
currentSlideIndex++;
if (currentSlideIndex > maxSlideReached) maxSlideReached = currentSlideIndex;
}
}
@ -126,6 +132,7 @@
}
function exitPresentation() {
PresiEvents.presentationExited(elapsedSeconds, maxSlideReached + 1);
if (document.fullscreenElement) {
document.exitFullscreen();
}

View file

@ -2,6 +2,7 @@
import { onMount, onDestroy } from 'svelte';
import { page } from '$app/stores';
import { shareApi } from '$lib/api/client';
import { PresiEvents } from '@manacore/shared-utils/analytics';
import type { Slide } from '@presi/shared';
import {
CaretLeft,
@ -34,6 +35,7 @@
const data = await shareApi.getByCode(shareCode);
deck = data;
slides = data.slides || [];
PresiEvents.sharedDeckViewed();
} catch (e) {
error = e instanceof Error ? e.message : 'Failed to load shared deck';
} finally {