Skip to content

v2.2.4

Choose a tag to compare

@ProphetSe7en ProphetSe7en released this 27 Apr 14:41
· 402 commits to main since this release

A bundle of user-reported fixes and small UX improvements collected and stabilised over the v2.2.3-dev cycle.

CF group activation respects per-CF default flag

When toggling a TRaSH CF group on (in Profile Builder, Profile Detail, or
the legacy toggleGroup path), Clonarr used to add every optional CF
in the group to the profile. The TRaSH JSON marks some CFs as default: true precisely so consumers know which subset to auto-include — that
flag was being read into the data model but ignored at activation time.
Result: groups like [Unwanted] Unwanted Formats (15 CFs, 11 marked
default) added all 15 instead of just the 11.

Fix touches four call sites with the same fix in each:

  • pdToggleGroup (new, replaces inline @change handler in Profile
    Detail) — uses cf.default from ProfileCFGroupEntry.
  • pbToggleGroupInclude (Profile Builder group toggle) — uses
    cf.cfDefault from CategorizedCF (different field name because
    /api/trash/{app}/all-cfs carries a different shape than
    /api/trash/{app}/profiles/{trashId}).
  • initSelectedCFs for non-exclusive default-enabled groups loaded with
    a profile.
  • toggleGroup (legacy orphan path) — kept consistent.

Also: clicking any of the three state pills (Req / Opt / Fmt) on a CF in
Profile Builder now adds that CF to the profile's selectedCFs. Before,
only Fmt did — clicking Req or Opt set the state override but the
CF remained absent from the saved profile, which silently cancelled the
state change. Combined with the new dimmed rendering for "in an enabled
group but not yet in profile" CFs, this gives users a one-click way to
include the non-default optional CFs they want.

Required CFs remain mandatory when the group is enabled. Custom CFs in
user-created CF groups (CF Group Builder) work the same way: per-CF
default flag controls auto-inclusion. The group's enable/disable toggle
itself works unchanged — flipping a group off clears all its CFs from
selection just like before.

Maintenance: "Unused Custom Formats" cleanup action

New cleanup action under Maintenance → Cleanup that finds CFs in an Arr instance which are not referenced by any Clonarr sync rule and are not used for renaming. Hard caveat: assumes Clonarr is the sole tool managing CFs on the instance — CFs added directly via the Arr UI, Recyclarr, Notifiarr, etc. will appear here as "unused", since Clonarr has no record of them. The user reviews the preview list and the existing Keep List protection still applies.

Two safety guards prevent destructive false positives:

  • Refuses to scan when TRaSH guide data isn't loaded. Without it, every TRaSH ID in a rule's selectedCFs would resolve to "" and the user would be shown "delete all your TRaSH CFs" as candidates. The scan returns a clear "TRaSH guide data is not loaded for {appType}" error instead and points the user to Settings → TRaSH Guides.
  • Belt-and-suspenders sync-history fallback. The "managed" set is built from each rule's selectedCFs, score-override keys, the rule's TRaSH or imported profile's intrinsic CFs, AND from the latest sync-history entry per (instance, arrProfileId). The history fallback protects against TRaSH-side schema drift — if a rule's referenced TRaSH profile was renamed/removed upstream, ResolveProfileCFs returns empty, but the history captures what Clonarr actually pushed at last sync, so previously-required CFs aren't mistakenly flagged.

Custom Format editor: preserve field values across Type changes

Switching a specification's Type dropdown used to wipe the field values
unconditionally — type a regex into a ReleaseTitleSpecification, click
Type by accident, and the regex is gone. Now the editor remembers the
field state per implementation type for the lifetime of the editor session
(snapshot taken when leaving an implementation, restored when returning),
plus carries values forward across compatible types (same field name +
type — e.g. ReleaseTitle ↔ ReleaseGroup, both expose a "value" textbox).
Mismatched types still reset (Source's number/select "value" to a textbox
"value" can't carry meaningfully). Loaded specs get their initial values
seeded into the history so a Type → other → original round-trip restores
the original value.

Maintenance UI: visual consistency on cleanup cards

The "Unused Custom Formats" card now uses the same full-border styling as
the other warning-level cleanup cards (yellow border + matching title +
yellow outline scan button — same treatment as "Delete All Custom Formats
(Keep Scores)"). The earlier left-border-only design was an outlier in a
section that already had a consistent visual language.

Sync result banner: include Settings + Quality counts in summary line

Toast/banner used to read "Created: 0 CFs, Updated: 0 CFs, Scores: 0 updated" even when the sync only changed profile settings or quality structure — nothing in the summary indicated anything had happened, the user had to click Show details to see e.g. "Language: Original → Any". The banner now reads Created: X CFs, Updated: Y CFs, Scores: Z updated, Settings: A changed, Quality: B changes. Counts come from settingsDetails.length and qualityDetails.length on the sync result. Updated in both result-banner instances (sync modal + Compare chained-sync banner). Sync history view already had its own per-category columns.

Sync log: include language in "profile settings changed" line

When only the profile language changed during a sync, the log line read
profile settings changed (minScore=500→500, minUpgrade=50→50, cutoffScore=10000→10000, upgrade=true→true)
— every numeric field unchanged, no clue what triggered the "changed" claim. The detector
included language in the diff but the log message and SettingsDetails (sync history
display) both omitted it. The log line now appends language=Original→Any, and a
Language: X → Y entry is added to SettingsDetails when language changes (matching the
existing per-field detail entries for min-score, min-upgrade, cutoff-score, upgrades).

Custom Format JSON import — cross-Arr compatibility check

Importing a Radarr CF JSON to Sonarr (or vice-versa) used to silently misinterpret value-encoded specs. The most common case: SourceSpecification value 7 means WEBDL in Radarr but BlurayRaw in Sonarr — so a Radarr "WEBDL" CF imported to Sonarr would silently start matching BlurayRaw releases.

The import now runs a compatibility check before sending to the backend and surfaces issues in a confirmation modal:

  • Errors: spec types that don't exist in the target Arr (QualityModifierSpecification is Radarr-only, ReleaseTypeSpecification is Sonarr-only) and value-out-of-range cases (e.g. SourceSpecification value 8 or 9 imported to Sonarr where the enum tops at 7).
  • Warnings: canonical-name mismatches — when spec.name is a recognised Source name (WEBDL, WEBRip, Bluray, BlurayRaw, Remux, DVD, etc.) but the value resolves to a different name in the target Arr. Also flags IndexerFlagSpecification Internal value mismatches (Radarr Internal=32 vs Sonarr Internal=8).

User-named specs without canonical names (e.g. spec.name="My favourite source") are left alone — we don't know intent there. The user can still Import anyway through the modal — this is a safety check, not a hard block.

Full changelog