aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/style.css
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-06 19:03:22 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-06 19:03:22 +0200
commitab44526a291fa673aa2850d105f6412a70a5341f (patch)
tree5940f18acfc15fc732eb65d90de346920461b8c8 /packages/meshbay-hub/src/meshbay_hub/static/style.css
parent85a2ec47b7ad334208a3dbb091fadccc7631785c (diff)
downloadmeshbay-ab44526a291fa673aa2850d105f6412a70a5341f.tar.gz
feat(client): Phase 2 — per-app settings panes, folder tree, multi-directory
Each app's settings were inlined in `group-settings.js` — TMDB, MusicBrainz, and one folder picker per app, each with its own draft state and save handler saying the same thing about a different key. They are one file per app now, reached through the `apps.js` registry, and the page that renders them names no application at all: adding one is a registry entry and a settings file. The line between the two is what makes that true. What every app has — folders — the page does generically, through one `saveDirectories` bound to the app. What one app alone has, its pane does itself with the transport it is handed. An app that only needs directories touches neither `group-settings.js` nor `group-page.js`, which is `test_app_settings_plugin.py`'s subject. `settings-ui.js` exists because a pane importing the page that renders it is a cycle, and ES modules answer that with a temporal-dead-zone ReferenceError at first render — a component that silently does not appear, the fault already recorded in CLAUDE.md about hook ordering. The flat depth-indented `<select>` of every folder in the library becomes a modal tree. It asks the node for nothing: the tree is derived from paths the client already holds, so it shows exactly what the group's index contains and adds no folder-browsing protocol. For Chat's attachment folder — the one directory that is written to rather than read — read-only roots are greyed out, so the node's refusal arrives before the operator picks rather than when somebody sends a file. Videos and Music take a list of folders. A library on two drives could not be described before; the only recourse was pointing the app at a parent containing both, which pulls in everything else under it. The scalar shapes survive on the wire alone, for a node speaking MNP 1.0, and the client reads them as a one-element list. Two things the tests caught that I would not have: `test_asset_versioning` — six new modules were missing from `_ASSETS`. Reached through the registry rather than imported by name, they are exactly the files nothing else would notice changing, and a stale one is served from cache with no version bump. And `node --check foo.js` does **not** reliably report a module syntax error: it accepted `${/* ... */''}` — htm template syntax pasted into a plain object literal — and reported success. A `.mjs` copy forces the module parser and reports it. The suite had no syntax check at all, which is how that reached a file; `test_spa_syntax.py` does it for every module now, and pins that the loose path is not what it uses. Suite: 12 failures, all pre-existing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011pvMdvLBG92jyhvD5pD6us
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/style.css')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css74
1 files changed, 74 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index eaf1298..1354340 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -1240,6 +1240,18 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
}
.settings-collapsible-header .settings-heading { margin-bottom: 0; }
.settings-collapsible-body { margin-top: 12px; }
+/* The header button stretches; the action (an app's on/off switch) keeps its
+ own width at the end, and its own click. */
+.settings-collapsible-bar { display: flex; align-items: center; gap: 12px; }
+.settings-collapsible-bar .settings-collapsible-header { flex: 1 1 auto; min-width: 0; }
+.settings-collapsible-action { flex: 0 0 auto; }
+
+/* One app's settings pane, inside its section. */
+.app-settings > .settings-row:first-child { margin-top: 0; }
+.app-settings-sub {
+ margin: 18px 0 4px; font-size: 0.9em; font-weight: 600;
+ padding-top: 12px; border-top: 1px solid var(--border);
+}
.settings-meta-title {
display: flex;
@@ -4054,3 +4066,65 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
background: linear-gradient(to top, rgba(0, 0, 0, 0.55), transparent);
}
.photo-lightbox-count { color: #94a3b8; }
+
+/* ── Folder tree picker (folder-tree.js) ─────────────────────────────────── */
+
+.ftp-backdrop {
+ position: fixed; inset: 0; z-index: 1200;
+ background: rgba(0, 0, 0, 0.55);
+ display: flex; align-items: center; justify-content: center;
+ padding: 16px;
+}
+.ftp-panel {
+ background: var(--bg-panel, var(--bg)); 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;
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4);
+}
+.ftp-panel:focus { outline: none; }
+.ftp-title { margin: 0 0 8px; font-size: 1em; }
+/* The tree is the only part that scrolls: the title, the selection and the
+ buttons stay put, so OK never travels off-screen in a deep library. */
+.ftp-tree, .ftp-children { list-style: none; margin: 0; padding: 0; }
+.ftp-tree {
+ flex: 1 1 auto; overflow-y: auto; overflow-x: hidden;
+ border: 1px solid var(--border); border-radius: 4px;
+ 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-twisty {
+ flex: 0 0 auto; width: 20px; height: 20px; padding: 0;
+ background: none; border: none; color: var(--text-dim);
+ font-family: inherit; font-size: 0.95em; line-height: 1; cursor: pointer;
+}
+.ftp-twisty:disabled { cursor: default; opacity: 0; }
+.ftp-label {
+ flex: 1 1 auto; min-width: 0;
+ display: flex; align-items: center; gap: 6px;
+ background: none; border: none; color: inherit;
+ font: inherit; text-align: left; padding: 3px 6px; cursor: pointer;
+}
+.ftp-label:disabled { cursor: not-allowed; }
+.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);
+ text-transform: uppercase; letter-spacing: 0.03em;
+}
+.ftp-badge.warn { color: var(--danger, #ef4444); border-color: currentColor; }
+.ftp-check { margin-left: auto; flex: 0 0 auto; }
+.ftp-selection {
+ display: flex; flex-wrap: wrap; gap: 4px;
+ max-height: 84px; overflow-y: auto; margin-bottom: 10px;
+}
+.ftp-chip {
+ font-size: 0.82em; padding: 2px 6px; border-radius: 3px;
+ background: var(--bg-hover); 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; }