Termix-SSH / Termix-SSH/Support
[BUG]
Nobody has claimed this yet.
- 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-1265—toast.error(t("fileManager.sshConnectionFailed", …), { id: toastId })FileManager.tsx:1266—toast.error(t("fileManager.failedToDownloadFile"), { id: toastId })transferProgressMonitor.tsx:235-239—toast.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
- Open a host → File Manager.
- Download any file (a few MB, so the progress toast is visible — it also happens on small files).
- The download finishes and the File «…» downloaded successfully toast appears bottom-right.
- Wait. It never disappears; only a manual dismiss or a page reload removes it. Other toasts in the app vanish after ~5 s.
- 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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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