Skip to content

feat: write Sonarr/Radarr tags from Maintainerr collections & exclusions#3162

Merged
enoch85 merged 5 commits into
developmentfrom
feat/arr-tagging
Jun 24, 2026
Merged

feat: write Sonarr/Radarr tags from Maintainerr collections & exclusions#3162
enoch85 merged 5 commits into
developmentfrom
feat/arr-tagging

Conversation

@enoch85

@enoch85 enoch85 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Adds the write side of three feature requests — custom *arr tag for content, its duplicate tag based on collections/rules, and a dnd tag when excluded (the read side — matching rules on existing *arr tags — already shipped). Maintainerr now applies Radarr/Sonarr tags as a side effect of its own state, via a shared best-effort ServarrTagService.

What

  • Membership (A): while a tagInArr rule group's collection holds an item, the matching Radarr movie / Sonarr series carries a tag named after the group. Maintained off the executor's per-run membership deltas (not the per-item eval loop), and reconciled on the enable/disable toggle and on rule-group deletion — so existing members are backfilled on enable and stripped on disable/delete.
  • Exclusion (B): excluding an item applies a protective tag (default dnd) to the matching *arr entity, across every exclude/un-exclude path (collection-scoped + global, POST + DELETE). A global exclusion resolves the single configured instance of the item's type. Radarr and Sonarr are configured independently (enable + label + opt-in removal-on-unexclude). Removal is conservative — opt-in, only ever the configured label, and never while another exclusion still protects the item.
  • Shared helpers ensureTag (race-tolerant), setMovieTags/setSeriesTags (movie|series/editor, applyTags add/remove only — never replace).
  • Tag labels are normalized to the *arr charset ^[a-z0-9-]+$ (single source of truth in @maintainerr/contracts, validated up front in the UI so the saved label is exactly the tag the instance receives). A rule group "Stale Movies" → tag stale-movies; Radarr/Sonarr 400 on anything else.
  • Collection.tagInArr + 6 per-service settings columns via a generated, end-to-end-verified migration. Item→*arr resolution reuses the action-handler path and honours the #3125 transient(undefined)/not-tracked(null) contract.
  • UI: a conditional "Tag this content" checkbox in the rule modal (left Options column, only when the matching *arr is selected) and per-service exclusion-tag settings on the Radarr/Sonarr pages.
  • Dev tooling: fake-radarr.mjs gains /tag + PUT /movie/editor (+ the charset 400).

Gated to movie+show (Sonarr has no per-season tag); season/episode collections are skipped with a debug log. No new ServarrAction.

Verification

  • Automated: yarn lint/check-types/test/build green. 1796 server specs incl. ServarrTagService (every branch), ensureTag/setMovieTags/setSeriesTags, the exclusion hooks across all paths (scoped + global, conservative removal + shared-tag guard + null-type→collection-type fallback), the membership toggle reconcile (enable backfill, disable + crucial-change snapshot), delete-group cleanup, a 100k-item bounded-batch stress test, a generic in-memory migration matrix (whole chain applies + the new migration reproduces its entity columns exactly — a hand-waived/drifted migration fails — + the newest migration reverts), and Vitest+RTL UI (per-service toggle gating, payload, charset + normalize-stability validation). Migration verified end-to-end (clean DB → migration:runmigration:generate = "No changes").
  • *Live dev stack (real Radarr/Sonarr HTTP API + Playwright, confirmed in the arr UI):
    • Plex + Radarr — Behavior A: enabling tagInArr on a populated collection backfilled all 5 members in dev-radarr (no rule re-run); disabling removed them. Confirmed in Radarr's own Settings → Tags (the group tag on 5 movies). Behavior B via the media-modal path: a global exclude applied dnd to the resolved single instance; the POST remove un-exclude path stripped it. The rule-modal checkbox renders only for the matching *arr; the settings label input rejects hyphen-edge labels up front (no save).
    • Emby + Radarr (movie) and Emby + Sonarr (show) — base A + B flows against real dev-radarr/dev-sonarr; confirms tmdb/tvdb resolution and the Emby/Jellyfin parentId movie-classification gotcha (resolved by item.type).
  • Notes: Jellyfin shares Emby's adapter (same parentId behaviour), covered by the cross-server resolution unit tests. The transient *arr-down case is unit-tested (the dev Docker socket is read-only). A delete action removes the tag with the *arr object (resolveArrIdnull, no run error). Streamystats/Seerr/plex.tv enrichment degrade gracefully and can't be fully mocked.

Adds the write side of three feature requests — Maintainerr now applies *arr
tags as a side effect of its own state, via a shared best-effort
`ServarrTagService`:

- https://features.maintainerr.info/posts/8
- https://features.maintainerr.info/posts/125
- https://features.maintainerr.info/posts/81

Two triggers share the plumbing:

- Membership (A): while a `tagInArr` rule group's collection holds an item, the
  matching Radarr movie / Sonarr series carries a tag named after the group.
  Driven from the executor's per-run membership deltas, and reconciled on the
  enable/disable toggle and on rule-group deletion. Off the per-item eval loop.
- Exclusion (B): excluding an item applies a protective tag (default "dnd") to
  the matching *arr entity — covering both collection-scoped and global
  exclusions, across every exclude/un-exclude path. Radarr and Sonarr are
  configured independently (enable + label + opt-in removal-on-unexclude).
  Removal is conservative: opt-in, only the configured label, and never while
  another exclusion still protects the item.

Details:
- Shared helpers `ensureTag` (race-tolerant), `setMovieTags`/`setSeriesTags`
  (`movie|series/editor`, applyTags add/remove only — never replace).
- Tag labels normalized to the *arr charset `^[a-z0-9-]+$` — a single source of
  truth in @maintainerr/contracts, validated up front in the UI so the saved
  label is exactly the tag the instance receives (Radarr/Sonarr 400 otherwise).
- `Collection.tagInArr` + 6 per-service settings columns via a generated,
  end-to-end-verified migration; item→*arr resolution reuses the action-handler
  path and honours the #3125 transient/null contract.
- UI: a conditional "Tag this content" checkbox in the rule modal and per-service
  exclusion-tag settings on the Radarr/Sonarr pages.
- Dev mocks: fake-radarr gains tag + movie/editor (+ charset 400); new
  fake-sonarr mirrors it.
@enoch85 enoch85 force-pushed the feat/arr-tagging branch from 609c0f6 to f43e1d0 Compare June 23, 2026 22:24
Adds the write side of three feature requests — Maintainerr now applies *arr
tags as a side effect of its own state, via a shared best-effort
`ServarrTagService`:

- https://features.maintainerr.info/posts/8
- https://features.maintainerr.info/posts/125
- https://features.maintainerr.info/posts/81

Two triggers share the plumbing:

- Membership (A): while a `tagInArr` rule group's collection holds an item, the
  matching Radarr movie / Sonarr series carries a tag named after the group.
  Driven from the executor's per-run membership deltas, and reconciled on the
  enable/disable toggle and on rule-group deletion. Off the per-item eval loop.
- Exclusion (B): excluding an item applies a protective tag (default "dnd") to
  the matching *arr entity — covering both collection-scoped and global
  exclusions, across every exclude/un-exclude path. Radarr and Sonarr are
  configured independently (enable + label + opt-in removal-on-unexclude).
  Removal is conservative: opt-in, only the configured label, and never while
  another exclusion still protects the item.

Details:
- Shared helpers `ensureTag` (race-tolerant), `setMovieTags`/`setSeriesTags`
  (`movie|series/editor`, applyTags add/remove only — never replace).
- Tag labels normalized to the *arr charset `^[a-z0-9-]+$` — a single source of
  truth in @maintainerr/contracts, validated up front in the UI so the saved
  label is exactly the tag the instance receives (Radarr/Sonarr 400 otherwise).
- `Collection.tagInArr` + 6 per-service settings columns via a generated,
  end-to-end-verified migration; item→*arr resolution reuses the action-handler
  path and honours the #3125 transient/null contract.
- UI: a conditional "Tag this content" checkbox in the rule modal and per-service
  exclusion-tag settings on the Radarr/Sonarr pages.
- Dev mocks: `fake-radarr.mjs` gains `/tag` + `PUT /movie/editor` (+ the
  charset 400).
@enoch85 enoch85 force-pushed the feat/arr-tagging branch from 4142f49 to 2c5bcb1 Compare June 24, 2026 05:30
enoch85 added 3 commits June 24, 2026 18:04
- await the tagInArr toggle reconcile so the backfill completes before save returns
- clarify invalid exclusion-tag message (no leading/trailing/repeated hyphens)
- document multi-instance untag limitation and the strict 'show' AddModal gate
Matching columns can be reproduced by a hand-written ALTER TABLE ADD COLUMN;
only migration:generate emits the create-temporary-table rebuild, so assert
its presence as a stronger hand-waived-migration guard.
@enoch85 enoch85 merged commit ec8e45c into development Jun 24, 2026
13 checks passed
@enoch85 enoch85 deleted the feat/arr-tagging branch June 24, 2026 16:36
maintainerr-automation Bot added a commit that referenced this pull request Jun 25, 2026
* build(deps-dev): bump @eslint-react/eslint-plugin from 5.9.0 to 5.9.1 (#3148)

Bumps [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/plugins/eslint-plugin) from 5.9.0 to 5.9.1.
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v5.9.1/plugins/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 5.9.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump react-hook-form from 7.79.0 to 7.80.0 (#3149)

Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.79.0 to 7.80.0.
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](react-hook-form/react-hook-form@v7.79.0...v7.80.0)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.80.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump knip from 6.17.1 to 6.17.2 (#3151)

Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.17.1 to 6.17.2.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.17.2/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.17.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @eslint-react/eslint-plugin from 5.9.1 to 5.9.2 (#3156)

Bumps [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/plugins/eslint-plugin) from 5.9.1 to 5.9.2.
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v5.9.2/plugins/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 5.9.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @vitejs/plugin-react from 6.0.2 to 6.0.3 (#3157)

Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 6.0.2 to 6.0.3.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@6.0.3/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump vite from 8.0.16 to 8.1.0 (#3158)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 8.0.16 to 8.1.0.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@8.1.0/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 8.1.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @typescript-eslint/eslint-plugin (#3159)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.61.1 to 8.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.62.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @typescript-eslint/parser from 8.61.1 to 8.62.0 (#3160)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.61.1 to 8.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.62.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump globals from 17.6.0 to 17.7.0 (#3161)

Bumps [globals](https://github.com/sindresorhus/globals) from 17.6.0 to 17.7.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](sindresorhus/globals@v17.6.0...v17.7.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 17.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump knip from 6.17.2 to 6.18.0 (#3155)

Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.17.2 to 6.18.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.18.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.18.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump actions/checkout from 6 to 7 (#3150)

Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>

* build(deps): bump @tanstack/react-query from 5.101.0 to 5.101.1 (#3163)

Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.101.0 to 5.101.1.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/react-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/react-query@5.101.1/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.101.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump typescript-eslint from 8.61.1 to 8.62.0 (#3164)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.61.1 to 8.62.0.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.62.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.62.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump axios from 1.18.0 to 1.18.1 (#3165)

Bumps [axios](https://github.com/axios/axios) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.18.0...v1.18.1)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.18.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump knip from 6.18.0 to 6.20.0 (#3166)

Bumps [knip](https://github.com/webpro-nl/knip/tree/HEAD/packages/knip) from 6.18.0 to 6.20.0.
- [Release notes](https://github.com/webpro-nl/knip/releases)
- [Commits](https://github.com/webpro-nl/knip/commits/knip@6.20.0/packages/knip)

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.20.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @tanstack/eslint-plugin-query (#3167)

Bumps [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) from 5.101.0 to 5.101.1.
- [Release notes](https://github.com/TanStack/query/releases)
- [Changelog](https://github.com/TanStack/query/blob/main/packages/eslint-plugin-query/CHANGELOG.md)
- [Commits](https://github.com/TanStack/query/commits/@tanstack/eslint-plugin-query@5.101.1/packages/eslint-plugin-query)

---
updated-dependencies:
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.101.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* feat: write Sonarr/Radarr tags from Maintainerr collections & exclusions (#3162)

* feat: write Sonarr/Radarr tags from Maintainerr collections & exclusions

Adds the write side of three feature requests — Maintainerr now applies *arr
tags as a side effect of its own state, via a shared best-effort
`ServarrTagService`:

- https://features.maintainerr.info/posts/8
- https://features.maintainerr.info/posts/125
- https://features.maintainerr.info/posts/81

Two triggers share the plumbing:

- Membership (A): while a `tagInArr` rule group's collection holds an item, the
  matching Radarr movie / Sonarr series carries a tag named after the group.
  Driven from the executor's per-run membership deltas, and reconciled on the
  enable/disable toggle and on rule-group deletion. Off the per-item eval loop.
- Exclusion (B): excluding an item applies a protective tag (default "dnd") to
  the matching *arr entity — covering both collection-scoped and global
  exclusions, across every exclude/un-exclude path. Radarr and Sonarr are
  configured independently (enable + label + opt-in removal-on-unexclude).
  Removal is conservative: opt-in, only the configured label, and never while
  another exclusion still protects the item.

Details:
- Shared helpers `ensureTag` (race-tolerant), `setMovieTags`/`setSeriesTags`
  (`movie|series/editor`, applyTags add/remove only — never replace).
- Tag labels normalized to the *arr charset `^[a-z0-9-]+$` — a single source of
  truth in @maintainerr/contracts, validated up front in the UI so the saved
  label is exactly the tag the instance receives (Radarr/Sonarr 400 otherwise).
- `Collection.tagInArr` + 6 per-service settings columns via a generated,
  end-to-end-verified migration; item→*arr resolution reuses the action-handler
  path and honours the #3125 transient/null contract.
- UI: a conditional "Tag this content" checkbox in the rule modal and per-service
  exclusion-tag settings on the Radarr/Sonarr pages.
- Dev mocks: fake-radarr gains tag + movie/editor (+ charset 400); new
  fake-sonarr mirrors it.

* refactor(arr-tagging): address review feedback

- await the tagInArr toggle reconcile so the backfill completes before save returns
- clarify invalid exclusion-tag message (no leading/trailing/repeated hyphens)
- document multi-instance untag limitation and the strict 'show' AddModal gate

* test(migrations): assert generated SQLite temporary-table rebuild

Matching columns can be reproduced by a hand-written ALTER TABLE ADD COLUMN;
only migration:generate emits the create-temporary-table rebuild, so assert
its presence as a stronger hand-waived-migration guard.

* fix: clean collection logs without TypeORMError on undefined ruleGroup (#3147) (#3168)

* fix: clean collection logs without TypeORMError on undefined ruleGroup (#3147)

* chore: format

* fix: don't mutate shared Sonarr season array in part_of_latest_season (#3153) (#3169)

getLastAiredOrCurrentlyAiringSeason reversed showResponse.seasons in
place. The series is memoized per run via ArrLookupCache, so every season
of a show shares one seasons array; a full run resolves them concurrently
(RULE_EVALUATION_CONCURRENCY) and the in-place reverse corrupted season
order mid-iteration, collapsing the latest aired season to an earlier
one. part_of_latest_season then returned false for the latest season.
Test Media evaluates a single item with no shared cache, so it stayed
correct and the two paths disagreed.

Reverse a copy instead, and correct the return type to
SonarrSeason | undefined.

Also adds dev tooling to exercise the Sonarr season path end-to-end:
tools/dev/fake-sonarr.mjs, a four-season show in fake-plex.mjs, and a
seeded part_of_latest_season == False season group.

* build(deps-dev): bump @swc/core from 1.15.41 to 1.15.43 (#3171)

Bumps [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core) from 1.15.41 to 1.15.43.
- [Release notes](https://github.com/swc-project/swc/releases)
- [Changelog](https://github.com/swc-project/swc/blob/main/CHANGELOG.md)
- [Commits](https://github.com/swc-project/swc/commits/v1.15.43/packages/core)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.43
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump turbo from 2.9.18 to 2.10.0 (#3172)

Bumps [turbo](https://github.com/vercel/turborepo) from 2.9.18 to 2.10.0.
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md)
- [Commits](vercel/turborepo@v2.9.18...v2.10.0)

---
updated-dependencies:
- dependency-name: turbo
  dependency-version: 2.10.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps-dev): bump @eslint-react/eslint-plugin from 5.9.2 to 5.9.3 (#3173)

Bumps [@eslint-react/eslint-plugin](https://github.com/Rel1cx/eslint-react/tree/HEAD/plugins/eslint-plugin) from 5.9.2 to 5.9.3.
- [Release notes](https://github.com/Rel1cx/eslint-react/releases)
- [Changelog](https://github.com/Rel1cx/eslint-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/Rel1cx/eslint-react/commits/v5.9.3/plugins/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@eslint-react/eslint-plugin"
  dependency-version: 5.9.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump rolldown from 1.1.2 to 1.1.3 (#3174)

Bumps [rolldown](https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown) from 1.1.2 to 1.1.3.
- [Release notes](https://github.com/rolldown/rolldown/releases)
- [Changelog](https://github.com/rolldown/rolldown/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rolldown/rolldown/commits/v1.1.3/packages/rolldown)

---
updated-dependencies:
- dependency-name: rolldown
  dependency-version: 1.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix: bulk-prefetch Seerr requests for rule evaluation (#3152) (#3170)

* fix: bulk-prefetch Seerr requests for rule evaluation (#3152)

Rules seeded on a Seerr property (e.g. "Seerr - Requested") matched
almost nothing on large libraries. The getter made a per-item
GET /movie|/tv call per item; under a whole-library run those calls
rate-limited, the getter returned a value the comparator skipped, and
Seerr-seeded rules silently degraded to near-zero matches.

- SeerrApiService now fetches all requests in one paginated /request
  sweep and builds a run-scoped index grouped by media.tmdbId, with
  in-flight dedup, cached in a dedicated non-persistent cache that is
  flushed (and rebuilt) per rule-group run. Page size is 100 (Seerr
  imposes no take cap and OFFSET re-scans), reducing round-trips.
- SeerrGetterService reads that index for every request-derived property
  (isRequested, amountRequested, requestDate, addUser, approvalDate,
  mediaAddedAt) at movie and season/episode level.
- A failed sweep yields undefined (transient — the comparator protects
  the item), never null; releaseDate keeps its per-item fallback.
- Add tools/dev/fake-seerr.mjs and a shared scale-library fixture, and
  seed Seerr rule groups, for end-to-end testing.
- Consolidate request-service naming to "Seerr" across code comments, UI
  strings and docs (the legacy /overseerr and /jellyseerr route aliases
  stay for backward compatibility).

* fix: order Seerr requestDate oldest-first and drop no-op sort param (#3152)

Review follow-up to #3152:
- buildRequestIndex sorts each title's requests createdAt-ascending, so
  requestDate (and addUser/season order) returns the first request, matching
  the pre-#3152 getMovie ordering rather than the newest re-request.
- Drop the `sort=added` query param: Seerr has no `added` sort (it falls back
  to request.id DESC); the index now normalises order itself.
- getRequestsForMedia returns a deep copy (lodash cloneDeep) so callers cannot
  mutate the cached run-scoped index (useClones is off); cloneDeep over
  structuredClone so an unexpected non-cloneable value can't throw.
- Seed the remaining Seerr property rule groups and add focused tests.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.16.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant