Skip to content

feat(settings): pinned default download folder and save prompt preferences (#203) - #233

Open
DhruvilPatel603 wants to merge 1 commit into
anmolkapil:mainfrom
DhruvilPatel603:feature-default-download-location
Open

DhruvilPatel603 wants to merge 1 commit into
anmolkapil:mainfrom
DhruvilPatel603:feature-default-download-location

Conversation

@DhruvilPatel603

@DhruvilPatel603 DhruvilPatel603 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

Closes #203

Problem

Previously, Plexo remembered whichever folder was last picked for a download, which meant one-off folder changes permanently overwrote the user's preferred download destination. Additionally, users had no way to configure a permanent pinned default download location.

Solution & Changes

  1. Pinned Default Download Folder:

    • Added defaultDestinationDir to AppSettings and InitialState.
    • In Settings under General → All downloads, added a Default download folder section showing the display path, a Change button, and a Reset to default button (only shown when a folder is pinned, to unpin without resetting limits/schedules).
    • If a pinned directory is missing from disk (e.g., an unplugged USB drive), Settings displays a warning badge indicating the folder is missing, and New Download safely falls back to the system Downloads folder.
    • When a folder is pinned, new downloads start pre-selected to that pinned folder. One-off folder overrides in New Download only apply to that specific download and reset back to the pinned default on the next download.
    • When no folder is pinned (unpinned mode), Plexo preserves its existing behavior of remembering the last used folder across downloads and app relaunches.
  2. Global Settings Shortcut & Status Bar Button:

    • Added a Settings button in the status bar with standard shortcuts (⌘, on macOS / Ctrl+, on Windows/Linux).
  3. E2E Test Suite:

    • Comprehensive test suite in e2e/settings.spec.ts covering pinned persistence, one-off overrides, missing folder fallbacks, unpinning with Reset to default, legacy folder upgrades, unpinned persistence, and resume destination preservation.

@Arpit-Khandelwal Arpit-Khandelwal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for picking up #203, and for the e2e spec. I merged current main and ran typecheck (node/web/e2e), lint, format:check, the build, e2e/settings.spec.ts and the settings @smoke tests. Your 6 new tests pass, and they fail without the change. An existing user's remembered folder is still used after the upgrade, and the new settings are checked in settings.ts like destinationDir. A few things to fix:

  1. The settings smoke test breaks. NetworkEditPopover.tsx:127 changes the label back to Edit network, but main's test (#234) now looks for Edit Office fibre, so ui.spec.ts:288 fails. Please keep main's label.
  2. A missing pinned folder is still used. Main already checks the folder (handlers.ts:138) and sends the result as destinationDir. But the store and New download use the unchecked defaultDestinationDir instead. I tested it: with a missing pinned folder, New download shows that folder. On an unplugged drive, Download fails with "…choose another folder". Starting from initial.destinationDir fixes it. Please add a test like the existing "broken settings file" one.
  3. With the prompt off, you can't change the folder for one download. The whole Save to row is hidden, so the user only sees the error from point 2, with nothing to click. Please keep a way to change the folder in that mode, and always show the row when the folder is missing.
  4. Existing users see the wrong folder in Settings. If the remembered folder is ~/Movies, Settings shows ~/Downloads (system default), but New download uses ~/Movies. Please show the folder new downloads will really use.

Scope questions for @anmolkapil: is the "ask each time" switch needed, since New download already shows the folder and waits for a click? And should the status bar gear, ⌘, / Ctrl+, and the LimitsDialog move stay here, or go in a separate PR? If they stay, the dialog should probably be called "Settings" instead of "Speed & data limits".

Heads-up: #227 adds free space and a low-space warning to the same Save to row. Hiding the row hides that warning, and the two PRs will conflict there.

variant="ghost"
size="icon-xs"
aria-label={`Edit ${visual.name}`}
aria-label="Edit network"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This breaks ui.spec.ts:288 on current main (#234 changed the test to Edit Office fibre). Please keep `Edit ${visual.name}`. Screen readers need to know which network the button edits.

Comment thread src/renderer/src/store/useAppStore.ts Outdated
destinationDir: initial.destinationDir ?? initial.downloadsDir,
defaultDestinationDir: initial.defaultDestinationDir,
askDestinationEachTime: initial.askDestinationEachTime ?? true,
destinationDir: initial.defaultDestinationDir ?? initial.destinationDir ?? initial.downloadsDir,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Main already checks the pinned folder and sends the result as initial.destinationDir (handlers.ts:138). Reading initial.defaultDestinationDir here skips that check, so a deleted or unplugged folder still gets used. initial.destinationDir ?? initial.downloadsDir would be enough.

const askDestinationEachTime = useAppStore((store) => store.askDestinationEachTime)
const storeDestinationDir = useAppStore((store) => store.destinationDir)
const [destinationDir, setDestinationDir] = useState(
() => defaultDestinationDir ?? storeDestinationDir

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Same as in the store: please start from the folder main checked (storeDestinationDir), not the raw pinned path.

<div className={labelClass}>Save to</div>
<div className="min-w-0 flex-1 truncate font-mono text-[12.5px]">
{toDisplayPath(destinationDir, homeDir)}
{askDestinationEachTime && (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

With the row hidden, the user can't change the folder for this one download. If the pinned folder is missing, they get an error telling them to choose another folder, with no control to do it. Please keep some way to change the folder, and always show the row when the folder is missing.

{toDisplayPath(draft.defaultDestinationDir ?? downloadsDir, homeDir)}
{draft.defaultDestinationDir === undefined && (
<span className="ml-2 font-sans text-[11px] text-muted-foreground">
(system default)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

For an existing user whose remembered folder is not Downloads, this shows ~/Downloads (system default), but New download uses the remembered folder. Please show the folder new downloads will really use.

@DhruvilPatel603

DhruvilPatel603 commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review and feedback @Arpit-Khandelwal! I’ve addressed all the review points and resolved the CI smoke test issues. Here is a breakdown of the updates:

1. Restored Main's Network Label (ui.spec.ts:288)

2. Missing / Unplugged Pinned Folder Safe Fallback

  • src/renderer/src/store/useAppStore.ts now initializes destinationDir from initial.destinationDir ?? initial.downloadsDir. Because main checks directory existence on startup, an unplugged drive safely falls back to the system Downloads directory in New Download instead of failing with an error.
  • Added a dedicated test in e2e/settings.spec.ts (a missing pinned folder falls back safely and can be changed) verifying that missing pinned folders safely fall back and expose the Change button.

3. "Save to" Row & Removal of Redundant "Ask Each Time" Switch

  • Agreeing with the scope observation: since New Download already presents the download folder and waits for user confirmation before starting, the askDestinationEachTime toggle added unnecessary complexity and hid the Save to row.
  • We completely removed the askDestinationEachTime switch. The Save to row is now always visible, avoiding any conflict with feat(ui): display destination free space and warn on insufficient disk space #227 (free space / low space warning).

4. Existing User Upgrade Folder Display in Settings

  • Settings (LimitsDialog.tsx) now displays effectiveDestinationDir = draft.defaultDestinationDir ?? (destinationDir || downloadsDir).
  • Existing users with an existing remembered folder (e.g. ~/Movies) will see their actual folder in Settings rather than ~/Downloads (system default). Added Settings shows the effective legacy download folder test to verify this.

5. Pinned vs. Unpinned Folder Behavior

  • When pinned (defaultDestinationDir is set): Changing the folder via Change in New Download is strictly a one-off override for that download only. Opening New Download again automatically resets back to the pinned default.
  • When unpinned (defaultDestinationDir is undefined): Preserves upstream main's behavior where changing the folder in New Download updates saved.destinationDir so it is remembered across subsequent downloads and app restarts.
  • Resumed downloads: Resumed downloads continue downloading into their original directory even if the default directory was changed in Settings.

E2E Test Suite (e2e/settings.spec.ts)

The test suite now covers all scenarios:

  1. pinned default download directory persists across relaunches
  2. one-off folder changes in New Download do not overwrite pinned default
  3. without pinned default, folder changes in New Download update remembered destination
  4. changing default download directory updates the destination
  5. a missing pinned folder falls back safely and can be changed
  6. Settings shows the effective legacy download folder
  7. resumed download continues saving to its original destination directory

Local Verification

  • npm run typecheck (node, web, e2e, extension) — 0 errors
  • npm run lint — 0 errors
  • Prettier formatting check — Passed

@Arpit-Khandelwal Arpit-Khandelwal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick update. I re-checked 099ebe3 and all 4 points from my last review are fixed. I also tried them by hand: with a deleted pinned folder, New download shows ~/Downloads and Change works, and a remembered ~/Movies shows up correctly in Settings. Typecheck, lint, format:check, the build and the related e2e specs pass, except one test:

  1. settings.spec.ts:240 fails every time. After the relaunch the app is already on the downloads list, so getByRole('button', { name: 'Downloads' }) matches the "All downloads" filter button and opens its menu. Then New download can't be clicked. Removing that line makes all 8 tests pass. CI only runs the smoke tests, so it didn't catch this.
  2. A pinned folder can only be unpinned with "Reset to defaults". That also resets the speed limits, slow mode and schedule. Your first version had a "Reset to default" button for the folder. Please bring back a small one, shown only when a folder is pinned.

Please also update the PR description. It still talks about the "ask each time" switch and the hidden Save to row.

@anmolkapil: the "ask each time" switch from #203 is gone now. New download already shows the folder and waits for a click, so I think that's fine, but it's your call. The status bar gear and ⌘, / Ctrl+, are still here, and the folder setting now lives in "Speed & data limits".

#227 and #226 are merged now, so please rebase on main. There is one small conflict in the StatusBar.tsx imports (useFreeSpace vs the Settings icon). I tried it with #227, and with that fixed everything builds and the tests pass, apart from the one above.

Comment thread e2e/settings.spec.ts Outdated
expect(newDefaultFiles.length).toBe(0)

// And verify subsequent new download defaults to newDefaultDir
await plexo.page.getByRole('button', { name: 'Downloads' }).click()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

After relaunch() the app is already on the list, so this matches the "All downloads" filter button (not exact) and opens its menu. The next newDownload() then times out because the menu covers the page. Removing this line makes the test pass.

if (chosen) change({ defaultDestinationDir: chosen })
}}
>
Change

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Once a folder is pinned, the only way to unpin it is "Reset to defaults". Could you add a small button next to Change, shown only when draft.defaultDestinationDir is set, that does change({ defaultDestinationDir: undefined })? Your first version had one. Also, when the pinned folder is missing, please say so here, because New download falls back to ~/Downloads.

- Add pinned default download folder in Settings under General

- Show missing folder warning when pinned drive/folder is unavailable

- Provide Reset to default button to unpin default folder

- Default new downloads to pinned folder while allowing one-off overrides

- Fall back safely to system Downloads when pinned directory is missing

- Preserve remembered directory across restarts when unpinned

- Add full E2E test coverage in e2e/settings.spec.ts
@DhruvilPatel603
DhruvilPatel603 force-pushed the feature-default-download-location branch from 099ebe3 to 74bd727 Compare November 7, 2026 15:13
@DhruvilPatel603

Copy link
Copy Markdown
Contributor Author

Thanks for the review @Arpit-Khandelwal! I've addressed all the feedback and rebased the branch on latest main:

1. Fixed settings.spec.ts:240 Failure

  • Removed await plexo.page.getByRole('button', { name: 'Downloads' }).click() after relaunch in the resume test.
  • Since the app is already on the downloads list after relaunch, that call was accidentally clicking the "All downloads" filter menu button and blocking newDownload(). All 9 tests now pass cleanly.

2. "Reset to default" Button in Settings (LimitsDialog.tsx)

  • Brought back the ghost Reset to default button next to Change under Default download folder, shown only when a folder is pinned (draft.defaultDestinationDir !== undefined).
  • Clicking it clears draft.defaultDestinationDir back to unpinned mode without resetting speed limits, slow mode, or schedules.
  • Added test coverage in e2e/settings.spec.ts (pinned default folder can be unpinned with Reset to default) verifying both UI state and that defaultDestinationDir is cleanly deleted from app-settings.json on disk.

3. Missing/Unplugged Pinned Folder Indicator in Settings

  • When a pinned folder is missing from disk, Settings now indicates: (folder missing — falls back to system default) in red/danger styling, clarifying why New Download is defaulting to system Downloads.
  • Covered by the test: a missing pinned folder falls back safely and can be changed.

4. Rebase on main & Conflict Resolution

  • Rebased on latest main incorporating merged PR

This branch has not been deployed

No deployments
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.

[Important for a downloading Manager App]

2 participants