diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 20:46:39 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 20:46:39 +0200 |
| commit | 37225762109ad8d11e073f8661359860d678f84a (patch) | |
| tree | 45ff4cb1fb2c244c5ed144bd41268c5e23dfd0fc /packages/meshbay-hub/src | |
| parent | 6c71341422712aee3d4271ecf805146396b9d0e2 (diff) | |
| download | meshbay-37225762109ad8d11e073f8661359860d678f84a.tar.gz | |
fix(client): the folder picker had no background, and its field no layout
Two defects from one screenshot, and a test for the class behind the first.
**The modal was transparent.** `.ftp-panel` asked for `var(--bg-panel,
var(--bg))` and this stylesheet defines neither — the palette is `--bg-base`,
`--bg-surface`, `--bg-raised`. An unknown custom property makes the whole
declaration invalid while the rule around it still applies, so the panel simply
had no background and the page showed straight through it. Three more of mine
were the same: `--bg-hover`, `--bg-input`, `--danger`.
**The field had no layout.** It was built on `.settings-row`, which is
`display:flex; justify-content:space-between` — so a label, a hint and a value
inside one end up spread across a single line in source order, which is how it
read as three unrelated fragments per app. It is its own block now, and the
chosen folders are a table borrowing `.shared-dirs-tbl`: these are lists, and a
wrapped run of chips gives nothing to scan and nowhere to put a per-row remove.
The operator is looking at two lists of directories on one page and they should
read alike.
`test_css_variables.py` is the general form. CSS fails silently and generously
here, and nothing checked. It found four more that predate this branch: the
unread-count badge (`--danger`) had white text on nothing, a transfer link had
no colour, a notification card had no rounding and no unread marker. Fixed, and
`--warn` and `--accent-bg` are promoted from literal fallbacks to real palette
entries at exactly their current light values.
Two of its own regexes were wrong before they were right — a scoped definition
written inline, and one preceded by a comment, were both reported as undefined.
A third check comparing the two palettes fired on `--border-focus`, a focus
ring the themes share deliberately; a heuristic that has to be explained away
on its first run is worse than no test, so it is gone rather than exempted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
Diffstat (limited to 'packages/meshbay-hub/src')
12 files changed, 123 insertions, 29 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/folder-tree.js b/packages/meshbay-hub/src/meshbay_hub/static/folder-tree.js index 1af6573..6e6dfad 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/folder-tree.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/folder-tree.js @@ -215,8 +215,19 @@ function FolderTreePicker({ } /** - * The button-plus-modal pairing every settings pane wants, so none of them - * has to hold `open` state of its own. + * The chosen folders, as a table, plus the button that opens the picker. + * + * Not a `.settings-row`: that class is `display:flex; justify-content: + * space-between`, so a label, a hint and a value laid out inside one end up + * spread across a single line in whatever order they were written — which is + * how the first version of this read as three unrelated fragments per app. + * + * A table rather than a row of chips because these are lists now. Videos and + * Music can hold several folders, Photos routinely does, and a wrapped run of + * chips gives no column to scan and nowhere to put a per-row control. One + * folder per line, removable where it sits, in the same shape as the shared + * directories table above it — the operator is looking at two lists of + * directories on one page and they should read alike. */ function FolderPickerField({ label, hint, roots, dirs, mode = 'single', requireWritable = false, @@ -226,21 +237,52 @@ function FolderPickerField({ const multi = mode === 'multi'; const chosen = multi ? (value || []) : (value ? [value] : []); + const removeAt = (path) => { + if (!multi) { onChange(''); return; } + onChange(chosen.filter((p) => p !== path)); + }; + return html` - <div class="settings-row"> - <label class="settings-label">${label}</label> - ${hint && html`<p class="settings-hint">${hint}</p>`} - <div class="ftp-field"> - <div class="ftp-field-value"> - ${chosen.length - ? chosen.map((p) => html`<code key=${p} class="ftp-chip">${p}</code>`) - : html`<span class="settings-hint">${t('folder_tree.nothing_selected')}</span>`} - </div> + <div class="folder-field"> + <div class="folder-field-head"> + <h4 class="folder-field-label">${label}</h4> + ${hint && html`<p class="settings-hint">${hint}</p>`} + </div> + + ${chosen.length > 0 && html` + <table class="shared-dirs-tbl folder-field-tbl"> + <tbody> + ${chosen.map((path) => html` + <tr key=${path}> + <td class="sdt-col-dir"> + <span class="sdt-dir-name"> + <${Icon} name="folder" /> + ${path} + </span> + </td> + <td class="sdt-col-actions"> + <button class="sdt-action-btn sdt-action-danger" + disabled=${disabled} + title=${t('folder_tree.remove')} + onClick=${() => removeAt(path)}>\u{2715}</button> + </td> + </tr> + `)} + </tbody> + </table> + `} + + <div class="folder-field-actions"> + ${chosen.length === 0 && html` + <span class="settings-hint">${t('folder_tree.nothing_selected')}</span>`} <button class="btn btn-small btn-secondary" disabled=${disabled} onClick=${() => setOpen(true)}> - <${Icon} name="folder" /> ${t('folder_tree.choose')} + <${Icon} name="folder" /> + ${' '}${chosen.length && multi ? t('folder_tree.add') + : t('folder_tree.choose')} </button> </div> + ${open && html` <${FolderTreePicker} roots=${roots} dirs=${dirs} mode=${mode} 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 edaa70a..2e7d3c5 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js @@ -821,6 +821,8 @@ export default { 'folder_tree.title_single': 'Ordner auswählen', 'folder_tree.title_multi': 'Ordner auswählen', 'folder_tree.choose': 'Auswählen…', + 'folder_tree.remove': 'Entfernen', + 'folder_tree.add': 'Ordner hinzufügen…', 'folder_tree.confirm': 'Übernehmen', 'folder_tree.expand': 'Aufklappen', 'folder_tree.collapse': 'Zuklappen', 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 d1aca93..350a597 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js @@ -609,6 +609,8 @@ export default { 'folder_tree.title_single': 'Choose a folder', 'folder_tree.title_multi': 'Choose folders', 'folder_tree.choose': 'Choose…', + 'folder_tree.remove': 'Remove', + 'folder_tree.add': 'Add a folder…', 'folder_tree.confirm': 'Use these', 'folder_tree.expand': 'Expand', 'folder_tree.collapse': 'Collapse', 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 880de2e..22a9461 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js @@ -817,6 +817,8 @@ export default { 'folder_tree.title_single': 'Elegir una carpeta', 'folder_tree.title_multi': 'Elegir carpetas', 'folder_tree.choose': 'Elegir…', + 'folder_tree.remove': 'Quitar', + 'folder_tree.add': 'Añadir una carpeta…', 'folder_tree.confirm': 'Usar', 'folder_tree.expand': 'Expandir', 'folder_tree.collapse': 'Contraer', 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 eb977f3..3da68b2 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js @@ -835,6 +835,8 @@ export default { 'folder_tree.title_single': 'Choisir un dossier', 'folder_tree.title_multi': 'Choisir des dossiers', 'folder_tree.choose': 'Choisir…', + 'folder_tree.remove': 'Retirer', + 'folder_tree.add': 'Ajouter un dossier…', 'folder_tree.confirm': 'Utiliser', 'folder_tree.expand': 'Déplier', 'folder_tree.collapse': 'Replier', 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 2f58015..335ed68 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js @@ -831,6 +831,8 @@ export default { 'folder_tree.title_single': 'Scegli una cartella', 'folder_tree.title_multi': 'Scegli le cartelle', 'folder_tree.choose': 'Scegli…', + 'folder_tree.remove': 'Rimuovi', + 'folder_tree.add': 'Aggiungi una cartella…', 'folder_tree.confirm': 'Usa', 'folder_tree.expand': 'Espandi', 'folder_tree.collapse': 'Comprimi', 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 f22ce21..4dd2f30 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js @@ -815,6 +815,8 @@ export default { 'folder_tree.title_single': 'フォルダーを選択', 'folder_tree.title_multi': 'フォルダーを選択', 'folder_tree.choose': '選択…', + 'folder_tree.remove': '外す', + 'folder_tree.add': 'フォルダーを追加…', 'folder_tree.confirm': '決定', 'folder_tree.expand': '展開', 'folder_tree.collapse': '折りたたむ', 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 306cf44..cb36d38 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js @@ -833,6 +833,8 @@ export default { 'folder_tree.title_single': 'Kies een map', 'folder_tree.title_multi': 'Kies mappen', 'folder_tree.choose': 'Kiezen…', + 'folder_tree.remove': 'Verwijderen', + 'folder_tree.add': 'Map toevoegen…', 'folder_tree.confirm': 'Gebruiken', 'folder_tree.expand': 'Uitklappen', 'folder_tree.collapse': 'Inklappen', 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 f2b3faf..91d0d90 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js @@ -859,6 +859,8 @@ export default { 'folder_tree.title_single': 'Wybierz folder', 'folder_tree.title_multi': 'Wybierz foldery', 'folder_tree.choose': 'Wybierz…', + 'folder_tree.remove': 'Usuń', + 'folder_tree.add': 'Dodaj folder…', 'folder_tree.confirm': 'Użyj', 'folder_tree.expand': 'Rozwiń', 'folder_tree.collapse': 'Zwiń', 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 e608fbd..2b26a0b 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 @@ -818,6 +818,8 @@ export default { 'folder_tree.title_single': 'Escolher uma pasta', 'folder_tree.title_multi': 'Escolher pastas', 'folder_tree.choose': 'Escolher…', + 'folder_tree.remove': 'Remover', + 'folder_tree.add': 'Adicionar uma pasta…', 'folder_tree.confirm': 'Usar', 'folder_tree.expand': 'Expandir', 'folder_tree.collapse': 'Recolher', 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 68d794e..f1d7f9c 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 @@ -802,6 +802,8 @@ export default { 'folder_tree.title_single': '选择文件夹', 'folder_tree.title_multi': '选择文件夹', 'folder_tree.choose': '选择…', + 'folder_tree.remove': '移除', + 'folder_tree.add': '添加文件夹…', 'folder_tree.confirm': '使用', 'folder_tree.expand': '展开', 'folder_tree.collapse': '折叠', diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css index 1354340..0441e14 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/style.css +++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css @@ -24,6 +24,11 @@ --nav-brand: #23b1f0; --sidebar-bg: #f1f5f9; --sidebar-hover: #e2e8f0; + /* Used with a literal fallback in a handful of rules before they were + defined here — same values, so nothing moves; now themeable, and the + names in those rules are real. */ + --warn: #d97706; + --accent-bg: rgba(59, 130, 246, 0.1); --shadow: 0 1px 3px rgba(0, 0, 0, 0.08); --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.1); } @@ -36,6 +41,8 @@ --text-secondary: #94a3b8; --text-dim: #64748b; --border: #334155; + --warn: #f59e0b; + --accent-bg: rgba(59, 130, 246, 0.18); --error-bg: #450a0a; --nav-bg: #020617; --sidebar-bg: #1e293b; @@ -1910,7 +1917,8 @@ button:disabled { opacity: 0.5; cursor: not-allowed; } white-space: nowrap; } a.transfer-name { - color: var(--link); + /* `--link` is not defined here; the palette's link colour is `--accent`. */ + color: var(--accent); text-decoration: underline; cursor: pointer; } @@ -2138,7 +2146,11 @@ a.transfer-name { position: absolute; top: -6px; right: -8px; - background: var(--danger); + /* `--danger` is not one of this stylesheet's variables — the palette calls + it `--error`. The declaration was invalid, so the unread-count badge had + white text on no background at all. Pre-existing; found while fixing four + more of the same in the folder picker. */ + background: var(--error); color: #fff; font-size: 0.65rem; padding: 1px 5px; @@ -2160,7 +2172,8 @@ a.transfer-name { align-items: center; gap: 8px; padding: 8px 12px; - border-radius: var(--radius); + /* `--radius` was never defined — 8px is what the other cards use. */ + border-radius: 8px; background: var(--bg-surface); margin-bottom: 4px; cursor: pointer; @@ -2170,7 +2183,8 @@ a.transfer-name { background: var(--bg-raised); } .notif-unread { - border-left: 3px solid var(--primary); + /* `--primary` is not defined here either; it is `--accent`. */ + border-left: 3px solid var(--accent); font-weight: 600; } .notif-kind { @@ -2578,7 +2592,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } .sdt-add-input { flex: 1 1 auto; min-width: 0; padding: 5px 8px; border: 1px solid var(--border); border-radius: 4px; - background: var(--bg-input, transparent); color: var(--text); + background: var(--bg-surface); color: var(--text); font-family: inherit; font-size: 0.9em; } .sdt-col-toggle { width: 90px; text-align: center; } @@ -2591,9 +2605,9 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } line-height: 1; vertical-align: middle; } .sdt-action-btn + .sdt-action-btn { margin-left: 6px; } -.sdt-action-btn:hover:not(:disabled) { background: var(--bg-hover); color: var(--text); } +.sdt-action-btn:hover:not(:disabled) { background: var(--bg-raised); color: var(--text); } .sdt-action-btn:disabled { opacity: 0.3; cursor: default; } -.sdt-action-danger:hover:not(:disabled) { color: var(--danger, #ef4444); border-color: var(--danger, #ef4444); } +.sdt-action-danger:hover:not(:disabled) { color: var(--error); border-color: var(--error); } .sdt-dir-name { display: inline-flex; align-items: center; gap: 6px; font-weight: 500; } .sdt-dir-name .icon { width: 16px; height: 16px; flex-shrink: 0; } .sdt-row-ejected { opacity: 0.5; } @@ -2603,7 +2617,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } padding: 2px 6px; cursor: pointer; font-size: 0.85em; color: var(--text-dim); line-height: 1; } -.root-eject-btn:hover { background: var(--bg-hover); } +.root-eject-btn:hover { background: var(--bg-raised); } .root-eject-btn { margin-left: 8px; } /* An ejected root in the Files table: still listed, deliberately — its files are frozen, not gone — but not somewhere you can walk into. */ @@ -4076,7 +4090,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } padding: 16px; } .ftp-panel { - background: var(--bg-panel, var(--bg)); color: var(--text); + background: var(--bg-surface); color: var(--text); border: 1px solid var(--border); border-radius: 8px; width: min(520px, 100%); max-height: min(70vh, 640px); display: flex; flex-direction: column; padding: 16px; @@ -4093,8 +4107,11 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } padding: 4px 0; margin: 8px 0; } .ftp-row { display: flex; align-items: center; gap: 2px; } -.ftp-row.chosen { background: var(--bg-hover); } -.ftp-row.blocked { opacity: 0.4; } +.ftp-row.chosen { background: var(--bg-raised); } +/* 0.4 was low enough that a greyed-out root read as an artefact rather than + a disabled control — the names were barely legible in the screenshot that + prompted this. */ +.ftp-row.blocked { opacity: 0.55; } .ftp-twisty { flex: 0 0 auto; width: 20px; height: 20px; padding: 0; background: none; border: none; color: var(--text-dim); @@ -4111,11 +4128,14 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } .ftp-label .icon { width: 15px; height: 15px; flex-shrink: 0; } .ftp-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .ftp-badge { - flex: 0 0 auto; font-size: 0.72em; padding: 1px 5px; border-radius: 3px; - border: 1px solid var(--border); color: var(--text-dim); + flex: 0 0 auto; margin-left: 8px; + font-size: 0.72em; padding: 1px 5px; border-radius: 3px; + border: 1px solid var(--border); background: var(--bg-raised); + color: var(--text-dim); white-space: nowrap; text-transform: uppercase; letter-spacing: 0.03em; } -.ftp-badge.warn { color: var(--danger, #ef4444); border-color: currentColor; } +.ftp-badge.rw { color: var(--accent); border-color: currentColor; } +.ftp-badge.warn { color: var(--error); border-color: currentColor; } .ftp-check { margin-left: auto; flex: 0 0 auto; } .ftp-selection { display: flex; flex-wrap: wrap; gap: 4px; @@ -4123,8 +4143,20 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; } } .ftp-chip { font-size: 0.82em; padding: 2px 6px; border-radius: 3px; - background: var(--bg-hover); border: 1px solid var(--border); + background: var(--bg-raised); border: 1px solid var(--border); } .ftp-actions { display: flex; justify-content: flex-end; gap: 8px; } -.ftp-field { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } -.ftp-field-value { flex: 1 1 200px; display: flex; flex-wrap: wrap; gap: 4px; } +/* An app's chosen folders. Deliberately not a .settings-row: that is a + space-between flex row, and a label, a hint and a list laid out inside one + end up spread across a single line in source order. */ +.folder-field { padding: 8px 0; } +.folder-field-head { margin-bottom: 6px; } +.folder-field-label { + margin: 0 0 2px; font-size: 0.9em; font-weight: 600; color: var(--text); +} +.folder-field-head .settings-hint { margin-bottom: 0; } +/* Borrows .shared-dirs-tbl so the two lists of directories on this page read + as the same kind of thing. */ +.folder-field-tbl { margin-bottom: 8px; } +.folder-field-tbl .sdt-col-dir { font-size: 0.9em; } +.folder-field-actions { display: flex; align-items: center; gap: 10px; } |