Skip to content

fix(collections): create collections empty, then batch-add (avoid HTTP 414)#3001

Merged
enoch85 merged 1 commit into
developmentfrom
fix/collection-create-414
May 27, 2026
Merged

fix(collections): create collections empty, then batch-add (avoid HTTP 414)#3001
enoch85 merged 1 commit into
developmentfrom
fix/collection-create-414

Conversation

@enoch85

@enoch85 enoch85 commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Creating a new collection from a large rule failed with Failed to create collection / HTTP 414 (URI Too Long). The BULK_COLLECTION_CREATE path added in #2911 seeds every matched item id into the create request's query string, and item ids can only travel in the query string on Plex/Jellyfin/Emby (no request body), so a large match overflows the URL.

  • Revert to the pre-feat(media-server): add Emby as a third supported media server #2911 / 2.x behaviour: create the collection empty, then populate it via the existing batched add path (addBatchToCollection), uniformly for all media servers.
  • Remove the now-unused BULK_COLLECTION_CREATE capability and the initialItemIds plumbing.
  • Batching, chunk sizes, collection caching, and rule-evaluation performance are untouched.

…P 414)

Creating a new collection from a large rule seeded every item id into the
create request's query string (BULK_COLLECTION_CREATE, #2911), overflowing
the URL and failing with "Failed to create collection" / HTTP 414 URI Too
Long. Item ids can only travel in the query string on Plex/Jellyfin/Emby
(no request body), so the seed is unbounded.

Revert to the pre-#2911 / 2.x behaviour: create the collection empty and
populate it via the existing batched add path (addBatchToCollection), for
all media servers. Removes the now-unused BULK_COLLECTION_CREATE capability
and the initialItemIds plumbing.
@enoch85 enoch85 merged commit 9f7271e into development May 27, 2026
14 checks passed
@enoch85 enoch85 deleted the fix/collection-create-414 branch May 27, 2026 21:18
maintainerr-automation Bot added a commit that referenced this pull request May 27, 2026
* feat(ui): migrate to Tailwind CSS v4 (CSS-first) + dev DB seed

- CSS-first theme: @theme + @plugin in globals.css; remove tailwind.config.js
- Use @tailwindcss/vite; drop postcss, autoprefixer, @tailwindcss/aspect-ratio
- Fix v4 regressions: @tailwindcss/forms .form-input collision (strategy: base),
  dead bg-opacity-* -> slash syntax, checkbox focus ring + single .checkbox class,
  translucent backgrounds, unlayered prose colors
- Standardize Plex checkboxes, Manual badge, and input-action button states;
  delay the overlay-schedule loading hint
- Add dev/seed-db.mjs to seed collections/rules/storage for local testing

* feat(ui): adopt Tailwind v4.x features

- color-scheme: dark so native controls render dark
- container query for the media-card grid (sizes to container, not viewport)
- field-sizing-content on the rule description and overlay text textareas
- text-shadow on poster-overlaid title/year/summary
- drop the v3->v4 border-color compat shim (one implicit border made explicit)

Closes #2970

* refactor(ui): single source for form field styling

- Move the field base style into the global input/select/textarea rule so it
  is the single source of truth; Forms/Input and Forms/Select keep deltas only
  (adornments, join, error, select chevron). One edit now restyles every field.
- SearchBar uses the shared field style (drops the pill override; fixes the
  clipped icon and the blue focus ring).
- Include input[type=search] in the shared rule; maintainerr focus app-wide.
- Keep the v4 field-sizing auto-grow on the rule description + overlay text,
  with min-h so the textareas no longer collapse when empty.
- Uniform rule-form row spacing; blank the log filter's default option (was '-').

* Document Codex MCP config and media integration coverage (#2974)

* perf: cache hygiene for external API and metadata responses (#2972)

- ExternalApiService: skip caching Buffer/null/non-object responses, so binary
  blobs and empty bodies can't poison the shared NodeCaches.
- cache: exempt tmdb/tvdb (immutable external metadata, 6h TTL) from the per-run
  flushAll(), so rule runs don't needlessly re-fetch them.
- tmdb metadata provider: ignore non-object records.

* fix(release): emit changelog version header and correct breaking-change classification

- Pass NEXT_RELEASE_VERSION/LAST_RELEASE_GITTAG/NEXT_RELEASE_GITHEAD into
  the notes generator via generateNotesCmd. semantic-release does not export
  these to plugin commands, so buildHeader() always saw an empty version and
  emitted no '# [x.y.z](compare) (date)' header for v3.11.2..v3.12.1.
- Tell the model to net out the commit range: an identifier introduced and
  renamed/removed within one release never shipped, so it is not breaking.
- Backfill the missing v3.11.2/v3.12.0/v3.12.1 headers and drop the false
  'Breaking Changes' (an internal settings-service rename) from v3.12.1.

* fix: OR rule sections incorrectly evaluated as AND due to operator coercion (#2971)

* fix: treat unset rule section operator as OR instead of AND

An unset section operator (null) was coerced to AND by `+null === 0`, so
sections meant to OR together were evaluated as AND. The coercion is now
null-guarded, resolving an unset operator to OR — consistent with how a
null operator is already treated within a section.

Also require an explicit operator on non-first rules in the rule editor,
backfill existing unset operators to OR, and stop dropping a numeric AND
section operator on YAML export.

Co-Authored-By: James Nobes <github@stormshaker.com>

* fix(rules): normalize legacy section operators

---------

Co-authored-by: enoch85 <mailto@danielhansson.nu>
Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com>

* fix(rules): robust community/YAML rule import across media servers (#2976)

Imports could silently break when a rule's properties don't all map to
the configured media server:

- Migrate firstVal and lastVal independently by each field's own app, so a
  media-server property compared against a Seerr/Tautulli value is no longer
  stranded on the source server (Plex/Emby) and rendered blank. Covers
  Plex/Jellyfin/Emby.
- Surface rules dropped on import (no equivalent on the target server) via a
  toast on both the community and YAML paths; keep the warn log.
- YAML export no longer writes `App.undefined` for an unknown property, and
  import skips an unresolved rule (reported as a skipped count) instead of
  rejecting the whole document.
- validateRule returns a clean status instead of throwing on a missing
  application/property; rule-group create no longer fails when notifications
  is omitted.

* chore(dev): add offline mock Jellyfin server

Dev-only HTTP stub answering the Jellyfin endpoints the server's adapter
calls (connection, users, libraries, item metadata, item images). Pairs
with dev/seed-db.mjs so the editor library list, rule-group save,
collection grids and rule evaluation work without a real Jellyfin.
Invented data only; not referenced by application code.

* feat(rules): add Plex "Amount of episodes marked as watched" rule (#2975)

Adds a show/season rule property (sw_markedWatchedEpisodes) backed by
Plex's viewedLeafCount (watched state) instead of play history.

Unlike "Amount of watched episodes" (sw_viewedEpisodes), which counts
episodes that have a play-history entry, this counts episodes Plex
considers watched -- including those manually marked as watched, since
Plex sets viewedLeafCount for manual marks but writes no play history.

* Refactor shared media getter rule helpers (#2922)


Co-authored-by: enoch85 <mailto@danielhansson.nu>

* feat(rules): add Streamystats watchlist rule properties for Jellyfin (#2977)

Adds a Jellyfin-only Streamystats rule Application (the Jellyfin analog of
Tautulli) with two watchlist-backed properties: "Is in a watchlist" and
"[list] In watchlist of (username)". Upstream Streamystats now accepts
Jellyfin API-key auth on its watchlist endpoints, so Maintainerr can reach
them via the MediaBrowser token scheme.

Only public Streamystats watchlists are visible to the API key. The
membership snapshot is cached in the shared Streamystats NodeCache (flushed
between rule-group runs). The Application is removed from the rule constants
unless Streamystats is configured, and the UI hides it on non-Jellyfin
servers. Username resolution failures surface as a transient skip rather
than an empty list.

* fix: Test Media search styling (Tailwind v4) + unary-rule comparison crash (#2978)

* docs: unify agent instructions into a single index, add handoff notes (#2980)

- AGENTS.md is the single documentation index; the Claude, Copilot, Cursor,
  and Codex entrypoints route to it and name the standing rules directly so
  they can't be missed.
- Split standing rules (read every session) from task-specific docs (read on
  demand); scope release-review's Copilot applyTo to release artifacts so it
  no longer loads on every interaction.
- Add project-notes.instructions.md (non-obvious project knowledge and
  conventions for handoff) and README_AGENTS.md (the wiring map).
- Move dev mocks + DB seed to tools/dev/ (fix seed-db repo-root resolution);
  add the seeded-DB + Playwright step to the release-review checklist.

* docs: strengthen migration rule and fix release-review SQL guidance

- implementation rule 8: demand TypeORM + typeorm_instructions.txt workflow,
  require migrations be tested end-to-end before considered working
- release-review: fix broken typeorm_instructions.txt path (root, not
  .github/instructions/); clarify that generated schema migrations may contain
  raw DDL while hand-written data migrations must use QueryBuilder

* docs: add YAML import/export + community-rule testing guide to project notes

How to exercise both import paths (YAML encode/decode vs community-rule
/migrate), why they don't share code, and quick checks for the #2971/#2976
behaviours and v2.0.0+ community-rule import.

* build(deps): bump the nestjs group with 4 updates (#2981)

Bumps the nestjs group with 4 updates: [@nestjs/common](https://github.com/nestjs/nest/tree/HEAD/packages/common), [@nestjs/core](https://github.com/nestjs/nest/tree/HEAD/packages/core), [@nestjs/platform-express](https://github.com/nestjs/nest/tree/HEAD/packages/platform-express) and [@nestjs/testing](https://github.com/nestjs/nest/tree/HEAD/packages/testing).


Updates `@nestjs/common` from 11.1.23 to 11.1.24
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.24/packages/common)

Updates `@nestjs/core` from 11.1.23 to 11.1.24
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.24/packages/core)

Updates `@nestjs/platform-express` from 11.1.23 to 11.1.24
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.24/packages/platform-express)

Updates `@nestjs/testing` from 11.1.23 to 11.1.24
- [Release notes](https://github.com/nestjs/nest/releases)
- [Commits](https://github.com/nestjs/nest/commits/v11.1.24/packages/testing)

---
updated-dependencies:
- dependency-name: "@nestjs/common"
  dependency-version: 11.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/core"
  dependency-version: 11.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/platform-express"
  dependency-version: 11.1.24
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: nestjs
- dependency-name: "@nestjs/testing"
  dependency-version: 11.1.24
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: nestjs
...

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

* build(deps): bump date-fns from 4.2.1 to 4.3.0 (#2982)

Bumps [date-fns](https://github.com/date-fns/date-fns) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/date-fns/date-fns/releases)
- [Commits](date-fns/date-fns@v4.2.1...v4.3.0)

---
updated-dependencies:
- dependency-name: date-fns
  dependency-version: 4.3.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 typescript-eslint from 8.59.4 to 8.60.0 (#2984)

Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.59.4 to 8.60.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.60.0/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: typescript-eslint
  dependency-version: 8.60.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 @swc/core from 1.15.33 to 1.15.40 (#2985)

Bumps [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core) from 1.15.33 to 1.15.40.
- [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.40/packages/core)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.40
  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 knip from 6.14.1 to 6.14.2 (#2987)

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

---
updated-dependencies:
- dependency-name: knip
  dependency-version: 6.14.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>

* fix(rules): harden rule import (TEXT_LIST YAML decode + community-import operator backfill) (#2986)

* fix(rules): resolve TEXT_LIST custom value on YAML import

The encoder serialises a custom value's type as the RuleType humanName, so
TEXT_LIST becomes "text list" (with a space). The decoder's switch only matched
'TEXT_LIST', so the spaced key fell through, left ruleType undefined, and threw
on the return's .toString() — failing the entire YAML import. Normalise spaces
to underscores before matching.

* style: run yarn format on rules constants spec

Agent-Logs-Url: https://github.com/Maintainerr/Maintainerr/sessions/38e8bea3-daf7-4de0-aeac-a61c2d3e5fa2

Co-authored-by: enoch85 <4511254+enoch85@users.noreply.github.com>

* fix(rules): backfill unset within-section operator on community import

migrateImportedRuleDtos remapped apps and reasserted section-boundary operators
but left within-section unset operators as null. A pre-explicit-operator
community rule (e.g. one authored before #2971) could therefore carry a null
operator on a non-first rule, which the new "operator is required for every rule
after the first" save validation rejects on import. Backfill those to OR after
the boundary pass — the same default the comparator and the
NormalizeRuleSectionOperators migration apply.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>

* build(deps): bump typeorm from 0.3.29 to 1.0.0 (#2983)

* build(deps): bump typeorm from 0.3.29 to 1.0.0

Bumps [typeorm](https://github.com/typeorm/typeorm) from 0.3.29 to 1.0.0.
- [Release notes](https://github.com/typeorm/typeorm/releases)
- [Changelog](https://github.com/typeorm/typeorm/blob/master/CHANGELOG.md)
- [Commits](typeorm/typeorm@0.3.29...1.0.0)

---
updated-dependencies:
- dependency-name: typeorm
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix(server): use object-form find relations for typeorm 1.0.0

TypeORM 1.0.0 removed string-based relations from find methods (#12215).
Convert the six affected findOne/find calls to the object form, which is
valid in both 0.3.x and 1.0.0.

---------

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-dev): bump @tanstack/eslint-plugin-query (#2992)

Bumps [@tanstack/eslint-plugin-query](https://github.com/TanStack/query/tree/HEAD/packages/eslint-plugin-query) from 5.100.11 to 5.100.14.
- [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.100.14/packages/eslint-plugin-query)

---
updated-dependencies:
- dependency-name: "@tanstack/eslint-plugin-query"
  dependency-version: 5.100.14
  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.76.0 to 7.76.1 (#2993)

Bumps [react-hook-form](https://github.com/react-hook-form/react-hook-form) from 7.76.0 to 7.76.1.
- [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.76.0...v7.76.1)

---
updated-dependencies:
- dependency-name: react-hook-form
  dependency-version: 7.76.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): bump nodemailer from 8.0.8 to 8.0.9 (#2994)

Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.8 to 8.0.9.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](nodemailer/nodemailer@v8.0.8...v8.0.9)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.9
  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): bump @tanstack/react-query from 5.100.11 to 5.100.14 (#2995)

Bumps [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) from 5.100.11 to 5.100.14.
- [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.100.14/packages/react-query)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.100.14
  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(notifications): omit empty Discord embed thumbnail

Discord rejects webhook payloads with 400 Invalid Form Body when an
embed contains an empty thumbnail object. Imageless notifications (e.g.
media-removed / rule-handling events) always sent thumbnail.url=undefined,
which serialized to an empty thumbnail and silently failed. Only attach
the thumbnail when an image is present.

* build(deps-dev): bump turbo from 2.9.14 to 2.9.15 (#2997)

Bumps [turbo](https://github.com/vercel/turborepo) from 2.9.14 to 2.9.15.
- [Release notes](https://github.com/vercel/turborepo/releases)
- [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md)
- [Commits](vercel/turborepo@v2.9.14...v2.9.15)

---
updated-dependencies:
- dependency-name: turbo
  dependency-version: 2.9.15
  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 @typescript-eslint/parser from 8.59.4 to 8.60.0 (#2998)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.59.4 to 8.60.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.60.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.60.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 rolldown from 1.0.2 to 1.0.3 (#2999)

Bumps [rolldown](https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown) from 1.0.2 to 1.0.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.0.3/packages/rolldown)

---
updated-dependencies:
- dependency-name: rolldown
  dependency-version: 1.0.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>

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

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.59.4 to 8.60.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.60.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.60.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>

* fix(collections): create collections empty, then batch-add (avoid HTTP 414) (#3001)

Creating a new collection from a large rule seeded every item id into the
create request's query string (BULK_COLLECTION_CREATE, #2911), overflowing
the URL and failing with "Failed to create collection" / HTTP 414 URI Too
Long. Item ids can only travel in the query string on Plex/Jellyfin/Emby
(no request body), so the seed is unbounded.

Revert to the pre-#2911 / 2.x behaviour: create the collection empty and
populate it via the existing batched add path (addBatchToCollection), for
all media servers. Removes the now-unused BULK_COLLECTION_CREATE capability
and the initialItemIds plumbing.

* fix(rules): scope exclusions to their rule group under TypeORM 1.0 (#2991)

* fix(rules): scope exclusions to their rule group under TypeORM 1.0

Adopt TypeORM 1.0 where-clause null semantics (default throw; IsNull() to
match NULL) instead of 0.3.x's silent-ignore footgun, fixing latent exclusion
bugs surfaced by the typeorm 1.0.0 bump (#2983):

- getExclusions: a group-scoped exclusion no longer leaks into other rule
  groups (and no longer returns duplicates); global exclusions still apply
  everywhere.
- setExclusion: global exclusions subsume scoped ones — an item is global or
  scoped, never both.
- removeExclusion: removing a global exclusion no longer writes a spurious
  collection-log entry; logs media + ownership.
- getCollection: a null/blank id returns undefined instead of an arbitrary
  first collection.

Bumps engines.node to TypeORM 1.0's floor and adds getter-property and
exclusion-scoping regression coverage.

* feat(ui): warn before a global exclusion replaces rule-group exclusions

When adding a global exclusion for an item that already has rule-group
exclusions, show a confirmation listing each "<item> — <rule group>" (the
group links to its collection, reusing the backdrop's maintainerr-status data
so labels/links match and stay fresh) before proceeding, since going global
removes those scoped exclusions. Only triggers on the Add action, never on
Remove.

* test(ui): cover the global-exclusion warning in AddModal

* fix(ui): clarify Add/Remove action labels in the media modal

* fix(ui): don't block global exclusion when warning prefetch fails

* fix(plex): don't drop auth when plex.tv is unreachable (#2996)

* fix(plex): don't drop auth when plex.tv is unreachable

A plex.tv timeout was reported as an invalid token, so the Plex settings
page declared stored credentials invalid and demanded re-authentication
even when the saved token was fine. Distinguish a genuine rejection
(401/403) from a connectivity failure: on unreachable, keep the account
authenticated, warn, and auto-retry until plex.tv responds.

* fix(ui): clear stale Plex auth warning

* fix(ui): navigate client-side from global-exclusion warning links

The rule-group links in the AddModal global-exclusion warning used a raw
<a href>, which forced a full page reload and ignored the router basename
(404 under BASE_PATH subpath installs). Use useNavigate instead, and clear
the maintainerr-status cache + invalidate collection queries first so the
destination still fetches fresh — what the full reload did implicitly.

* test(server): add 1.x upgrade-path and all-rules comparator regression coverage

- upgrade-from-1x.spec.ts: reconstructs the v1.7.1 schema, seeds 1.x-era
  data (rules with null operators), applies every post-1.7.1 migration and
  asserts the operator backfill + late-migration schema survive.
- rules-test-matrix.e2e.ts: generated matrix covering every RuleType x
  RulePossibility (value/missing/present/absent), for cross-version diffing.

* docs: correct Yarn linker note in project-notes (node-modules, not PnP)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: maintainerr-automation[bot] <261505141+maintainerr-automation[bot]@users.noreply.github.com>
Co-authored-by: enoch85 <mailto@danielhansson.nu>
Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com>
Co-authored-by: James Nobes <github@stormshaker.com>
Co-authored-by: CampbellMG <40409896+CampbellMG@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
@maintainerr-automation

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.13.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

enoch85 added a commit that referenced this pull request Jun 13, 2026
…add (#3094)

* fix(collections): self-heal empty Plex collections that reject every add

When a Plex collection record goes stale/corrupt it rejects all item adds
with 400 while reads still succeed. Since #3001 collections are created
empty, so such a record sticks forever: shared collections were never
deleted (#2766), every run retried the same adds, and the empty collection
stayed invisible in Plex while 'added' notifications kept firing.

- Restore the delete-and-recreate heal (f0dcea7) for this case: when a
  media server add attempt is rejected for every item and a live read
  confirms the automatic collection is still empty, delete it and clear
  the link so the next pass recreates it fresh. Plex-only, matching the
  existing empty-collection cleanup gate. Shared collections heal too:
  an empty collection holds nothing a sibling rule group could lose.
- Guard against churn: a collection heals once per process; a second
  total rejection without an accepted add in between logs an error
  instead of looping delete/recreate every run.
- Emit CollectionMedia_Added only for items the media server accepted,
  so failed adds no longer notify as successes.
- Unwrap the lib/plexApi error wrapper in buildCollectionMutationFailure
  so Plex's 400 response body (the rejection reason) reaches the logs.
- Drop a duplicated reconcileSharedManualCollectionState call.

* fix(collections): only notify added media whose local membership was persisted

* test(collections): pin stale-link clearing that sibling heal recovery relies on
enoch85 added a commit that referenced this pull request Jun 13, 2026
…3075) (#3097)

Emby's create-collection endpoint throws HTTP 500 ("Sequence contains no
elements" in CollectionManager) when asked to create an empty collection under
a library folder. Since #3001 (v3.13) switched all servers to create-empty
then batch-add (to avoid a 414 from seeding every id), Emby rejected every
create, so no rule action could complete and nothing was deleted.

- Add optional initialItemId to CreateCollectionParams; the Emby adapter sends
  it as Ids on create so the collection is created with one member. Plex and
  Jellyfin create empty and ignore it.
- One id keeps the create request well under the URL length limit that the
  all-ids create hit (#3001); the rest are added via addBatchToCollection
  (re-adding the seed there is an idempotent no-op).
- fake-emby mock now 500s on an empty create, matching the real server.
maintainerr-automation Bot added a commit that referenced this pull request Jun 13, 2026
* build(deps-dev): bump semantic-release from 25.0.3 to 25.0.5 (#3078)

Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 25.0.3 to 25.0.5.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](semantic-release/semantic-release@v25.0.3...v25.0.5)

---
updated-dependencies:
- dependency-name: semantic-release
  dependency-version: 25.0.5
  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 @tailwindcss/typography from 0.5.19 to 0.5.20 (#3079)

Bumps [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) from 0.5.19 to 0.5.20.
- [Release notes](https://github.com/tailwindlabs/tailwindcss-typography/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)
- [Commits](tailwindlabs/tailwindcss-typography@v0.5.19...v0.5.20)

---
updated-dependencies:
- dependency-name: "@tailwindcss/typography"
  dependency-version: 0.5.20
  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-konva from 19.2.4 to 19.2.5 (#3080)

Bumps [react-konva](https://github.com/konvajs/react-konva) from 19.2.4 to 19.2.5.
- [Release notes](https://github.com/konvajs/react-konva/releases)
- [Commits](https://github.com/konvajs/react-konva/commits)

---
updated-dependencies:
- dependency-name: react-konva
  dependency-version: 19.2.5
  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 @swc/core from 1.15.40 to 1.15.41 (#3081)

Bumps [@swc/core](https://github.com/swc-project/swc/tree/HEAD/packages/core) from 1.15.40 to 1.15.41.
- [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.41/packages/core)

---
updated-dependencies:
- dependency-name: "@swc/core"
  dependency-version: 1.15.41
  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 nodemailer from 8.0.10 to 8.0.11 (#3086)

Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.10 to 8.0.11.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](nodemailer/nodemailer@v8.0.10...v8.0.11)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.11
  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): bump rolldown from 1.1.0 to 1.1.1 (#3087)

Bumps [rolldown](https://github.com/rolldown/rolldown/tree/HEAD/packages/rolldown) from 1.1.0 to 1.1.1.
- [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.1/packages/rolldown)

---
updated-dependencies:
- dependency-name: rolldown
  dependency-version: 1.1.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 prettier from 3.8.3 to 3.8.4 (#3088)

Bumps [prettier](https://github.com/prettier/prettier) from 3.8.3 to 3.8.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.8.3...3.8.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.8.4
  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 @types/node from 22.19.20 to 22.19.21 (#3089)

Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 22.19.20 to 22.19.21.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 22.19.21
  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 @typescript-eslint/parser from 8.60.1 to 8.61.0 (#3090)

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.60.1 to 8.61.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.61.0/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.61.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 turbo from 2.9.16 to 2.9.18 (#3092)

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

---
updated-dependencies:
- dependency-name: turbo
  dependency-version: 2.9.18
  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 sharp from 0.34.5 to 0.35.1 (#3093)

Bumps [sharp](https://github.com/lovell/sharp) from 0.34.5 to 0.35.1.
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](lovell/sharp@v0.34.5...v0.35.1)

---
updated-dependencies:
- dependency-name: sharp
  dependency-version: 0.35.1
  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 @typescript-eslint/eslint-plugin (#3091)

Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.60.1 to 8.61.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.61.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.61.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>

* fix(collections): self-heal empty Plex collections that reject every add (#3094)

* fix(collections): self-heal empty Plex collections that reject every add

When a Plex collection record goes stale/corrupt it rejects all item adds
with 400 while reads still succeed. Since #3001 collections are created
empty, so such a record sticks forever: shared collections were never
deleted (#2766), every run retried the same adds, and the empty collection
stayed invisible in Plex while 'added' notifications kept firing.

- Restore the delete-and-recreate heal (f0dcea7) for this case: when a
  media server add attempt is rejected for every item and a live read
  confirms the automatic collection is still empty, delete it and clear
  the link so the next pass recreates it fresh. Plex-only, matching the
  existing empty-collection cleanup gate. Shared collections heal too:
  an empty collection holds nothing a sibling rule group could lose.
- Guard against churn: a collection heals once per process; a second
  total rejection without an accepted add in between logs an error
  instead of looping delete/recreate every run.
- Emit CollectionMedia_Added only for items the media server accepted,
  so failed adds no longer notify as successes.
- Unwrap the lib/plexApi error wrapper in buildCollectionMutationFailure
  so Plex's 400 response body (the rejection reason) reaches the logs.
- Drop a duplicated reconcileSharedManualCollectionState call.

* fix(collections): only notify added media whose local membership was persisted

* test(collections): pin stale-link clearing that sibling heal recovery relies on

* fix(radarr): treat already-excluded 400 as success when adding import-list exclusions (#3084) (#3096)

Since Radarr v5.26.2, RestController.OnActionExecuting unpacks and validates
IEnumerable bodies, so POST /exclusions/bulk now runs the uniqueness validator
and returns HTTP 400 ("This exclusion has already been added") on a re-add —
before the request reaches Radarr's server-side de-dup. That failed the whole
UNMONITOR/UNMONITOR_DELETE_ALL action on every re-run, so items never left the
collection (the singular endpoint always validated, so neither avoids it).

- Adding the exclusion is best-effort; an already-excluded 400 means the goal
  is already met, so treat it as success instead of failing the action whose
  unmonitor/delete already ran. Other failures still fail.
- Keep the bulk endpoint (server de-dup where reachable, single-movie array).
- Route through the shared post() client via a new opt-in { rethrow } so the
  caller can read the HTTP status, keeping the request on the one HTTP client
  the rest of servarr uses.
- Make the fake-radarr dev mock 400 on a duplicate for both endpoints, matching
  Radarr v5.26.2+.

* fix(emby): create collections with an initial item to avoid HTTP 500 (#3075) (#3097)

Emby's create-collection endpoint throws HTTP 500 ("Sequence contains no
elements" in CollectionManager) when asked to create an empty collection under
a library folder. Since #3001 (v3.13) switched all servers to create-empty
then batch-add (to avoid a 414 from seeding every id), Emby rejected every
create, so no rule action could complete and nothing was deleted.

- Add optional initialItemId to CreateCollectionParams; the Emby adapter sends
  it as Ids on create so the collection is created with one member. Plex and
  Jellyfin create empty and ignore it.
- One id keeps the create request well under the URL length limit that the
  all-ids create hit (#3001); the rest are added via addBatchToCollection
  (re-adding the seed there is an idempotent no-op).
- fake-emby mock now 500s on an empty create, matching the real server.

* fix(collections): skip remote create for empty collections (Emby) (#3075) (#3098)

createCollectionWithChildren is reachable via POST /api/collections with no
media (the body's media is optional), and it always created the remote
collection. With no items that means an empty remote create — pointless on
every server and a hard HTTP 500 on Emby, the same failure #3075 fixed for the
item-bearing paths, just through a narrower route.

- Create the DB row only when there are no items; the remote collection is
  created lazily on the first add, which seeds it with an item. Server-agnostic
  (no per-server branching in the shared layer).
- Tests: assert the singular initialItemId seed is passed on create (the prior
  assertions guarded the removed plural initialItemIds, so they passed
  vacuously), refresh the stale "create empty" comments, and add an empty-media
  case asserting no remote create.

* fix(radarr): only swallow the "already excluded" exclusion 400 (#3084) (#3099)

The exclusion add treated every HTTP 400 from /exclusions/bulk as "already
excluded". Radarr's validator also enforces non-empty tmdbId/title and a
non-negative year, so a non-duplicate validation 400 was misreported as
success — leaving the movie unmonitored/deleted but not actually import-excluded.

Inspect the 400 body and only treat the uniqueness failure ("This exclusion has
already been added") as success; any other validation 400 stays a failure and
is surfaced.

* fix(metadata): resolve movie/show ids from the item, not its parent (#3065) (#3100)

getHierarchyResolutionItem walked up to item.grandparentId ?? item.parentId
for any item with a parent. That's right for episodes/seasons (resolve up to
the show) but wrong for movies: on Emby/Jellyfin a movie's parentId points at
an id-less library/container folder, so the movie's own provider ids were
discarded → 0 lookup candidates → every Radarr movie action failed with
"Couldn't resolve any supported external IDs". The matching path was unaffected,
so collections filled but no action could run.

Switch on item.type (never parentId presence), matching resolveShowIdsForImage:
only episodes/seasons resolve from a parent; movies and shows use their own ids.
Plex is unchanged (its top-level movies already had no parent).

* test(overlays): raise timeout for sharp-based render tests to avoid CI flake (#3101)

The OverlayRenderService render tests do real sharp image processing and
TrueType font loading; the native cold-start can exceed Jest's 5s default on a
slow/cold CI runner (the whole suite is ~1.5s locally), intermittently failing
with "Exceeded timeout of 5000 ms". Raise the suite timeout to 15s.

* fix(rules): sw_lastWatched returns null for never-watched shows on Plex (#3083) (#3102)

The Plex sw_lastWatched getter ("Newest episode view date") guarded the date
branch with a truthy check on watchHistory. getWatchHistory returns [] for a
confirmed-empty history (it throws on a real outage), and [] is truthy — so for
a never-watched show it read viewedAt off watchHistory[0] (undefined) and threw
TypeError. The outer catch surfaced that as undefined, which the comparator
treats as a transient error, so the executor preserved the item — never-watched
shows got stuck in collections across runs.

Guard on length so an empty history takes the existing null branch ("never
watched", confirmed absent), per the getter contract (null = absent, undefined =
error). Emby/Jellyfin already length-guard this. Adds Plex coverage for empty →
null, populated → newest date, and outage → undefined.

* docs: update README feature wording

---------

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