feat: rename mukke to music, add cover art upload via mana-media

Rename the music module from "Mukke" to "Music" across the entire
codebase: API routes, web app module, shared packages, search provider,
dashboard widgets, i18n keys, app registry, and route paths.

Add POST /api/v1/music/cover/upload endpoint that uploads cover art
images through mana-media for deduplication, thumbnails, and Photos
gallery visibility.

Dexie table names (mukkePlaylists, mukkeProjects) kept unchanged to
preserve existing IndexedDB data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-05 15:25:34 +02:00
parent ee7ff7d5e8
commit d4700a07f9
64 changed files with 258 additions and 214 deletions

View file

@ -18,7 +18,7 @@ import {
// Module routes
import { calendarRoutes } from './modules/calendar/routes';
import { contactsRoutes } from './modules/contacts/routes';
import { mukkeRoutes } from './modules/mukke/routes';
import { musicRoutes } from './modules/music/routes';
import { chatRoutes } from './modules/chat/routes';
import { contextRoutes } from './modules/context/routes';
import { pictureRoutes } from './modules/picture/routes';
@ -48,7 +48,7 @@ app.use('/api/*', authMiddleware());
// ─── Module Routes ──────────────────────────────────────────
app.route('/api/v1/calendar', calendarRoutes);
app.route('/api/v1/contacts', contactsRoutes);
app.route('/api/v1/mukke', mukkeRoutes);
app.route('/api/v1/music', musicRoutes);
app.route('/api/v1/chat', chatRoutes);
app.route('/api/v1/context', contextRoutes);
app.route('/api/v1/picture', pictureRoutes);

View file

@ -1,6 +1,6 @@
/**
* Mukke module Audio upload, presigned URLs, cover art
* Ported from apps/mukke/apps/server
* Music module Audio upload, presigned URLs, cover art
* Renamed from Mukke.
*/
import { Hono } from 'hono';
@ -18,8 +18,8 @@ routes.post('/songs/upload', async (c) => {
const key = `users/${userId}/songs/${songId}/${filename}`;
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const uploadUrl = await storage.getUploadUrl(key, { expiresIn: 3600 });
return c.json({
@ -38,8 +38,8 @@ routes.get('/songs/:id/download-url', async (c) => {
if (!storagePath) return c.json({ error: 'storagePath required' }, 400);
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const url = await storage.getDownloadUrl(storagePath, { expiresIn: 3600 });
return c.json({ url });
} catch {
@ -47,6 +47,35 @@ routes.get('/songs/:id/download-url', async (c) => {
}
});
// ─── Cover Art Upload (via mana-media) ─────────────────────
routes.post('/cover/upload', async (c) => {
const userId = c.get('userId');
const formData = await c.req.formData();
const file = formData.get('file') as File | null;
if (!file) return c.json({ error: 'No file' }, 400);
if (file.size > 10 * 1024 * 1024) return c.json({ error: 'Max 10MB' }, 400);
if (!file.type.startsWith('image/')) return c.json({ error: 'Must be an image' }, 400);
try {
const { uploadImageToMedia } = await import('../../lib/media');
const buffer = await file.arrayBuffer();
const result = await uploadImageToMedia(buffer, file.name, { app: 'music', userId });
return c.json(
{
coverArtPath: result.id,
coverUrl: result.urls.thumbnail || result.urls.original,
mediaId: result.id,
},
201
);
} catch {
return c.json({ error: 'Upload failed' }, 500);
}
});
// ─── Cover Art URL ──────────────────────────────────────────
routes.get('/songs/:id/cover-url', async (c) => {
@ -54,8 +83,8 @@ routes.get('/songs/:id/cover-url', async (c) => {
if (!coverArtPath) return c.json({ url: null });
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const url = await storage.getDownloadUrl(coverArtPath, { expiresIn: 3600 });
return c.json({ url });
} catch {
@ -70,8 +99,8 @@ routes.post('/library/cover-urls', async (c) => {
if (!paths?.length) return c.json({ urls: {} });
try {
const { createMukkeStorage } = await import('@manacore/shared-storage');
const storage = createMukkeStorage();
const { createMusicStorage } = await import('@manacore/shared-storage');
const storage = createMusicStorage();
const urls: Record<string, string> = {};
for (const path of paths.slice(0, 50)) {
@ -88,4 +117,4 @@ routes.post('/library/cover-urls', async (c) => {
}
});
export { routes as mukkeRoutes };
export { routes as musicRoutes };

View file

@ -36,8 +36,8 @@ const PUBLIC_STORAGE_API_URL_CLIENT =
process.env.PUBLIC_STORAGE_API_URL_CLIENT || process.env.PUBLIC_STORAGE_API_URL || '';
const PUBLIC_CARDS_API_URL_CLIENT =
process.env.PUBLIC_CARDS_API_URL_CLIENT || process.env.PUBLIC_CARDS_API_URL || '';
const PUBLIC_MUKKE_API_URL_CLIENT =
process.env.PUBLIC_MUKKE_API_URL_CLIENT || process.env.PUBLIC_MUKKE_API_URL || '';
const PUBLIC_MUSIC_API_URL_CLIENT =
process.env.PUBLIC_MUSIC_API_URL_CLIENT || process.env.PUBLIC_MUSIC_API_URL || '';
const PUBLIC_NUTRIPHI_API_URL_CLIENT =
process.env.PUBLIC_NUTRIPHI_API_URL_CLIENT || process.env.PUBLIC_NUTRIPHI_API_URL || '';
const PUBLIC_ULOAD_SERVER_URL_CLIENT =
@ -63,7 +63,7 @@ const APP_SUBDOMAINS = new Set([
'presi',
'nutriphi',
'photos',
'mukke',
'music',
'picture',
'calc',
'citycorners',
@ -100,7 +100,7 @@ window.__PUBLIC_SYNC_SERVER_URL__ = ${JSON.stringify(PUBLIC_SYNC_SERVER_URL_CLIE
window.__PUBLIC_CHAT_API_URL__ = ${JSON.stringify(PUBLIC_CHAT_API_URL_CLIENT)};
window.__PUBLIC_STORAGE_API_URL__ = ${JSON.stringify(PUBLIC_STORAGE_API_URL_CLIENT)};
window.__PUBLIC_CARDS_API_URL__ = ${JSON.stringify(PUBLIC_CARDS_API_URL_CLIENT)};
window.__PUBLIC_MUKKE_API_URL__ = ${JSON.stringify(PUBLIC_MUKKE_API_URL_CLIENT)};
window.__PUBLIC_MUSIC_API_URL__ = ${JSON.stringify(PUBLIC_MUSIC_API_URL_CLIENT)};
window.__PUBLIC_NUTRIPHI_API_URL__ = ${JSON.stringify(PUBLIC_NUTRIPHI_API_URL_CLIENT)};
window.__PUBLIC_ULOAD_SERVER_URL__ = ${JSON.stringify(PUBLIC_ULOAD_SERVER_URL_CLIENT)};
window.__PUBLIC_MEMORO_SERVER_URL__ = ${JSON.stringify(PUBLIC_MEMORO_SERVER_URL_CLIENT)};
@ -124,7 +124,7 @@ window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)};
PUBLIC_CHAT_API_URL_CLIENT,
PUBLIC_STORAGE_API_URL_CLIENT,
PUBLIC_CARDS_API_URL_CLIENT,
PUBLIC_MUKKE_API_URL_CLIENT,
PUBLIC_MUSIC_API_URL_CLIENT,
PUBLIC_NUTRIPHI_API_URL_CLIENT,
PUBLIC_ULOAD_SERVER_URL_CLIENT,
PUBLIC_MEMORO_SERVER_URL_CLIENT,

View file

@ -13,7 +13,7 @@ export { pictureService, type GeneratedImage, type GenerationStats } from './pic
export { cardsService, type Deck, type Card, type LearningProgress } from './cards';
export { clockService, type Timer, type Alarm, type ClockStats } from './clock';
export { storageService, type StorageFile, type StorageStats } from './storage';
export { mukkeService, type Song, type MukkeStats } from './mukke';
export { musicService, type Song, type MusicStats } from './music';
export { presiService, type PresiDeck, type PresiStats } from './presi';
export {
contextService,

View file

@ -12,9 +12,9 @@ vi.mock('$lib/stores/auth.svelte', () => ({
},
}));
import { mukkeService } from './mukke';
import { musicService } from './music';
describe('mukkeService', () => {
describe('musicService', () => {
beforeEach(() => {
vi.restoreAllMocks();
});
@ -25,27 +25,27 @@ describe('mukkeService', () => {
describe('formatDuration', () => {
it('should format 0 seconds', () => {
expect(mukkeService.formatDuration(0)).toBe('0:00');
expect(musicService.formatDuration(0)).toBe('0:00');
});
it('should format seconds only', () => {
expect(mukkeService.formatDuration(45)).toBe('0:45');
expect(musicService.formatDuration(45)).toBe('0:45');
});
it('should format minutes and seconds', () => {
expect(mukkeService.formatDuration(185)).toBe('3:05');
expect(musicService.formatDuration(185)).toBe('3:05');
});
it('should format hours', () => {
expect(mukkeService.formatDuration(3661)).toBe('1:01:01');
expect(musicService.formatDuration(3661)).toBe('1:01:01');
});
it('should pad seconds with zero', () => {
expect(mukkeService.formatDuration(60)).toBe('1:00');
expect(musicService.formatDuration(60)).toBe('1:00');
});
it('should handle negative values', () => {
expect(mukkeService.formatDuration(-10)).toBe('0:00');
expect(musicService.formatDuration(-10)).toBe('0:00');
});
});
@ -63,7 +63,7 @@ describe('mukkeService', () => {
json: () => Promise.resolve(mockStats),
});
const result = await mukkeService.getStats();
const result = await musicService.getStats();
expect(result.data).toEqual(mockStats);
expect(global.fetch).toHaveBeenCalledWith(
@ -80,7 +80,7 @@ describe('mukkeService', () => {
json: () => Promise.resolve([{ id: 's-1', title: 'Song 1' }]),
});
const result = await mukkeService.getRecentSongs();
const result = await musicService.getRecentSongs();
expect(result.data).toHaveLength(1);
expect(global.fetch).toHaveBeenCalledWith(

View file

@ -1,17 +1,17 @@
/**
* Mukke API Service
* Music API Service
*
* Fetches music library stats from the Mukke backend for dashboard widgets.
* Fetches music library stats from the Music backend for dashboard widgets.
*/
import { browser } from '$app/environment';
import { createApiClient, type ApiResult } from '../base-client';
// Get Mukke API URL dynamically at runtime
function getMukkeApiUrl(): string {
// Get Music API URL dynamically at runtime
function getMusicApiUrl(): string {
if (browser && typeof window !== 'undefined') {
const injectedUrl = (window as unknown as { __PUBLIC_MUKKE_API_URL__?: string })
.__PUBLIC_MUKKE_API_URL__;
const injectedUrl = (window as unknown as { __PUBLIC_MUSIC_API_URL__?: string })
.__PUBLIC_MUSIC_API_URL__;
if (injectedUrl) {
return `${injectedUrl}`;
}
@ -24,13 +24,13 @@ let _client: ReturnType<typeof createApiClient> | null = null;
function getClient() {
if (!_client) {
_client = createApiClient(getMukkeApiUrl());
_client = createApiClient(getMusicApiUrl());
}
return _client;
}
/**
* Song entity from Mukke backend
* Song entity from Music backend
*/
export interface Song {
id: string;
@ -49,7 +49,7 @@ export interface Song {
/**
* Music library statistics
*/
export interface MukkeStats {
export interface MusicStats {
totalSongs: number;
totalPlaylists: number;
totalProjects: number;
@ -58,14 +58,14 @@ export interface MukkeStats {
}
/**
* Mukke service for dashboard widgets
* Music service for dashboard widgets
*/
export const mukkeService = {
export const musicService = {
/**
* Get library statistics
*/
async getStats(): Promise<ApiResult<MukkeStats>> {
return getClient().get<MukkeStats>('/library/stats');
async getStats(): Promise<ApiResult<MusicStats>> {
return getClient().get<MusicStats>('/library/stats');
},
/**
@ -83,7 +83,7 @@ export const mukkeService = {
},
/**
* Format duration for display (seconds MM:SS or HH:MM:SS)
* Format duration for display (seconds -> MM:SS or HH:MM:SS)
*/
formatDuration(seconds: number): string {
if (seconds <= 0) return '0:00';

View file

@ -377,12 +377,12 @@ registerApp({
});
registerApp({
id: 'mukke',
name: 'Mukke',
id: 'music',
name: 'Music',
color: '#F97316',
views: {
list: { load: () => import('$lib/modules/mukke/ListView.svelte') },
detail: { load: () => import('$lib/modules/mukke/views/DetailView.svelte') },
list: { load: () => import('$lib/modules/music/ListView.svelte') },
detail: { load: () => import('$lib/modules/music/views/DetailView.svelte') },
},
});

View file

@ -1,20 +1,30 @@
<script lang="ts">
/**
* CalendarEventsWidget - Upcoming calendar events (local-first)
* CalendarEventsWidget - Upcoming timeBlocks (local-first)
*
* Reads directly from Calendar's IndexedDB via cross-app reader.
* Reactive: auto-updates when events change (sync, other tabs).
* Shows events, tasks, habits, and time entries from the next 7 days.
* Reads directly from the timeBlocks table via cross-app query.
* Reactive: auto-updates when data changes (sync, other tabs).
*/
import { _ } from 'svelte-i18n';
import { useUpcomingEvents } from '$lib/data/cross-app-queries';
import type { LocalTimeBlock } from '$lib/data/time-blocks/types';
import { CalendarBlank, CheckSquare, Timer, Heart } from '@manacore/shared-icons';
const events = useUpcomingEvents(7);
const MAX_DISPLAY = 5;
function formatEventTime(event: any): string {
const start = new Date(event.startDate);
const typeIcons: Record<string, typeof CalendarBlank> = {
event: CalendarBlank,
task: CheckSquare,
timeEntry: Timer,
habit: Heart,
};
function formatEventTime(block: LocalTimeBlock): string {
const start = new Date(block.startDate);
const today = new Date();
const tomorrow = new Date(today);
tomorrow.setDate(tomorrow.getDate() + 1);
@ -32,7 +42,7 @@
});
}
if (event.allDay) {
if (block.allDay) {
return dateStr;
}
@ -47,7 +57,7 @@
<div>
<div class="mb-3 flex items-center justify-between">
<h3 class="flex items-center gap-2 text-lg font-semibold">
<span>🗓️</span>
<span><CalendarBlank size={20} /></span>
{$_('dashboard.widgets.calendar.title')}
</h3>
{#if (events.value ?? []).length > 0}
@ -65,24 +75,29 @@
</div>
{:else if (events.value ?? []).length === 0}
<div class="py-6 text-center">
<div class="mb-2 text-3xl">📅</div>
<div class="mb-2 text-3xl"><CalendarBlank size={32} /></div>
<p class="text-sm text-muted-foreground">
{$_('dashboard.widgets.calendar.empty')}
</p>
</div>
{:else}
<div class="space-y-2">
{#each displayedEvents as event (event.id)}
{#each displayedEvents as block (block.id)}
{@const TypeIcon = typeIcons[block.type] ?? CalendarBlank}
<div class="flex items-start gap-3 rounded-lg p-2 transition-colors hover:bg-surface-hover">
<div
class="mt-1 h-3 w-3 flex-shrink-0 rounded-full"
style="background-color: {event.color || '#3B82F6'}"
class:animate-pulse={block.isLive}
style="background-color: {block.color || '#3B82F6'}"
></div>
<div class="min-w-0 flex-1">
<p class="truncate text-sm font-medium">{event.title}</p>
<p class="text-xs text-muted-foreground">{formatEventTime(event)}</p>
{#if event.location}
<p class="truncate text-xs text-muted-foreground">📍 {event.location}</p>
<div class="flex items-center gap-1.5">
<svelte:component this={TypeIcon} size={12} class="text-muted-foreground" />
<p class="truncate text-sm font-medium">{block.title}</p>
</div>
<p class="text-xs text-muted-foreground">{formatEventTime(block)}</p>
{#if block.isLive}
<span class="text-xs text-green-600">live</span>
{/if}
</div>
</div>

View file

@ -1,11 +1,11 @@
<script lang="ts">
/**
* MukkeLibraryWidget - Music library stats (local-first)
* MusicLibraryWidget - Music library stats (local-first)
*/
import { _ } from 'svelte-i18n';
import { useMukkeStats } from '$lib/data/cross-app-queries';
const stats = useMukkeStats();
import { useMusicStats } from '$lib/data/cross-app-queries';
const stats = useMusicStats();
function formatDuration(seconds?: number): string {
if (!seconds) return '';
@ -19,7 +19,7 @@
<div class="mb-3">
<h3 class="flex items-center gap-2 text-lg font-semibold">
<span>🎵</span>
{$_('dashboard.widgets.mukke.title')}
{$_('dashboard.widgets.music.title')}
</h3>
</div>
@ -66,6 +66,6 @@
</div>
{/if}
<p class="mt-2 text-center text-xs text-muted-foreground">Mukke</p>
<p class="mt-2 text-center text-xs text-muted-foreground">Music</p>
{/if}
</div>

View file

@ -152,7 +152,7 @@ export const APP_POSITIONS: Record<string, { x: number; y: number; lakeId: strin
// Around Waldsee Mitte (db-center)
zitare: { x: 640, y: 600, lakeId: 'db-center' },
mukke: { x: 850, y: 610, lakeId: 'db-center' },
music: { x: 850, y: 610, lakeId: 'db-center' },
clock: { x: 880, y: 680, lakeId: 'db-center' },
nutriphi: { x: 650, y: 720, lakeId: 'db-center' },

View file

@ -154,8 +154,8 @@ const APP_DEFINITIONS: AppDefinition[] = [
},
},
{
id: 'mukke',
displayName: 'Mukke',
id: 'music',
displayName: 'Music',
score: 80,
status: 'beta',
categories: {

View file

@ -34,7 +34,7 @@
storage: 'https://storage.mana.how',
picture: 'https://picture.mana.how',
presi: 'https://presi.mana.how',
mukke: 'https://mukke.mana.how',
music: 'https://music.mana.how',
clock: 'https://clock.mana.how',
nutriphi: 'https://nutriphi.mana.how',
photos: 'https://photos.mana.how',

View file

@ -29,7 +29,7 @@
redis: 'Schneller Cache-Speicher. Klein, kristallklar, sofort verfugbar.',
minio: 'Objekt-Speicher fur Dateien, Bilder und Medien aller Apps.',
'db-left': 'PostgreSQL-Datenbanken fur Calendar, Todo, Contacts, Storage.',
'db-center': 'PostgreSQL-Datenbanken fur Zitare, Mukke, Clock, NutriPhi.',
'db-center': 'PostgreSQL-Datenbanken fur Zitare, Music, Clock, NutriPhi.',
'db-right': 'PostgreSQL-Datenbanken fur Photos, SkillTree, Context, Planta.',
};

View file

@ -12,7 +12,7 @@
"zitare": "Zitare",
"cards": "Karten",
"picture": "Bilder",
"mukke": "Musik",
"music": "Musik",
"photos": "Fotos",
"storage": "Ablage",
"nutriphi": "NutriPhi",

View file

@ -12,7 +12,7 @@
"zitare": "Zitare",
"cards": "Cards",
"picture": "Pictures",
"mukke": "Music",
"music": "Music",
"photos": "Photos",
"storage": "Storage",
"nutriphi": "NutriPhi",

View file

@ -12,7 +12,7 @@
"zitare": "Zitare",
"cards": "Tarjetas",
"picture": "Imágenes",
"mukke": "Música",
"music": "Música",
"photos": "Fotos",
"storage": "Almacén",
"nutriphi": "NutriPhi",

View file

@ -12,7 +12,7 @@
"zitare": "Zitare",
"cards": "Cartes",
"picture": "Images",
"mukke": "Musique",
"music": "Musique",
"photos": "Photos",
"storage": "Stockage",
"nutriphi": "NutriPhi",

View file

@ -12,7 +12,7 @@
"zitare": "Zitare",
"cards": "Schede",
"picture": "Immagini",
"mukke": "Musica",
"music": "Musica",
"photos": "Foto",
"storage": "Archivio",
"nutriphi": "NutriPhi",

View file

@ -94,14 +94,14 @@
"empty": "Keine Dateien",
"open": "Storage öffnen"
},
"mukke": {
"music": {
"title": "Musik",
"description": "Deine Musikbibliothek",
"songs": "Songs",
"playlists": "Playlists",
"favorites": "Favoriten",
"empty": "Keine Songs",
"open": "Mukke öffnen"
"open": "Music öffnen"
},
"presi": {
"title": "Präsentationen",

View file

@ -94,14 +94,14 @@
"empty": "No files",
"open": "Open Storage"
},
"mukke": {
"music": {
"title": "Music",
"description": "Your music library",
"songs": "Songs",
"playlists": "Playlists",
"favorites": "Favorites",
"empty": "No songs",
"open": "Open Mukke"
"open": "Open Music"
},
"presi": {
"title": "Presentations",

View file

@ -94,14 +94,14 @@
"empty": "Sin archivos",
"open": "Abrir Storage"
},
"mukke": {
"music": {
"title": "Música",
"description": "Tu biblioteca musical",
"songs": "Canciones",
"playlists": "Playlists",
"favorites": "Favoritos",
"empty": "Sin canciones",
"open": "Abrir Mukke"
"open": "Abrir Music"
},
"presi": {
"title": "Presentaciones",

View file

@ -94,14 +94,14 @@
"empty": "Aucun fichier",
"open": "Ouvrir Storage"
},
"mukke": {
"music": {
"title": "Musique",
"description": "Ta bibliothèque musicale",
"songs": "Titres",
"playlists": "Playlists",
"favorites": "Favoris",
"empty": "Aucun titre",
"open": "Ouvrir Mukke"
"open": "Ouvrir Music"
},
"presi": {
"title": "Présentations",

View file

@ -94,14 +94,14 @@
"empty": "Nessun file",
"open": "Apri Storage"
},
"mukke": {
"music": {
"title": "Musica",
"description": "La tua libreria musicale",
"songs": "Brani",
"playlists": "Playlist",
"favorites": "Preferiti",
"empty": "Nessun brano",
"open": "Apri Mukke"
"open": "Apri Music"
},
"presi": {
"title": "Presentazioni",

View file

@ -1,5 +1,5 @@
<!--
Mukke — Workbench ListView
Music — Workbench ListView
Song library with recent plays and playlists.
-->
<script lang="ts">

View file

@ -1,7 +1,7 @@
/**
* Mukke module collection accessors and guest seed data.
* Music module collection accessors and guest seed data.
*
* Table names: songs, mukkePlaylists, playlistSongs, mukkeProjects, markers
* Dexie table names kept as mukkePlaylists/mukkeProjects for backward compat.
*/
import { db } from '$lib/data/database';
@ -16,16 +16,16 @@ import type {
// ─── Collection Accessors ──────────────────────────────────
export const songTable = db.table<LocalSong>('songs');
export const mukkePlaylistTable = db.table<LocalPlaylist>('mukkePlaylists');
export const musicPlaylistTable = db.table<LocalPlaylist>('mukkePlaylists');
export const playlistSongTable = db.table<LocalPlaylistSong>('playlistSongs');
export const mukkeProjectTable = db.table<LocalProject>('mukkeProjects');
export const musicProjectTable = db.table<LocalProject>('mukkeProjects');
export const markerTable = db.table<LocalMarker>('markers');
// ─── Guest Seed ────────────────────────────────────────────
const DEMO_PLAYLIST_ID = 'demo-favorites';
export const MUKKE_GUEST_SEED = {
export const MUSIC_GUEST_SEED = {
songs: [] as Record<string, unknown>[],
mukkePlaylists: [
{

View file

@ -1,5 +1,5 @@
/**
* Mukke module barrel exports.
* Music module barrel exports.
*/
export { libraryStore } from './stores/library.svelte';
@ -29,11 +29,11 @@ export {
} from './queries';
export {
songTable,
mukkePlaylistTable,
musicPlaylistTable,
playlistSongTable,
mukkeProjectTable,
musicProjectTable,
markerTable,
MUKKE_GUEST_SEED,
MUSIC_GUEST_SEED,
} from './collections';
export type {
LocalSong,

View file

@ -1,5 +1,5 @@
/**
* Reactive queries & pure helpers for Mukke uses Dexie liveQuery on the unified DB.
* Reactive queries & pure helpers for Music uses Dexie liveQuery on the unified DB.
*/
import { liveQuery } from 'dexie';

View file

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

View file

@ -108,7 +108,7 @@ function createPlayerStore() {
state.duration = 0;
state.error = null;
// NOTE: In the unified app, audio URLs would come from the mukke backend
// NOTE: In the unified app, audio URLs would come from the music backend
// via presigned S3 download URLs. For now, playback requires the backend.
// The store manages queue/state regardless.
try {

View file

@ -5,9 +5,9 @@
* Handles playlist CRUD and song associations.
*/
import { mukkePlaylistTable, playlistSongTable } from '../collections';
import { musicPlaylistTable, playlistSongTable } from '../collections';
import { toPlaylist } from '../queries';
import { MukkeEvents } from '@manacore/shared-utils/analytics';
import { MusicEvents } from '@manacore/shared-utils/analytics';
import type { LocalPlaylist, LocalPlaylistSong } from '../types';
export const playlistsStore = {
@ -19,14 +19,14 @@ export const playlistsStore = {
description: description ?? null,
coverArtPath: null,
};
await mukkePlaylistTable.add(newLocal);
MukkeEvents.playlistCreated();
await musicPlaylistTable.add(newLocal);
MusicEvents.playlistCreated();
return toPlaylist(newLocal);
},
/** Update a playlist. */
async update(id: string, data: Partial<Pick<LocalPlaylist, 'name' | 'description'>>) {
await mukkePlaylistTable.update(id, {
await musicPlaylistTable.update(id, {
...data,
updatedAt: new Date().toISOString(),
});
@ -35,13 +35,13 @@ export const playlistsStore = {
/** Soft-delete a playlist and its song associations. */
async delete(id: string) {
const now = new Date().toISOString();
await mukkePlaylistTable.update(id, { deletedAt: now, updatedAt: now });
await musicPlaylistTable.update(id, { deletedAt: now, updatedAt: now });
// Soft-delete associated playlistSongs
const allPS = await playlistSongTable.where('playlistId').equals(id).toArray();
for (const ps of allPS) {
await playlistSongTable.update(ps.id, { deletedAt: now, updatedAt: now });
}
MukkeEvents.playlistDeleted();
MusicEvents.playlistDeleted();
},
/** Add a song to a playlist. */

View file

@ -5,9 +5,9 @@
* Handles project CRUD.
*/
import { mukkeProjectTable } from '../collections';
import { musicProjectTable } from '../collections';
import { toProject } from '../queries';
import { MukkeEvents } from '@manacore/shared-utils/analytics';
import { MusicEvents } from '@manacore/shared-utils/analytics';
import type { LocalProject } from '../types';
export const projectsStore = {
@ -19,14 +19,14 @@ export const projectsStore = {
description: data.description ?? null,
songId: data.songId ?? null,
};
await mukkeProjectTable.add(newLocal);
MukkeEvents.projectCreated();
await musicProjectTable.add(newLocal);
MusicEvents.projectCreated();
return toProject(newLocal);
},
/** Update a project. */
async update(id: string, data: Partial<Pick<LocalProject, 'title' | 'description' | 'songId'>>) {
await mukkeProjectTable.update(id, {
await musicProjectTable.update(id, {
...data,
updatedAt: new Date().toISOString(),
});
@ -35,7 +35,7 @@ export const projectsStore = {
/** Soft-delete a project. */
async delete(id: string) {
const now = new Date().toISOString();
await mukkeProjectTable.update(id, { deletedAt: now, updatedAt: now });
MukkeEvents.projectDeleted();
await musicProjectTable.update(id, { deletedAt: now, updatedAt: now });
MusicEvents.projectDeleted();
},
};

View file

@ -1,5 +1,5 @@
/**
* Umukke Tags Uses shared global tags + module-specific junction table.
* Music Tags Uses shared global tags + module-specific junction table.
*/
import { db } from '$lib/data/database';

View file

@ -1,5 +1,5 @@
/**
* Mukke module types for the unified app.
* Music module types for the unified app.
*/
import type { BaseRecord } from '@manacore/local-store';

View file

@ -1,5 +1,5 @@
<!--
Mukke — DetailView (inline editable overlay)
Music — DetailView (inline editable overlay)
All fields are always editable. Changes auto-save on blur.
-->
<script lang="ts">

View file

@ -157,11 +157,11 @@ export async function collectAppSnapshots(): Promise<AppSnapshot[]> {
});
}
// Mukke
// Music
if (songs.length > 0) {
snapshots.push({
app: 'Mukke',
appIndex: MANA_APP_INDEX.mukke,
app: 'Music',
appIndex: MANA_APP_INDEX.music,
totalItems: songs.length,
completedItems: 0,
favoriteItems: 0,

View file

@ -7,7 +7,7 @@ import { storageSearchProvider } from './storage';
import { cardsSearchProvider } from './cards';
import { pictureSearchProvider } from './picture';
import { presiSearchProvider } from './presi';
import { mukkeSearchProvider } from './mukke';
import { musicSearchProvider } from './music';
import { zitareSearchProvider } from './zitare';
import { clockSearchProvider } from './clock';
@ -20,7 +20,7 @@ export function registerAllProviders(registry: SearchRegistry): void {
registry.register(cardsSearchProvider);
registry.register(pictureSearchProvider);
registry.register(presiSearchProvider);
registry.register(mukkeSearchProvider);
registry.register(musicSearchProvider);
registry.register(zitareSearchProvider);
registry.register(clockSearchProvider);
}
@ -34,7 +34,7 @@ export {
cardsSearchProvider,
pictureSearchProvider,
presiSearchProvider,
mukkeSearchProvider,
musicSearchProvider,
zitareSearchProvider,
clockSearchProvider,
};

View file

@ -3,11 +3,11 @@ import { getManaApp } from '@manacore/shared-branding';
import { scoreRecord, truncateSubtitle } from '../scoring';
import type { SearchProvider, SearchResult, SearchOptions } from '../types';
const app = getManaApp('mukke');
const app = getManaApp('music');
export const mukkeSearchProvider: SearchProvider = {
appId: 'mukke',
appName: 'Mukke',
export const musicSearchProvider: SearchProvider = {
appId: 'music',
appName: 'Music',
appIcon: app?.icon,
appColor: app?.color,
searchableTypes: ['song', 'playlist', 'project'],
@ -33,19 +33,19 @@ export const mukkeSearchProvider: SearchProvider = {
results.push({
id: song.id,
type: 'song',
appId: 'mukke',
appId: 'music',
title: song.title,
subtitle: [song.artist, song.album].filter(Boolean).join(' · ') || undefined,
appIcon: app?.icon,
appColor: app?.color,
href: '/mukke/library',
href: '/music/library',
score,
matchedField,
});
}
}
// Search playlists
// Search playlists (Dexie table name kept for backward compat)
const playlists = await db.table('mukkePlaylists').toArray();
for (const pl of playlists) {
if (pl.deletedAt) continue;
@ -60,19 +60,19 @@ export const mukkeSearchProvider: SearchProvider = {
results.push({
id: pl.id,
type: 'playlist',
appId: 'mukke',
appId: 'music',
title: pl.name,
subtitle: truncateSubtitle(pl.description) || 'Playlist',
appIcon: app?.icon,
appColor: app?.color,
href: `/mukke/playlists/${pl.id}`,
href: `/music/playlists/${pl.id}`,
score,
matchedField,
});
}
}
// Search projects
// Search projects (Dexie table name kept for backward compat)
const projects = await db.table('mukkeProjects').toArray();
for (const proj of projects) {
if (proj.deletedAt) continue;
@ -87,12 +87,12 @@ export const mukkeSearchProvider: SearchProvider = {
results.push({
id: proj.id,
type: 'project',
appId: 'mukke',
appId: 'music',
title: proj.title,
subtitle: truncateSubtitle(proj.description) || 'Projekt',
appIcon: app?.icon,
appColor: app?.color,
href: `/mukke/projects`,
href: `/music/projects`,
score,
matchedField,
});

View file

@ -14,7 +14,7 @@ const SPLIT_APP_ID_LIST = [
'picture',
'cards',
'zitare',
'mukke',
'music',
'storage',
'presi',
'inventar',

View file

@ -233,7 +233,7 @@ export const dashboardStore = {
'chat-recent',
'contacts-favorites',
'zitare-quote',
'mukke-library',
'music-library',
'presi-decks',
'context-docs',
] as WidgetType[]

View file

@ -47,7 +47,7 @@ describe('WIDGET_REGISTRY', () => {
'cards',
'times',
'storage',
'mukke',
'music',
'presi',
'context',
'mana-core-auth',
@ -73,7 +73,7 @@ describe('WIDGET_REGISTRY', () => {
expect(types).toContain('cards-progress');
expect(types).toContain('clock-timers');
expect(types).toContain('storage-usage');
expect(types).toContain('mukke-library');
expect(types).toContain('music-library');
expect(types).toContain('presi-decks');
expect(types).toContain('context-docs');
});

View file

@ -6,7 +6,7 @@
useAllPlaylists,
useAllPlaylistSongs,
useAllProjects,
} from '$lib/modules/mukke/queries';
} from '$lib/modules/music/queries';
let { children }: { children: Snippet } = $props();

View file

@ -1,7 +1,7 @@
<script lang="ts">
import { getContext } from 'svelte';
import { computeStats } from '$lib/modules/mukke/queries';
import type { Song, Playlist, Project } from '$lib/modules/mukke/types';
import { computeStats } from '$lib/modules/music/queries';
import type { Song, Playlist, Project } from '$lib/modules/music/types';
import { MusicNote, Plus, Playlist as PlaylistIcon, Note } from '@manacore/shared-icons';
const songsCtx: { readonly value: Song[] } = getContext('songs');
@ -20,11 +20,11 @@
</script>
<svelte:head>
<title>Mukke - ManaCore</title>
<title>Music - ManaCore</title>
</svelte:head>
<div class="space-y-8">
<h1 class="text-2xl font-bold text-[hsl(var(--foreground))]">Mukke</h1>
<h1 class="text-2xl font-bold text-[hsl(var(--foreground))]">Music</h1>
<!-- Quick Stats -->
<section>
@ -62,21 +62,21 @@
</h2>
<div class="flex flex-wrap gap-3">
<a
href="/mukke/library"
href="/music/library"
class="inline-flex items-center gap-2 rounded-lg bg-[hsl(var(--primary))] px-4 py-2.5 text-sm font-medium text-[hsl(var(--primary-foreground))] hover:opacity-90"
>
<MusicNote size={20} />
Bibliothek
</a>
<a
href="/mukke/playlists"
href="/music/playlists"
class="inline-flex items-center gap-2 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-2.5 text-sm font-medium text-[hsl(var(--foreground))] hover:bg-[hsl(var(--muted))]"
>
<PlaylistIcon size={20} />
Playlists
</a>
<a
href="/mukke/projects"
href="/music/projects"
class="inline-flex items-center gap-2 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--card))] px-4 py-2.5 text-sm font-medium text-[hsl(var(--foreground))] hover:bg-[hsl(var(--muted))]"
>
<Plus size={20} />
@ -91,7 +91,7 @@
<h2 class="text-sm font-medium uppercase tracking-wide text-[hsl(var(--muted-foreground))]">
Letzte Projekte
</h2>
<a href="/mukke/projects" class="text-sm text-[hsl(var(--primary))] hover:underline">
<a href="/music/projects" class="text-sm text-[hsl(var(--primary))] hover:underline">
Alle anzeigen
</a>
</div>

View file

@ -1,15 +1,15 @@
<script lang="ts">
import { getContext } from 'svelte';
import { libraryStore } from '$lib/modules/mukke/stores/library.svelte';
import { playerStore } from '$lib/modules/mukke/stores/player.svelte';
import { libraryStore } from '$lib/modules/music/stores/library.svelte';
import { playerStore } from '$lib/modules/music/stores/player.svelte';
import {
searchSongs,
filterFavorites,
groupByAlbum,
groupByGenre,
formatDuration,
} from '$lib/modules/mukke/queries';
import type { Song } from '$lib/modules/mukke/types';
} from '$lib/modules/music/queries';
import type { Song } from '$lib/modules/music/types';
import {
MusicNote,
Heart,
@ -53,14 +53,14 @@
</script>
<svelte:head>
<title>Bibliothek - Mukke - ManaCore</title>
<title>Bibliothek - Music - ManaCore</title>
</svelte:head>
<div class="space-y-6">
<!-- Header -->
<div class="flex items-center gap-3">
<a
href="/mukke"
href="/music"
class="rounded-lg p-1.5 text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--muted))]"
>
<ArrowLeft size={20} />

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import { getContext } from 'svelte';
import { playlistsStore } from '$lib/modules/mukke/stores/playlists.svelte';
import type { Playlist } from '$lib/modules/mukke/types';
import { playlistsStore } from '$lib/modules/music/stores/playlists.svelte';
import type { Playlist } from '$lib/modules/music/types';
import {
ArrowLeft,
Plus,
@ -42,7 +42,7 @@
</script>
<svelte:head>
<title>Playlists - Mukke - ManaCore</title>
<title>Playlists - Music - ManaCore</title>
</svelte:head>
<div class="space-y-6">
@ -50,7 +50,7 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<a
href="/mukke"
href="/music"
class="rounded-lg p-1.5 text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--muted))]"
>
<ArrowLeft size={20} />
@ -83,7 +83,7 @@
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
{#each playlistsCtx.value as playlist (playlist.id)}
<a
href="/mukke/playlists/{playlist.id}"
href="/music/playlists/{playlist.id}"
class="group relative rounded-xl border border-[hsl(var(--border))] bg-[hsl(var(--card))] p-4 transition-all hover:border-[hsl(var(--primary)/0.3)]"
>
<div

View file

@ -2,10 +2,10 @@
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import { getContext } from 'svelte';
import { playlistsStore } from '$lib/modules/mukke/stores/playlists.svelte';
import { playerStore } from '$lib/modules/mukke/stores/player.svelte';
import { getPlaylistSongs, formatDuration } from '$lib/modules/mukke/queries';
import type { Song, Playlist, LocalPlaylistSong } from '$lib/modules/mukke/types';
import { playlistsStore } from '$lib/modules/music/stores/playlists.svelte';
import { playerStore } from '$lib/modules/music/stores/player.svelte';
import { getPlaylistSongs, formatDuration } from '$lib/modules/music/queries';
import type { Song, Playlist, LocalPlaylistSong } from '$lib/modules/music/types';
import {
ArrowLeft,
Play,
@ -31,7 +31,7 @@
let editName = $state('');
let showShare = $state(false);
let shareUrl = $derived(
`${typeof window !== 'undefined' ? window.location.origin : ''}/mukke/playlists/${playlistId}`
`${typeof window !== 'undefined' ? window.location.origin : ''}/music/playlists/${playlistId}`
);
function startEdit() {
@ -64,13 +64,13 @@
async function handleDeletePlaylist() {
if (confirm('Playlist wirklich loschen?')) {
await playlistsStore.delete(playlistId);
goto('/mukke/playlists');
goto('/music/playlists');
}
}
</script>
<svelte:head>
<title>{playlist?.name || 'Playlist'} - Mukke - ManaCore</title>
<title>{playlist?.name || 'Playlist'} - Music - ManaCore</title>
</svelte:head>
<div class="space-y-6">
@ -78,7 +78,7 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<a
href="/mukke/playlists"
href="/music/playlists"
class="rounded-lg p-1.5 text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--muted))]"
>
<ArrowLeft size={20} />
@ -222,6 +222,6 @@
onClose={() => (showShare = false)}
url={shareUrl}
title={playlist?.name ?? 'Playlist'}
source="mukke"
source="music"
description="{songs.length} {songs.length === 1 ? 'Song' : 'Songs'}"
/>

View file

@ -1,8 +1,8 @@
<script lang="ts">
import { _ } from 'svelte-i18n';
import { getContext } from 'svelte';
import { projectsStore } from '$lib/modules/mukke/stores/projects.svelte';
import type { Project } from '$lib/modules/mukke/types';
import { projectsStore } from '$lib/modules/music/stores/projects.svelte';
import type { Project } from '$lib/modules/music/types';
import { ArrowLeft, Plus, Trash, Note, X } from '@manacore/shared-icons';
const projectsCtx: { readonly value: Project[] } = getContext('projects');
@ -46,7 +46,7 @@
</script>
<svelte:head>
<title>Projekte - Mukke - ManaCore</title>
<title>Projekte - Music - ManaCore</title>
</svelte:head>
<div class="space-y-6">
@ -54,7 +54,7 @@
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<a
href="/mukke"
href="/music"
class="rounded-lg p-1.5 text-[hsl(var(--muted-foreground))] hover:bg-[hsl(var(--muted))]"
>
<ArrowLeft size={20} />

View file

@ -26,7 +26,7 @@
Picture: 'image',
Clock: 'clock',
Storage: 'hard-drive',
Mukke: 'music',
Music: 'music',
Presi: 'presentation',
Context: 'file-text',
Cards: 'layers',

View file

@ -16,7 +16,7 @@ const SERVICES = [
{ name: 'Chat API', url: process.env.PUBLIC_CHAT_API_URL || 'http://localhost:3030' },
{ name: 'Storage API', url: process.env.PUBLIC_STORAGE_API_URL || 'http://localhost:3034' },
{ name: 'Cards API', url: process.env.PUBLIC_CARDS_API_URL || 'http://localhost:3036' },
{ name: 'Mukke API', url: process.env.PUBLIC_MUKKE_API_URL || 'http://localhost:3037' },
{ name: 'Music API', url: process.env.PUBLIC_MUSIC_API_URL || 'http://localhost:3037' },
{ name: 'NutriPhi API', url: process.env.PUBLIC_NUTRIPHI_API_URL || 'http://localhost:3038' },
{ name: 'Uload Server', url: process.env.PUBLIC_ULOAD_SERVER_URL || 'http://localhost:3070' },
{ name: 'Memoro Server', url: process.env.PUBLIC_MEMORO_SERVER_URL || 'http://localhost:3015' },

View file

@ -58,7 +58,7 @@
questions: ['Recherche mit System', 'Quelloffen & unabhängig', 'Privat by Design'],
context: ['Dein Wissen, strukturiert', 'Quelloffen & unabhängig', 'Privat by Design'],
presi: ['Präsentationen neu gedacht', 'Quelloffen & unabhängig', 'Privat by Design'],
mukke: ['Musik machen, einfach so', 'Quelloffen & unabhängig', 'Privat by Design'],
music: ['Musik machen, einfach so', 'Quelloffen & unabhängig', 'Privat by Design'],
storage: ['Deine Dateien, dein Tresor', 'Quelloffen & unabhängig', 'Privat by Design'],
times: ['Zeiterfassung ohne Overhead', 'Quelloffen & unabhängig', 'Privat by Design'],
inventar: ['Alles im Überblick behalten', 'Quelloffen & unabhängig', 'Privat by Design'],
@ -91,7 +91,7 @@
questions: ['Research with structure', 'Open-source & independent', 'Private by design'],
context: ['Your knowledge, organized', 'Open-source & independent', 'Private by design'],
presi: ['Presentations reimagined', 'Open-source & independent', 'Private by design'],
mukke: ['Make music, just like that', 'Open-source & independent', 'Private by design'],
music: ['Make music, just like that', 'Open-source & independent', 'Private by design'],
storage: ['Your files, your vault', 'Open-source & independent', 'Private by design'],
times: ['Time tracking without overhead', 'Open-source & independent', 'Private by design'],
inventar: ['Keep track of everything', 'Open-source & independent', 'Private by design'],

View file

@ -119,7 +119,7 @@ export const APP_ICONS = {
guides: svgToDataUrl(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><linearGradient id="gg" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#0d9488"/><stop offset="100%" style="stop-color:#0f766e"/></linearGradient></defs><rect width="100" height="100" rx="22" fill="url(#gg)"/><rect x="18" y="25" width="28" height="50" rx="3" fill="white" fill-opacity="0.15"/><rect x="54" y="25" width="28" height="50" rx="3" fill="white" fill-opacity="0.15"/><rect x="46" y="25" width="8" height="50" fill="white" fill-opacity="0.25"/><circle cx="27" cy="40" r="4" fill="white" fill-opacity="0.9"/><rect x="34" y="37" width="11" height="3" rx="1.5" fill="white" fill-opacity="0.6"/><circle cx="27" cy="52" r="4" fill="white" fill-opacity="0.55"/><rect x="34" y="49" width="9" height="3" rx="1.5" fill="white" fill-opacity="0.4"/><circle cx="27" cy="64" r="4" fill="white" fill-opacity="0.3"/><rect x="34" y="61" width="10" height="3" rx="1.5" fill="white" fill-opacity="0.25"/><path d="M60 52l6 7 12-14" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>`
),
mukke: svgToDataUrl(
music: svgToDataUrl(
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><linearGradient id="mk" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#ec4899"/><stop offset="100%" style="stop-color:#db2777"/></linearGradient></defs><rect width="100" height="100" rx="22" fill="url(#mk)"/><circle cx="35" cy="62" r="10" stroke="white" stroke-width="4" fill="none"/><circle cx="65" cy="58" r="10" stroke="white" stroke-width="4" fill="none"/><path d="M45 62V30l30-6v28" stroke="white" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/><rect x="47" y="30" width="8" height="4" rx="1" fill="white" fill-opacity="0.5"/><rect x="57" y="27" width="8" height="4" rx="1" fill="white" fill-opacity="0.5"/></svg>`
),
photos: svgToDataUrl(

View file

@ -272,9 +272,9 @@ export const APP_BRANDING: Record<AppId, AppBranding> = {
logoStroke: true,
logoStrokeWidth: 1.5,
},
mukke: {
id: 'mukke',
name: 'Mukke',
music: {
id: 'music',
name: 'Music',
tagline: 'Music Workspace',
primaryColor: '#ec4899',
secondaryColor: '#f472b6',

View file

@ -35,7 +35,7 @@ export {
SkillTreeLogo,
PlantaLogo,
LightWriteLogo,
MukkeLogo,
MusicLogo,
ContextLogo,
CitycornersLogo,
} from './logos';

View file

@ -10,4 +10,4 @@
let { size = 55, color, class: className = '' }: Props = $props();
</script>
<AppLogo app="mukke" {size} {color} class={className} />
<AppLogo app="music" {size} {color} class={className} />

View file

@ -22,6 +22,6 @@ export { default as QuestionsLogo } from './QuestionsLogo.svelte';
export { default as SkillTreeLogo } from './SkillTreeLogo.svelte';
export { default as PlantaLogo } from './PlantaLogo.svelte';
export { default as LightWriteLogo } from './LightWriteLogo.svelte';
export { default as MukkeLogo } from './MukkeLogo.svelte';
export { default as MusicLogo } from './MusicLogo.svelte';
export { default as ContextLogo } from './ContextLogo.svelte';
export { default as CitycornersLogo } from './CitycornersLogo.svelte';

View file

@ -514,8 +514,8 @@ export const MANA_APPS: ManaApp[] = [
requiredTier: 'beta',
},
{
id: 'mukke',
name: 'Mukke',
id: 'music',
name: 'Music',
description: {
de: 'Musikproduktion',
en: 'Music Production',
@ -524,7 +524,7 @@ export const MANA_APPS: ManaApp[] = [
de: 'Erstelle und verwalte Songs, Playlists und Musikprojekte mit Markern und Arrangements.',
en: 'Create and manage songs, playlists, and music projects with markers and arrangements.',
},
icon: APP_ICONS.mukke,
icon: APP_ICONS.music,
color: '#ec4899',
comingSoon: false,
status: 'beta',
@ -759,7 +759,7 @@ export const APP_URLS: Record<AppIconId, { dev: string; prod: string }> = {
cards: { dev: 'http://localhost:5173/cards', prod: 'https://mana.how/cards' },
zitare: { dev: 'http://localhost:5173/zitare', prod: 'https://mana.how/zitare' },
clock: { dev: 'http://localhost:5173/clock', prod: 'https://mana.how/clock' },
mukke: { dev: 'http://localhost:5173/mukke', prod: 'https://mana.how/mukke' },
music: { dev: 'http://localhost:5173/music', prod: 'https://mana.how/music' },
storage: { dev: 'http://localhost:5173/storage', prod: 'https://mana.how/storage' },
presi: { dev: 'http://localhost:5173/presi', prod: 'https://mana.how/presi' },
inventar: { dev: 'http://localhost:5173/inventar', prod: 'https://mana.how/inventar' },

View file

@ -24,7 +24,7 @@ export type AppId =
| 'planta'
| 'lightwrite'
| 'context'
| 'mukke'
| 'music'
| 'citycorners';
/**

View file

@ -46,7 +46,7 @@ const DEEP_LINK_PATTERNS: Record<string, Record<string, string>> = {
decks: '/decks/{id}',
cards: '/decks/{id}', // Navigate to deck containing the card
},
mukke: {
music: {
songs: '/',
playlists: '/playlists/{id}',
},

View file

@ -27,7 +27,7 @@ Each app gets its own isolated bucket, created automatically by `minio-init`:
| `storage-storage` | Storage | Cloud drive files |
| `mail-storage` | Mail | Email attachments |
| `inventory-storage` | Inventory | Product photos |
| `mukke-storage` | Mukke | Music tracks, beats, covers |
| `music-storage` | Music | Music tracks, beats, covers |
| `planta-storage` | Planta | Plant photos |
| `projectdoc-storage` | ProjectDoc | Document files |
@ -88,7 +88,7 @@ import { createStorage } from '@manacore/shared-storage';
// Instead of app-specific factories:
const storage = createStorage('PICTURE');
const storage = createStorage('CHAT');
const storage = createStorage('MUKKE');
const storage = createStorage('MUSIC');
```
App-specific aliases still work: `createPictureStorage()`, `createChatStorage()`, etc.

View file

@ -110,6 +110,6 @@ export const createContactsStorage = () => createStorage('CONTACTS');
export const createStorageStorage = (publicUrl?: string) => createStorage('STORAGE', publicUrl);
export const createMailStorage = () => createStorage('MAIL');
export const createInventoryStorage = (publicUrl?: string) => createStorage('INVENTORY', publicUrl);
export const createMukkeStorage = () => createStorage('MUKKE');
export const createMusicStorage = () => createStorage('MUSIC');
export const createPlantaStorage = (publicUrl?: string) => createStorage('PLANTA', publicUrl);
export const createProjectDocStorage = () => createStorage('PROJECTDOC');

View file

@ -16,7 +16,7 @@ export {
createStorageStorage,
createMailStorage,
createInventoryStorage,
createMukkeStorage,
createMusicStorage,
createPlantaStorage,
createProjectDocStorage,
} from './factory';

View file

@ -136,7 +136,7 @@ export const BUCKETS = {
STORAGE: 'storage-storage',
MAIL: 'mail-storage',
INVENTORY: 'inventory-storage',
MUKKE: 'mukke-storage',
MUSIC: 'music-storage',
PLANTA: 'planta-storage',
PROJECTDOC: 'projectdoc-storage',
} as const;

View file

@ -45,7 +45,7 @@ export type AppSource =
| 'chat'
| 'storage'
| 'presi'
| 'mukke'
| 'music'
| 'cards'
| 'picture'
| 'uload'
@ -59,7 +59,7 @@ export const APP_SOURCE_LABELS: Record<string, string> = {
chat: 'Chat',
storage: 'Storage',
presi: 'Presi',
mukke: 'Mukke',
music: 'Music',
cards: 'Cards',
picture: 'Picture',
uload: 'uLoad',

View file

@ -160,7 +160,7 @@ const track = {
questions: createModuleTracker('questions'),
photos: createModuleTracker('photos'),
storage: createModuleTracker('storage'),
mukke: createModuleTracker('mukke'),
music: createModuleTracker('music'),
zitare: createModuleTracker('zitare'),
presi: createModuleTracker('presi'),
subscription: createModuleTracker('subscription'),
@ -434,21 +434,21 @@ export const StorageEvents = {
};
/**
* Mukke App Events
* Music App Events
*/
export const MukkeEvents = {
songUploaded: () => track.mukke('song_uploaded'),
songUploadFailed: () => track.mukke('song_upload_failed'),
songPlayed: () => track.mukke('song_played'),
songFavorited: (favorited: boolean) => track.mukke('song_favorited', { favorited }),
songDeleted: () => track.mukke('song_deleted'),
playlistCreated: () => track.mukke('playlist_created'),
playlistDeleted: () => track.mukke('playlist_deleted'),
playlistPlayAll: () => track.mukke('playlist_play_all'),
playlistShufflePlay: () => track.mukke('playlist_shuffle_play'),
projectCreated: () => track.mukke('project_created'),
projectDeleted: () => track.mukke('project_deleted'),
projectExported: (format: string) => track.mukke('project_exported', { format }),
export const MusicEvents = {
songUploaded: () => track.music('song_uploaded'),
songUploadFailed: () => track.music('song_upload_failed'),
songPlayed: () => track.music('song_played'),
songFavorited: (favorited: boolean) => track.music('song_favorited', { favorited }),
songDeleted: () => track.music('song_deleted'),
playlistCreated: () => track.music('playlist_created'),
playlistDeleted: () => track.music('playlist_deleted'),
playlistPlayAll: () => track.music('playlist_play_all'),
playlistShufflePlay: () => track.music('playlist_shuffle_play'),
projectCreated: () => track.music('project_created'),
projectDeleted: () => track.music('project_deleted'),
projectExported: (format: string) => track.music('project_exported', { format }),
};
/**

View file

@ -183,7 +183,7 @@ export const MANA_APP_INDEX: Record<string, number> = {
picture: 5,
clock: 6,
storage: 7,
mukke: 8,
music: 8,
presi: 9,
context: 10,
cards: 11,