chore: sync development to main#2979
Conversation
- 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): migrate to Tailwind CSS v4
- 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
- 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 '-').
feat(ui): adopt Tailwind v4.x features
- 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.
…ge 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.
…ercion (#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>
…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.
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.
) 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.
Co-authored-by: enoch85 <mailto@danielhansson.nu>
…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.
…#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.
- 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
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>
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>
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>
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>
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>
…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>
Release testing — end-to-end results (
|
| What we tested | Result (observed) | How |
|---|---|---|
| Migration on boot (#2971) | NormalizeRuleSectionOperators… in migrations table |
booted on real SQLite; queried table |
Rule engine (/api/rules/test, grp 337 × 3 movies) |
code:1, 26 rules each; ratings 9/6/2, Times viewed 0, in 3 collections, 1920x1080/h264, Is Watched false |
curl + JSON parse vs Jellyfin mock |
| Null / unary path (#2978) | two date fields null + firstValueReason:"…not recorded", no crash |
/api/rules/test JSON |
| OR section combining (#2971) | first rule no operator; 25 rules operator:OR; section true |
rules/test JSON + Test Media YAML |
| YAML export — AND preserved (#2971) | operator: AND present in encoded YAML |
/api/rules/yaml/encode of AND-boundary group |
YAML export — no App.undefined (#2976) |
absent; DATE/NUMBER round-trip encode→decode (code 1) |
encode→decode round-trip |
| YAML import — unresolved prop SKIP (#2976) | code:1, skipped:1, 2 of 3 rules kept |
decode YAML with one bogus firstValue |
Cross-server /migrate (#2976) |
Plex [0,0]→[6,0]; Seerr [3,0] untouched while lastVal[0,5]→[6,5] (independent remap) |
POST /api/rules/migrate |
| Community rules import — ALL versions | 165/165 migrate code:1, 0 failures; 0 residual non-first null operators |
/api/rules/community → /migrate each |
| Community import — full UI flow (rule 81) | imported via Community modal → values populate → operators [null, AND, OR] → saved, 3 rules, Active |
Playwright (import → select Radarr server → Save) |
| Plex watched-episodes rule (#2975) | "Amount of episodes marked as watched" = 1 (viewedLeafCount:1), result true |
Plex stack; /api/rules/test grp 341 × show sh1 |
| Collections page (Tailwind v4) | 3 cards, grid hydrated 14/10/8, sizes/schedules, Active | Playwright DOM assert + screenshot |
| Rules page + editor | 3 group cards; editor opens /rules/edit/337 |
Playwright DOM assert |
| Test Media search (#2978) | styled field; typeahead → [Mock Alpha/Bravo/Charlie]; Test → YAML with operator:OR |
Playwright type/select/Test + screenshot |
| Storage | API 200, reachable:true, Radarr+Sonarr ok, lib sizes; totals 0 |
curl + Playwright |
| Browser console | only benign /api/collections/337/poster 404 |
Playwright console capture |
| TEXT_LIST custom value YAML import (fixed, PR #2986) | was code:0 crash → now code:1, customVal preserved |
live round-trip + unit tests |
| Within-section null operator on community import (fixed, PR #2986) | rule 81 would fail #2971 save-gate → now [null, AND, OR], saves |
live + unit test + full UI save |
Suites: full repo test suite green · check-types · format:check · build — all clean.
Not driven: Streamystats getter (needs live service), cache hygiene #2972 (unit-tested), release-notes tooling (release workflow).
Verdict: ✅ no CRITICAL/HIGH/MEDIUM regressions.
* 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>
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>
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>
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>
) 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>
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.
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>
…#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>
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>
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>
…P 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.
…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 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
Release testing — addendum (post-#2986 commits,
|
| What we tested | Result (observed) | How |
|---|---|---|
| TypeORM 0.3.29→1.0.0 boot (#2983) | server boots clean; migrations table carries NormalizeRuleSectionOperators1779622081794; IsNull()/Not(IsNull())/delete/findOne all execute live (the whole exclusion suite below runs on 1.0) |
booted real SQLite; GET /api/settings 200 |
| Exclusion scoping — no cross-group leak (#2991) | scoped excl. on grp 183 absent from GET ?rulegroupId=184; present in 183; the global is appended exactly once (no dup) |
POST /api/rules/exclusion (collectionId 16) + GET per group |
| Global subsumes scoped (#2991) | adding a global for 90001 deleted the scoped grp-183 row and left one global row (ruleGroupId NULL) — no duplicate |
POST scoped → POST global; queried exclusion table |
| Already-global → scoped add skipped (#2991) | re-adding scoped (grp 184) for a globally-excluded item is a no-op; log: Media 90001 is already globally excluded; skipped rule group 184 exclusion |
POST scoped while global exists; DB unchanged |
| Remove global exclusion (#2991) | code:1; log Removed exclusion 33 for media 90001 (global); no spurious collection-log; no TypeORM-1.0 null-where throw (the 0.3.x footgun) |
DELETE /api/rules/exclusion/33 |
| Collection create — no ids / no 414 (#3001) | live rule run created collection 95000; create URI is /library/collections?type=&title=§ionId= — zero item ids in the query string |
POST /api/rules/185/execute + log + source |
| Plex auth — invalid token (#2996) | plex.tv reachable + bad seed token → {status:NOK, "Stored Plex credentials are invalid…"} — classified invalid, not unreachable |
GET /api/settings/test/plex/auth |
| AddModal global-exclusion warning (#2991 UI) | confirm modal lists each "item — rule group" before going global; best-effort fall-through if prefetch fails | Vitest spec (4 cases) |
| Discord empty thumbnail | thumbnail omitted from embed when payload.image is empty |
Vitest spec |
Suites: full repo test suite green · check-types · build — all clean.
Not driven: Plex auth unreachable branch (can't sever plex.tv locally; unit-tested in plextvApi.spec.ts) · Discord live webhook delivery (unit-tested) · collection batch-add populate step (blocked by un-mockable plex.tv hub-visibility enrichment, the documented local-mock limitation; adapter unit-tested).
Verdict: ✅ no CRITICAL/HIGH/MEDIUM regressions in the post-comment changes.
Follow-up validation — review fix + upgrade & full-rule regression
Performed while finalizing review fixes; captured as two permanent regression harnesses.
| What we tested | Result (observed) | How |
|---|---|---|
| Review fix — global-exclusion warning links | now SPA navigate() (respects router basename; old <a href> 404'd under BASE_PATH) + status-cache clear & ['collections'] invalidation so the destination still loads fresh |
unit spec (5 cases) + manual UI on the seeded stack |
| 1.x → current upgrade path | reconstructed the v1.7.1 schema (6 migrations), seeded 1.x-era rules, then applied all 37 post-1.7.1 migrations in order (ending NormalizeRuleSectionOperators); operator backfill correct, seeded rows survived every SQLite table rebuild, late 3.x columns present |
new upgrade-from-1x.spec.ts (real TypeORM migration runner on a temp SQLite) |
| Migration class-name stability | the 6 v1.7.1 migration class names are byte-identical to current → TypeORM skips them on upgrade (no re-run / "table exists") | v1.7.1 vs HEAD source compare |
| Rule catalog vs v3.12.1 | 8 apps / 208 props vs 7 / 205: +3 (Plex sw_markedWatchedEpisodes #2975; Streamystats isInWatchlist + watchlistedByUsers #2977), 0 removed, 0 changed — #2922 getter refactor caused zero catalog drift |
dump every Application×Property on both builds, diff |
| Comparator matrix vs v3.12.1 — all RuleType×RulePossibility (107 scenarios) | 106/107 identical; the sole diff is the intended #2971 fix (AND/false → OR/true). Unary EXISTS/NOT_EXISTS identical on both (no crash) |
expanded rules-test-matrix.e2e.ts run on both builds, diff |
Suites: full repo yarn turbo test green with the two new regression specs added; check-types, eslint, prettier clean.
Verdict: ✅ upgrade path applies cleanly from 1.x; no rule-evaluation regression vs v3.12.1 beyond the intended #2971 change.
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.
…n 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 drift reportComparing Rule glossary parity
In code but missing from Glossary (2)
New migrations on this branch
Each migration typically introduces a setting or schema change. Confirm Rule constants
Public contracts (
|
|
🚀
|
|
🎉 This PR is included in version 3.13.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
Summary
Promotes
developmenttomainfor release. Squash-merge when approved; release automation continues on approval.Changes