summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-09 14:24:59 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-09 14:24:59 +0200
commite6f895c473a0b19e7b186889c1836d3945bc880b (patch)
tree05689049fd48f01ebbdb9995d5189cba15cee052 /packages/meshbay-hub
parentb472b4d43149ed12a7f64c94f34c34f436bef492 (diff)
downloadmeshbay-e6f895c473a0b19e7b186889c1836d3945bc880b.tar.gz
fix(spa): say why a download cannot be paused
Reported from Chrome, with a screenshot: four downloads with no pause button and an upload beside them with one, and nothing anywhere saying why. The reason is real. Without a granted download folder the browser writes through the service worker — a download it already owns, which cannot be paused without stalling it somewhere we can neither see nor resume. An upload writes to the node, which keeps the position, so it is always pausable. But that was stated only in a Settings line nobody reads on the way to a download, and a gap where the row above has a button is not an explanation. So a download that cannot be paused now shows a dimmed pause icon where the button would be, carrying the reason and the remedy in its tooltip. Not a button: there is nothing to click, and a disabled one invites the click anyway. And only where the advice can be taken. Firefox and Safari have no folder to choose — the streamed path is the only target they have, which is what §6.5 of ~/next/improve-downloads.md costs out — so telling someone there to choose one would be advice they cannot follow. Nothing is drawn. Hub suite 866 passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
Diffstat (limited to 'packages/meshbay-hub')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js14
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css10
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py35
13 files changed, 69 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index de47982..dfd0aeb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -269,6 +269,20 @@ function TransferRow({ it }) {
<${Icon} name=${it.status === 'paused' ? 'play' : 'pause'} />
</button>
`}
+ ${!it.pausable && downloads.SUPPORTED && it.kind === 'download'
+ && (it.status === 'running' || it.status === 'queued') && html`
+ ${/* Say why, rather than leaving a gap where a button is on the row
+ above. Without a granted folder this browser writes through the
+ service worker — a download it already owns, which cannot be
+ paused — so the button is absent for a reason nobody can see,
+ and an upload beside it has one. Shown only where choosing a
+ folder is actually possible: on Firefox and Safari there is no
+ folder to choose and this hint would be a lie. */''}
+ <span class="transfer-nopause" title=${t('transfers.not_pausable')}
+ aria-label=${t('transfers.not_pausable')}>
+ <${Icon} name="pause" />
+ </span>
+ `}
${(it.status === 'running' || it.status === 'queued'
|| it.status === 'preparing' || it.status === 'paused') && html`
<button class="transfer-cancel"
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index 738b131..0f632e3 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -590,6 +590,7 @@ export default {
'transfers.pause': 'Anhalten',
'transfers.resume': 'Fortsetzen',
'transfers.paused': 'Angehalten',
+ 'transfers.not_pausable': 'Kann nicht angehalten werden — wählen Sie in den Einstellungen einen Download-Ordner',
'transfers.pause_one': '{name} anhalten',
'transfers.resume_one': '{name} fortsetzen',
'transfers.eta_seconds': 'noch {n} s',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 0b9de8a..cb7f4a0 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -706,6 +706,7 @@ export default {
'transfers.pause': 'Pause',
'transfers.resume': 'Resume',
'transfers.paused': 'Paused',
+ 'transfers.not_pausable': 'Cannot be paused — choose a download folder in Settings to enable it',
'transfers.pause_one': 'Pause {name}',
'transfers.resume_one': 'Resume {name}',
'transfers.eta_seconds': '{n}s left',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index 8da8f9f..fe1c13b 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -586,6 +586,7 @@ export default {
'transfers.pause': 'Pausar',
'transfers.resume': 'Reanudar',
'transfers.paused': 'En pausa',
+ 'transfers.not_pausable': 'No se puede pausar — elija una carpeta de descargas en Ajustes para activarlo',
'transfers.pause_one': 'Pausar {name}',
'transfers.resume_one': 'Reanudar {name}',
'transfers.eta_seconds': 'quedan {n} s',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index 3135c03..ea3f60e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -589,6 +589,7 @@ export default {
'transfers.pause': 'Suspendre',
'transfers.resume': 'Reprendre',
'transfers.paused': 'En pause',
+ 'transfers.not_pausable': 'Non suspendable — choisissez un dossier de téléchargement dans les Réglages pour l\'activer',
'transfers.pause_one': 'Suspendre {name}',
'transfers.resume_one': 'Reprendre {name}',
'transfers.eta_seconds': '{n} s restantes',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 91f0e9b..0687b25 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -589,6 +589,7 @@ export default {
'transfers.pause': 'Sospendi',
'transfers.resume': 'Riprendi',
'transfers.paused': 'In pausa',
+ 'transfers.not_pausable': 'Non si può sospendere — scelga una cartella di download nelle Impostazioni',
'transfers.pause_one': 'Sospendi {name}',
'transfers.resume_one': 'Riprendi {name}',
'transfers.eta_seconds': '{n} s rimanenti',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index b352188..a02e058 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -581,6 +581,7 @@ export default {
'transfers.pause': '一時停止',
'transfers.resume': '再開',
'transfers.paused': '一時停止中',
+ 'transfers.not_pausable': '一時停止できません。設定でダウンロードフォルダーを選ぶと使えます',
'transfers.pause_one': '{name} を一時停止',
'transfers.resume_one': '{name} を再開',
'transfers.eta_seconds': '残り {n} 秒',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index e0a799d..0235d8a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -590,6 +590,7 @@ export default {
'transfers.pause': 'Pauzeren',
'transfers.resume': 'Hervatten',
'transfers.paused': 'Gepauzeerd',
+ 'transfers.not_pausable': 'Kan niet worden gepauzeerd — kies een downloadmap in Instellingen',
'transfers.pause_one': '{name} pauzeren',
'transfers.resume_one': '{name} hervatten',
'transfers.eta_seconds': 'nog {n} s',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index e6319b0..af4a5bb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -602,6 +602,7 @@ export default {
'transfers.pause': 'Wstrzymaj',
'transfers.resume': 'Wznów',
'transfers.paused': 'Wstrzymano',
+ 'transfers.not_pausable': 'Nie można wstrzymać — proszę wybrać folder pobierania w Ustawieniach',
'transfers.pause_one': 'Wstrzymaj {name}',
'transfers.resume_one': 'Wznów {name}',
'transfers.eta_seconds': 'pozostało {n} s',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index 02c8356..f179c6f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -588,6 +588,7 @@ export default {
'transfers.pause': 'Pausar',
'transfers.resume': 'Retomar',
'transfers.paused': 'Pausado',
+ 'transfers.not_pausable': 'Não pode ser pausado — escolha uma pasta de downloads em Configurações',
'transfers.pause_one': 'Pausar {name}',
'transfers.resume_one': 'Retomar {name}',
'transfers.eta_seconds': 'faltam {n} s',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index 6c4c73f..c89bbdc 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -569,6 +569,7 @@ export default {
'transfers.pause': '暂停',
'transfers.resume': '继续',
'transfers.paused': '已暂停',
+ 'transfers.not_pausable': '无法暂停——请在设置中选择下载文件夹以启用',
'transfers.pause_one': '暂停 {name}',
'transfers.resume_one': '继续 {name}',
'transfers.eta_seconds': '剩余 {n} 秒',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index a37e541..5e08a6a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -1986,6 +1986,16 @@ a.transfer-name {
display: flex;
}
.transfer-pause:hover { color: var(--accent); }
+/* Not a button: there is nothing to click. Dimmer than the cancel beside it,
+ and it carries its explanation in a tooltip rather than in the row, which
+ would be four lines of prose in a panel that has none. */
+.transfer-nopause {
+ color: var(--text-dim);
+ opacity: .45;
+ padding: 0 2px;
+ display: flex;
+ cursor: help;
+}
/* A paused bar keeps its fill -- what was written is still on disk -- but stops
looking like something in progress. */
.dl-fill.dl-paused { background: var(--text-dim); }
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index d19a458..d93cf80 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -879,3 +879,38 @@ console.log(JSON.stringify({ active }));
proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
assert proc.returncode == 0, proc.stderr
assert json.loads(proc.stdout)["active"] == 1
+
+
+def test_a_row_that_cannot_pause_says_so_where_the_button_would_be():
+ """Reported from Chrome: four downloads with no pause button and an upload
+ with one, and no way to tell why.
+
+ The reason is real — without a granted folder the browser writes through the
+ service worker, a download it already owns and cannot pause — but it was
+ stated only in a Settings line nobody reads on the way to a download. A gap
+ where the row above has a button is not an explanation.
+
+ Shown only where a folder can actually be chosen: Firefox and Safari have
+ none to choose, and "choose a folder" would be advice that cannot be taken.
+ """
+ src = (STATIC / "app.js").read_text()
+ row = src[src.index("function TransferRow"):]
+ row = row[:row.index("\n}\n")]
+
+ hint = row[row.index("!it.pausable"):]
+ hint = hint[:hint.index("`}")]
+ assert "downloads.SUPPORTED" in hint, (
+ "the hint would tell a Firefox user to choose a folder it cannot offer")
+ assert "it.kind === 'download'" in hint, (
+ "an upload is always pausable; this is about download targets")
+ assert "transfers.not_pausable" in hint, "the reason is not stated"
+ # Not a button. There is nothing to click, and a disabled one invites the
+ # click anyway.
+ assert "<button" not in hint
+
+
+def test_the_reason_is_translated_everywhere():
+ """`t()` falls back to the key, so a missing catalogue entry shows
+ `transfers.not_pausable` in a tooltip rather than a sentence."""
+ for path in sorted((STATIC / "locales").glob("*.js")):
+ assert "'transfers.not_pausable'" in path.read_text(), path.name