Skip to content

feat: plugins in the player, and analyzer fields in filters and sorts - #31

Merged
botandbang merged 16 commits into
bnb/plugin-editsfrom
bnb/plugin-tui
Oct 17, 2026
Merged

botandbang merged 16 commits into
bnb/plugin-editsfrom
bnb/plugin-tui

Conversation

@botandbang

Copy link
Copy Markdown
Contributor

What and why

The player hosts plugins through the same crate::plugins::host() the CLI and sidecar use. Everything is drawn from the manifests, with no code for any one plugin. Analyzers' fields also become something tracks --filter, tracks --sort and tracks.query can name, since the player's columns and search box needed that from core anyway.

Added, in the player (apps/locallibrary/src/tui/):

  • Commands, operations, analyzers and export formats in the palette (:) and at the end of a drive's e menu in the Drives view, labelled by title with the plugin's name beside them. Operations also end the actions menu (e, edits::Action::Plugin). An entry that can't run says why: no drive open, or a folder opened by path instead of a drive plugged in (offered, plugin_items). They dispatch through new Act::Plugin, Act::Analyze, Act::FieldColumn, Act::PluginSettings and Act::Export variants (keymap), none of them bound to a key.
  • A form for params, options and settings, built from a JSON Schema (Form, drawn by draw_form). Switches and short enums are picked with ←→ or space, lists take commas, and paths may start with ~. Values are parsed with the CLI's plugins::params and checked against the schema on the field at fault. A list of tracks starts as the marked or selected ones, and a command without params skips the form.
  • Commands run in the background on the open drive, with its playlist and tracks as the context; esc stops one. A View answer shows in the report panel, and a plain value as a message (show_answer). Tables keep their columns aligned using new Tone::Columns/Tone::Row lines, which are clipped instead of wrapped. plugin_cmd::view::table and pairs are pulled out so the CLI and the player draw tables and key-value lists the same way.
  • Operations are planned in the background and confirmed in the usual dialog, which now starts with the plugin's summary and lists the plan's own warnings. They are applied and undone (u) like any edit.
  • Analyzers run from the palette or a drive's menu over the tracks not yet analysed (analyze). "show the sound.bpm column" toggles a Col::Field column, placed before tempo, key and time (with_fields) and saved in tui.json (Saved.field_columns). Its values are read in the background (read_fields, Msg::Fields) the first time a column, filter or sort needs them. Clicking the header sorts by it, with tracks lacking a value always last, and the search box takes sound.bpm:>120.
  • Each plugin's settings open from the palette as a form filled with the current values (plugin_settings). enter saves only what changed, and an emptied field goes back to its default.
  • Export formats ("export for traktor…") open a form asking where to write, prefilled under ~/Music/ and named after the drive, plus the format's options (plugin_export). The marked playlists are exported when there are some, as with rekordbox XML. The plan is a dry run off the UI thread (plan_export), shown like export --dry-run (export_lines) and confirmed as "Write N files?", or "Overwrite N files?" when some exist already. export_cmd::FILES_SHOWN and about are shared with the CLI's listing.
  • Plugins' logs go to the messages (!) while the player owns the terminal, through plugins::log_to and Msg::Log, and back to stderr when it exits.

Added, in core, the CLI and the sidecar:

  • Filters can name a plugin's field as PLUGIN.FIELD (Cond::Plugin). A number matches to the precision typed (128 is 127.5 to 128.5), text matches whole words, and a boolean matches true/false. A field no installed analyzer declares, or a value its type can't hold, is refused. Sorts take -sound.bpm through parse_sort_with and sort_tracks_with. SortField::Field holds an interned FieldName so SortKey stays Copy.
  • fields::TrackValues holds a library's values by track and PLUGIN.FIELD, read from a fields::Source (store, analyzers, declared fields). Plugins::fields lists what the installed analyzers declare. api::tracks_query_with takes that source, and api::filter_context takes the values. locallibrary tracks and the sidecar's tracks.query pass the installed analyzers through plugins::Installed; collection passes none.

Changed:

  • Plugin exports create the folder their output sits in, as locallibrary's own exports do (exports.rs). A Touched path now records whether it existed at planning time, so Planned::check stops reporting a folder that wasn't there yet as gone. It reports one that has appeared since as stale.
  • Sorting a playlist on the drive is refused while the list is sorted by a plugin's column.
  • The host's module docs say the TUI hosts plugins now. The test plugin's exporter names the playlists it was given in a note, and unit tests serve plugins from the locallibrary binary cargo builds next to them, since the test harness can't serve one.
  • Docs: player.md (the palette, menus, form, analyzers, columns, settings, exports), plugins.md, editing.md (the filter table and sort orders), other-software.md (exporting from the player), and tracks --help / --sort's help. CHANGELOG's plugins entry covers the player and the new filters.

How it was tested

The player's screens are covered by render tests in tui/screens.rs (plugins, plugins_fields, plugins_settings, plugins_exports), using the echo plugin the tests register (app::testing::echo_plugin). tui/plugins/tests.rs covers the palette and menu entries, commands and their panels, operations planned, cancelled with esc, confirmed, applied and undone, analyzers filling columns, sorting and the search box, settings, and exports planned, confirmed, overwritten, refused onto a drive and limited to the marked playlists. tui/form/tests.rs covers the form, including hegel properties over its keys and numbers; tui/ui/tests.rs covers column placement and value formatting. In core, query/tests.rs (with a hegel property), fields/tests.rs and op/tests.rs cover filters, sorts, values and the missing-folder check. apps/locallibrary/tests/it/plugins.rs adds tracks_are_filtered_and_sorted_by_what_analyzers_worked_out and plugins_formats_make_the_folders_they_write_into.

Ran the full workspace checks locally on the branch: cargo fmt --all --check, cargo clippy --workspace --all-targets --locked -- -D warnings and cargo nextest run --workspace --locked.

Checklist

  • Tests cover the change
  • For the website: Storybook stories cover any new UI states
  • For locallibrary: apps/locallibrary/CHANGELOG.md has a line under Unreleased, if people using it will notice
  • The READMEs, docs/ and --help still say what the code does (apps/locallibrary/docs/rpc/ too, for a sidecar change)

…their params, answers in a panel, and their logs in the messages
…ns menu, planned in the background, confirmed with the plugin's summary and warnings, and undone like any edit
…ayer's search take sound.bpm:>120 and -sound.bpm, and the player runs analyzers from the palette and shows their fields as columns it remembers
…a form for their options, what they'd write shown first and overwriting asked about
@github-actions github-actions Bot added the locallibrary locallibrary and the loclib-* crates label Oct 16, 2026
@botandbang
botandbang requested a review from bnb as a code owner October 17, 2026 06:28
@botandbang botandbang added locallibrary: cli The locallibrary command line locallibrary: player The terminal player and its waveforms locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary: core The core crate, the data model and rekordbox's formats locallibrary: interop Libraries for Traktor, Serato and Engine DJ locallibrary: plugins The plugin protocol, its host and the first-party plugins documentation Improvements or additions to documentation tests Tests, fixtures and test harnesses labels Oct 17, 2026
Base automatically changed from bnb/plugin-integration to bnb/plugin-edits October 17, 2026 15:59
@botandbang
botandbang merged commit 8dd4b48 into bnb/plugin-edits Oct 17, 2026
35 checks passed
@botandbang
botandbang deleted the bnb/plugin-tui branch October 17, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation locallibrary: cli The locallibrary command line locallibrary: core The core crate, the data model and rekordbox's formats locallibrary: interop Libraries for Traktor, Serato and Engine DJ locallibrary: player The terminal player and its waveforms locallibrary: plugins The plugin protocol, its host and the first-party plugins locallibrary: sidecar The JSON-RPC sidecar and the api module it serves locallibrary locallibrary and the loclib-* crates tests Tests, fixtures and test harnesses

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant