aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/api/users.py6
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/config.py2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/db/models.py2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/mail.py4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/apps.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/auth-page.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/file-utils.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/files-app.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-name.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/group-page.js12
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/helloworld-app-settings.js7
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/helloworld-app.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/hub-client.js11
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/keyderive.js10
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-app-settings.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-app.js39
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/music-player.js18
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/photos-app-settings.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/photos-app.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/platform.js5
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/profile-page.js4
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/search-page.js8
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/settings-page.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/source-merge.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css12
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/transport.js34
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/video-app-settings.js2
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/video-app.js32
28 files changed, 124 insertions, 116 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/users.py b/packages/meshbay-hub/src/meshbay_hub/api/users.py
index 7b2fc8c..0394f53 100644
--- a/packages/meshbay-hub/src/meshbay_hub/api/users.py
+++ b/packages/meshbay-hub/src/meshbay_hub/api/users.py
@@ -107,7 +107,7 @@ class RegisterRequest(BaseModel):
email: str
password: str | None = None # deprecated — legacy native clients
auth_key: str | None = None # PBKDF2-derived, new clients
- # Client-generated account recovery key (docs/auth-confirm.md §4.4). Pure
+ # Client-generated account recovery key (docs/MESHBAY_DESIGN.md §3.6). Pure
# pass-through: appended to the verification e-mail so the user's mailbox
# backs it up, then dropped. Never written to any table, never logged.
recovery_key: str | None = None
@@ -901,7 +901,7 @@ async def verify_email_change(
# ── Passphrase change (Flow A) ──────────────────────────────────────────────
#
-# docs/auth-confirm.md §3. The passphrase derives two independent values on the
+# docs/MESHBAY_DESIGN.md §3.6. The passphrase derives two independent values on the
# client: auth_key (verified here) and bundle_key (AES-GCM key for the per-node
# identity bundles, which live on nodes and never on the hub). The client
# re-wraps those bundles from the old bundle_key to the new one on every
@@ -1023,7 +1023,7 @@ async def change_password(
# ── Passphrase reset (Flow B) ──────────────────────────────────────────────
#
-# docs/auth-confirm.md §4.2. An e-mail code re-opens hub login for someone who
+# docs/MESHBAY_DESIGN.md §3.6. An e-mail code re-opens hub login for someone who
# has lost their passphrase. It recovers no group content — that needs the
# recovery key, which the client applies on its own after the reset.
# reset-request never reveals whether an account exists.
diff --git a/packages/meshbay-hub/src/meshbay_hub/config.py b/packages/meshbay-hub/src/meshbay_hub/config.py
index 876583e..f2212de 100644
--- a/packages/meshbay-hub/src/meshbay_hub/config.py
+++ b/packages/meshbay-hub/src/meshbay_hub/config.py
@@ -75,7 +75,7 @@ class CaptchaConfig:
# Set this when that check is turned off in the reCAPTCHA console, which is
# what the desktop client needs: its page is served from `app://meshbay`,
# so the hostname Google sees is not the hub's and never can be. See
- # docs/captcha.md §6.
+ # docs/MESHBAY_DESIGN.md §7.7.
allowed_hosts: list[str] = field(default_factory=list)
# A solve from a page Google cannot attribute to a domain reports an empty
# hostname — `app://meshbay` does, measured live, and so does any other
diff --git a/packages/meshbay-hub/src/meshbay_hub/db/models.py b/packages/meshbay-hub/src/meshbay_hub/db/models.py
index f1fff41..62e4a11 100644
--- a/packages/meshbay-hub/src/meshbay_hub/db/models.py
+++ b/packages/meshbay-hub/src/meshbay_hub/db/models.py
@@ -201,7 +201,7 @@ class UserDevice(Base):
directory *others* read from, where a substituted key was handed the
GEK by an honest member.
* **It is not a node identity key.** Those are generated per node, pinned
- there, and never leave that relationship (`docs/per-node-identity-v1.md`).
+ there, and never leave that relationship (`docs/MESHBAY_DESIGN.md` §3.2).
A device holds one of these *plus* a different key per node, so nothing
here correlates a person across operators.
diff --git a/packages/meshbay-hub/src/meshbay_hub/mail.py b/packages/meshbay-hub/src/meshbay_hub/mail.py
index aa6b74e..126ed45 100644
--- a/packages/meshbay-hub/src/meshbay_hub/mail.py
+++ b/packages/meshbay-hub/src/meshbay_hub/mail.py
@@ -315,7 +315,7 @@ def send_verification_code(to: str, code: str, recovery_key: str | None = None)
"""
Registration verification e-mail. When `recovery_key` is given it is
appended to the body so the recipient's mailbox becomes the backup for it
- (docs/auth-confirm.md §4.4).
+ (docs/MESHBAY_DESIGN.md §3.6).
`recovery_key` is a **pass-through**: it is generated on the client, never
stored anywhere on the hub, and never logged — only whether one was present.
@@ -374,7 +374,7 @@ def send_email_change_code(to: str, code: str) -> None:
def send_password_reset_code(to: str, code: str) -> None:
"""
- Passphrase-reset code (docs/auth-confirm.md §4.2). This only re-opens hub
+ Passphrase-reset code (docs/MESHBAY_DESIGN.md §3.6). This only re-opens hub
login; it recovers no group content — that needs the recovery key.
"""
msg = EmailMessage()
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/apps.js b/packages/meshbay-hub/src/meshbay_hub/static/apps.js
index 461bd57..32f2314 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/apps.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/apps.js
@@ -70,7 +70,7 @@ const APPS = [
Component: MusicApp, Settings: MusicSettings },
{ key: 'photo', icon: 'image', labelKey: 'group.tab_photos',
Component: PhotosApp, Settings: PhotoSettings },
- // The reference implementation (docs/refactor-groups.md §4.1). `dev` keeps
+ // The reference implementation (docs/MESHBAY_DESIGN.md §9.4). `dev` keeps
// it out of an operator's way; everything else about it is an ordinary
// entry, which is the point.
{ key: 'helloworld', icon: 'chat', labelKey: 'group.tab_helloworld',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
index 04a00af..8958d44 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/auth-page.js
@@ -301,7 +301,7 @@ export function RegisterPage() {
setLoading(true);
try {
if (window.MeshBayKeys) {
- // The account recovery key (docs/auth-confirm.md §4.3/§4.4): generated
+ // The account recovery key (docs/MESHBAY_DESIGN.md §3.6): generated
// here, shown once on the next screen. When the user leaves "e-mail it"
// checked, the mnemonic goes in the register body so the hub appends it
// to the verification e-mail (and stores it nowhere); otherwise it is
@@ -511,7 +511,7 @@ export function RegisterPage() {
}
-// ── Passphrase reset — Flow B (docs/auth-confirm.md §4) ─────────────────────
+// ── Passphrase reset — Flow B (docs/MESHBAY_DESIGN.md §3.6) ─────────────────
//
// An e-mail code restores hub login. A recovery key, if the user still has one,
// restores the per-node identities in the same step: the fan-out reads each
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
index b732734..decf916 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js
@@ -507,11 +507,11 @@ async function downloadEntry(transfers, transport, gek, entry) {
* only alternative is to build the whole thing in memory — so that path is
* offered but says what it costs first.
*
- * Lifted out of files-app.js (docs/photos.md §3) so photos-app.js's own
+ * Lifted out of files-app.js (docs/MESHBAY_DESIGN.md §9.9) so photos-app.js's own
* "zip this album" button calls the same implementation rather than a
* second one — nothing here is Files-specific once `entries`/`transport`/
* `gek`/`setError` are passed in, the same shared-context shape every app
- * already receives (apps.md §2).
+ * already receives (docs/MESHBAY_DESIGN.md §9.2).
*/
async function downloadDirectory(transfers, transport, gek, entries, dir, { setError }) {
if (!transport || !transport.connected) return;
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
index 7629459..3f4212e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/files-app.js
@@ -314,7 +314,7 @@ function FilesPanel({
}
}, [currentPath]);
- // The implementation lives in file-utils.js (docs/photos.md §3) so
+ // The implementation lives in file-utils.js (docs/MESHBAY_DESIGN.md §9.9) so
// photos-app.js's own "zip this album" button can call the same code
// rather than a second one.
const downloadDirectory = useCallback(async (dir) => {
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-name.js b/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
index f109da7..f0ace6a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-name.js
@@ -8,7 +8,7 @@ import { sourceLabel } from './source-merge.js';
* Group names are unique only per owner account (the hub enforces that), so the
* `@handle` is what tells two groups called "photos" apart. `owner` is the
* owner's username for a local group, or the source hub for a federated one
- * (decision 5 in ~/next/groupnames.md); the caller decides which.
+ * (docs/MESHBAY_DESIGN.md §7.3); the caller decides which.
*
* `inline` renders "name@owner" on one line, for places that cannot take a
* block — a badge, a `confirm()` string built elsewhere.
@@ -29,7 +29,7 @@ export function GroupName({ name, owner, inline = false }) {
* The Search view merges a file several groups share into one entry, so the
* badge under a card cannot always name a group. One source keeps naming it
* and keeps linking to it; more than one becomes a count, and *which* one was
- * picked is deliberately not shown (docs/refactoring-search.md §5.5).
+ * picked is deliberately not shown (docs/MESHBAY_DESIGN.md §9.11).
*
* `entries` is the whole unit — every episode of a show, every track of an
* album — not the entry the card was drawn from; `sourceLabel` explains why.
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
index 205fce2..9a86f2a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/group-page.js
@@ -154,10 +154,10 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// that). Null until the handshake ack arrives.
const [scanSettings, setScanSettings] = useState(null);
// TMDB on/off + whether a custom token is set, node-wide (not per-group) —
- // docs/mediacenter.md §5.5. Null until the handshake ack arrives.
+ // docs/MESHBAY_DESIGN.md §9.7. Null until the handshake ack arrives.
const [tmdbConfig, setTmdbConfig] = useState(null);
// Which folders each app works over. One shape for all of them — a list,
- // always, even where an app only wants one (docs/refactor-groups.md §1.6):
+ // always, even where an app only wants one (docs/MESHBAY_DESIGN.md §9.3):
// Videos and Music were single values, which meant a library spread over two
// drives could not be described at all. Empty means nothing configured yet,
// which every app reads as "show nothing", never "the whole group index".
@@ -171,7 +171,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// Whether members' cross-group Search lists this group. Not an app setting:
// it is about the group as a whole, and it hides nothing from this page.
const [searchListed, setSearchListed] = useState(true);
- // MusicBrainz on/off (per-group) — docs/musicbay.md §3.2.
+ // MusicBrainz on/off (per-group) — docs/MESHBAY_DESIGN.md §9.8.
const [musicbrainzConfig, setMusicbrainzConfig] = useState(null);
// `op` rides through to the shell — 'replace', 'next' or 'append'
// (docs/playlists.md §9.2). It has to be named here: a wrapper that takes
@@ -320,7 +320,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
setDeviceReady(false);
gekRef.current = null;
if (!session.bundleKey) session.bundleKey = await _loadBundleKey();
- // Persisted (docs/auth-confirm.md §4.3) so a group joined in a later
+ // Persisted (docs/MESHBAY_DESIGN.md §3.6) so a group joined in a later
// session still leaves a recovery-wrapped identity copy on its node.
if (!session.recoveryKey) session.recoveryKey = await _loadRecoveryKey();
if (!session.bundleKey && window.MeshBayKeys) {
@@ -669,7 +669,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// the only unambiguous answer once a group can have several writable roots.
// Chat has no folder to browse, so it needs one picked for it, and this is
// the same rule the node applies when a client names no root at all. It
- // becomes an operator-chosen directory in phase 2 (refactor-groups.md §1.7).
+ // becomes an operator-chosen directory in phase 2 (docs/MESHBAY_DESIGN.md §9.6).
//
const writableRoots = useMemo(
() => nodeRoots.filter((r) => r.writable && r.available !== false),
@@ -735,7 +735,7 @@ function GroupPage({ groupId, group, token, username, userId, userPrefs,
// rather than written out. Naming them here would mean adding an app
// required editing this file, which is the one thing the plugin
// architecture is supposed to have removed — and the reference app
- // (docs/refactor-groups.md §4.1) is what made the difference visible.
+ // (docs/MESHBAY_DESIGN.md §9.4) is what made the difference visible.
const perAppDirectories = useMemo(() => {
const out = {};
for (const app of APPS) out[`${app.key}Directories`] = appDirs(app.key);
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app-settings.js
index 9058199..be26fbc 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app-settings.js
@@ -11,9 +11,10 @@ import { FolderPickerField } from './folder-tree.js';
* app's registry key by the page, and `ops.set_app_directories` stores the row
* under that name without knowing what it is.
*
- * It takes the shared props and no others (`docs/apps.md` §3b), which is what
- * `test_app_settings_plugin.py` checks of every pane — including this one, so
- * the reference implementation is held to the contract it demonstrates.
+ * It takes the shared props and no others (`docs/MESHBAY_DESIGN.md` §9.2),
+ * which is what `test_app_settings_plugin.py` checks of every pane — including
+ * this one, so the reference implementation is held to the contract it
+ * demonstrates.
*/
function HelloWorldSettings({ roots, dirs, settings, saveDirectories }) {
const { busy, msg, run } = useSaver();
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app.js b/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app.js
index 01b4ed4..98ca8fa 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/helloworld-app.js
@@ -7,7 +7,7 @@ import { formatSize } from './file-utils.js';
* The smallest application this platform can host, and the proof that adding
* one costs nothing outside its own two files.
*
- * Everything else in `docs/refactor-groups.md` §3 is asserted by tests that
+ * Everything else in `docs/MESHBAY_DESIGN.md` §9.4 is asserted by tests that
* read source. This is the other kind of evidence: an app nobody wrote a line
* of plumbing for, that stores directories, appears as a tab, and lists files —
* because the registry entry beside it is genuinely all there is.
@@ -18,7 +18,7 @@ import { formatSize } from './file-utils.js';
* put a toy app in everybody's group; deleting the app would leave the claim
* resting entirely on tests that read text.
*
- * It takes the standard props — see `docs/apps.md` §2 — and reads
+ * It takes the standard props — see `docs/MESHBAY_DESIGN.md` §9.2 — and reads
* `helloworldDirectories`, which nothing on the node knows about by name:
* `ops.set_app_directories` keys the row by whatever the app is called.
*/
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js b/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
index 81185a5..f467720 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js
@@ -136,7 +136,7 @@ async function clearAllCachedIndexes() {
// so importing modules can update either field without this module handing
// out a rebindable export.
//
-// `recoveryKey` (docs/auth-confirm.md §4.3) is the AES key that wraps the
+// `recoveryKey` (docs/MESHBAY_DESIGN.md §3.6) is the AES key that wraps the
// *recovery* copy of an identity bundle. In-memory only, and set only when the
// user has just generated or entered the recovery secret (registration, or the
// Flow B screen) — it cannot be re-derived from the passphrase.
@@ -169,10 +169,11 @@ async function _loadKey(slot) {
return val || null;
} catch { return null; }
}
-// 'bk' = passphrase-derived bundle key; 'rk' = recovery key (docs/auth-confirm.md
-// §4.3). Persisting 'rk' is what lets a group joined in a *later* session still
-// get a recovery-wrapped identity copy, instead of only groups joined in the
-// unbroken session that generated it. Cleared with everything else on sign-out.
+// 'bk' = passphrase-derived bundle key; 'rk' = recovery key
+// (docs/MESHBAY_DESIGN.md §3.6). Persisting 'rk' is what lets a group joined
+// in a *later* session still get a recovery-wrapped identity copy, instead of
+// only groups joined in the unbroken session that generated it. Cleared with
+// everything else on sign-out.
const _storeBundleKey = (key) => _storeKey('bk', key);
const _loadBundleKey = () => _loadKey('bk');
const _storeRecoveryKey = (key) => _storeKey('rk', key);
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
index edfa109..879f56f 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/keyderive.js
@@ -191,7 +191,7 @@ async function deriveBundleKeys(password, username) {
// ── Account recovery key ─────────────────────────────────────────────────────
//
-// docs/auth-confirm.md §4.3. A full-entropy secret the user keeps outside the
+// docs/MESHBAY_DESIGN.md §3.6. A full-entropy secret the user keeps outside the
// passphrase — in their password manager, or (step 3) e-mailed to them. It
// wraps a *second* copy of every per-node identity bundle, so a forgotten
// passphrase does not strand the account's group identities.
@@ -323,7 +323,7 @@ async function registerUser(username, email, password, recoveryMnemonic, captcha
const payload = { username, email, auth_key: authKey };
// The recovery mnemonic, when the user opted to have it e-mailed: the hub
// appends it to the verification e-mail and stores it nowhere
- // (docs/auth-confirm.md §4.4). Omitted when they chose to save it themselves.
+ // (docs/MESHBAY_DESIGN.md §3.6). Omitted when they chose to save it themselves.
if (recoveryMnemonic) payload.recovery_key = recoveryMnemonic;
// reCAPTCHA response, when the hub has a captcha configured. The widget lives
// in RegisterPage (auth-page.js); this function just forwards its token. A
@@ -347,7 +347,7 @@ async function registerUser(username, email, password, recoveryMnemonic, captcha
* bundle goes to that node and nowhere else, and is what any other browser
* fetches to become the same person there. When `recoveryKey` is supplied a
* second copy wrapped under it rides along, so a forgotten passphrase does not
- * strand this identity (docs/auth-confirm.md §4.3).
+ * strand this identity (docs/MESHBAY_DESIGN.md §3.6).
*/
async function generateNodeIdentity(bundleKey, recoveryKey) {
const { skEdRaw, pkEdRaw, skXRaw, pkXRaw } = await generateKeypairs();
@@ -465,7 +465,7 @@ async function _bundleKeyPairFields(password, username) {
window.MeshBayKeys = {
registerUser, loginAndRecover, generateNodeIdentity, generateKeypairs, signBytes,
deriveAuthKey, decryptBundleWithKey, encryptBundleWithKey, bundleVersion,
- // Exposed for the passphrase change (docs/auth-confirm.md §3): re-wrapping a
+ // Exposed for the passphrase change (docs/MESHBAY_DESIGN.md §3.6): re-wrapping a
// node's identity bundle needs the old key (a {v2,v1} pair, since an old
// bundle may be v1) to read it and the new v2 key to write it back.
deriveEncryptionKey, deriveEncryptionKeyV1,
@@ -473,6 +473,6 @@ window.MeshBayKeys = {
// `session.bundleKey` uses this, so `v2hkdf` is never the field one sign-in
// path forgot (docs/playlists.md §3.4).
deriveBundleKeys, bundleKeyPairFields: _bundleKeyPairFields,
- // Account recovery key (docs/auth-confirm.md §4.3).
+ // Account recovery key (docs/MESHBAY_DESIGN.md §3.6).
generateRecoveryKey, deriveRecoveryKey,
};
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-app-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/music-app-settings.js
index 8ce2ab6..a9a3c95 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-app-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app-settings.js
@@ -8,7 +8,7 @@ import { FolderPickerField } from './folder-tree.js';
*
* Same shape as Videos, minus a credential: MusicBrainz's read endpoints need
* no API key, only a descriptive User-Agent, and that is one operator identity
- * held node-wide rather than a per-group setting (docs/musicbay.md §3.2).
+ * held node-wide rather than a per-group setting (docs/MESHBAY_DESIGN.md §9.8).
*
* Several folders, for the same reason Videos has several: a music library
* that lives on two drives had no way to say so.
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
index baa3520..e0308cb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-app.js
@@ -15,13 +15,13 @@ import * as P from './playlists.js';
//
// An album-grid (MusicBrainz-enriched, when a track has no usable embedded
// cover) or flat (tag/filename-only) browser for a group's audio files, per
-// docs/musicbay.md. Grouping is by `artist`/`album` -- already resolved at
-// index time from embedded tags, falling back to filename/folder parsing
-// (indexer/enrich_audio.py) -- never guessed here.
+// docs/MESHBAY_DESIGN.md §9.8. Grouping is by `artist`/`album` -- already
+// resolved at index time from embedded tags, falling back to filename/folder
+// parsing (indexer/enrich_audio.py) -- never guessed here.
//
// Unlike Videos, MusicBrainz is looked up only when a track has no embedded
-// cover art at all (musicbay.md section 2.1's tiered trust: tags first,
-// filename parsing second, MusicBrainz last) -- most of a real, well-ripped
+// cover art at all (docs/MESHBAY_DESIGN.md §9.8's order of trust: tags
+// first, filename parsing second, MusicBrainz last) -- most of a real, well-ripped
// library already carries good artist/album text and often its own cover, so
// this avoids a network round trip most tiles never need. Playback never
// touches this file: clicking a track calls the `onPlayQueue` prop the shell
@@ -52,7 +52,7 @@ function foldKey(s) {
}
// Same shape as video-app.js's underVideoRoot: an unset root means "show
-// nothing" (docs/musicbay.md's amended §2.1 — the node itself runs no
+// nothing" (docs/MESHBAY_DESIGN.md §9.8 — the node itself runs no
// tag/cover enrichment for this group before a root is chosen either,
// daemon.py's _enrich_new_audio_entries), not "the whole shared tree" —
// falling back to that would just show files nothing has enriched.
@@ -212,17 +212,16 @@ function useMusicMeta(transportRef, fileId, active) {
}
// A drawn CD standing in for a cover nothing supplied one for -- most tiles
-// in a real, older/well-ripped library land here (musicbay.md's own
-// measurement: ~11% embedded art, ~26% once sibling image files are counted
-// too), so this is the *default* look of the grid, not a rare fallback, and
-// needed to read as a deliberate piece of art rather than a broken image.
-// A flat single-color icon (the first version of this) looked exactly like
-// "missing", not "no cover" -- an actual disc, with the iridescent sheen a
-// real CD's data side has, reads as intentional at a glance. Genuinely
-// unique gradient ids: a `<radialGradient>` id is a plain DOM id, and a grid
-// full of these renders many instances at once -- reusing one literal id
-// would leave every disc after the first pointing at whichever def the
-// browser resolves first.
+// in a real, older/well-ripped library land here (measured: ~11% embedded
+// art, ~26% once sibling image files are counted too), so this is the
+// *default* look of the grid, not a rare fallback, and needed to read as a
+// deliberate piece of art rather than a broken image. A flat single-color icon
+// (the first version of this) looked exactly like "missing", not "no cover" --
+// an actual disc, with the iridescent sheen a real CD's data side has, reads
+// as intentional at a glance. Genuinely unique gradient ids: a
+// `<radialGradient>` id is a plain DOM id, and a grid full of these renders
+// many instances at once -- reusing one literal id would leave every disc
+// after the first pointing at whichever def the browser resolves first.
let _discIdSeq = 0;
function DiscPlaceholder({ cls }) {
@@ -517,8 +516,8 @@ function FlatArtistFolder({ artist, onPlayQueue, onMenu }) {
${open && html`
<div class="music-flat-children">
${/* A real artist folder with no album layer at all is common here
- -- a pile of loose singles, not one release (musicbay.md
- section 2.1's "flat per-artist folder" case). Nesting them
+ -- a pile of loose singles, not one release (a flat
+ per-artist folder, docs/MESHBAY_DESIGN.md §9.8). Nesting them
one more level behind their own always-empty "Unknown album"
row was exactly the friction reported live: an extra,
pointless expand before reaching a track that's playable
@@ -748,7 +747,7 @@ function MusicApp({
}
// foldKey rides along for the Search page's merge unit keys
-// (docs/refactoring-search.md §5.2). An album's *display* strings are the
+// (docs/MESHBAY_DESIGN.md §9.11). An album's *display* strings are the
// first-seen spelling, and which group is seen first is the order its index
// happened to arrive in — so keying a unit on them would let the chosen source
// change between page loads. The folded key is the one grouping actually used,
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
index c3a2868..7c81f4e 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
@@ -8,7 +8,7 @@ import { useStickyBand } from './sticky.js';
import { CHUNK_SIZE, pipelinedDownload } from './file-utils.js';
/**
- * The Music app's persistent player bar (docs/musicbay.md §2.3, §7.2).
+ * The Music app's persistent player bar (docs/MESHBAY_DESIGN.md §9.8).
*
* Owned and rendered by app.js — the router's parent — so playback survives
* navigating between groups, search, and other pages. Both group-page.js
@@ -23,11 +23,12 @@ import { CHUNK_SIZE, pipelinedDownload } from './file-utils.js';
* track is a few megabytes, so it is downloaded and decrypted once through
* the same chunk pipeline Files already uses (file-utils.js's
* pipelinedDownload), then played from a blob URL — the deliberate
- * simplification recorded in musicbay.md §2.2. WMA and Musepack are the one
- * exception (`NEEDS_TRANSCODE_RE` below): neither decodes in a browser's
- * <audio> element at all, tagged correctly or not, so those two go through
- * `transport.requestAudioTranscode` first — a node-side, cached-after-once
- * AAC/M4A conversion — before the same download/blob path runs.
+ * simplification recorded in docs/MESHBAY_DESIGN.md §9.8. WMA and Musepack are
+ * the one exception (`NEEDS_TRANSCODE_RE` below): neither decodes in a
+ * browser's <audio> element at all, tagged correctly or not, so those two go
+ * through `transport.requestAudioTranscode` first — a node-side,
+ * cached-after-once AAC/M4A conversion — before the same download/blob path
+ * runs.
*/
const MIME_BY_EXT = {
@@ -271,7 +272,7 @@ function MusicPlayerBar({ getConnection, queue, onClose, userPrefs, onSaveQueue
// Screen Wake Lock, opt-in only (Settings → music_keep_screen_on) and only
// while a track is actually playing — off by default because the ordinary
// expectation, matching Spotify/Deezer, is that the phone locks on its own
- // idle timer while listening (docs/musicbay.md §2.2). Unlike the video
+ // idle timer while listening (docs/MESHBAY_DESIGN.md §9.8). Unlike the video
// player's unconditional lock, this must not fight that default for
// everyone who never asked for it; it exists for whoever explicitly wants
// to trade battery for riding out the WebRTC screen-lock reconnect gap
@@ -368,7 +369,8 @@ function MusicPlayerBar({ getConnection, queue, onClose, userPrefs, onSaveQueue
}, [getConnection, evictOldBlobs]);
// Silently warms the cache for the next tracks so pressing "next" doesn't
- // visibly wait (musicbay.md §2.2) — best-effort, never surfaces an error.
+ // visibly wait (docs/MESHBAY_DESIGN.md §9.8) — best-effort, never surfaces
+ // an error.
//
// More than one: a screen lock can cost the transport several minutes (see
// the WebRTC auto-reconnect in transport.js — this is the other half of
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/photos-app-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/photos-app-settings.js
index 8656fa5..0407e67 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/photos-app-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/photos-app-settings.js
@@ -6,7 +6,7 @@ import { FolderPickerField } from './folder-tree.js';
/**
* The Photos app's operator settings: folders, and nothing else.
*
- * Photos was always several folders (docs/photos.md §2.1) — a photo library
+ * Photos was always several folders (docs/MESHBAY_DESIGN.md §9.9) — a photo library
* is routinely scattered, with no single natural root — so this pane is what
* the other two grew into rather than the exception it used to be. No
* third-party service: EXIF is read locally on the node, and nothing about a
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/photos-app.js b/packages/meshbay-hub/src/meshbay_hub/static/photos-app.js
index 2c4356c..261f2af 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/photos-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/photos-app.js
@@ -12,7 +12,7 @@ import { SourceTag } from './group-name.js';
// ── Photos ───────────────────────────────────────────────────────────────────
//
-// docs/photos.md. Unlike Videos/Music: several root folders per group
+// docs/MESHBAY_DESIGN.md §9.9. Unlike Videos/Music: several root folders per group
// (photoRoots is a list, §2.1), one album-grid view with no mode toggle and
// no third-party matching step (§2.3), and per-photo info read from the
// file's own EXIF at index time rather than fetched live. Every directory
@@ -383,7 +383,7 @@ function PhotosApp({
// groupPhotoAlbums is exported for the Search page, which needs the album a
// photo belongs to in order to merge duplicate sources per album rather than
-// per file (docs/refactoring-search.md §5.2). It calls this one, never a copy:
+// per file (docs/MESHBAY_DESIGN.md §9.11). It calls this one, never a copy:
// a second implementation of the album key would keep agreeing with this one
// right up until one of them changed.
export { PhotosApp, groupPhotoAlbums };
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
index 07e5b6f..cfa4d87 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
@@ -2,8 +2,9 @@
* What differs between running in a browser and running as an installed app.
*
* The interface is the same code either way — that is the whole reason Electron
- * was chosen over a shell that replaces the engine (docs/desktop-client-v1.md
- * §2). What genuinely differs is small and lives here:
+ * was chosen over a shell that replaces the engine
+ * (docs/MESHBAY_DESIGN.md §8.2). What genuinely differs is small and lives
+ * here:
*
* · **where the hub is.** Served from the hub, it is the current origin. Ship
* the interface in a package and it becomes a configured URL, because the
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
index 4d452e1..a19b543 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/profile-page.js
@@ -83,7 +83,7 @@ export function ProfilePage({ user, onLogout }) {
setPinCount(window.MeshBayTransport?.pinnedNodeCount?.() ?? 0);
}, []);
- // ── Passphrase change (docs/auth-confirm.md §3) ─────────────────────────
+ // ── Passphrase change (docs/MESHBAY_DESIGN.md §3.6) ─────────────────────
const [cpOpen, setCpOpen] = useState(false);
const [cpOld, setCpOld] = useState('');
const [cpNew, setCpNew] = useState('');
@@ -168,7 +168,7 @@ export function ProfilePage({ user, onLogout }) {
}
}, [cpOld, cpNew, user]);
- // ── Recovery key backfill (docs/auth-confirm.md §4.3) ───────────────────
+ // ── Recovery key backfill (docs/MESHBAY_DESIGN.md §3.6) ─────────────────
// Enter the recovery key once per browser to add a recovery-wrapped copy of
// your identity to every group — covers groups joined before the key was
// loaded here.
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js
index 26d4e4c..a096414 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/search-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/search-page.js
@@ -207,7 +207,7 @@ async function fetchGroupIndex(groupId, token, bundleKey, username, userId) {
};
// Which of the reader's groups sit on their own node — the tie-breaker
// when the same file is announced by several of them
- // (docs/refactoring-search.md §5.3). Computed by the node from its own
+ // (docs/MESHBAY_DESIGN.md §9.11). Computed by the node from its own
// record of who it belongs to (webrtc_server.py's _is_node_admin), never
// from a hub claim, and deliberately not written to the index cache: it
// describes this connection, not the group's content.
@@ -349,7 +349,7 @@ function cachedDirs(roots, appKey, legacyKey) {
// different people invited to different libraries — arrived here as two
// entries per file, so a film showed as two poster cards and every episode
// twice inside a show. `source-merge.js` folds them on the content hash and
-// resolves one source per *unit*. See docs/refactoring-search.md.
+// resolves one source per *unit*. See docs/MESHBAY_DESIGN.md §9.11.
//
// The units come from video-app.js's own `groupVideoEntries`, never from a
// second copy of its keys here: a copy would keep agreeing with the original
@@ -465,7 +465,7 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs })
// A group whose connection failed stops being chosen as a merged entry's
// source, so a unit fails over to another group that has the file
- // (docs/refactoring-search.md §5.4). Without this the merge could make a
+ // (docs/MESHBAY_DESIGN.md §9.11). Without this the merge could make a
// file *less* available than it was before it, which would be a regression
// dressed as a feature.
//
@@ -609,7 +609,7 @@ function SearchPage({ token, username, userId, groups, onPlayQueue, userPrefs })
}, [indexedGroups]);
// How a unit's source is chosen, shared by every merged view
- // (docs/refactoring-search.md §5.3). `isLocal` reads the flag the node itself
+ // (docs/MESHBAY_DESIGN.md §9.11). `isLocal` reads the flag the node itself
// put in the handshake ack — computed from its own record of who it belongs
// to (webrtc_server.py's `_is_node_admin`), never from a hub claim.
const mergeOpts = useMemo(() => ({
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js b/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
index 51d99cd..9a52059 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/settings-page.js
@@ -19,7 +19,7 @@ export function SettingsPage({ user, theme, onThemeChange, groups, onPrefsChange
() => Object.fromEntries((groups || []).map(g => [g.id, !!g.muted])));
const [globalMute, setGlobalMute] = useState(false);
const [defaultTab, setDefaultTab] = useState('chat');
- // Off by default (musicbay.md §2.2): the ordinary expectation, matching
+ // Off by default (docs/MESHBAY_DESIGN.md §9.8): the ordinary expectation, matching
// Spotify/Deezer, is that the phone locks on its own idle timer while
// listening. This is for whoever would rather trade battery for it —
// e.g. to ride out the WebRTC screen-lock reconnect gap without waiting
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/source-merge.js b/packages/meshbay-hub/src/meshbay_hub/static/source-merge.js
index 2c08e06..b6ce0bd 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/source-merge.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/source-merge.js
@@ -6,7 +6,7 @@
* paths are already one entry. The Search page is what creates duplicates, by
* concatenating N independently keyed indexes into one list — a directory
* shared by two groups then shows every film twice, every episode twice, every
- * track twice. See docs/refactoring-search.md.
+ * track twice. See docs/MESHBAY_DESIGN.md §9.11.
*
* Two rules decide everything here:
*
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 2d07fd1..81ae895 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -1793,7 +1793,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
margin: 0;
}
-/* Photos app's root add/remove list (docs/photos.md §2.2) — a set, unlike
+/* Photos app's root add/remove list (docs/MESHBAY_DESIGN.md §9.9) — a set, unlike
the Videos/Music single-value picker above. */
.settings-root-list { list-style: none; margin: 4px 0 8px; padding: 0; }
.settings-root-list-item {
@@ -4031,7 +4031,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
.video-flat-season { padding-left: 24px; margin-bottom: 8px; }
.video-flat-season .video-season-header { margin: 6px 0 4px; }
-/* Season picker — docs/mediacenter.md §5.4's per-season overview view.
+/* Season picker — docs/MESHBAY_DESIGN.md §9.7's per-season overview view.
Was a row of pills with `overflow-x: auto`: a show with a dozen seasons hid
most of them behind a horizontal scrollbar, worst on the narrow screens that
can least afford it. One trigger and a menu is one row high whatever the
@@ -4139,7 +4139,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
border-radius: 4px;
}
-/* ── Music app (docs/musicbay.md) ─────────────────────────────────────────
+/* ── Music app (docs/MESHBAY_DESIGN.md §9.8) ──────────────────────────────
Reuses .video-overlay/.video-top-bar/.video-title/.video-close, .tb-btn/
.tb-search, .video-flat-list/.video-flat-row/.video-flat-info/
.video-flat-title/.video-flat-sub/.video-flat-folder/.video-flat-chevron
@@ -4346,7 +4346,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
back rather than compounding with the rail. */
.music-flat-track { padding: 5px 8px; }
-/* ── Persistent player bar (docs/musicbay.md §2.3) ────────────────────────
+/* ── Persistent player bar (docs/MESHBAY_DESIGN.md §9.8) ──────────────────
`position: sticky`, not `fixed` — deliberately: CLAUDE.md's own history
records more than one layout bug from a fixed-position element quietly
double-reserving space against a page that also sized itself against the
@@ -4571,7 +4571,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
.music-player-volume { display: none; }
}
-/* ── Photos app (photos-app.js, docs/photos.md) ───────────────────────────── */
+/* ── Photos app (photos-app.js, docs/MESHBAY_DESIGN.md §9.9) ──────────────── */
.photo-toolbar {
display: flex;
@@ -4924,7 +4924,7 @@ h2 .gn-owner, h3 .gn-owner { font-size: 0.55em; }
.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
+/* HelloWorld (docs/MESHBAY_DESIGN.md §9.4) — 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; }
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/transport.js b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
index 72c831f..f4979f4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/transport.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/transport.js
@@ -893,7 +893,7 @@ class MeshBayTransport {
openErr = e;
// The passphrase key did not open the bundle. If we hold a recovery
// key and the node kept a recovery copy, try that — Flow B
- // (docs/auth-confirm.md §4.5): recovering an identity after a lost
+ // (docs/MESHBAY_DESIGN.md §3.6): recovering an identity after a lost
// passphrase, before re-wrapping it under the new one.
if (this._recoveryKey && kpResp.bundle_enc_recovery) {
try {
@@ -1357,7 +1357,7 @@ class MeshBayTransport {
}
/**
- * TMDB metadata for one file (Videos app, docs/mediacenter.md §5.4).
+ * TMDB metadata for one file (Videos app, docs/MESHBAY_DESIGN.md §9.7).
* Keyed by the entry's own `id` (its content hash) — never a path: a
* path names the *folder* a file is in (indexer.py's `_virtual_dir`), so
* two files sharing a folder (any multi-episode season) would resolve to
@@ -1388,7 +1388,7 @@ class MeshBayTransport {
}
/**
- * One season's own overview/air_date/poster (docs/mediacenter.md §5.4's
+ * One season's own overview/air_date/poster (docs/MESHBAY_DESIGN.md §9.7's
* per-season view) — a show's own tmdb_meta is one static field that does
* not necessarily describe every season alike, found live: a 3-season
* show whose overview read as season-3-specific for every season.
@@ -1445,7 +1445,7 @@ class MeshBayTransport {
/**
* Drop one file's cached TMDB match so it re-resolves with the node's
- * current matcher (§10.1/V13) — the one-click alternative to the full
+ * current matcher (V13) — the one-click alternative to the full
* search-and-pick flow. Signed for the same reason as overrideTmdbMatch.
*/
async rematchTmdbMatch(fileId, signFn) {
@@ -1466,7 +1466,7 @@ class MeshBayTransport {
* setTmdbEnabled below for the per-group on/off switch). Signed like
* setAppsEnabled/updateRoot — an unsigned change would let any
* member alter outbound third-party network traffic the operator never
- * agreed to (docs/mediacenter.md §5.5, §8). `token: ''` explicitly clears
+ * agreed to (docs/MESHBAY_DESIGN.md §9.7, §6.5). `token: ''` explicitly clears
* a previously-set custom token; omit it (undefined/null), like
* `language`, to leave whatever is stored unchanged.
*/
@@ -1598,7 +1598,7 @@ class MeshBayTransport {
}
/**
- * MusicBrainz metadata for one track (Music app, docs/musicbay.md §4.3)
+ * MusicBrainz metadata for one track (Music app, docs/MESHBAY_DESIGN.md §9.8)
* — same shape as fetchMediaMeta, minus a season/episode concept:
* album-level (release), resolved from the track's own artist/album
* fields already in the index. Keyed by the track's own `id` (content
@@ -1659,7 +1659,7 @@ class MeshBayTransport {
/**
* Whether MusicBrainz lookups run for this group at all — per-group from
- * the start (docs/musicbay.md §3.2/§6). Signed like setTmdbEnabled.
+ * the start (docs/MESHBAY_DESIGN.md §9.8). Signed like setTmdbEnabled.
*/
async setMusicbrainzEnabled(enabled, signFn) {
const msg = await this._sendAndWait({
@@ -1833,7 +1833,7 @@ class MeshBayTransport {
* Who is in this group and which device keys they hold — verified here, not
* taken on the node's word.
*
- * Tier 2 of `desktop-client-v1.md` §4.8. The node relays, for each device,
+ * Tier 2 of `docs/MESHBAY_DESIGN.md` §3.3. The node relays, for each device,
* the already-pinned key that countersigned it and the signature itself; this
* walks that from each account's first device outwards and keeps only the
* devices it could actually reach. A device the node asserts but cannot
@@ -1843,7 +1843,7 @@ class MeshBayTransport {
* The property this buys, stated exactly: once a client has seen an account,
* a node that later substitutes a key for it is **detected**. It buys nothing
* at first sight, where there is nothing to compare against — that boundary
- * is `per-node-identity-v1.md`'s and does not move.
+ * is `docs/MESHBAY_DESIGN.md` §3.2's and does not move.
*/
async groupRoster() {
if (this._roster) return this._roster;
@@ -1875,8 +1875,9 @@ class MeshBayTransport {
* 'changed' a key this account has not shown before and cannot evidence
*
* Only `changed` is worth a person's attention, and it is the one notice
- * §4.8 budgets for. `first` is not an alarm — every account is new once, and
- * treating that as a warning is how a warning stops being read.
+ * docs/MESHBAY_DESIGN.md §3.3 budgets for. `first` is not an alarm — every
+ * account is new once, and treating that as a warning is how a warning stops
+ * being read.
*/
async accountDeviceStatus(userId, devicePk) {
let roster;
@@ -2658,7 +2659,7 @@ class MeshBayTransport {
// Identity keys are per node, so a browser and a desktop client are two keys
// on one account here. A new one is admitted by a key this node already
// pinned — never by the hub, which holds no user keys and so cannot
- // countersign anything. See docs/desktop-client-v1.md §4.
+ // countersign anything. See docs/MESHBAY_DESIGN.md §3.3.
/**
* Ask to be added, and return the code to show the person.
@@ -3286,7 +3287,7 @@ class MeshBayTransport {
});
}
// Same shape: the operator dropped one file's match to have it
- // re-resolved (§10.1/V13). No tmdbId — the node re-derives it.
+ // re-resolved (V13). No tmdbId — the node re-derives it.
if (msg.type === 'tmdb_rematch_ack' && this._onTmdbOverride) {
this._onTmdbOverride({ fileId: msg.file_id || '', tmdbId: '', mediaType: '' });
}
@@ -4044,7 +4045,7 @@ function pinnedNodeCount() {
// ── Passphrase change: re-wrap every reachable identity bundle ───────────────
//
-// docs/auth-confirm.md §3.2. The passphrase-derived bundle_key encrypts this
+// docs/MESHBAY_DESIGN.md §3.6. The passphrase-derived bundle_key encrypts this
// account's per-node identity on every node it has joined. Changing the
// passphrase changes that key, so each bundle must be read with the old key and
// written back with the new one — on the node, while both keys are in hand.
@@ -4086,7 +4087,8 @@ function _acWithTimeout(promise, ms, label) {
* @param {string} o.userId
* @param {string} [o.oldPassphrase] omit in Flow B — connect falls back to the recovery copy
* @param {string} o.newPassphrase
- * @param {string} [o.recoveryKey] the recovery mnemonic (Flow B, docs/auth-confirm.md §4.5).
+ * @param {string} [o.recoveryKey] the recovery mnemonic (Flow B,
+ * docs/MESHBAY_DESIGN.md §3.6).
* When given, the recovery-wrapped copy is read where the
* passphrase copy cannot be, and a fresh one is written back.
* @param {(p:{done:number,total:number})=>void} [o.onProgress]
@@ -4100,7 +4102,7 @@ async function rewrapAllNodes(o) {
let oldKey, newKey;
if (o.bundleKey) {
// "Keep the current passphrase key, just add / refresh the recovery copy"
- // — the Profile backfill (docs/auth-confirm.md §4.3). `o.bundleKey` is the
+ // — the Profile backfill (docs/MESHBAY_DESIGN.md §3.6). `o.bundleKey` is the
// live {v2,v1} session key, so no passphrase is needed.
oldKey = newKey = o.bundleKey;
} else {
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-app-settings.js b/packages/meshbay-hub/src/meshbay_hub/static/video-app-settings.js
index 5c79bc5..4662c25 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/video-app-settings.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/video-app-settings.js
@@ -23,7 +23,7 @@ const TMDB_LANGUAGE_BY_LOCALE = {
* The TMDB parts are two independent settings that happen to sit together:
* the on/off switch is per group, while the API key and the query language
* are node-wide, because they are one operator's credential and one shared
- * cache (docs/mediacenter.md §5.5). They save separately for that reason.
+ * cache (docs/MESHBAY_DESIGN.md §9.7). They save separately for that reason.
*/
function VideoSettings({ roots, dirs, settings, saveDirectories, transport, signFn }) {
const { busy, msg, run } = useSaver();
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
index d4af4ef..d4ec01a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/video-app.js
@@ -10,16 +10,16 @@ import { usePager, Pager, pageSizeFrom } from './pager.js';
// ── Videos ───────────────────────────────────────────────────────────────────
//
// A poster-grid (TMDB-enriched) or flat (thumbnail-only) browser for a
-// group's video files, per docs/mediacenter.md. Grouping: one card per movie,
-// one card per show — shows are grouped by `display_title` (already
+// group's video files, per docs/MESHBAY_DESIGN.md §9.7. Grouping: one card per
+// movie, one card per show — shows are grouped by `display_title` (already
// resolved/corroborated at index time, §3.4), not by folder path, since a
// client-side path convention would have to guess how many roots/subfolders
// deep a show folder sits, which display_title already settled once.
//
// TMDB metadata is fetched lazily, only for a tile once it is actually
-// visible (LazyTile below) — apps.md §5's virtualization requirement for a
-// grid of many tiles. Thumbnails go through the same `file_req`/chunk path
-// as a real file (docs/mediacenter.md §5.3) via MediaThumb, reusing
+// visible (LazyTile below) — the virtualization requirement for a grid of
+// many tiles. Thumbnails go through the same `file_req`/chunk path
+// as a real file (docs/MESHBAY_DESIGN.md §6.5) via MediaThumb, reusing
// chat-app.js's ChatImage pattern.
const VIEW_MODE_KEY = 'meshbay_video_view_mode';
@@ -121,7 +121,7 @@ function groupVideoEntries(entries, videoDirectories) {
return { movies, shows };
}
-// ── lazy-mount tile (apps.md §5 virtualization) ─────────────────────────────
+// ── lazy-mount tile (virtualization) ───────────────────────────────────────
const LAZY_TILE_MARGIN = 300;
@@ -485,7 +485,7 @@ function OverviewText({ text, reserve }) {
`;
}
-// ── season picker (docs/mediacenter.md §5.4's fix for a mis-scoped overview) ─
+// ── season picker (docs/MESHBAY_DESIGN.md §9.7's per-season text) ────────────
//
// A row of tabs, which this was, scrolls horizontally once a show has more
// seasons than fit — a scrollbar nobody finds, hiding the seasons that matter
@@ -708,7 +708,7 @@ function VideoDetailModal({
const [rematching, setRematching] = useState(false);
const mediaType = show ? 'tv' : 'movie';
- // §10.1/V13: drop this file's cached match on the node and let it
+ // V13: drop this file's cached match on the node and let it
// re-resolve with the current matcher — the one-click alternative to the
// full search-and-pick flow above.
const doRematch = useCallback(async () => {
@@ -779,10 +779,11 @@ function VideoDetailModal({
`}
${/* Both of these act on a TMDB match, and with TMDB off for this
group there is none to act on: "Fix match" opens a search the
- node answers with an empty result list (§5.7's silent
- degradation), and "Rematch" drops a cached match that was
- never made. Two buttons that cannot do anything, offered to
- the one person who already knows why. */''}
+ node answers with an empty result list
+ (docs/MESHBAY_DESIGN.md §9.7's silent degradation), and
+ "Rematch" drops a cached match that was never made. Two
+ buttons that cannot do anything, offered to the one person
+ who already knows why. */''}
${isNodeAdmin && tmdbEnabled && html`
<div class="video-admin-actions">
<button class="admin-btn video-fix-match" onClick=${() => setSearching(true)}>
@@ -1182,9 +1183,10 @@ function VideoApp({
`;
}
-// MediaThumb and LazyTile are also used by music-app.js (docs/musicbay.md
-// §7.1): the same "decrypt a thumb_hash via the chunk path into a cached
+// MediaThumb and LazyTile are also used by music-app.js
+// (docs/MESHBAY_DESIGN.md §9.8): the same "decrypt a thumb_hash via the
+// chunk path into a cached
// blob" and "mount only once actually scrolled near" mechanisms apply to a
// track's cover art unchanged, so Music imports them here rather than
-// re-implementing (apps.md §4's checklist).
+// re-implementing (docs/MESHBAY_DESIGN.md §9.4's checklist).
export { VideoApp, MediaThumb, LazyTile, groupVideoEntries, bumpMediaMetaGeneration, bumpThumbGeneration };