diff --git a/apps/mana/apps/web/package.json b/apps/mana/apps/web/package.json index 58bd77768..36890d992 100644 --- a/apps/mana/apps/web/package.json +++ b/apps/mana/apps/web/package.json @@ -67,7 +67,6 @@ "@mana/shared-links": "workspace:*", "@mana/shared-llm": "workspace:*", "@mana/shared-privacy": "workspace:*", - "@mana/cards-core": "workspace:*", "@mana/shared-stores": "workspace:*", "@mana/shared-tags": "workspace:*", "@mana/shared-tailwind": "workspace:*", diff --git a/apps/mana/apps/web/src/lib/app-registry/apps.ts b/apps/mana/apps/web/src/lib/app-registry/apps.ts index f35b87676..6ac310a59 100644 --- a/apps/mana/apps/web/src/lib/app-registry/apps.ts +++ b/apps/mana/apps/web/src/lib/app-registry/apps.ts @@ -22,7 +22,6 @@ import { ChatCircle, Clock, Quotes, - Cards, Image, MusicNotes, Camera, @@ -597,16 +596,8 @@ registerApp({ }, }); -registerApp({ - id: 'cards', - name: 'Cards', - color: '#EF4444', - icon: Cards, - views: { - list: { load: () => import('$lib/modules/cards/ListView.svelte') }, - detail: { load: () => import('$lib/modules/cards/views/DetailView.svelte') }, - }, -}); +// Cards-Modul: dekommissioniert 2026-05-08, Cards lebt jetzt als +// standalone-App auf cardecky.mana.how (git.mana.how/till/cards). registerApp({ id: 'picture', diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts index 9bb138afa..418e16115 100644 --- a/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts +++ b/apps/mana/apps/web/src/lib/components/dashboard/widget-registry.ts @@ -18,7 +18,6 @@ import ChatRecentWidget from './widgets/ChatRecentWidget.svelte'; import ContactsFavoritesWidget from './widgets/ContactsFavoritesWidget.svelte'; import QuoteWidget from './widgets/QuoteWidget.svelte'; import PictureRecentWidget from './widgets/PictureRecentWidget.svelte'; -import CardsProgressWidget from './widgets/CardsProgressWidget.svelte'; import ClockTimersWidget from './widgets/ClockTimersWidget.svelte'; import StorageUsageWidget from './widgets/StorageUsageWidget.svelte'; import MusicLibraryWidget from './widgets/MusicLibraryWidget.svelte'; @@ -51,7 +50,6 @@ export const widgetComponents: Record = { 'contacts-recent': RecentContactsWidget, 'quotes-quote': QuoteWidget, 'picture-recent': PictureRecentWidget, - 'cards-progress': CardsProgressWidget, 'clock-timers': ClockTimersWidget, 'storage-usage': StorageUsageWidget, 'music-library': MusicLibraryWidget, diff --git a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CardsProgressWidget.svelte b/apps/mana/apps/web/src/lib/components/dashboard/widgets/CardsProgressWidget.svelte deleted file mode 100644 index ba9236497..000000000 --- a/apps/mana/apps/web/src/lib/components/dashboard/widgets/CardsProgressWidget.svelte +++ /dev/null @@ -1,55 +0,0 @@ - - -
-
-

- 🎴 - {$_('dashboard.widgets.cards.title')} -

-
- - {#if progress.loading} -
- {#each Array(3) as _} -
- {/each} -
- {:else} -
-
-
{progress.value.totalCards}
-
Karten
-
-
-
{progress.value.cardsLearned}
-
Gelernt
-
-
-
{progress.value.totalDecks}
-
Decks
-
-
-
{progress.value.dueForReview}
-
Fällig
-
-
- - {#if progress.value.dueForReview > 0} - - {progress.value.dueForReview} Karten wiederholen → - - {/if} - {/if} -
diff --git a/apps/mana/apps/web/src/lib/data/cross-app-queries.ts b/apps/mana/apps/web/src/lib/data/cross-app-queries.ts index c10d2ae4a..60e7734fb 100644 --- a/apps/mana/apps/web/src/lib/data/cross-app-queries.ts +++ b/apps/mana/apps/web/src/lib/data/cross-app-queries.ts @@ -19,7 +19,6 @@ import type { LocalAlarm, LocalCountdownTimer } from '$lib/modules/times/types'; import type { LocalFile } from '$lib/modules/storage/types'; import type { LocalSong, LocalPlaylist } from '$lib/modules/music/types'; import type { LocalDeck as LocalPresiDeck } from '$lib/modules/presi/types'; -import type { LocalDeck as LocalCardDeck, LocalCard } from '$lib/modules/cards/types'; // ─── Todo Queries ─────────────────────────────────────────── @@ -278,43 +277,7 @@ export function useRecentDecks(limit = 5) { } // ─── Cards Queries ───────────────────────────────────────── - -interface CardsProgress { - totalDecks: number; - totalCards: number; - cardsLearned: number; - dueForReview: number; - decks: LocalCardDeck[]; -} - -/** Cards learning progress. */ -export function useCardsProgress() { - return useLiveQueryWithDefault( - async (): Promise => { - const decks = await db.table('cardDecks').toArray(); - const cards = await db.table('cards').toArray(); - const activeDecks = decks.filter((d) => !d.deletedAt); - const activeCards = cards.filter((c) => !c.deletedAt); - const now = new Date().toISOString(); - const dueCards = activeCards.filter((c) => c.nextReview && c.nextReview <= now); - // Phase 6: cardDecks.name is encrypted — the widget renders the - // deck names so they need decryption. Counts work plaintext. - const { decryptRecords } = await import('./crypto'); - const decryptedDecks = await decryptRecords('cardDecks', activeDecks); - return { - totalDecks: activeDecks.length, - totalCards: activeCards.length, - cardsLearned: activeCards.filter((c) => (c.reviewCount ?? 0) > 0).length, - dueForReview: dueCards.length, - decks: decryptedDecks, - }; - }, - { - totalDecks: 0, - totalCards: 0, - cardsLearned: 0, - dueForReview: 0, - decks: [] as LocalCardDeck[], - } - ); -} +// Cards-Modul ist 2026-05-08 dekommissioniert (eigenständig auf +// cardecky.mana.how). Cross-App-Progress-Widgets, die Cards-Daten +// gezeigt haben, müssen entweder entfernt werden oder gegen die +// Cardecky-API queren — heute kein Konsument im mana-Frontend. diff --git a/apps/mana/apps/web/src/lib/data/module-registry.ts b/apps/mana/apps/web/src/lib/data/module-registry.ts index 8721b4fd6..f8ed22923 100644 --- a/apps/mana/apps/web/src/lib/data/module-registry.ts +++ b/apps/mana/apps/web/src/lib/data/module-registry.ts @@ -56,7 +56,6 @@ import { calendarModuleConfig } from '$lib/modules/calendar/module.config'; import { contactsModuleConfig } from '$lib/modules/contacts/module.config'; import { chatModuleConfig } from '$lib/modules/chat/module.config'; import { pictureModuleConfig } from '$lib/modules/picture/module.config'; -import { cardsModuleConfig } from '$lib/modules/cards/module.config'; import { quotesModuleConfig } from '$lib/modules/quotes/module.config'; import { musicModuleConfig } from '$lib/modules/music/module.config'; import { storageModuleConfig } from '$lib/modules/storage/module.config'; @@ -119,7 +118,6 @@ export const MODULE_CONFIGS: readonly ModuleConfig[] = [ contactsModuleConfig, chatModuleConfig, pictureModuleConfig, - cardsModuleConfig, quotesModuleConfig, musicModuleConfig, storageModuleConfig, diff --git a/apps/mana/apps/web/src/lib/data/privacy/exposed-records.ts b/apps/mana/apps/web/src/lib/data/privacy/exposed-records.ts index 257e6aeb2..523fc5318 100644 --- a/apps/mana/apps/web/src/lib/data/privacy/exposed-records.ts +++ b/apps/mana/apps/web/src/lib/data/privacy/exposed-records.ts @@ -211,18 +211,6 @@ const TABLES: TableConfig[] = [ return memosStore.setVisibility(id, next); }, }, - { - module: 'cards', - collection: 'cardDecks', - moduleLabel: 'Karten (Decks)', - encrypted: true, - title: (r) => asString(r.name), - href: (id) => `/cards/deck/${id}`, - setVisibility: async (id, next) => { - const { deckStore } = await import('$lib/modules/cards/stores/decks.svelte'); - return deckStore.setVisibility(id, next); - }, - }, { module: 'presi', collection: 'presiDecks', diff --git a/apps/mana/apps/web/src/lib/data/seed-registry.ts b/apps/mana/apps/web/src/lib/data/seed-registry.ts index f33ba1da5..04c492345 100644 --- a/apps/mana/apps/web/src/lib/data/seed-registry.ts +++ b/apps/mana/apps/web/src/lib/data/seed-registry.ts @@ -22,7 +22,6 @@ import { MOODLIT_GUEST_SEED } from '$lib/modules/moodlit/collections'; import { CONTACTS_GUEST_SEED } from '$lib/modules/contacts/collections'; import { CALENDAR_GUEST_SEED } from '$lib/modules/calendar/collections'; import { CHAT_GUEST_SEED } from '$lib/modules/chat/collections'; -import { CARDS_GUEST_SEED } from '$lib/modules/cards/collections'; import { SKILLTREE_GUEST_SEED } from '$lib/modules/skilltree/collections'; import { TODO_GUEST_SEED } from '$lib/modules/todo/collections'; import { NOTES_GUEST_SEED } from '$lib/modules/notes/collections'; @@ -63,7 +62,6 @@ register(MOODLIT_GUEST_SEED); register(CONTACTS_GUEST_SEED); register(CALENDAR_GUEST_SEED); register(CHAT_GUEST_SEED); -register(CARDS_GUEST_SEED); register(SKILLTREE_GUEST_SEED); register(TODO_GUEST_SEED); register(NOTES_GUEST_SEED); diff --git a/apps/mana/apps/web/src/lib/data/tools/init.ts b/apps/mana/apps/web/src/lib/data/tools/init.ts index cf9dfaba5..4ada90270 100644 --- a/apps/mana/apps/web/src/lib/data/tools/init.ts +++ b/apps/mana/apps/web/src/lib/data/tools/init.ts @@ -16,7 +16,6 @@ import { contactsTools } from '$lib/modules/contacts/tools'; import { bodyTools } from '$lib/modules/body/tools'; import { financeTools } from '$lib/modules/finance/tools'; import { dreamsTools } from '$lib/modules/dreams/tools'; -import { cardsTools } from '$lib/modules/cards/tools'; import { timesTools } from '$lib/modules/times/tools'; import { socialEventsTools } from '$lib/modules/events/tools'; import { musicTools } from '$lib/modules/music/tools'; @@ -68,7 +67,6 @@ export function initTools(): void { registerTools(bodyTools); registerTools(financeTools); registerTools(dreamsTools); - registerTools(cardsTools); registerTools(timesTools); registerTools(socialEventsTools); registerTools(musicTools); diff --git a/apps/mana/apps/web/src/lib/i18n/locales/cards/de.json b/apps/mana/apps/web/src/lib/i18n/locales/cards/de.json deleted file mode 100644 index 4a07a9990..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/cards/de.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "app": { - "name": "Cards", - "description": "KI-Lernkarten" - }, - "nav": { - "decks": "Decks", - "study": "Lernen", - "stats": "Statistiken", - "settings": "Einstellungen" - }, - "deck": { - "create": "Deck erstellen", - "edit": "Deck bearbeiten", - "delete": "Deck löschen", - "empty": "Noch keine Decks", - "cards": "Karten", - "study": "Lernen starten", - "addCard": "Karte hinzufügen", - "importCards": "Karten importieren", - "generateWithAI": "Mit KI generieren" - }, - "card": { - "front": "Vorderseite", - "back": "Rückseite", - "edit": "Karte bearbeiten", - "delete": "Karte löschen", - "hint": "Hinweis" - }, - "study": { - "again": "Nochmal", - "hard": "Schwer", - "good": "Gut", - "easy": "Einfach", - "showAnswer": "Antwort zeigen", - "complete": "Abgeschlossen!", - "cardsRemaining": "Karten übrig", - "streak": "Serie" - }, - "stats": { - "studied": "Gelernt", - "mastered": "Gemeistert", - "accuracy": "Genauigkeit", - "reviewsDue": "Fällige Wiederholungen" - }, - "common": { - "save": "Speichern", - "cancel": "Abbrechen", - "delete": "Löschen", - "back": "Zurück", - "loading": "Lädt...", - "error": "Fehler", - "success": "Erfolgreich" - }, - "progress": { - "page_title_html": "Fortschritt - Cards - Mana", - "heading": "Fortschritt", - "subtitle": "Verfolge deinen Lernfortschritt", - "stat_decks": "Decks", - "stat_total_cards": "Karten gesamt", - "stat_due": "Fällig zur Wiederholung", - "section_overview": "Decks Übersicht", - "empty_title": "Noch keine Lernsitzungen.", - "empty_hint": "Erstelle ein Deck und beginne zu lernen!", - "deck_cards": "{n} Karten" - }, - "detail": { - "not_found": "Deck nicht gefunden", - "confirm_delete": "Deck wirklich löschen?", - "toast_deleted": "Deck gelöscht", - "placeholder_name": "Deck-Name...", - "name_fallback": "Unbenannt", - "prop_color": "Farbe", - "prop_visibility": "Sichtbarkeit", - "prop_cards": "Karten", - "prop_last_studied": "Zuletzt gelernt", - "section_description": "Beschreibung", - "placeholder_description": "Beschreibung hinzufügen...", - "meta_created": "Erstellt: {date}", - "meta_updated": "Bearbeitet: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/cards/en.json b/apps/mana/apps/web/src/lib/i18n/locales/cards/en.json deleted file mode 100644 index 3c8fcfcf0..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/cards/en.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "app": { - "name": "Cards", - "description": "AI Flashcards" - }, - "nav": { - "decks": "Decks", - "study": "Study", - "stats": "Statistics", - "settings": "Settings" - }, - "deck": { - "create": "Create Deck", - "edit": "Edit Deck", - "delete": "Delete Deck", - "empty": "No decks yet", - "cards": "Cards", - "study": "Start Studying", - "addCard": "Add Card", - "importCards": "Import Cards", - "generateWithAI": "Generate with AI" - }, - "card": { - "front": "Front", - "back": "Back", - "edit": "Edit Card", - "delete": "Delete Card", - "hint": "Hint" - }, - "study": { - "again": "Again", - "hard": "Hard", - "good": "Good", - "easy": "Easy", - "showAnswer": "Show Answer", - "complete": "Complete!", - "cardsRemaining": "cards remaining", - "streak": "Streak" - }, - "stats": { - "studied": "Studied", - "mastered": "Mastered", - "accuracy": "Accuracy", - "reviewsDue": "Reviews due" - }, - "common": { - "save": "Save", - "cancel": "Cancel", - "delete": "Delete", - "back": "Back", - "loading": "Loading...", - "error": "Error", - "success": "Success" - }, - "progress": { - "page_title_html": "Progress - Cards - Mana", - "heading": "Progress", - "subtitle": "Track your learning progress", - "stat_decks": "Decks", - "stat_total_cards": "Total cards", - "stat_due": "Due for review", - "section_overview": "Decks overview", - "empty_title": "No study sessions yet.", - "empty_hint": "Create a deck and start studying!", - "deck_cards": "{n} cards" - }, - "detail": { - "not_found": "Deck not found", - "confirm_delete": "Really delete this deck?", - "toast_deleted": "Deck deleted", - "placeholder_name": "Deck name...", - "name_fallback": "Untitled", - "prop_color": "Color", - "prop_visibility": "Visibility", - "prop_cards": "Cards", - "prop_last_studied": "Last studied", - "section_description": "Description", - "placeholder_description": "Add a description...", - "meta_created": "Created: {date}", - "meta_updated": "Edited: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/cards/es.json b/apps/mana/apps/web/src/lib/i18n/locales/cards/es.json deleted file mode 100644 index 86bc42470..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/cards/es.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "app": { - "name": "Cards", - "description": "Flashcards con IA" - }, - "nav": { - "decks": "Mazos", - "study": "Estudiar", - "stats": "Estadísticas", - "settings": "Ajustes" - }, - "deck": { - "create": "Crear mazo", - "edit": "Editar mazo", - "delete": "Eliminar mazo", - "empty": "Aún no hay mazos", - "cards": "Tarjetas", - "study": "Empezar a estudiar", - "addCard": "Añadir tarjeta", - "importCards": "Importar tarjetas", - "generateWithAI": "Generar con IA" - }, - "card": { - "front": "Anverso", - "back": "Reverso", - "edit": "Editar tarjeta", - "delete": "Eliminar tarjeta", - "hint": "Pista" - }, - "study": { - "again": "Otra vez", - "hard": "Difícil", - "good": "Bien", - "easy": "Fácil", - "showAnswer": "Mostrar respuesta", - "complete": "¡Completado!", - "cardsRemaining": "tarjetas restantes", - "streak": "Racha" - }, - "stats": { - "studied": "Estudiadas", - "mastered": "Dominadas", - "accuracy": "Precisión", - "reviewsDue": "Repasos pendientes" - }, - "common": { - "save": "Guardar", - "cancel": "Cancelar", - "delete": "Eliminar", - "back": "Atrás", - "loading": "Cargando...", - "error": "Error", - "success": "Éxito" - }, - "progress": { - "page_title_html": "Progreso - Cards - Mana", - "heading": "Progreso", - "subtitle": "Sigue tu progreso de aprendizaje", - "stat_decks": "Mazos", - "stat_total_cards": "Tarjetas en total", - "stat_due": "Pendientes de repasar", - "section_overview": "Resumen de mazos", - "empty_title": "Aún no hay sesiones de estudio.", - "empty_hint": "¡Crea un mazo y empieza a estudiar!", - "deck_cards": "{n} tarjetas" - }, - "detail": { - "not_found": "Mazo no encontrado", - "confirm_delete": "¿Eliminar realmente este mazo?", - "toast_deleted": "Mazo eliminado", - "placeholder_name": "Nombre del mazo...", - "name_fallback": "Sin título", - "prop_color": "Color", - "prop_visibility": "Visibilidad", - "prop_cards": "Tarjetas", - "prop_last_studied": "Último estudio", - "section_description": "Descripción", - "placeholder_description": "Añadir una descripción...", - "meta_created": "Creado: {date}", - "meta_updated": "Editado: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/cards/fr.json b/apps/mana/apps/web/src/lib/i18n/locales/cards/fr.json deleted file mode 100644 index a567b2173..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/cards/fr.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "app": { - "name": "Cards", - "description": "Flashcards IA" - }, - "nav": { - "decks": "Paquets", - "study": "Étudier", - "stats": "Statistiques", - "settings": "Paramètres" - }, - "deck": { - "create": "Créer un paquet", - "edit": "Modifier le paquet", - "delete": "Supprimer le paquet", - "empty": "Pas encore de paquets", - "cards": "Cartes", - "study": "Commencer à étudier", - "addCard": "Ajouter une carte", - "importCards": "Importer des cartes", - "generateWithAI": "Générer avec l'IA" - }, - "card": { - "front": "Recto", - "back": "Verso", - "edit": "Modifier la carte", - "delete": "Supprimer la carte", - "hint": "Indice" - }, - "study": { - "again": "Encore", - "hard": "Difficile", - "good": "Bien", - "easy": "Facile", - "showAnswer": "Afficher la réponse", - "complete": "Terminé !", - "cardsRemaining": "cartes restantes", - "streak": "Série" - }, - "stats": { - "studied": "Étudiées", - "mastered": "Maîtrisées", - "accuracy": "Précision", - "reviewsDue": "Révisions à faire" - }, - "common": { - "save": "Enregistrer", - "cancel": "Annuler", - "delete": "Supprimer", - "back": "Retour", - "loading": "Chargement...", - "error": "Erreur", - "success": "Succès" - }, - "progress": { - "page_title_html": "Progression - Cards - Mana", - "heading": "Progression", - "subtitle": "Suis ta progression d'apprentissage", - "stat_decks": "Paquets", - "stat_total_cards": "Cartes au total", - "stat_due": "À réviser", - "section_overview": "Vue d'ensemble des paquets", - "empty_title": "Pas encore de sessions d'étude.", - "empty_hint": "Crée un paquet et commence à étudier !", - "deck_cards": "{n} cartes" - }, - "detail": { - "not_found": "Paquet introuvable", - "confirm_delete": "Vraiment supprimer ce paquet ?", - "toast_deleted": "Paquet supprimé", - "placeholder_name": "Nom du paquet...", - "name_fallback": "Sans titre", - "prop_color": "Couleur", - "prop_visibility": "Visibilité", - "prop_cards": "Cartes", - "prop_last_studied": "Dernière révision", - "section_description": "Description", - "placeholder_description": "Ajouter une description...", - "meta_created": "Créé : {date}", - "meta_updated": "Modifié : {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/i18n/locales/cards/it.json b/apps/mana/apps/web/src/lib/i18n/locales/cards/it.json deleted file mode 100644 index 194bee77c..000000000 --- a/apps/mana/apps/web/src/lib/i18n/locales/cards/it.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "app": { - "name": "Cards", - "description": "Flashcard IA" - }, - "nav": { - "decks": "Mazzi", - "study": "Studia", - "stats": "Statistiche", - "settings": "Impostazioni" - }, - "deck": { - "create": "Crea mazzo", - "edit": "Modifica mazzo", - "delete": "Elimina mazzo", - "empty": "Nessun mazzo ancora", - "cards": "Carte", - "study": "Inizia a studiare", - "addCard": "Aggiungi carta", - "importCards": "Importa carte", - "generateWithAI": "Genera con IA" - }, - "card": { - "front": "Fronte", - "back": "Retro", - "edit": "Modifica carta", - "delete": "Elimina carta", - "hint": "Suggerimento" - }, - "study": { - "again": "Ancora", - "hard": "Difficile", - "good": "Bene", - "easy": "Facile", - "showAnswer": "Mostra risposta", - "complete": "Completato!", - "cardsRemaining": "carte rimanenti", - "streak": "Serie" - }, - "stats": { - "studied": "Studiate", - "mastered": "Padroneggiate", - "accuracy": "Precisione", - "reviewsDue": "Ripetizioni in scadenza" - }, - "common": { - "save": "Salva", - "cancel": "Annulla", - "delete": "Elimina", - "back": "Indietro", - "loading": "Caricamento...", - "error": "Errore", - "success": "Successo" - }, - "progress": { - "page_title_html": "Progresso - Cards - Mana", - "heading": "Progresso", - "subtitle": "Segui i tuoi progressi di studio", - "stat_decks": "Mazzi", - "stat_total_cards": "Carte totali", - "stat_due": "Da ripassare", - "section_overview": "Panoramica mazzi", - "empty_title": "Ancora nessuna sessione di studio.", - "empty_hint": "Crea un mazzo e inizia a studiare!", - "deck_cards": "{n} carte" - }, - "detail": { - "not_found": "Mazzo non trovato", - "confirm_delete": "Eliminare davvero questo mazzo?", - "toast_deleted": "Mazzo eliminato", - "placeholder_name": "Nome del mazzo...", - "name_fallback": "Senza titolo", - "prop_color": "Colore", - "prop_visibility": "Visibilità", - "prop_cards": "Carte", - "prop_last_studied": "Ultimo studio", - "section_description": "Descrizione", - "placeholder_description": "Aggiungi una descrizione...", - "meta_created": "Creato: {date}", - "meta_updated": "Modificato: {date}" - } -} diff --git a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte b/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte deleted file mode 100644 index a866faf59..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/ListView.svelte +++ /dev/null @@ -1,126 +0,0 @@ - - - -{#if !standaloneHintDismissed} -
- - Cardecky gibt es jetzt auch als eigenständige App auf - cardecky.mana.how — gleiche Daten, fokussierte UI. - - -
-{/if} - d.id} emptyTitle="Keine Decks"> - {#snippet header()} - {decks.length} Decks - {totalDue} fällig - {/snippet} - - {#snippet item(deck)} - {@const due = dueByDeck.get(deck.id) ?? 0} - - {/snippet} - diff --git a/apps/mana/apps/web/src/lib/modules/cards/card-reviews.ts b/apps/mana/apps/web/src/lib/modules/cards/card-reviews.ts deleted file mode 100644 index 15870809f..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/card-reviews.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Cards module — review fan-out is now sourced from `@mana/cards-core`. - * Thin re-export so existing local imports keep working. - */ - -export { subIndexesFor } from '@mana/cards-core'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/cloze.ts b/apps/mana/apps/web/src/lib/modules/cards/cloze.ts deleted file mode 100644 index a0f026aab..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/cloze.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Cards module — cloze parser is now sourced from `@mana/cards-core`. - * Thin re-export so existing local imports keep working. - */ - -export { - tokenize, - clusterIndexes, - clusters, - renderCloze, - type ClozeCluster, - type RenderedCloze, -} from '@mana/cards-core'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/collections.ts b/apps/mana/apps/web/src/lib/modules/cards/collections.ts deleted file mode 100644 index 9a1bb029e..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/collections.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Cards module — collection accessors and guest seed data. - * - * Tables in the unified DB: cardDecks, cards, cardReviews, cardStudyBlocks. - */ - -import { db } from '$lib/data/database'; -import type { LocalDeck, LocalCard, LocalCardReview, LocalCardStudyBlock } from './types'; - -// ─── Collection Accessors ────────────────────────────────── - -export const cardDeckTable = db.table('cardDecks'); -export const cardTable = db.table('cards'); -export const cardReviewTable = db.table('cardReviews'); -export const cardStudyBlockTable = db.table('cardStudyBlocks'); - -// ─── Guest Seed ──────────────────────────────────────────── - -const ONBOARDING_DECK_ID = 'onboarding-deck'; - -export const CARDS_GUEST_SEED = { - cardDecks: [ - { - id: ONBOARDING_DECK_ID, - name: 'Erste Schritte', - description: 'Lerne Cards kennen mit diesen Beispiel-Karteikarten.', - color: '#6366f1', - cardCount: 3, - }, - ], - cards: [ - { - id: 'card-1', - deckId: ONBOARDING_DECK_ID, - front: 'Was ist Cards?', - back: 'Cards ist eine Karteikarten-App zum effizienten Lernen mit Spaced Repetition.', - difficulty: 1, - reviewCount: 0, - order: 0, - }, - { - id: 'card-2', - deckId: ONBOARDING_DECK_ID, - front: 'Wie funktioniert Spaced Repetition?', - back: 'Karten, die du gut kennst, werden seltener gezeigt. Schwierige Karten erscheinen haufiger, bis du sie beherrschst.', - difficulty: 2, - reviewCount: 0, - order: 1, - }, - { - id: 'card-3', - deckId: ONBOARDING_DECK_ID, - front: 'Wie erstelle ich ein neues Deck?', - back: 'Klicke auf den + Button auf der Decks-Seite, um ein neues Deck mit eigenen Karteikarten zu erstellen.', - difficulty: 1, - reviewCount: 0, - order: 2, - }, - ], -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/components/CardFace.svelte b/apps/mana/apps/web/src/lib/modules/cards/components/CardFace.svelte deleted file mode 100644 index 3c3538628..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/components/CardFace.svelte +++ /dev/null @@ -1,95 +0,0 @@ - - -
-
- {@html view.prompt} -
- - {#if isTypeIn} - onTypedAnswer?.((e.currentTarget as HTMLInputElement).value)} - disabled={showBack} - /> - {/if} - - {#if showBack} -
- {@html view.answer} -
- {/if} -
diff --git a/apps/mana/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte b/apps/mana/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte deleted file mode 100644 index 04331d7fe..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/components/CreateDeckModal.svelte +++ /dev/null @@ -1,140 +0,0 @@ - - -{#if open} -
e.key === 'Escape' && handleClose()} - tabindex="-1" - role="presentation" - > - - -
e.stopPropagation()} - > -

Neues Deck erstellen

- -
{ - e.preventDefault(); - handleSubmit(); - }} - class="space-y-4" - > -
- - -
- -
- - -
- -
- Tags - (selectedTagIds = ids)} - /> -
- -
- Farbe - (color = c)} - size="sm" - /> -
- - {#if deckStore.error} -
- {deckStore.error} -
- {/if} - -
- - -
-
-
-
-{/if} diff --git a/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte b/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte deleted file mode 100644 index c3c488965..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/components/DeckCard.svelte +++ /dev/null @@ -1,51 +0,0 @@ - - - -
- -
- - -

{deck.title}

- - - {#if deck.description} -

- {deck.description} -

- {/if} - - -
-
- {deck.cardCount || 0} Karten - {#if deck.visibility === 'public'} - - Öffentlich - - {/if} -
- {formatDate(deck.updatedAt)} -
-
-
diff --git a/apps/mana/apps/web/src/lib/modules/cards/fsrs.ts b/apps/mana/apps/web/src/lib/modules/cards/fsrs.ts deleted file mode 100644 index bf1264557..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/fsrs.ts +++ /dev/null @@ -1,6 +0,0 @@ -/** - * Cards module — FSRS wrapper is now sourced from `@mana/cards-core`. - * Thin re-export so existing local imports keep working. - */ - -export { newReview, gradeReview } from '@mana/cards-core'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/index.ts b/apps/mana/apps/web/src/lib/modules/cards/index.ts deleted file mode 100644 index f063df7aa..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Cards module — barrel exports. - */ - -export { deckStore } from './stores/decks.svelte'; -export { cardStore } from './stores/cards.svelte'; -export { - useAllDecks, - useDeck, - useCardsByDeck, - toDeck, - toCard, - getDeckById, - getPublicDecks, - getCardCountForDeck, - getDueCards, -} from './queries'; -export { cardDeckTable, cardTable, CARDS_GUEST_SEED } from './collections'; -export type { - LocalDeck, - LocalCard, - Deck, - Card, - CreateDeckInput, - UpdateDeckInput, - CreateCardInput, - UpdateCardInput, -} from './types'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/module.config.ts b/apps/mana/apps/web/src/lib/modules/cards/module.config.ts deleted file mode 100644 index 98818d4d5..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/module.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ModuleConfig } from '$lib/data/module-registry'; - -export const cardsModuleConfig: ModuleConfig = { - appId: 'cards', - tables: [ - { name: 'cardDecks', syncName: 'decks' }, - { name: 'cards' }, - { name: 'deckTags' }, - { name: 'cardReviews' }, - { name: 'cardStudyBlocks' }, - ], -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/queries.ts b/apps/mana/apps/web/src/lib/modules/cards/queries.ts deleted file mode 100644 index 166e18145..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/queries.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Reactive queries & pure helpers for Cards — uses Dexie liveQuery on the unified DB. - * - * Uses table names: cardDecks, cards. - */ - -import { liveQuery } from 'dexie'; -import { deriveUpdatedAt } from '$lib/data/sync'; -import { db } from '$lib/data/database'; -import { scopedForModule } from '$lib/data/scope'; -import { decryptRecord, decryptRecords } from '$lib/data/crypto'; -import type { - CardFields, - CardType, - LocalDeck, - LocalCard, - LocalCardReview, - Deck, - Card, - CardReview, -} from './types'; - -// ─── Type Converters ─────────────────────────────────────── - -export function toDeck(local: LocalDeck): Deck { - return { - id: local.id, - title: local.name, - description: local.description ?? undefined, - color: local.color, - visibility: local.visibility ?? 'space', - tags: [], - cardCount: local.cardCount, - createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: deriveUpdatedAt(local), - }; -} - -/** - * Promote any LocalCard row — including legacy pre-Phase-0 ones — to - * the canonical {type, fields} shape. Readers must go through this so - * the rest of the app sees one schema. - * - * - Phase-0+ rows: returned as-is, with `front`/`back` derived from - * fields for the convenience accessors on the DTO. - * - Legacy rows (only `front`/`back` set): synthesised as - * {type: 'basic', fields: {front, back}}. - */ -export function toLogicalCard(local: LocalCard): { - type: CardType; - fields: CardFields; - front: string; - back: string; -} { - const type: CardType = local.type ?? 'basic'; - const fields: CardFields = local.fields ?? { - front: local.front ?? '', - back: local.back ?? '', - }; - const front = fields.front ?? local.front ?? ''; - const back = fields.back ?? local.back ?? ''; - return { type, fields, front, back }; -} - -export function toCard(local: LocalCard): Card { - const { type, fields, front, back } = toLogicalCard(local); - return { - id: local.id, - deckId: local.deckId, - type, - fields, - front, - back, - order: local.order, - createdAt: local.createdAt ?? new Date().toISOString(), - updatedAt: deriveUpdatedAt(local), - // Legacy fields surfaced for pre-Phase-0 UI. Populated only when the - // underlying row carries them. - difficulty: local.difficulty, - nextReview: local.nextReview ?? undefined, - reviewCount: local.reviewCount, - }; -} - -// ─── Live Queries ────────────────────────────────────────── - -/** All decks, auto-updates on any change. */ -export function useAllDecks() { - return liveQuery(async () => { - const visible = ( - await scopedForModule('cards', 'cardDecks').toArray() - ).filter((d) => !d.deletedAt); - const decrypted = await decryptRecords('cardDecks', visible); - return decrypted.map(toDeck); - }); -} - -/** Single deck by ID. Auto-updates on any change. */ -export function useDeck(deckId: string) { - return liveQuery(async () => { - const local = await db.table('cardDecks').get(deckId); - if (!local || local.deletedAt) return null; - const decrypted = await decryptRecord('cardDecks', { ...local }); - return toDeck(decrypted); - }); -} - -/** All cards for a specific deck, sorted by order. Auto-updates on any change. */ -export function useCardsByDeck(deckId: string) { - return liveQuery(async () => { - const visible = ( - await db.table('cards').where('deckId').equals(deckId).sortBy('order') - ).filter((c) => !c.deletedAt); - const decrypted = await decryptRecords('cards', visible); - return decrypted.map(toCard); - }); -} - -/** - * All reviews that are due now (or overdue), optionally filtered by - * deck. Joined with the parent card so the UI can render the prompt - * immediately without a second lookup. - * - * Sorted by `due` ascending so the oldest-due learnable unit comes - * first — that's the natural session order. - */ -export function useDueReviews(deckId?: string) { - return liveQuery(async () => { - const nowIso = new Date().toISOString(); - const due = await db - .table('cardReviews') - .where('due') - .belowOrEqual(nowIso) - .toArray(); - const live = due.filter((r) => !r.deletedAt); - if (live.length === 0) return [] as { review: CardReview; card: Card }[]; - - const cardIds = [...new Set(live.map((r) => r.cardId))]; - const cardRows = await db.table('cards').where('id').anyOf(cardIds).toArray(); - const decryptedCards = await decryptRecords( - 'cards', - cardRows.filter((c) => !c.deletedAt) - ); - const cardById = new Map(decryptedCards.map((c) => [c.id, toCard(c)] as const)); - - return live - .filter((r) => { - const c = cardById.get(r.cardId); - if (!c) return false; - if (deckId && c.deckId !== deckId) return false; - return true; - }) - .sort((a, b) => (a.due < b.due ? -1 : a.due > b.due ? 1 : 0)) - .map((r) => ({ review: toCardReview(r), card: cardById.get(r.cardId)! })); - }); -} - -/** Just the reviews row, no card join — useful in the session UI mid-grade. */ -export function useReview(reviewId: string) { - return liveQuery(async () => { - const r = await db.table('cardReviews').get(reviewId); - if (!r || r.deletedAt) return null; - return toCardReview(r); - }); -} - -function toCardReview(r: LocalCardReview): CardReview { - return { - id: r.id, - cardId: r.cardId, - subIndex: r.subIndex, - state: r.state, - stability: r.stability, - difficulty: r.difficulty, - due: r.due, - reps: r.reps, - lapses: r.lapses, - lastReview: r.lastReview, - elapsedDays: r.elapsedDays, - scheduledDays: r.scheduledDays, - }; -} - -// ─── Pure Helper Functions ───────────────────────────────── - -export function getDeckById(decks: Deck[], id: string): Deck | undefined { - return decks.find((d) => d.id === id); -} - -export function getPublicDecks(decks: Deck[]): Deck[] { - return decks.filter((d) => d.visibility === 'public'); -} - -export function getCardCountForDeck(cards: Card[], deckId: string): number { - return cards.filter((c) => c.deckId === deckId).length; -} - -export function getDueCards(cards: Card[]): Card[] { - const now = new Date().toISOString(); - return cards.filter((c) => c.nextReview && c.nextReview <= now); -} diff --git a/apps/mana/apps/web/src/lib/modules/cards/render.ts b/apps/mana/apps/web/src/lib/modules/cards/render.ts deleted file mode 100644 index d37a1fcb5..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/render.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** - * Cards module — Markdown render helper is now sourced from - * `@mana/cards-core`. Thin re-export so existing local imports keep - * working. - */ - -export { renderMarkdown, type RenderOptions } from '@mana/cards-core'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts deleted file mode 100644 index bde4e6ff0..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/cards.svelte.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * Card Store — Mutations Only - * - * Reads come from liveQuery hooks in queries.ts. - * This store only handles writes to IndexedDB via the unified database. - * - * Phase 0+: writes the new {type, fields} shape AND mirrors basic-card - * content to the legacy front/back columns so older mana builds keep - * rendering. Every create/update fans out to cardReviews via - * reviewStore.ensureReviewsForCard(). - */ - -import { CardsEvents } from '@mana/shared-utils/analytics'; -import { cardTable, cardDeckTable } from '../collections'; -import { toCard, toLogicalCard } from '../queries'; -import { encryptRecord, decryptRecord } from '$lib/data/crypto'; -import { emitDomainEvent } from '$lib/data/events'; -import type { - CardFields, - CardType, - LocalCard, - Card, - CreateCardInput, - UpdateCardInput, -} from '../types'; -import { reviewStore } from './reviews.svelte'; - -let error = $state(null); - -/** - * Build the {type, fields} pair from a CreateCardInput. Accepts the - * convenience `front`/`back` shortcut for basic cards and falls back - * to an explicit `fields` map for cloze and friends. - */ -function resolveTypeAndFields(input: CreateCardInput): { - type: CardType; - fields: CardFields; -} { - const type = input.type ?? 'basic'; - if (input.fields) return { type, fields: input.fields }; - if (type === 'cloze') return { type, fields: { text: input.front ?? '' } }; - return { type, fields: { front: input.front ?? '', back: input.back ?? '' } }; -} - -/** Mirror basic-card text into the legacy columns for older clients. */ -function legacyMirror(type: CardType, fields: CardFields): { front?: string; back?: string } { - if (type === 'basic' || type === 'basic-reverse' || type === 'type-in') { - return { front: fields.front ?? '', back: fields.back ?? '' }; - } - if (type === 'cloze') { - // Surface the cloze source on `front` so legacy list-views show - // something meaningful rather than an empty row. - return { front: fields.text ?? '', back: '' }; - } - return {}; -} - -export const cardStore = { - get error() { - return error; - }, - - async createCard(input: CreateCardInput, currentCardCount: number = 0): Promise { - error = null; - try { - const { type, fields } = resolveTypeAndFields(input); - const legacy = legacyMirror(type, fields); - - const newLocal: LocalCard = { - id: crypto.randomUUID(), - deckId: input.deckId, - type, - fields, - order: currentCardCount, - ...legacy, - }; - - const plaintextSnapshot = toCard(newLocal); - await encryptRecord('cards', newLocal); - await cardTable.add(newLocal); - - const deck = await cardDeckTable.get(input.deckId); - if (deck) { - await cardDeckTable.update(input.deckId, { - cardCount: (deck.cardCount || 0) + 1, - }); - } - - await reviewStore.ensureReviewsForCard({ id: newLocal.id, type, fields }); - - emitDomainEvent('CardCreated', 'cards', 'cards', newLocal.id, { - cardId: newLocal.id, - deckId: input.deckId, - }); - CardsEvents.cardCreated(); - return plaintextSnapshot; - } catch (err: any) { - error = err.message || 'Failed to create card'; - console.error('Create card error:', err); - return null; - } - }, - - async updateCard(id: string, updates: UpdateCardInput) { - error = null; - try { - const existing = await cardTable.get(id); - if (!existing) return; - - const decrypted = await decryptRecord('cards', { ...existing }); - const current = toLogicalCard(decrypted as LocalCard); - const nextType: CardType = updates.type ?? current.type; - const nextFields: CardFields = updates.fields - ? updates.fields - : updates.front !== undefined || updates.back !== undefined - ? nextType === 'cloze' - ? { ...current.fields, text: updates.front ?? current.fields.text ?? '' } - : { - ...current.fields, - front: updates.front ?? current.fields.front ?? '', - back: updates.back ?? current.fields.back ?? '', - } - : current.fields; - - const legacy = legacyMirror(nextType, nextFields); - const diff: Partial = { - type: nextType, - fields: nextFields, - ...legacy, - }; - if (updates.order !== undefined) diff.order = updates.order; - if (updates.difficulty !== undefined) diff.difficulty = updates.difficulty; - - await encryptRecord('cards', diff); - await cardTable.update(id, diff); - - const structuralChange = - updates.type !== undefined || - updates.fields !== undefined || - (nextType === 'cloze' && updates.front !== undefined); - if (structuralChange) { - await reviewStore.ensureReviewsForCard({ id, type: nextType, fields: nextFields }); - } - } catch (err: any) { - error = err.message || 'Failed to update card'; - console.error('Update card error:', err); - } - }, - - async deleteCard(id: string, deckId?: string) { - error = null; - try { - const now = new Date().toISOString(); - await cardTable.update(id, { deletedAt: now }); - await reviewStore.softDeleteForCard(id); - CardsEvents.cardDeleted(); - - if (deckId) { - const deck = await cardDeckTable.get(deckId); - if (deck) { - await cardDeckTable.update(deckId, { - cardCount: Math.max(0, (deck.cardCount || 0) - 1), - }); - } - } - } catch (err: any) { - error = err.message || 'Failed to delete card'; - console.error('Delete card error:', err); - } - }, - - async reorderCards(cardIds: string[]) { - error = null; - try { - for (let i = 0; i < cardIds.length; i++) { - await cardTable.update(cardIds[i], { order: i }); - } - } catch (err: any) { - error = err.message || 'Failed to reorder cards'; - console.error('Reorder cards error:', err); - } - }, - - clearError() { - error = null; - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts deleted file mode 100644 index 1f0ad27bb..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/decks.svelte.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Deck Store — Mutations Only - * - * Reads come from liveQuery hooks in queries.ts. - * This store only handles writes to IndexedDB via the unified database. - */ - -import { CardsEvents } from '@mana/shared-utils/analytics'; -import { db } from '$lib/data/database'; -import { cardDeckTable, cardTable } from '../collections'; -import { toDeck } from '../queries'; -import { encryptRecord, decryptRecord } from '$lib/data/crypto'; -import { emitDomainEvent } from '$lib/data/events'; -import { getActiveSpace } from '$lib/data/scope'; -import { getEffectiveUserId } from '$lib/data/current-user'; -import { defaultVisibilityFor, type VisibilityLevel } from '@mana/shared-privacy'; -import { createBlock, updateBlock } from '$lib/data/time-blocks/service'; -import type { LocalDeck } from '../types'; -import type { Deck, CreateDeckInput, UpdateDeckInput } from '../types'; - -let error = $state(null); - -export const deckStore = { - get error() { - return error; - }, - - async createDeck(input: CreateDeckInput): Promise { - error = null; - try { - const newLocal: LocalDeck = { - id: crypto.randomUUID(), - name: input.title, - description: input.description ?? null, - color: '#6366f1', - cardCount: 0, - visibility: defaultVisibilityFor(getActiveSpace()?.type), - }; - - const plaintextSnapshot = toDeck(newLocal); - await encryptRecord('cardDecks', newLocal); - await cardDeckTable.add(newLocal); - CardsEvents.deckCreated(); - return plaintextSnapshot; - } catch (err: any) { - error = err.message || 'Failed to create deck'; - console.error('Create deck error:', err); - return null; - } - }, - - async updateDeck(id: string, updates: UpdateDeckInput) { - error = null; - try { - const localUpdates: Partial = {}; - if (updates.title !== undefined) localUpdates.name = updates.title; - if (updates.description !== undefined) localUpdates.description = updates.description; - - const diff: Partial = { - ...localUpdates, - }; - await encryptRecord('cardDecks', diff); - await cardDeckTable.update(id, diff); - } catch (err: any) { - error = err.message || 'Failed to update deck'; - console.error('Update deck error:', err); - } - }, - - /** - * Flip a deck's visibility. Public decks surface in the cards - * embed-resolver on the user's website. - */ - async setVisibility(id: string, next: VisibilityLevel) { - const existing = await cardDeckTable.get(id); - if (!existing) throw new Error(`Deck ${id} not found`); - const before: VisibilityLevel = existing.visibility ?? 'space'; - if (before === next) return; - - const stamp = new Date().toISOString(); - await cardDeckTable.update(id, { - visibility: next, - visibilityChangedAt: stamp, - visibilityChangedBy: getEffectiveUserId(), - updatedAt: stamp, - }); - - emitDomainEvent('VisibilityChanged', 'cards', 'cardDecks', id, { - recordId: id, - collection: 'cardDecks', - before, - after: next, - }); - }, - - async deleteDeck(id: string) { - error = null; - try { - const now = new Date().toISOString(); - - // Atomic cascade: deck + all child cards are soft-deleted in one - // Dexie transaction. If any write fails, the whole operation aborts — - // no orphaned cards left pointing at a deleted deck. - await db.transaction('rw', cardDeckTable, cardTable, async () => { - const cards = await cardTable.where('deckId').equals(id).toArray(); - for (const card of cards) { - await cardTable.update(card.id, { deletedAt: now }); - } - await cardDeckTable.update(id, { deletedAt: now }); - }); - CardsEvents.deckDeleted(); - } catch (err: any) { - error = err.message || 'Failed to delete deck'; - console.error('Delete deck error:', err); - } - }, - - async startStudySession(deckId: string): Promise { - const deck = await cardDeckTable.get(deckId); - if (!deck) return null; - - // Don't start a second session if one is already active - if (deck.activeStudyBlockId) return deck.activeStudyBlockId; - - const decrypted = await decryptRecord('cardDecks', { ...deck }); - const deckName = decrypted?.name ?? 'Deck'; - const now = new Date().toISOString(); - - const timeBlockId = await createBlock({ - startDate: now, - endDate: null, - isLive: true, - kind: 'logged', - type: 'study', - sourceModule: 'cards', - sourceId: deckId, - title: `${deckName} lernen`, - color: '#0ea5e9', - }); - - await cardDeckTable.update(deckId, { - activeStudyBlockId: timeBlockId, - lastStudied: now, - }); - - return timeBlockId; - }, - - async endStudySession(deckId: string): Promise { - const deck = await cardDeckTable.get(deckId); - if (!deck?.activeStudyBlockId) return; - - const now = new Date().toISOString(); - await updateBlock(deck.activeStudyBlockId, { - endDate: now, - isLive: false, - }); - - await cardDeckTable.update(deckId, { - activeStudyBlockId: null, - }); - }, - - clearError() { - error = null; - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/reviews.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/reviews.svelte.ts deleted file mode 100644 index a7b3e69d0..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/reviews.svelte.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Card-Review Store — FSRS scheduling state. - * - * Reviews are plaintext (no encryptRecord) — cardReviews is in - * `plaintext-allowlist.ts` because the scheduler must query by `due` - * to find what's fällig today. - * - * Three operations the rest of the module needs: - * - ensureReviewsForCard: create the right number of subIndex rows - * for a card, soft-delete obsolete ones (e.g. when a cloze cluster - * gets removed). Idempotent — safe to call after every card edit. - * - grade: apply a user rating, persist the next FSRS state. - * - softDeleteForCard: cascade soft-delete when a card is deleted. - */ - -import { cardReviewTable } from '../collections'; -import { newReview, gradeReview as fsrsGrade } from '../fsrs'; -import { subIndexesFor } from '../card-reviews'; -import type { CardFields, CardType, LocalCardReview, ReviewGrade } from '../types'; - -let error = $state(null); - -export const reviewStore = { - get error() { - return error; - }, - - /** - * Reconcile the cardReviews rows for a card with what the card - * structurally needs. New subIndexes get a fresh review; obsolete - * ones get soft-deleted. Returns the live set of reviews. - */ - async ensureReviewsForCard(card: { - id: string; - type: CardType; - fields: CardFields; - }): Promise { - error = null; - try { - const existing = await cardReviewTable.where('cardId').equals(card.id).toArray(); - const live = existing.filter((r) => !r.deletedAt); - const liveByIdx = new Map(live.map((r) => [r.subIndex, r])); - - const wanted = subIndexesFor(card); - const wantedSet = new Set(wanted); - const nowIso = new Date().toISOString(); - - for (const subIndex of wanted) { - if (!liveByIdx.has(subIndex)) { - const r = newReview({ cardId: card.id, subIndex }); - await cardReviewTable.add(r); - liveByIdx.set(subIndex, r); - } - } - - for (const r of live) { - if (!wantedSet.has(r.subIndex)) { - await cardReviewTable.update(r.id, { deletedAt: nowIso }); - liveByIdx.delete(r.subIndex); - } - } - - return [...liveByIdx.values()].sort((a, b) => a.subIndex - b.subIndex); - } catch (err: any) { - error = err.message || 'Failed to ensure reviews'; - console.error('Ensure reviews error:', err); - return []; - } - }, - - async grade(reviewId: string, grade: ReviewGrade): Promise { - error = null; - try { - const existing = await cardReviewTable.get(reviewId); - if (!existing) return null; - const next = fsrsGrade(existing, grade); - await cardReviewTable.put(next); - return next; - } catch (err: any) { - error = err.message || 'Failed to grade review'; - console.error('Grade review error:', err); - return null; - } - }, - - async softDeleteForCard(cardId: string): Promise { - const reviews = await cardReviewTable.where('cardId').equals(cardId).toArray(); - const now = new Date().toISOString(); - for (const r of reviews) { - if (!r.deletedAt) await cardReviewTable.update(r.id, { deletedAt: now }); - } - }, - - clearError() { - error = null; - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/study-blocks.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/study-blocks.svelte.ts deleted file mode 100644 index 888d537b0..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/study-blocks.svelte.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Study-Block Store — daily aggregate of learning activity. - * - * One row per local date with counters. The streak query walks back - * from today; finding a gap (no row, or cardsReviewed=0) ends the - * streak. Plaintext, no encryption. - * - * Why a daily aggregate row instead of just summing cardReviews? - * Because the streak is a UI-hot read — we want it cheap (≤ 30 row - * lookups) regardless of how many reviews exist in total. - */ - -import { cardStudyBlockTable } from '../collections'; -import type { LocalCardStudyBlock } from '../types'; - -let error = $state(null); - -function localDateKey(d: Date = new Date()): string { - // YYYY-MM-DD in the user's local timezone — matches LocalCardStudyBlock.date. - const y = d.getFullYear(); - const m = `${d.getMonth() + 1}`.padStart(2, '0'); - const day = `${d.getDate()}`.padStart(2, '0'); - return `${y}-${m}-${day}`; -} - -export const studyBlockStore = { - get error() { - return error; - }, - - /** - * Record one review against today's block. Creates the row on the - * first review of the day. Idempotent across concurrent calls only - * within a Dexie transaction — for now we accept the small chance of - * an off-by-one race; real users grade one card at a time. - */ - async recordReview(durationMs: number, count: number = 1): Promise { - error = null; - try { - const date = localDateKey(); - const existing = await cardStudyBlockTable.where('date').equals(date).first(); - if (existing && !existing.deletedAt) { - await cardStudyBlockTable.update(existing.id, { - cardsReviewed: existing.cardsReviewed + count, - durationMs: existing.durationMs + durationMs, - }); - } else { - const row: LocalCardStudyBlock = { - id: crypto.randomUUID(), - date, - cardsReviewed: count, - durationMs, - }; - await cardStudyBlockTable.add(row); - } - } catch (err: any) { - error = err.message || 'Failed to record review'; - console.error('Record review error:', err); - } - }, - - /** - * Walk back from today; return how many consecutive days have at - * least one reviewed card. Stops at the first gap. Caps at 365 days - * to keep the worst case bounded. - */ - async getRecentStreak(): Promise { - const today = new Date(); - let streak = 0; - for (let i = 0; i < 365; i++) { - const d = new Date(today); - d.setDate(d.getDate() - i); - const row = await cardStudyBlockTable.where('date').equals(localDateKey(d)).first(); - if (!row || row.deletedAt || row.cardsReviewed <= 0) break; - streak++; - } - return streak; - }, - - clearError() { - error = null; - }, -}; diff --git a/apps/mana/apps/web/src/lib/modules/cards/stores/tags.svelte.ts b/apps/mana/apps/web/src/lib/modules/cards/stores/tags.svelte.ts deleted file mode 100644 index d960a888c..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/stores/tags.svelte.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Ucards Tags — Uses shared global tags + module-specific junction table. - */ - -import { db } from '$lib/data/database'; -import { createTagLinkOps } from '@mana/shared-stores'; - -export { - tagMutations, - useAllTags, - getTagById, - getTagsByIds, - getTagColor, -} from '@mana/shared-stores'; - -export const deckTagOps = createTagLinkOps({ - table: () => db.table('deckTags'), - entityIdField: 'deckId', -}); diff --git a/apps/mana/apps/web/src/lib/modules/cards/tools.ts b/apps/mana/apps/web/src/lib/modules/cards/tools.ts deleted file mode 100644 index 8133eb6da..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/tools.ts +++ /dev/null @@ -1,25 +0,0 @@ -import type { ModuleTool } from '$lib/data/tools/types'; -import { cardStore } from './stores/cards.svelte'; - -export const cardsTools: ModuleTool[] = [ - { - name: 'create_card', - module: 'cards', - description: 'Erstellt eine neue Lernkarte (Flashcard)', - parameters: [ - { name: 'deckId', type: 'string', description: 'ID des Decks', required: true }, - { name: 'front', type: 'string', description: 'Vorderseite (Frage)', required: true }, - { name: 'back', type: 'string', description: 'Rueckseite (Antwort)', required: true }, - ], - async execute(params) { - const card = await cardStore.createCard({ - deckId: params.deckId as string, - front: params.front as string, - back: params.back as string, - }); - return card - ? { success: true, data: card, message: 'Lernkarte erstellt' } - : { success: false, message: 'Fehler beim Erstellen der Karte' }; - }, - }, -]; diff --git a/apps/mana/apps/web/src/lib/modules/cards/types.ts b/apps/mana/apps/web/src/lib/modules/cards/types.ts deleted file mode 100644 index 263c01487..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Cardecky / cards module — types are now sourced from `@mana/cards-core` - * so the standalone cardecky.mana.how app and this in-mana module stay in sync. - * - * This file is a thin re-export to keep existing - * `from './types'` / `from '$lib/modules/cards/types'` imports working. - */ - -export type { - CardType, - CardFields, - LocalDeck, - LocalCard, - LocalCardReview, - LocalCardStudyBlock, - Deck, - Card, - CardReview, - CreateDeckInput, - UpdateDeckInput, - CreateCardInput, - UpdateCardInput, - ReviewGrade, -} from '@mana/cards-core'; diff --git a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte b/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte deleted file mode 100644 index f3746c84b..000000000 --- a/apps/mana/apps/web/src/lib/modules/cards/views/DetailView.svelte +++ /dev/null @@ -1,147 +0,0 @@ - - - - - detail.deleteWithUndo({ - label: $_('cards.detail.toast_deleted'), - delete: () => deckStore.deleteDeck(deckId), - goBack, - })} -> - {#snippet body(deck)} - - -
-
- {$_('cards.detail.prop_color')} - -
- -
- {$_('cards.detail.prop_visibility')} - deckStore.setVisibility(deckId, next)} - disabledLevels={['unlisted']} - /> -
- -
- {$_('cards.detail.prop_cards')} - {cardCount} -
- - {#if deck.lastStudied} -
- {$_('cards.detail.prop_last_studied')} - {formatDate(new Date(deck.lastStudied))} -
- {/if} -
- -
- - -
- -
- {$_('cards.detail.meta_created', { - values: { date: formatDate(new Date(deck.createdAt ?? '')) }, - })} - {#if deck.updatedAt} - {$_('cards.detail.meta_updated', { - values: { date: formatDate(new Date(deck.updatedAt)) }, - })} - {/if} -
- {/snippet} -
diff --git a/apps/mana/apps/web/src/lib/modules/website/embeds.ts b/apps/mana/apps/web/src/lib/modules/website/embeds.ts index 6e9f52247..c13677eb4 100644 --- a/apps/mana/apps/web/src/lib/modules/website/embeds.ts +++ b/apps/mana/apps/web/src/lib/modules/website/embeds.ts @@ -35,7 +35,6 @@ import type { LocalHabit, LocalHabitLog } from '$lib/modules/habits/types'; import type { LocalQuiz } from '$lib/modules/quiz/types'; import type { LocalSocialEvent } from '$lib/modules/events/types'; import type { LocalMemo } from '$lib/modules/memoro/types'; -import type { LocalDeck as LocalCardDeck } from '$lib/modules/cards/types'; import type { LocalDeck as LocalPresiDeck } from '$lib/modules/presi/types'; import type { LocalAugurEntry } from '$lib/modules/augur/types'; import type { LocalTimeBlock } from '$lib/data/time-blocks/types'; @@ -91,9 +90,11 @@ export async function resolveEmbed(props: ModuleEmbedProps): Promise { }); } -/** - * Card-decks: shareable-flashcard-collection teaser. Returns decks - * flipped to 'public' with their card count as subtitle. - * - * Whitelist: title + "N Karten". Card fronts/backs, difficulty - * scores, and review history all stay private — the deck is a - * unit; its cards belong to the play-experience (future - * unlisted-share flow), not the public teaser. - */ -async function resolveCardDecks(_props: ModuleEmbedProps): Promise { - let decks = await db.table('cardDecks').toArray(); - decks = decks.filter((d) => !d.deletedAt && canEmbedOnWebsite(d.visibility ?? 'private')); - - if (decks.length === 0) return []; - - const decrypted = (await decryptRecords('cardDecks', decks)) as LocalCardDeck[]; - - // Newest first. - decrypted.sort((a, b) => (b.updatedAt ?? '').localeCompare(a.updatedAt ?? '')); - - return decrypted.map((d) => { - const count = d.cardCount ?? 0; - return { - title: d.name, - subtitle: `${count} ${count === 1 ? 'Karte' : 'Karten'}`, - }; - }); -} +// resolveCardDecks: dekommissioniert 2026-05-08, Cards lebt eigenständig +// auf cardecky.mana.how. Public-Deck-Embeds für Cardecky kommen später +// über die Cardecky-API. /** * Presi-decks: "talks I've given" teaser. Returns decks flipped to diff --git a/apps/mana/apps/web/src/lib/search/providers/cards.ts b/apps/mana/apps/web/src/lib/search/providers/cards.ts deleted file mode 100644 index 12ee0046e..000000000 --- a/apps/mana/apps/web/src/lib/search/providers/cards.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { db } from '$lib/data/database'; -import { getManaApp } from '@mana/shared-branding'; -import { scoreRecord, truncateSubtitle } from '../scoring'; -import type { SearchProvider, SearchResult, SearchOptions } from '../types'; - -const app = getManaApp('cards'); - -export const cardsSearchProvider: SearchProvider = { - appId: 'cards', - appName: 'Cards', - appIcon: app?.icon, - appColor: app?.color, - searchableTypes: ['deck', 'card'], - - async search(query: string, options?: SearchOptions): Promise { - const limit = options?.limit ?? 5; - const results: SearchResult[] = []; - - // Search decks - const decks = await db.table('cardDecks').toArray(); - for (const deck of decks) { - if (deck.deletedAt) continue; - const { score, matchedField } = scoreRecord( - [ - { name: 'name', value: deck.name, weight: 1.0 }, - { name: 'description', value: deck.description, weight: 0.7 }, - ], - query - ); - if (score > 0) { - results.push({ - id: deck.id, - type: 'deck', - appId: 'cards', - title: deck.name, - subtitle: truncateSubtitle(deck.description) || 'Deck', - appIcon: app?.icon, - appColor: app?.color, - href: `/cards/${deck.id}`, - score, - matchedField, - }); - } - } - - // Search cards (front/back) - const cards = await db.table('cards').toArray(); - for (const card of cards) { - if (card.deletedAt) continue; - const { score, matchedField } = scoreRecord( - [ - { name: 'front', value: card.front, weight: 1.0 }, - { name: 'back', value: card.back, weight: 0.8 }, - ], - query - ); - if (score > 0) { - results.push({ - id: card.id, - type: 'card', - appId: 'cards', - title: truncateSubtitle(card.front, 60) || 'Karte', - subtitle: truncateSubtitle(card.back, 60), - appIcon: app?.icon, - appColor: app?.color, - href: `/cards/${card.deckId}`, - score, - matchedField, - }); - } - } - - return results.sort((a, b) => b.score - a.score).slice(0, limit); - }, -}; diff --git a/apps/mana/apps/web/src/lib/search/providers/index.ts b/apps/mana/apps/web/src/lib/search/providers/index.ts index f97ca975d..9b600e682 100644 --- a/apps/mana/apps/web/src/lib/search/providers/index.ts +++ b/apps/mana/apps/web/src/lib/search/providers/index.ts @@ -20,7 +20,7 @@ export function registerAllProviders(registry: SearchRegistry): void { ); registry.registerLazy('chat', () => import('./chat').then((m) => m.chatSearchProvider)); registry.registerLazy('storage', () => import('./storage').then((m) => m.storageSearchProvider)); - registry.registerLazy('cards', () => import('./cards').then((m) => m.cardsSearchProvider)); + // 'cards': dekommissioniert 2026-05-08 — Cards eigenständig auf cardecky.mana.how. registry.registerLazy('picture', () => import('./picture').then((m) => m.pictureSearchProvider)); registry.registerLazy('presi', () => import('./presi').then((m) => m.presiSearchProvider)); registry.registerLazy('music', () => import('./music').then((m) => m.musicSearchProvider)); diff --git a/apps/mana/apps/web/src/lib/types/dashboard.test.ts b/apps/mana/apps/web/src/lib/types/dashboard.test.ts index 8c3328aa8..fe31962ed 100644 --- a/apps/mana/apps/web/src/lib/types/dashboard.test.ts +++ b/apps/mana/apps/web/src/lib/types/dashboard.test.ts @@ -75,7 +75,6 @@ describe('WIDGET_REGISTRY', () => { expect(types).toContain('contacts-favorites'); expect(types).toContain('quotes-quote'); expect(types).toContain('picture-recent'); - expect(types).toContain('cards-progress'); expect(types).toContain('clock-timers'); expect(types).toContain('storage-usage'); expect(types).toContain('music-library'); diff --git a/apps/mana/apps/web/src/lib/types/dashboard.ts b/apps/mana/apps/web/src/lib/types/dashboard.ts index 3ae0f7402..820abc01a 100644 --- a/apps/mana/apps/web/src/lib/types/dashboard.ts +++ b/apps/mana/apps/web/src/lib/types/dashboard.ts @@ -19,7 +19,6 @@ export type WidgetType = | 'contacts-recent' // Contacts: recently updated | 'quotes-quote' // Quotes API: daily inspiration quote | 'picture-recent' // Picture API: recent generations - | 'cards-progress' // Cards API: learning progress | 'clock-timers' // Clock: active timers and alarms | 'storage-usage' // Storage: file storage stats | 'music-library' // Music: music library stats @@ -232,15 +231,6 @@ export const WIDGET_REGISTRY: WidgetMeta[] = [ allowMultiple: false, requiredBackend: 'picture', }, - { - type: 'cards-progress', - nameKey: 'dashboard.widgets.cards.title', - descriptionKey: 'dashboard.widgets.cards.description', - icon: '🎴', - defaultSize: 'medium', - allowMultiple: false, - requiredBackend: 'cards', - }, { type: 'clock-timers', nameKey: 'dashboard.widgets.clock.title', diff --git a/apps/mana/apps/web/src/routes/(app)/cards/+layout.svelte b/apps/mana/apps/web/src/routes/(app)/cards/+layout.svelte deleted file mode 100644 index 7e0db5dc8..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/+layout.svelte +++ /dev/null @@ -1,15 +0,0 @@ - - -{@render children()} diff --git a/apps/mana/apps/web/src/routes/(app)/cards/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/+page.svelte deleted file mode 100644 index ae85d9971..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/+page.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - - - Cards - Mana - - - -
-
-

Cards

-

Karteikarten & Spaced Repetition

-
- - -
-
-
- -
-
-
Lerne effizienter
-
- Karteikarten erstellen, organisieren und mit Spaced Repetition lernen -
-
-
-
- - -
- {#each quickLinks as link} - -
-
- -
-
-
{link.label}
-
{link.description}
-
-
-
- {/each} -
-
-
diff --git a/apps/mana/apps/web/src/routes/(app)/cards/decks/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/decks/+page.svelte deleted file mode 100644 index dde873bdc..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/decks/+page.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - - - Meine Decks - Cards - Mana - - - -
- -
-
-

Meine Decks

-

Organisiere deine Lernmaterialien in Decks

-
- -
- - - {#if deckStore.error} -
-

{$_('common.error_loading')}

-

{deckStore.error}

-
- {:else if (allDecks?.value ?? []).length === 0} - -
-
📚
-

Noch keine Decks

-

- Erstelle dein erstes Deck, um mit dem Lernen zu beginnen. -

- -
- {:else} - -
- {#each allDecks.value as deck (deck.id)} - handleDeckClick(deck.id)} /> - {/each} -
- {/if} -
- - - -
diff --git a/apps/mana/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte deleted file mode 100644 index fb1ca5de6..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/decks/[id]/+page.svelte +++ /dev/null @@ -1,410 +0,0 @@ - - - - {deck?.title || 'Deck'} — Cards — Mana - - - - {#if deck} -
- - - -
-
-
-
-

{deck.title}

-
- {#if deck.description} -

{deck.description}

- {/if} -
- -
- {#if deck.visibility === 'public'} - Öffentlich - {/if} - - -
-
- - -
- - {#if dueCount === 0 && cards.length > 0} - - Heute alles gelernt — schau später wieder rein. - - {/if} -
- - -
-
-
{cards.length}
-
Karten gesamt
-
-
-
{dueCount}
-
Fällig
-
-
- - -
- -
- - - {#if showNewCardForm} -
-

Neue Karte

- - -
- {#each cardTypeOptions as opt} - - {/each} -
- -
- {#if newCardType === 'cloze'} -
- - - -

- Markiere mit - {{c1::Wort}} - — optional Hinweis: ::Hinweis. -

-
- {:else} -
- - - -
-
- - -
- {/if} -
- - -
-
-
- {/if} - - -
-

- Karten ({cards.length}) -

- {#if cards.length === 0} -
-
📝
-

Noch keine Karten. Erstelle deine erste Karte!

- -
- {:else} -
- {#each cards as card, i (card.id)} - {@const preview = previewSummary(card)} -
- {i + 1}. -
-
- {@html renderMarkdown(preview.primary)} -
- {#if preview.secondary} -
- {@html renderMarkdown(preview.secondary)} -
- {/if} -
-
- - {typeBadge(card.type)} - - -
-
- {/each} -
- {/if} -
- - - {#if showDeleteConfirm} -
(showDeleteConfirm = false)} - onkeydown={(e) => e.key === 'Escape' && (showDeleteConfirm = false)} - tabindex="-1" - role="presentation" - > - - -
e.stopPropagation()} - > -

Deck löschen?

-

- Möchtest du "{deck.title}" wirklich löschen? Diese Aktion kann nicht rückgängig - gemacht werden und löscht auch alle Karten in diesem Deck. -

-
- - -
-
-
- {/if} -
- {:else} -
-

Deck nicht gefunden

- -
- {/if} - - (showShare = false)} - url={shareUrl} - title={deck?.title ?? ''} - source="cards" - description={deck?.description ?? ''} - /> -
diff --git a/apps/mana/apps/web/src/routes/(app)/cards/explore/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/explore/+page.svelte deleted file mode 100644 index 147dd12f5..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/explore/+page.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - - - Entdecken - Cards - Mana - - - -
-
-

Entdecken

-

- Offentliche Decks aus der Community entdecken -

-
- -
-
-
- -
-

Entdecken-Feature

-

- Offentliche Decks durchsuchen und entdecken — kommt bald! -

-
-
-
-
diff --git a/apps/mana/apps/web/src/routes/(app)/cards/learn/[deckId]/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/learn/[deckId]/+page.svelte deleted file mode 100644 index 5a16ba8dc..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/learn/[deckId]/+page.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - -
-
-
- -

Lernen

-
- {#if queue.length > 0 && !finished} -
- {Math.min(currentIndex + 1, queue.length)} / {queue.length} -
- {/if} -
- - {#if empty} -
-
Alles gelernt
-

- Komm später wieder — fällige Karten erscheinen automatisch. -

- -
- {:else if finished} -
-
Session abgeschlossen
-

- {sessionCount} Karten in {Math.round((Date.now() - sessionStartedAt) / 1000)} s. -

- -
- {:else if current} - (typedAnswer = v)} - /> - - {#if !showBack} - - {:else} -
- - - - -
- {/if} - {:else} -
Lade…
- {/if} -
diff --git a/apps/mana/apps/web/src/routes/(app)/cards/progress/+page.svelte b/apps/mana/apps/web/src/routes/(app)/cards/progress/+page.svelte deleted file mode 100644 index ccaa516df..000000000 --- a/apps/mana/apps/web/src/routes/(app)/cards/progress/+page.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - - {$_('cards.progress.page_title_html')} - - - -
-
-

{$_('cards.progress.heading')}

-

{$_('cards.progress.subtitle')}

-
- - -
-
-
{decks.length}
-
{$_('cards.progress.stat_decks')}
-
-
-
{totalCards}
-
{$_('cards.progress.stat_total_cards')}
-
-
-
0
-
{$_('cards.progress.stat_due')}
-
-
- - -
-

- - - {$_('cards.progress.section_overview')} - -

- {#if decks.length === 0} -
-
🎯
-

{$_('cards.progress.empty_title')}

-

{$_('cards.progress.empty_hint')}

-
- {:else} -
- {#each decks as deck (deck.id)} -
-
-
-
-
{deck.title}
-
- {$_('cards.progress.deck_cards', { values: { n: deck.cardCount || 0 } })} -
-
-
-
-
- {formatDate(new Date(deck.updatedAt), { - day: '2-digit', - month: 'short', - })} -
-
-
- {/each} -
- {/if} -
-
-
diff --git a/packages/shared-branding/src/mana-apps.ts b/packages/shared-branding/src/mana-apps.ts index e2856d244..670690fc6 100644 --- a/packages/shared-branding/src/mana-apps.ts +++ b/packages/shared-branding/src/mana-apps.ts @@ -153,23 +153,9 @@ export const MANA_APPS: ManaApp[] = [ status: 'development', requiredTier: 'guest', }, - { - id: 'cards', - name: 'Cardecky', - description: { - de: 'KI Karteikarten', - en: 'AI Flashcards', - }, - longDescription: { - de: 'Lerne intelligenter mit KI-generierten Karteikarten und Spaced Repetition.', - en: 'Learn smarter with AI-generated flashcards and spaced repetition.', - }, - icon: APP_ICONS.cards, - color: '#8b5cf6', - comingSoon: false, - status: 'development', - requiredTier: 'guest', - }, + // Cards/Cardecky: dekommissioniert 2026-05-08 — eigenständig auf + // cardecky.mana.how (git.mana.how/till/cards). App-Eintrag bleibt + // nur in der Standalone-App. { id: 'quiz', name: 'Quiz', diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b1dafebb..98d87c10b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,105 +183,6 @@ importers: specifier: ~5.9.2 version: 5.9.3 - apps/cards: {} - - apps/cards/apps/web: - dependencies: - '@mana/cards-core': - specifier: workspace:* - version: link:../../../../packages/cards-core - '@mana/local-store': - specifier: workspace:* - version: link:../../../../packages/local-store - '@mana/shared-auth': - specifier: workspace:* - version: link:../../../../packages/shared-auth - '@mana/shared-auth-ui': - specifier: workspace:* - version: link:../../../../packages/shared-auth-ui - '@mana/shared-branding': - specifier: workspace:* - version: link:../../../../packages/shared-branding - '@mana/shared-icons': - specifier: workspace:* - version: link:../../../../packages/shared-icons - '@mana/shared-privacy': - specifier: workspace:* - version: link:../../../../packages/shared-privacy - '@mana/shared-pwa': - specifier: workspace:* - version: link:../../../../packages/shared-pwa - '@mana/shared-stores': - specifier: workspace:* - version: link:../../../../packages/shared-stores - '@mana/shared-tailwind': - specifier: workspace:* - version: link:../../../../packages/shared-tailwind - '@mana/shared-theme': - specifier: workspace:* - version: link:../../../../packages/shared-theme - '@mana/shared-theme-ui': - specifier: workspace:* - version: link:../../../../packages/shared-theme-ui - '@mana/shared-types': - specifier: workspace:* - version: link:../../../../packages/shared-types - '@mana/shared-utils': - specifier: workspace:* - version: link:../../../../packages/shared-utils - dexie: - specifier: ^4.4.1 - version: 4.4.2 - jszip: - specifier: ^3.10.1 - version: 3.10.1 - pdfjs-dist: - specifier: ^5.7.284 - version: 5.7.284 - sql.js: - specifier: ^1.14.1 - version: 1.14.1 - devDependencies: - '@mana/shared-vite-config': - specifier: workspace:* - version: link:../../../../packages/shared-vite-config - '@sveltejs/adapter-node': - specifier: ^5.0.0 - version: 5.5.4(@sveltejs/kit@2.56.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.9.3)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))) - '@sveltejs/kit': - specifier: ^2.47.1 - version: 2.56.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.9.3)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@sveltejs/vite-plugin-svelte': - specifier: ^5.0.4 - version: 5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@tailwindcss/vite': - specifier: ^4.1.7 - version: 4.2.2(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - '@types/node': - specifier: ^22.10.5 - version: 22.19.17 - '@types/sql.js': - specifier: ^1.4.11 - version: 1.4.11 - '@vite-pwa/sveltekit': - specifier: ^1.1.0 - version: 1.1.0(@sveltejs/kit@2.56.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.9.3)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))(workbox-build@7.4.0(@types/babel__core@7.20.5))(workbox-window@7.4.0) - svelte: - specifier: ^5.41.0 - version: 5.55.1 - svelte-check: - specifier: ^4.3.3 - version: 4.4.6(picomatch@4.0.4)(svelte@5.55.1)(typescript@5.9.3) - tailwindcss: - specifier: ^4.1.17 - version: 4.2.2 - typescript: - specifier: ^5.7.2 - version: 5.9.3 - vite: - specifier: ^6.0.7 - version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3) - apps/chat: {} apps/chat/apps/landing: @@ -561,9 +462,6 @@ importers: '@huggingface/transformers': specifier: ^4.0.0 version: 4.0.1 - '@mana/cards-core': - specifier: workspace:* - version: link:../../../../packages/cards-core '@mana/credits': specifier: workspace:^ version: link:../../../../packages/credits @@ -1745,34 +1643,6 @@ importers: specifier: ^5.0.0 version: 5.9.3 - packages/cards-core: - dependencies: - '@mana/local-store': - specifier: workspace:* - version: link:../local-store - '@mana/shared-privacy': - specifier: workspace:* - version: link:../shared-privacy - isomorphic-dompurify: - specifier: ^3.7.1 - version: 3.7.1(@noble/hashes@2.0.1) - marked: - specifier: ^17.0.5 - version: 17.0.6 - ts-fsrs: - specifier: ^5.3.2 - version: 5.3.2 - devDependencies: - '@types/node': - specifier: ^24.10.1 - version: 24.12.2 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - vitest: - specifier: ^4.1.3 - version: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)) - packages/credits: devDependencies: svelte: @@ -2581,34 +2451,6 @@ importers: specifier: ^2.0.0 version: 2.1.9(@types/node@24.12.2)(jsdom@29.0.2(@noble/hashes@2.0.1))(lightningcss@1.32.0)(terser@5.46.1) - services/cards-server: - dependencies: - '@mana/shared-hono': - specifier: workspace:* - version: link:../../packages/shared-hono - drizzle-orm: - specifier: ^0.38.3 - version: 0.38.4(@opentelemetry/api@1.9.1)(@types/pg@8.6.1)(@types/react@19.2.14)(@types/sql.js@1.4.11)(bun-types@1.3.13)(kysely@0.28.15)(postgres@3.4.9)(react@19.2.0)(sql.js@1.14.1) - hono: - specifier: ^4.7.0 - version: 4.12.12 - jose: - specifier: ^6.1.2 - version: 6.2.2 - postgres: - specifier: ^3.4.5 - version: 3.4.9 - zod: - specifier: ^3.24.0 - version: 3.25.76 - devDependencies: - drizzle-kit: - specifier: ^0.30.4 - version: 0.30.6 - typescript: - specifier: ^5.9.3 - version: 5.9.3 - services/mana-ai: dependencies: '@mana/shared-ai': @@ -5944,76 +5786,6 @@ packages: resolution: {integrity: sha512-Z+CZ3QaosfFaTqvhQsIktyGrjFjSC0Fa4EMph4mqKnWhmyoGICsV/8QK+8HpXut6zV7zwfWwqDmEjtk1Qf6EgQ==} engines: {node: '>=14.0.0'} - '@napi-rs/canvas-android-arm64@0.1.100': - resolution: {integrity: sha512-hjhCKhntPv9+t4ckHymdx0phYNcVW+GKQR6Lzw2zE+pOVjOplSmtx9nNNknTjbEDLcuLZqA1y8ufKg1XfgftzQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [android] - - '@napi-rs/canvas-darwin-arm64@0.1.100': - resolution: {integrity: sha512-2PcswRaC7Ly645DGt88///zuFDhJxJYdKAs1uU3mfk1atYkXufgcgLfBpk6Tm12nCQBaNt1wpybuPZ4qOhTo8A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@napi-rs/canvas-darwin-x64@0.1.100': - resolution: {integrity: sha512-ePNZtj7pNIva/siZMg+HmbeozkIjqUIYdoymH8HaA3qK7LfzFN4WMBM8G6HQ9ZC+H3+Dnn5pqtiXpgLykaPOhw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': - resolution: {integrity: sha512-d5cDB48oWFGU8/XPhUOFAlySgb/VAu7D+s8fi55K1Pcfg8aPplHWqMgibhVLU8ky7Pyg/fuiVLz4Nf3JrSTuUA==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - - '@napi-rs/canvas-linux-arm64-gnu@0.1.100': - resolution: {integrity: sha512-rDxgxRu69RvDlX/bh9o22DxLsGr8EqsNgotL9+RwQE1S0b0cqeatqsw6aW45mukm0B42DIAaAacKaYQ8cqS1nw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/canvas-linux-arm64-musl@0.1.100': - resolution: {integrity: sha512-K3mDW66N+xT2/V439u1alFANiBUjdEx2gLiNYnCmUsva5jZMxWTjafBYwTzYK+EMFMHrUoabuU+T1BIP5CgbYQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - - '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': - resolution: {integrity: sha512-mooqUBTIsccZpnoQC4NgrC1v6C1vof39etLNMnBwCY+p0gajWJvAHLGQ6g/gGyS5YrpDW+GefSN4+Cvcr08UWw==} - engines: {node: '>= 10'} - cpu: [riscv64] - os: [linux] - - '@napi-rs/canvas-linux-x64-gnu@0.1.100': - resolution: {integrity: sha512-1eCvkDCazm7FFhsT7DfGOdSaHgZVK3bt/dSBl5EWHOWmnz+I7j8tPseJqqD81NF+MH21jKUK4wQSDjN0mdhnTg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/canvas-linux-x64-musl@0.1.100': - resolution: {integrity: sha512-20arT6lnI19S68qNlii73TSEDbECNgzMz2EpldC1V3mZFuRkeujXkcebRk0LRJe9SEUAooYiLokfMViY8IX7yA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - - '@napi-rs/canvas-win32-arm64-msvc@0.1.100': - resolution: {integrity: sha512-DZFFT1wIAg37LJw37yhMRFfjATd3vTQzjZ1Yki8u2vhO6Hi5VE6BVaGQ1aaDu7xb4iMErz+9EOwjpS7xcxFeBw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@napi-rs/canvas-win32-x64-msvc@0.1.100': - resolution: {integrity: sha512-MyT1j3mHC2+Lu4pBi9mKyMJhtP6U7k7EldY7sj/uS5gJA65gTXt8MefJQXLJo5d/vZbuWmfxzkEUNc/urV3pHA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@napi-rs/canvas@0.1.100': - resolution: {integrity: sha512-xglYA6q3XO5P3BNJYxVZ1IV7DLVjp1Py6nwag88YntrS+3vKHyYcMqXVS4ZztJmwz2uGvz1FWhI/4LgbR5uQDA==} - engines: {node: '>= 10'} - '@nestjs/cli@10.4.9': resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} engines: {node: '>= 16.14'} @@ -11649,9 +11421,6 @@ packages: engines: {node: '>=16.x'} hasBin: true - immediate@3.0.6: - resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} @@ -11934,9 +11703,6 @@ packages: resolution: {integrity: sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==} engines: {node: '>=16'} - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -12269,9 +12035,6 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} - jszip@3.10.1: - resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} - keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -12323,9 +12086,6 @@ packages: libphonenumber-js@1.12.41: resolution: {integrity: sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA==} - lie@3.3.0: - resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} - lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} @@ -13497,10 +13257,6 @@ packages: pdf-lib@1.17.1: resolution: {integrity: sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==} - pdfjs-dist@5.7.284: - resolution: {integrity: sha512-h4EdYQczmGhbOlqc3PPZwxevn7ApdWPbovAuWXOB/DjIyigSnwfy2oze7c6mRcSr9XgLp3eN3EeL4DyySTPMFw==} - engines: {node: '>=22.13.0 || >=24'} - pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -13837,9 +13593,6 @@ packages: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -14224,9 +13977,6 @@ packages: read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -14539,9 +14289,6 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -14926,9 +14673,6 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -20190,54 +19934,6 @@ snapshots: '@mozilla/readability@0.5.0': {} - '@napi-rs/canvas-android-arm64@0.1.100': - optional: true - - '@napi-rs/canvas-darwin-arm64@0.1.100': - optional: true - - '@napi-rs/canvas-darwin-x64@0.1.100': - optional: true - - '@napi-rs/canvas-linux-arm-gnueabihf@0.1.100': - optional: true - - '@napi-rs/canvas-linux-arm64-gnu@0.1.100': - optional: true - - '@napi-rs/canvas-linux-arm64-musl@0.1.100': - optional: true - - '@napi-rs/canvas-linux-riscv64-gnu@0.1.100': - optional: true - - '@napi-rs/canvas-linux-x64-gnu@0.1.100': - optional: true - - '@napi-rs/canvas-linux-x64-musl@0.1.100': - optional: true - - '@napi-rs/canvas-win32-arm64-msvc@0.1.100': - optional: true - - '@napi-rs/canvas-win32-x64-msvc@0.1.100': - optional: true - - '@napi-rs/canvas@0.1.100': - optionalDependencies: - '@napi-rs/canvas-android-arm64': 0.1.100 - '@napi-rs/canvas-darwin-arm64': 0.1.100 - '@napi-rs/canvas-darwin-x64': 0.1.100 - '@napi-rs/canvas-linux-arm-gnueabihf': 0.1.100 - '@napi-rs/canvas-linux-arm64-gnu': 0.1.100 - '@napi-rs/canvas-linux-arm64-musl': 0.1.100 - '@napi-rs/canvas-linux-riscv64-gnu': 0.1.100 - '@napi-rs/canvas-linux-x64-gnu': 0.1.100 - '@napi-rs/canvas-linux-x64-musl': 0.1.100 - '@napi-rs/canvas-win32-arm64-msvc': 0.1.100 - '@napi-rs/canvas-win32-x64-msvc': 0.1.100 - optional: true - '@nestjs/cli@10.4.9': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -22996,7 +22692,8 @@ snapshots: '@types/earcut@3.0.0': {} - '@types/emscripten@1.41.5': {} + '@types/emscripten@1.41.5': + optional: true '@types/eslint-scope@3.7.7': dependencies: @@ -23175,6 +22872,7 @@ snapshots: dependencies: '@types/emscripten': 1.41.5 '@types/node': 22.19.17 + optional: true '@types/stack-utils@2.0.3': {} @@ -27854,8 +27552,6 @@ snapshots: dependencies: queue: 6.0.2 - immediate@3.0.6: {} - import-fresh@3.3.1: dependencies: parent-module: 1.0.1 @@ -28152,8 +27848,6 @@ snapshots: dependencies: is-inside-container: 1.0.0 - isarray@1.0.0: {} - isarray@2.0.5: {} isexe@2.0.0: {} @@ -28740,13 +28434,6 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 - jszip@3.10.1: - dependencies: - lie: 3.3.0 - pako: 1.0.11 - readable-stream: 2.3.8 - setimmediate: 1.0.5 - keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -28783,10 +28470,6 @@ snapshots: libphonenumber-js@1.12.41: {} - lie@3.3.0: - dependencies: - immediate: 3.0.6 - lighthouse-logger@1.4.2: dependencies: debug: 2.6.9 @@ -30451,10 +30134,6 @@ snapshots: pako: 1.0.11 tslib: 1.14.1 - pdfjs-dist@5.7.284: - optionalDependencies: - '@napi-rs/canvas': 0.1.100 - pend@1.2.0: {} performance-now@2.1.0: @@ -30701,8 +30380,6 @@ snapshots: proc-log@4.2.0: {} - process-nextick-args@2.0.1: {} - progress@2.0.3: {} prom-client@15.1.3: @@ -31372,16 +31049,6 @@ snapshots: dependencies: pify: 2.3.0 - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -31821,8 +31488,6 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 - safe-buffer@5.1.2: {} - safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -32189,7 +31854,8 @@ snapshots: sprintf-js@1.1.3: {} - sql.js@1.14.1: {} + sql.js@1.14.1: + optional: true stack-utils@2.0.6: dependencies: @@ -32308,10 +31974,6 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1