/ScheduledTasks is not ordered the way upstream orders it #127

Closed
opened 2026-08-14 00:56:38 +02:00 by Claude · 1 comment
Collaborator

Upstream sorts the listing by Name, so Refresh People precedes Scan Media Library. We list the scan first, in declaration order.

Measured during #95 and deliberately left out of that change to keep it to the issue. One line plus two test edits.

It matters slightly more than an ordering usually does: a dashboard renders the list in the order it is given, so the two servers' Scheduled Tasks pages do not read alike — and when this server grows the other thirteen jobs (#97 and its children), declaration order and alphabetical order diverge much further than they do with two.

Where to start

crates/jellyfin-http/src/routes/tasks.rs. Upstream is ScheduledTasksController.GetTasks, which orders by name after filtering hidden tasks.

Worth checking while there: whether upstream's ordering is culture-aware, since the by-name work elsewhere in this project found that a linguistic compare and an ordinal one differ on punctuation, and task names carry spaces.

How to know it is done

scenarios/tasks.json grows a listing request that diffs clean against the reference on order as well as content — which it cannot do today, because the listing itself cannot agree while this server declares two of upstream's twenty. Until #97's children land, the honest check is a test against the expected order plus a note in the scenario saying why the listing is read per-task instead.

House rules

Tests run under cargo nextest; upstream's source is the checkout in upstream/ at the pin; generated files are regenerated rather than edited; comments say what the code does, not how it got there. crates/jellyfin-harness/README.md says what a diff means and what an ignore entry must carry. A server started off current main binds UDP 7359 — set [discovery] enabled = false in a scratch config when another one is running.

Upstream sorts the listing by `Name`, so `Refresh People` precedes `Scan Media Library`. We list the scan first, in declaration order. Measured during #95 and deliberately left out of that change to keep it to the issue. One line plus two test edits. It matters slightly more than an ordering usually does: a dashboard renders the list in the order it is given, so the two servers' Scheduled Tasks pages do not read alike — and when this server grows the other thirteen jobs (#97 and its children), declaration order and alphabetical order diverge much further than they do with two. ## Where to start `crates/jellyfin-http/src/routes/tasks.rs`. Upstream is `ScheduledTasksController.GetTasks`, which orders by name after filtering hidden tasks. Worth checking while there: whether upstream's ordering is culture-aware, since the by-name work elsewhere in this project found that a linguistic compare and an ordinal one differ on punctuation, and task names carry spaces. ## How to know it is done `scenarios/tasks.json` grows a listing request that diffs clean against the reference on order as well as content — which it cannot do today, because the listing itself cannot agree while this server declares two of upstream's twenty. Until #97's children land, the honest check is a test against the expected order plus a note in the scenario saying why the listing is read per-task instead. ### House rules Tests run under `cargo nextest`; upstream's source is the checkout in `upstream/` **at the pin**; generated files are regenerated rather than edited; comments say what the code does, not how it got there. `crates/jellyfin-harness/README.md` says what a diff means and what an ignore entry must carry. A server started off current `main` binds UDP 7359 — set `[discovery] enabled = false` in a scratch config when another one is running.
Author
Collaborator

Done, on main.

ScheduledTasksController.cs:44 is .OrderBy(o => o.Name); we listed in declaration order. Upstream's twenty names come back exactly sorted, and ours now gives Refresh People, Scan Media Library rather than the scan first.

Ordinal and culture compare agree on every pair present, so the linguistic-versus-ordinal question this project has hit elsewhere does not arise here — worth re-checking if the other thirteen jobs land, since #97's children will widen the list considerably.


The batch this closed in

Seven issues went out together because they looked like one defect class. They turned out to be three roots:

  • #120 and #124 — /Items' Params had been grown one client at a time instead of held against upstream's controller signature, and /UserItems/Resume shared it.
  • #121 and #122 — an ordering key read off the wrong column: sortBy=Name maps to CleanName upstream, not SortName, and a by-name search orders by relevance with no sort-name tiebreak, so ties fall out in id order.
  • #133 — its own, and it forced a real fix in the shared sort table: upstream's PremiereDate coalesces with ProductionYear where ours read the bare column.

A reviewer re-derived the parameter audit mechanically — extracting every [FromQuery] name from GetItems and doing set arithmetic against what we bind — and got nothing bound that upstream does not take and nothing in the register that is not an upstream parameter. This class is closed rather than narrowed, which matters because it had been found three times.

Verified on a restored pair: browsing 34 → 0, items 33 requests at 0, clients 56 → 3, portable 115 → 6. JELLYFIN_RS_REQUIRE_FIXTURES=1 cargo nextest run --workspace: 2228 passed, 0 skipped. Clippy clean. No ignore-list entry was added or touched.

Two things the review caught that the batch had not

Five of the ten new scenario entries could not fail. The default snapshot has no genres, studios or tags, so five items.json entries compared two empty pages — reverting the whole fix would still have replayed clean. They moved into editing.json, after the save that gives one film a genre, a tag, a certificate and a studio and then takes them away, so each selects one row out of nineteen. That is the fifth time this session the same failure has turned up in a different layer, after M5's vacuous scenarios, M8's bundle.json, #111's all-refusals visibility.json and #137's silently-skipping tests.

Reading the reference can write to it. Studios=, Artists= and Albums= resolve through upstream's GetStudio/GetArtist, which call CreateItemByName — so a read files a BaseItems row. That is how a stray Brandywine Productions got into the shared fixture mid-session and made portable.json disagree by 149. Genres, Tags and OfficialRatings narrow through ItemValues and are safe. Both halves are now written into crates/jellyfin-harness/README.md.

Done, on `main`. `ScheduledTasksController.cs:44` is `.OrderBy(o => o.Name)`; we listed in declaration order. Upstream's twenty names come back exactly sorted, and ours now gives `Refresh People, Scan Media Library` rather than the scan first. Ordinal and culture compare agree on every pair present, so the linguistic-versus-ordinal question this project has hit elsewhere does not arise here — worth re-checking if the other thirteen jobs land, since #97's children will widen the list considerably. --- ## The batch this closed in Seven issues went out together because they looked like one defect class. They turned out to be **three roots**: - **#120 and #124** — `/Items`' `Params` had been grown one client at a time instead of held against upstream's controller signature, and `/UserItems/Resume` shared it. - **#121 and #122** — an ordering key read off the wrong column: `sortBy=Name` maps to `CleanName` upstream, not `SortName`, and a by-name search orders by relevance with no sort-name tiebreak, so ties fall out in id order. - **#133** — its own, and it forced a real fix in the shared sort table: upstream's `PremiereDate` coalesces with `ProductionYear` where ours read the bare column. A reviewer re-derived the parameter audit mechanically — extracting every `[FromQuery]` name from `GetItems` and doing set arithmetic against what we bind — and got nothing bound that upstream does not take and nothing in the register that is not an upstream parameter. **This class is closed rather than narrowed**, which matters because it had been found three times. Verified on a restored pair: `browsing` **34 → 0**, `items` 33 requests at 0, `clients` **56 → 3**, `portable` 115 → 6. `JELLYFIN_RS_REQUIRE_FIXTURES=1 cargo nextest run --workspace`: **2228 passed, 0 skipped**. Clippy clean. No ignore-list entry was added or touched. ### Two things the review caught that the batch had not **Five of the ten new scenario entries could not fail.** The `default` snapshot has no genres, studios or tags, so five `items.json` entries compared two empty pages — reverting the whole fix would still have replayed clean. They moved into `editing.json`, after the save that gives one film a genre, a tag, a certificate and a studio and then takes them away, so each selects one row out of nineteen. That is the fifth time this session the same failure has turned up in a different layer, after M5's vacuous scenarios, M8's `bundle.json`, #111's all-refusals `visibility.json` and #137's silently-skipping tests. **Reading the reference can write to it.** `Studios=`, `Artists=` and `Albums=` resolve through upstream's `GetStudio`/`GetArtist`, which call `CreateItemByName` — so a *read* files a `BaseItems` row. That is how a stray `Brandywine Productions` got into the shared fixture mid-session and made `portable.json` disagree by 149. `Genres`, `Tags` and `OfficialRatings` narrow through `ItemValues` and are safe. Both halves are now written into `crates/jellyfin-harness/README.md`.
Sign in to join this conversation.
No description provided.