# Jellyfin — the extraction record Extracted from the platform on **2026-08-19**, the eighth plugin out, immediately after soulseek. `plugins/EXTRACTING-A-PLUGIN.md` is the runbook. ## Also a resurrection Like soulseek, and for the same reason: `hono.ts` carried both the router import and its `PROTECTED_MOUNTS` entry commented out (`// plugin — switched off 2026-08-13`), and the permission had been reduced to `api: []`. So `assertPermissionTotality` was satisfied, the server booted clean, and every request from the panels 404'd. It had been dead for six days. The same data hazard applied: `schema.ts` had `export * from './jellyfin/schema'` commented out, so `jellyfin_servers` was absent from the drizzle barrel and `bun db:push` would have dropped it. ## What moved | Was | Is now | | --------------------------------------------------------- | --------------- | | `src/servers/sidecar/jellyfin/*` (5 files) | `sidecar/*` | | `src/servers/api/jellyfin/router.ts` | `api/router.ts` | | `src/databases/officer_db/src/jellyfin/*` (3 files) | `db/*` | | `src/workspaces/officerdev/src/apps/Jellyfin/*` (20 files, incl. `vendor/`) | `web/*` | | `src/apps/officer-web/Screens/Dashboard/Jellyfin/*` | `web/layout.ts` | ## Easier than soulseek, in one specific way The mount prefix is unchanged. `mountPrefix` derives `/jellyfin` from the appName, which is what the router was already hardcoding — so **no frontend call site had to be repointed**. Soulseek needed 47, because its appName (`soulseek`) and its old prefix (`slskd`) differed. The frontend also already routed everything through one helper (`useJellyfinData.ts`), so there were five call sites in total rather than forty-seven spread across every panel. ## It owns its data, unlike transmission and soulseek `jellyfin_servers` is a **registry** — label, URL, encrypted token, one active — and it moved into this plugin whole. That is the right shape here for the same reason `service_connections` is the right shape there: more than one Jellyfin is a thing someone actually has, and more than one Transmission is not. So this plugin is self-contained. No core table, no platform union to extend. ## Fixtures: fixed properly this time `jellyfin` was the canonical app-store test fixture, and extracting it broke **18 tests**. That is the fourth extraction in a row to break the same files — photos and transmission on 2026-08-18, slskd earlier on 2026-08-19, jellyfin an hour later. `installer.test.ts` now builds **literal** fixtures and asks the catalogue nothing: ```ts const fullInstall: CatalogueEntry = { id: 'test-full', members: 'accounts', modes: [...], ... }; ``` Those tests pin `planSteps` and `runInstall` — installer *logic*. A service leaving for `plugins/` says nothing about whether the installer still plans the right steps, so it should never have been able to break them. `catalogue.test.ts` remains the place where real entries are asserted well-formed. `members.test.ts`, `catalogue.test.ts` and `preflight.test.ts` were re-anchored to `memos`, which has jellyfin's exact shape (`members: 'accounts'`, `modes: ['existing', 'provisioned']`). ## Carried its own ignore rule `.prettierignore` in the platform named `src/workspaces/officerdev/src/apps/Jellyfin/vendor/hls.mjs`. That path no longer exists, so the rule moved into this repo's own `.prettierignore` rather than being left behind as a stale line matching nothing. ## Not carried over `composeTemplate: 'jellyfin'` and `modes: ['existing', 'provisioned']` died with the catalogue entry. Like every plugin so far, this is **point-at-your-own-server only** until plugin Docker provisioning exists.