feat(overlays): add force processing and reset concurrency gating#2827
Conversation
|
Quick note on the Phase 1 PR size: the current PR shows Runtime/product code is much smaller than the raw diff suggests. The larger pieces are mainly:
So roughly ~750 lines of the PR are tests/specs, and the actual runtime implementation is closer to the intended target. The final tightening pass also removed duplicate UI implementation by collapsing I intentionally kept the focused tests in the PR because the changes touch poster mutation, forced reapply behavior, reset/reapply semantics, UI gating, and processor mutual exclusion. Those are release-sensitive paths, and the tests are what make the smaller implementation safe to review and maintain. One included fix, If preferred, I can split the render-order crash fix into its own PR to reduce the visible Phase 1 diff, but I’d recommend keeping the focused overlay tests with the feature work. |
| if (this.status === 'running') { | ||
| this.logger.warn('Overlay processor is already running, skipping'); | ||
| return this.createEmptyResult(); | ||
| } |
|
@SmolSoftBoi Don't do Copilot reviews please. Review it yourself. 👍 |
|
@MrLinford is this ready to be merged? Will have a look this weekend if the answer is yes. |
|
I believe so. |
- Promote process result + request schema to @maintainerr/contracts so server and UI share one wire shape; drop the duplicate UI interface (skipped was incorrectly optional) and the inline zod schema in the controller. - Remove the appliedMediaItems recursion sentinel from the public processCollection signature so the lock check can't be bypassed by a truthy empty array; processAllCollections now calls the internal helper directly. - Gate DELETE /overlays/reset against concurrent processing and add spec coverage for both reset-while-disabled and reset-while-running. - Surface the skipped count in the inline overlay summary alert. - Replace the TriggerRuleButton re-export shim with a direct import of TriggerRuleActionButton at the one usage site, and revert the no-op canTestMedia change on CollectionDetailPage.
|
@MrLinford — pushed What changed:
Validated locally:
Note on the description: the PR body still describes Could you pull and verify the flows on your end? Particularly: enabling overlays then saving, Run Now with the new |
|
@MrLinford Just waiting for your testing here on this. I'm thinking this will go into 3.11.0. |
Planning to do this at the weekend, not sure what day yet. |
From the testing I have done, all looks good. Can't test this, though. "Test concurrency: Trigger a forced overlay run, then immediately click Reset — confirm it's rejected with a 409 error" As you can't hit Reset while Run Now is running 😄 |
* build(deps): bump fast-uri from 3.0.6 to 3.1.2 (#2862) Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.0.6 to 3.1.2. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](fastify/fast-uri@v3.0.6...v3.1.2) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add typed TanStack Query test helpers and migrate spec mocks (#2863) * Add typed React Query test helpers * Reuse existing deferred helper in queryResults * fix(storage-metrics): merge shared volumes across hosts when byte-exact (#2852) * fix(storage-metrics): merge shared volumes across hosts when signature is byte-exact Resolves #2841. * style: apply prettier formatting --------- Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com> * build(deps-dev): bump typescript-eslint from 8.59.1 to 8.59.2 (#2850) Bumps [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) from 8.59.1 to 8.59.2. - [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.59.2/packages/typescript-eslint) --- updated-dependencies: - dependency-name: typescript-eslint dependency-version: 8.59.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> * feat(overlays): add force processing and reset concurrency gating (#2827) * feat(overlays): add force reapply processing flow * feat(ui): add reapply all overlays action * feat(ui): add collection overlay reapply action * feat(ui): clarify overlay action semantics * fix: prevent collection detail render crash * fix(overlays): serialize collection reapply actions * refactor(overlays): tighten phase one implementation * style(ui): format collection detail spec * fix(overlays): use named zod import * fix(overlays): simplify manual reapply controls * test(ui): harden notification save spec * refactor(overlays): tighten phase 1 review fixes - Promote process result + request schema to @maintainerr/contracts so server and UI share one wire shape; drop the duplicate UI interface (skipped was incorrectly optional) and the inline zod schema in the controller. - Remove the appliedMediaItems recursion sentinel from the public processCollection signature so the lock check can't be bypassed by a truthy empty array; processAllCollections now calls the internal helper directly. - Gate DELETE /overlays/reset against concurrent processing and add spec coverage for both reset-while-disabled and reset-while-running. - Surface the skipped count in the inline overlay summary alert. - Replace the TriggerRuleButton re-export shim with a direct import of TriggerRuleActionButton at the one usage site, and revert the no-op canTestMedia change on CollectionDetailPage. --------- Co-authored-by: Craig Linford <craig.linford@sas.com> Co-authored-by: enoch85 <mailto@danielhansson.nu> * build(deps-dev): bump @suites/unit and @suites/doubles.jest to 3.1.x (#2865) The two packages augment each other's types; bumping only one causes StubbedInstance/Mocked type mismatches. Bumps both in lockstep, superseding #2846 and #2848. * build(deps): bump node from 24.15.0-alpine3.22 to 26.1.0-alpine3.22 (#2857) * build(deps): bump node from 24.15.0-alpine3.22 to 26.1.0-alpine3.22 Bumps node from 24.15.0-alpine3.22 to 26.1.0-alpine3.22. --- updated-dependencies: - dependency-name: node dependency-version: 26.0.0-alpine3.22 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(docker): install corepack for Node 26+ where it's no longer bundled --------- 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 typescript from 5.9.3 to 6.0.3 (#2713) * build(deps-dev): bump typescript from 5.9.3 to 6.0.3 Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.3 to 6.0.3. - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](microsoft/TypeScript@v5.9.3...v6.0.3) --- updated-dependencies: - dependency-name: typescript dependency-version: 6.0.3 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> * fix(server): set explicit rootDir for TypeScript 6 build TS 6 no longer infers rootDir from input files; it defaults to the tsconfig directory. Setting rootDir explicitly in the build config preserves the dist/<file> layout that mirrors src/<file>. --------- 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> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kristian Matthews-Kennington <kristian@matthews-kennington.com> Co-authored-by: enoch85 <mailto@danielhansson.nu> Co-authored-by: Craig Linford <clinford86@gmail.com> Co-authored-by: Craig Linford <craig.linford@sas.com>
* docs: sync overlay processing API and add UI test-utils reference (#2866) Documents the force flag on overlay processing endpoints and the 409 gating on DELETE /overlays/reset (#2827), and points contributors at the new TanStack Query test helpers (#2863). * fix(rules): scope exclusion cascade by type, not entry point (#2858) (#2867) Excluding a single episode used to skip every other episode of the same show. The cascade filter keyed off `Exclusion.parent`, but `parent` is the entry point of the exclusion request (set to `data.mediaId`, which from the show overview is always the show id) — not the structural parent. A typed cascade set driven by `mediaServerId` of `type='show'`/`'season'` rows fixes the regression without breaking the legacy null-type fallback. * fix(collections): sort by addDate and group episodes by show (#2868) * fix(collections): sort by addDate and group episodes by show Maintainerr is now the source of truth for collection ordering: both the Plex push and the UI fetch use the same comparator, so what users see in the collection page matches what the media server displays. - deleteSoonest now reads collection_media.addDate (drives the visible "Leaving in X days" overlay) instead of MediaItem.addedAt (the unrelated media-server library date) - Same-day items tie via UTC day bucketing so the title tiebreaker actually fires within a "Leaving Today" group - Title sort groups episodes/seasons under their show via grandparentTitle/parentTitle; movies are unaffected * refactor(sort): pin missing values to end + DRY numeric branches - Items missing the sort value (no air date, no rating, no view count, no add date) now sort to the end regardless of asc/desc. Previously they coerced to 0 and silently sorted to the front in ascending order — an item with no air date appeared before one from 1995. - airDate, rating, watchCount, and deleteSoonest collapse onto one helper (compareNumericWithTitleFallback), removing three near-identical case bodies in compareMediaItemsBySort. - airDate now day-buckets like deleteSoonest, so same-day items always reach the title tiebreaker instead of comparing by clock time. - Pin tsBuildInfoFile in apps/server/tsconfig.build.json so nest-cli's type-check pass stops emitting a stray tsconfig.build.tsbuildinfo at the package root. * refactor(sort): align deleteSoonest buckets with visible daysLeft - Bucket deleteSoonest by `ceil((addDate - referenceTime) / dayMs)` so items showing the same "Leaving in X days" countdown tie even when their addDates straddle UTC midnight. Falls back to UTC-day bucketing when no reference time is supplied. - Wire `referenceTime = now - deleteAfterDays * dayMs` through the two collection sort paths (applyCollectionSort + paginated fetch). - Replace the show-aware title test data — old data alphabetized identically under either comparator and didn't actually prove the grouping behavior. New data interleaves episode titles across shows and asserts on [show, episode] pairs. - Drop dead `baseUrl` + `paths` from apps/ui/tsconfig.json (unused alias) to clear the TS 6 deprecation surfaced in the IDE. * perf(collections): restore SQL fast path for deleteSoonest paging Page-loading a collection with `deleteSoonest` was hydrating every row in the collection before slicing — minutes-long blocking page loads on collections of a few hundred items, especially on Jellyfin where `getMetadata` isn't cached at the adapter layer. `deleteSoonest` orders by `addDate + deleteAfterDays`, but `deleteAfterDays` is constant per collection, so the only sort key that matters lives on `collection_media.addDate`. SQL can paginate it directly without touching MediaItem metadata. `applyCollectionSort` (the media-server push) still applies the day-bucketed title tiebreaker via the comparator, so the polished alphabetical-within-day order is what users see when browsing the actual Plex/Jellyfin collection. The Maintainerr UI page may show same-day items in a slightly different order — acceptable because the primary sort key is correct and Maintainerr's DB remains the source of truth driving the next push. Other explicit sorts (airDate / rating / watchCount / title) still go through hydrate-then-paginate because their sort keys live on MediaItem, not on collection_media. --------- Co-authored-by: enoch85 <mailto@danielhansson.nu>
|
🎉 This PR is included in version 3.11.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
…11.1 ) (#29) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/maintainerr/maintainerr](https://github.com/Maintainerr/Maintainerr) | minor | `3.10.1` → `3.11.1` | --- ### Release Notes <details> <summary>Maintainerr/Maintainerr (ghcr.io/maintainerr/maintainerr)</summary> ### [`v3.11.1`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3111-2026-05-12) [Compare Source](Maintainerr/Maintainerr@v3.11.0...v3.11.1) #### Highlights - Fixed an issue where Jellyfin libraries with "Group films into collections" enabled caused BoxSet members to incorrectly toggle in and out of rule results ([#​2870](Maintainerr/Maintainerr#2870)). - Improved error message for invalid Plex library section IDs to better guide users in resolving configuration issues ([#​2883](Maintainerr/Maintainerr#2883)). - Enhanced custom collection UX by renaming tags for clarity and adding tooltips to explain collection handling options ([#​2882](Maintainerr/Maintainerr#2882)). #### Fixes - Validated Jellyfin IDs before refresh to prevent errors ([#​2853](Maintainerr/Maintainerr#2853)). - Improved error message when Plex library section ID is invalid ([#​2883](Maintainerr/Maintainerr#2883)). - Resolved Jellyfin collection add/remove loop for BoxSet items ([#​2870](Maintainerr/Maintainerr#2870)). - Clarified custom collection terminology and added warnings for disabling collection handling ([#​2882](Maintainerr/Maintainerr#2882)). - Added explicit token permissions to the Fider move job to address CodeQL findings. - Added environment gate to mitigate TOCTOU vulnerability in the release\_pr workflow ([#​2879](Maintainerr/Maintainerr#2879)). - Addressed transitive dependency vulnerabilities by adding Yarn resolutions for specific packages ([#​2881](Maintainerr/Maintainerr#2881)). #### Dependencies - Updated 10 dependencies, including notable packages: vite, typeorm, and [@​typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/eslint-plugin). #### New Contributors - [@​blixten85](https://github.com/blixten85) made their first contribution in [#​2881](Maintainerr/Maintainerr#2881) ### [`v3.11.0`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3110-2026-05-11) [Compare Source](Maintainerr/Maintainerr@v3.10.2...v3.11.0) #### Highlights - Added support for force-processing overlays and gated reset operations against concurrent processing runs ([#​2827](Maintainerr/Maintainerr#2827)). - Improved collection sorting: collections now apply sort order on save and synchronize with the media server ([#​2860](Maintainerr/Maintainerr#2860)). - Enhanced storage metrics: potential reclaimable storage is now split into movie, show, season, and episode panels ([#​2854](Maintainerr/Maintainerr#2854)). #### Breaking Changes - None. #### Features - Added force-processing support for overlay operations and gated reset operations against concurrent processing runs ([#​2827](Maintainerr/Maintainerr#2827)). - Improved storage metrics by splitting potential reclaimable storage into movie, show, season, and episode panels ([#​2854](Maintainerr/Maintainerr#2854)). #### Fixes - Fixed collection sorting to apply on save and synchronize with the media server ([#​2860](Maintainerr/Maintainerr#2860)). - Resolved issue where excluding a single episode incorrectly excluded all episodes of the same show ([#​2867](Maintainerr/Maintainerr#2867)). - Fixed storage metrics to merge shared volumes across hosts when byte-exact ([#​2852](Maintainerr/Maintainerr#2852)). - Fixed Jellyfin retry ID check to align with pre-filter logic ([#​2853](Maintainerr/Maintainerr#2853)). - Addressed issue where reclaimed bytes were not credited when `sizeBytes` was not yet cached ([#​2855](Maintainerr/Maintainerr#2855)). #### Performance - None. #### Database migrations - Added a new `mediaServerSort` column to the `collection` table to support media server-specific sorting. #### Internal - Added typed TanStack Query test helpers and migrated UI spec mocks to use typed builders ([#​2863](Maintainerr/Maintainerr#2863)). - Added an architecture overview document detailing the monorepo structure, runtime flow, and core components ([#​2817](Maintainerr/Maintainerr#2817)). #### Dependencies - Updated 5 dependencies, including notable updates to TypeScript and typescript-eslint. ### [`v3.10.2`](https://github.com/Maintainerr/Maintainerr/blob/HEAD/CHANGELOG.md#3102-2026-05-08) [Compare Source](Maintainerr/Maintainerr@v3.10.1...v3.10.2) #### Highlights - Fixed incorrect version comparison logic that caused update notifications to fail for multi-digit version segments ([#​2838](Maintainerr/Maintainerr#2838)). - Improved storage metrics by deduplicating reclaimable bytes and adding per-type cleanup byte counters ([#​2833](Maintainerr/Maintainerr#2833)). </details> --- ### Configuration 📅 **Schedule**: (in timezone America/New_York) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjAuNyIsInVwZGF0ZWRJblZlciI6IjQzLjE2MC43IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://git.greyrock.io/greyrock-labs/home-ops/pulls/29
Description & Design
This change adds force-processing support to overlay operations and gates reset against concurrent processing runs.
What changed:
OverlayProcessorRunResultandoverlayProcessRequestSchemato@maintainerr/contractsfor shared useforceflag to overlay processing endpoints to bypass stale-state skip logicDELETE /overlays/resetto return 409 if a processor run is in progress (prevents concurrent mutations)SkippedcountWhy this matters:
Related issue
AI-Assisted Development
AI-assisted portions of this PR:
What I personally reviewed, changed, or validated:
Why this solution fits this project’s coding standards and design:
Modal,PendingButton,SaveButton, and settings feedback helpersChecklist
How to test
Validation performed
✓ Server tests: 44/44 pass (includes reset-while-disabled and reset-while-running 409 scenarios)
✓ UI tests: 13/13 pass (includes Run Now force flag and reset availability)
✓
yarn format/yarn lint/tsc --noEmitall clean✓
yarn workspace @maintainerr/server buildandvite buildboth cleanScope
This PR includes:
OverlayProcessorRunResultand overlay process requestNotes for reviewers
The Notifications/index.spec.tsx hardening is included in this PR. This is unrelated to overlays; if you'd prefer, it can be split into a separate PR.
Key commits in this clean PR branch:
f42d2613feat(overlays): add force reapply processing flow08d7cdaafeat(ui): add reapply all overlays action528a2cccfeat(ui): add collection overlay reapply actionbb87c3f5feat(ui): clarify overlay action semantics9e208c15fix: prevent collection detail render crash49d686d6fix(overlays): serialize collection reapply actions3b86c94brefactor(overlays): tighten phase one implementation