Skip to content

Moonbase for Silo: exact Jellyfin page copy, per-host hidden sections, Silo Home rows, per-profile admin actions, Watch Party extras, room for thousands of profiles - #359

Closed
Brady3035 wants to merge 26 commits into
Moonfin-Client:masterfrom
Brady3035:silo-moonbase-v2

Conversation

@Brady3035

Copy link
Copy Markdown
Contributor

Pull Request

Summary

Follows up the review of the earlier Moonbase for Silo PR, on top of the move to Go/.

  • The admin page. web/configPage.html is an unchanged copy of the Jellyfin page again, so updating it stays a copy step. Everything Silo- or Plex-specific lives in web/host.js, per host in its hosts table:
    • Sections Silo handles itself or will never use are hidden, not removed, and still save.
    • Sections Silo can't run yet keep a note saying why.
    • The Studio Logos note is gone.
  • Silo's own Home rows. They can now be set as admin defaults.
  • Per-profile admin actions. Apply Defaults and Remove Data can target one profile of an account.
  • Watch Party. Moonbase keeps the two things Silo's Watch Party rooms don't: the host's subtitle choice and members' avatars.
  • Storage. Silo gives a plugin 256 storage keys and can't delete one. Moonbase used a key per profile, so a server topped out at about 240 profiles. Settings and collection orders are now packed into 32 gzipped keys, which holds a few thousand profiles.

For full functionality this needs Moonfin-Core Silo-Support at e8f7610d9 or later (Moonfin-Client/Moonfin-Core PR "Silo Media Server support"):

  • Silo Home rows added by type (type:<section_type>) load in the app from Moonfin-Core 1c7761579 (fixed further in 7201d27a0). Older apps show them as empty rows.
  • The Watch Party subtitle and avatar routes are used by Moonfin-Core's Watch Party on Silo (03f1c2b53 and later). Older apps never call them.

Everything else works with any Moonfin version that already talks to Moonbase for Silo.

Related Issues

  • Related to the review of the earlier Moonbase for Silo PR (base silo)
  • Related to Moonfin-Client/Moonfin-Core "Silo Media Server support" (needs e8f7610d9 or later)

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Performance improvement
  • API / endpoint change
  • Settings schema change
  • Documentation update
  • Build/CI change
  • Other (describe):

Area

  • Settings sync / profiles
  • Admin defaults / config page
  • Ratings (MDBList / TMDB)
  • Notifications / Push (FCM / relay)
  • Seerr integration
  • Games / Emulators
  • Custom home rows
  • Web Client (Go to Moonfin-Core repo)
  • Other / shared (Moonbase in Go: Silo, and the shared admin page Plex uses)

Changes Made

  • Admin page (de8b41f).
    • web/configPage.html is copied from Jellyfin/backend/Pages/configPage.html unchanged, which also picks up the Custom Lists tab and the other settings added since.
    • Each entry in web/host.js's hosts table now has hidden (sections hidden but still saved) next to unsupported (sections with a note), plus a few switches: liveTv, ownRequests and serverRows.
    • On Silo:
      • Hidden: Connecting to this server, Seerr, Recommendations, Live TV, Web Plugin Status and Active Downloads. A tab left empty is hidden too.
      • Noted: IMDb charts, anime markers, games, the official MDBList lists and PINs.
      • Seerr is called Requests in the user defaults and the Home rows builder, the request defaults don't wait for the hidden Seerr switch, and "Seerr requests" is off the list of what relies on sync.
      • Live TV rows aren't offered as new rows, but saved layouts keep them.
      • The Home rows builder gets a Silo tab listing Silo's 29 row types (Recommended for you, Trending on this server, Seasonal picks…), saved by type as type:<section_type>, since Moonbase can't read a server's section ids.
    • Plex keeps its notes, Seerr and Live TV, and loses only the Studio Logos note, since ProductionCompanies sends logo URLs.
    • On both hosts:
      • The per-device defaults picker and uploaded themes in the App Theme default moved out of the page and into host.js. The page is handed one layer of DefaultUserSettings at a time. Picking a device keeps unsaved changes and reloads the page with them.
      • The Actions user list is a menu: an account picks its own profile, and hovering it (or tapping its arrow on a phone) offers its other profiles or all of them.
  • One profile at a time (b7ab64a, 53bb5db).
    • PushDefaults/{userId} and DELETE Admin/Users/{userId} also take <account id>~<profile>.
    • GET /Moonfin/Admin/Users adds a Profiles list per account, with each profile's target id and Primary.
    • The primary profile comes from Silo's X-Silo-Profile-Primary. Entries saved before this count the profile named after the account until it syncs again.
    • Hosts without profiles (Plex) list just the account.
  • Watch Party (9c32f3d, 4f5efac).
    • GET/PUT /Moonfin/WatchParty/{roomId} keeps the host's subtitle choice for a room. The first profile to set it owns it.
    • PUT /Moonfin/WatchParty/{roomId}/Avatar keeps each member's avatar link.
    • Rooms are kept 24 hours under one key. Room ids must be UUIDs.
    • A full store drops the rooms least recently used rather than refusing new ones, so made-up rooms can't lock real ones out.
  • Storage (250aeaf).
    • store.Packed keeps every profile's settings and collection orders in 32 gzipped keys (pk/<n>, chosen by a hash of the key). The index, config, messages, themes and Watch Party keep their own keys. Only Moonbase for Silo uses it; the Plex companion stores files and has no key limit.
    • A test fits 400 profiles with Moonfin-sized settings (about 11 KB each) in under a quarter of each key. In a one-off run, 2,000 profiles filled the fullest key to 74%, so the limit is about 2,500 profiles, and the profile index reaches its limit at about 2,900.
    • Settings saved before packing are read from their old key until the profile next saves, which moves them into a packed key and empties the old one.
    • Removing a profile's data now frees its room. Before, Silo couldn't delete the key, so the slot stayed used.
  • Review fixes (de8d04f).
    • A device's built-in theme clears an uploaded theme set for all devices.
    • The uploaded-theme list follows the Themes tab as it changes.
    • Removing an account's last profile takes the account off the list.
    • If the page's globals change in a later copy, the Home rows builder falls back to Jellyfin's rows and the rest of the page still loads.
  • Docs (7144563, and in 250aeaf). Go/README.md covers the collection order and Watch Party routes, what host.js hides and notes on Silo, the Silo rows tab, the user menu, and the new storage layout and limits.

Client Impact

  • No client changes needed
  • Companion client PR(s) required, linked here: Moonfin-Client/Moonfin-Core "Silo Media Server support", Silo-Support at e8f7610d9 or later (Silo Home rows by type, Watch Party subtitles and avatars)
  • New setting keys added. List each key and confirm it matches the client key exactly, including casing:

No new synced setting keys. DefaultDeviceSettings and customThemeId were already there.

Compatibility

  • Change to the settings profile is additive only, no renamed or removed properties
  • New properties use the same type the client sends (a client bool maps to bool?, an int to int?)
  • Migration added for any renamed or removed settings (stored settings move into packed keys as each profile next saves; nothing is renamed)
  • Older clients still work, unknown fields are ignored and no keys were removed

The index gains an optional primary field. The page still saves every setting it doesn't show, hidden sections included.

Testing

  • Built the plugin and deployed to a Silo server
  • Verified against a live client Windows on reqired moonfin-core build.
  • Manual testing completed
  • Not tested (explain why):

go vet and go test -race ./... in Go/ pass. New tests cover:

  • per-profile apply and remove
  • the users list with and without profiles
  • the primary flag from Silo's header
  • Watch Party room eviction and id checks
  • packing: capacity, moving old values over, and deletes

The admin page was loaded in a browser against a mocked Moonbase API as Silo and as Plex. On Silo:

  • the right sections and tabs are hidden, the notes show, and the Silo and Requests tabs appear
  • the device picker keeps an unsaved theme across a switch and saves only a device's differences
  • a saved Live TV row survives a save
  • picking a profile from the user menu sends PushDefaults/<account>~<profile>

On Plex, Seerr, Live TV and the notes are unchanged.

The Watch Party routes and the profile menu were tried on a Silo test server in earlier builds. This branch's build hasn't been yet.

Test Steps

  1. Build with make dist in Go/ and upload Moonbase.Silo-<version>-linux-amd64 on Silo's Plugins page.
  2. Open Moonbase from Plugin Apps:
    • Check the hidden sections and the notes.
    • Pick TV under User defaults, change a setting, switch back to All devices and Save.
    • Add a row from the Silo tab under Home rows.
  3. On Actions, hover an account with more than one profile, pick one, and Apply To Selected User. Only that profile's settings should change.
  4. With Moonfin-Core Silo-Support at e8f7610d9:
    • Sign in as a user and check the Silo row from step 2 shows on Home.
    • Start a Watch Party and check the host's subtitle choice and avatars reach the other member.

Screenshots (if applicable)

Checklist

  • Code builds successfully
  • Code follows project style and conventions
  • No unnecessary commented-out code
  • No new warnings introduced
  • Any new setting keys match the client-side keys exactly (none added)

Known limits, not changed here

  • Picking an account in the Actions menu means its own profile. "All profiles" is in the account's submenu. This is on purpose, so the account doesn't appear twice in the list.
  • Switching the device in User defaults reloads the page. Unsaved settings come back, but a half-filled message or custom list form doesn't. The page only fills its form when it loads, so the switch has to go through a reload.
  • Collection orders of removed profiles stay behind. Removing a profile's data doesn't clear its collection orders, which aren't in the index. Since packing they use space inside a packed key rather than a key of their own.

AI disclosure

Written with Claude Code (Anthropic's Claude), working from my direction and review. I've read the changes and tested them against my Silo test server, and can walk through any of it.

🤖 Generated with Claude Code

RadicalMuffinMan and others added 26 commits October 29, 2026 20:13
…role

Silo #3021 verifies the profile (and its PIN proof) on plugin routes and
forwards it as X-Silo-Profile-Id, and sends X-Silo-User-Role admin only for
the acting admin. Moonbase now:

- keys each profile's settings by account and profile id, so renaming a
  profile keeps them. Without an id the profile name stands in, as before.
- takes the admin role as Silo sends it instead of also requiring the
  primary-profile header.
- answers Silo/Whoami with the caller's libraries for every user, since
  ListLibraries now filters by account.

Also moves to silo-plugin-sdk v0.30.0, the version Silo pins, updates the
README's supported versions and known limits, and versions the plugin 1.0.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a user's data

- An app sends every setting it holds, so storing them all hid any later
  change to the defaults from that user. A value the profile doesn't hold
  yet and would resolve to anyway is no longer stored.
- When an admin changes a default (on the Moonbase page, or in Silo's own
  form, caught up on the first request after the restart), users still on
  the old default, or holding any value where there was no default
  before, move to the new one. Values picked against an earlier default
  stay.
- "Apply defaults" clears the users' own values so the defaults show
  through, and a full reset leaves an empty profile, so both keep
  following later changes.
- The page pings Moonbase each minute, so Silo renews its launch cookie
  and the page stays signed in past five minutes.
- DELETE /Moonfin/Admin/Users/{userId} and a page button remove every
  profile's settings for an account, for one deleted from Silo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Settings could be edited both on Silo's own plugin form and on the
Moonbase page. The form can't hold the layout editors, messages or user
actions, and each save restarted the plugin, so the page is now the only
place. Settings live in instance state.

Values an admin saved on the old form still count until the page is
first saved, which keeps them with the page's settings from then on.
The form generator, the schema and the write-back to Silo are gone; the
1.0.0 field table stays to read the old values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributions leave version numbers alone, so the manifest goes back to
0.6.2, and the old settings form is described as from earlier releases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Moonbase page's Themes section and the apps' theme sync now work on
Silo, with the routes the Jellyfin plugin's MoonfinThemesController has.
Uploads go through a Go port of MoonfinThemeValidator, so a theme either
plugin accepts loads the same in the apps.

All themes are stored gzipped together under one instance-state key, so
they take one of the plugin's 256 keys. Up to 5 are kept, which leaves
room in that key and keeps the list the apps download well inside Silo's
4 MiB plugin response. An upload that wouldn't fit even compressed is
refused with a message saying so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Tmdb/Reviews, Tmdb/ProductionCompanies and Tmdb/Details answer as the
Jellyfin plugin's do, so the apps' reviews, studio rows and details for
titles outside the library work on Silo. Details follows Tmdb/Details on
master.

Studio logos aren't served by the plugin: an app can't send Silo's headers
with an image URL. Each company carries a logoUrl on TMDB instead. Silo
doesn't share its metadata language, so details are in English, as the
thumb already is.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The App Theme default only listed the bundled themes. Themes uploaded on
the Themes tab now show under "Uploaded" in that list. Picking one saves
it as the customThemeId default, which the apps already sync and apply
over visualTheme once they have downloaded the theme. Picking a bundled
theme clears it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The User defaults tab gets a "Defaults for" picker: All devices, Desktop,
Mobile or TV. A device shows the all-devices values until one is changed,
and stores only what differs, in DefaultDeviceSettings. Resolve, saves
and Apply Defaults use a profile's own defaults over the shared ones, and
a changed device default moves that device's users the way a shared one
does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fin, with Apply Defaults reaching each device profile on its own defaults
…st, so another server can serve the same routes
…talog and the Moonbase for Plex image on ghcr.io
Silo's Watch Party rooms have no field for it, so Moonfin's host stores
the room's subtitle pick here (GET/PUT /Moonfin/WatchParty/{roomId}) and
members read it when the item starts. The first profile to set a room's
choice owns it; entries expire after a day and at most 200 rooms are
kept, all in one key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silo's room lists members without avatars, and one account can't read
another's profiles. Each member's app now posts its own avatar link for
the room (PUT /Moonfin/WatchParty/{roomId}/Avatar), and the room read
returns them by profile id. Preset links are public; an upload's is
signed for 15 minutes, so apps post it again before then. Links must be
http(s) or a path on the server, and a room keeps at most 50. Only the
subtitle write claims a room's owner now, so a member posting first
doesn't take it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The single-user list on Actions lists each account's profiles under it.
Picking the account still means all of its profiles; picking a profile
means just that one, for Apply To Selected User and for removing data.

The admin routes take "<account id>~<profile key>" for one profile, and
/Moonfin/Admin/Users lists each account's Profiles with those ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Moonbase keeps which profile is the account's own, from Silo's
X-Silo-Profile-Primary, and /Moonfin/Admin/Users marks it Primary.
Entries saved before that count the profile named after the account
until it syncs again. The admin page's user menu uses it to pick an
account's own profile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
web/configPage.html is the Jellyfin page again, unchanged, so updating
it stays a copy step. What only Moonbase for Silo and Plex need now
lives in web/host.js, per host in its hosts table:

- hidden: sections for things the host does itself or will never use,
  hidden rather than removed so their settings still load and save. On
  Silo that's hosting Moonfin web, Seerr, recommendations, Live TV, web
  plugin status and active downloads; a tab left empty is hidden too.
- unsupported: a note on sections the host can't run yet. Silo's say
  why (IMDb charts, anime markers, games, the official MDBList lists,
  PINs). The Studio Logos note is gone on both hosts, since
  ProductionCompanies sends logo URLs.
- On Silo, which takes requests itself, Seerr reads Requests in the user
  defaults and the Home rows builder, the request defaults don't wait
  for the hidden Seerr switch, and Seerr requests is off the list of
  what relies on sync. Silo's Home rows get a tab of their own, saved by
  section type as type:<section_type>. Live TV rows and the Live TV
  bottom tab are left out.
- On both hosts: the per-device defaults picker and uploaded themes in
  the App Theme default, moved out of the page (the page is handed one
  layer of DefaultUserSettings at a time), and a user menu on Actions
  that picks an account's own profile, one of its other profiles, or all
  of them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lists the collection order and Watch Party routes, describes what
host.js hides and notes on Silo, the Silo Home rows tab and the user
menu, and counts collection orders against Silo's 256 storage keys.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Silo gives a plugin 256 instance-state keys and can't delete one, and
Moonbase used a key per profile and per saved collection order, so a
server topped out at about 240 profiles. store.Packed keeps every
profile's settings and collection orders in 32 gzipped keys (pk/<n>,
by a hash of the key) and leaves the other keys as they were. A test
with Moonfin-sized settings fits 400 profiles in under a quarter of each
key, and 2,000 fit when measured, so the limit is now a few thousand
profiles.

Values saved before packing are read from their own key until written
again, which moves them into their bucket and empties the old key. A
delete frees its room in the bucket. Only Moonbase for Silo packs; the
Plex companion's files have no key limit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Watch Party: room ids must be UUIDs, and a full store drops the rooms
  least recently used instead of refusing new ones or going past the
  256 KiB key, so made-up rooms can't lock real ones out. Members are
  keyed by profile, or by account on a host without profiles.
- Admin users: an account with a stored own-profile flag isn't also
  matched by name, and a host without profiles (Plex) lists just the
  account rather than a blank profile.
- Admin page: Live TV rows stay in the page's catalog, so a saved layout
  keeps them, and are only left out of the rows on offer. A device's
  built-in theme clears an uploaded theme set for all devices. Uploaded
  themes follow the Themes list as it changes. Removing an account's
  last profile takes the account off the list. If the page's globals
  change, the Home rows builder keeps Jellyfin's rows and the rest of the
  page still loads.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added API Bug Something isn't working Config Page documentation Improvements or additions to documentation labels Oct 30, 2026
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Brady3035 Brady3035 closed this Oct 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

API Bug Something isn't working Config Page documentation Improvements or additions to documentation Feature Request New feature or request Home Rows Settings Sync

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants