metaedit

Architecture and status

What is implemented, what is deliberately not, and where the design decisions live. For the reasoning behind a decision, read the ADR it names rather than this page.

Status

Area State
Container, Postgres 18, migrations, health endpoints done
CI: lint, types, tests, SPA build, image smoke test done
Archive schema (raw + derived), byte accounting vs. the ToS cap done
SPA shell served by the API done
Jellyfin read: libraries, browse, batch item state, full write whitelist done
Last.fm client: typed models, shared rate limiter, backoff, archive-first reads done
Archive writes: append-only request log, content-addressed bodies, cap policy done
Harvest: fetch and archive for one item or a whole selection (SSE) done
Derivation into entity/tag/similarity tables, reindex done
Tag policy, mapping, confidence, diff done
Apply, snapshot, undo done
Bulk editing (SSE) done
Library browser, editor, bulk and archive-explorer UI done
Hardening, end-to-end tests done

The UI

React + Vite single-page app, served by the same container as the API. Five routes:

Route What it does
/ Library Browse artists, albums or songs; filter to items missing metadata; open the editor.
/edit/:itemId Editor Pick an archived candidate, review the diff field by field, write only what you tick, undo from the snapshot history.
/review Review The pre-write review of what a candidate would change.
/bulk Bulk Review a selection, apply the reviewed job, watch per-item progress, revert the batch as a unit.
/archive Archive Storage-cap headroom, the derived layer’s counts, stored entities with tags by popularity and similar artists.

Reaching these routes is the only thing the SPA fallback in main.py does besides serving built assets, which is why the containment check on it matters.

Layering

api  →  service  →  domain  ←  adapters

Dependency direction is enforced: adapters depend on the domain, never the reverse, and domain/ is pure logic with no I/O — writable fields, snapshots, tag policy, mapping, diff. See ADRs 0001 (a custom app calling the Jellyfin REST API, not a plugin) and 0013 (one container plus Postgres 18).

Known deviations from the original plan

Where the design lives

Attribution

Metadata originates from Last.fm. The UI links every Last.fm-derived value back to the corresponding artist, album or track page on https://www.last.fm, as the API Terms of Service require. This tool is for personal, non-commercial use.