diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-06 19:03:22 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-06 19:03:22 +0200 |
| commit | ab44526a291fa673aa2850d105f6412a70a5341f (patch) | |
| tree | 5940f18acfc15fc732eb65d90de346920461b8c8 /packages/meshbay-hub/src/meshbay_hub/static/style.css | |
| parent | 85a2ec47b7ad334208a3dbb091fadccc7631785c (diff) | |
| download | meshbay-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.css | 74 |
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; } |