Skip to content

fix(web): indent subcategories in SetCategoryDialog#636

Merged
s0up4200 merged 4 commits intomainfrom
fix/category-dialog-subcategory-indent
Nov 25, 2025
Merged

fix(web): indent subcategories in SetCategoryDialog#636
s0up4200 merged 4 commits intomainfrom
fix/category-dialog-subcategory-indent

Conversation

@s0up4200
Copy link
Copy Markdown
Collaborator

@s0up4200 s0up4200 commented Nov 25, 2025

Closes #598

Summary by CodeRabbit

  • New Features

    • Optional subcategory support for hierarchical category organization across the app.
    • Category selection dialogs, management bars, context menus, and list rows now display and handle nested categories with indentation and proper selection.
    • Subcategory behavior is automatically enabled/disabled based on backend capability and user preference.
  • Performance

    • Large category lists are optimized for responsiveness via filtered/virtualized rendering.

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

@s0up4200 s0up4200 added this to the v1.8.0 milestone Nov 25, 2025
@s0up4200 s0up4200 added the web label Nov 25, 2025
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 25, 2025

Walkthrough

Derives a useSubcategories flag from instance capabilities and user preferences, threads it through UI and callbacks, extends onFilteredDataUpdate signature, and adds hierarchical category rendering to SetCategoryDialog using a CategoryTree when subcategories are enabled.

Changes

Cohort / File(s) Summary
Flag propagation & callers
web/src/components/torrents/TorrentCardsMobile.tsx, web/src/components/torrents/TorrentManagementBar.tsx, web/src/components/torrents/TorrentTableOptimized.tsx
Read capabilities and instance preferences to compute allowSubcategories (boolean) and pass it as useSubcategories into category dialogs/components; TorrentCardsMobile forwards backend useSubcategories to onFilteredDataUpdate.
SetCategoryDialog — hierarchical UI
web/src/components/torrents/TorrentDialogs.tsx
Add useSubcategories prop and Category typing; import and use buildCategoryTree / CategoryNode; implement tree-based filtering, flattening (with level/displayName), indentation rendering and virtualization when useSubcategories is true; retain flat list behavior when false.
Type & API updates
web/src/types/index.ts, web/src/components/torrents/TorrentDialogs.tsx, web/src/components/torrents/TorrentCardsMobile.tsx, web/src/components/torrents/TorrentTableOptimized.tsx
Add use_subcategories?: boolean to AppPreferences; change SetCategoryDialogProps.availableCategories to Record<string, Category> and add useSubcategories?: boolean; extend onFilteredDataUpdate(...) signature to include optional useSubcategories?: boolean; add optional useSubcategories prop to CompactRow and TorrentContextMenu usages.

Sequence Diagram(s)

sequenceDiagram
    participant Parent as Parent Component
    participant Cap as useInstanceCapabilities
    participant Meta as Instance Metadata
    participant SetDlg as SetCategoryDialog
    participant Tree as CategoryTree

    Parent->>Cap: fetch capabilities
    Parent->>Meta: read preferences
    Cap-->>Parent: { supportsSubcategories }
    Meta-->>Parent: { use_subcategories }
    Parent->>Parent: allowSubcategories = supportsSubcategories && use_subcategories
    Parent->>SetDlg: open(..., useSubcategories=allowSubcategories)

    alt allowSubcategories == true
        SetDlg->>Tree: buildCategoryTree(availableCategories)
        Tree-->>SetDlg: hierarchical nodes
        SetDlg->>SetDlg: flatten visible nodes (name, displayName, level)
        SetDlg-->>Parent: selected category (category.name)
    else allowSubcategories == false
        SetDlg->>SetDlg: filter/render flat category list
        SetDlg-->>Parent: selected category (flat name)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas to focus on:
    • Tree integration, flatten/cache correctness and performance in web/src/components/torrents/TorrentDialogs.tsx.
    • Updated onFilteredDataUpdate signature in TorrentCardsMobile.tsx and all call sites.
    • Consistent propagation of useSubcategories through UI components (ManagementBar, Table, CompactRow, ContextMenu) and TypeScript type updates.

Possibly related PRs

Suggested labels

bugfix, torrent

Poem

🐰 I hopped through nodes both shallow and deep,
Built trees of names for users to keep,
Indented leaves now easy to see,
Subcategories dancing, tidy as can be —
A tiny hop for UI harmony. 🥕

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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(web): indent subcategories in SetCategoryDialog' directly matches the main change described in issue #598: fixing subcategory indentation in the top-menu Set Category dialog.
Linked Issues check ✅ Passed The PR changes implement hierarchical category rendering with indentation in SetCategoryDialog, matching the requirements from issue #598. The implementation adds useSubcategories support, tree-based filtering, and level-based visual indentation throughout affected components.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing subcategory indentation support. While the changes span multiple files (TorrentCardsMobile, TorrentDialogs, TorrentManagementBar, TorrentTableOptimized, types), each modification directly supports the core objective of displaying indented subcategories in the SetCategoryDialog.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/category-dialog-subcategory-indent

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c21aa6 and 16ed3f9.

📒 Files selected for processing (4)
  • web/src/components/torrents/TorrentCardsMobile.tsx (7 hunks)
  • web/src/components/torrents/TorrentManagementBar.tsx (3 hunks)
  • web/src/components/torrents/TorrentTableOptimized.tsx (2 hunks)
  • web/src/types/index.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/src/components/torrents/TorrentTableOptimized.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
web/src/components/torrents/TorrentManagementBar.tsx (1)
web/src/hooks/useInstanceCapabilities.ts (1)
  • useInstanceCapabilities (15-34)
web/src/components/torrents/TorrentCardsMobile.tsx (2)
web/src/types/index.ts (3)
  • Torrent (172-229)
  • TorrentCounts (259-265)
  • Category (310-313)
internal/qbittorrent/sync_manager.go (1)
  • TorrentCounts (1657-1663)
🪛 Biome (2.1.2)
web/src/components/torrents/TorrentManagementBar.tsx

[error] 109-109: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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
🔇 Additional comments (5)
web/src/types/index.ts (1)

281-296: Subcategory flags are modeled consistently and look good

TorrentResponse.useSubcategories and AppPreferences.use_subcategories? align with existing patterns (ServerState.use_subcategories?) and being optional is appropriate for backward‑compat APIs. I don’t see any type‑level issues here; just ensure the backend actually populates these fields so the UI logic (e.g. allowSubcategories) doesn’t silently stay false on supported instances.

Also applies to: 725-731

web/src/components/torrents/TorrentManagementBar.tsx (1)

99-104: Subcategory capability + preference gating is wired correctly into SetCategoryDialog

The new allowSubcategories flag combines backend capability (supportsSubcategories) with user preference (preferences?.use_subcategories ?? false) and is then passed to SetCategoryDialog as useSubcategories. This matches the new types and gives you a clean single gate for hierarchical rendering in the top‑bar “Set Category” dialog. Just make sure metadata.preferences.use_subcategories is actually populated by the metadata endpoint so you don’t end up with capability enabled but UI still flat.

Also applies to: 108-113, 659-670

web/src/components/torrents/TorrentCardsMobile.tsx (3)

60-61: Cross-seed filter integration on mobile looks coherent

Importing GitBranch and useCrossSeedFilter, then wiring the “Filter Cross-Seeds” button only when onFilterChange is provided and exactly one torrent is selected, is a clean integration. The hook is still called unconditionally at the top level, so hooks rules are respected, and the isFilteringCrossSeeds flag is correctly used to disable the action while a filter is in progress.

Also applies to: 83-84, 1928-1941


311-323: Extended onFilteredDataUpdate callback and subcategory plumbing

Updating TorrentCardsMobileProps.onFilteredDataUpdate to include an optional trailing useSubcategories?: boolean and calling it with subcategoriesFromData keeps the runtime contract backward compatible while exposing the new flag to parents.

The combination:

  • useSubcategories: subcategoriesFromData from useTorrentsList, and
  • allowSubcategories = supportsSubcategories && (preferences?.use_subcategories ?? subcategoriesFromData ?? false)

is reasonable: parents see “server says subcategories are in play” via the callback, while dialogs gate on both capability and user preference. Just confirm that any existing onFilteredDataUpdate implementations have been updated to accept the new sixth argument in their TypeScript signatures (even if they ignore it), and that they indeed expect the raw backend state (subcategoriesFromData) rather than the UI‑gated allowSubcategories.

Also applies to: 1167-1175, 1190-1195, 1197-1203


1067-1071: Mobile Set Category dialog correctly honors preferences + capabilities

On mobile, you now:

  • Read preferences from metadata,
  • Derive supportsSubcategories from instance capabilities, and
  • Compute allowSubcategories = supportsSubcategories && (preferences?.use_subcategories ?? subcategoriesFromData ?? false),

then pass useSubcategories={allowSubcategories} into SetCategoryDialog.

This matches the new type surface and should ensure the bottom‑sheet category picker shows indented subcategories only when both the backend and user settings permit it. Given this is the mobile analog of the desktop behavior, it would be good to smoke‑test that the mobile and desktop dialogs stay in sync when toggling the subcategory preference.

Also applies to: 1187-1195, 2148-2158


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

Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/src/components/torrents/TorrentManagementBar.tsx (1)

93-113: Critical: Hook called after early return violates Rules of Hooks.

The early return at lines 93-95 causes useInstanceCapabilities (and other hooks below it) to be called conditionally. React requires hooks to be called in the same order on every render. When instanceId is invalid, the component returns early, but on subsequent renders with a valid instanceId, hooks will be called in a different order.

This can cause React to lose track of hook state, leading to bugs or crashes.

Move the guard logic after all hooks, or restructure to avoid conditional hook invocation:

 export const TorrentManagementBar = memo(function TorrentManagementBar({
   instanceId,
   selectedHashes = [],
   selectedTorrents = [],
   isAllSelected = false,
   totalSelectionCount = 0,
   totalSelectionSize = 0,
   filters,
   search,
   excludeHashes = [],
   onComplete,
 }: TorrentManagementBarProps) {
-  if (typeof instanceId !== "number" || instanceId <= 0) {
-    return null
-  }
+  const isValidInstanceId = typeof instanceId === "number" && instanceId > 0

   const selectionCount = totalSelectionCount || selectedHashes.length

   // Use shared metadata hook to leverage cache from table and filter sidebar
-  const { data: metadata, isLoading: isMetadataLoading } = useInstanceMetadata(instanceId)
+  const { data: metadata, isLoading: isMetadataLoading } = useInstanceMetadata(isValidInstanceId ? instanceId : 0)
   const availableTags = metadata?.tags || []
   const availableCategories = metadata?.categories || {}
   const preferences = metadata?.preferences

   const isLoadingTagsData = isMetadataLoading && availableTags.length === 0
   const isLoadingCategoriesData = isMetadataLoading && Object.keys(availableCategories).length === 0

   // Get capabilities to check subcategory support
-  const { data: capabilities } = useInstanceCapabilities(instanceId)
+  const { data: capabilities } = useInstanceCapabilities(isValidInstanceId ? instanceId : null)
   const supportsSubcategories = capabilities?.supportsSubcategories ?? false
   const allowSubcategories = Boolean(
     supportsSubcategories && (preferences?.use_subcategories ?? false)
   )

   // ... rest of hooks ...

+  // Keep this guard after hooks so their invocation order stays stable.
+  if (!isValidInstanceId) {
+    return null
+  }

Alternatively, use the enabled option in useInstanceCapabilities to disable the query when instanceId is invalid, ensuring all hooks are still called.

🧹 Nitpick comments (3)
web/src/components/torrents/TorrentDialogs.tsx (1)

31-31: Unused import: Torrent type is imported but not used in this file.

The Torrent type is imported alongside Category, but only Category is used in the SetCategoryDialogProps interface.

-import type { Category, Torrent } from "@/types"
+import type { Category } from "@/types"
web/src/components/torrents/TorrentCardsMobile.tsx (2)

1067-1071: Subcategory flags: consider unifying or documenting backendUseSubcategories vs allowSubcategories.

You now have two related booleans:

  • backendUseSubcategories from useTorrentsList (data-layer view).
  • allowSubcategories from capabilities + preferences?.use_subcategories, used for SetCategoryDialog.

If these are intended to represent the same “effective” behavior, it may be clearer to derive a single effectiveUseSubcategories and use it both for the callback and SetCategoryDialog. If they are intentionally different (e.g., backend vs. user-facing gating), a brief comment explaining the distinction would help future maintainers reason about when each should be used.

Also applies to: 1187-1193, 2148-2157


83-83: useCrossSeedFilter with optional onFilterChange: ensure the hook tolerates undefined.

onFilterChange is optional in props but is passed directly into useCrossSeedFilter. The button that calls filterCrossSeeds is already guarded behind onFilterChange && …, so the callback won’t be invoked without it, but the hook itself still sees onFilterChange (possibly undefined).

Confirm that useCrossSeedFilter handles a missing onFilterChange gracefully (e.g., checks before calling) rather than assuming it’s always defined. If not, consider either:

  • Making onFilterChange required in TorrentCardsMobileProps, or
  • Only calling useCrossSeedFilter when onFilterChange is present, or having the hook default to a no‑op when it’s not.

Also applies to: 1620-1623, 1927-1940

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd3caaf and a5feb94.

📒 Files selected for processing (4)
  • web/src/components/torrents/TorrentCardsMobile.tsx (7 hunks)
  • web/src/components/torrents/TorrentDialogs.tsx (6 hunks)
  • web/src/components/torrents/TorrentManagementBar.tsx (3 hunks)
  • web/src/components/torrents/TorrentTableOptimized.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
web/src/components/torrents/TorrentManagementBar.tsx (1)
web/src/hooks/useInstanceCapabilities.ts (1)
  • useInstanceCapabilities (15-34)
web/src/components/torrents/TorrentCardsMobile.tsx (2)
web/src/types/index.ts (3)
  • Torrent (172-229)
  • TorrentCounts (259-265)
  • Category (310-313)
internal/qbittorrent/sync_manager.go (1)
  • TorrentCounts (1657-1663)
🪛 Biome (2.1.2)
web/src/components/torrents/TorrentManagementBar.tsx

[error] 109-109: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

⏰ 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
🔇 Additional comments (8)
web/src/components/torrents/TorrentDialogs.tsx (3)

1106-1166: Well-structured category filtering with subcategory support.

The tree-building and filtering logic correctly handles:

  • Caching via Map to avoid redundant recursive calls
  • Inclusive filtering that shows parent nodes when children match the search query
  • Proper flattening of the tree structure with level information for indentation

The fallback path for non-subcategory mode maintains backward compatibility.


1250-1264: Virtualized category rendering correctly applies hierarchical indentation.

The implementation properly:

  • Uses category.name for selection and comparison
  • Applies paddingLeft based on category.level for visual hierarchy
  • Includes title attribute for accessibility on truncated names

1272-1288: Non-virtualized category rendering mirrors the virtualized path correctly.

Both rendering paths maintain consistent behavior for indentation and selection logic.

web/src/components/torrents/TorrentManagementBar.tsx (1)

670-670: Correct propagation of useSubcategories to SetCategoryDialog.

The allowSubcategories flag is correctly derived from both capability support and user preference, then passed to the dialog.

web/src/components/torrents/TorrentTableOptimized.tsx (3)

1000-1002: Correct derivation of allowSubcategories flag.

The logic correctly combines:

  1. Backend capability support (supportsSubcategories)
  2. User preference (preferences?.use_subcategories)
  3. Fallback to data-driven flag (subcategoriesFromData)

This ensures subcategory features are only enabled when both supported and configured.


2510-2510: Consistent propagation of useSubcategories to TorrentContextMenu.

Both compact and normal view paths correctly pass allowSubcategories to the context menu, ensuring consistent subcategory behavior across view modes.

Also applies to: 2609-2609


2951-2951: SetCategoryDialog receives useSubcategories prop.

The dialog correctly receives allowSubcategories to enable hierarchical category display when supported.

web/src/components/torrents/TorrentCardsMobile.tsx (1)

311-323: Upstream types are properly updated across all components—no TypeScript signature mismatches.

Verification confirms that all related type definitions and implementations have already been updated to include the trailing optional useSubcategories parameter:

  • TorrentTableResponsive.tsx (line 20): callback signature includes useSubcategories?: boolean
  • TorrentTableOptimized.tsx (line 601): callback signature includes useSubcategories?: boolean
  • TorrentTableOptimized.tsx (line 1120): passes nextUseSubcategories as the 6th argument ✓
  • Torrents.tsx (line 101): handler handleFilteredDataUpdate accepts subcategoriesEnabled?: boolean

The implementation is consistent and type-safe throughout. No updates are needed.

Copy link
Copy Markdown
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/components/torrents/TorrentCardsMobile.tsx (1)

1190-1195: Remove redundant Boolean() wrapper.

The expression supportsSubcategories && (preferences?.use_subcategories ?? subcategoriesFromData ?? false) already resolves to a boolean value. The outer Boolean() wrapper is unnecessary.

Apply this diff:

-  const allowSubcategories = Boolean(
-    supportsSubcategories && (preferences?.use_subcategories ?? subcategoriesFromData ?? false)
-  )
+  const allowSubcategories = supportsSubcategories && (preferences?.use_subcategories ?? subcategoriesFromData ?? false)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 461c1d8 and 6c21aa6.

📒 Files selected for processing (1)
  • web/src/components/torrents/TorrentCardsMobile.tsx (7 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
web/src/components/torrents/TorrentCardsMobile.tsx (2)
web/src/types/index.ts (3)
  • Torrent (172-229)
  • TorrentCounts (259-265)
  • Category (310-313)
internal/qbittorrent/sync_manager.go (1)
  • TorrentCounts (1657-1663)
⏰ 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
🔇 Additional comments (5)
web/src/components/torrents/TorrentCardsMobile.tsx (5)

60-60: LGTM - Missing imports added for existing functionality.

These imports support existing features (cross-seed filtering) that were already implemented but had missing imports. Good catch fixing these while updating the file.

Also applies to: 83-83


318-318: Callback signature correctly extended for subcategory support.

The useSubcategories parameter receives backend-derived subcategory state, which is appropriate for data filtering callbacks. This differs from the UI-level allowSubcategories (which combines backend capabilities with user preferences) that's passed to dialog components.


1070-1070: LGTM - Preferences extracted correctly.

Proper use of optional chaining to safely access preferences from instance metadata.


1198-1203: LGTM - Callback correctly invoked with backend subcategory state.

The effect properly passes subcategoriesFromData (backend state) to the callback and includes it in the dependency array. This ensures parent components receive data updates when subcategory configuration changes on the backend.


2158-2158: LGTM - Dialog correctly receives UI-level subcategory preference.

Passing allowSubcategories (which respects both backend capabilities and user preferences) enables SetCategoryDialog to render indented subcategories when appropriate. This aligns with the PR objective to fix subcategory indentation in the dialog.

@s0up4200 s0up4200 merged commit 3b60821 into main Nov 25, 2025
11 checks passed
alexlebens pushed a commit to alexlebens/infrastructure that referenced this pull request Dec 3, 2025
This PR contains the following updates:

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

---

### Release Notes

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

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

[Compare Source](autobrr/qui@v1.8.0...v1.8.1)

#### Changelog

##### Bug Fixes

- [`61c87e1`](autobrr/qui@61c87e1): fix(torznab): use detached context for indexer tests ([#&#8203;659](autobrr/qui#659)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.8.0...v1.8.1>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.8.1`
- `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!

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

[Compare Source](autobrr/qui@v1.7.0...v1.8.0)

#### Changelog

##### New Features

- [`6903812`](autobrr/qui@6903812): feat(crossseed): batch torrent file lookups end-to-end ([#&#8203;625](autobrr/qui#625)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`336ce48`](autobrr/qui@336ce48): feat(crossseed): persist seeded search settings ([#&#8203;618](autobrr/qui#618)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`7b0b292`](autobrr/qui@7b0b292): feat(docker): add curl to Dockerfiles ([#&#8203;570](autobrr/qui#570)) ([@&#8203;onedr0p](https://github.com/onedr0p))
- [`91e1677`](autobrr/qui@91e1677): feat(filters): default-hide empty status/category/tag groups ([#&#8203;581](autobrr/qui#581)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f07bb8d`](autobrr/qui@f07bb8d): feat(header): add missing links to header burger menu ([#&#8203;624](autobrr/qui#624)) ([@&#8203;nuxencs](https://github.com/nuxencs))
- [`ee4c16b`](autobrr/qui@ee4c16b): feat(instances): allow disabling qbit instances ([#&#8203;582](autobrr/qui#582)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`477db14`](autobrr/qui@477db14): feat(search): column filters ([#&#8203;633](autobrr/qui#633)) ([@&#8203;nuxencs](https://github.com/nuxencs))
- [`cd6db45`](autobrr/qui@cd6db45): feat(themes): add basic variation support ([#&#8203;569](autobrr/qui#569)) ([@&#8203;jabloink](https://github.com/jabloink))
- [`979a0d4`](autobrr/qui@979a0d4): feat(torrents): add clear filters action for empty filtered state ([#&#8203;627](autobrr/qui#627)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e06acb7`](autobrr/qui@e06acb7): feat(torrents): add cross-seeding and search ([#&#8203;553](autobrr/qui#553)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`95cef23`](autobrr/qui@95cef23): feat(torrents): add reannounce monitor ([#&#8203;606](autobrr/qui#606)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`098fdb0`](autobrr/qui@098fdb0): feat(torrents): add rename functionality in TorrentDetailsPanel ([#&#8203;590](autobrr/qui#590)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6e8fdbd`](autobrr/qui@6e8fdbd): feat(torrents): implement drag-and-drop file upload to add torrents ([#&#8203;568](autobrr/qui#568)) ([@&#8203;dthinhle](https://github.com/dthinhle))
- [`9240545`](autobrr/qui@9240545): feat(ui): add dense view mode for compact table display ([#&#8203;643](autobrr/qui#643)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`77fad15`](autobrr/qui@77fad15): feat(ui): improve torrent details panel file tree and rename UX ([#&#8203;650](autobrr/qui#650)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`8b1e70e`](autobrr/qui@8b1e70e): feat(web): Use original qBittorrent status names ([#&#8203;595](autobrr/qui#595)) ([@&#8203;FibreTTP](https://github.com/FibreTTP))
- [`01dd553`](autobrr/qui@01dd553): feat(web): show listening port in connectable status tooltip ([#&#8203;635](autobrr/qui#635)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3140739`](autobrr/qui@3140739): feat: make tracker icon column sortable ([#&#8203;513](autobrr/qui#513)) ([@&#8203;s0up4200](https://github.com/s0up4200))

##### Bug Fixes

- [`240b40d`](autobrr/qui@240b40d): fix(auth): avoid logout on license activation errors ([#&#8203;602](autobrr/qui#602)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`7185408`](autobrr/qui@7185408): fix(backups): do not persist ZIPs to disk ([#&#8203;632](autobrr/qui#632)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`de0e00a`](autobrr/qui@de0e00a): fix(content): use Hints for detection ([#&#8203;621](autobrr/qui#621)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`5f016a8`](autobrr/qui@5f016a8): fix(cross): performance improvements ([#&#8203;629](autobrr/qui#629)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`82c74ba`](autobrr/qui@82c74ba): fix(crossseed): flip deduplication to maps ([#&#8203;622](autobrr/qui#622)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`b78a079`](autobrr/qui@b78a079): fix(crossseed): inherit TMM state from matched torrent ([#&#8203;654](autobrr/qui#654)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2438fc6`](autobrr/qui@2438fc6): fix(crossseed): process full RSS feeds ([#&#8203;615](autobrr/qui#615)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`6f57090`](autobrr/qui@6f57090): fix(database): do not release mutex on tx err ([#&#8203;571](autobrr/qui#571)) ([@&#8203;KyleSanderson](https://github.com/KyleSanderson))
- [`74509d4`](autobrr/qui@74509d4): fix(incognito): prevent categories leaking ([#&#8203;592](autobrr/qui#592)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f08eff2`](autobrr/qui@f08eff2): fix(instances): support empty username for localhost bypass ([#&#8203;575](autobrr/qui#575)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`cd3caaf`](autobrr/qui@cd3caaf): fix(license): cap 7d offline grace, ignore transient errors ([#&#8203;617](autobrr/qui#617)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`59c747b`](autobrr/qui@59c747b): fix(reannounce): validate number fields and show min hints ([#&#8203;613](autobrr/qui#613)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`f6bd1e6`](autobrr/qui@f6bd1e6): fix(themes): correct Nightwalker description from purple to blue ([#&#8203;648](autobrr/qui#648)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`2b641c5`](autobrr/qui@2b641c5): fix(torznab): filter Prowlarr autodiscovery to enabled torrent indexers ([#&#8203;638](autobrr/qui#638)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`1995783`](autobrr/qui@1995783): fix(ui): improve cross-seed mobile responsiveness ([#&#8203;647](autobrr/qui#647)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`b83aebe`](autobrr/qui@b83aebe): fix(web): align CrossSeedDialog indexers with search flows ([#&#8203;619](autobrr/qui#619)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`3b60821`](autobrr/qui@3b60821): fix(web): indent subcategories in SetCategoryDialog ([#&#8203;636](autobrr/qui#636)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`82850cd`](autobrr/qui@82850cd): fix: glob pattern formatting in tooltip content ([#&#8203;579](autobrr/qui#579)) ([@&#8203;onedr0p](https://github.com/onedr0p))

##### Other Changes

- [`c20bc0a`](autobrr/qui@c20bc0a): build(vite): enable default minification ([#&#8203;574](autobrr/qui#574)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`ceac8ca`](autobrr/qui@ceac8ca): chore(ci): upgrade Claude Code workflow to Opus 4.5 ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`9d6c10e`](autobrr/qui@9d6c10e): chore(deps): bump actions/checkout from 5 to 6 in the github group ([#&#8203;628](autobrr/qui#628)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`f5704de`](autobrr/qui@f5704de): chore(deps): bump golang.org/x/crypto from 0.43.0 to 0.45.0 ([#&#8203;611](autobrr/qui#611)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`0aae9aa`](autobrr/qui@0aae9aa): chore(deps): bump the golang group with 3 updates ([#&#8203;546](autobrr/qui#546)) ([@&#8203;dependabot](https://github.com/dependabot)\[bot])
- [`0d97087`](autobrr/qui@0d97087): chore(themes): add crypto instructions in-app ([#&#8203;620](autobrr/qui#620)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`e778865`](autobrr/qui@e778865): docs(funding): add donation methods and crypto addresses ([#&#8203;583](autobrr/qui#583)) ([@&#8203;s0up4200](https://github.com/s0up4200))
- [`563645c`](autobrr/qui@563645c): docs: update qui image ([#&#8203;655](autobrr/qui#655)) ([@&#8203;s0up4200](https://github.com/s0up4200))

**Full Changelog**: <autobrr/qui@v1.7.0...v1.8.0>

#### Docker images

- `docker pull ghcr.io/autobrr/qui:v1.8.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:eyJjcmVhdGVkSW5WZXIiOiI0Mi41LjAiLCJ1cGRhdGVkSW5WZXIiOiI0Mi41LjAiLCJ0YXJnZXRCcmFuY2giOiJtYWluIiwibGFiZWxzIjpbImltYWdlIl19-->

Reviewed-on: https://gitea.alexlebens.dev/alexlebens/infrastructure/pulls/2211
Co-authored-by: Renovate Bot <renovate-bot@alexlebens.net>
Co-committed-by: Renovate Bot <renovate-bot@alexlebens.net>
@s0up4200 s0up4200 deleted the fix/category-dialog-subcategory-indent branch February 17, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Top-menu for modifying selected torrents doesn't indentate categories if subcategories are enabled

1 participant