diff --git a/src/lib/components/onboarding/WelcomeStep.svelte b/apps/desktop/src/lib/components/onboarding/WelcomeStep.svelte
similarity index 94%
rename from src/lib/components/onboarding/WelcomeStep.svelte
rename to apps/desktop/src/lib/components/onboarding/WelcomeStep.svelte
index 40cebb40..c7599ec2 100644
--- a/src/lib/components/onboarding/WelcomeStep.svelte
+++ b/apps/desktop/src/lib/components/onboarding/WelcomeStep.svelte
@@ -1,6 +1,6 @@
diff --git a/src/lib/components/onboarding/index.ts b/apps/desktop/src/lib/components/onboarding/index.ts
similarity index 100%
rename from src/lib/components/onboarding/index.ts
rename to apps/desktop/src/lib/components/onboarding/index.ts
diff --git a/src/lib/components/player/PlaybackControls.svelte b/apps/desktop/src/lib/components/player/PlaybackControls.svelte
similarity index 96%
rename from src/lib/components/player/PlaybackControls.svelte
rename to apps/desktop/src/lib/components/player/PlaybackControls.svelte
index ffa13415..76e07d09 100644
--- a/src/lib/components/player/PlaybackControls.svelte
+++ b/apps/desktop/src/lib/components/player/PlaybackControls.svelte
@@ -1,6 +1,6 @@
+
+
+ Crate Mobile
+ Scaffold placeholder — shared type resolution {placeholder === null ? 'works' : 'works'}.
+
diff --git a/apps/mobile/svelte.config.js b/apps/mobile/svelte.config.js
new file mode 100644
index 00000000..6498264d
--- /dev/null
+++ b/apps/mobile/svelte.config.js
@@ -0,0 +1,18 @@
+import adapter from '@sveltejs/adapter-static'
+import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
+
+/** @type {import('@sveltejs/kit').Config} */
+const config = {
+ preprocess: vitePreprocess(),
+ kit: {
+ adapter: adapter({
+ fallback: 'index.html',
+ }),
+ alias: {
+ $shared: '../../shared',
+ '$shared/*': '../../shared/*',
+ },
+ },
+}
+
+export default config
diff --git a/apps/mobile/tsconfig.json b/apps/mobile/tsconfig.json
new file mode 100644
index 00000000..a8f10c8e
--- /dev/null
+++ b/apps/mobile/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "./.svelte-kit/tsconfig.json",
+ "compilerOptions": {
+ "allowJs": true,
+ "checkJs": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": true,
+ "sourceMap": true,
+ "strict": true,
+ "moduleResolution": "bundler"
+ }
+}
diff --git a/apps/mobile/vite.config.ts b/apps/mobile/vite.config.ts
new file mode 100644
index 00000000..2270b88f
--- /dev/null
+++ b/apps/mobile/vite.config.ts
@@ -0,0 +1,7 @@
+import { defineConfig } from 'vite'
+import { sveltekit } from '@sveltejs/kit/vite'
+
+// https://vite.dev/config/
+export default defineConfig(async () => ({
+ plugins: [sveltekit()],
+}))
diff --git a/eslint.config.js b/eslint.config.js
index 071eb99e..608b343b 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -44,10 +44,8 @@ export default defineConfig(
},
{
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
- basePath: 'src',
languageOptions: {
parserOptions: {
- projectService: true,
extraFileExtensions: ['.svelte'],
parser: ts.parser,
},
diff --git a/package.json b/package.json
index 1c73b9c2..e0a2dc93 100644
--- a/package.json
+++ b/package.json
@@ -5,15 +5,15 @@
"type": "module",
"scripts": {
"dev": "cross-env CRATE_ENV=development tauri dev --config src-tauri/tauri.dev.conf.json -- --release --features devtools",
- "dev:vite": "vite dev",
+ "dev:vite": "cd apps/desktop && vite dev",
"build": "yarn build:production",
"build:production": "cross-env CRATE_ENV=production tauri build --config src-tauri/tauri.prod.conf.json",
"build:staging": "cross-env CRATE_ENV=staging tauri build --config src-tauri/tauri.staging.conf.json -- --features devtools",
- "build:vite": "vite build",
+ "build:vite": "cd apps/desktop && vite build",
"check": "yarn check:cargo && yarn check:svelte",
"check:cargo": "cd src-tauri/ && cargo check --release",
- "check:svelte": "svelte-kit sync && svelte-check --tsconfig tsconfig.json",
- "check:watch": "svelte-kit sync && svelte-check --tsconfig tsconfig.json --watch",
+ "check:svelte": "cd apps/desktop && svelte-kit sync && svelte-check --tsconfig tsconfig.json",
+ "check:watch": "cd apps/desktop && svelte-kit sync && svelte-check --tsconfig tsconfig.json --watch",
"format": "yarn format:fix && yarn format:rust",
"format:check": "prettier --check \"**/*.{ts,js,json,svelte,css}\"",
"format:fix": "prettier --write \"**/*.{ts,js,json,svelte,css}\"",
@@ -25,7 +25,7 @@
"pre-commit": "lint-staged",
"prepare": "yarn prepare:husky",
"prepare:husky": "husky install",
- "preview": "vite preview",
+ "preview": "cd apps/desktop && vite preview",
"tauri": "tauri",
"bump": "node scripts/version.js",
"changelog:prepare": "node scripts/changelog.js prepare",
diff --git a/src/lib/api/analysis.ts b/shared/api/analysis.ts
similarity index 95%
rename from src/lib/api/analysis.ts
rename to shared/api/analysis.ts
index 277fed83..723c3978 100644
--- a/src/lib/api/analysis.ts
+++ b/shared/api/analysis.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { Track } from '$lib/types'
+import type { Track } from '../types'
/**
* Analyze tracks for BPM and key detection
diff --git a/src/lib/api/app.ts b/shared/api/app.ts
similarity index 100%
rename from src/lib/api/app.ts
rename to shared/api/app.ts
diff --git a/src/lib/api/backup.ts b/shared/api/backup.ts
similarity index 100%
rename from src/lib/api/backup.ts
rename to shared/api/backup.ts
diff --git a/src/lib/api/devices.ts b/shared/api/devices.ts
similarity index 93%
rename from src/lib/api/devices.ts
rename to shared/api/devices.ts
index 47dd931d..187df567 100644
--- a/src/lib/api/devices.ts
+++ b/shared/api/devices.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { UsbDevice } from '$lib/types'
+import type { UsbDevice } from '../types'
/**
* Get all connected removable USB devices
diff --git a/src/lib/api/diagnostics.ts b/shared/api/diagnostics.ts
similarity index 98%
rename from src/lib/api/diagnostics.ts
rename to shared/api/diagnostics.ts
index 1be84103..7b8e12ff 100644
--- a/src/lib/api/diagnostics.ts
+++ b/shared/api/diagnostics.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { DiagnosticEntry, DiagnosticsReport, SystemInfo } from '$lib/types'
+import type { DiagnosticEntry, DiagnosticsReport, SystemInfo } from '../types'
/**
* Get all diagnostic entries
diff --git a/src/lib/api/discovery.ts b/shared/api/discovery.ts
similarity index 99%
rename from src/lib/api/discovery.ts
rename to shared/api/discovery.ts
index 8e4576c3..1208f574 100644
--- a/src/lib/api/discovery.ts
+++ b/shared/api/discovery.ts
@@ -10,7 +10,7 @@ import type {
ScannedPage,
ScannedRelease,
BulkImportResult,
-} from '$lib/types'
+} from '../types'
export async function createRelease(create: DiscoveryReleaseCreate): Promise
{
return invoke('create_discovery_release', { create })
diff --git a/src/lib/api/export.ts b/shared/api/export.ts
similarity index 97%
rename from src/lib/api/export.ts
rename to shared/api/export.ts
index f987e60f..2ceae0e1 100644
--- a/src/lib/api/export.ts
+++ b/shared/api/export.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { ExportRequest, ExportResult, DeviceExport, ExportCheckpoint } from '$lib/types'
+import type { ExportRequest, ExportResult, DeviceExport, ExportCheckpoint } from '../types'
/**
* Export playlists to a USB device
diff --git a/src/lib/api/index.ts b/shared/api/index.ts
similarity index 100%
rename from src/lib/api/index.ts
rename to shared/api/index.ts
diff --git a/src/lib/api/library.ts b/shared/api/library.ts
similarity index 99%
rename from src/lib/api/library.ts
rename to shared/api/library.ts
index fb5c1989..995d66cc 100644
--- a/src/lib/api/library.ts
+++ b/shared/api/library.ts
@@ -8,7 +8,7 @@ import type {
TrackColor,
TrackFilter,
TrackUpdate,
-} from '$lib/types'
+} from '../types'
/**
* Import tracks from file paths into the library
diff --git a/src/lib/api/mediaControls.ts b/shared/api/mediaControls.ts
similarity index 100%
rename from src/lib/api/mediaControls.ts
rename to shared/api/mediaControls.ts
diff --git a/src/lib/api/menu.ts b/shared/api/menu.ts
similarity index 100%
rename from src/lib/api/menu.ts
rename to shared/api/menu.ts
diff --git a/src/lib/api/player.ts b/shared/api/player.ts
similarity index 96%
rename from src/lib/api/player.ts
rename to shared/api/player.ts
index 888aab96..db40adef 100644
--- a/src/lib/api/player.ts
+++ b/shared/api/player.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { PlaybackState } from '$lib/types'
+import type { PlaybackState } from '../types'
/**
* Start playing a track by ID
diff --git a/src/lib/api/playlists.ts b/shared/api/playlists.ts
similarity index 99%
rename from src/lib/api/playlists.ts
rename to shared/api/playlists.ts
index d7359e0f..a4ca79a2 100644
--- a/src/lib/api/playlists.ts
+++ b/shared/api/playlists.ts
@@ -6,7 +6,7 @@ import type {
Playlist,
SmartRules,
Track,
-} from '$lib/types'
+} from '../types'
/**
* Get all playlists for a given context
diff --git a/src/lib/api/settings.ts b/shared/api/settings.ts
similarity index 92%
rename from src/lib/api/settings.ts
rename to shared/api/settings.ts
index 5a2d99bb..7d970e49 100644
--- a/src/lib/api/settings.ts
+++ b/shared/api/settings.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { AppSettings, AudioDevice } from '$lib/types'
+import type { AppSettings, AudioDevice } from '../types'
/**
* Get all application settings
diff --git a/src/lib/api/sync.ts b/shared/api/sync.ts
similarity index 97%
rename from src/lib/api/sync.ts
rename to shared/api/sync.ts
index 22fa8c3b..a104e1c1 100644
--- a/src/lib/api/sync.ts
+++ b/shared/api/sync.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { SyncResult, DeviceInfo } from '$lib/types'
+import type { SyncResult, DeviceInfo } from '../types'
/**
* Sync playlists to a USB device (incremental sync)
diff --git a/src/lib/api/tags.ts b/shared/api/tags.ts
similarity index 97%
rename from src/lib/api/tags.ts
rename to shared/api/tags.ts
index 2fec2297..b7960838 100644
--- a/src/lib/api/tags.ts
+++ b/shared/api/tags.ts
@@ -1,5 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
-import type { Tag, TagCategory } from '$lib/types'
+import type { Tag, TagCategory } from '../types'
/**
* Get all tag categories with their tags
diff --git a/src/lib/api/updater.ts b/shared/api/updater.ts
similarity index 100%
rename from src/lib/api/updater.ts
rename to shared/api/updater.ts
diff --git a/src/lib/i18n/index.ts b/shared/i18n/index.ts
similarity index 100%
rename from src/lib/i18n/index.ts
rename to shared/i18n/index.ts
diff --git a/src/lib/i18n/locales/de.json b/shared/i18n/locales/de.json
similarity index 100%
rename from src/lib/i18n/locales/de.json
rename to shared/i18n/locales/de.json
diff --git a/src/lib/i18n/locales/en.json b/shared/i18n/locales/en.json
similarity index 100%
rename from src/lib/i18n/locales/en.json
rename to shared/i18n/locales/en.json
diff --git a/src/lib/i18n/locales/es.json b/shared/i18n/locales/es.json
similarity index 100%
rename from src/lib/i18n/locales/es.json
rename to shared/i18n/locales/es.json
diff --git a/src/lib/i18n/locales/fr.json b/shared/i18n/locales/fr.json
similarity index 100%
rename from src/lib/i18n/locales/fr.json
rename to shared/i18n/locales/fr.json
diff --git a/src/lib/i18n/locales/it.json b/shared/i18n/locales/it.json
similarity index 100%
rename from src/lib/i18n/locales/it.json
rename to shared/i18n/locales/it.json
diff --git a/src/lib/i18n/locales/ja.json b/shared/i18n/locales/ja.json
similarity index 100%
rename from src/lib/i18n/locales/ja.json
rename to shared/i18n/locales/ja.json
diff --git a/src/lib/i18n/locales/ko.json b/shared/i18n/locales/ko.json
similarity index 100%
rename from src/lib/i18n/locales/ko.json
rename to shared/i18n/locales/ko.json
diff --git a/src/lib/i18n/locales/nl.json b/shared/i18n/locales/nl.json
similarity index 100%
rename from src/lib/i18n/locales/nl.json
rename to shared/i18n/locales/nl.json
diff --git a/src/lib/i18n/locales/pl.json b/shared/i18n/locales/pl.json
similarity index 100%
rename from src/lib/i18n/locales/pl.json
rename to shared/i18n/locales/pl.json
diff --git a/src/lib/i18n/locales/pt.json b/shared/i18n/locales/pt.json
similarity index 100%
rename from src/lib/i18n/locales/pt.json
rename to shared/i18n/locales/pt.json
diff --git a/src/lib/i18n/locales/ro.json b/shared/i18n/locales/ro.json
similarity index 100%
rename from src/lib/i18n/locales/ro.json
rename to shared/i18n/locales/ro.json
diff --git a/src/lib/i18n/locales/sv.json b/shared/i18n/locales/sv.json
similarity index 100%
rename from src/lib/i18n/locales/sv.json
rename to shared/i18n/locales/sv.json
diff --git a/src/lib/i18n/locales/tr.json b/shared/i18n/locales/tr.json
similarity index 100%
rename from src/lib/i18n/locales/tr.json
rename to shared/i18n/locales/tr.json
diff --git a/src/lib/i18n/locales/uk.json b/shared/i18n/locales/uk.json
similarity index 100%
rename from src/lib/i18n/locales/uk.json
rename to shared/i18n/locales/uk.json
diff --git a/src/lib/i18n/locales/zh.json b/shared/i18n/locales/zh.json
similarity index 100%
rename from src/lib/i18n/locales/zh.json
rename to shared/i18n/locales/zh.json
diff --git a/src/lib/services/previewPlayer.ts b/shared/services/previewPlayer.ts
similarity index 100%
rename from src/lib/services/previewPlayer.ts
rename to shared/services/previewPlayer.ts
diff --git a/src/lib/stores/discovery.ts b/shared/stores/discovery.ts
similarity index 99%
rename from src/lib/stores/discovery.ts
rename to shared/stores/discovery.ts
index 792f2fcb..43333aa5 100644
--- a/src/lib/stores/discovery.ts
+++ b/shared/stores/discovery.ts
@@ -6,13 +6,13 @@ import type {
DiscoveryFilter,
DiscoverySortConfig,
ImportResultWithDuplicates,
-} from '$lib/types'
-import * as discoveryApi from '$lib/api/discovery'
+} from '../types'
+import * as discoveryApi from '../api/discovery'
import { playerStore } from './player'
import { discoveryPlaylistStore } from './discoveryPlaylist'
import { uiStore } from './ui'
import { toastStore } from './toast'
-import { translate } from '$lib/i18n'
+import { translate } from '../i18n'
// =============================================================================
// State
diff --git a/src/lib/stores/discoveryPlaylist.ts b/shared/stores/discoveryPlaylist.ts
similarity index 98%
rename from src/lib/stores/discoveryPlaylist.ts
rename to shared/stores/discoveryPlaylist.ts
index 48bb2322..b92e60eb 100644
--- a/src/lib/stores/discoveryPlaylist.ts
+++ b/shared/stores/discoveryPlaylist.ts
@@ -1,5 +1,5 @@
import { writable, derived } from 'svelte/store'
-import type { DiscoveryRelease } from '$lib/types'
+import type { DiscoveryRelease } from '../types'
import { SvelteMap } from 'svelte/reactivity'
// =============================================================================
diff --git a/src/lib/stores/expandedReleases.ts b/shared/stores/expandedReleases.ts
similarity index 95%
rename from src/lib/stores/expandedReleases.ts
rename to shared/stores/expandedReleases.ts
index eabf96b8..04280de0 100644
--- a/src/lib/stores/expandedReleases.ts
+++ b/shared/stores/expandedReleases.ts
@@ -1,5 +1,5 @@
import { writable } from 'svelte/store'
-import { getStoredSet, setStoredSet } from '$lib/utils/storage'
+import { getStoredSet, setStoredSet } from '../utils/storage'
const STORAGE_KEY = 'expandedReleaseIds'
diff --git a/shared/stores/index.ts b/shared/stores/index.ts
new file mode 100644
index 00000000..343d4401
--- /dev/null
+++ b/shared/stores/index.ts
@@ -0,0 +1,84 @@
+export {
+ playerStore,
+ isPlaying,
+ currentTrack,
+ playbackPosition,
+ playbackDuration,
+ volume,
+ playbackProgress,
+ playbackSource,
+ playbackSpeed,
+ previewInfo,
+ previewTrackIndex,
+ previewLoadingReleaseId,
+ isMuted,
+} from './player'
+export type { PlayerStoreHooks } from './player'
+export { tagsStore, allTags, getTagById, getCategoryById, computeTagStates } from './tags'
+export {
+ playlistsStore,
+ rootPlaylists,
+ getPlaylistChildren,
+ buildPlaylistTree,
+ getPlaylistPath,
+ buildBreadcrumbItems,
+} from './playlists'
+export type { PlaylistTreeNode, PlaylistsStoreHooks } from './playlists'
+export {
+ uiStore,
+ activeView,
+ selectedTrackIds,
+ selectedTrackCount,
+ hasSelection,
+ selectedReleaseIds,
+ selectedReleaseCount,
+ recentlyToggledMixedTags,
+ selectedTagIds,
+ tagFilterMode,
+ rightSidebarVisible,
+ rightSidebarWidth,
+ selectedTreeIds,
+ contextMenuPlaylistId,
+ scrollOffset,
+ playlistScrollOffsets,
+} from './ui'
+export { toastStore, toasts, hasToasts } from './toast'
+export type { Toast, ToastType } from './toast'
+export {
+ settingsStore,
+ theme,
+ accentColor,
+ font,
+ resolvedTheme,
+ settingsLoading,
+ keyNotationFormat,
+ language,
+ dateFormat,
+ exportFormat,
+ autoAnalyzeOnImport,
+ autoSyncOnConnect,
+ autoSyncOnChange,
+ continuousPlayback,
+ autoFetchMetadata,
+ transferTagsOnImport,
+ removeReleaseAfterImport,
+ ignoredDeviceIds,
+ lastBackupAt,
+ backupFrequency,
+ lastBackupType,
+ hasCompletedOnboarding,
+ hasCompletedWizard,
+ audioDevice,
+ audioDevices,
+} from './settings'
+export type { SettingsStoreHooks } from './settings'
+export {
+ discoveryStore,
+ sortedReleases,
+ displayedReleases,
+ releaseCount,
+ isDiscoveryLoading,
+ refreshingReleaseIds,
+} from './discovery'
+export { expandedReleaseIds } from './expandedReleases'
+export { discoveryPlaylistStore, discoveryPlaylistReleases } from './discoveryPlaylist'
diff --git a/src/lib/stores/player.ts b/shared/stores/player.ts
similarity index 97%
rename from src/lib/stores/player.ts
rename to shared/stores/player.ts
index d56c7672..5c5f76ec 100644
--- a/src/lib/stores/player.ts
+++ b/shared/stores/player.ts
@@ -1,11 +1,10 @@
import { writable, derived, get } from 'svelte/store'
-import type { Track, PlaybackState, PreviewInfo, DiscoveryRelease } from '$lib/types'
-import * as playerApi from '$lib/api/player'
-import * as discoveryApi from '$lib/api/discovery'
-import * as previewPlayer from '$lib/services/previewPlayer'
-import { missingTracksStore } from './missingTracks'
+import type { Track, PlaybackState, PreviewInfo, DiscoveryRelease } from '../types'
+import * as playerApi from '../api/player'
+import * as discoveryApi from '../api/discovery'
+import * as previewPlayer from '../services/previewPlayer'
import { toastStore } from './toast'
-import { translate } from '$lib/i18n'
+import { translate } from '../i18n'
import {
getStoredNumber,
setStoredNumber,
@@ -13,7 +12,7 @@ import {
setStoredString,
getStoredBoolean,
setStoredBoolean,
-} from '$lib/utils/storage'
+} from '../utils/storage'
// =============================================================================
// State
@@ -67,9 +66,14 @@ const initialState: PlayerState = {
// Store
// =============================================================================
+export interface PlayerStoreHooks {
+ onTrackMissing?: (trackId: string) => void
+}
+
function createPlayerStore() {
const { subscribe, set, update } = writable(initialState)
+ let hooks: PlayerStoreHooks = {}
let positionInterval: ReturnType | null = null
let onTrackEndCallback: (() => void) | null = null
let previewRetryAttempted = false
@@ -265,6 +269,13 @@ function createPlayerStore() {
return {
subscribe,
+ /**
+ * Register hooks for desktop-only store interactions.
+ */
+ registerHooks(h: PlayerStoreHooks) {
+ hooks = h
+ },
+
/**
* Play a library track. If preview is active, stop it first.
*/
@@ -304,7 +315,7 @@ function createPlayerStore() {
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Failed to play track'
if (errorMsg.toLowerCase().includes('file not found') || errorMsg.toLowerCase().includes('filenotfound')) {
- missingTracksStore.markMissing(track.id)
+ hooks.onTrackMissing?.(track.id)
}
update((s) => ({ ...s, error: errorMsg }))
}
@@ -471,7 +482,7 @@ function createPlayerStore() {
} catch (error) {
const errorMsg = error instanceof Error ? error.message : 'Failed to play track'
if (errorMsg.toLowerCase().includes('file not found') || errorMsg.toLowerCase().includes('filenotfound')) {
- missingTracksStore.markMissing(state.currentTrack.id)
+ hooks.onTrackMissing?.(state.currentTrack.id)
}
update((s) => ({ ...s, error: errorMsg }))
}
diff --git a/src/lib/stores/playlists.ts b/shared/stores/playlists.ts
similarity index 96%
rename from src/lib/stores/playlists.ts
rename to shared/stores/playlists.ts
index e15823d7..2433fe0c 100644
--- a/src/lib/stores/playlists.ts
+++ b/shared/stores/playlists.ts
@@ -8,10 +8,9 @@ import type {
BreadcrumbItem,
MoveConflictResolution,
MovePlaylistResult,
-} from '$lib/types'
-import * as playlistsApi from '$lib/api/playlists'
+} from '../types'
+import * as playlistsApi from '../api/playlists'
import { toastStore } from './toast'
-import { syncStore } from './sync'
// =============================================================================
// State
@@ -33,12 +32,25 @@ const initialState: PlaylistsState = {
// Store
// =============================================================================
+export interface PlaylistsStoreHooks {
+ onPlaylistChanged?: (playlistIds: string[]) => void
+}
+
function createPlaylistsStore() {
const { subscribe, set, update } = writable(initialState)
+ let hooks: PlaylistsStoreHooks = {}
+
return {
subscribe,
+ /**
+ * Register hooks for desktop-only store interactions.
+ */
+ registerHooks(h: PlaylistsStoreHooks) {
+ hooks = h
+ },
+
/**
* Load all playlists from both contexts
*/
@@ -117,8 +129,7 @@ function createPlaylistsStore() {
playlists: state.playlists.map((p) => (p.id === id ? updated : p)),
}))
- // Notify sync store about playlist changes (for auto-sync)
- syncStore.notifyPlaylistChanges([id])
+ hooks.onPlaylistChanged?.([id])
return updated
} catch (error) {
@@ -233,8 +244,7 @@ function createPlaylistsStore() {
playlists: state.playlists.map((p) => (p.id === playlistId ? updatedPlaylist : p)),
}))
- // Notify sync store about playlist changes (for auto-sync)
- syncStore.notifyPlaylistChanges([playlistId])
+ hooks.onPlaylistChanged?.([playlistId])
} catch (error) {
update((state) => ({
...state,
@@ -255,8 +265,7 @@ function createPlaylistsStore() {
playlists: state.playlists.map((p) => (p.id === playlistId ? updatedPlaylist : p)),
}))
- // Notify sync store about playlist changes (for auto-sync)
- syncStore.notifyPlaylistChanges([playlistId])
+ hooks.onPlaylistChanged?.([playlistId])
} catch (error) {
update((state) => ({
...state,
@@ -272,8 +281,7 @@ function createPlaylistsStore() {
try {
await playlistsApi.reorderPlaylist(playlistId, trackIds)
- // Notify sync store about playlist changes (for auto-sync)
- syncStore.notifyPlaylistChanges([playlistId])
+ hooks.onPlaylistChanged?.([playlistId])
} catch (error) {
update((state) => ({
...state,
diff --git a/src/lib/stores/settings.ts b/shared/stores/settings.ts
similarity index 94%
rename from src/lib/stores/settings.ts
rename to shared/stores/settings.ts
index 1ca316b0..16fe1a1c 100644
--- a/src/lib/stores/settings.ts
+++ b/shared/stores/settings.ts
@@ -10,11 +10,9 @@ import type {
DateFormat,
ExportFormat,
BackupFrequency,
-} from '$lib/types'
-import * as settingsApi from '$lib/api/settings'
-import { rebuildMenu, type MenuTranslations } from '$lib/api/app'
-import { setLanguage as setI18nLanguage, translate } from '$lib/i18n'
-import { appStore } from './app'
+} from '../types'
+import * as settingsApi from '../api/settings'
+import { setLanguage as setI18nLanguage, translate } from '../i18n'
// =============================================================================
// State
@@ -89,8 +87,14 @@ function getSystemTheme(): 'light' | 'dark' {
// Store
// =============================================================================
+export interface SettingsStoreHooks {
+ getAppName?: () => string
+ onLanguageChanged?: () => Promise
+}
+
function createSettingsStore() {
const { subscribe, set, update } = writable(initialState)
+ let hooks: SettingsStoreHooks = {}
let systemThemeMediaQuery: MediaQueryList | null = null
let mediaQueryHandler: ((e: MediaQueryListEvent) => void) | null = null
@@ -154,23 +158,9 @@ function createSettingsStore() {
systemThemeMediaQuery.addEventListener('change', mediaQueryHandler)
}
- function getAppName(): string {
- const appState = get(appStore)
- const environment = appState.info?.environment ?? 'development'
- if (environment === 'production') {
- return 'Crate'
- }
- if (environment === 'development') {
- return 'Crate Dev'
- }
- // Other environments (alpha, beta, staging, etc.) use capitalized name
- const suffix = environment.charAt(0).toUpperCase() + environment.slice(1)
- return `Crate ${suffix}`
- }
-
- function getMenuTranslations(): MenuTranslations {
+ function getMenuTranslations() {
const t = get(translate)
- const appName = getAppName()
+ const appName = hooks.getAppName?.() ?? 'Crate'
return {
// Menu titles
file: t('menu.file'),
@@ -235,17 +225,21 @@ function createSettingsStore() {
}
}
- async function updateMenuTranslations() {
- try {
- await rebuildMenu(getMenuTranslations())
- } catch (error) {
- console.error('Failed to rebuild menu:', error)
- }
- }
-
return {
subscribe,
+ /**
+ * Register hooks for desktop-only store interactions.
+ */
+ registerHooks(h: SettingsStoreHooks) {
+ hooks = h
+ },
+
+ /**
+ * Get translated menu labels for rebuilding the native menu.
+ */
+ getMenuTranslations,
+
/**
* Load settings from backend
*/
@@ -293,7 +287,7 @@ function createSettingsStore() {
// Update i18n language and menu
await setI18nLanguage(settings.language)
await tick()
- await updateMenuTranslations()
+ await hooks.onLanguageChanged?.()
} catch (error) {
update((s) => ({
...s,
@@ -384,7 +378,7 @@ function createSettingsStore() {
update((s) => ({ ...s, language }))
await setI18nLanguage(language)
await tick()
- await updateMenuTranslations()
+ await hooks.onLanguageChanged?.()
persistToLocalStorage(state.theme, state.accentColor, language)
try {
diff --git a/src/lib/stores/tags.ts b/shared/stores/tags.ts
similarity index 99%
rename from src/lib/stores/tags.ts
rename to shared/stores/tags.ts
index 11c723a5..ee6b722f 100644
--- a/src/lib/stores/tags.ts
+++ b/shared/stores/tags.ts
@@ -1,6 +1,6 @@
import { writable, derived } from 'svelte/store'
-import type { TagCategory, Tag, TagSelectionState, Track } from '$lib/types'
-import * as tagsApi from '$lib/api/tags'
+import type { TagCategory, Tag, TagSelectionState, Track } from '../types'
+import * as tagsApi from '../api/tags'
import { toastStore } from './toast'
// =============================================================================
diff --git a/src/lib/stores/toast.ts b/shared/stores/toast.ts
similarity index 98%
rename from src/lib/stores/toast.ts
rename to shared/stores/toast.ts
index 1fee89a9..afe9757b 100644
--- a/src/lib/stores/toast.ts
+++ b/shared/stores/toast.ts
@@ -1,5 +1,5 @@
import { writable, derived } from 'svelte/store'
-import * as diagnosticsApi from '$lib/api/diagnostics'
+import * as diagnosticsApi from '../api/diagnostics'
// =============================================================================
// Types
diff --git a/src/lib/stores/ui.ts b/shared/stores/ui.ts
similarity index 99%
rename from src/lib/stores/ui.ts
rename to shared/stores/ui.ts
index 4301a992..f6fcd701 100644
--- a/src/lib/stores/ui.ts
+++ b/shared/stores/ui.ts
@@ -1,5 +1,5 @@
import { writable, derived } from 'svelte/store'
-import type { ActiveView, SidebarView, TagFilterMode } from '$lib/types'
+import type { ActiveView, SidebarView, TagFilterMode } from '../types'
import {
getStoredBoolean,
getStoredNumber,
@@ -7,7 +7,7 @@ import {
setStoredBoolean,
setStoredNumber,
setStoredString,
-} from '$lib/utils/storage'
+} from '../utils/storage'
// =============================================================================
// State
diff --git a/src/lib/types/index.ts b/shared/types/index.ts
similarity index 100%
rename from src/lib/types/index.ts
rename to shared/types/index.ts
diff --git a/src/lib/utils/artwork.ts b/shared/utils/artwork.ts
similarity index 79%
rename from src/lib/utils/artwork.ts
rename to shared/utils/artwork.ts
index 8acbd7d7..8c3879b4 100644
--- a/src/lib/utils/artwork.ts
+++ b/shared/utils/artwork.ts
@@ -1,18 +1,15 @@
import { convertFileSrc } from '@tauri-apps/api/core'
-import { get } from 'svelte/store'
-import { appDataDir } from '$lib/stores/app'
/**
* Converts an artwork relative path to a displayable URL using Tauri's asset protocol.
* Returns undefined if no artwork path or app data dir is available.
*
* @param artworkPath - Relative path like "artwork/{track_id}.webp"
+ * @param dataDir - Absolute path to the app data directory
* @returns Asset URL for use in img src, or undefined
*/
-export function getArtworkUrl(artworkPath: string | null | undefined): string | undefined {
+export function getArtworkUrl(artworkPath: string | null | undefined, dataDir: string | null): string | undefined {
if (!artworkPath) return undefined
-
- const dataDir = get(appDataDir)
if (!dataDir) return undefined
const fullPath = `${dataDir}/${artworkPath}`
diff --git a/src/lib/utils/bulkEdit.ts b/shared/utils/bulkEdit.ts
similarity index 98%
rename from src/lib/utils/bulkEdit.ts
rename to shared/utils/bulkEdit.ts
index b8868d0d..1b7d2217 100644
--- a/src/lib/utils/bulkEdit.ts
+++ b/shared/utils/bulkEdit.ts
@@ -1,4 +1,4 @@
-import type { ArtworkSource, BulkEditValue, BulkTrackInfo, Track } from '$lib/types'
+import type { ArtworkSource, BulkEditValue, BulkTrackInfo, Track } from '../types'
/**
* Placeholder text for mixed values in bulk edit mode
diff --git a/src/lib/utils/dom.ts b/shared/utils/dom.ts
similarity index 94%
rename from src/lib/utils/dom.ts
rename to shared/utils/dom.ts
index e0511e26..c25ead6b 100644
--- a/src/lib/utils/dom.ts
+++ b/shared/utils/dom.ts
@@ -1,4 +1,4 @@
-import { setDialogConflictingItemsEnabled } from '$lib/api/app'
+import { setDialogConflictingItemsEnabled } from '../api/app'
/**
* Check if the currently focused element is an input or textarea.
diff --git a/src/lib/utils/drag.ts b/shared/utils/drag.ts
similarity index 100%
rename from src/lib/utils/drag.ts
rename to shared/utils/drag.ts
diff --git a/src/lib/utils/format.ts b/shared/utils/format.ts
similarity index 100%
rename from src/lib/utils/format.ts
rename to shared/utils/format.ts
diff --git a/src/lib/utils/index.ts b/shared/utils/index.ts
similarity index 100%
rename from src/lib/utils/index.ts
rename to shared/utils/index.ts
diff --git a/src/lib/utils/playlist.ts b/shared/utils/playlist.ts
similarity index 95%
rename from src/lib/utils/playlist.ts
rename to shared/utils/playlist.ts
index 0a0cdd59..d33587f4 100644
--- a/src/lib/utils/playlist.ts
+++ b/shared/utils/playlist.ts
@@ -1,4 +1,4 @@
-import type { Playlist } from '$lib/types'
+import type { Playlist } from '../types'
/**
* Find a playlist by its ID.
diff --git a/src/lib/utils/position.ts b/shared/utils/position.ts
similarity index 100%
rename from src/lib/utils/position.ts
rename to shared/utils/position.ts
diff --git a/src/lib/utils/selection.ts b/shared/utils/selection.ts
similarity index 100%
rename from src/lib/utils/selection.ts
rename to shared/utils/selection.ts
diff --git a/src/lib/utils/smartRules.ts b/shared/utils/smartRules.ts
similarity index 99%
rename from src/lib/utils/smartRules.ts
rename to shared/utils/smartRules.ts
index 25939686..503bfa22 100644
--- a/src/lib/utils/smartRules.ts
+++ b/shared/utils/smartRules.ts
@@ -7,7 +7,7 @@ import type {
EnumOperator,
TagOperator,
TagCategory,
-} from '$lib/types'
+} from '../types'
// =============================================================================
// Field Definitions
diff --git a/src/lib/utils/sorting.ts b/shared/utils/sorting.ts
similarity index 96%
rename from src/lib/utils/sorting.ts
rename to shared/utils/sorting.ts
index 8ae08218..40430d92 100644
--- a/src/lib/utils/sorting.ts
+++ b/shared/utils/sorting.ts
@@ -1,5 +1,5 @@
-import type { Track, SortConfig, TrackSortField, SortDirection, TrackColor } from '$lib/types'
-import { COLOR_SORT_ORDER } from '$lib/types'
+import type { Track, SortConfig, TrackSortField, SortDirection, TrackColor } from '../types'
+import { COLOR_SORT_ORDER } from '../types'
/**
* Sort tracks by the given configuration
diff --git a/src/lib/utils/storage.ts b/shared/utils/storage.ts
similarity index 100%
rename from src/lib/utils/storage.ts
rename to shared/utils/storage.ts
diff --git a/src/lib/utils/tagComputation.ts b/shared/utils/tagComputation.ts
similarity index 98%
rename from src/lib/utils/tagComputation.ts
rename to shared/utils/tagComputation.ts
index fd454168..1fe2d540 100644
--- a/src/lib/utils/tagComputation.ts
+++ b/shared/utils/tagComputation.ts
@@ -1,4 +1,4 @@
-import type { TagCategory, TagSelectionState, DiscoveryRelease } from '$lib/types'
+import type { TagCategory, TagSelectionState, DiscoveryRelease } from '../types'
import { SvelteMap } from 'svelte/reactivity'
export function computeDiscoveryTagStates(
diff --git a/src/lib/utils/transitions.ts b/shared/utils/transitions.ts
similarity index 100%
rename from src/lib/utils/transitions.ts
rename to shared/utils/transitions.ts
diff --git a/src/lib/utils/virtualizer.svelte.ts b/shared/utils/virtualizer.svelte.ts
similarity index 99%
rename from src/lib/utils/virtualizer.svelte.ts
rename to shared/utils/virtualizer.svelte.ts
index 3653f6ff..707c51c6 100644
--- a/src/lib/utils/virtualizer.svelte.ts
+++ b/shared/utils/virtualizer.svelte.ts
@@ -85,7 +85,6 @@ export function createVirtualList(options: VirtualListOptions) {
// re-trigger the $effect.pre → syncState() → infinite loop.
// Writes still fire notifications, so {#each} templates update.
untrack(() => {
- // @ts-expect-error we know
totalSize = instance.getTotalSize()
// Check if we can update in-place (same keys in same order).
diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json
index 7f229a39..9b9e0e7c 100644
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -7,7 +7,7 @@
"beforeDevCommand": "yarn dev:vite",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "yarn build:vite",
- "frontendDist": "../build"
+ "frontendDist": "../apps/desktop/build"
},
"app": {
"windows": [