Skip to content

fix(dashboard): merge tracker customizations with duplicate displayName#751

Merged
s0up4200 merged 2 commits intoautobrr:mainfrom
jabloink:fix/tracker-breakdown-customization-merge-duplicates
Dec 12, 2025
Merged

fix(dashboard): merge tracker customizations with duplicate displayName#751
s0up4200 merged 2 commits intoautobrr:mainfrom
jabloink:fix/tracker-breakdown-customization-merge-duplicates

Conversation

@jabloink
Copy link
Contributor

@jabloink jabloink commented Dec 11, 2025

when creating a tracker customization with a name that already exists, it now merges the new domains into the existing customization instead of creating a duplicate entry

previously this would create two individual entries which can be seen by exporting, since its being deduped by displayName, one would disappear and the remaining one wouldnt have a chainlink icon and editing it wouldnt show the urls of the other entry

Summary by CodeRabbit

  • Bug Fixes
    • Improved tracker customization consolidation: customizations with matching display names are now automatically merged rather than creating duplicates. When you create a customization with a display name that already exists, the system combines the new domains with the existing entry instead of creating a separate customization. This reduces clutter and improves overall organization of your tracker settings.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

Walkthrough

The TrackerBreakdownCard component's customization creation logic now conditionally merges domains into existing customizations when a matching case-insensitive displayName is found, instead of always creating new entries. Non-matching customizations fall back to the original creation behavior.

Changes

Cohort / File(s) Summary
Tracker customization consolidation
web/src/pages/Dashboard.tsx
Modified TrackerBreakdownCard logic to check for existing customizations by displayName; if found, merges domains via updateCustomization; otherwise creates new customization.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Focus areas: Logic for case-insensitive displayName matching and domain merging conditions
  • Potential concerns: Data consistency implications when merging domains into existing customizations; ensure success handler and dialog closure work correctly for both update and create paths
  • Testing: Verify behavior with duplicate displayNames (different cases), empty domain merges, and failure scenarios

Possibly related PRs

  • PR #717: Updates FilterSidebar to consume and group tracker customizations by consolidated displayNames, complementing this PR's consolidation logic

Suggested labels

enhancement, web

Poem

🐰 Duplicate names no more shall stay,
We merge them all in one neat way,
Domains consolidated with care,
No fragmentation floating in air!
One customization to bind them true,
Making trackers gleam brand-new!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main change: merging tracker customizations with duplicate displayName instead of creating duplicates.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
web/src/pages/Dashboard.tsx (1)

1094-1130: Solid implementation of the merge-on-duplicate behavior.

The case-insensitive comparison for displayName is correct and addresses the PR objective. A few observations:

  1. Domain deduplication: Line 1106 concatenates domains without deduplicating. Per retrieved learnings, the backend's normalizeDomains handler performs deduplication, so this works correctly. However, deduplicating client-side would make intent explicit and reduce payload size:
       if (existing) {
         // Merge into existing
+        const mergedDomains = [...new Set([...existing.domains, ...domains])]
         updateCustomization.mutate(
           {
             id: existing.id,
             data: {
               displayName: existing.displayName,
-              domains: [...existing.domains, ...domains],
+              domains: mergedDomains,
             },
           },
  1. User feedback: Consider adding a toast to inform users when domains are merged into an existing customization versus creating a new one—this helps avoid confusion if they expected a separate entry.

Both points are optional improvements; the current implementation is functionally correct. Based on learnings, domain normalization at the API boundary ensures duplicates won't persist.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9c7754 and a07bf76.

📒 Files selected for processing (1)
  • web/src/pages/Dashboard.tsx (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: s0up4200
Repo: autobrr/qui PR: 637
File: web/src/pages/Dashboard.tsx:805-831
Timestamp: 2025-11-25T11:39:54.748Z
Learning: In web/src/pages/Dashboard.tsx, the TrackerIconImage component intentionally receives displayDomain (incognito-mapped name) instead of the real domain in incognito mode. This causes icon lookups to fail and show only fallback letters, which is desired behavior for privacy - hiding both tracker names and icons when incognito mode is enabled.
Learnt from: s0up4200
Repo: autobrr/qui PR: 637
File: internal/api/handlers/tracker_customizations.go:138-154
Timestamp: 2025-12-07T21:15:46.214Z
Learning: In the qui codebase (internal/api/handlers/tracker_customizations.go and internal/models/tracker_customization.go), domain normalization follows a layered pattern: the handler's normalizeDomains performs input sanitization at the API boundary (trim, lowercase, deduplicate), while the store's joinDomains is only for serialization. All domains flow through the handler first, ensuring they're normalized before reaching the store layer.
📚 Learning: 2025-11-25T11:39:54.748Z
Learnt from: s0up4200
Repo: autobrr/qui PR: 637
File: web/src/pages/Dashboard.tsx:805-831
Timestamp: 2025-11-25T11:39:54.748Z
Learning: In web/src/pages/Dashboard.tsx, the TrackerIconImage component intentionally receives displayDomain (incognito-mapped name) instead of the real domain in incognito mode. This causes icon lookups to fail and show only fallback letters, which is desired behavior for privacy - hiding both tracker names and icons when incognito mode is enabled.

Applied to files:

  • web/src/pages/Dashboard.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Run tests

Copy link
Collaborator

@s0up4200 s0up4200 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is good 👍

@s0up4200 s0up4200 merged commit c4b1f0a into autobrr:main Dec 12, 2025
12 checks passed
alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Dec 18, 2025
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.9.1` -> `v1.10.0` |

---

### Release Notes

<details>
<summary>autobrr/qui (ghcr.io/autobrr/qui)</summary>

### [`v1.10.0`](https://github.com/autobrr/qui/releases/tag/v1.10.0)

[Compare Source](autobrr/qui@v1.9.1...v1.10.0)

#### Changelog

##### New Features

- [`f2b17e6`](autobrr/qui@f2b17e6): feat(config): add SESSION\_SECRET\_FILE env var ([#&#8203;661](autobrr/qui#661)) ([@&#8203;undefined-landmark](https://github.com/undefined-landmark))
- [`f5ede56`](autobrr/qui@f5ede56): feat(crossseed): add RSS source filters for categories and tags ([#&#8203;757](autobrr/qui#757)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9dee7bb`](autobrr/qui@9dee7bb): feat(crossseed): add Unicode normalization for title and file matching ([#&#8203;742](autobrr/qui#742)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d44058f`](autobrr/qui@d44058f): feat(crossseed): add skip auto-resume settings per mode ([#&#8203;755](autobrr/qui#755)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9e3534a`](autobrr/qui@9e3534a): feat(crossseed): add webhook source filters for categories and tags ([#&#8203;763](autobrr/qui#763)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c8bbe07`](autobrr/qui@c8bbe07): feat(crossseed): only poll status endpoints when features are enabled ([#&#8203;738](autobrr/qui#738)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fda8101`](autobrr/qui@fda8101): feat(sidebar): add size tooltips and deduplicate cross-seed sizes ([#&#8203;724](autobrr/qui#724)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e4c0556`](autobrr/qui@e4c0556): feat(torrent): add sequential download toggles ([#&#8203;776](autobrr/qui#776)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`2a43f15`](autobrr/qui@2a43f15): feat(torrents): autocomplete paths ([#&#8203;634](autobrr/qui#634)) ([@&#8203;rare-magma](https://github.com/rare-magma))
- [`1c07b33`](autobrr/qui@1c07b33): feat(torrents): replace filtered speeds with global ([#&#8203;745](autobrr/qui#745)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`cd0deee`](autobrr/qui@cd0deee): feat(tracker): add per-domain stats inclusion toggle for merged trackers ([#&#8203;781](autobrr/qui#781)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b6a6200`](autobrr/qui@b6a6200): feat(web): add Size column to Tracker Breakdown table ([#&#8203;770](autobrr/qui#770)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`560071b`](autobrr/qui@560071b): feat(web): add zebra striping to torrent table ([#&#8203;726](autobrr/qui#726)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f65a8`](autobrr/qui@f8f65a8): feat(web): improve auto-search on completion UX ([#&#8203;743](autobrr/qui#743)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e36312f`](autobrr/qui@e36312f): feat(web): improve torrent selection UX with unified click and escape behavior ([#&#8203;782](autobrr/qui#782)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`27c1daa`](autobrr/qui@27c1daa): feat(web): napster theme ([#&#8203;728](autobrr/qui#728)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e3950de`](autobrr/qui@e3950de): feat(web): new torrent details panel for desktop ([#&#8203;760](autobrr/qui#760)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6c66ba5`](autobrr/qui@6c66ba5): feat(web): persist tab state in URL for CrossSeed and Settings pages ([#&#8203;775](autobrr/qui#775)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`59884a9`](autobrr/qui@59884a9): feat(web): share tracker customizations with filtersidebar ([#&#8203;717](autobrr/qui#717)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Bug Fixes

- [`fafd278`](autobrr/qui@fafd278): fix(api): add webhook source filter fields to PATCH settings endpoint ([#&#8203;774](autobrr/qui#774)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`bdf0339`](autobrr/qui@bdf0339): fix(api): support apikey query param with custom base URL ([#&#8203;748](autobrr/qui#748)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c3c8d66`](autobrr/qui@c3c8d66): fix(crossseed): compare Site and Sum fields for anime releases ([#&#8203;769](autobrr/qui#769)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cb4c965`](autobrr/qui@cb4c965): fix(crossseed): detect file name differences and fix hasExtraSourceFiles ([#&#8203;741](autobrr/qui#741)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`fd9e054`](autobrr/qui@fd9e054): fix(crossseed): fix batch completion searches and remove legacy settings ([#&#8203;744](autobrr/qui#744)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`26706a0`](autobrr/qui@26706a0): fix(crossseed): normalize punctuation in title matching ([#&#8203;718](autobrr/qui#718)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`db30566`](autobrr/qui@db30566): fix(crossseed): rename files before folder to avoid path conflicts ([#&#8203;752](autobrr/qui#752)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8886ac4`](autobrr/qui@8886ac4): fix(crossseed): resolve category creation race condition and relax autoTMM ([#&#8203;767](autobrr/qui#767)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f8f2a05`](autobrr/qui@f8f2a05): fix(crossseed): support game scene releases with RAR files ([#&#8203;768](autobrr/qui#768)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`918adee`](autobrr/qui@918adee): fix(crossseed): treat x264/H.264/H264/AVC as equivalent codecs ([#&#8203;766](autobrr/qui#766)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`c4b1f0a`](autobrr/qui@c4b1f0a): fix(dashboard): merge tracker customizations with duplicate displayName ([#&#8203;751](autobrr/qui#751)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`3c6e0f9`](autobrr/qui@3c6e0f9): fix(license): remove redundant validation call after activation ([#&#8203;749](autobrr/qui#749)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`a9c7754`](autobrr/qui@a9c7754): fix(reannounce): simplify tracker detection to match qbrr logic ([#&#8203;746](autobrr/qui#746)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3baa007`](autobrr/qui@3baa007): fix(rss): skip download when torrent already exists by infohash ([#&#8203;715](autobrr/qui#715)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`55d0ccc`](autobrr/qui@55d0ccc): fix(swagger): respect base URL for API docs routes ([#&#8203;758](autobrr/qui#758)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`47695fd`](autobrr/qui@47695fd): fix(web): add height constraint to filter sidebar wrapper for proper scrolling ([#&#8203;778](autobrr/qui#778)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`4b3bfea`](autobrr/qui@4b3bfea): fix(web): default torrent format to v1 in creator dialog ([#&#8203;723](autobrr/qui#723)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2d54b79`](autobrr/qui@2d54b79): fix(web): pin submit button in Services sheet footer ([#&#8203;756](autobrr/qui#756)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2bcd6a3`](autobrr/qui@2bcd6a3): fix(web): preserve folder collapse state during file tree sync ([#&#8203;740](autobrr/qui#740)) ([@&#8203;ewenjo](https://github.com/ewenjo))
- [`57f3f1d`](autobrr/qui@57f3f1d): fix(web): sort Peers column by total peers instead of connected ([#&#8203;759](autobrr/qui#759)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`53a8818`](autobrr/qui@53a8818): fix(web): sort Seeds column by total seeds instead of connected ([#&#8203;747](autobrr/qui#747)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`d171915`](autobrr/qui@d171915): fix(web): sort folders before files in torrent file tree ([#&#8203;764](autobrr/qui#764)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Other Changes

- [`172b4aa`](autobrr/qui@172b4aa): chore(assets): replace napster.svg with napster.png for logo update ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`dc83102`](autobrr/qui@dc83102): chore(deps): bump the github group with 3 updates ([#&#8203;761](autobrr/qui#761)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`75357d3`](autobrr/qui@75357d3): chore: fix napster logo ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`206c4b2`](autobrr/qui@206c4b2): refactor(web): extract CrossSeed completion to accordion component ([#&#8203;762](autobrr/qui#762)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.9.1...v1.10.0>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.10.0`
- `docker pull ghcr.io/autobrr/qui:latest`

#### What to do next?

- Join our [Discord server](https://discord.autobrr.com/qui)

Thank you for using qui!

</details>

---

### Configuration

📅 **Schedule**: 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 is behind base branch, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0Mi4zOS4xIiwidXBkYXRlZEluVmVyIjoiNDIuMzkuMSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiaW1hZ2UiXX0=-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2664
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants