This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Jellyfin Vue is an experimental, browser-based web client for a Jellyfin media server, written in Vue 3. It is a pure frontend: it only ever talks to the Jellyfin server the user points it at. It is not meant to replace the main jellyfin-web client and is not feature-complete.
pnpm workspace (pnpm@11.1.2, Node >=24.11.0 <25). engineStrict is on — use the exact versions. Packages:
packages/frontend— the actual SPA (@jellyfin-vue/frontend). Vite + Vuetify 3 + vue-router. This is where almost all app code lives.packages/ui-toolkit— base components (all prefixedJ, e.g.JImg,JVirtual), composables, and a global store. Auto-resolved into the frontend viaunplugin-vue-components.packages/shared— framework-agnostic helpers, split intosrc/universal/(browser+node safe) andsrc/node/. Imported as@jellyfin-vue/shared/<file>and@jellyfin-vue/shared/node/<file>.packages/i18n— i18next setup; locale files are generated into virtual modules at build time (@jellyfin-vue/i18n/vite).packages/configs— shared ESLint flat config, TypeScript base config, UnoCSS preset, Storybook config. Consumed by every other package'sdevDependencies.packages/vite-plugins— internal Vite plugins (JBundle,JMonorepo) and bundle analysis.packages/tauri-runtime+packaging/tauri— desktop builds;tauri-runtimere-bundles the frontend with Tauri-specific runtime code.
Dependency versions shared across packages are pinned in pnpm-workspace.yaml catalogs (catalog:runtime, catalog:development) — change versions there, not in individual package.json files.
Root scripts fan out to every package with pnpm -r --if-present --no-bail:
pnpm lint/pnpm lint:fix— ESLint across all packagespnpm check:types—vue-tsctype-check across all packagespnpm test— Vitest (onlypackages/sharedcurrently has tests)pnpm analyze:cycles— detect circular imports
Run a single package's script with pnpm -C <dir>, e.g.:
pnpm -C packages/frontend start— dev server (Vite, port 3000)pnpm -C packages/frontend build— production buildpnpm -C packages/frontend check— lint + type-check for that packagepnpm -C packages/frontend storybook— Storybook on port 6006pnpm -C packages/shared test— run the shared package's Vitest suitepnpm -C packaging/tauri start— Tauri desktop dev build
Vite configs are loaded with --configLoader runner so the TS config files can import workspace code.
packages/frontend/src/plugins/remote/ is the single gateway to the Jellyfin server. It is a @sealed class exported as the remote singleton (also installed as $remote) with four parts:
remote.auth— authentication / current user / current server, persistedremote.sdk— the@jellyfin/sdkclient; call APIs viaremote.sdk.newUserApi(getSomeApi)remote.socket— the Jellyfin WebSocketremote.axios— the underlying request performer
Always go through remote to reach the server; do not instantiate SDK/axios clients directly.
There is no Pinia. Stores are plain classes extending a three-level hierarchy:
BaseState— reactive state with adefaultState, reset, and optionallocalStorage/sessionStoragepersistence. For plugins/abstract use only (no auth dependency).CommonStore extends BaseState— adds automatic state reset on logout (resetOnLogout). Base class for normal stores.SyncedStore extends CommonStore— additionally syncs selected keys to the server'sDisplayPreferences.
A store's generic params are <StateShape, ExposedKeys>: keys listed in ExposedKeys become public and writable; everything else is private. Stores are exported as singletons. Notable stores: playback-manager.ts, player-element.ts, task-manager.ts, dbs/ (Dexie/IndexedDB-backed item cache, with a worker).
Routes are generated from packages/frontend/src/pages/ by vue-router/vite (unplugin-vue-router). <route> blocks in .vue files are YAML. Routes are registered in main.ts (not the router plugin) to avoid circular imports. router.currentRoute.value.meta.layout drives layout selection (see src/layouts/).
- UI-toolkit components (
J*prefix) are auto-imported — no explicit import needed; theJellyfinVueUIToolkitresolver matches/^J[A-Z]/. Vuetify components are auto-imported viaVuetify3Resolver. - Styling is UnoCSS (preset in
@jellyfin-vue/configs/uno) plus Vuetify; CSS is processed with Lightning CSS, browser targets frombrowserslist. - provide/inject keys are centralised as typed
InjectionKeysymbols instore/keys.tsfiles.
- User-facing settings pages (
pages/settings/account.vue,experimental.vue,subtitles.vue) wrap their content inSettingsPage(components/Layout/SettingsPage.vue) — slots#title,#actions,#content. - Admin pages wrap in
AdminSettingsLayoutinstead — same slot API, but adds a persistent left sidebar (a temporary right drawer on mobile) listing every admin section. New admin pages must useAdminSettingsLayoutto keep the sidebar from popping in/out across navigations, and must carry a<route lang="yaml">meta: admin: true</route>block soadminGuard(plugins/router/middlewares/admin-pages.ts) enforces access. - The sidebar entries come from a single source:
composables/use-admin-sections.ts. Add new admin pages there (and topages/settings/index.vuevia the same composable).
main.ts relies on top-level await (ES2022) and Object.groupBy (ES2024) — keep the TS target in sync if touching tsconfig.json.
- TypeScript
importstatements must use explicit extensions (.ts,.vue) — see existing imports like#/store/index.ts. - Path aliases:
#/*maps to a package'ssrc/(configured per-package inpackage.jsonimports). - Commits follow Conventional Commits (enforced; see the
chore(i18n):,chore(deps):history). - i18n strings are managed through Weblate — do not hand-edit generated locale data.