This directory contains composables for managing user preferences — settings that are synced to the server for authenticated users and persisted in localStorage for anonymous users.
| Store | Composable | localStorage key | Synced to server | Use case |
|---|---|---|---|---|
| User preferences | useUserPreferencesState() |
npmx-user-preferences |
Yes (authenticated) | Settings the user would expect on another device |
| Local settings | useUserLocalSettings() |
npmx-settings |
No | Device-specific UI state |
Would a user expect this setting to transfer to another browser or device?
- Yes → user preference (
npmx-user-preferences)- No → local setting (
npmx-settings)
User preferences (synced): accent color, background theme, color mode, locale, search provider, keyboard shortcuts, instant search, relative dates, hide platform packages, include @types in install.
Local settings (device-only): chart filter params (average window, smoothing, prediction, anomalies), sidebar collapse state, connector auto-open URL.
| Composable | Purpose |
|---|---|
useUserPreferencesState() |
Read/write access to the full preferences ref |
useAccentColor() |
Accent color picker with DOM sync |
useBackgroundTheme() |
Background shade with DOM sync |
useColorModePreference() |
Color mode synced with @nuxtjs/color-mode |
useInstantSearchPreference() |
Toggle instant search (shared) |
useKeyboardShortcutsPreference() |
Toggle keyboard shortcuts with DOM attribute sync (shared) |
useRelativeDatesPreference() |
Read-only computed for relative date display |
useSearchProvider() |
npm/algolia toggle |
useUserPreferencesSyncStatus() |
Sync status signals (isSyncing, isSynced, hasError) for UI |
useInitUserPreferencesSync() |
Imperative initSync() — called by the plugin, not by components |
-
Add the field to the schema in
shared/schemas/userPreferences.ts:export const UserPreferencesSchema = object({ // ... existing fields myNewPref: optional(boolean()), })
-
Add a default value in
DEFAULT_USER_PREFERENCES(same file):export const DEFAULT_USER_PREFERENCES = { // ... existing defaults myNewPref: false, }
-
Create a composable in this directory (e.g.
useMyNewPref.ts):export function useMyNewPref() { const { preferences } = useUserPreferencesState() return computed({ get: () => preferences.value.myNewPref ?? false, set: (value: boolean) => { preferences.value.myNewPref = value }, }) }
-
Use it in components — the composable is auto-imported:
<script setup lang="ts"> const myNewPref = useMyNewPref() </script>
The preference will automatically persist to localStorage and sync to the server for authenticated users. No additional wiring needed.
-
Add the field to the
UserLocalSettingsinterface andDEFAULT_USER_LOCAL_SETTINGSinapp/composables/useUserLocalSettings.ts:export interface UserLocalSettings { // ... existing fields myLocalThing: boolean } const DEFAULT_USER_LOCAL_SETTINGS: UserLocalSettings = { // ... existing defaults myLocalThing: false, }
-
Use it in components:
<script setup lang="ts"> const { localSettings } = useUserLocalSettings() // localSettings.value.myLocalThing </script>
useUserPreferencesProvider ← cached singleton, manages localStorage + sync lifecycle
├── createProvider() ← internal: sets up localStorage ref + lazy sync state
│ └── initSync() ← resolves useAtproto() + useUserPreferencesSync() lazily
├── useUserPreferencesSync ← client-only: receives isAuthenticated ref via DI
├── useUserPreferencesState ← read/write access to reactive ref (used by all composables above)
└── preferences-merge.ts ← merge logic for first-login vs returning-user scenarios
useUserLocalSettings ← separate singleton, localStorage only, no sync
useLocalStorageHashProvider ← generic localStorage + defu provider (used by usePackageListPreferences)
preferences-sync.client.tsplugin callsinitSync()on app bootinitSync()lazily resolvesuseAtproto()anduseUserPreferencesSync(isAuthenticated)— auth is not fetched at provider construction time- Preferences are loaded from server and merged with local state
- A deep watcher on the preferences ref triggers
scheduleSync()on every change scheduleSync()debounces for 2 seconds, then pushes toPUT /api/user/preferences- On route navigation,
router.beforeEachflushes any pending sync - On tab close,
sendBeaconfires the latest preferences viaPOST /api/user/preferences