diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-19 02:12:47 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-19 02:12:47 +0200 |
| commit | 20a824118c09af15d6c338db4c9480ffe5cbcdb6 (patch) | |
| tree | 39fa0c2056b12e3423eefe0cda78cf160ce17131 /packages/meshbay-hub/src/meshbay_hub | |
| parent | cc0820de8d8c33560c44bd6a0864de30e33a0109 (diff) | |
| download | meshbay-20a824118c09af15d6c338db4c9480ffe5cbcdb6.tar.gz | |
docs: cite MESHBAY_DESIGN.md and a section instead of the merged notes
The per-feature design notes were merged into docs/MESHBAY_DESIGN.md and
deleted from the tree on 2026-09-11, but ~230 comments across the three
packages still named them — usually written `docs/musicbay.md §3.2`, as
though the file were still in docs/. A reader had to know §16 existed to
resolve any of them. They now name the section directly.
Every mapping comes from §16, the concordance, which already records where
each old section landed: musicbay -> §9.8, mediacenter -> §9.7 for the
Videos app and §6.5 where the subject is derived data, photos -> §9.9,
auth-confirm -> §3.6, refactoring-search -> §9.11, invite-pairing-v1 ->
§3.4, per-node-identity-v1 -> §3.2, captcha -> §7.7, chat-sender-keys ->
§4.5, apps/refactor-groups -> §9.1–§9.4, desktop-client-v1 per section.
Bare citations of the same documents (`draft-v6 §2.11`, `§4.8`, `§3.4`)
are retargeted too: those collide with real section numbers in the design
document, so leaving them would have been worse than the named form.
Four cases the concordance does not cover, each decided rather than guessed:
Sub-item references into documents that no longer exist — mediacenter's
`§3.3 row 4`, `§3.4b/c`, `apps.md §3b` — name rows and sub-items §9.7 and
§9.2 do not reproduce. The module-level citation stays; the sub-item
pointer is dropped.
The V-findings keep their labels but lose the dead `§10.1/` prefix.
§13.8 lists V1–V13 as per-application open items, which is not what the
labels mean in these comments, so pointing them at §13.8 would have been
a false citation.
`apps.md §5`'s virtualization requirement has no counterpart anywhere in
the design document. The requirement is stated in the comment itself, so
the citation is dropped rather than aimed at a section that does not say
it.
Comments that attributed a *sentence* to an old note — musicbay's "several
thousand files" example, its "what got measured" note, its measured
~11%/~26% cover-art figures, the "original no root, whole shared tree"
call — state the fact without attribution now. §9.8 does not contain those
sentences and citing it for them would have been wrong.
CLAUDE.md's "a reference to a document that no longer exists" row now says
the concordance is for git history and out-of-tree material; the code cites
sections directly.
Verified: 2851 passed, 4 skipped. The 12 errors in the run are the Firefox
leg of test_sticky_header.py's browser harness, which is broken at the
browser level on this machine — headless Firefox (snap) dies with
`[GFX1-]: RenderCompositorSWGL failed mapping default framebuffer`, renders
nothing, and the probe exits `{"error": "no measurement"}` after its full
90s wait. Chrome runs the same 12 assertions in 3.2s and passes. Nothing
here can affect it: every changed line in style.css is inside a comment.
Also checked: ast.parse on every changed .py, `node --check` on every
changed .js, the /* */ balance in style.css, and that no changed line
exceeds the width its file already used.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub')
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 }; |