fix: OR rule sections incorrectly evaluated as AND due to operator coercion#2971
Conversation
a01ca5a to
4613c10
Compare
|
Hey thanks for the PR. Can you describe the reasoning behind this? Please also describe how you prompted and if you ever corrrected the AI. Would also like to know if you run this yourself in your prod? |
|
@stormshaker Looking at this now. The bug is real. Though this PR is incomplete. I will fix it for you. Will commit soon. Once I've committed, please test this. Even if I tested it you are the one with the issue, so we need to confirm this for real instead of assuming anything - this is a behavioral change, and quite critical. |
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>
a520758 to
a1afe2a
Compare
|
@stormshaker This is ready for testing on your end now. Please checkout the branch and run against a working server. I asked AI to do some tests, and also write down some test sceneraios. What's left to test needs a real server, and manual testing. Here they are: The latest commit (
Tests performed
What is left to testCritical gaps, mostly things that require a reachable media server or a real
|
|
The last push was regression fixes. |
|
Did a few more tests. Updated the comment above. 👍 |
|
@stormshaker Testing rules now. :)
|
|
@stormshaker I've been on this for 10 hours straight now. I've tested what I can in my dev environment (codespace) but not yet on "a real" server. Merging this to |
|
WIll do... you've been busy! I'm in Australia, so we're offset on time zones. But I'm off to work now, will take a look tonight. Thanks! |
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.
|
I'm still testing this, but @enoch85, I thought I'd give you an update. Migration looks good, but I think we need to target OR for the existing rule migration to replicate how they worked before the bugfix, rather than AND. The first run after the migration (using AND) resulted in a 14-item collection -> 0. Changing the section operator to OR resulted in the same 14 items as before migration. That was on a Movies rule, I'll continue with TV Series. |
…ort 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>
|
Thanks for the update. Please test on latest development build. Pushed some more fixes. |
|
@stormshaker Please also ask your AI to read the latest release review documents. They explain how to test. Note; you always need a clean untouched DB on 3.12.1 before you upgrade to the development container. Else you won't get true/real results. |
|
Thanks for investigating the 14→0 case. Here's the raw pre-migration ruleJson as requested — extracted from a CA Backup taken 2026-05-23 04:48 UTC, before any upgrade. Rule 11 (Movies Leaving Soon) — raw rows:
Rule 12 (TV Leaving Soon) — raw rows:
This confirms your analysis exactly: rows 441 and 456 have Also worth noting: Rule 12 mixes storage formats — some operators are stored as string I'm now running a clean test using the May 23 backup DB (pre-migration) against the latest development image. Will report back. |
|
Great! |
|
Suggested release note:
|
Clean DB test resultsTested against a pristine pre-migration DB (CA Backup from 2026-05-23 04:48 UTC, before any upgrade), running the latest development image. Migration verificationMigration
All other fields in Rule 11 execution (Movies Leaving Soon)
Result: 0 matches, as expected. With section-boundary operator now explicitly AND, the mutually exclusive sections (section 0: ConclusionThe migration correctly preserves 3.12.1's effective behaviour. The 14→0 observation from our earlier testing was not caused by the migration — it simply made explicit what was already true: these rules had broken section logic that was always AND under 3.12.1. To get the rules working as intended (OR between sections), users need to manually set the section operator to OR in the rule editor — which the new UI now makes possible. Test environment: Plex + Radarr + Sonarr on real production library data. DB restored from CA Backup taken the morning of 2026-05-23, before any upstream-dev container was run. |
|
Thanks for writing that up — the structure is clear and the migration explanation is accurate. One small suggestion before it ships: The final sentence "The first rule run after upgrading will reconcile collections (removing items that no longer match)" may cause unnecessary alarm — since the migration preserves 3.12.1 semantics exactly, the first run should produce identical results to before the upgrade. Our clean-DB test confirmed this: 0 new items were added or removed on the first run post-migration. It might be worth removing that sentence, or it could be read as implying the migration changes behaviour when it doesn't. Also two things that might be worth adding, since they're user-facing:
Here's a suggested rewrite incorporating those points, feel free to adjust the tone:
Happy to leave it entirely with you if you'd prefer to word it differently — just flagging the contradiction before it goes out. |
|
Thanks! So testing proves everything is OK? Did you run the PR-tagged container to see the exact logs? |
|
@stormshaker Also, what is your opinion on this? Does it work as you would expect? |
|
I would like to test it some more. Without starting from a blank database... I'd like to take an existing database with null values in the rules, with collections that have mediaCount > 0, and upgrade the container just as a user would. Then, I'd like to see the rules run, and my mediaCount remain the same. No impact of the upgrade - have it all seamless. The only change from a user's perspective should be that the section operator dropdowns now show the operator, rather than being empty. So in my opinion, I'd like more testing. For a few more days, it's worth it for more assurance. |
|
Good call! I'll be waiting for your results. The logging PR I made will only log now for testing sake. Then I'll remove it or keep the regression tests. But, you can use it for testing to get logged results. That's easier for you. 👍🏼 |
|
I also tested this once more, based on clean DB and the community rules. Here are the result: Method: clean 3.12.1 DB (verified: migration not in
Conclusion: every output is identical before vs after — including the 47 rules where AND-vs-OR is outcome-determining. The migration makes section operators explicit ( |
|
OK, that's a solid test. I'm still setting mine up, but I have only a few rules in my prod - nothing like the community rules. I'll let you know when I'm done but those results above are very promising :) |
|
Hi @enoch85, I've completed a full pass pre to post upgrade and verified that the counts look good. One small discrepancy when I captured the pre-upgrade 3.12.1 Rule 11 number. Overall, I'd call that a pass on my large library. Test environment: Claude Code (AI assistant) ran the API calls and monitoring; results reviewed and verified by me throughout. Setup Tested Rules 11 (Movies Leaving Soon) and 12 (TV Leaving Soon) — both multi-section rules, each with a section 0 and a section 1. The null section-boundary operators were left as-is (not manually patched) to let the migration do its work naturally. 3.12.1 baseline (pre-upgrade)
The 9-item gap between Rule 11's DB count and Plex collection count is write-lag: the Plex collection sync had finished at 448 but the DB batch write was still in progress when we captured the snapshot. Dev image (post-upgrade)
Rule 11 DB and Plex collection are now in sync at 448. Rule 12 unchanged at 157. ✅
On first startup the migration ran and converted the Unrelated finding during testing Rule 12 logs show repeated Verdict: Pass on a large library (~2,800 movies, ~1,000 TV shows). Migration runs cleanly, counts are consistent pre/post upgrade. |
|
Perfect! This goes into |
* 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>
|
🎉 This PR is included in version 3.13.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



Problem
Each rule section combines with the previous via the operator on the section's first rule. When that operator is left unset it is stored as
null, and the comparator misreadnullas AND:RuleOperators.AND === 0, and in JS+null === 0istrue, so an unset section operator was treated as AND. Items that matched only one section of such a rule were then dropped by the AND intersection.Explicitly choosing AND or OR always worked (
+'1' === 0isfalse); only the unset default was affected. Two related gaps made that state reachable and sticky:0) operator via a truthy check (rule.operator ? ...), losing it on round-trip.Fix
Null-guard the coercion so an unset operator falls through to OR, consistent with how
nullis already treated within a section:Operators are persisted as strings (e.g.
"0"/"1"), so the+coercion is kept — a strictparsedRule.operator === RuleOperators.ANDwould never match the stored"0".Also included
0) operator is preserved: