Skip to content

feat(ui): expose the whole downloads config block in Settings - #80

Merged
roziscoding merged 2 commits into
feat/unlink-imported-filesfrom
feat/settings-full-config
Aug 1, 2026
Merged

feat(ui): expose the whole downloads config block in Settings#80
roziscoding merged 2 commits into
feat/unlink-imported-filesfrom
feat/settings-full-config

Conversation

@roziscoding

@roziscoding roziscoding commented Jul 31, 2026

Copy link
Copy Markdown
Owner

What does this PR do?

Settings already covered jack, peers, servers, and API keys in full. Of the downloads block it covered exactly one key — the unlink toggle from #79. This exposes the remaining ten, so the config file has no UI-invisible corners left.

Settings → Downloads

One form, three tiers:

  • Up top, always visible — completed folder, simultaneous transfers, and the unlink switch. The settings you actually choose.
  • Collapsed: "Transfer retries" — attempts, stall timeout, first and longest retry delay.
  • Collapsed: "Handing files to *arr" — import check interval, import attempts, first and longest import retry delay.

Each collapsed group is labelled with how many of its values differ from jack's defaults, so a closed group still tells you whether it holds anything you set yourself. One dirty-aware Save, with Revert.

Clearing a field means "use the default"

Millisecond keys stay millisecond keys — that's what the config file holds — but each field reads its value back in human terms beside the input (180000030 min), and the placeholder is jack's default. Clearing a field sends null, and the backend drops the key from the file so the schema default applies again. completedPath has no default, so it is the one key that can't be cleared (null → 400).

A config file with no downloads block gets a "Set up downloads" flow instead of a dead end — writing that block is exactly what this form does.

Save semantics

Only unlinkImportedFiles applies immediately (the import watcher reads it per import, per #79). Everything else is read at startup, and the success message says so — including the first-save case, where nothing downloads until the server restarts.

Checklist

  • Commits follow Conventional Commits
  • Commits are signed (GitHub enforces this — unsigned commits are rejected)
  • mise run lint:fix was run and its output is committed (no changes)
  • mise run test passes — 472 pass / 0 fail. mise run test:e2e was not run: no Docker in the dev environment.
  • Docs updated if behavior or configuration changed — website/reference/configuration.md, website/guide/management-ui.md

Tests

  • formatDurationMs — 6 tests over the config defaults, unit boundaries, and empty values. These caught a real bug: 59999 ms rendered as 60.0 s.
  • Config API — null drops a key so the default applies again; completedPath: null is rejected 400 and leaves the file untouched.
  • Also exercised end to end against a running backend through the UI's own proxy: PATCH with nulls removed the keys from the config file on disk and left the rest intact.

@roziscoding
roziscoding marked this pull request as ready for review July 31, 2026 23:02
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds complete Downloads configuration management to Settings.

  • Exposes paths, concurrency, retry, import-watcher, and unlink settings in one form.
  • Supports clearing optional values to restore schema defaults.
  • Adds backend persistence semantics, validation tests, duration formatting, and documentation.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported post-save edit-loss path is addressed by keeping all form-editing controls disabled until both the PATCH and refresh complete.

Reviews (3): Last reviewed commit: "fix(ui): freeze the downloads form while..." | Re-trigger Greptile

Comment thread apps/ui/app/components/DownloadsSection.vue
Settings covered jack, peers, servers, and API keys in full, but of the
`downloads` block only the unlink toggle. This exposes the remaining ten keys so
the config file has no UI-invisible corners left.

Settings -> Downloads is now one form:

- completed folder, simultaneous transfers, and the unlink switch up top — the
  settings you actually choose
- transfer retry tuning and import-watcher tuning in two collapsed groups, each
  labelled with how many of its values differ from jack's defaults
- one dirty-aware Save, with Revert

Millisecond keys stay millisecond keys (that's what the file holds) but read back
in human terms next to the input, and each field's placeholder is jack's default.
Clearing a field now means "use the default": the PATCH sends null and the
backend drops the key from the file. `completedPath` has no default, so it can't
be cleared. A config with no downloads block gets a "Set up downloads" flow
rather than a dead end.
@roziscoding
roziscoding force-pushed the feat/settings-full-config branch from 79b09be to a79bd49 Compare July 31, 2026 23:40
Saving refreshes from the server and re-seeds the form with the response, so an
edit made while the PATCH was in flight was silently overwritten — and since
seeding also resets the baseline, the lost edit didn't even leave the form dirty.

Every control is now disabled for the duration of the save, which closes the
window rather than trying to reconcile after the fact. The submit button already
disabled itself while loading; the fields, switch, and Revert did not.

Reported by Greptile on #80.
@roziscoding
roziscoding merged commit ff5d8c4 into main Aug 1, 2026
10 checks passed
@roziscoding
roziscoding deleted the feat/settings-full-config branch August 1, 2026 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant