Skip to content

Adding things

A new page

  1. Create frontend/js/views/thing.js exporting { search: null, async render(root, ctx) {…} }. Copy the shape of tracks.js.
  2. Add { path: '/library/:libraryId/thing', view: thing, title: 'Thing' } to views/routes.js.
  3. Add a link in nessie-sidebar.js (the link(path, label, icon) helper), and add top: true to the route so phones don't show Back on it.

A new server call

  1. Add a route in the right app/api/*_routes.py, with Pydantic models in app/schemas/. Add dependencies=[Admin] if only admins should use it.
  2. Put the queries in app/repositories/ and anything larger in app/services/. Commit in the route.
  3. Add one function to frontend/js/api.js. Views never build URLs themselves.
  4. Write a test in tests/. client and db_session fixtures are available, and a test's database work is rolled back afterwards.

A new component

Create components/nessie-thing.js extending NessieElement and call define('nessie-thing', …). Import it in main.js. Take data through properties and report through emit().

A chart

Settings → Stats (views/settings/stats.js) has the pattern to copy. columns() draws a column chart as plain HTML in the theme's --nessie-chart colour: one tab stop, arrow keys between columns, a tooltip on hover and focus, and "Show as a table". Every theme defines --nessie-chart in themes.css, each checked for 3:1 contrast against its background. Use it for chart marks, not --bs-primary, which is too faint in the light theme.

A new mix rule

Rules live in app/services/mix_rules.py. Add the field to Rules, then:

  • clear it in tidy();
  • add its words to describe();
  • add its condition to conditions();
  • add it to a GROUPS entry, so the preview can suggest dropping it.

In the editor, add a control and a SUMMARIES line to views/mix-editor.js. load() treats a rule that saved mixes don't have yet as no rule.

A new Settings tab

Create a module in views/settings/ with the same { render(root, ctx) } shape and add it to SECTIONS in settings/index.js. For a server setting that the UI may edit, add it to app/core/editable_settings.py. Settings → Server builds its form from that list.

A new permission

Copy can_request: a column on users (with a migration), a field on Person and a property on Identity that's true for admins, a group setting kept in step in _signed_in(), a require_… dependency in app/api/auth.py, the field on ProfileOut, MeOut, ProfileIn and ProfileUpdate, a switch in the profile dialogs, and a can…() helper in identity.js that falls back to is_admin for older servers.

Words on screen

Use the terms people know from other apps. Name settings by what they do ("Enable Lidarr", "Disable online metadata retrieval"), use the standard verbs (request, retrieve, download, transfer, refresh metadata), and name the other system involved ("Requested “Kid A” from Lidarr"). Help text says what happens in one or two short sentences. Error toasts say what failed ("Couldn't delete the playlist.") and then give the server's reason.

A database change

Edit the model in app/models/, run alembic revision --autogenerate -m "…", read the file it writes, then test it with alembic upgrade head.