diff options
Diffstat (limited to 'packages/meshbay-hub/src')
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 }; |