Files
officer a7f1a71785 jellyfin, extracted from the platform as a plugin
Eighth plugin out, and the second resurrection: like soulseek, hono.ts carried
the router import and its mount commented out since 2026-08-13, with the
permission reduced to api: [], so the server booted clean while every panel
request 404'd. jellyfin_servers was also absent from the drizzle barrel, so
bun db:push would have dropped it.

Easier than soulseek in one specific way: mountPrefix derives /jellyfin from
the appName, which is exactly what the router already hardcoded, so no frontend
call site needed repointing. Soulseek needed 47 because its appName and its old
prefix differed. The frontend also already routed through one helper, so there
were five call sites rather than forty-seven.

Self-contained, unlike transmission and soulseek: jellyfin_servers is a
registry this plugin owns outright — label, URL, encrypted token, one active —
because more than one Jellyfin is a thing someone has, and more than one
Transmission is not.

Carries its own .prettierignore for the vendored hls.mjs, rather than leaving a
stale rule behind in the platform naming a path that no longer exists.
2026-08-19 03:17:32 +00:00

3.7 KiB

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:

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.