Termix-SSH / Termix-SSH/Support

[BUG]

Open Beginner friendly
#1,255 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug file-manager good-first-issue platform-docker platform-web ui
Dominant language
No language data
Stars
28
Forks
4
PR merge metrics
No merged PRs in 30d

Description

Title

File manager: "File downloaded successfully" toast never disappears — duration: Infinity inherited from the progress toast

Platform

Website - Chrome

Server Installation Method

Docker

Version

2.7.1

CLI Installation Method

None

CLI Version

No response

Troubleshooting
  • I have examined logs and tried to find the issue
  • I have reviewed opened and closed issues
  • I have tried restarting the application
  • I have checked open issues and ensured this is not a duplicate
The Problem

Downloading a file from the file manager works, but the success toast — File «name» downloaded successfully — never auto-dismisses. It sits in the corner until it is dismissed by hand (close button / swipe) or the page is reloaded, while every other toast in the app goes away after a few seconds.

Root cause: sonner merges options into an existing toast instead of replacing them, so duration: Infinity from the progress toast is inherited by the final success toast.

src/ui/features/file-manager/FileManager.tsx:1215-1250

toast.loading(<DownloadProgressToast fileName={file.name} loaded={0} />, {
  id: toastId,
  duration: Infinity,      // correct — a progress toast must not auto-close
});

// ... progress updates re-render the same id, also with duration: Infinity

toast.success(
  t("fileManager.fileDownloadedSuccessfully", { name: file.name }),
  { id: toastId },         // no duration → Infinity survives the merge
);

In sonner 2.0.7 (package.json:188), updating a toast by id is a shallow merge, not a replace — ToastState.create():

if (alreadyExists) {
  this.toasts = this.toasts.map((toast) => {
    if (toast.id === id) {
      this.publish({ ...toast, ...data, id, title: message });

data is { id: toastId }, so duration: Infinity from the loading toast is carried over. The auto-close effect in sonner's Toast component then bails out on exactly that value:

React.useEffect(() => {
  if (toast.promise && toastType === 'loading' || toast.duration === Infinity || toast.type === 'loading') return;
  // ... startTimer() / deleteToast() live below this guard

No close timer is ever armed for the success toast. <Toaster duration={5000}> (src/ui/features/FullScreenAppWrapper.tsx:174) doesn't help — the per-toast duration wins (toast.duration || durationFromToaster || TOAST_LIFETIME).

Same defect in the error branches of the same function (FileManager.tsx:1258-1266): a failed download also leaves a permanent toast, so a few failed downloads permanently fill the corner.

Second occurrence — copy/move transfers. showDefaultCompletionToast() reuses the progress toast id for the completion toast:

// src/ui/features/file-manager/transferProgressMonitor.tsx:235-239
toast.success(t("transfer.transferSuccess"), {
  id: toastId,
  description: metrics || undefined,
  className: TOAST_CLASS,
});

The progress toast it updates was created with duration: Infinity (:263-270, and re-rendered with it at :57), so the "transfer complete" toast sticks too.

Inconsistency inside the same file. The upload path already handles this correctly — it dismisses the progress toast and posts a fresh one:

// FileManager.tsx:1175-1180 (handleUploadFile)
toast.dismiss(progressToast);
toast.success(t("fileManager.fileUploadedSuccessfully", { name: file.name }));

Same in AppShell.tsx:835-836 for the DB-degraded banner. And showCancelledTransferToast() uses the other correct idiom — explicitly resetting the inherited value with duration: showCleanupAction ? Infinity : undefined (transferProgressMonitor.tsx:118). Only the download and transfer-completion paths miss it.

Suggested fix

Reset the duration explicitly on every terminal update of a progress toast:

       toast.success(
         t("fileManager.fileDownloadedSuccessfully", { name: file.name }),
-        { id: toastId },
+        // sonner merges options into the existing toast, so the progress toast's
+        // `duration: Infinity` has to be reset explicitly
+        { id: toastId, duration: undefined },
       );

Same one-line change on:

  • FileManager.tsx:1258-1265toast.error(t("fileManager.sshConnectionFailed", …), { id: toastId })
  • FileManager.tsx:1266toast.error(t("fileManager.failedToDownloadFile"), { id: toastId })
  • transferProgressMonitor.tsx:235-239toast.success(t("transfer.transferSuccess"), { id: toastId, … })

duration: undefined is enough: the key is present in the spread, so it overrides Infinity, and sonner then falls back to the <Toaster duration> value (there is a useEffect(() => { remainingTime.current = duration }, [duration]) that re-arms the timer on the change). The alternative, matching handleUploadFile, is toast.dismiss(toastId) followed by a fresh toast — slightly more code and the toast visually re-enters, but it is the pattern already used elsewhere in the file. Either is fine; happy to send a PR for whichever you prefer.

How to Reproduce
  1. Open a host → File Manager.
  2. Download any file (a few MB, so the progress toast is visible — it also happens on small files).
  3. The download finishes and the File «…» downloaded successfully toast appears bottom-right.
  4. Wait. It never disappears; only a manual dismiss or a page reload removes it. Other toasts in the app vanish after ~5 s.
  5. Same with a failing download (disconnect the host mid-download) and with a completed copy/move transfer.
Additional Context

Seen on 2.7.1 (Docker, ghcr.io/lukegus/termix:2.7.1); the same code is in the shipped bundle of that release (html/assets/FileManager-*.js: l.success(u("fileManager.fileDownloadedSuccessfully",{name:e.name}),{id:t}) right after {id:t,duration:1/0}). Verified still present on main @ 42f8270. Not platform-specific — web and desktop share this code path.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/ui/features/file-manager/FileManager.tsx around the download success and error branches, then inspect showDefaultCompletionToast() in src/ui/features/file-manager/transferProgressMonitor.tsx. Reproduce a download and completed copy/move transfer, and verify that terminal success and error toasts use the normal auto-dismiss behavior instead of inheriting the progress toast duration.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript
Domain
frontend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.