summaryrefslogtreecommitdiffstats
path: root/packages
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-18 17:11:00 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-18 17:11:00 +0200
commit2ef5498ab1509a93691b87b4cc5d9b52bb3f52dc (patch)
tree9f5343cc0eed9d3cc42e6bdb079d8aa6307766c4 /packages
parenteedbca3f0d47af39b4dd8812683e5a14ae4e48e6 (diff)
downloadmeshbay-2ef5498ab1509a93691b87b4cc5d9b52bb3f52dc.tar.gz
fix(ui): name the section after its button, drop the folder slash, colour the widget
**"Zone sensible" said nothing.** The heading is now the name of the action in it — "Quitter le groupe", or "Supprimer le groupe" for the owner, who sees a different button. Worth stating because it is not quite what was asked for: a fixed "Quitter le groupe" would have sat above a delete button for whoever owns the group. The red goes with it; only the button is red, which is where the warning belongs. `members.danger_title` is gone from all ten locales rather than left behind unread. **A folder name no longer ends in a slash.** The folder icon in the cell beside it already says what it is. **The transfers widget turns green while transfers run.** The badge counts them, but a count has to be read; colour is what carries from across the room, which is the point of a widget in the nav bar rather than on the page. Derived from the live list on every render, so there is no state that can forget to clear when the last transfer ends. The class is set in `app.js` and coloured in `style.css` — either alone does nothing and neither fails loudly, so there is a test for each half. 848 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js11
-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.css12
-rw-r--r--packages/meshbay-hub/tests/test_spa_ordering.py2
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py47
14 files changed, 60 insertions, 22 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index c460f23..eb11469 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -504,7 +504,8 @@ function TransferWidget() {
return html`
<div class="transfer-wrap" ref=${ref}>
- <button class="nav-notif transfer-btn" title=${t('transfers.title')}
+ <button class="nav-notif transfer-btn ${running.length ? 'active' : ''}"
+ title=${t('transfers.title')}
onClick=${(e) => { e.stopPropagation(); setOpen(o => !o); }}>
<${Icon} name="transfer" />
${running.length > 0 && html`
@@ -2057,7 +2058,7 @@ function GroupPage({ groupId, group, token, username, userId, onRefreshAuth,
</td>
`}
<td>${unavailableHere.includes(d) ? '\u{26A0}' : '\u{1F4C1}'}</td>
- <td>${d}/${unavailableHere.includes(d) ? html`
+ <td>${d}${unavailableHere.includes(d) ? html`
<span class="root-offline"> ${t('group.root_unavailable')}</span>
` : ''}</td>
<td class="file-size">${inside.length ? formatSize(bytes) : ''}</td>
@@ -2562,8 +2563,10 @@ function GroupSettingsPanel({ groupId, group, token, transportRef, gekRef,
${/* Before the roster, not after it: this is what someone came here to
do, and a list of two hundred names is a long way to scroll for
it. */ html`
- <div class="settings-section danger-section">
- <h3 class="settings-heading">${t('members.danger_title')}</h3>
+ <div class="settings-section">
+ <h3 class="settings-heading">
+ ${isOwner ? t('group.delete_group') : t('group.leave')}
+ </h3>
<div class="settings-row">
<span class="settings-label">
${isOwner ? t('members.danger_delete_hint')
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 d222bba..7eaf86f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -78,7 +78,6 @@ export default {
'group.tab_chat': 'Chat',
'group.tab_members': 'Mitglieder',
'group.tab_settings': "Einstellungen",
- 'members.danger_title': "Gefahrenbereich",
'members.danger_leave_hint': "Sie verlieren den Zugriff auf die Dateien und den Chat dieser Gruppe.",
'members.danger_delete_hint': "Die Gruppe verschwindet für alle Mitglieder. Das lässt sich nicht rückgängig machen.",
'group.filter': 'Dateien filtern …',
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 33e8f5e..b68d02d 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -79,7 +79,6 @@ export default {
'group.tab_chat': 'Chat',
'group.tab_members': 'Members',
'group.tab_settings': "Settings",
- 'members.danger_title': "Danger zone",
'members.danger_leave_hint': "You will lose access to this group's files and chat.",
'members.danger_delete_hint': "This removes the group for every member. It cannot be undone.",
'group.filter': 'Filter files...',
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 81b2c97..20f6910 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -76,7 +76,6 @@ export default {
'group.tab_chat': 'Chat',
'group.tab_members': 'Miembros',
'group.tab_settings': "Ajustes",
- 'members.danger_title': "Zona de riesgo",
'members.danger_leave_hint': "Perderá el acceso a los archivos y al chat de este grupo.",
'members.danger_delete_hint': "El grupo desaparece para todos sus miembros. No se puede deshacer.",
'group.filter': 'Filtrar archivos...',
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 e44b03b..c3c5580 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -77,7 +77,6 @@ export default {
'group.tab_chat': 'Discussion',
'group.tab_members': 'Membres',
'group.tab_settings': "Paramètres",
- 'members.danger_title': "Zone sensible",
'members.danger_leave_hint': "Vous perdrez l’accès aux fichiers et à la discussion de ce groupe.",
'members.danger_delete_hint': "Le groupe disparaît pour tous ses membres. C’est irréversible.",
'group.filter': 'Filtrer les fichiers...',
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 206c749..1ae8217 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -77,7 +77,6 @@ export default {
'group.tab_chat': 'Chat',
'group.tab_members': 'Membri',
'group.tab_settings': "Impostazioni",
- 'members.danger_title': "Zona critica",
'members.danger_leave_hint': "Perderai l’accesso ai file e alla chat di questo gruppo.",
'members.danger_delete_hint': "Il gruppo scompare per tutti i membri. Non è reversibile.",
'group.filter': 'Filtra i file...',
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 b4ccfaf..f8713f5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -75,7 +75,6 @@ export default {
'group.tab_chat': 'チャット',
'group.tab_members': 'メンバー',
'group.tab_settings': "設定",
- 'members.danger_title': "取り扱い注意",
'members.danger_leave_hint': "このグループのファイルとチャットにアクセスできなくなります。",
'members.danger_delete_hint': "グループはすべてのメンバーから消えます。元に戻せません。",
'group.filter': 'ファイルを絞り込み…',
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 ac75f26..497cc4a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -78,7 +78,6 @@ export default {
'group.tab_chat': 'Chat',
'group.tab_members': 'Leden',
'group.tab_settings': "Instellingen",
- 'members.danger_title': "Gevarenzone",
'members.danger_leave_hint': "U verliest de toegang tot de bestanden en de chat van deze groep.",
'members.danger_delete_hint': "De groep verdwijnt voor alle leden. Dit kan niet ongedaan worden gemaakt.",
'group.filter': 'Bestanden filteren...',
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 fe9c18d..af4861a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -82,7 +82,6 @@ export default {
'group.tab_chat': 'Czat',
'group.tab_members': 'Członkowie',
'group.tab_settings': "Ustawienia",
- 'members.danger_title': "Strefa ryzyka",
'members.danger_leave_hint': "Utracisz dostęp do plików i czatu tej grupy.",
'members.danger_delete_hint': "Grupa zniknie dla wszystkich członków. Tego nie można cofnąć.",
'group.filter': 'Filtruj pliki...',
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 270af8e..8e1de4c 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
@@ -78,7 +78,6 @@ export default {
'group.tab_chat': 'Conversa',
'group.tab_members': 'Membros',
'group.tab_settings': "Configurações",
- 'members.danger_title': "Zona de risco",
'members.danger_leave_hint': "Você perderá o acesso aos arquivos e ao chat deste grupo.",
'members.danger_delete_hint': "O grupo desaparece para todos os membros. Não há como desfazer.",
'group.filter': 'Filtrar arquivos...',
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 6e19df9..4e0a332 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
@@ -75,7 +75,6 @@ export default {
'group.tab_chat': '聊天',
'group.tab_members': '成员',
'group.tab_settings': "设置",
- 'members.danger_title': "危险区域",
'members.danger_leave_hint': "您将无法再访问该群组的文件和聊天。",
'members.danger_delete_hint': "该群组将对所有成员消失,且无法恢复。",
'group.filter': '筛选文件…',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index cb56321..12fc15f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -1333,13 +1333,6 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
min-width: 0;
}
-/* Leaving or deleting a group. Marked, because the two buttons in this tab
- that cannot be undone should not look like the ones that can. */
-.danger-section {
- border-color: color-mix(in srgb, var(--error) 40%, var(--border));
-}
-.danger-section .settings-heading { color: var(--error); }
-
.badge-owner {
background: var(--accent);
color: var(--accent-text);
@@ -1429,6 +1422,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
/* ── Transfers widget ────────────────────────────────────────────────────── */
+/* Green while transfers are running: the count in the badge says how many, but
+ the colour is what is readable without looking at it. Back to the ordinary
+ nav colour the moment the last one finishes. */
+.transfer-btn.active .icon { color: var(--success); }
+
.transfer-wrap { position: relative; display: flex; align-items: center; }
.transfer-btn {
background: none;
diff --git a/packages/meshbay-hub/tests/test_spa_ordering.py b/packages/meshbay-hub/tests/test_spa_ordering.py
index 1556cb7..dfc92f9 100644
--- a/packages/meshbay-hub/tests/test_spa_ordering.py
+++ b/packages/meshbay-hub/tests/test_spa_ordering.py
@@ -144,7 +144,7 @@ def test_the_roster_comes_last():
for name, marker in (("the invite form", "onSubmit=${doInvite}"),
("the pairing form", "onSubmit=${doPair}"),
("the devices section", "device.mine_title"),
- ("leaving and deleting", "members.danger_title")):
+ ("leaving and deleting", "group.delete_group_confirm")):
assert panel.index(marker) < listing, f"{name} belongs above the roster"
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index 08743f6..35fe1cc 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -167,3 +167,50 @@ def test_the_rate_is_readable(tmp_path):
say(formatSpeed(0), formatSpeed(2048), formatSpeed(5 * 1024 * 1024));
""", tmp_path)
assert result == ["", "2 KB/s", "5.0 MB/s"]
+
+
+# ── What the widget shows without being opened ──────────────────────────────
+
+APP = STATIC / "app.js"
+
+
+def _widget() -> str:
+ source = APP.read_text(encoding="utf-8")
+ start = source.index("function TransferWidget(")
+ return source[start:source.index("\nfunction ", start + 1)]
+
+
+def test_the_widget_marks_itself_while_transfers_run():
+ """
+ The badge counts them, but a count has to be read. Colour is what says
+ "something is moving" from across the room, which is the point of a widget
+ that lives in the nav bar rather than on the page.
+ """
+ widget = _widget()
+ assert "running.length ? 'active' : ''" in widget, (
+ "the button no longer marks itself while transfers are running")
+
+
+def test_the_mark_comes_off_when_the_last_one_finishes():
+ """`running` is derived from the live list on every render, not stored — so
+ there is no state to forget to clear."""
+ widget = _widget()
+ assert "const running = items.filter(i => i.status === 'running');" in widget
+
+
+def test_the_colour_is_defined_for_that_mark():
+ """The class is set in one file and coloured in another; either alone does
+ nothing, and neither fails loudly."""
+ css = (STATIC / "style.css").read_text(encoding="utf-8")
+ assert ".transfer-btn.active" in css
+
+
+# ── The file list ───────────────────────────────────────────────────────────
+
+def test_a_folder_name_carries_no_trailing_slash():
+ """The folder icon in the cell beside it already says what it is."""
+ source = APP.read_text(encoding="utf-8")
+ row = source[source.index('class="file-row dir-row"'):]
+ row = row[:row.index("</tr>")]
+ assert "${d}/" not in row, "the folder name is rendered with a trailing slash"
+ assert "${d}" in row