diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/MESHBAY_DESIGN.md | 1 | ||||
| -rw-r--r-- | docs/playlists.md | 12 |
2 files changed, 7 insertions, 6 deletions
diff --git a/docs/MESHBAY_DESIGN.md b/docs/MESHBAY_DESIGN.md index 943aeef..a47cea0 100644 --- a/docs/MESHBAY_DESIGN.md +++ b/docs/MESHBAY_DESIGN.md @@ -3192,6 +3192,7 @@ process runs it — `systemctl --user` on Linux, Task Scheduler on Windows. | **Per-device revocation has no CLI** | A device is revoked over MNP (`roster.revoke_device`), from a device the node has already pinned. On a headless node the operator's only lever is `member unpin`, which removes **every** device of that account — so the per-device control the roster is built around is reachable from an interface and from nowhere else. §6.7 listed a `meshbay-node member device list\|revoke` verb that was never written, and that listing is how this was found: `USERGUIDE.md` was the first document written by reading the CLI rather than this specification, and the verb it copied out did not run | | **Migrations run on SQLite only** | The chain reaches head and agrees with the models there (§12), which is not where it ships. **The exposure is one revision deep, not the whole chain**: every revision behind the first packaged release was development that no installation ever ran, so nothing replays them on PostgreSQL. What is unguarded is the *next* migration — a default, an index type or a constraint PostgreSQL refuses reaches a deploy without the suite saying so | | **The loopback path removes access without writing an audit entry** | `ops.revoke_member` and `ops.unpin_member` log to the daemon's log and nothing to `audit.db`; the MNP admin handlers doing the same work audit `member_revoke` and `member_unpin`. So a removal made from the node page or the CLI — the two doors an operator sitting at their own machine actually uses — leaves the journal showing an admission and then, whenever that person next connects, a `join_refused` with nothing in between to explain it. §5.4's signed transcript is not what is missing: a loopback caller is authorized by being on localhost with the run token and signs nothing, so the gap is the record, not the authority. Found by reading a node's audit log for a refusal whose cause was six hours earlier and unrecorded | +| **The Create group wizard calls two hooks after an early return** | `CreateGroupWizard` (`create-group-page.js`) returns during node detection, before its `useRef`/`useEffect` for provisioning, so the hook count changes between renders. Preact tolerates a list that grows, and nothing is known to break; `test_hook_ordering.py` checks declaration order, not this. Found while tracing the frozen-fields report, which had another cause (`ask.js`) | | **A node key is read from the terminal or the desktop client, never a browser** | **Accepted.** `meshbay-node status` on the node's own machine and Node → Overview in the desktop client are the two places the key can be read; the Node page is Electron-only, because `platform.node` resolves to "not available" without the bridge, and no hub route exposes the key. The create-group wizard links it automatically over that same bridge, so the manual paste in **Profile → Link Node** exists for the operator who runs the node from a terminal and the hub from a browser — who has a terminal by definition. Anyone linking a node is already at a shell prompt, so a browser-reachable copy would buy nothing and widen what the hub knows about the node | --- diff --git a/docs/playlists.md b/docs/playlists.md index 232eae5..6378e63 100644 --- a/docs/playlists.md +++ b/docs/playlists.md @@ -69,7 +69,7 @@ were *wrong* are more useful than the ones that were right. | 1 | MNP MINOR bump to **2.1** | The wire version was already **3.0**; the bump is **3.1**, and `MNP_MIN_SUPPORTED` does not move because nothing here is required | | 2 | `kind` is `playlist:<uuid>` | That refuses `playlist:favorites` — the one playlist every account has. The pattern is `[A-Za-z0-9_-]{1,64}` | | 3 | The blob is **msgpack**, "same as everything else on MNP" | The node never parses it, so the encoding is a private choice; MNP's codec is private to `transport.js`, a classic script. **JSON**, which is what keeps the merge runnable by `node` with nothing around it | -| 4 | Delete confirms inline, "not `window.confirm()`, which blocks the SPA" | Backwards. `prompt()` **throws** in Electron and is banned by a test; `confirm()` is measured to work and is used in twenty places. A *name* needs a field; a *confirmation* uses `confirm` | +| 4 | Delete confirms inline, "not `window.confirm()`, which blocks the SPA" | Right, for a reason nobody had yet. `prompt()` **throws** in Electron; `confirm()` opens, and leaves the window unable to type once it closes. A *name* needs a field; a *confirmation* uses `ask()` from `ask.js`, drawn by the page | | 5 | Submenus fly out, and are a sheet on a coarse pointer | They **expand in place**, which the account menu's language list already does. No flipping, no hover intent, no separate mobile design. The tracklist level loads when it is expanded | | 6 | Add all four new files to `STATIC_FILES` / `SPLIT_FILES` | Those lists check hook ordering. The four hookless modules do not belong in them, exactly as `source-merge.js` does not; only `menu.js` and `playlist-menu.js` do | | 7 | Four modules | **Five**: `playlist-crypto.js` is split out so seal/open can be executed standalone. A crypto layer that cannot be executed is one nobody has checked | @@ -921,15 +921,15 @@ Rules the list items carry: - **New** takes a name only, as asked, typed into **a field**: `window.prompt` does not exist in the desktop client — it throws, which is how the Files toolbar's New folder button came to do nothing at all, and - `test_no_prompt_in_the_spa.py` refuses a build that reintroduces it. The name + `test_no_native_dialogs_in_the_spa.py` refuses a build that reintroduces it. The name must be unique *for this user*, checked against the manifest and folded for case and accents, so "Soirée" and "soiree" are not two playlists. - **Delete** never lists Favourites (§5.1) — the store refuses it anyway, and offering an action that always fails is worse than not offering it. It asks - with **`window.confirm`**, which is the correction: `prompt()` throws in - Electron and is banned by a test, while `confirm()` is measured to work and is - used in twenty places in this SPA. A deletion is a tombstone and nothing in - the interface undoes it, so it is worth one question. + with **`ask()`** (`ask.js`), not `window.confirm`: in the desktop client a + native `confirm()` leaves the window unable to type after it closes, so the + whole SPA asks in the page. A deletion is a tombstone and nothing in the + interface undoes it, so it is worth one question. - **Remove a track** is the two-level one, expanded in place: pick the playlist, then its tracklist, one click per track. The second level is read from IndexedDB when it is opened (§10.1's `loadItems`). |