Skip to content

Development

RadicalMuffinMan edited this page Sep 25, 2026 · 4 revisions

Development

Notes for working on the Moonfin Roku codebase. For build commands, see Building from Source.

Developer notes

  • Written in BrighterScript, which transpiles to BrightScript, with SceneGraph XML for the UI.
  • Built with the brighterscript compiler (bsc) through npm run build.
  • Linted with @rokucommunity/bslint and formatted according to bsfmt.json.
  • Target resolution is FHD, 1920x1080.
  • UI changes should be tested on real Roku hardware when possible. The emulator doesn't reproduce focus and animation behavior faithfully.

Project structure

├── source/           # Entry point, API clients, utilities, constants, enums
│   ├── Main.bs       # Channel entry point
│   ├── api/          # Jellyfin, Seerr, and plugin API calls
│   └── utils/        # Shared helpers
├── components/       # SceneGraph components, one .bs + .xml pair each
│   ├── home/         # Home screen, media bar, rows
│   ├── details/      # Movie, series, season, and playlist detail screens
│   ├── settings/     # Settings screens and the plugin sync surface
│   ├── seerr/        # Seerr discovery, browse, and request flows
│   ├── mediaPlayers/ # Video, audio, and photo playback
│   ├── liveTv/       # Guide, channels, and recordings
│   └── ...           # Login, search, genres, favorites, keyboards, tasks
├── locale/           # Translations, one folder per locale
├── images/           # Bundled artwork and icons
├── settings/         # Settings definitions
└── manifest          # Roku channel manifest

Things that bite

Four BrighterScript and SceneGraph behaviours that have each cost real debugging time in this codebase. Read them before your first change.

  • Namespace members shadow locals. Inside namespace foo, a bare identifier resolves to a namespace member before a local variable or parameter, case-insensitively. A parameter called name in a namespace that also has function Name() transpiles to the function pointer, compiles clean, and fails on the device with "Type Mismatch ... can't be applied to Function and String". Don't reuse a namespace function's name for a local.
  • Task threads don't see the main thread's globals. GetGlobalAA() is per thread. A value set in Main.bs is empty inside a SceneGraph Task, so the auth header there falls back to a device id of Roku. From a Task, never look up your own session by device id. Match on the playing item and the client name instead.
  • Where a shared mixin file can live. A .bs under source/ is validated in the source scope too, so it can't call component-only functions. A .bs under components/ joins only the scopes of the components that import it, which is what a shared un-namespaced mixin needs. Handlers named in XML (onChange=, observeField) resolve unqualified, and autoImportComponentScript flattens the imports at transpile time.
  • Verify what shipped, not what you saved. Editors and sync tools have been seen reverting a save between edit and build here. After changing a file, grep for the new line in the source, in build/staging/, and inside the zip, in the same command as the build.

Quick playback checks

You don't have to steer the remote to test playback. With the app running, curl -d '' "http://<Roku IP>:8060/input?mediaType=movie&contentId=<item id>" plays that item straight away through the app's deep link handler. http://<Roku IP>:8060/query/media-player then shows the player state, and the server's /Sessions for the client "Moonfin Roku" shows whether it direct played or transcoded, and why.

Translations

Translations are managed in Weblate at translate.moonfin.io, which owns the contents of locale/. Edit strings there rather than by hand so contributions aren't overwritten on the next sync.

Contributing

  • Check the existing issues before starting, in case your idea or bug is already tracked.
  • Open an issue first for anything significant, so the approach can be discussed before the work happens.
  • Match the existing code style, which bsfmt.json and bslint.json enforce.
  • Test on real Roku hardware.

To submit a change, fork the repo, create a feature branch, make your changes with clear commit messages, test on device, and open a pull request with a clear description.

Clone this wiki locally