aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/style.css
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-07 10:35:09 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-07 10:35:09 +0200
commit2c0903c648e24b4e2adf20492398e8b67d033b49 (patch)
tree0435f298010f0f946362f28baebbe88337ca8768 /packages/meshbay-hub/src/meshbay_hub/static/style.css
parent0ed078c92cabab1dab0f70f321562032ea549ce6 (diff)
parenteeda274d751c537f4ecef3087994a16a9517478f (diff)
downloadmeshbay-2c0903c648e24b4e2adf20492398e8b67d033b49.tar.gz
Merge branch 'refactor/groups-phase1'
Groups refactor, phases 1-3. The root model replaces the old `upload` flag and group-wide `member_upload` with per-root `writable`/`removable`/`ejected`, carried by a `RootSet` that both front doors — the loopback API and signed MNP — reach through the same `ops` functions. MNP goes to 1.1, additively: the roots table now rides on `index_delta`, so a root added, removed, ejected or plugged reaches every connected client instead of only whoever reloaded. The group UI becomes a plugin architecture: an application is a registry entry in `apps.js` plus its own files, with directories stored generically by `ops.set_app_directories` under whatever the app is called. A reference application, hidden behind `?dev=1`, is what makes that claim testable — adding it is what found the two places still naming apps by hand. 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.css231
1 files changed, 227 insertions, 4 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index c183d7e..d1d5732 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;
@@ -826,6 +833,22 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
.tb-btn:disabled { opacity: 0.45; cursor: not-allowed; }
.tb-btn:disabled:hover { border-color: var(--border); color: var(--text); }
.tb-btn .icon { width: 15px; height: 15px; }
+/* Square, for a toolbar button whose name is in its tooltip rather than
+ beside it. The horizontal padding goes; the height does not, so it lines up
+ with the labelled buttons next to it. */
+.tb-btn-icon { padding: 0; width: 32px; justify-content: center; gap: 0; }
+
+/* Naming a new folder, in the toolbar. Not `window.prompt`, which throws in
+ Electron — and an inline field can put the node's refusal next to the input
+ rather than after a dialog has closed. */
+.tb-mkdir { display: inline-flex; align-items: center; gap: 6px; }
+.tb-mkdir-input {
+ height: 32px; width: 170px; padding: 0 9px;
+ border: 1px solid var(--border-focus); border-radius: 6px;
+ background: var(--bg-surface); color: var(--text);
+ font-family: inherit; font-size: 0.83em;
+}
+.tb-mkdir-input:focus { outline: none; }
.tb-btn.primary {
background: var(--accent);
border-color: var(--accent);
@@ -1240,6 +1263,46 @@ 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; }
+/* The Save button of an app's settings pane.
+ *
+ * It was `btn btn-small btn-secondary`, and there is no `.btn` rule at all —
+ * so it inherited `.btn-secondary`: no background, a transparent border, dim
+ * grey text. Enabled, it already looked like a disabled control; disabled, it
+ * was the same thing at 40% opacity. "You cannot always click Save, you do not
+ * notice, and it does not work" was one sentence describing all of that.
+ *
+ * It looks like a button now, and a disabled one looks disabled. */
+.app-save {
+ display: inline-flex; align-items: center; gap: 6px;
+ height: 30px; padding: 0 14px; margin-top: 4px;
+ border: 1px solid var(--accent); border-radius: 6px;
+ background: var(--accent); color: var(--accent-text);
+ font-family: inherit; font-size: 0.83em; cursor: pointer;
+}
+.app-save:hover:not(:disabled) { background: var(--accent-hover); }
+/* No transition on the enabled/disabled swap. It animates a colour change that
+ coincides with a text-colour change, which reads as a flicker — and it made
+ the state genuinely hard to observe: a measurement taken during it reports a
+ transparent background on a button that is not transparent. */
+.app-save:disabled {
+ background: none; color: var(--text-dim);
+ border-color: var(--border); opacity: 1; cursor: default;
+}
+/* Why it is inert, beside it — a disabled control that explains itself is the
+ difference between "nothing changed" and "this is broken". */
+
+.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;
@@ -1267,6 +1330,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
/* A modern on/off switch — replaces a checkbox or a "Turn on/off" button
wherever the setting is a straight binary. */
.toggle-switch {
+ position: relative;
display: inline-flex;
align-items: center;
gap: 10px;
@@ -1897,7 +1961,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;
}
@@ -2125,7 +2190,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;
@@ -2147,7 +2216,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;
@@ -2157,7 +2227,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 {
@@ -2542,6 +2613,66 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
.node-roots-header { margin-bottom: 8px; }
+/* Shared directories table */
+.shared-dirs-tbl { width: 100%; border-collapse: collapse; border: none; }
+.shared-dirs-tbl th {
+ text-align: left; font-size: 0.78em; font-weight: 500;
+ color: var(--text-dim); padding: 0 12px 6px 0;
+ text-transform: uppercase; letter-spacing: 0.04em;
+ border: none;
+}
+.shared-dirs-tbl td { padding: 7px 12px 7px 0; border: none; vertical-align: middle; }
+.shared-dirs-tbl tbody tr + tr td { border-top: 1px solid var(--border); }
+.sdt-col-dir { min-width: 140px; }
+/* The path is the only thing separating two libraries whose folders happen to
+ share a basename, so it is shown — truncated, because it is usually long and
+ rarely the thing being read. */
+.sdt-col-path {
+ color: var(--text-dim); font-size: 0.85em;
+ max-width: 260px; overflow: hidden; text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.sdt-add-row { display: flex; gap: 6px; align-items: center; margin-top: 8px; }
+.sdt-add-input {
+ flex: 1 1 auto; min-width: 0; padding: 5px 8px;
+ border: 1px solid var(--border); border-radius: 4px;
+ background: var(--bg-surface); color: var(--text);
+ font-family: inherit; font-size: 0.9em;
+}
+.sdt-col-toggle { width: 90px; text-align: center; }
+.sdt-col-toggle th { text-align: center; }
+.sdt-col-toggle .toggle-switch { justify-content: center; }
+.sdt-col-actions { white-space: nowrap; text-align: right; }
+.sdt-action-btn {
+ background: none; border: 1px solid var(--border); border-radius: 4px;
+ padding: 4px 7px; cursor: pointer; font-size: 0.85em; color: var(--text-dim);
+ line-height: 1; vertical-align: middle;
+}
+.sdt-action-btn + .sdt-action-btn { margin-left: 6px; }
+.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(--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; }
+.sdt-row-unavail { opacity: 0.6; }
+.root-eject-btn {
+ background: none; border: 1px solid var(--border); border-radius: 4px;
+ padding: 2px 6px; cursor: pointer; font-size: 0.85em; color: var(--text-dim);
+ line-height: 1;
+}
+.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. */
+.file-row.root-ejected { opacity: 0.5; }
+.file-row.root-ejected td:not(.sel-cell) { cursor: default; }
+
+/* The paperclip with no writable directory to write to. Shown rather than
+ hidden, so the reason is discoverable instead of the control just being
+ absent. */
+.chat-attach-off { opacity: 0.35; cursor: not-allowed; }
+
.node-root {
display: flex;
align-items: center;
@@ -3993,3 +4124,95 @@ 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-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;
+ 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-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);
+ 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; 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.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;
+ max-height: 84px; overflow-y: auto; margin-bottom: 10px;
+}
+.ftp-chip {
+ font-size: 0.82em; padding: 2px 6px; border-radius: 3px;
+ background: var(--bg-raised); border: 1px solid var(--border);
+}
+.ftp-actions { display: flex; justify-content: flex-end; gap: 8px; }
+/* 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; }
+
+/* HelloWorld (docs/refactor-groups.md §4.1) — the reference app, hidden
+ behind ?dev=1. Deliberately plain: it exists to prove the plumbing, and
+ anything decorative here would be a second thing to keep working. */
+.hw-list { list-style: none; margin: 12px 0 0; padding: 0; }
+.hw-item {
+ display: flex; align-items: center; gap: 8px;
+ padding: 6px 0; border-bottom: 1px solid var(--border);
+}
+.hw-item .icon { width: 15px; height: 15px; flex-shrink: 0; }
+.hw-name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.hw-size { flex: 0 0 auto; color: var(--text-dim); font-size: 0.85em; }