README.md
# {{ widget:recently_added_media }} — Wizarr custom widget patch
This is real, currently-running code, pulled directly out of the live `wizarr` container's
`/app/app/services/` on 2026-08-28. It only exists in that container's writable layer, not in
`/data` and not in stock Wizarr — it will be lost the next time the Wizarr image gets updated
unless it's reapplied. Saving it here so it survives regardless.
## Files
- **`wizard_widgets.py`** — full file, 475 lines. The widget engine itself: `WIDGET_REGISTRY`,
the `{{ widget:... }}` template syntax parser, and `RecentlyAddedMediaWidget` (the class that
renders the actual carousel — pulls a pool of items via a media client's `get_random_items()`
or `get_recent_items()`, then builds the HTML). This is the piece that makes
`{{ widget:recently_added_media source=audiobookshelf limit=8 }}` work at all inside a wizard
step's markdown.
- **`audiobookshelf_get_random_items.py`** — one method, pulled from
`app/services/media/audiobookshelf.py`. Samples randomly across the *whole* Audiobookshelf
library (not just the "recently added" shelf) so the carousel doesn't end up dominated by
whatever was bulk-imported most recently, and tags each item with author/series so the widget
can diversify by author.
## What's genuinely custom vs. stock Wizarr
`wizard_widgets.py` and `get_random_items()` are the parts built specifically for this: neither
exists in a plain Wizarr install. The widget also calls each media client's `get_recent_items()`
(Jellyfin, Plex, Audiobookshelf all have one) for the non-diversified path — those methods
appear to be Wizarr's own pre-existing functionality, reused by this widget rather than written
for it. No clean diff against upstream was done to confirm that line-for-line, so treat that
distinction as a strong likelihood, not a verified fact.
## Where it lives live right now
Container `wizarr`, path `/app/app/services/wizard_widgets.py` and
`/app/app/services/media/audiobookshelf.py`. Bind-mounted config is at
`M:\Docker\config\wizarr\`, but this patch is NOT in that mount — it's only in the container's
own writable layer, which is why it needs to be reapplied by hand after any
`docker compose pull && docker compose up -d` on this service.