<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Shortlist releases</title>
  <subtitle>New releases of Shortlist, private per-user recommendation rows for Plex.</subtitle>
  <link href="https://shortlistapp.dev/feed.xml" rel="self" type="application/atom+xml"/>
  <link href="https://shortlistapp.dev/" rel="alternate" type="text/html"/>
  <id>https://shortlistapp.dev/feed.xml</id>
  <updated>2026-10-05T00:00:00Z</updated>
  <author><name>Steven Adams</name></author>
  <entry>
    <title>Shortlist 1.10.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.10.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.10.0</id>
    <updated>2026-10-05T00:00:00Z</updated>
    <content type="text">### Added

- **An AI row: describe a row in plain words.** In **Add a row**, pick the AI filter and the **Describe a
  row** template, write what you want ("slow-burn heist films", "cosy mysteries for a rainy Sunday"),
  and the AI builds a theme once: a name, rules, TMDB tags and genres, and about 60 named titles with a
  one-line reason each. Shortlist checks every title against TMDB, your library and the row's limits.
  After that, every run fills the row for each person from that theme with no AI, ranked by their own
  taste. The editor has **Write the list**, **Adjust the list** (shows a diff, and works before the row is
  saved), **Try it** (a
  practice run for one person that writes nothing to Plex, and works on a switched-off row), **Advanced: how
  the AI is instructed** (your guidance, plus the locked mechanics), usage per row, and **Pause AI for this
  row**. A new AI row goes live like any other row. Per-person rows only, and library-only: a title your server lacks is
  never requested. With no AI provider you can still hand-edit the
  tags, genres and limits. See [An AI row](https://shortlistapp.dev/guides/ai/#an-ai-row). (#138)
- **Explore: an AI row that picks a new theme every few days (#138).** In an AI row's editor, choose
  **Pick a new theme every few days** and each person gets their own theme, chosen from what they watch (or
  from a brief you write), for 7 days by default. The next theme is written a day early and shown as **Up
  next**, where you can **Change it** or **Pick another**; the last six are listed as **Recent themes**. A
  new **Pick new row themes** job (setting `themes.rotate_cron`, daily by default) does the switching. One
  AI call per person per change. Every AI row starts on **Keep the same theme**, so nothing changes on
  upgrade.
- **An AI row only covers the kinds of title its AI named (#138).** A films-only list no longer fills your
  TV libraries with genre filler on a row that covers both. If a change or an Explore theme drops a kind,
  the row's existing collection in those libraries is removed on its next run.
- **An AI row's list is topped up once (#138).** When someone has watched most of the titles the AI named
  and their row has started filling with tag and genre matches, the nightly **Pick new row themes** job
  asks the AI once for about 40 more titles for that theme and adds them, keeping what the list already
  has. Never repeated for a theme (a night the AI doesn't answer doesn't count), and the theme card says
  when it happened.
- **Over-time controls on AI rows (#138).** **How much changes each time** (a fifth, a third by default,
  half, or almost everything), **Don't repeat a title for N days** (off by default), and **Keep out titles
  already in** other per-person rows. See [Explore](https://shortlistapp.dev/guides/ai/#explore-a-new-theme-every-few-days).
- **AI instructions: tell AI web search what to look for**, server-wide in Settings → Defaults → Title
  sources and per row under What goes in. A row can use the default, add to it, or write its own.
  See [AI instructions](https://shortlistapp.dev/guides/rows/what-goes-in/#ai-instructions).
- **A "Your requests" row.** Choose _Your requests_ as a row's kind and each person gets a private row of
  the titles they asked for in Overseerr (or Jellyseerr), or that carry their requester tag in Radarr or
  Sonarr, once the title is on Plex and until they have watched it. Newest arrival first, no AI, no
  padding; a person with nothing ready has no row. The Users page gains a **Requests** column saying
  whether each person is linked to an Overseerr account, and the row editor shows where requests are
  read from and whether Overseerr's Tag Requests is on. (#127)
- **Length, year and rating limits on a row.** Under What goes in, a row can leave out titles longer
  than a set number of minutes, released outside a range of years, or rated below a minimum. All are off
  by default, and a title TMDB has no data for (or that TMDB failed to answer for) is kept. They also apply to Watch it again rows and the popular-on-this-server fallback. See
  [Length, year and rating limits](https://shortlistapp.dev/guides/rows/what-goes-in/#length-year-and-rating-limits). (#138)

### Changed

- **A run reports "OK with warnings" when an account can see other people's rows.** A run used to read
  plain OK while a Plex restriction profile kept someone's sharing filter from hiding the rest; the run
  page and the dashboard now name those accounts, and a run that could not measure privacy says so.
- **Activity has a "Changes on Plex" tab** listing every write Shortlist made to your server, newest first.
- **The dashboard leads with last run, next run, privacy and the Plex connection**, and a run's page opens
  on its rows, each with what happened for every person.
- **Laptop-width and phone layouts are tidier.** The Users table switches to cards below 1280px instead of
  wrapping every cell, run logs read one line per entry on a phone, the uninstall preview groups repeated
  rows, and the website's reference tables stack into readable cards on a phone.
- **On-switches are neutral, and amber is kept for each screen's one main button.** Settings text is
  full size, and no section draws a box inside a box.
- **Picking a template is a page of its own.** The template picker shows each row kind with what it
  holds and who it is for, instead of a drop-down.
- **The web interface has one consistent layout** across every page, with navigation that works on a
  phone and a row editor whose sections you can jump to.
- **The Users page has a Status column** (Active, Paused, Off or Restricted) with search and filters,
  and shows how many different picks each person watched in the last 30 days.
- **Failed web searches say how long they took, and an unreadable AI reply is kept.** The log now names
  the attempt that failed and how long it ran before failing. When the AI's reply to a web search could
  not be read as a list of titles, the whole reply is saved in that person's run details so it can be
  diagnosed.

### Fixed

- **A dry run, or a run cancelled before someone's turn, no longer empties their user page.** The page
  showed "No picks in this row yet" for everyone after one cancelled test run, while Plex still held
  their rows; it now shows the last titles actually built.
- **The Users page's Requests column, the Requests page, and the nightly run's request read are faster.** Radarr, Sonarr and
  Overseerr are now read at the same time instead of one after another; on a large library that took
  the wait from 12–25 seconds to about 8.
- **A Radarr, Sonarr or Overseerr that doesn't answer is explained in plain words** ("Radarr didn't answer
  at http://host:7878. Check the address and that it's running.") instead of a Python error name.
- **A cancelled run says it was cancelled**, instead of "nothing was due" or that it predates the run page.
- **The Users page's last column says "Last run"**, not "Last built": it counts dry and cancelled runs too.
- **A row run that starts late still runs.** It used to be skipped if it started more than 30 seconds
  late. Any other scheduled job that is skipped for starting late now shows a bell alert, "A scheduled
  job didn't run", and is recorded in the change log.
- **Stopping or updating the container while Shortlist is open in a browser now shuts down cleanly.**
  It used to hang until Docker force-killed it, which also left in-progress plays marked as timed out.
- **Everything a background job changes on Plex is now recorded.** Share-filter changes, rows removed
  and rows taken off Home by the privacy sync, restore, row-visibility and sync check jobs used to
  leave no event, and so did the orphaned rows the sync check deletes. Runs now also record the rows
  they take off Home and the orphans they delete.
- **"Your requests": a tag that could belong to two different people now goes to nobody.** It used to go
  to whichever person matched first. A person matched by the tags of two rows now gets the title in
  both.
- **"Your requests": an unreadable reply from Radarr, Sonarr or Overseerr no longer removes someone's
  row.** It is treated as a failed read, so the row is kept.
- **An upgrade no longer goes ahead when its safety backup could not be written.** Shortlist stops with
  a message saying why, and the database is left unchanged. Free some disk space or fix the config
  folder's permissions, then restart.
- **A half-written backup is never offered as a restore point.** A backup stopped part-way through, for
  example by a container stop, is now discarded instead of listed.
- **A job that succeeded on a retry no longer shows its earlier error.**
- **A Plex server that can't be reached is explained in plain words** instead of showing the Python
  error. (#139)
- **The run log no longer says "existing rows are left as they are" for a person whose rows that run
  removed.** It says how many rows were removed.

### Security

- **Updated cryptography to 50.0.2 and urllib3 to 2.8.0.** The build-tool dependency brace-expansion is
  also updated.</content>
  </entry>
  <entry>
    <title>Shortlist 1.9.3</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.9.3" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.9.3</id>
    <updated>2026-09-27T00:00:00Z</updated>
    <content type="text">### Changed

- **The row editor asks what kind of row you are making, and shows only that kind's settings.** The
  kinds are Picked for You, Because you watched, Watch it again, Seasonal and Popular on this server.
  Switching kind asks first and says what will change on Plex; switching back restores the row as it
  was. "Because you watched" rows pick how many watches they are "Based on", and "Take turns" is
  always shown. Request settings are switches, show only what can take effect, and warn when one
  person could never meet the minimum demand. Templates are grouped by kind, and "Happy to see again"
  is now "Watch it again".

### Fixed

- **A new shared row for only some people is hidden from everyone else as soon as it is written.** It
  used to be listed in other people's library Collections tab until the run finished. Rows for
  everyone are unaffected; the same applies to people you have disabled.
- **Switching a row from "each person" to "shared" no longer leaves someone's old copy behind.** A copy
  the save failed to remove used to stay on that person's Home for good. Each night's run now removes
  it.
- **Rename… renames a "Because you watched X" row.** It used to report "renamed 0". Renaming such a row
  to a plain name now happens straight away; giving a row a "Because you watched X" name says it takes
  effect at the row's next run, which is when it can be filled in.
- **Changing one row's days or seasons updates only that row on Plex.** It used to re-apply every row
  on the server.
- **Deleting, disabling or narrowing a shared row clears Shortlist's record of its old collection.** The
  kept record could credit plays to a collection that no longer existed.
- **The row editor fits a phone, uses the full page width, and its summary scrolls with the page.** The
  summary names the row a row sits beside instead of its internal id, and saving Settings refreshes
  the row list, so an editor opened afterwards no longer writes the old default row name back.
- **"Because you watched X" no longer stays on an old watch (#133).** When a row's best pick came
  from a source that follows no watch (discover or web search), the row could miss that the person
  had watched something newer. It then kept the old title, and most of the old picks, night after
  night, and restarting or running the row by hand did not help. It now switches to the new watch on
  the next run. Rows whose watch had not changed but had such a pick at the top were also rebuilt
  from scratch every night; they now keep most of their picks as intended.
- **A "Because you watched X" row no longer freezes on Plex (#133).** When someone's newest watch had
  no look-alikes in your library, every pick in the new row came from discover or web search, which
  follow no single watch, so the row had nothing to be named after. Shortlist then left the old row
  on Plex, old title and old titles in it, until a later watch happened to have look-alikes, while
  the run page showed picks that never arrived. The row is now named after the watch it was built
  from and updated as normal. If you set a fallback name on such a row, people with watch history
  now get "Because you watched X" in its place; the fallback is for people with too little history.
- **A Movies and TV "Because you watched X" row names each library after its own watch.** The TV row
  used to borrow the film's name whenever none of its picks came from look-alikes of the show, so it
  only changed when the person watched a film.</content>
  </entry>
  <entry>
    <title>Shortlist 1.9.2</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.9.2" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.9.2</id>
    <updated>2026-09-24T00:00:00Z</updated>
    <content type="text">### Added

- **A run's log shows its own warnings.** Warnings and errors raised while a run works used to reach
  only the container log. They now appear in that run's log on the Runs page, in amber, and the log
  refreshes once the run finishes so its last lines are not missed.

- **The run summary counts people and shared rows separately.** "47 ok" was 46 people and one shared
  row.

- **A night that only queued titles for your approval now leaves a record.** The `run.requests` event
  is written whenever the run queued or sent anything, with how many of each. A night that only
  queued titles used to leave no trace.

- **Watched titles Shortlist cannot match are reported once, with a count.** Titles Plex holds without
  a TMDB id cannot be used and are skipped. The warning fired once per person; it now fires once per
  library per run, saying how many people it affects.

- **Every failed attempt of a retried job is kept.** A retried job kept only its latest error, so the
  first failure was lost. Each failed attempt is now recorded as a `job.attempt_failed` event, with
  the same error text a failed job shows.

### Fixed

- **"database is locked" during the watch sync.** Reading someone's watch history kept the database
  locked for writing while Plex answered for their next library — 12 to 18 seconds for a large TV
  library — so other work waiting to write gave up. It showed as jobs failing to start during the
  nightly history fill, and as lost playback updates. Each library is now saved before the next one
  is read.

- **A shared row showing twice on Home is cleaned up.** Renaming a shared row before 1.9.1 could leave
  a second copy of it, and everyone saw both. The next run deletes the extra copy and records the
  delete in the audit log.

- **A row whose titles all left Plex before it was built now says which row.** If every title picked
  for a library was deleted from Plex before the row was written, the error named nobody. It now
  names the person, the row and the library. The row is still skipped that night and rebuilt on the
  next run.

- **Seasonal picks no longer claim "in genres you watch".** Seasonal titles are chosen for the season,
  not for someone's genres, so that reason could be untrue. It now reads "Right for the season".

- **The Jobs page no longer says a hidden seasonal row comes straight back.** That holds for a row on
  its day off; between seasons, the row is rebuilt the night before its next season opens.</content>
  </entry>
  <entry>
    <title>Shortlist 1.9.1</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.9.1" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.9.1</id>
    <updated>2026-09-16T00:00:00Z</updated>
    <content type="text">### Added

- **Seasonal rows that follow the calendar.** A row can follow seasons — Valentine's Day, Halloween,
  Christmas. In season it holds that season's films from your library, ranked for each person; between
  seasons it is hidden but kept, and the night before its next season it is rebuilt for that season
  while still hidden, so it appears on the day already filled. Pick the
  seasons on the row, and how many days before and after each one it shows (30 before and none after,
  to begin with). Start from the new **Seasonal** template. The row's name can use `{season}` and
  `{season_emoji}`, and follows the season it is in (#124).

- **Choose what the webhook tells you, and give it a key.** The webhook sent one thing: a run
  failing. In Settings → Notifications you now tick what to send — runs starting, finishing, partly
  failing, failing or stopping; jobs starting, finishing or failing; someone able to see a row that
  isn't theirs; titles waiting for your approval; and a new version being out. A failed run and a
  privacy problem are ticked to begin with, so a webhook you already set up only gains the privacy
  alert. Jobs that run every few minutes, like the privacy sync, only speak up when something changed
  or went wrong, and no message ever names anybody.

  The webhook's address has moved to a **Webhook** card in Settings → Connections, beside every other
  service, with Send a test and Remove. Once an address is saved, the card says whether anything is
  being sent yet and links to Notifications, where you turn it on and choose what it sends. If your
  receiver needs a key (ntfy, Gotify, n8n), add a header name and value on the same card. It is sent
  with every message and stored encrypted; clear the name to stop sending it.

### Changed

- **The API refuses a row setting it does not know.** A misspelt field sent to create or edit a row
  (`POST`/`PATCH /api/collections`) used to be ignored and the row saved as if nothing were wrong. It is
  now answered with an error naming the field. The Shortlist app itself is unaffected; this only
  matters if you script against the API.

### Fixed

- **Renamed rows stay where you put them, and "Something else is reordering your shelf" stops
  blaming other tools for it.** When a row changed its name overnight — a "Because you watched…" row
  picking a new title — Plex kept showing Shortlist the old name in its shelf list, so Shortlist
  stopped recognising the row as its own and could leave it out of place. Most nights the whole
  shelf was also rebuilt and every row was counted as
  "put back", which set off the alert after three runs in a day even with Agregarr told to leave
  Shortlist's rows alone. Rows are now recognised by Plex's id for them rather than by name, and the
  alert only counts rows that were actually out of place.

- **A new row keeps the request settings you gave it.** The row editor lets you set a row's own
  request floors and Radarr/Sonarr target before you first save it, but those were dropped on create
  and only stuck once you edited the row again.

- **"Avoid these genres" holds at the front of a row.** When a row had more candidates than it could
  keep, the kept titles were re-sorted without the genre, franchise and cast dials, so an avoided genre
  could still lead the row.

- **Renaming a shared row no longer builds a second copy of it.** The rename dropped the row's
  invisible marker, so the next run did not recognise the renamed row and built it again.

- **The row editor stops asking which watch a shared row follows.** A shared row follows nobody's
  watches, but a leftover setting from before it was shared still brought the question back.

- **The run's AI token tile stops repeating its total.** Web search is the only AI step now, so the
  "breakdown" was just the total again; it shows only when more than one step used tokens.

- **Un-pausing someone on a row's day off no longer puts that row back on their Home.** A row named
  after a title they watched could reappear for the rest of the day when Shortlist couldn't tell which
  row it was, even though its schedule said to hide it that day. It is now left hidden, as the nightly
  run already did.</content>
  </entry>
  <entry>
    <title>Shortlist 1.9.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.9.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.9.0</id>
    <updated>2026-09-14T00:00:00Z</updated>
    <content type="text">### Added

- **See what changed after you update.** Shortlist told you when an update was out, but once you
  had installed it, nothing said what was in it. Now the first page you open after an update shows
  the release notes for every version since you last read them. If you skipped a few, you see all of
  them. Close it and it stays closed, in every browser, until the next release. A new install
  doesn't show it.

- **Star on GitHub, or buy me a coffee.** The sidebar's "Support this project" link opened GitHub
  Sponsors, which only works if you have a GitHub account and a card to hand. It is now two links: a
  star, which is free and is how other Plex owners find Shortlist, and a Ko-fi coffee, which takes a
  guest checkout with card, Apple Pay, Google Pay or PayPal. GitHub Sponsors is still on the repo.

- **A row can now take days off.** Rows were permanent fixtures: once built, one sat on everybody's
  Home until you switched it off or deleted it. Open a row and set **When it appears → Only on these
  days**, and it shows on the days you pick and is hidden the rest of the time.

  Two rows can cover a week between them — one on Monday, Wednesday and Friday, another filling the
  rest — so the Home screen is not the same every night. On its off days a row is hidden, not
  deleted: it keeps its titles and comes straight back on its next day without being rebuilt, so this
  costs a few milliseconds rather than the minutes a rebuild takes on a large TV library.

  Days turn over at midnight, on the server's clock. Changing them takes effect immediately rather
  than waiting for the night. The Rows page shows **Showing today** or **Hidden today** against any
  row that has a schedule, because a row that is simply missing is indistinguishable from a broken
  one — and some Plex apps only notice the change once you leave the Home screen and come back. A
  Roku re-reads it on its own; a Shield needs the nudge. (#102)

- **Requests can go through Overseerr or Jellyseerr instead of straight to Radarr and Sonarr.** If
  you already run Overseerr, Jellyseerr or Seerr, add it in Settings → **Connections**, then set
  **Where requests go** to it under **Requests**. Overseerr then fetches each title with its own
  quality profiles and folders. Pick a **Request as** account and that account's own approval
  settings apply: choose one with auto-approve off, and Shortlist's requests wait in Overseerr for
  your yes. The Requests settings now say in one sentence what will happen to a title, instead of
  leaving you to work it out from three separate settings. Radarr and Sonarr stay the default, so
  nothing changes until you switch. (#110)

- **Someone with an "allow only" restriction now sees their own row.** Plex's allow list shows only
  the labels or ratings it names, and a Shortlist row carries neither, so a person restricted that
  way could not see their own row at all. Shortlist now adds that person's own row label to their
  allow list, and nothing else: everything the list hid stays hidden, inside the row too. Their row
  is also built only from titles their restrictions let them see, so it isn't half-empty. (#115)

- **A row can set its Plex description and sort title.** Two optional fields in the row editor. A
  **Description** is what Plex shows when someone opens the collection, and takes the same
  placeholders as the row's name. A **Sort title prefix** orders the row in the library's
  Collections tab (not on Home), and keeps working when the row is renamed. Leave either empty and
  Shortlist never touches that field, so a summary or sort title set in agregarr stays. (#120)

- **A row can wait while its person isn't watching.** A row rebuilt for someone who has watched
  nothing since it was last built just swaps in more titles from the same taste, and on a large TV
  library every rebuild is a slow Plex write. Set a hold in Settings, or per row with **Hold when
  they aren't watching**, and the row waits until they watch something — but only for the number
  of days you set, because the row nobody watches is the one that most needs to look different.
  Off by default. (#109)

- **Get told when a run fails.** Shortlist runs overnight, and a failed run was only visible to
  someone who opened the app. Turn on **Send failures to a webhook** in Settings and give it an
  address, and a failed run posts a message there: a Discord or Slack channel, Home Assistant, n8n,
  or anything else that accepts a webhook. A button sends a test first. The address is stored
  encrypted, because a webhook address works as a password.

- **Serve Shortlist from a subpath.** Set `APP_BASE_PATH` (for example `/shortlist`) to run it
  behind a reverse proxy at `https://your-host/shortlist`, with no prefix-stripping in the proxy.
  Thanks to @jaredledvina. (#105)

- **Posters where you look at titles.** Pick lists, each person's **Watched** tab and the Dashboard
  now show posters, read from your own Plex server, so they are the artwork your library actually
  has. The Dashboard's **Most watched** is a poster shelf, **Recently watched** groups titles under
  the day they were watched, and titles carry TMDB, IMDb and Trakt links.

- **A Sharing and privacy page.** It checks every account's Plex sharing live from plex.tv, rather
  than trusting what Shortlist last wrote, and names any account that can see a row it shouldn't.
  Open it from **Users**.

- **Web search with Exa no longer needs an AI provider.** Exa returns the titles its pages
  recommend, so it can now feed recommendations on its own. You can choose how hard it searches
  (Instant, Auto, Deep lite — the default — or Deep), and the hint beside each says how it
  performed when measured. Searches are cached for 7 days instead of 14, so a new release or a new
  season shows up sooner.

### Changed

- **The privacy sync runs every 30 minutes by default, instead of once a night.** It is what hides
  everyone's rows from a Plex account you have just shared your server with. Run once a night, that
  account could browse every person's rows in the library's Collections tab for up to a day. A scheduled
  pass that finds nothing to change takes about 20 seconds, writes nothing, and is no longer listed under
  **Recent**; one that fails, changes something, raises a new warning, or that you caused by changing a
  setting still is. A failure is no longer reported once a later pass succeeds, and passes no longer pile up behind a
  long run. If you set your own schedule for it, yours is kept. (#119)

- **The Dashboard no longer shows a row of "needs attention" chips.** Each chip repeated an alert
  the notification bell already lists, so the bell is now the one place to look.

- **The Dashboard's watch rate counts what people watched, not what they were shown.** The old
  figure divided watched picks by every title a row had ever shown, most of which nobody will ever
  watch, so it sat under 1% whether Shortlist was working or not. It now shows, of the titles people
  watched, the share a row of theirs was showing at the time: on the maintainer's server, 99 of 535
  over 30 days, where the old figure read 71 of 10,898. It says when it is too early to judge
  instead of reporting an all-clear.

- **A "watch it again" row is built from what people have finished.** It used to draw from the same
  similar-titles pool as every other row, so someone with hundreds of finished films could get a row
  of two, topped up with things they had never seen. It now leads with their favourites (4 stars or
  more, when Plex ratings are on), then titles close to what they watch now, then whatever they have
  gone longest without seeing. Anything finished in the last 30 days stays out; change that with
  **Skip titles finished in the last**. (#114)

- **The row editor is grouped around what people see.** Six groups instead of eight, in the order
  you'd ask the questions: how it looks on Plex, who gets it, what goes in it, when it updates,
  where people see it, and requests. The first puts the name, description and poster together,
  beside an **On Plex** card showing them filled in for a sample person.

- **The Requests inbox has tabs and one toolbar.** Waiting, Sent and Rejected are tabs. One toolbar
  holds the movie/TV split, a search that finds a title by name or by who wanted it, filters for
  rating, votes and language, and sort. Anything narrowing the list shows as a chip you can remove.

- **The run page says what a live run is doing,** person by person, and "waiting for Plex" while it
  waits its turn to write. AI tokens are shown as input and output, because output costs several
  times more. Someone whose rows came out the same as last night now gets a line saying why.

- **The Watched tab lists each title once.** A film held in two libraries was two lines, each with
  a Block button that blocked both. It is now one line, with its libraries as tags beside the title
  and a library filter when there is more than one to choose from. (#111)

- **Two rows can share a name when they build in different libraries.** A Movies-only row and a
  TV-only row can now both be called "{library_name} Picked For You". (#121)

- **The documentation moved to [shortlistapp.dev](https://shortlistapp.dev).**

### Fixed

- **Restrictions you set on someone in Plex were being switched off.** If you had set a restriction
  on someone — excluding a content rating like R, or an allow-list of ratings or labels — Shortlist
  added its hide rule to it joined with `|`. Plex reads that as "either/or", not "both", so your
  restriction quietly stopped applying. With an exclude rule, that person could also see every other
  person's row; with an allow-list, they could see the whole library. Plex stored the rule exactly as
  written, so the Sharing page and the check before promotion both reported the account as hidden.
  Measured on a real server before fixing it.

  Shortlist now joins its rule with `&amp;`, which Plex applies together with yours. The first privacy
  pass after you update repairs every account an earlier version wrote, and a notice names the
  accounts it fixed. Those people may notice less on the server than before: that is your original
  restriction working again. Someone with an allow-list still sees their own row: see the entry on
  "allow only" restrictions above.

  One case Shortlist can't fix for you: a label with a literal `&amp;` in its name ("Kids &amp; Family") in
  someone's restrictions. Plex returns an error for their Home screen with a restriction like that,
  so Shortlist can't add its rule to it. You'll get a notice naming the person and the label to
  rename; the Sharing page says the same, and everyone else's rows carry on as normal. (#116, #115)

- **Schedules with a day of the week now run on that day, not the day after.** `0 4 * * 1,4` is
  Mondays and Thursdays, but it ran on Tuesdays and Fridays: the scheduler library counts weekdays
  from Monday where cron counts from Sunday. The same slip moved every **Weekly** row from Sunday
  to Monday, and "mondays at 9pm" to Tuesday. Saved schedules are unchanged and simply run on the
  day they always said. If you shifted a day number to work around this, shift it back. `7` is now
  accepted as Sunday too, and a list of days now gets a description ("Every Monday and Thursday at
  4:00 AM") instead of none. A schedule that sets both a day of the month and a day of the week now
  runs when either one matches, as cron does: `0 4 1 * 1` used to run only on a 1st that fell on a
  Monday. (#123)

- **A person's first row is hidden from everyone else as soon as it is built, not at the end of the run.**
  Until Shortlist has written the exclusion onto every other account's share, a brand-new row could be
  seen in the library's Collections tab by the other people on your server: the row's name, which can
  say what the person watched, and the titles in it. That lasted until the privacy step at the end of
  the run, which on a large library could be an hour or more. Now someone's first row is hidden from
  every other account as soon as it is written, before any other row is. If plex.tv is failing, the
  privacy step at the end of the run still hides it, as before. People who already had rows were not
  affected. A first run, where everyone is new, makes more share-filter writes as a result — one round
  per person rather than one at the end — and the rows pause while each round is written. (#119)

- **A row keeps its Plex collection when its titles change, so agregarr settings stick.**
  A row that lost five or more titles in a run used to be deleted and created again, because that
  was quicker on a large TV library. The new collection had a new Plex id, and tools that remember a
  collection by its id lost what you had set on it: agregarr's custom summary and sort title were
  wiped every time the row refreshed. Shortlist now always updates the existing collection in place.

  The trade is time. Plex removes titles from a collection one at a time, and on a very large TV
  library each removal is slow, so a nightly run there can take a lot longer than before. Nothing
  changes for a row whose titles did not change. While a row is being written, the run page now says
  what it is doing: which library, and how many titles it is adding and removing, or that it is
  creating a new row. (#119)

- **"Something else is reordering your shelf" no longer fires because two people watched the same
  film.** A "Because you watched X" row is named after the film, so everyone who watched that film
  gets a row with the *same* name. The check that spots another tool fighting Shortlist for the shelf
  was counting those as one row being moved over and over — so two people sharing a film was enough
  to set it off, and the alert then pointed at Kometa or Agregarr, which had done nothing. It now
  counts each row once per pass, which is what it always meant to do. A shelf genuinely being fought
  over still raises it. (#106)

- **"Something else is reordering your shelf" no longer fires when nothing else is.** A row rebuilt
  during a run got a new Plex id, and the shelf-ordering pass at the end of that same run was still
  working from the delivery record read before the run started. So it did not recognise the row as
  one you had placed by hand: it swept it to the library default instead, and the next pass — the
  privacy sync minutes later, or the next run — put it back where you asked. Every rebuilt row moved
  twice per cycle, for ever, and moving the same row over and over is exactly what Shortlist treats
  as proof that another tool (Kometa, Agregarr) is fighting it for the shelf.

  Rows now land in their configured slot in the run that rebuilds them. On the maintainer's server
  one run moved 70 rows and the pass twenty seconds later moved the same 70 straight back; that is
  now zero. If you still see the alert after this, something really is reordering your shelf.
  (#106)

- **Rows land where you put them on the Recommended shelf again.** Plex stores each row's shelf
  position as a number and places a row by halving the gap between its neighbours. After about fifty
  moves there is no gap left, and Plex then accepts every move and applies none — in every Plex app,
  its own web app included. One library on the maintainer's server had been stuck like that for over
  a week. Shortlist now arranges the shelf by sending each row to the end in turn, the one move Plex
  always has room for, which also repairs a library that is already stuck. Other tools' rows move
  only as far as fitting Shortlist's among them requires, and keep their order relative to each
  other. Placing a row "before" another row, next to a collection that isn't on the shelf, or in a
  loop of rows that point at each other now settles too. (#106)

- **A series marked watched by hand is picked up the same night.** Marking a TV series watched in
  Plex could leave it out of Shortlist for up to a week, because the sync read history in pieces and
  a show's own date can be older than its episodes'. Every sync now reads each library in full. A
  series marked watched is dated from its episodes, not left at the last time it was played, and that
  date decides how strongly it steers recommendations, so a show you finish today counts as today.
  Unmarking a title takes back its pick's credit that night, not a week later. (#108)

- **Renaming a row onto a name Plex refuses now works.** Plex keeps a collection's name reserved
  after the collection is deleted and refuses to rename anything onto it, so a row named after its
  top title could keep the old title's name over the new title's picks while the run page showed the
  new name. Shortlist now frees a name a deleted collection left behind. Renaming from the row editor
  lists each person as renamed, waiting for the next run, or refused with the reason, instead of
  stopping at the first refusal.

- **Restoring a backup actually restores it.** The restore copied the backup in while the app still
  had the database open, and the restart you were told to do then wrote the old data back over it.
  A restore now waits and is applied at the next start, before anything opens the database.
  **Jobs → Backups** shows a waiting restore with a Cancel button, and one still waiting after a day
  is dropped.

- **A nightly run cut short by a restart finishes for the people it never reached.** When a container
  update restarted Shortlist part-way through a run, everyone after that point went without new rows
  — the same people each time. The next start now runs for them, listed on Runs as **Resumed after a
  restart**. Only a scheduled run from the last 20 hours is resumed, and never twice.

- **Losing `secret.key` no longer destroys your saved credentials.** If `/config/secret.key` went
  missing or was regenerated, the next start re-encrypted every saved Plex token and API key with the
  new key, overwriting the only copy the old key could read, and logged it as a success. They are now
  left untouched, so putting the original key back still works.

- **A failed Support check could write your Plex token to the log file.** The checks on the Support
  page removed tokens from what they showed you but not from the log line they wrote, and the log file
  always records at full detail, so a failed check put a live Plex token on disk and into any support
  bundle made after it. If you have posted a support bundle publicly, remove it or check it for
  `X-Plex-Token` first.

- **On a server with a single Shortlist row, one bad read from Plex could delete it.** Before deleting a
  row it can't match to anyone, Shortlist cross-checks the read against every other row — which was
  skipped when there was only one row on the server, the case with nothing to check against. Every
  such delete is now confirmed with a second read first.

- **A row Plex had stopped accepting titles for is rebuilt.** Plex occasionally breaks a collection
  so that it refuses every title added to it, and that person's row then stayed empty night after
  night. Shortlist now deletes and recreates a row in that state.

- **Auto-send no longer spends its nightly slots on titles that can never be added.** A show with no
  TheTVDB id, or a title whose Radarr or Sonarr isn't fully set up, took a send slot, failed, and took
  it again the next night: on one server, 25 of 40 slots in a week. Those titles now wait in the
  Requests inbox with the reason.

- **Going back in the setup wizard no longer resets your row name and size.** The customize step
  forgot what you had saved, and the next save — including **Skip for now** — wrote the defaults over
  it.

- **AI token counts are right after a failed call.** A failed call counted the previous call's tokens
  again, usually another person's, which inflated the run's total.

- **Web search rides out a slow or busy Exa.** A timeout or a server error from Exa lost that search
  outright, and on a busy run Exa also starts turning requests away; both now retry with a pause. A
  dead MDBList no longer uses up the whole lookup budget before giving up.</content>
  </entry>
  <entry>
    <title>Shortlist 1.8.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.8.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.8.0</id>
    <updated>2026-08-26T00:00:00Z</updated>
    <content type="text">### Added

- **Shortlist can now tell you whether its picks actually get watched.** It only ever read one
  signal — Plex's binary watched flag — which cannot answer either question that matters. *When* did
  they watch it, since a rewatch overwrites the original date? And what about the film someone is
  two nights into, which the flag calls unwatched right up until the credits?

  Shortlist now watches playback itself. A pick is credited at the moment someone presses play, a
  part-watch is captured when playback stops rather than waiting for the next sync, and picks
  delivered through shared rows count too — previously they credited nothing at all, because a
  shared row writes no per-person pick record to match a play against. If tracking stops working,
  the dashboard says so rather than quietly reporting zero.

  A watch still in progress is no longer called an abandonment. "Gave up part-way" is the harshest
  thing this app says about a pick, and the rule behind it was invisible — so it now explains itself
  where the claim is made, instead of leaving you to conclude the tracking is broken.

- **The dashboard leads with whether your setup is working.** It had grown to six stat tiles and
  eleven sections, with the one number that actually judges a setup — the share of delivered picks
  that got watched — nowhere near the top. It now opens with whether it is working and what isn't,
  and every person and row links through to its own page.

- **Requests can be tagged with the person they were picked for.** Radarr and Sonarr have no field
  for "who wanted this", only tags, so the Requests inbox's why-line never reached them. Turn it on
  and each request carries the wanting person's name as a second tag, so you can tell in Radarr and
  Sonarr who a title was added for. Off by default, and any row can opt in or out on its own.

- **Settings changes now record who made them.** The audit log said what changed and when, but
  never who — so a value that moved with nobody owning up to it was simply unanswerable. That
  happened on a live server: a request threshold shifted by 0.2 between two runs and no record could
  say what did it. Each `settings.change` event now carries the actor (a browser session or an API
  token, plus a short client hint). Deliberately not the client IP: these rows are immutable and the
  support bundle exports them, and a LAN address is the kind of environment detail that should not
  end up in something people paste into public issues.

- **Prefer a language when requesting.** The request pool is by definition what your library
  _lacks_, so on a library that already holds the popular English titles what's left missing skews
  non-English before any setting applies — and the rating floor then favours it further, because
  TMDB's audience rates anime and K-drama generously. Nightly runs ended up mostly asking Sonarr and
  Radarr for subtitled titles.

  Settings → Requests → Guardrails now has a **Language** choice, and any per-person row can override
  it. **Any language** is the default and behaves exactly as before, so an upgrade changes nothing.
  **Prefer these** keeps your languages on the normal bars and makes everything else clear a higher
  rating to be sent on its own — below that bar a title waits in the inbox with the reason on it
  rather than being dropped, so a great foreign film is still one click away. **Only these** never
  asks for another language at all.

  The higher bar has no fixed default: it follows your own minimum rating plus 1.5 and keeps
  following it, so a permissive 6.0 server starts at 7.5 and a strict 8.0 server at 9.5. Type a
  number to pin it. The ranking is deliberately unchanged — a foreign title that clears the bar still
  competes on merit and usually wins, because it out-rates the English titles around it. This thins
  the middle; it doesn't exclude a language.

  The inbox now shows a language chip on any title outside your languages, so a held-back one
  explains itself without opening TMDB.

- **Choose how much of a show to grab.** Every show Shortlist requested arrived with all seasons
  monitored, so one pick of a long-running series started the whole back catalogue downloading that
  night — enough to fill a disk, and enough to spend a day's worth of indexer queries on a show
  nobody had watched an episode of yet.

  Settings → Requests now has **How much of a show to grab** on the Sonarr card, and any per-person
  row can override it in the row editor. The choices are Sonarr's own Add Series _Monitor_ options,
  passed through unchanged, so they mean exactly what they mean in Sonarr: **All Episodes** (still
  the default), **First Season** for a taster you can extend later, **Last Season**, **Pilot
  Episode**, and **None** to file the show in Sonarr without downloading anything.

  Anything other than **All Episodes** also switches off Sonarr's separate **Monitor New Seasons**
  for that show, so "first season only" still means that when season eight airs. Without it a
  restriction on a still-running series quietly grows back a season at a time.

  It applies only to shows Shortlist adds. One Sonarr already tracks is left exactly as you have it,
  as before.

### Fixed

- **Moving your watching to a second account now copies your history exactly.** It was marking whole
  shows watched when you were part-way through one. If you were 400 episodes into One Piece, the new
  account said you had finished all 1,100 — and the same happened to any series you had not
  completed. On a real 50-account server, 342 of 535 watched shows were part-watched, so this was the
  normal outcome rather than an edge case.

  The cause was that Shortlist only ever knew _how many_ episodes you had watched, never _which_. It
  now reads your account episode by episode and copies that, so the new account matches yours:

  - the exact episodes of each show, not the whole show
  - how many times you have rewatched something, not just that you watched it
  - anything you are part-way through, at the same position, back in Continue Watching — these could
    not be carried across at all before

  It also **removes** anything watched on the new account that you have not watched, which is what
  makes the two match, and what repairs an account an earlier version over-marked. The preview lists
  what would be removed **by name** and asks you to confirm before anything is written. Your own
  account is never written to.

  It is now reversible: the new account's state is saved before the first write, and **Undo** puts it
  back exactly — rewatch counts and positions included. The one exception is an account that can't see
  all of your libraries, where a complete picture can't be saved; the preview tells you that before
  you agree to anything. Afterwards Shortlist re-reads the account and
  tells you what did not land, instead of reporting the writes it sent.

  Plex still records everything as watched _today_ — no Plex API accepts a date, and that has not
  changed. The writes now go oldest-first so Continue Watching still comes out in the right order,
  and Shortlist keeps your real dates itself, so your recommendations are unaffected.</content>
  </entry>
  <entry>
    <title>Shortlist 1.7.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.7.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.7.0</id>
    <updated>2026-08-18T00:00:00Z</updated>
    <content type="text">### Added

- **Each row can have its own Sonarr/Radarr settings.** Until now every row asked for titles under
  one set of rules, which is wrong the moment your rows are for different people. A kids' row and a
  film-buff row want different rating floors, different quality profiles, and different folders — and
  a run with three rows was really one pass that mixed them together.

  Open a row, go to **Requests**, and override what that row needs: rating and vote floors, year
  bounds, how many titles it may ask for, whether it sends automatically, and which quality profile
  and root folder it files into. Anything you leave alone is inherited from the global settings, so a
  row you never touch behaves exactly as it does today.

  Two limits stay global on purpose. **Max per run** is the ceiling for the whole run and a row can
  only ask for _less_ than it, never more — otherwise the one setting protecting your download queue
  would be a suggestion. And the rating source (plus its API key) stays global, because it is one
  account shared by everything.

  When several rows want titles in the same run, the run's slots are split evenly between them, and
  each row is evaluated on its own rules. A row that wants fewer than its share hands the remainder
  back to the others rather than wasting it. **A title two rows both want costs one slot, not two** —
  it is requested once, and the run says which row claimed it.

- **A share filter that Plex stores but does not apply is now caught and reported**
  ([#88](https://github.com/stevezau/shortlist/discussions/88)). Shortlist keeps each row private by
  adding label exclusions to everyone else's Plex share, and every check it had said the same thing:
  the exclusions were written, plex.tv stored them, and reading them back confirmed it. None of that
  proves Plex is _applying_ them. Six Home accounts saw all six rows anyway, and the first person to
  notice was a user rather than the owner.

  Each run now looks at one account's home screen through that account's own eyes and reports it if
  rows belonging to other people are showing. It never blocks anything — it measures and tells you,
  because nothing you change in Plex fixes it and the useful action is a bug report.

### Fixed

- **Nothing was reaching Sonarr or Radarr, and nothing said why.** Titles were being examined and
  discarded before any request could be made, and every screen reported a confident zero. The cause
  was a lookup budget spent on cached answers, so a run could exhaust it without ever asking about a
  new title. Rating lookups are now billed only when they actually cost something, and rejections are
  remembered rather than re-fetched nightly.
- **The screens that said "0 requested" now explain their own zero** — whether nothing cleared the
  bar, or titles are sitting in the queue waiting for you to approve them, which the old wording
  reported as if none had been good enough.
- **A row's delivery no longer collapses because one title was deleted from Plex.** A title that
  disappears between being picked and being delivered took the whole row down with it. Runs also now
  report what Plex actually accepted rather than what was asked for, so a partial delivery reads as
  partial instead of complete.
- **Copying a watching account's history moved nothing**, because the owner's own history was never
  read.
- Approvals for titles from different rows each get their own Radarr/Sonarr connection, so a title
  approved from one row can no longer be filed using another row's settings.</content>
  </entry>
  <entry>
    <title>Shortlist 1.6.1</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.6.1" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.6.1</id>
    <updated>2026-08-17T00:00:00Z</updated>
    <content type="text">### Added

- **Leave one person's Plex sharing settings alone**
  ([#92](https://github.com/stevezau/shortlist/discussions/92)). To keep each row private, Shortlist
  adds label exclusions to every other account's Plex restrictions — which is right until it collides
  with a restriction you set yourself. The case that surfaced it: a child's managed account with an
  **allow only** label list, where the whole point is that the account sees nothing but the labels you
  named, and Shortlist's exclusions were fighting it. Switching that person off in Shortlist did not
  help, because off means "no row for them", not "stop touching their account".

  Open the person, go to **Settings → Plex sharing**, and turn off **Manage their Plex sharing
  settings**. Shortlist takes back out the exclusions it added and never touches that account again;
  the Users list badges them **Sharing untouched** so it is visible at a glance, and Support → Sharing
  lists them separately rather than reporting them as a fault. Said plainly at the switch: that
  account can then see other people's rows, unless — as with an allow-only list — its own Plex
  restrictions already keep it away from them.

  Two things stay true regardless. Everyone else still hides _that_ person's row, so leaving one
  account alone never makes their row public. And a **shared row you have limited to certain people**
  stays hidden from them: that exclusion is the only thing keeping the row away from people you did
  not pick, so removing it would undo a choice you made on the row itself. The trade-off to know is
  that later changes to who a shared row is for stop reaching a left-alone account — turn management
  back on if you need them picked up.

### Changed

- Frontend dependencies bumped (lucide-react, sonner, eslint, typescript-eslint, testing-library),
  and the CI checkout action brought in line with the rest of the workflow.</content>
  </entry>
  <entry>
    <title>Shortlist 1.6.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.6.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.6.0</id>
    <updated>2026-08-17T00:00:00Z</updated>
    <content type="text">### Added

- **Judge a request without leaving the inbox**
  ([#87](https://github.com/stevezau/shortlist/discussions/87)). Deciding on a queue of unfamiliar
  titles meant opening a tab per row to find out what each one was, then coming back to the bulk
  toolbar to act on them. Every waiting title now carries its synopsis, and every card has its own
  Send, Delete and Reject. The synopsis rides in the same TMDB response the poster already came
  from, so it costs nothing extra for a title a TMDB source surfaced; rows already waiting fill in
  on the next run that re-surfaces them.

- **"Watched" and "finished" are different questions, and the dashboard now asks both.** Plex's
  watched flag trips on a series' FIRST finished episode, so one episode of a sixty-episode show
  scored exactly like a whole film — on a real 47-user server, of 158 show picks credited as
  watched only 21 had been finished and 31 were a single episode, which ranked TV rows above movie
  rows for a purely structural reason. Every breakdown now reads "N watched · M finished".

### Fixed

- **A name collision took one person's whole night down with it.** A Plex collection is keyed by
  title within a library, so renaming a row onto a title that already exists there answers 409
  Conflict — and a `{top_seed}` row renames itself whenever the seed it is named after changes.
  Recorded on a 46-user server as 45 people fine and one with nothing at all, over a name. The row
  now keeps its old title for that night and still gets its titles; only the collision is
  survivable, so a dead server or a refused write still fails loudly.

- **The recent-watches feed said "watched" for a show somebody sampled one episode of.** Every
  other figure on that page already drew the distinction; this one feed never asked for it. Films
  still read "watched" — they have no middle state — and series now read "started" or "finished".

- **"8 failed" on the Jobs page led nowhere, and the Failed tab claimed nothing had failed.** The
  badge counts every failed job on record; the feed behind it could only fetch the newest hundred
  and filter those in the browser. On a real server the eight failures sat well past that window,
  so following the badge produced "No failed jobs. Nothing has given up — that's the good outcome."
  underneath a badge reading "8 failed". The badge is now a way in, and the list asks the server.

- **A run could report itself failed and then explain nothing.** The banner only appeared for a
  failure that belonged to the run itself, so a run where 45 people succeeded and one did not
  announced "Failed" and showed no reason — leaving one bad account to be found by eye among
  forty-six. Failures are now named and grouped by cause, because forty people failing one outage
  share one cause and printing it forty times buries the only fact that matters.

- **The run header said "Finishing up" for the whole per-user stretch**, and the ordering pass
  counted itself out of its own progress.

- **A local AI server can be a hosted one, and those want a key.**

### Changed

- **A UI pass over every screen.** Eight measured layout defects: the dashboard scrolled sideways
  at exactly 1280px; row names were squeezed to 32px and rendered as "Lat…"; the users and runs
  tables pushed columns — including a switch you could not press — outside their own cards on a
  phone; job names ellipsed into text you could not tell apart; the logs page opened already
  scrolled past its own heading and gave each message a third of the width it needed. All gone,
  verified at 320 through 1536.

- **Less prose in front of the controls.** On-screen explanatory text is down by roughly a third.
  The pattern removed throughout was a page-header explainer, then a section explainer, then a card
  subtitle, then a per-field paragraph — four layers before a value. Anything that prevents a real
  mistake stayed.</content>
  </entry>
  <entry>
    <title>Shortlist 1.5.2</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.5.2" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.5.2</id>
    <updated>2026-08-15T00:00:00Z</updated>
    <content type="text">### Fixed

- **One row, two names on Plex** ([#84](https://github.com/stevezau/shortlist/issues/84)). A row
  named with `{top_seed}` and set to **movies &amp; shows** could deliver a correctly named collection to
  one library and a plain "✨ Picked for You" to the other — from the same row, for the same person,
  so both showed up side by side. Each library named the row from its OWN picks, and if the person's
  seeds were all films, the TV half had nothing to name itself after and fell back. A library still
  uses its own seed when it has one — a row spanning two libraries genuinely can follow a different
  watch in each, and its titles should say so — but it now borrows the row's other seed before giving
  up and using the default name.

- **The owner caveat is offered during setup, not pointed at**
  ([#85](https://github.com/stevezau/shortlist/issues/85)). Plex cannot hide anyone's row from the
  account that owns the server, so the owner sees everybody's on each library's shelf. Shortlist said
  so in the wizard — and then ended with "look for **You see everyone's rows** on the Users page",
  which is homework, handed out during setup, for a problem that only becomes visible in Plex days
  later. Someone was told exactly that, never went to the Users page, and reported 22 rows on their
  shelf as a bug. The wizard now asks the question where the decision is being made — _do you watch on
  this admin account?_ — and opens the real "move my watching to a separate account" flow in place,
  watch-history transfer and all. Skip it and nothing changes: the note on the Users page and the
  alert are both still there, both dismissable.

- **Dismissing that note now clears the matching alert too.** Choosing "Got it — don't show this
  again" while reading the full explanation is a considered answer; the same message sitting in the
  bell afterwards was just nagging. The reverse still does not hold — clearing the bell is a light
  "seen it" and leaves the explanation on the Users page where you can find it again.

- **Shortlist no longer invents a name for a row** ([#84](https://github.com/stevezau/shortlist/issues/84)).
  A row called "Because you watched {top_seed}" needs a title the person has actually watched.
  Someone new to your server hasn't got one — so the row had no name, and Shortlist substituted a
  hardcoded "✨ Picked for You". On a 22-user server with a French row-name template, 19 of 22 people
  got that: not your words, and a claim about a watch that never happened, over a row filled with
  whatever rates highest.

  Rows are now named by you or not built. The row editor asks, right beside the name that raises the
  question: **"Name for people with nothing watched yet"**. Fill it in and they get the row under that
  name; leave it empty and they simply don't get this row until they've watched enough — often the
  right answer, since "because you watched" can't be true for them yet.

  **Shortlist tells you when this affects a row**, in the notification bell — naming the row, saying
  nothing was deleted, and linking to the field. A behaviour change nobody is told about reads as a
  new bug.

  **On upgrade, nothing is deleted** — but a `{top_seed}` row stops appearing for people with nothing
  watched until you give it that name. Their existing row stays on Plex exactly as it is, still
  private, just no longer updated; type a name and it comes back on the next rebuild. Shortlist
  deliberately does not pick one for you: the obvious candidate is your default row's own title, and
  handing two of someone's rows the same name is what makes them fight over a single collection.

- **A skipped show request now says what to do about it.** Sonarr identifies shows by their TheTVDB
  id, and Shortlist looks that up on TMDB — so when TMDB hasn't recorded one, there is no way to name
  the show to Sonarr and the request is skipped. (Guessing would be worse: the closest title match
  for "The Haunting of Bly Manor" is "The Haunting", a different and much larger series.) The reason
  on the **Requests** page used to read "no TheTVDB id for this show" — true, and no help at all
  unless you already knew what a TVDB id was. It now says TMDB has none and that adding the show in
  Sonarr yourself is the way through. A lookup that _failed_ — TMDB down or slow — says something
  different again, because that one is worth waiting a night for.

- **The errors notification can be dismissed.** "N errors in the last day" counts what has already
  happened, and there is nothing to do about the past except acknowledge it — but it was pinned
  open, so the bell carried a badge for a full day with no way to clear it. It now dismisses, and the
  next error brings it straight back rather than staying hidden behind the last dismissal. The two
  alerts that stay pinned are the two describing something still true right now: runs paused, and an
  account that can see other people's rows.</content>
  </entry>
  <entry>
    <title>Shortlist 1.5.1</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.5.1" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.5.1</id>
    <updated>2026-08-14T00:00:00Z</updated>
    <content type="text">### Added

- **A way to support the project, for anyone who wants to.** A quiet "Support this project" line at
  the bottom of the sidebar, under the version, opening GitHub Sponsors. Deliberately in the chrome
  rather than among the navigation, and nothing floats over the page — people self-host to get away
  from being sold to, and anyone inclined to give goes looking rather than needing to be asked twice.

- **Somewhere to ask, rather than only somewhere to report.** GitHub Discussions is open, and Have
  an issue? now offers **Ask a question** beside **Report a bug**. Not everyone who gets stuck is
  looking at a defect — plenty are looking at Shortlist working exactly as designed and not
  understanding why, and with one button on the page those get filed as bugs. Questions go to the
  Q&amp;A category, where an answer can be marked as the answer, so the next person searching the same
  problem finds one instead of an abandoned thread.

- **The "something else is reordering your shelf" alert now tells you how to stop it.** It used to
  say to exclude `shortlist_*`, which is a wildcard no tool's exclusion list accepts. Agregarr has
  since gained an **Exclude from Ordering (Plex Label)** field, and it matches a label exactly or as a
  prefix followed by `_` — so the single word `shortlist` covers the constant label and every
  person's own. The alert names the field and the value.

- **What a row actually cost, per row.** A run page showed one number per person — their whole
  night — so a row that took four minutes and a row that took four seconds looked identical, and the
  shared work every row depends on was billed to whichever row happened to be first. Each row now
  reports its own time and its own AI spend, with the shared setup shown separately and the pools
  that fed more than one row named. Waiting for the Plex write-lock is charged to the row that
  waited, so a slow night points at what was slow rather than at whoever was unlucky.

- **A download that says it is working.** The Logs page's **Download .zip** and Have an issue?'s
  **Download everything** were plain links: the browser fetched them in silence while the server
  gathered and zipped the logs, which takes seconds, so nothing on screen changed and the natural
  reading was that the click had missed. Both now say **Preparing…** while the file is built.

- **One label for co-managing tools.** Every row now carries a constant `shortlist` label (Plex
  stores it as `Shortlist`) alongside its per-person one. Tools that share your Recommended shelf —
  Agregarr's **Exclude from Ordering (Plex Label)**, Kometa's equivalents — take a list of labels to
  leave alone, and Shortlist's per-person labels were no use there: a 46-account server has 46 of
  them plus one per shared row, and the list goes stale the moment somebody joins or leaves. One
  entry now covers every row, for good. It hides nothing and changes nothing about who sees what —
  privacy still runs entirely off the per-person label. Existing rows pick it up the next time they
  are built, so there is nothing to run — but that first run does one extra label write per row you
  already have, and on a server whose Plex is slow to answer writes it may be a noticeably longer
  night. Once only; the log names every row as it goes.

### Fixed

- **A `{top_seed}` row no longer falls back to "✨ Picked for You" for people who have plenty of
  history** ([#84](https://github.com/stevezau/shortlist/issues/84)). A row titled "Because you
  watched…" needs a pick that came from something the person watched, and it only ever looked at the
  single best pick. Some sources suggest a title without following one — what's trending, what's
  popular on your server, a web-search find — so if one of those ranked first, the row acted as
  though the person had no history at all, with a dozen perfectly good seeded picks right behind it.
  It now takes the strongest pick that actually has a seed. That is why this was happening on
  established accounts and not just new ones.

  Still open in that issue: when there genuinely is no seed — a brand-new account — the row is named
  from a hardcoded "✨ Picked for You" that ignores your own row-name setting. Fixing that properly
  means changing what a row with no title of its own is called, and per-user rows are told apart by
  their titles, so it is a change with teeth. It is not shipping in a patch release.

- **"Remove or delete" looks like what it does.** In a row's editor it was grey text that did not
  read as a control at all, let alone one that ends in deleting a row. It now matches the Delete on
  the Rows page — red, outlined, with a bin. It still only takes you to the section at the bottom of
  the page rather than acting, because removing and deleting reach into other people's Plex and
  Cancel does not undo them.</content>
  </entry>
  <entry>
    <title>Shortlist 1.5.0 (withdrawn)</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.5.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.5.0</id>
    <updated>2026-08-13T00:00:00Z</updated>
    <content type="text">Tagged and published for about half an hour, then pulled before anyone was told about it. Everything
in it ships in 1.5.1; the notes are kept here because the images were briefly public.

### Added

- **A row can be positioned after another Shortlist row.** Choosing where a row sits on the
  Recommended shelf offered your own library's collections, but never your other Shortlist rows — so
  "put Because you watched right after Picked for You", the obvious thing to want, could not be
  asked for (issue #81). It can now, and it is chosen as a ROW rather than as a collection: a
  per-person row is one Plex collection per person, so on a 46-account server the old picker would
  have had to list 46 identical-looking entries, and picking any one of them would have placed the
  row for that one account and nobody else. Rows are then placed in dependency order, so a row lands
  only once the row it follows is itself in position. Pointing two rows at each other, or a row at
  itself, is refused when you save it rather than silently doing nothing every night; and deleting a
  row clears the placements that pointed at it, so the rows that followed it fall back to the
  library default instead of never being placed again.

- **Run one row, from the row.** Rebuilding a single row was already possible — it was behind a
  dialog on the Runs page that made you pick, from a list, the row you were already looking at.
  **Run now** sits on each row's card and at the top of its editor, and takes you to the run it
  started so you can watch it happen. A row that is switched off can't be run, because a run doesn't
  build a disabled row, it takes it off Plex — and a button called "Run" doing that is the opposite
  of what the word promises. The editor also gains a **Runs** link to that row's own history, which
  until now only existed on the card the editor replaced. If you press Run with unsaved changes on
  screen, it says so first: a run rebuilds the row as it was last saved.

- **"Where is each row actually showing?"** — a new check on the Have an issue? page, and the only
  one that can see your OWN Home screen. Every other row-visibility check reads the share filters
  that hide a row from other people, and you, as the server owner, don't have one — so the flag that
  keeps somebody else's row off your Home was the one thing nothing could report on. It also
  separates a real fault from a Plex limitation: another person's row on your Home is always a bug,
  while their row on your Recommended shelf is what showing rows on friends' library shelves does,
  and is a setting rather than a defect.

- **Runs are now shown by row, not by person.** A run's unit of work is a row, but the run page
  listed people — so a **shared row**, built once for the whole server and belonging to nobody, had
  nowhere to appear at all. On a night when no per-person row was due, the page was 46 lines of
  "skipped" with the run's only real output, a shared row of 40 picks, nowhere on screen.

  Runs now open on a **Rows** tab showing the rows that run actually built. Each row expands to what
  it produced: a per-person row puts a person picker in front of the picks, a shared row shows them
  directly, and that is the only difference between the two kinds. Rows the run didn't touch are one
  quiet line at the bottom rather than a card each, so a run of one row looks like a run of one row.

  A row is named once, by its own name, with the libraries it delivered to beside it — a row that
  builds both Movies and TV Shows reads "✨ Picked for You · Movies · TV Shows" instead of taking one
  library's title and hiding the other. The People tab is gone: everything it did — the searchable,
  status-grouped person list and that person's own results, trace included — now sits inside the row
  they belong to, so you read a run one row at a time rather than choosing between two ways of
  slicing it.

- **A row's on/off switch is on its own editing page.** It lived only on the Rows card, and the
  editor sent you back there for it — a page change to do the one reversible thing to the row you
  already had open. It now sits beside Run now, with the same confirmation: turning a row off takes
  it off Plex for everyone who has it at the next run, and turning it back on rebuilds it. Removing
  and deleting stay at the bottom of the page, away from the rebuild controls, because they reach
  into other people's Plex and Cancel doesn't undo them — but the header now says they are there and
  takes you to them, which is the part that was missing.

- **Shared rows have a trace.** The same view a per-person row has always had, minus the per-person
  watch history a shared row doesn't use — so "why did it pick that" is answerable for a shared row
  for the first time.

- **The sidebar now names the exact build you're running.** A release shows its version
  (`Shortlist · 1.4.0`); a `:dev` build shows the branch and commit instead
  (`Shortlist · dev · 2ee14f8`), with the full commit on hover and in the debug bundle. The version
  is deliberately absent on `:dev`: it is the last RELEASED version, so on a dev build it names the
  release you came after rather than what you are running — five commits ago, in the case that
  prompted this. The commit is the only thing that tells two dev builds apart anyway, since every
  push between two releases carries the same version number, so "is my container actually on the
  fix?" had no answer short of shelling in.

### Removed

- **The Agregarr connection is gone**, and with it the Agregarr card under **Settings →
  Connections**. It existed for one job: after each run it wrote Shortlist's shelf order back into
  Agregarr so Agregarr's own half-hourly sync would reproduce it instead of undoing it. Maintaining a
  client against a private, unversioned API to stop one third-party tool fighting us over hub
  positions was not worth carrying.

  **If you had it connected**, the Recommended shelf goes back to being contested — Agregarr reorders
  it on its clock, Shortlist puts its rows back on the next run. Two ways to settle that, both
  configuration: exclude collections labelled `shortlist_*` in Agregarr (or stop its "Randomize Home
  Order" job), or set **Row placement** to "Wherever Plex puts them" and let Agregarr own the order
  outright. Your rows are still built, delivered and kept private either way — only their position on
  the shelf changes. Shortlist still tells you when it is happening: the "Something else is
  reordering your shelf" notification is unchanged, and now also names the maintained Agregarr fork.

  Your stored Agregarr address and API key are **deleted** on upgrade (migration `0067`) rather than
  left behind — the key was held encrypted and there is no longer a screen to remove it from. Past
  `run.agregarr_order` and `shelf.agregarr` events stay in your history; they record writes that
  really happened.

- **A note for Agregarr users, wherever Shortlist mentions it.** The original at `agregarr/agregarr`
  is no longer actively released, and reordering a shelf on it re-promotes collections with Plex's
  defaults — which puts other people's rows on the **server owner's** Home, the one place no share
  filter can cover. Shortlist already clears that on every run, so it is a gap between runs rather
  than a standing leak. The maintained fork at
  [bitr8/agregarr-dev](https://github.com/bitr8/agregarr-dev) (image `bitr8/agregarr`) fixes it at
  the source and is a drop-in swap. It still reorders the shelf, so the placement advice above
  applies either way.

### Changed

- **The Have an issue? page now answers the question you picked.** Choosing a problem ran one check;
  three of the cards named two or three things in their description and then opened only the first —
  "checks the queue, the schedule and the clocks" checked the queue. Each card now runs every check
  it promises, the answers stack up on the page instead of replacing one another so you can gather
  several into one report, and eleven more checks state their finding as a sentence rather than
  leaving you to read a table. A check that fails now offers its failure to copy — previously the one
  moment you had nothing to send. Filing a bug no longer needs the checks switched on at all; only
  attaching the diagnostics does.

- **The Jobs page's row schedules read as schedules.** Rows that build at the same time were joined
  into one line of comma-separated names, truncated, with the time as its subtitle and a single Edit
  button that could only take you to the rows list — there was no one row it could mean. The time
  now leads, with how many rows it builds, and each row is its own link to its own editor.

- **The dashboard's "Recently watched" says how much it is showing** and folds the rest away, rather
  than silently stopping. **"N more people watched something"** under By person is now "Show N more",
  because it was never a separate finding — it is the rest of the list above it, which now says it is
  ordered by watches.

- **The Logs page drops the paragraph explaining its own buttons.** What mattered in it — that
  everything down to DEBUG is recorded whatever level you are viewing — moved to the line at the
  bottom that already tells you the full history is in the download.

- **A "Popular on this server" row now actually shows what's popular on this server.** It was built
  the same way a personal row is: your server's most-watched titles became the _starting point_ for a
  TMDB similar-titles search and an AI web search — and that search deliberately excludes the titles
  it started from, so the most-watched titles on your server were the one thing the row could never
  contain. Every pick was a suggestion captioned "Popular on this server", which was untrue of all of
  them. It also cost around 10,000 AI tokens and a minute a night to produce.

  The row is now the count: the titles the most people on your server have actually watched, in that
  order, and each says how many ("11 people watched it"). No searching, no AI, and it builds in
  seconds. Your existing shared rows will change contents on their next run — this is the row you
  were asking for the whole time.

  A shared row's editor drops every control that only made sense for a search — where the picks come
  from, how many recent watches to build on, the AI web-search dials and **Recent releases** — because
  a tally has nothing to point them at, and a control the app ignores is worse than no control. What
  is left is what still decides the row: its libraries, how many people must have watched a title, its
  size, and its order (use **Newest first** to lean modern). Your **Don't seed** list now keeps a
  title out of a shared row entirely, rather than only stopping it being a search starting point.

### Fixed

- **Cancel stops a run.** Pressing it could leave a run writing to Plex for minutes, and a run that
  was only queued would not stop at all until the run ahead of it had finished — the code meant to
  stop it could not run until the thing it was waiting for got out of the way. A queued run is now
  finished on the spot, having done nothing. A running one stops at the next row boundary: every
  person's writes queue on a single lock, so most of them are waiting inside delivery when you press
  it, and each used to go on to write a whole row as its turn came. Measured on a 46-user server, a
  cancel that took over five minutes now takes about one — the remainder being the person whose row
  was already half-written, which is finished on purpose, and the privacy merge that hides
  everything delivered so far. Cancelling mid-retry also no longer erases the record of a collection
  that had already been written.

- **The Runs page notices when a run ends.** It had no live updates at all, so a run that finished
  while you watched kept its "Running" badge and a ticking timer until you navigated away and back —
  which made a cancel that had worked indistinguishable from one that was ignored.

- **A run that never ran says so.** Queue three runs, cancel them nine minutes later, and each
  reported "9m 26s" of work none of them had done: the duration was measured from the moment the run
  was asked for, so it was timing the queue. Runs now record when they actually started, and one
  that never got that far reads "never ran" instead of billing you for the wait.

- **The "How we picked" trace is reachable again.** It had gone with the People tab it lived in, so
  a per-person trace could not be opened from anywhere in the app. It now sits in the person's own
  panel, along with who they are, how long they took and what they cost — and shared rows keep their
  own trace beside them.

- **The watches-per-week chart tells you the numbers.** The count was on a tooltip attached to each
  bar, so on a quiet week the only place you could hover was a three-pixel sliver at the bottom of
  the chart and most of the column did nothing at all. Hovering anywhere in a week's column now
  reads it out, and the chart is dated at both ends — sixteen unlabelled bars never said _when_.

- **A downloading request updates itself.** The Sonarr/Radarr status on the Requests page was
  fetched once when the page opened and never again, so a title that finished downloading while you
  were looking at it went on saying "Searching" until you reloaded, and a title you had just sent
  showed nothing at all. It now checks while you watch and says **Checking…** while it does — and
  only while something is actually moving, so an inbox where everything has downloaded stops asking
  rather than re-reading your whole Radarr library every half minute for as long as the tab is open.

- **A Sonarr or Radarr that can't be reached says so.** A failed lookup is deliberately ignored so
  that one app being down doesn't blank the other's titles — but that left an unreachable app
  looking exactly like one that simply isn't tracking anything, and the inbox showed no badges for
  ever with nothing anywhere explaining why. Each title now says which app couldn't be reached.

- **"Where is each row actually showing?" no longer reports all-clear over a real leak.** Its
  headline sentence looked only at rows claiming the owner's Home screen, so two findings the check
  itself had already made were read straight past: a collection of ours carrying **no label** — which
  nothing can hide, so everyone on the server can see it — and rows Plex refused to answer for at
  all. Both produced the same empty result as a healthy server, and the banner said so. It now names
  an unlabelled row as the bug the server calls it, refuses to call anything clean when the reads
  failed, and — following the same rule the delete path uses — treats _every_ row reading unlabelled
  as a failed read rather than as a server full of leaks.

- **A run that is only queued no longer counts a duration up.** `started_at` is stamped the moment
  a run is _asked for_, not the moment it begins, so a run still waiting for the Plex writer lock
  ticked a stopwatch up from the button press — under a tooltip reading "Running…" beside a badge
  reading "queued". It now shows nothing until it actually starts, and the Started column says
  "queued 5m ago" rather than claiming it began then. The run's own page said "started … · still
  running" for the same reason, which — since **Run now** takes you straight there — was the first
  thing you saw after pressing it.

- **A background job waiting to be retried no longer shows how long it took.** After a failure the
  job goes back in the queue keeping the timestamps of the attempt that failed, so the Jobs page
  displayed that attempt's duration beside "Retrying" — a time for work that had not started.

- **A shared row's result is kept.** It was thrown away at the end of every run: the row was built,
  labelled and promoted on Plex, but only a one-line audit note survived — no trace, no per-library
  breakdown, no token cost, and not even the list of what it picked. Older runs can't be recovered;
  from now on it is all recorded.

- **A shared row's collections are now tracked like everyone else's.** They never entered the
  internal ledger Shortlist uses to find a row again later, which is what lets it clean up a row
  whose title changes every night.

- **Docker installs no longer report themselves as source checkouts.** The app read `GIT_SHA` and
  `GIT_BRANCH` to tell a container from a development checkout, but nothing ever set them — the image
  carried those facts only as OCI labels, which nothing running inside the container can read. The
  build now bakes them in, so the install type is right and the commit is visible.

- **Accounts that were switched off when they left your server now show as gone.** The daily roster
  sweep only compared **enabled** accounts against Plex, so anyone already turned off when they were
  deleted from Plex stayed in the Users list looking like an ordinary disabled account — with nothing
  to say why, and no way to clear them out, because **Remove** only offers itself for someone Plex no
  longer lists. Deleted Home users were the common case (one real server had five of them stuck).
  They are now badged **Left the server** and offer **Remove** like anyone else. Nothing is created
  or promoted on Plex for them — an account that was already off has no rows to take down — though
  the stale rule hiding their old row does become eligible for tidying out of everyone else's share
  filter on the next run, which is the point. The protection that refuses to act when half the server
  seems to vanish at once now covers the badge as well as the switch-off, and the two are judged
  separately, so a server carrying old departures nobody cleared can't block a real one from being
  handled today.</content>
  </entry>
  <entry>
    <title>Shortlist 1.4.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.4.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.4.0</id>
    <updated>2026-08-12T00:00:00Z</updated>
    <content type="text">### Added

- **Shortlist now tells you when it cannot make a row private.** Plex refuses a label share-filter
  for a managed account with a parental **Restriction Profile** set, so those accounts were skipped —
  on the stated grounds that such an account "sees zero collections anyway". That is true of _Younger
  Kid_ and false of _Older Kid_, which on a real server listed three collections. So for those
  accounts nothing hid other people's rows and nothing said so. Every run now checks each profiled
  account **with that account's own token** and reports what it can actually see: a dashboard alert
  that cannot be dismissed while it is true, a **"Sees N rows of others'"** badge in the Users list,
  and an explanation on the person's page. Shortlist still cannot fix it — hiding a row _is_ the
  filter Plex is refusing — so the one remedy that works (clear the Restriction Profile) is named,
  and disabling the account is explicitly called out as _not_ a fix, since that removes their own row
  rather than their view of everyone else's. (#76)

- **People who leave your Plex server are handled properly.** Losing access already switched someone
  off and removed their rows, but the Users list then showed them exactly like an account you had
  turned off yourself, and the share-filter exclude for their deleted row stayed in every other
  account's filter permanently (one real server had reached 990-character filter strings). Departed
  accounts are now badged **Left the server** and offer **Remove**, which drops their pick and run
  history and clears them out of the list. Remove deliberately keeps their original Plex share
  settings, so uninstalling Shortlist can still restore that account exactly as it was found — that
  record is the only copy. A departure is re-checked every sync, so re-inviting somebody brings them
  straight back.

- **Shortlist tells you when another tool is fighting it over the Recommended shelf.** That shelf is
  one server-wide list, so anything else that manages Plex recommendations (Kometa, agregarr,
  Plex-Meta-Manager) reorders your rows along with everything else. It is invisible from any single
  pass — Shortlist moves its rows, re-reads the shelf, confirms the new order, and is right; the other
  tool moves them back minutes later. When the same row has had to be put back three or more times in
  a day, an alert now says so, names the likely tools, and gives the two ways out: exclude
  `shortlist_*` collections in that tool, or turn off **Let Shortlist order the Recommended shelf** and
  hand the order over. Your rows are still built, delivered and kept private either way.

- **Shortlist and Agregarr stop fighting over the Recommended shelf.** If you also run
  [Agregarr](https://github.com/agregarr/agregarr), both tools arrange the same shelf and each
  undoes the other: Shortlist puts the rows up top, Agregarr re-applies its own stored order within
  half an hour, and around it goes — on one real server 35 of 91 rows were left below position 21.
  Connect Agregarr under **Settings → Connections** (its address plus the API key from Agregarr's own
  Settings → General) and the end of each run tells Agregarr where the rows ended up, so its next
  sync reproduces the shelf instead of undoing it.

  It stores the shelf **as Plex is already showing it**, never an order of its own invention, so your
  other Agregarr rows keep their order relative to each other and simply move down to make room.
  Nothing else about them is touched — not posters, visibility, titles or summaries. The connection
  is entirely optional: without it nothing changes and not one extra call is made. If Agregarr is
  unreachable the run finishes normally with a warning rather than failing, and gives up in about
  twenty seconds rather than stalling. Every attempt is recorded — `run.agregarr_order` for a nightly
  run, `shelf.agregarr` for the **Fix privacy** and **Check server** buttons — each carrying the
  before and after ordering, since Agregarr's previous order is not otherwise recoverable.

### Fixed

- **A failed run no longer clears the "can see other people's rows" alert.** The alert and the
  **Sees N rows of others'** badges are driven by the most recent run that actually checked — but
  every run recorded itself as having checked, including one that died before it got anywhere near
  the check. So a single failed run silently removed the alert, zeroed every badge and switched the
  person's page to its reassuring wording, while the exposure itself was untouched. Runs now record
  whether they got as far as looking, and only a run that did can clear a finding. The same silence
  in two other places is fixed with it: an account whose token could not be obtained was counted as
  seeing nothing rather than as unchecked, and the badge suggested turning the person off — which
  removes their own row, not their view of everyone else's, as the rest of the feature already said.

- **Rows promoted nowhere are no longer shuffled around the shelf.** A row belonging to a paused or
  disabled person sits on no surface at all, so its position is invisible to everyone — but it is
  still listed among the managed hubs, and every ordering pass moved it back into place. That was
  four pointless Plex writes per library per pass, and it made a settled shelf look contested: a
  co-managing tool rightly ignores those rows, so Shortlist alone kept moving them. A row on any
  surface — including the owner's own Home — is still placed as before.

- **Rows no longer get stranded at the bottom of the Recommended shelf.** Three separate faults, each
  enough on its own to make it unfixable. A run with no users — which is what every privacy sync is —
  came out with no libraries to order, so the whole pass silently did nothing. A per-library shelf
  anchor made the pass take its rows from the run in progress, so a run with no users had nothing to
  move and said nothing about it; it also dropped the row of anyone paused, errored or skipped. And
  the reorder itself re-issued a move for _every_ row whenever any one was misplaced (47 writes where
  19 were needed) and then reported success without ever re-reading the shelf. Ordering now happens on
  every privacy sync and on **Check and fix rows on Plex** rather than only overnight, moves only what
  is out of place, and re-reads to confirm — recording the result as unverified rather than claiming a
  shelf it did not get.

- **Dead privacy excludes are cleaned up.** A `label!=shortlist_&lt;person&gt;` exclude for a row that no
  longer exists is now removed from everyone's share filter — but only once **two independent checks**
  agree the row is gone: a complete collections read in which nothing carries that label, _and_
  Shortlist's own record that the person departed. Either alone can be wrong in the direction that
  un-hides a live row, so neither is trusted by itself.

- **The nightly departure sweep is now tested.** It disables people and deletes collections
  unattended, and had no test coverage at all — including the two limits that stop it acting on a
  truncated read from plex.tv (an empty roster is ignored; more than half the server appearing to
  leave at once is refused and recorded).</content>
  </entry>
  <entry>
    <title>Shortlist 1.3.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.3.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.3.0</id>
    <updated>2026-08-11T00:00:00Z</updated>
    <content type="text">### Added

- **Rows can prefer recent releases.** Ranking had no age term at all — a title's score was seed
  frequency x rating x seed weight x affinity — so a well-rated, highly-similar 1996 film beat a 2024
  one every time and rows filled with catalog titles. **Settings → Finding titles → Recent releases**
  is a weight, never a filter: an older title still reaches a row, it just has to be a better match.
  It applies to the pool CUT as well as the order, so a newer title below the line can still get in.
  **This applies to every install, existing servers included** — the default is 0.5 ("leans towards
  recent releases"), and no migration pins the old age-blind behaviour. Nothing is rewritten at
  upgrade: each row adopts it on its next refresh night, so rows shift towards newer titles over the
  following days rather than all at once. Set **Recent releases** to 0 to rank as before, globally or
  per row.

- **Changing a setting rebuilds the row instead of waiting out freshness.** Freshness is a cadence
  for suppressing churn when nothing has changed — but nothing recorded WHICH settings a row's picks
  were chosen under, so it also delayed changes made on purpose. On a real server, raising "Recent
  releases" left 36 of 42 rows redelivering byte-identical picks for up to a fortnight, which from
  the outside is indistinguishable from the setting not working. Each row now stores a fingerprint of
  the settings that decide its CONTENTS and rebuilds on a mismatch, whatever the cadence says.

- **`/api/support/surfaces`** — a read-only diagnostic that reports which Plex surfaces each row is
  actually on, included in the support bundle.

- **A run says what year each pick is and how well rated it was.** "Why is this row full of old
  films?" was unanswerable on the one page built to answer it.

- **Web search can now run entirely on your own hardware, via SearXNG.** The AI web-search source
  previously had two backends: your AI provider's own search (Claude, GPT and Gemini only) or the
  hosted Exa API. Neither suited a fully self-hosted server — a local Ollama model can't search on
  its own, so Exa was the only way to use the feature at all, and that meant a third-party account
  and sending queries off the box. **Settings → Finding titles → AI web search** now offers
  **SearXNG** as a third backend: point it at your own instance and the whole search stays local.
  It works with every AI provider, exactly as Exa does. ([#78](https://github.com/stevezau/shortlist/issues/78))

  Set it up in **Connections → Web search** — one card where you pick the backend and enter what it
  needs, replacing the separate per-vendor cards — or inline on the web-search card itself. One prerequisite,
  which the card and the **Test** button both state outright: SearXNG's JSON API is **off in a stock
  install**, so add `json` to `search.formats` in its `settings.yml` and restart. Without it SearXNG
  answers with a bare `403` that explains nothing — Shortlist translates that into the exact fix.
  A reverse-proxy login is supported via its own username/password fields, where the password is
  encrypted at rest. A login embedded in the address (`http://user:pass@host`) is refused outright,
  with a message pointing at those fields: the address itself is stored in the clear, returned by the
  API and written verbatim into the immutable `settings.change` audit event that support bundles
  export, so a password must never be allowed into it. A subpath deployment such as
  `https://example.com/searxng` is kept intact.

  Choosing a backend by name means only that backend — an unconfigured Exa or SearXNG never falls
  through to the other, which would have sent a self-hoster's queries to a paid vendor they never
  picked.

### Changed

- **The "Auto" search backend is gone.** It was the default, so it is what nearly everyone ran, and
  it meant "the provider's own search UNIONED with whichever external is configured" — a real
  behaviour that its name described not at all. You now pick exactly one backend, and it is the one
  used. Migration 0063 pins every install to what it was actually using: a configured SearXNG, else
  a configured Exa key, else the provider's own search. **A server that had both loses the combined
  search**, which did widen the candidate pool; raise a row's seed count if you want that reach back.

- **Which backend the web search uses now lives in one place.** It was briefly both on the
  Connections card and on the Finding-titles card — the same control twice, with no way to tell
  which was authoritative. Connections → Web search owns the backend and its credentials (the AI
  provider already works that way); Settings → Finding titles owns whether the source runs, and
  names the backend with a link rather than repeating the form.

- **"Test" on the provider's own web search now really searches.** Being Claude, GPT or Gemini says
  the provider offers a web-search tool; it does not say your plan or model may use it. When it may
  not, the call failed at run time, logged a warning and returned no titles — so the source quietly
  contributed nothing, every night, with nothing in the UI to say so. The Test button now performs
  one real search and tells you if the tool came back empty.

- **The run trace names every candidate and what became of it.** "What survived, and what release
  date did to it" reported counts — "40 candidates survived filtering" — which is a summary, not a
  trace: it could not answer "so why isn't X in my row". Every candidate is now listed under what
  happened to it (made the shortlist / not in your libraries / lost the cut / already watched), each
  with the rating and the release-date multiplier actually applied to it. The per-title verdicts were
  already being recorded; they were simply never shown.

- **The "not in your libraries" group says what Radarr/Sonarr did with it.** That group IS the
  request pool, and nothing on the page said so. Each title now reads "requested — added to Sonarr
  and searching" or the reason it is still waiting. Which exposed the gap behind it: the engine
  worked out per-title why a title stayed queued and then discarded it into an aggregate log line, so
  every pending row had an empty reason and the inbox's one question had no answer anywhere.

- **"How we ordered the shortlist" shows the ordering instead of describing it.** The claim an owner
  most wants checked — that one heavily-watched favourite cannot swallow the row — was the one prose
  could not settle. The rank list now marks where each source's and each watched title's turn begins.

- **Run stats say "Web searches" rather than "Exa searches".** The same counter now serves both
  external backends, so naming one vendor was simply wrong on a SearXNG server. A run's "How we
  picked" trace also records _which_ backend actually ran.

### Fixed

- **A share filter written by Plex Web is merged, not corrupted.** `parse_filter` split conditions on
  `|` and values on `,` only, but Plex Web writes a combined allow+exclude filter as
  `label=Age%200%2CAge%203&amp;label!=X`. That parsed into one condition whose field swallowed the allow
  clause, so the existing exclude was invisible, the merge appended a second one, and the filter grew
  corrupt. This is the privacy machinery, so it is the most important fix in this release.

  **It stops the corruption; it does not undo it.** An account whose filter already carries two
  `label!=` clauses keeps both — future runs merge into the first and leave the second alone, and Plex
  Web is unhappy with the pair. If you hit this before upgrading, open **Plex → Settings → Users →
  that account → Restrictions** and delete the duplicate exclude line; Shortlist rebuilds what it
  needs on the next run. Healing them automatically means rewriting a filter we only partly wrote,
  which is how they got corrupted in the first place, so it is deliberately left as a manual step.

- **The broken-row sweep asks twice before deleting.** It is the only destructive Plex path, and an
  empty label read is indistinguishable from a genuinely unlabelled collection — so a collection is
  now re-read to confirm before removal, and a sweep where NOT ONE of our rows reads as labelled is
  treated as a failed read rather than a server full of orphans.

- **A refresh night no longer collapses a row onto one taste.** The refresh branch merged survivors
  with newcomers and then truncated by pool order, re-applying the very ordering `diversify_by_seed`
  had just defeated — so a heavily-watched title's look-alikes took the row back over.

- **Shared rows honour their display order** and stop offering dials they ignore: a shared row set to
  "Shuffled" or "Highest rated" was delivering ranking order, because the shared path never called
  the code those settings live in.

- **Cold-start rows are full size and from the right library**, and "unstarted only" is rechecked —
  three findings from the pipeline audit, all in paths whose victims are least likely to report them.

- **An exclusion written URL-encoded is no longer reported as missing.** The sharing report compared
  filter values raw while the privacy code compares them decoded, so an encoded copy of a label read
  as absent — and the one question that report exists to answer named a leak that wasn't there.

- **Cancel stops lying about what it did.** It worked on the first press and then answered "this run
  isn't currently running" to every press after, about a run that was. Asking a stopping run to stop
  is now a no-op rather than an error, and the accepted cancel is recorded on the run so a reloaded
  page still shows "Stopping…" instead of a live-looking button that can only fail.

- **A connection card says what it needs before what it is** — the part that actually blocks you was
  the easiest to skim past.</content>
  </entry>
  <entry>
    <title>Shortlist 1.2.1</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.2.1" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.2.1</id>
    <updated>2026-08-07T00:00:00Z</updated>
    <content type="text">### Added

- **A run now says what Plex ratings did to it.** Open a run → **How we picked** for a person, and
  the "Watched recently" step states the rating policy that run used in one line: off, on but nobody
  rated anything low enough, on but the account's ratings were disbelieved as tool-written, or how
  many titles a rating dropped. Until now all four looked identical — the trace listed dropped
  titles when there were any and said nothing at all when there weren't, so a run where the feature
  silently did nothing read exactly like a healthy one. The distrusted case is the one worth
  catching: a Kometa rating sync on that account means every rating is ignored, all run, invisibly.

  The line reports the setting **as it was when the run happened**, not as Settings reads today, so
  changing it later doesn't rewrite the history of a run you open a fortnight afterwards. Runs
  recorded before this release don't carry it and still show just the dropped titles.

  It also catches the half-case the account-level check is designed to let through: if a tool has
  written _some_ of someone's ratings but not enough to condemn the account, the counted ratings and
  the skipped ones are reported separately, rather than a single total that quietly speaks for values
  nothing ever looked at.

### Fixed

- **A pick's "why" said "Because you liked…" when it only ever knew what someone watched.** Every
  pick with a genre in common with its seed claimed a preference nobody had expressed — seeds are
  weighted purely by how recently something was watched, and a Plex rating can only ever _remove_ a
  seed, never mark one as liked. It now reads "Because you watched…", which is the fact we have.
  Reasons are written into each run as it happens, so runs already recorded keep the old wording.</content>
  </entry>
  <entry>
    <title>Shortlist 1.2.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.2.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.2.0</id>
    <updated>2026-08-05T00:00:00Z</updated>
    <content type="text">A minor release with **one behaviour change** — read the first item below before upgrading if any of
your rows are set to 0% already-watched, which is the default.

### Changed

- **A row set to 0% already-watched now also excludes shows someone has only STARTED.** It used to
  exclude only shows they had _finished_, where "finished" meant 80% of the episodes or a
  length-scaled floor of about three. So a series you were two episodes into was, as far as the row
  was concerned, a fresh discovery — and could be recommended straight back to you. That was
  reported, and it was doing exactly what it was told.

  Plex draws no such line: its own watched filter returns a series from its first episode. A probe of
  a real server found it returning shows as little as 1.1% watched (2 of 176), and on that server
  five of ten started shows were still eligible to be suggested to the person watching them. At 0%,
  started now means watched — the two agree.

  **Rows above 0% are unchanged.** There the percentage is still a ceiling on _finished_ titles,
  because "N% of the row" needs a definite line to mean anything. If you relied on the old
  behaviour, set the cap above 0. Rows re-pick their titles on their own refresh night, so the change
  reaches a row when it next rebuilds rather than immediately.

- **"Only series they haven't started" is now offered on a films-and-shows row**, not just a
  shows-only one. The API and the engine always accepted it there; the switch was hidden, so the
  default row most installs have could never turn it on. On a 0% row it is now a no-op — that row
  excludes started series anyway — and it still does real work on a row whose cap is above 0%.

### Added

- **"Have an issue?"** — a new page, replacing the sidebar's separate _Report a bug_ and _Copy
  diagnostics_ buttons. It runs twenty-one read-only checks against your own server and, for most
  problems, names the cause outright: which library refused someone's token, which setting actually
  applied, why a row is short, whether Plex still matches what Shortlist thinks it delivered.

  Nothing on the page changes anything — not your Plex server, not your rows, not your settings. The
  checks stay off until you switch them on and switch themselves off again after 24 hours, because
  they read share filters and per-user tokens. Every check has a **Copy for support** button, and
  what it copies is shown on screen first so a paste holds no surprises. The last section files the
  report: a pre-filled GitHub issue plus a diagnostic to attach, as a paste or a file. That
  diagnostic masks credentials, IP addresses and your server's machine id. It is a good first pass
  rather than a guarantee: logs carry whatever a dependency decided to print, so give the report a
  skim before posting it publicly. It does name the people on your server by their Plex username —
  replace those yourself if you'd rather not publish them.

  If someone reports a problem on a server you don't administer, sending them there is usually faster
  than a list of questions.

### Fixed

- **A downloaded log zip no longer carries your server's IP address or machine id.**
  `Logs → Download` calls itself "the attachment for a bug report" and had only ever removed
  credentials — so it shipped your Plex server's address on every line that recorded a call to it
  (17,234 of them in one real export) and, where a collection write was logged, the machine id that
  identifies your server. Both are now removed there and in the diagnostic report, which had the same
  gap in the log files it bundles.

  A URL keeps its scheme and port (`https://&lt;host&gt;:32400`) because those are the parts that answer a
  question. The live Logs view is deliberately unchanged: it renders on your own screen, where your
  server's address is what makes a line readable.

- The documentation described a `recommendations.watched_show_pct` setting that has never existed.
  The finished threshold is fixed in the engine; the docs now say so.</content>
  </entry>
  <entry>
    <title>Shortlist 1.1.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.1.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.1.0</id>
    <updated>2026-08-05T00:00:00Z</updated>
    <content type="text">A minor release: one new setting, four fixes reported by v1 users, and no breaking changes. An
existing install upgrades in place and behaves exactly as it did until you change the new setting.

### Added

- **You can now choose what someone with too little watch history gets** ([#66](https://github.com/stevezau/shortlist/issues/66)).
  Until now they always got a row of the server's highest-rated titles. That is still the default,
  but **Settings → Finding titles → When someone hasn't watched enough** can now be set to
  **Don't build their row** instead — no row is created, and any row they already have is removed,
  so "skip" means gone rather than left to go stale. It comes back on its own the night they cross
  the threshold.

  **Any row can override this in its editor**, which is the point of having it per row. A
  `{top_seed}` row ("Because you watched X") is the one worth skipping: it has no favourite to name
  itself after, so for these people it quietly falls back to a plain title. A general "Picked for
  you" row is perfectly happy holding popular titles in the meantime.

- **Where that line sits is now yours to set.** **Enough watch history** (default 10 titles) was
  fixed in the engine and unreachable; owners of small or new servers can lower it.

### Fixed

- **A run in progress no longer disagrees with itself about when it started**
  ([#67](https://github.com/stevezau/shortlist/issues/67)). "Started 8m ago" sat beside a duration
  of "10m 54s" — the two cells read the same timestamp against the same clock, but were evaluated at
  different moments. They now share one.
- **A first run no longer dies because the container started before its network did.** The one
  plex.tv read whose failure aborts a whole run had about three seconds of retry, which is not
  enough to outlast a slow network stack; it now gets about thirty. Every other read is unchanged,
  and a run still fails loudly when plex.tv is genuinely gone.
- **The Rows page now says why the admin's Collections tab lists everyone's rows.** The answer
  existed but only covered the Recommended shelf, and only while that setting was on — an owner who
  found the rows in Plex's Collections tab had nothing in front of them.
- **The "How we picked" button no longer strands mid-header** on a person whose display name is long
  enough to wrap the run card's header.

### Changed

- A muted row whose title depends on its picks (a `{top_seed}` row) is now genuinely removed from
  Plex. It could not be matched by title, so it survived every run — private, but never actually
  gone. Muting has always meant "gone"; now it is.</content>
  </entry>
  <entry>
    <title>Shortlist 1.0.0</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v1.0.0" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v1.0.0</id>
    <updated>2026-08-04T00:00:00Z</updated>
    <content type="text">First stable release. No breaking changes from `0.1.0-beta.9` — the version number is a statement
about stability, not a rewrite. An existing install upgrades in place; the migrations run on boot
after taking a pre-migration backup.

### Added

- **A "Because you watched" row can cycle its seed** instead of sitting on the newest watch for
  weeks, so the row keeps moving even when someone's viewing does not.
- **The row editor is a page**, showing what the row will actually do — and whether it is working —
  beside the settings that decide it. Rows can be renamed and deleted from there.
- **A way back from Off**, and a request filter that reaches past the 500-row inbox cap: picking a
  name under "Wanted by" now asks the SERVER, so it searches every title on file rather than the
  page that happened to load.
- **Two new row orders, for when the front of a row feels stuck** ([#63](https://github.com/stevezau/shortlist/issues/63)):
  **Just added** puts whatever is new to the row at the front, and **Taking turns** advances the
  front by one title a day so every pick gets a spell there. Both are presentation only — neither
  changes which titles a row holds, or how often it refreshes, which is still **Freshness**.
  "Newest" is now labelled **Newest released**, to keep it distinct from "Just added".
- **A rebuilt documentation site**, split into eight task-shaped guides.
- **The row editor leads with how the row is doing** — delivered, watched, runs and last built,
  across the top. Runs links to that row's own history, and counts the runs that list actually
  holds, so the number and the page behind it can never disagree.

### Fixed

- **An un-watch is noticed within the night's read** rather than waiting up to a week for the full
  re-read. Only when the read proves it covered its window — a truncated walk deletes nothing, so a
  PMS that omits `totalSize` can never be mistaken for "they un-watched everything".
- **A library removed from the server no longer counts as watched for ever.** Its cached titles and
  cursor are swept on the weekly pass. A library that is merely unshared is left alone — that
  history is still true.
- **`fetch_section` raises instead of returning an empty set** when plex.tv will not mint a token.
  Reported as "nothing watched", it made a full read wipe the section and stamp the sync a success.
- **The dashboard and Rows pages no longer scroll sideways on a phone** (134px and 184px past a
  390px screen; Rows put the Delete button out of reach entirely). Every route, the wizard, the nav
  drawer and every dialog now fit 390px, enforced by `tests/e2e/test_mobile_audit.py`.
- **The drift check's off switch now turns it off.**
- **Pages no longer wait on Plex.** The library list behind every row card was read live from the
  server on each page load, so while a job was deleting collections — one DELETE took 15.8s inside
  Plex's own write lock — every page queued behind it. It is cached now, concurrent misses collapse
  into one read, and a failed refresh serves the last good answer instead of an error.
- **Turning a row off asks first.** The next run takes that row off Plex for everyone who has it,
  which a bare switch gave no hint of. Turning one back on is unchanged — it removes nothing.
- **Rename only offers itself when the name has changed**, and now does the rename rather than
  showing you another button to press. Pressed on an unchanged name it used to rewrite every
  collection, for every person, to the name they already had.
- Warnings look like warnings, small grey text is readable, and the settings are explained in words
  a first-time user already knows.

### Changed

- The read-only Plex audit moved out of the Danger zone into Advanced — it changes nothing, and
  filing it under a destructive heading made the safest control on the page look like the riskiest.
- Renaming moved off the Rows list and into the editor, beside the name it changes.</content>
  </entry>
  <entry>
    <title>Shortlist 0.1.0-beta.9</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v0.1.0-beta.9" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v0.1.0-beta.9</id>
    <updated>2026-08-02T00:00:00Z</updated>
    <content type="text">### Added

- **Rows choose their own order.** Best match (the ranking, unchanged and still the default),
  Highest rated, Newest, or Shuffled. Plex only sorts a collection by release date, alphabetically,
  or by a custom order, so every one of these is applied by Shortlist and written as that custom
  order — which is what the Home row displays.
- **Highest rated can use IMDb, Trakt, Rotten Tomatoes or Metacritic** instead of TMDB, via MDBList
  (Settings → Finding titles → "Rated by", also editable straight from the row editor). TMDB needs no
  setup and costs no lookups. Without an MDBList key, or once its daily quota is spent, a row falls
  back to TMDB for its whole ordering rather than sorting half of itself on one scale and half on
  another.

### Fixed

- **A "Because you watched X" row now follows the watch it names.** Its title renders from the top
  pick, and every refresh carried that pick forward — so the row stayed named after the first thing
  that ever seeded it while newer watches quietly filled its tail. It now rebuilds around the new
  watch and renames itself when the seed moves. That template also refreshes nightly, since an
  eight-day cadence kept it naming last week's film for a week.
- **The top of a row moves again.** A refresh kept the strongest two-thirds pinned to the head, so on
  a 20-title row thirteen positions could never change however the candidates scored. Survivors and
  newcomers are now ranked together, so a better new suggestion can reach the front.
- **The library picker no longer ticks libraries a row never builds in.** An empty selection means
  "every library of this row's type"; it was drawn as every library, so a movies-only row showed its
  TV ones ticked — and touching them flipped the row to cover both, which on a one-seed row silently
  built an empty collection.

### Changed

- **The row editor is five decisions instead of nineteen.** Name, who gets it, order, schedule and
  size stand alone; artwork, what it draws on, where it appears and requests fold away, each
  captioned with its current values so a closed section still answers "is what I want in here?".
- Several settings now say what they do: "Make this a 'watch it again' row" (was "Lead with things
  they've seen"), "Watches the AI searches from" (was "Recent watches to search", and it is hidden on
  rows that do not use AI web search, where it did nothing), and freshness now says it decides _which_
  titles a row holds rather than the order they appear in.</content>
  </entry>
  <entry>
    <title>Shortlist 0.1.0-beta.5</title>
    <link href="https://github.com/stevezau/shortlist/releases/tag/v0.1.0-beta.5" rel="alternate" type="text/html"/>
    <id>https://github.com/stevezau/shortlist/releases/tag/v0.1.0-beta.5</id>
    <updated>2026-07-22T00:00:00Z</updated>
    <content type="text">### Fixed

- **The run page really does show where each pick came from now.** There were three places that
  build a pick, and the run page renders the one that was still missing provenance — a stored
  per-(row, library) breakdown, not the picks table. beta.4 fixed the renderer; the data feeding it
  was still blank.
- **Existing runs explain themselves too.** Provenance is joined onto the breakdown from the picks
  rows when a run is read, so runs recorded before this don't stay blank until they're rebuilt. A
  pick with no matching row stays blank rather than being given an invented source.</content>
  </entry>
</feed>
