Add sorting and filters for Seeker stats#576
Conversation
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
@greptileai review |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Adds table-style sorting and advanced filtering to Seeker Stats (Searches / Quality Scores / Upgrades), plus supporting UI primitives (drawer, sticky-aware headers) and backend query capabilities (new sort/filter params and a new LastUpgradedAt field).
Changes:
- Introduces a reusable filter drawer UI and sticky-header “stuck” styling hook (directive + SCSS updates).
- Adds sort + search + advanced filters to Seeker Stats tabs, backed by updated frontend API clients.
- Extends backend Seeker endpoints to support filtering/sorting and persists
LastUpgradedAtfor CF score entries (incl. migration/backfill).
Reviewed changes
Copilot reviewed 46 out of 47 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| code/frontend/src/styles/_page-animations.scss | New mixin for consistent staggered page-entry animations. |
| code/frontend/src/styles/_data-toolbar.scss | Updates sticky toolbar behavior to apply frosting only when “stuck” + transitions. |
| code/frontend/src/styles/_animations.scss | Tweaks slide-up animation keyframes. |
| code/frontend/src/app/ui/index.ts | Exposes new DrawerComponent from the UI barrel. |
| code/frontend/src/app/ui/drawer/drawer.component.ts | New drawer component (visibility model, escape/backdrop close, focus-first control). |
| code/frontend/src/app/ui/drawer/drawer.component.scss | Drawer/backdrop styling + responsive layout. |
| code/frontend/src/app/ui/drawer/drawer.component.html | Drawer structure + optional header/footer content projection. |
| code/frontend/src/app/features/strikes/strikes.component.ts | Wires in StickyAwareDirective for toolbar stuck-state styling. |
| code/frontend/src/app/features/strikes/strikes.component.scss | Switches to shared page stagger animation utilities. |
| code/frontend/src/app/features/strikes/strikes.component.html | Adds stickyAware to the toolbar element. |
| code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.ts | Adds search + sort + advanced filters drawer; updates API usage to query-object form. |
| code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss | Adds sticky toolbar + shared stagger animations + filter drawer/table styles. |
| code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.html | Adds search/sort controls and a drawer-based filter UI. |
| code/frontend/src/app/features/seeker-stats/seeker-stats.component.scss | Applies shared stagger animation; disables animation on tab content to avoid backdrop-filter clipping. |
| code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts | Adds sort + advanced filters drawer; updates events query call to use new API query object. |
| code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss | Adds sticky toolbar + shared stagger animations + drawer/chip styling. |
| code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html | Adds sort controls and a drawer for cycle/status/type/reason/grabbed filters. |
| code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.ts | Adds sorting, quality-profile/cutoff/monitored filters via drawer; updates API call to query-object form. |
| code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss | Adds sticky toolbar + shared stagger animations + drawer/table/detail styles. |
| code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.html | Adds sort controls, last-upgraded display, and filter drawer UI. |
| code/frontend/src/app/features/logs-component/logs.component.ts | Wires in StickyAwareDirective for toolbar stuck-state styling. |
| code/frontend/src/app/features/logs-component/logs.component.scss | Switches to shared page stagger animation utilities. |
| code/frontend/src/app/features/logs-component/logs.component.html | Adds stickyAware to the toolbar element. |
| code/frontend/src/app/features/events/events.component.ts | Wires in StickyAwareDirective for toolbar stuck-state styling. |
| code/frontend/src/app/features/events/events.component.scss | Switches to shared page stagger animation utilities. |
| code/frontend/src/app/features/events/events.component.html | Adds stickyAware to the toolbar element. |
| code/frontend/src/app/features/dashboard/dashboard.component.ts | Updates recent upgrades call to new query-object API signature. |
| code/frontend/src/app/features/dashboard/dashboard.component.scss | Uses shared stagger animation utility for dashboard rows. |
| code/frontend/src/app/core/directives/sticky-aware.directive.ts | New directive toggling .is-stuck based on sticky intersection state. |
| code/frontend/src/app/core/api/search-stats.api.ts | Adds typed query object + sorting/filtering params and HttpParams construction. |
| code/frontend/src/app/core/api/cf-score.api.ts | Adds typed query objects + enums for sorting/filtering; updates endpoints to accept richer params. |
| code/backend/Cleanuparr.Persistence/Models/State/CustomFormatScoreEntry.cs | Adds LastUpgradedAt column to persisted CF score entries. |
| code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs | EF snapshot updated for LastUpgradedAt + index. |
| code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cs | Migration adds last_upgraded_at, index, and backfills from history via SQL. |
| code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.Designer.cs | EF migration designer output for the new schema. |
| code/backend/Cleanuparr.Persistence/DataContext.cs | Adds index + UTC conversion for LastUpgradedAt. |
| code/backend/Cleanuparr.Infrastructure/Features/Jobs/CustomFormatScoreSyncer.cs | Sets LastUpgradedAt when a score increase is detected during sync. |
| code/backend/Cleanuparr.Domain/Enums/SortDirection.cs | New shared sort-direction enum for API query params. |
| code/backend/Cleanuparr.Domain/Enums/SearchEventsSortBy.cs | New sort key enum for search events. |
| code/backend/Cleanuparr.Domain/Enums/MonitoredFilter.cs | New monitored-state filter enum for CF scores. |
| code/backend/Cleanuparr.Domain/Enums/CutoffFilter.cs | New cutoff filter enum for CF scores. |
| code/backend/Cleanuparr.Domain/Enums/CfUpgradesSortBy.cs | New sort key enum for CF upgrades. |
| code/backend/Cleanuparr.Domain/Enums/CfScoresSortBy.cs | New sort key enum for CF scores. |
| code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs | Adds query params + filtering + sorting for search events endpoint. |
| code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs | Adds query params + filters + sorting for scores and upgrades endpoints; extends instances response. |
| code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/CustomFormatScoreEntryResponse.cs | Includes LastUpgradedAt in API response contract. |
| code/backend/Cleanuparr.Api.Tests/Features/Seeker/CustomFormatScoreControllerTests.cs | Updates tests to use new filter enums and sort enum. |
Files not reviewed (1)
- code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Greptile SummaryThis PR adds server-side sorting, filtering, and pagination to the Seeker stats tabs (CF score quality, upgrades, and search events). The most significant backend change replaces the previous in-memory upgrade-detection loop in Confidence Score: 5/5Safe to merge; only P2 findings remain (stale doc comment and a defensive-coding suggestion). All remaining findings are P2: a doc-comment typo on pageSize range and a non-defensive Enum.Parse that is safe in practice since values come from EF Core writes. The previously flagged full-scan concern is directly addressed by the new SQL CTE approach. SearchStatsController.cs (doc comment) and CustomFormatScoreController.cs (Enum.Parse). Important Files Changed
Sequence DiagramsequenceDiagram
participant FE as Frontend Tab
participant API as Controller
participant DB as SQLite (DataContext)
Note over FE,DB: CF Score Upgrades (new SQL path)
FE->>API: GET /api/seeker/cf-scores/upgrades?page=1&sortBy=UpgradedAt&days=30
API->>DB: CTE: WITH scored (LAG over full history), upgrades WHERE score > prev_score
DB-->>API: UpgradeSqlRow[]
API->>DB: CTE + SELECT COUNT(*) FROM upgrades WHERE filters
DB-->>API: totalCount
API-->>FE: { Items, Page, TotalCount, TotalPages }
Note over FE,DB: CF Score Entries (LINQ path)
FE->>API: GET /api/seeker/cf-scores?cutoffFilter=Below&sortBy=CurrentScore
API->>DB: EF Core query with Where+OrderBy+Skip+Take
DB-->>API: CustomFormatScoreEntry[]
API-->>FE: { Items, Page, TotalCount, TotalPages }
Note over FE,DB: Search Events
FE->>API: GET /api/seeker/search-stats/events?sortBy=Timestamp&searchStatus=Completed
API->>DB: EventsContext LINQ with filters + ThenByDescending(Timestamp)
DB-->>API: AppEvent[] (page)
API->>DB: ArrInstances lookup for InstanceType resolution
DB-->>API: instanceTypeMap
API-->>FE: { Items, Page, TotalCount, TotalPages }
Reviews (2): Last reviewed commit: "added tests for new seeker sort and filt..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs (1)
120-135:⚠️ Potential issue | 🟡 MinorDocument the expanded query contract.
GetEventsnow exposes a much larger public query surface, but the XML docs still only give a one-line summary. Please add<param>docs for the new sort and filter inputs, plus the default ordering behavior.As per coding guidelines "Add XML documentation comments for public APIs in C#".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs` around lines 120 - 135, Update the XML docs on the public GetEvents action to fully document the expanded query contract: expand the one-line <summary> into a brief description of what the endpoint returns and add <param> entries for page, pageSize, instanceId, cycleId, search, sortBy, sortDirection, searchStatus, searchType, searchReason and grabbed describing each parameter's purpose and accepted values where applicable; explicitly document default values (page=1, pageSize=50) and the default ordering behavior (sortBy defaults to SearchEventsSortBy.Timestamp and sortDirection defaults to Desc) so consumers know how results are filtered and ordered.code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.ts (1)
199-223:⚠️ Potential issue | 🟠 MajorPrevent stale responses from overwriting newer filter state.
Each interaction fires a fresh request, but older in-flight responses still update the same signals when they finish. If a slower previous request resolves last, the table can end up showing rows/counts for the wrong sort or filter selection.
💡 One lightweight guard
export class UpgradesTabComponent implements OnInit { private static readonly PAGE_SIZE_KEY = 'cleanuparr-page-size-seeker-upgrades'; + private latestLoadToken = 0; @@ private loadUpgrades(): void { this.loading.set(true); + const loadToken = ++this.latestLoadToken; const a = this.applied(); const days = parseInt(a.timeRange, 10); const instanceId = this.selectedInstanceId() || undefined; @@ }).subscribe({ next: (result) => { + if (loadToken !== this.latestLoadToken) return; this.upgrades.set(result.items); this.totalRecords.set(result.totalCount); this.loading.set(false); }, error: () => { + if (loadToken !== this.latestLoadToken) return; this.loading.set(false); this.toast.error('Failed to load upgrades'); }, }); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.ts` around lines 199 - 223, The loadUpgrades method can let out-of-order responses overwrite newer state; add a lightweight request guard by stamping each call with a monotonic requestId (e.g., increment a private this._latestRequestId before calling this.api.getRecentUpgrades) and capture that id in the subscribe callbacks for next and error; inside next/error only apply this.upgrades.set, this.totalRecords.set, this.loading.set, and this.toast.error if the captured id equals this._latestRequestId, otherwise ignore the response. Update the loadUpgrades method and declare the private this._latestRequestId field used by the guard.code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs (1)
288-318:⚠️ Potential issue | 🟠 MajorThis endpoint materializes far more rows than it needs.
ToListAsync()happens before any deduplication, so the instances endpoint loads one row per tracked score entry just to build filter options. On large libraries that makes this action O(all score rows), and the laterDistinct()is still case-sensitive, so mixed-case profile names can appear twice. Push the distinct projection into SQL first, then collapse profile names withStringComparer.OrdinalIgnoreCase.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs` around lines 288 - 318, The code fetches all score-entry rows into raw via ToListAsync() before deduplicating, and Distinct() is case-sensitive; change the LINQ to perform the distinct projection in SQL before materialization: move the .Select(...) projection (projecting ArrInstanceId/Name/ItemType and a case-normalized QualityProfileName, e.g. e.QualityProfileName.ToLower() or ToUpper()) immediately before calling ToListAsync() so the database returns one row per distinct profile name per instance, then perform the in-memory grouping (the instances variable) using StringComparer.OrdinalIgnoreCase to collapse any remaining case variants; look for _dataContext.CustomFormatScoreEntries, the anonymous projection that creates Id/Name/ItemType/QualityProfileName, the ToListAsync() call on raw, and the later Distinct()/OrderBy usage to apply this change.
🧹 Nitpick comments (4)
code/frontend/src/app/core/api/search-stats.api.ts (1)
19-30: Type the remaining enum-backed filters end-to-end.
searchStatus,searchType, andsearchReasonare still plain strings, so callers can build invalid requests without any compile-time signal. Prefer enum or string-literal types here as well to keep the frontend contract aligned with the backend.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/frontend/src/app/core/api/search-stats.api.ts` around lines 19 - 30, SearchEventsQuery currently types searchStatus, searchType, and searchReason as plain string arrays; update them to enum/string-literal types to match backend contracts and provide compile-time safety. Replace searchStatus?: string[] with the appropriate enum/union type (e.g., SearchStatus[]), searchType?: string[] with SearchType[] and searchReason?: string[] with SearchReason[] (or the existing string-literal unions), import or reference the existing enums/types used elsewhere in the codebase, and update any callers/tests to use the enum values instead of raw strings.code/frontend/src/styles/_page-animations.scss (1)
9-19: Consider reduced-motion support in the shared stagger mixin.Since this utility is now reused broadly, adding a
prefers-reduced-motionbranch here avoids repeated per-page overrides.♻️ Proposed enhancement
`@mixin` page-section-stagger($increment: 40ms, $start: 0ms, $count: 10) { > * { animation: slide-up var(--duration-normal) var(--ease-default) both; } `@for` $i from 1 through $count { > :nth-child(#{$i}) { animation-delay: #{$start + ($i - 1) * $increment}; } } + + `@media` (prefers-reduced-motion: reduce) { + > * { + animation: none; + } + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/frontend/src/styles/_page-animations.scss` around lines 9 - 19, The shared mixin page-section-stagger should respect users' reduced-motion preference: inside the mixin (mixin page-section-stagger) add a prefers-reduced-motion: reduce branch that disables animations for the children (e.g., set animation: none and remove animation-delay/transition behavior or set transition-duration/animation-duration to 0) so pages using this mixin don't need per-page overrides; ensure both the > * rule (animation: slide-up ...) and the per-child animation-delay rules are overridden inside the `@media` (prefers-reduced-motion: reduce) block to fully disable the staggered animation.code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cs (1)
20-23: Create the new index after the backfill.Right now the migration builds
ix_custom_format_score_entries_last_upgraded_atbefore the bulkUPDATE, so every populated row also updates the index. On larger seeker datasets that can make startup migrations much slower than necessary.💡 Proposed fix
- migrationBuilder.CreateIndex( - name: "ix_custom_format_score_entries_last_upgraded_at", - table: "custom_format_score_entries", - column: "last_upgraded_at"); - // Backfill last_upgraded_at from existing history: per item, the most recent // recorded_at at which the score strictly exceeded the preceding score. migrationBuilder.Sql(@" @@ UPDATE custom_format_score_entries SET last_upgraded_at = ( SELECT last_upgraded_at FROM upgrades u WHERE u.arr_instance_id = custom_format_score_entries.arr_instance_id AND u.external_item_id = custom_format_score_entries.external_item_id AND u.episode_id = custom_format_score_entries.episode_id ); "); + + migrationBuilder.CreateIndex( + name: "ix_custom_format_score_entries_last_upgraded_at", + table: "custom_format_score_entries", + column: "last_upgraded_at");Also applies to: 27-58
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cs` around lines 20 - 23, The migration currently calls migrationBuilder.CreateIndex for "ix_custom_format_score_entries_last_upgraded_at" on table "custom_format_score_entries" before performing the bulk backfill UPDATE (see the backfill block around lines 27-58); move the CreateIndex call so it runs after the backfill completes (or instead drop/recreate the index after the UPDATE), ensuring the index creation occurs only once after rows are populated to avoid per-row index updates during the backfill.code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs (1)
288-320: Add XML docs for the updated public endpoint.
GetInstancesnow returns a richer payload, but this action still has no XML documentation like the other public endpoints in this controller.As per coding guidelines "Add XML documentation comments for public APIs in C#".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs` around lines 288 - 320, Add XML documentation comments to the public action that returns instances (the GetInstances endpoint in CustomFormatScoreController) describing purpose, parameters (if any), and the response shape; include a <summary> that explains the richer payload, a <returns> describing that it returns an OkObjectResult with an Instances collection containing Id, Name, ItemType and QualityProfiles, and any <remarks> or <response> tags required by project conventions so the public API aligns with other documented endpoints in this controller.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/CustomFormatScoreEntryResponse.cs`:
- Line 20: Add an XML documentation comment for the public property
LastUpgradedAt on the CustomFormatScoreEntryResponse record to describe its
purpose and semantics; include a <summary> that explains this nullable DateTime
indicates when the entry was last upgraded (or null if never upgraded) and, if
relevant, a <remarks> or <value> note about time zone or format expectations so
API consumers understand the contract.
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs`:
- Around line 195-198: The current code applies the days/RecordedAt filter to
the IQueryable (variable query) before computing upgrades, which removes the
prior-row baseline needed to detect improvements at the window edge; modify the
flow so you first compute per-item upgrades (use the full time series or include
the last pre-window row as baseline), populate an UpgradedAt timestamp or flag
for each item, and only then apply the days filter (i.e., filter by UpgradedAt
or by the computed upgrade flag) instead of trimming by RecordedAt up front;
locate the logic in CustomFormatScoreController where query is built and
adjusted, and change the order so upgrade detection (and any projection that
produces UpgradedAt) happens before applying the days filter.
In `@code/backend/Cleanuparr.Domain/Enums/CfScoresSortBy.cs`:
- Around line 6-14: Public enum CfScoresSortBy and its members (Title,
CurrentScore, CutoffScore, QualityProfile, LastSyncedAt, LastUpgradedAt) are
missing XML documentation; add XML summary comments above the enum declaration
and above each enum member describing the purpose of the enum and what each sort
key represents (e.g., "Sort by title", "Sort by current score", etc.), following
the project's XML doc style so the public API is documented for consumers and
tooling.
In `@code/backend/Cleanuparr.Domain/Enums/CfUpgradesSortBy.cs`:
- Around line 5-14: Add XML documentation comments to the public enum
CfUpgradesSortBy and each of its values (UpgradedAt, Title, NewScore,
PreviousScore, ScoreDelta, CutoffScore): add a summary for the enum describing
its purpose (used to specify the sort order for CF upgrades) and add brief
<summary> lines for each member explaining what field they sort by (e.g.,
UpgradedAt = sort by upgrade timestamp, Title = sort by item title, NewScore =
sort by new score after upgrade, PreviousScore = sort by score before upgrade,
ScoreDelta = sort by change in score, CutoffScore = sort by cutoff threshold).
Ensure the XML comments follow standard C# doc format (///
<summary>...</summary>) placed immediately above the enum and each enum member.
In `@code/backend/Cleanuparr.Domain/Enums/CutoffFilter.cs`:
- Around line 6-11: Add XML documentation comments to the public enum
CutoffFilter and each of its members (All, Below, Met): describe the enum's
purpose and the semantic meaning of each value so the public API contract is
clear; update the declaration for CutoffFilter and add ///
<summary>...</summary> comments for All, Below, and Met following the project's
XML doc style and conventions.
In `@code/backend/Cleanuparr.Domain/Enums/MonitoredFilter.cs`:
- Around line 6-11: Add XML documentation for the public enum MonitoredFilter
and each of its members (All, Monitored, Unmonitored): add a summary on the enum
describing its purpose (e.g., indicates which items are monitored) and add
individual <summary> comments for All, Monitored, and Unmonitored explaining
what each value represents; ensure the comments follow C# XML doc style so the
public API is self-describing and satisfies the coding guideline.
In `@code/backend/Cleanuparr.Domain/Enums/SearchEventsSortBy.cs`:
- Around line 5-12: Add XML documentation comments for the public enum
SearchEventsSortBy and each of its members (Timestamp, Title, Status, Type): add
a /// <summary> for the enum describing its purpose in sorting search event
results and individual /// <summary> comments for each member explaining what
field or behavior they represent when used for sorting; keep the JsonConverter
attribute intact and ensure the comments are placed immediately above the enum
and each enum member so generated API docs include meaningful descriptions.
In `@code/backend/Cleanuparr.Domain/Enums/SortDirection.cs`:
- Around line 5-10: Add XML documentation comments to the public enum
SortDirection and its members (SortDirection, Asc, Desc) so the public contract
has XML docs; update the SortDirection declaration (the enum marked with
[JsonConverter(typeof(JsonStringEnumConverter))]) to include a summary
describing what the enum represents, and add <summary> comments for each member
(Asc — ascending sort order, Desc — descending sort order), following the
project's XML doc style.
In
`@code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss`:
- Around line 289-293: Replace the deprecated "word-break: break-word" in
quality-tab.component.scss by setting "word-break: normal" and adding
"overflow-wrap: anywhere" (or "overflow-wrap: break-word" if you prefer legacy
behavior) for the &__text rule and the other similar rule later in the file;
locate the &__text selector in the quality-tab component and update its
declarations to use overflow-wrap and normal word-break to satisfy Stylelint.
In
`@code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.html`:
- Around line 223-228: The status-chip button currently conveys selection only
via CSS; update the template for the button in searches-tab.component.html to
expose that state to assistive tech by adding an aria-pressed binding that uses
the same predicate as the visual state (isStatusDrafted(opt.value)); keep the
existing [class.chip--active]="isStatusDrafted(opt.value)" and
(click)="toggleStatus(opt.value)" intact and bind aria-pressed to
isStatusDrafted(opt.value) so screen readers can detect pressed/unpressed state.
In
`@code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scss`:
- Around line 280-284: The rule in the searches-tab.component.scss selector
&__value uses the deprecated property "word-break: break-word"; replace that
with a non-deprecated wrapping rule by removing "word-break: break-word" and
adding "overflow-wrap: anywhere;" (or "overflow-wrap: break-word;" if you prefer
less aggressive breaking) so the selector still breaks long words without
Stylelint errors.
In
`@code/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.ts`:
- Around line 341-347: The code only derives cycleId when instanceId is present,
so when a.cycleFilter === 'current' but instanceId is cleared the request ends
up unfiltered; update the logic in searches-tab.component.ts around the
applied()/cycleId block so that if a.cycleFilter === 'current' and instanceId is
falsy you normalize the filter before querying—either convert a.cycleFilter to
'all' (or another explicit fallback) or explicitly set cycleId to undefined and
update the applied filter object; locate the applied() result, the cycleFilter
variable, instanceId, summary()?.perInstanceStats and currentCycleId usage and
ensure the request uses the normalized cycleFilter/cycleId consistently.
In `@code/frontend/src/app/features/seeker-stats/seeker-stats.component.scss`:
- Around line 12-15: In seeker-stats.component.scss add a single blank line
immediately before the inline comment that begins "// Chromium retains the
compositor layer..." so the comment block is separated from the prior rule;
ensure the comment stays directly above the "animation: none;" declaration and
that there is an empty line above the comment to satisfy stylelint spacing
rules.
In
`@code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss`:
- Around line 168-172: In the &__text rule (selector &__text in
upgrades-tab.component.scss) replace the deprecated declaration "word-break:
break-word" with the modern equivalent "overflow-wrap: anywhere" (remove the
deprecated property and add overflow-wrap: anywhere so long words will break
correctly while preserving the existing font-weight and color).
In `@code/frontend/src/app/ui/drawer/drawer.component.html`:
- Around line 6-13: Make the aside's aria-labelledby conditional and use a
per-instance unique id for the title to avoid missing references and ID
collisions: generate a unique string (e.g., drawerTitleId) in the
DrawerComponent class, bind the H2's id to that value when title() is truthy
(id="{{drawerTitleId}}" or the framework binding equivalent), and set the
aside's aria-labelledby only when title() exists (otherwise remove it and
require/provide an aria-label fallback input). Update template bindings
referencing title() and the header H2 (drawer__title) and keep the close()
button unchanged.
In `@code/frontend/src/app/ui/drawer/drawer.component.scss`:
- Around line 19-21: The .drawer rule violates stylelint because there is no
blank line between the mixin include and the first declaration; update the
.drawer block (the `@include` glass('elevated') line and the position: relative
declaration) to ensure there is a single blank line separating the mixin include
and the first CSS declaration so stylelint passes.
In `@code/frontend/src/app/ui/drawer/drawer.component.ts`:
- Around line 19-60: When the drawer opens save the currently focused element
and add Angular CDK focus-trap to the drawer element, then restore focus on
close/destroy: in the effect inside the constructor (where you detect
this.visible()) capture document.activeElement (store e.g. this.previousFocus)
before calling focusFirstControl(); add cdkTrapFocus to the drawer root element
in the component template so Tab/Shift+Tab is trapped; update close() to restore
focus to this.previousFocus (if it’s focusable and still in the document) before
emitting closed and setting visible false; also restore focus in ngOnDestroy()
to cover unmounts; keep using focusFirstControl() to pick the initial focus
target.
---
Outside diff comments:
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs`:
- Around line 288-318: The code fetches all score-entry rows into raw via
ToListAsync() before deduplicating, and Distinct() is case-sensitive; change the
LINQ to perform the distinct projection in SQL before materialization: move the
.Select(...) projection (projecting ArrInstanceId/Name/ItemType and a
case-normalized QualityProfileName, e.g. e.QualityProfileName.ToLower() or
ToUpper()) immediately before calling ToListAsync() so the database returns one
row per distinct profile name per instance, then perform the in-memory grouping
(the instances variable) using StringComparer.OrdinalIgnoreCase to collapse any
remaining case variants; look for _dataContext.CustomFormatScoreEntries, the
anonymous projection that creates Id/Name/ItemType/QualityProfileName, the
ToListAsync() call on raw, and the later Distinct()/OrderBy usage to apply this
change.
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs`:
- Around line 120-135: Update the XML docs on the public GetEvents action to
fully document the expanded query contract: expand the one-line <summary> into a
brief description of what the endpoint returns and add <param> entries for page,
pageSize, instanceId, cycleId, search, sortBy, sortDirection, searchStatus,
searchType, searchReason and grabbed describing each parameter's purpose and
accepted values where applicable; explicitly document default values (page=1,
pageSize=50) and the default ordering behavior (sortBy defaults to
SearchEventsSortBy.Timestamp and sortDirection defaults to Desc) so consumers
know how results are filtered and ordered.
In
`@code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.ts`:
- Around line 199-223: The loadUpgrades method can let out-of-order responses
overwrite newer state; add a lightweight request guard by stamping each call
with a monotonic requestId (e.g., increment a private this._latestRequestId
before calling this.api.getRecentUpgrades) and capture that id in the subscribe
callbacks for next and error; inside next/error only apply this.upgrades.set,
this.totalRecords.set, this.loading.set, and this.toast.error if the captured id
equals this._latestRequestId, otherwise ignore the response. Update the
loadUpgrades method and declare the private this._latestRequestId field used by
the guard.
---
Nitpick comments:
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs`:
- Around line 288-320: Add XML documentation comments to the public action that
returns instances (the GetInstances endpoint in CustomFormatScoreController)
describing purpose, parameters (if any), and the response shape; include a
<summary> that explains the richer payload, a <returns> describing that it
returns an OkObjectResult with an Instances collection containing Id, Name,
ItemType and QualityProfiles, and any <remarks> or <response> tags required by
project conventions so the public API aligns with other documented endpoints in
this controller.
In
`@code/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cs`:
- Around line 20-23: The migration currently calls migrationBuilder.CreateIndex
for "ix_custom_format_score_entries_last_upgraded_at" on table
"custom_format_score_entries" before performing the bulk backfill UPDATE (see
the backfill block around lines 27-58); move the CreateIndex call so it runs
after the backfill completes (or instead drop/recreate the index after the
UPDATE), ensuring the index creation occurs only once after rows are populated
to avoid per-row index updates during the backfill.
In `@code/frontend/src/app/core/api/search-stats.api.ts`:
- Around line 19-30: SearchEventsQuery currently types searchStatus, searchType,
and searchReason as plain string arrays; update them to enum/string-literal
types to match backend contracts and provide compile-time safety. Replace
searchStatus?: string[] with the appropriate enum/union type (e.g.,
SearchStatus[]), searchType?: string[] with SearchType[] and searchReason?:
string[] with SearchReason[] (or the existing string-literal unions), import or
reference the existing enums/types used elsewhere in the codebase, and update
any callers/tests to use the enum values instead of raw strings.
In `@code/frontend/src/styles/_page-animations.scss`:
- Around line 9-19: The shared mixin page-section-stagger should respect users'
reduced-motion preference: inside the mixin (mixin page-section-stagger) add a
prefers-reduced-motion: reduce branch that disables animations for the children
(e.g., set animation: none and remove animation-delay/transition behavior or set
transition-duration/animation-duration to 0) so pages using this mixin don't
need per-page overrides; ensure both the > * rule (animation: slide-up ...) and
the per-child animation-delay rules are overridden inside the `@media`
(prefers-reduced-motion: reduce) block to fully disable the staggered animation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 82e1f011-b780-45cb-95f3-4032b2c48116
📒 Files selected for processing (47)
code/backend/Cleanuparr.Api.Tests/Features/Seeker/CustomFormatScoreControllerTests.cscode/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/CustomFormatScoreEntryResponse.cscode/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cscode/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cscode/backend/Cleanuparr.Domain/Enums/CfScoresSortBy.cscode/backend/Cleanuparr.Domain/Enums/CfUpgradesSortBy.cscode/backend/Cleanuparr.Domain/Enums/CutoffFilter.cscode/backend/Cleanuparr.Domain/Enums/MonitoredFilter.cscode/backend/Cleanuparr.Domain/Enums/SearchEventsSortBy.cscode/backend/Cleanuparr.Domain/Enums/SortDirection.cscode/backend/Cleanuparr.Infrastructure/Features/Jobs/CustomFormatScoreSyncer.cscode/backend/Cleanuparr.Persistence/DataContext.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.Designer.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cscode/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cscode/backend/Cleanuparr.Persistence/Models/State/CustomFormatScoreEntry.cscode/frontend/src/app/core/api/cf-score.api.tscode/frontend/src/app/core/api/search-stats.api.tscode/frontend/src/app/core/directives/sticky-aware.directive.tscode/frontend/src/app/features/dashboard/dashboard.component.scsscode/frontend/src/app/features/dashboard/dashboard.component.tscode/frontend/src/app/features/events/events.component.htmlcode/frontend/src/app/features/events/events.component.scsscode/frontend/src/app/features/events/events.component.tscode/frontend/src/app/features/logs-component/logs.component.htmlcode/frontend/src/app/features/logs-component/logs.component.scsscode/frontend/src/app/features/logs-component/logs.component.tscode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.htmlcode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scsscode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.tscode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.htmlcode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scsscode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.tscode/frontend/src/app/features/seeker-stats/seeker-stats.component.scsscode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.htmlcode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scsscode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.tscode/frontend/src/app/features/strikes/strikes.component.htmlcode/frontend/src/app/features/strikes/strikes.component.scsscode/frontend/src/app/features/strikes/strikes.component.tscode/frontend/src/app/ui/drawer/drawer.component.htmlcode/frontend/src/app/ui/drawer/drawer.component.scsscode/frontend/src/app/ui/drawer/drawer.component.tscode/frontend/src/app/ui/index.tscode/frontend/src/styles/_animations.scsscode/frontend/src/styles/_data-toolbar.scsscode/frontend/src/styles/_page-animations.scss
4b3a152 to
298881b
Compare
|
@greptileai review |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Caution Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted. Error details |
There was a problem hiding this comment.
🧹 Nitpick comments (6)
code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs (3)
179-181: Use a fixed baseline timestamp for deterministic ordering.Using
DateTime.UtcNowthree times can make this test clock-dependent. Prefer a single fixed UTC baseline and derive offsets from it.Suggested tweak
- AddSearchEvent(itemTitle: "Newest", timestamp: DateTime.UtcNow); - AddSearchEvent(itemTitle: "Oldest", timestamp: DateTime.UtcNow.AddHours(-2)); - AddSearchEvent(itemTitle: "Middle", timestamp: DateTime.UtcNow.AddHours(-1)); + var baseTime = new DateTime(2026, 1, 1, 12, 0, 0, DateTimeKind.Utc); + AddSearchEvent(itemTitle: "Newest", timestamp: baseTime); + AddSearchEvent(itemTitle: "Oldest", timestamp: baseTime.AddHours(-2)); + AddSearchEvent(itemTitle: "Middle", timestamp: baseTime.AddHours(-1));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs` around lines 179 - 181, The three AddSearchEvent calls use DateTime.UtcNow separately causing non-deterministic ordering; fix by creating a single fixed UTC baseline (e.g., DateTime baseline = new DateTime(2020,1,1,0,0,0, DateTimeKind.Utc) or capture DateTime baseline = DateTime.UtcNow once) in SearchStatsControllerTests and replace the timestamps with baseline, baseline.AddHours(-2), and baseline.AddHours(-1) when calling AddSearchEvent(itemTitle: "...", timestamp: ...).
192-203: Strengthen status-filter assertions beyond count-only checks.
TotalCount == 2can still pass with wrong records. Assert the returned statuses or titles explicitly to verify filter correctness.Suggested tweak
body.GetProperty("TotalCount").GetInt32().ShouldBe(2); + var items = body.GetProperty("Items"); + items.GetArrayLength().ShouldBe(2); + items.EnumerateArray().Any(i => i.GetProperty("ItemTitle").GetString() == "A").ShouldBeTrue(); + items.EnumerateArray().Any(i => i.GetProperty("ItemTitle").GetString() == "B").ShouldBeTrue();Based on learnings: "Write unit tests for new features and bug fixes with descriptive test names".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs` around lines 192 - 203, The test GetEvents_WithSearchStatusFilter_ReturnsOnlyMatchingStatuses currently only asserts TotalCount == 2 which can miss wrong records; update the test in SearchStatsControllerTests to also inspect the response array from _controller.GetEvents (use GetResponseBody(result) and enumerate the events collection) and assert that each returned item's searchStatus (or itemTitle) matches one of the allowed enums SearchCommandStatus.Completed or SearchCommandStatus.Failed (and/or assert the returned titles are exactly "A" and "B"), so the filter logic in GetEvents is verified by content not just count.
245-255: Add coverage forgrabbed=falsewhenSearchEventDatais null.The controller also includes events with no
SearchEventDataforgrabbed=false(seeSearchStatsController.csline range 209-220), but this test currently covers only the empty grabbed-list branch.Based on learnings: "Write unit tests for new features and bug fixes with descriptive test names".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs` around lines 245 - 255, The test covers only events whose SearchEventData has an empty grabbed list; add coverage for the branch where SearchEventData is null to ensure grabbed=false includes events with no SearchEventData. Update or create a test (e.g., alongside GetEvents_WithGrabbedFalse_KeepsOnlyEventsWithoutGrabbedItems) that calls AddSearchEvent with SearchEventData set to null (or a helper overload) and then calls _controller.GetEvents(grabbed: false); assert the TotalCount and that the returned Items includes the event whose SearchEventData was null using the same body assertions (GetProperty("TotalCount") and Items[...].GetProperty("ItemTitle")) to verify the controller handles null SearchEventData correctly.code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss (1)
81-87: Replace deprecatedword-break: break-wordwith modern alternative.The CSS value
word-break: break-wordis deprecated. The newer.cell-title__textclass (lines 168-173) correctly usesoverflow-wrap: anywhere, but this rule still uses the deprecated value.♻️ Proposed fix
&__title { font-weight: 500; color: var(--text-primary); padding: 0 var(--space-4) var(--space-2); - word-break: break-word; + overflow-wrap: anywhere; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss` around lines 81 - 87, The CSS rule for &__title uses the deprecated word-break: break-word; replace it with the modern equivalent by removing word-break and adding overflow-wrap: anywhere (or overflow-wrap: anywhere plus word-break: normal if you want explicit behavior) to the &__title selector so it matches the behavior used in .cell-title__text; update the &__title block in upgrades-tab.component.scss accordingly to use overflow-wrap: anywhere and ensure no deprecated properties remain.code/frontend/src/app/core/directives/sticky-aware.directive.ts (1)
36-46: Consider handling the case where no scrollable parent exists.When
findScrollParentreturnsnull, the observer uses the viewport as root. This works for most cases, but if the sticky element is deeply nested without any scroll container, the behavior might be unexpected.This is a minor edge case for now since all current usages appear to be within scrollable shell containers.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/frontend/src/app/core/directives/sticky-aware.directive.ts` around lines 36 - 46, findScrollParent currently returns null when no scrollable ancestor is found which causes the IntersectionObserver to use the viewport unexpectedly; change findScrollParent to return a fallback scroll container (e.g. document.scrollingElement or document.documentElement) instead of null and ensure the code that constructs the IntersectionObserver uses that returned element as the root (reference findScrollParent and the code that creates the IntersectionObserver) so deeply nested sticky elements use the page's scroll container consistently.code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs (1)
251-256: Consider reusing parameters to avoid redundant allocations.
BuildCommonParameters()is called twice (lines 269 and 280), creating two separate arrays with identical values. SinceSqliteParameterobjects can be reused across queries when they have the same values, consider extracting this to a local variable.♻️ Suggested refactor
- SqliteParameter[] BuildCommonParameters() => new[] - { - new SqliteParameter("@instanceId", instanceId.HasValue ? instanceId.Value : DBNull.Value), - new SqliteParameter("@search", (object?)searchPattern ?? DBNull.Value), - new SqliteParameter("@cutoff", (object?)cutoff ?? DBNull.Value), - }; + var commonParams = new SqliteParameter[] + { + new("@instanceId", instanceId.HasValue ? instanceId.Value : DBNull.Value), + new("@search", (object?)searchPattern ?? DBNull.Value), + new("@cutoff", (object?)cutoff ?? DBNull.Value), + };Then use
commonParamsdirectly inlistParamsand create a copy forcountSql:SqliteParameter[] listParams = [ - ..BuildCommonParameters(), + ..commonParams, new("@take", pageSize), new("@skip", (page - 1) * pageSize), ]; // ... - int totalCount = await _dataContext.Database - .SqlQueryRaw<int>(countSql, BuildCommonParameters()) + var countParams = commonParams.Select(p => new SqliteParameter(p.ParameterName, p.Value)).ToArray(); + int totalCount = await _dataContext.Database + .SqlQueryRaw<int>(countSql, countParams) .FirstAsync();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs` around lines 251 - 256, BuildCommonParameters() is being invoked twice creating identical SqliteParameter arrays; call BuildCommonParameters() once into a local variable (e.g., commonParams) and reuse that array when constructing listParams (the query parameters used for the list SQL), and when building the count SQL (countSql) create a shallow copy of commonParams before appending or modifying any additional parameters so you don't mutate the reused array; reference the SqliteParameter[] BuildCommonParameters(), listParams, and countSql symbols when making this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In
`@code/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cs`:
- Around line 179-181: The three AddSearchEvent calls use DateTime.UtcNow
separately causing non-deterministic ordering; fix by creating a single fixed
UTC baseline (e.g., DateTime baseline = new DateTime(2020,1,1,0,0,0,
DateTimeKind.Utc) or capture DateTime baseline = DateTime.UtcNow once) in
SearchStatsControllerTests and replace the timestamps with baseline,
baseline.AddHours(-2), and baseline.AddHours(-1) when calling
AddSearchEvent(itemTitle: "...", timestamp: ...).
- Around line 192-203: The test
GetEvents_WithSearchStatusFilter_ReturnsOnlyMatchingStatuses currently only
asserts TotalCount == 2 which can miss wrong records; update the test in
SearchStatsControllerTests to also inspect the response array from
_controller.GetEvents (use GetResponseBody(result) and enumerate the events
collection) and assert that each returned item's searchStatus (or itemTitle)
matches one of the allowed enums SearchCommandStatus.Completed or
SearchCommandStatus.Failed (and/or assert the returned titles are exactly "A"
and "B"), so the filter logic in GetEvents is verified by content not just
count.
- Around line 245-255: The test covers only events whose SearchEventData has an
empty grabbed list; add coverage for the branch where SearchEventData is null to
ensure grabbed=false includes events with no SearchEventData. Update or create a
test (e.g., alongside
GetEvents_WithGrabbedFalse_KeepsOnlyEventsWithoutGrabbedItems) that calls
AddSearchEvent with SearchEventData set to null (or a helper overload) and then
calls _controller.GetEvents(grabbed: false); assert the TotalCount and that the
returned Items includes the event whose SearchEventData was null using the same
body assertions (GetProperty("TotalCount") and
Items[...].GetProperty("ItemTitle")) to verify the controller handles null
SearchEventData correctly.
In
`@code/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cs`:
- Around line 251-256: BuildCommonParameters() is being invoked twice creating
identical SqliteParameter arrays; call BuildCommonParameters() once into a local
variable (e.g., commonParams) and reuse that array when constructing listParams
(the query parameters used for the list SQL), and when building the count SQL
(countSql) create a shallow copy of commonParams before appending or modifying
any additional parameters so you don't mutate the reused array; reference the
SqliteParameter[] BuildCommonParameters(), listParams, and countSql symbols when
making this change.
In `@code/frontend/src/app/core/directives/sticky-aware.directive.ts`:
- Around line 36-46: findScrollParent currently returns null when no scrollable
ancestor is found which causes the IntersectionObserver to use the viewport
unexpectedly; change findScrollParent to return a fallback scroll container
(e.g. document.scrollingElement or document.documentElement) instead of null and
ensure the code that constructs the IntersectionObserver uses that returned
element as the root (reference findScrollParent and the code that creates the
IntersectionObserver) so deeply nested sticky elements use the page's scroll
container consistently.
In
`@code/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scss`:
- Around line 81-87: The CSS rule for &__title uses the deprecated word-break:
break-word; replace it with the modern equivalent by removing word-break and
adding overflow-wrap: anywhere (or overflow-wrap: anywhere plus word-break:
normal if you want explicit behavior) to the &__title selector so it matches the
behavior used in .cell-title__text; update the &__title block in
upgrades-tab.component.scss accordingly to use overflow-wrap: anywhere and
ensure no deprecated properties remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a1a032c9-aaae-4d70-b5ce-b1b7f916ab7d
📒 Files selected for processing (50)
code/backend/Cleanuparr.Api.Tests/Features/Seeker/CustomFormatScoreControllerTests.cscode/backend/Cleanuparr.Api.Tests/Features/Seeker/SearchStatsControllerTests.cscode/backend/Cleanuparr.Api.Tests/Features/Seeker/TestHelpers/SeekerTestDataFactory.cscode/backend/Cleanuparr.Api/Features/Seeker/Contracts/Responses/CustomFormatScoreEntryResponse.cscode/backend/Cleanuparr.Api/Features/Seeker/Controllers/CustomFormatScoreController.cscode/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cscode/backend/Cleanuparr.Domain/Enums/CfScoresSortBy.cscode/backend/Cleanuparr.Domain/Enums/CfUpgradesSortBy.cscode/backend/Cleanuparr.Domain/Enums/CutoffFilter.cscode/backend/Cleanuparr.Domain/Enums/MonitoredFilter.cscode/backend/Cleanuparr.Domain/Enums/SearchEventsSortBy.cscode/backend/Cleanuparr.Domain/Enums/SortDirection.cscode/backend/Cleanuparr.Infrastructure/Features/Jobs/CustomFormatScoreSyncer.cscode/backend/Cleanuparr.Persistence/DataContext.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.Designer.cscode/backend/Cleanuparr.Persistence/Migrations/Data/20260420134250_AddLastUpgradedAtToCfScoreEntry.cscode/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cscode/backend/Cleanuparr.Persistence/Models/State/CustomFormatScoreEntry.cscode/frontend/src/app/core/api/cf-score.api.tscode/frontend/src/app/core/api/search-stats.api.tscode/frontend/src/app/core/directives/sticky-aware.directive.tscode/frontend/src/app/core/models/search-stats.models.tscode/frontend/src/app/features/dashboard/dashboard.component.scsscode/frontend/src/app/features/dashboard/dashboard.component.tscode/frontend/src/app/features/events/events.component.htmlcode/frontend/src/app/features/events/events.component.scsscode/frontend/src/app/features/events/events.component.tscode/frontend/src/app/features/logs-component/logs.component.htmlcode/frontend/src/app/features/logs-component/logs.component.scsscode/frontend/src/app/features/logs-component/logs.component.tscode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.htmlcode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scsscode/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.tscode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.htmlcode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.scsscode/frontend/src/app/features/seeker-stats/searches-tab/searches-tab.component.tscode/frontend/src/app/features/seeker-stats/seeker-stats.component.scsscode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.htmlcode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.scsscode/frontend/src/app/features/seeker-stats/upgrades-tab/upgrades-tab.component.tscode/frontend/src/app/features/strikes/strikes.component.htmlcode/frontend/src/app/features/strikes/strikes.component.scsscode/frontend/src/app/features/strikes/strikes.component.tscode/frontend/src/app/ui/drawer/drawer.component.htmlcode/frontend/src/app/ui/drawer/drawer.component.scsscode/frontend/src/app/ui/drawer/drawer.component.tscode/frontend/src/app/ui/index.tscode/frontend/src/styles/_animations.scsscode/frontend/src/styles/_data-toolbar.scsscode/frontend/src/styles/_page-animations.scss
✅ Files skipped from review due to trivial changes (18)
- code/frontend/src/app/features/logs-component/logs.component.html
- code/frontend/src/app/features/strikes/strikes.component.ts
- code/frontend/src/app/ui/index.ts
- code/frontend/src/app/features/events/events.component.html
- code/frontend/src/app/features/dashboard/dashboard.component.ts
- code/frontend/src/app/features/logs-component/logs.component.ts
- code/frontend/src/app/core/models/search-stats.models.ts
- code/backend/Cleanuparr.Domain/Enums/CfUpgradesSortBy.cs
- code/backend/Cleanuparr.Persistence/Migrations/Data/DataContextModelSnapshot.cs
- code/backend/Cleanuparr.Domain/Enums/SortDirection.cs
- code/backend/Cleanuparr.Persistence/Models/State/CustomFormatScoreEntry.cs
- code/backend/Cleanuparr.Domain/Enums/CutoffFilter.cs
- code/backend/Cleanuparr.Domain/Enums/CfScoresSortBy.cs
- code/frontend/src/styles/_page-animations.scss
- code/frontend/src/app/ui/drawer/drawer.component.scss
- code/frontend/src/app/features/strikes/strikes.component.html
- code/backend/Cleanuparr.Domain/Enums/MonitoredFilter.cs
- code/backend/Cleanuparr.Domain/Enums/SearchEventsSortBy.cs
🚧 Files skipped from review as they are similar to previous changes (10)
- code/backend/Cleanuparr.Persistence/DataContext.cs
- code/backend/Cleanuparr.Infrastructure/Features/Jobs/CustomFormatScoreSyncer.cs
- code/frontend/src/styles/_animations.scss
- code/frontend/src/app/features/dashboard/dashboard.component.scss
- code/frontend/src/app/features/strikes/strikes.component.scss
- code/frontend/src/app/features/seeker-stats/quality-tab/quality-tab.component.scss
- code/frontend/src/app/features/logs-component/logs.component.scss
- code/backend/Cleanuparr.Api/Features/Seeker/Controllers/SearchStatsController.cs
- code/frontend/src/app/core/api/search-stats.api.ts
- code/frontend/src/app/features/events/events.component.ts
Relates to #568