fix(dirscan): retain recent runs and clarify restart behavior#1564
fix(dirscan): retain recent runs and clarify restart behavior#1564
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughAdds per-directory dir-scan run-history pruning (keep last 10 runs), run-injection validation/marshaling, service startup pruning, tests for pruning behavior and humanized link-plan errors, plus UI and docs text updates describing incremental scan/resume semantics. Changes
Sequence Diagram(s)sequenceDiagram
participant Service as DirScan Service
participant Store as DirScanStore
participant DB as Database
Service->>Service: Start()
Service->>Store: PruneRunHistory(ctx with 10s timeout)
Store->>DB: DELETE old runs per directory (ORDER BY started_at DESC, id DESC LIMIT offset)
DB-->>Store: OK / rows affected
Store-->>Service: Result / error
Service-->>Service: Log success or warning, continue Start flow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
internal/models/dirscan.go (1)
885-888: Don’t let pruning failures disappear silently.Both helpers drop DB errors on the floor. If trimming starts failing, the new retention cap quietly stops working and history/injection rows can grow unbounded again with no signal. Please at least surface these failures via logging or an explicit non-fatal error path.
As per coding guidelines, "Prefer explicit error handling over silent failures in Go."
Also applies to: 983-996
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/models/dirscan.go` around lines 885 - 888, The current trimRunHistoryBestEffort swallows errors from pruneRunHistoryForDirectory; change the handler to surface failures (log them or return a non-fatal error) instead of discarding them — e.g., replace the "_ = err" with a descriptive logging call that includes directoryID and err (use the store's logger field, e.g. s.logger.Errorf("pruneRunHistoryForDirectory failed for directory %d: %v", directoryID, err) or fallback to log.Printf) and apply the same change to the other similar helper mentioned (lines ~983-996) so pruning failures are visible.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/src/components/cross-seed/DirScanTab.tsx`:
- Line 339: Add a new Vitest + React Testing Library test file
DirScanTab.test.tsx that renders the DirScanTab component and asserts the
presence of the user-facing strings changed in this PR (including "Scan
canceled. Next run will recheck the directory and continue with unfinished
items." and the other messages at the flagged lines). Mock any external
dependencies used by DirScanTab (e.g., toast from react-hot-toast or similar,
API hooks, and context/providers) and simulate the actions that trigger the
messages (for example invoking the cancel handler that calls the onSuccess toast
in the cancel function referenced in the component). Use screen.getByText /
queryByText assertions to verify each string appears, and ensure the tests run
with Vitest configuration consistent with the repo’s test setup.
---
Nitpick comments:
In `@internal/models/dirscan.go`:
- Around line 885-888: The current trimRunHistoryBestEffort swallows errors from
pruneRunHistoryForDirectory; change the handler to surface failures (log them or
return a non-fatal error) instead of discarding them — e.g., replace the "_ =
err" with a descriptive logging call that includes directoryID and err (use the
store's logger field, e.g. s.logger.Errorf("pruneRunHistoryForDirectory failed
for directory %d: %v", directoryID, err) or fallback to log.Printf) and apply
the same change to the other similar helper mentioned (lines ~983-996) so
pruning failures are visible.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 55ba926d-1300-46d8-9756-59743ef28d45
📒 Files selected for processing (7)
AGENTS.mddocumentation/docs/features/cross-seed/dir-scan.mdinternal/models/dirscan.gointernal/models/dirscan_run_status_test.gointernal/services/dirscan/cancel_scan_test.gointernal/services/dirscan/service.goweb/src/components/cross-seed/DirScanTab.tsx
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
web/src/components/cross-seed/DirScanTab.test.tsx (1)
229-235: Add an accessible name or test ID to the cancel button.The test couples to the current icon implementation by selecting via
svg.lucide-pause. An icon swap would break the test even if behavior is unchanged. Add anaria-label(e.g., "Cancel scan") ordata-testidto the button inDirScanTab.tsxand update the test to usegetByRole("button", { name: "Cancel scan" })orgetByTestId, matching the pattern used elsewhere in this test file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/components/cross-seed/DirScanTab.test.tsx` around lines 229 - 235, The test is brittle because it selects the cancel button by an internal SVG class; update the component DirScanTab (the cancel/pause button element) to include an accessible identifier (add aria-label="Cancel scan" or data-testid="cancel-scan-button" on the button) and then update the test in DirScanTab.test.tsx to find the button via getByRole("button", { name: "Cancel scan" }) or getByTestId("cancel-scan-button") instead of searching for svg.lucide-pause; keep the existing fireEvent.click(pauseButton) logic but replace the pauseButton lookup with the new accessible selector.web/src/test/setup.ts (1)
9-38: Clear the setup-file mocks after each test.
matchMedia,ResizeObserver, andscrollIntoVieware installed once as sharedvi.fn()mocks. Withoutvi.clearAllMocks()inafterEach, their call history leaks between test cases.Suggested fix
afterEach(() => { cleanup() + vi.clearAllMocks() })🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@web/src/test/setup.ts` around lines 9 - 38, The test setup installs shared mocks (window.matchMedia, ResizeObserver via ResizeObserverMock, and HTMLElement.prototype.scrollIntoView) whose call histories leak between tests; update the afterEach block (the one that currently calls cleanup()) to also call vi.clearAllMocks() so all vi.fn() histories are cleared between tests (optionally vi.restoreAllMocks() if you need to restore implementations), ensuring matchMedia, ResizeObserverMock, and scrollIntoView have fresh call state for each test.internal/models/dirscan.go (1)
720-723: Query only directory IDs for the startup sweep.This path only needs
dir.ID, butListDirectoriespulls and unmarshals full directory rows. A small ID-only query/helper would make the cleanup cheaper and decouple it from unrelated row decode failures.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/models/dirscan.go` around lines 720 - 723, The startup sweep only needs directory IDs but calls ListDirectories which fetches and unmarshals full rows; add a lightweight ID-only query (e.g., implement s.ListDirectoryIDs or ListDirectoriesIDsForPrune that SELECTs only id) and call that from the startup sweep instead of ListDirectories to avoid unnecessary decoding and coupling to other fields; update the call site (the code that currently does directories, err := s.ListDirectories(ctx)) to use the new ID-only helper and adapt the downstream loop to work with the returned []int64 (or []string as appropriate) IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@web/vite.config.ts`:
- Line 12: Add the missing TypeScript lib reference by inserting the
triple-slash directive for the WebWorker lib at the top of web/vite.config.ts
(above the existing import of defineConfig) so the file includes /// <reference
lib="WebWorker" />; this ensures web types like Request used by the PWA config
are available despite tsconfig.node.json omitting DOM libs.
---
Nitpick comments:
In `@internal/models/dirscan.go`:
- Around line 720-723: The startup sweep only needs directory IDs but calls
ListDirectories which fetches and unmarshals full rows; add a lightweight
ID-only query (e.g., implement s.ListDirectoryIDs or ListDirectoriesIDsForPrune
that SELECTs only id) and call that from the startup sweep instead of
ListDirectories to avoid unnecessary decoding and coupling to other fields;
update the call site (the code that currently does directories, err :=
s.ListDirectories(ctx)) to use the new ID-only helper and adapt the downstream
loop to work with the returned []int64 (or []string as appropriate) IDs.
In `@web/src/components/cross-seed/DirScanTab.test.tsx`:
- Around line 229-235: The test is brittle because it selects the cancel button
by an internal SVG class; update the component DirScanTab (the cancel/pause
button element) to include an accessible identifier (add aria-label="Cancel
scan" or data-testid="cancel-scan-button" on the button) and then update the
test in DirScanTab.test.tsx to find the button via getByRole("button", { name:
"Cancel scan" }) or getByTestId("cancel-scan-button") instead of searching for
svg.lucide-pause; keep the existing fireEvent.click(pauseButton) logic but
replace the pauseButton lookup with the new accessible selector.
In `@web/src/test/setup.ts`:
- Around line 9-38: The test setup installs shared mocks (window.matchMedia,
ResizeObserver via ResizeObserverMock, and HTMLElement.prototype.scrollIntoView)
whose call histories leak between tests; update the afterEach block (the one
that currently calls cleanup()) to also call vi.clearAllMocks() so all vi.fn()
histories are cleared between tests (optionally vi.restoreAllMocks() if you need
to restore implementations), ensuring matchMedia, ResizeObserverMock, and
scrollIntoView have fresh call state for each test.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: e3f4a687-41ff-4ca9-9081-86d07501fcda
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
internal/models/dirscan.goweb/package.jsonweb/src/components/cross-seed/DirScanTab.test.tsxweb/src/test/setup.tsweb/vite.config.ts
There was a problem hiding this comment.
🧹 Nitpick comments (2)
internal/services/dirscan/inject_test.go (1)
114-172: Consider making this a small table-driven suite.The new helper has multiple translation branches plus a generic fallback, but this test locks only one of them. A table-driven test over all outputs would give better coverage with less duplication.
As per coding guidelines, "Prefer table-driven test cases in Go tests".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/services/dirscan/inject_test.go` around lines 114 - 172, TestInjector_Inject_HumanizesLinkPlanMismatchError only asserts one translation branch; convert it into a table-driven test that exercises all humanized error branches plus the generic fallback by iterating test cases with distinct InjectRequest setups and expected error strings. Refactor the current test into a TestInjector_Inject_HumanizesLinkPlanMismatchErrors table where each case provides a name, a modified ParsedTorrent/MatchResult/Searchee (to trigger each branch), expected error string, then call injector.Inject and assert both err.Error() and res.ErrorMessage match; keep the same injector creation and reuse the existing TestInjector_Inject_HumanizesLinkPlanMismatchError logic body inside the loop, referencing TestInjector_Inject_HumanizesLinkPlanMismatchError and Injector.Inject to locate the code. Ensure each case has a descriptive name and use t.Run for subtests.internal/services/dirscan/inject.go (1)
470-490: Replace string-based error parsing with typed/sentinel errors.
humanizeLinkPlanErrordepends on exacterr.Error()prefixes frompkg/hardlinktree.BuildPlan(lines 106, 120, 165), which currently emits plainerrors.New()strings. Any wording change there will silently drop to the generic message. Define typed/sentinel errors inpkg/hardlinktreeand useerrors.Is/Ashere to decouple the layers and keep the UI copy stable even if low-level error text changes.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@internal/services/dirscan/inject.go` around lines 470 - 490, humanizeLinkPlanError currently relies on exact err.Error() prefixes from pkg/hardlinktree.BuildPlan which is brittle; change the low-level package to export sentinel errors and/or a typed error (e.g., ErrNoMatchingFile, ErrNoAvailableMatch, ErrCouldNotMatchFile or a struct type like LinkPlanError with a File field) and have BuildPlan return those (wrapping context with fmt.Errorf("...: %w", err) if needed), then update humanizeLinkPlanError to detect them via errors.Is or errors.As (use the File field from the typed error to fill the message) instead of parsing err.Error() so the UI messages remain stable when implementation text changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@internal/services/dirscan/inject_test.go`:
- Around line 114-172: TestInjector_Inject_HumanizesLinkPlanMismatchError only
asserts one translation branch; convert it into a table-driven test that
exercises all humanized error branches plus the generic fallback by iterating
test cases with distinct InjectRequest setups and expected error strings.
Refactor the current test into a
TestInjector_Inject_HumanizesLinkPlanMismatchErrors table where each case
provides a name, a modified ParsedTorrent/MatchResult/Searchee (to trigger each
branch), expected error string, then call injector.Inject and assert both
err.Error() and res.ErrorMessage match; keep the same injector creation and
reuse the existing TestInjector_Inject_HumanizesLinkPlanMismatchError logic body
inside the loop, referencing TestInjector_Inject_HumanizesLinkPlanMismatchError
and Injector.Inject to locate the code. Ensure each case has a descriptive name
and use t.Run for subtests.
In `@internal/services/dirscan/inject.go`:
- Around line 470-490: humanizeLinkPlanError currently relies on exact
err.Error() prefixes from pkg/hardlinktree.BuildPlan which is brittle; change
the low-level package to export sentinel errors and/or a typed error (e.g.,
ErrNoMatchingFile, ErrNoAvailableMatch, ErrCouldNotMatchFile or a struct type
like LinkPlanError with a File field) and have BuildPlan return those (wrapping
context with fmt.Errorf("...: %w", err) if needed), then update
humanizeLinkPlanError to detect them via errors.Is or errors.As (use the File
field from the typed error to fill the message) instead of parsing err.Error()
so the UI messages remain stable when implementation text changes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: a7d84690-55fb-45c0-a5c2-80ee78d1e5a6
📒 Files selected for processing (2)
internal/services/dirscan/inject.gointernal/services/dirscan/inject_test.go
…tention # Conflicts: # documentation/docs/features/cross-seed/dir-scan.md # internal/models/dirscan.go # internal/models/dirscan_run_status_test.go
…#90) This PR contains the following updates: | Package | Update | Change | |---|---|---| | [ghcr.io/autobrr/qui](https://github.com/autobrr/qui) | minor | `v1.15.0` → `v1.16.0` | --- ### Release Notes <details> <summary>autobrr/qui (ghcr.io/autobrr/qui)</summary> ### [`v1.16.0`](https://github.com/autobrr/qui/releases/tag/v1.16.0) [Compare Source](autobrr/qui@v1.15.0...v1.16.0) ##### Changelog ##### Highlights - Automations got a major upgrade: rules can now match across instances, use system time, control AutoTMM, and opt out of notifications per workflow. - Cross-seed is more reliable during state changes. Completion searches now wait for torrents to finish checking or moving, disabled instances are skipped cleanly, and hardlink/reflink save-path handling is more accurate. - Dir Scan works better with real media libraries, with improved partial season-pack handling in link-tree mode, support for downloading missing files when needed, and better progress retention across restarts. - Managing torrents in the unified view is smoother, with more accurate tracker health, quicker instance-level actions, and more stable category and tag editing dialogs. - OIDC and backups both got practical quality-of-life improvements: OIDC now supports PKCE, backup settings can be applied across instances, and backup export handling is safer for tricky torrent layouts. ##### New Features - [`2b92c7b`](autobrr/qui@2b92c7b): feat(auth): add PKCE support to OIDC implementation ([#​1737](autobrr/qui#1737)) ([@​oynqr](https://github.com/oynqr)) - [`ba3d5d9`](autobrr/qui@ba3d5d9): feat(automations): add AutoTMM condition and action ([#​1698](autobrr/qui#1698)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`702e808`](autobrr/qui@702e808): feat(automations): add system time to query builder ([#​1677](autobrr/qui#1677)) ([@​wastaken7](https://github.com/wastaken7)) - [`e6493b3`](autobrr/qui@e6493b3): feat(automations): allow disable of notifications ([#​1652](autobrr/qui#1652)) ([@​heathlarsen](https://github.com/heathlarsen)) - [`565ac2d`](autobrr/qui@565ac2d): feat(automations): cross instance condition ([#​1648](autobrr/qui#1648)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`7e12a02`](autobrr/qui@7e12a02): feat(update): verify self-updates with signed release checksums ([#​1665](autobrr/qui#1665)) ([@​s0up4200](https://github.com/s0up4200)) - [`3778c7b`](autobrr/qui@3778c7b): feat(web): Add action buttons to unified instance. ([#​1637](autobrr/qui#1637)) ([@​drtaru](https://github.com/drtaru)) - [`bf9eaba`](autobrr/qui@bf9eaba): feat(web): Clarify dashboard quick links ([#​1636](autobrr/qui#1636)) ([@​drtaru](https://github.com/drtaru)) - [`b930530`](autobrr/qui@b930530): feat(web): add "Save changes to all instances" button to backup settings ([#​1651](autobrr/qui#1651)) ([@​drtaru](https://github.com/drtaru)) - [`5975c34`](autobrr/qui@5975c34): feat(web): add Discord perk section to license manager ([#​1656](autobrr/qui#1656)) ([@​s0up4200](https://github.com/s0up4200)) - [`d8ad0d6`](autobrr/qui@d8ad0d6): feat(web): unify tab styling and animations ([#​1632](autobrr/qui#1632)) ([@​nuxencs](https://github.com/nuxencs)) ##### Bug Fixes - [`44596b9`](autobrr/qui@44596b9): fix(automations): add AutoTMM to condition validation ([#​1726](autobrr/qui#1726)) ([@​s0up4200](https://github.com/s0up4200)) - [`8f757b2`](autobrr/qui@8f757b2): fix(automations): hardlink signature grouping ([#​1670](autobrr/qui#1670)) ([@​aulterego](https://github.com/aulterego)) - [`d242f0c`](autobrr/qui@d242f0c): fix(automations): include AutoManagement in delete standalone check ([#​1731](autobrr/qui#1731)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`744bdb8`](autobrr/qui@744bdb8): fix(backups): adaptive export throttle ([#​1630](autobrr/qui#1630)) ([@​s0up4200](https://github.com/s0up4200)) - [`d1dbb81`](autobrr/qui@d1dbb81): fix(backups): gate bulk save on resolved instance capabilities ([#​1682](autobrr/qui#1682)) ([@​s0up4200](https://github.com/s0up4200)) - [`2eea961`](autobrr/qui@2eea961): fix(backups): skip live export for hybrid torrents ([#​1669](autobrr/qui#1669)) ([@​s0up4200](https://github.com/s0up4200)) - [`2cac32d`](autobrr/qui@2cac32d): fix(crossseed): skip disabled instances ([#​1635](autobrr/qui#1635)) ([@​s0up4200](https://github.com/s0up4200)) - [`ebbba8e`](autobrr/qui@ebbba8e): fix(crossseed): tone down async cache reuse log ([#​1686](autobrr/qui#1686)) ([@​s0up4200](https://github.com/s0up4200)) - [`fd382b7`](autobrr/qui@fd382b7): fix(dirscan): link plan size tolerance + partial season pack injection in link tree mode ([#​1695](autobrr/qui#1695)) ([@​s0up4200](https://github.com/s0up4200)) - [`5131092`](autobrr/qui@5131092): fix(dirscan): retain recent runs and clarify restart behavior ([#​1564](autobrr/qui#1564)) ([@​s0up4200](https://github.com/s0up4200)) - [`3fbcc7a`](autobrr/qui@3fbcc7a): fix(openapi): document dirscan downloadMissingFiles ([#​1727](autobrr/qui#1727)) ([@​s0up4200](https://github.com/s0up4200)) - [`d16fee2`](autobrr/qui@d16fee2): fix(orphanscan): use content\_path to prevent false positives when Auto TMM changes save\_path ([#​1712](autobrr/qui#1712)) ([@​nitrobass24](https://github.com/nitrobass24)) - [`e74bf02`](autobrr/qui@e74bf02): fix(qbittorrent): avoid tracker health URL false positives ([#​1738](autobrr/qui#1738)) ([@​s0up4200](https://github.com/s0up4200)) - [`632fc54`](autobrr/qui@632fc54): fix(torrents): honor tracker health in unified view ([#​1668](autobrr/qui#1668)) ([@​s0up4200](https://github.com/s0up4200)) - [`76fddc4`](autobrr/qui@76fddc4): fix(torrents): stabilize tag and category dialogs ([#​1638](autobrr/qui#1638)) ([@​s0up4200](https://github.com/s0up4200)) - [`c758b6d`](autobrr/qui@c758b6d): fix(torrents): validate creator output path ([#​1739](autobrr/qui#1739)) ([@​s0up4200](https://github.com/s0up4200)) - [`6c23f0e`](autobrr/qui@6c23f0e): fix(web): cross-seed warning in unified view ([#​1692](autobrr/qui#1692)) ([@​s0up4200](https://github.com/s0up4200)) - [`57822c0`](autobrr/qui@57822c0): fix(web): improve duplicate torrent state and check another field.state.value type in AddTorrentDialog ([#​1679](autobrr/qui#1679)) ([@​keatonhasse](https://github.com/keatonhasse)) - [`246c8f6`](autobrr/qui@246c8f6): fix(web): migrate vite chunk splitting config ([@​s0up4200](https://github.com/s0up4200)) ##### Other Changes - [`263b0bd`](autobrr/qui@263b0bd): build(deps): add cooldown to dependabot config ([#​1691](autobrr/qui#1691)) ([@​s0up4200](https://github.com/s0up4200)) - [`a394157`](autobrr/qui@a394157): chore(deps): bump github.com/go-jose/go-jose/v4 from 4.1.3 to 4.1.4 ([#​1713](autobrr/qui#1713)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`10612a7`](autobrr/qui@10612a7): chore(deps): bump golang.org/x/image from 0.36.0 to 0.38.0 ([#​1685](autobrr/qui#1685)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`16019dd`](autobrr/qui@16019dd): chore(deps): bump pnpm/action-setup from 4 to 5 in the github group ([#​1634](autobrr/qui#1634)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`fbb25fc`](autobrr/qui@fbb25fc): chore(deps): bump the golang group with 11 updates ([#​1693](autobrr/qui#1693)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`cbb9594`](autobrr/qui@cbb9594): chore(deps): bump the golang group with 3 updates ([#​1701](autobrr/qui#1701)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`805ab74`](autobrr/qui@805ab74): chore(deps): bump the npm group in /web with 25 updates ([#​1694](autobrr/qui#1694)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`e3f839c`](autobrr/qui@e3f839c): chore(deps): bump the npm group in /web with 5 updates ([#​1702](autobrr/qui#1702)) ([@​dependabot](https://github.com/dependabot)\[bot]) - [`b076ad4`](autobrr/qui@b076ad4): docs(dirscan): clarify re-identification after torrent removal ([#​1720](autobrr/qui#1720)) ([@​s0up4200](https://github.com/s0up4200)) - [`340f343`](autobrr/qui@340f343): docs: add license management page with deactivation guide ([#​1706](autobrr/qui#1706)) ([@​s0up4200](https://github.com/s0up4200)) - [`5d148be`](autobrr/qui@5d148be): docs: fix link in issue triage template ([@​s0up4200](https://github.com/s0up4200)) - [`0b64237`](autobrr/qui@0b64237): docs: update release follow-up docs ([#​1741](autobrr/qui#1741)) ([@​s0up4200](https://github.com/s0up4200)) **Full Changelog**: <autobrr/qui@v1.15.0...v1.16.0> ##### Docker images - `docker pull ghcr.io/autobrr/qui:v1.16.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 becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about these updates again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZS9jb250YWluZXIiLCJ0eXBlL21pbm9yIl19--> Reviewed-on: https://git.erwanleboucher.dev/eleboucher/homelab/pulls/90 Co-authored-by: bot-owl <bot@erwanleboucher.dev> Co-committed-by: bot-owl <bot@erwanleboucher.dev>
Dir Scan now keeps only the 10 most recent run records per directory instead of letting run history grow without bounds. New runs prune older rows automatically, and service startup also cleans up legacy history from older installs, so existing databases converge to the same limit without a migration.
I also tightened the user-facing language around stop/start behavior. Dir Scan does not resume from an exact checkpoint; it rechecks the directory, skips finished items based on persisted per-file state, and retries unfinished work. The UI copy and end-user docs now say that plainly so the behavior matches the user’s mental model.
Summary by CodeRabbit
New Features
Documentation
UI/UX
Bug Fixes
Tests