aboutsummaryrefslogtreecommitdiffstats
path: root/packages
Commit message (Collapse)AuthorAgeFilesLines
* test(hub): pin the Android Sync probe to EnglishHEADmainChristophe Besson11 hours1-1/+3
| | | | | | | The probe followed the browser's language, so its assertions on English labels failed on a machine set to French. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): run the connection processes on Windows, source and frozenChristophe Besson26 hours4-3/+70
| | | | | | | | | | | | | | | | | | | | | | | | | Connection processes and the larger UDP receive buffer were written and measured on Linux; nothing had run on Windows, and `meshbay-node.exe webrtc-worker` had never run from a frozen build on any system. - test_connection_processes.py failed on Windows (8 tests): the selector loop the suite uses there cannot start a process. A test marked `daemon_loop` now runs on the daemon's own loop, chosen through pytest-asyncio's loop factories. - The build starts both frozen executables as connection processes and carries a connection through each (smoke-connection-process.py). The daemon's own smoke test never signs in to a hub, which is when the processes start. - The warning for a receive buffer below what was asked named net.core.rmem_max on every system; it names it on Linux only. Windows grants the 4 MB. Measured on an installed build in service mode, docs/WINDOWS-PORT.md §5.12: two processes at startup and on a live change, a browser's handshake, chat, a 200 MB download over 8 connections across both processes, an upload, a video stream, and stop, restart and start leaving no process behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): look for Node in the menu under the avatarChristophe Besson26 hours2-8/+20
| | | | | | | | | | Node and Create group left the sidebar for the user menu. A source-reading test still looked for the old `showNode=` expression and failed everywhere; the installed application's end-to-end test waited for "Create group" in the sidebar and would have timed out. Both now check the menu, and the packaging guide says where the two entries are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep the photo lightbox's close button on a phone screenChristophe Besson37 hours5-40/+81
| | | | | | | | | The bar was 457 px wide and pushed the close button to 397..437 px, off a 360 px phone. Delete leaves the lightbox (it stays on the tile's menu: right-click, or press and hold), and below 768 px the bar drops the file name, the zoom label and 100%. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: keep the sidebar for groups; search in the nav, the rest in the user menuChristophe Besson2 days16-88/+186
| | | | | | | | | | | | | | | | | | | | | | | Search is an icon in the nav, left of the tray button and the bell, and marks itself current on its page. Administration, Node, Create group and Android Sync moved to the user menu, between Settings and the theme, each under the condition it had in the sidebar. The sidebar keeps Public groups and the groups. On a phone, a tablet or the Android application the menu trigger is the avatar alone (max-width 768px or a coarse pointer); the name is the dropdown's first line, one line with an ellipsis. Measured: a 20-character handle put the trigger 159 px off a 320 px screen with search and transfers in the nav, and a 64-character one pushed the dropdown 122 px off the left. Creating a group is a desktop-application feature by design: a hand-typed #/create-group no longer opens the form in a browser, and QUICKSTART step 6 now says to use the desktop application instead of the browser. test_layout_responsive read only the first 768px media block; it reads them all now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: let the operator pause and resume a root's indexing (MNP 6.2)Christophe Besson2 days42-165/+1793
| | | | | | | | | | | | | | | A whole-root walk now checks its root before every file: removed or ejected, it stops instead of reading the drive to the end; paused, it finishes from the hash cache alone. A paused root is not watched, not swept by reconcile and survives a restart (roster), its indexed files stay served. Resume runs the reconcile sweep over the root, so entries and their enrichment are kept. Signed root_pause / root_resume over MNP, loopback routes, CLI `root pause|resume`, a pause button on the indexing dock and pause/resume in the roots table of Group settings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): tell the person at sign-up what the hub keeps and whyChristophe Besson2 days14-10/+44
| | | | | | | | | | | | | | | | A line under the Register button: creating an account accepts the hub's terms of use, and the law requires the hub to keep the connection log (IP address, date, action) for one year, even after the account is deleted. It links to the legal pages of the hub in use, so a self-hosted hub shows its own operator's documents. A notice, not a checkbox: the account rests on the contract and the log on a legal obligation, so there is no consent to ask for, and one that blocks sign-up would be neither free nor withdrawable. legalUrl() moves to platform.js, shared by the sidebar and the form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): tell the Node page how many cores there areChristophe Besson2 days3-14/+30
| | | | | | | The page reads its settings from list_groups, which lacked cpu_count, so the field showed "of 1" and stayed at 1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): a receive buffer that holds an upload's windowChristophe Besson2 days5-1/+90
| | | | | | | Uploads ran at 6.5 MB/s because the kernel dropped datagrams at the node's 256 KB socket. Asking for 4 MB gave 19.5 MB/s, no drop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): carry connections on several coresChristophe Besson2 days23-14/+1109
| | | | | | | | connection_processes (default 1) starts that many processes for the WebRTC stack; sessions and keys stay in the node. Set from node.toml or the Node page, applied to new connections. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: spread a large download over eight connectionsChristophe Besson2 days8-12/+524
| | | | | | | | | One WebRTC connection carries 5 to 6 MB/s whatever the line can do. A leased download of 64 MB or more now opens seven more to its node, and a lease serves the other connections of its device in its group. Measured through the whole stack: 5 MB/s before, 24 after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): log what a transfer moved when it endsChristophe Besson2 days9-2/+237
| | | | | | | One INFO line per lease: bytes, duration, rate, and the SCTP association's window, round trip, losses and T3 expirations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(android): what the first backup on a phone showedChristophe Besson3 days24-37/+128
| | | | | | | | | Slices are asked for in the address, not a Range header the WebView drops; a settings change runs at once; the notification is updated once a second; the SMS section says how to lift Android's restricted setting; the photo section says whether videos are included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back up WhatsApp's own chat backups, and its media if askedChristophe Besson3 days28-54/+582
| | | | | | | | A WhatsApp section takes WhatsApp's folder through the picker, sends its encrypted Databases and Backups (not the dated copies of earlier weeks) into <folder>/<account>-whatsapp, and names the restore set in the manifest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): send a manifest of what each backup run sentChristophe Besson3 days11-37/+292
| | | | | | | | Photos, videos and files record, per file, their path on the node, their path and album on the phone, dates, size and SHA-256, uploaded as meshbay-manifest/manifest-<date>.jsonl once the run is done. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back up the files of folders the person choosesChristophe Besson3 days34-232/+1365
| | | | | | | | A Files section takes folders through the system picker (no storage permission), refuses DCIM, Pictures and Movies, skips what the photo backup sends, and runs on the photo backup's own runner into <folder>/<account>-drive. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(photos): show a phone's clips in their albumsChristophe Besson3 days16-12/+447
| | | | | | | | Videos in a photo folder are thumbnailed by the node, counted and marked in the album, and played in the group's video player from the lightbox; the slideshow passes them by. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back the videos of the chosen albums up beside the photosChristophe Besson3 days24-78/+357
| | | | | | | | A 'Videos too' option, off by default, sends them into the same YYYY/YYYY-MM folders. Files are read from the phone a ranged chunk at a time, so a large video is never whole in the page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back the calendars up as an iCalendar fileChristophe Besson3 days23-14/+465
| | | | | | | A Calendar section sends every calendar the person can edit, as a dated .ics, into <folder>/<account>-calendar once a day when it changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back up the personal profile onlyChristophe Besson3 days4-41/+90
| | | | | | | A copy of the application inside a work profile offers no backup, and no source reads another profile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back text messages up, in a build Play does not getChristophe Besson3 days30-26/+389
| | | | | | | | A Messages section sends the SMS added since the last copy, as restorable <smses> XML, into <folder>/<account>-messages/YYYY. A play flavor has neither READ_SMS nor the code that reads messages; full is the default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): one backup destination, a group the account owns aloneChristophe Besson3 days34-613/+883
| | | | | | | | Chosen once at the top of Android Sync for every kind; photos and contacts go into <folder>/<account>-photos and -contacts. Owner and sole member are checked at set-up and before every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): delete a photo from the Photos appChristophe Besson4 days3-8/+394
| | | | | | | On a right-clicked tile and in the lightbox bar, after a confirmation, for the node's operator or the photo's uploader, as in Files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): back the phone's contacts up to a group of one's ownChristophe Besson4 days29-37/+1443
| | | | | | | | A Contacts backup section on the Android Sync page sends a dated .vcf into <folder>/<account>-contacts once a day when the address book changed, only to a group the account is alone in, checked again at every run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): photo backup moves to its own Android Sync pageChristophe Besson4 days20-18/+66
| | | | | | | A Phone section of the side menu leads to it, on the Android application only; Settings no longer holds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): stop aiortc's SCTP sender leaking bytes in flightChristophe Besson4 days3-0/+92
| | | | | | | | A chunk gap-acked then resent after T3 stayed counted for good; enough of them and the node sent nothing more on that connection. The photo backup stalled on it after a few dozen photos. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(hub): let the cast relay script end by itselfChristophe Besson4 days1-1/+3
| | | | | | | | | | | test_cast_subtitles.py's node script called process.exit(0) right after relay.stop(), with fetch's sockets still closing. Node 24 on Windows aborted there on a libuv assertion (UV_HANDLE_CLOSING, src\win\async.c) two runs in three, after printing its results, so the module fixture failed and seven tests errored at setup. Nothing is left once the relay stops: the script ends by itself, in the same 0.1 s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(android): back photos up under YYYY/YYYY-MM, not YYYY/MMChristophe Besson4 days4-12/+12
| | | | | | A month folder named 08 alone read like an album number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(hub): wait for a resumed transfer's state, not for 120 msChristophe Besson4 days1-4/+15
| | | | | | | | | | | | | The pausable run in test_transfers.py moves in setTimeout(5) chunks, and the resume test gave it a fixed 120 ms to finish. A setTimeout(5) lasts about 15 ms on Windows' default timer, so the resumed run took 156 ms there and the test failed with nothing wrong; the 20 ms wait before pausing had the same thin margin. The run now records how far it got (state.at), and the test pauses after two chunks and waits for "done", bounded at 2 s. The assertions are unchanged, and a resume that restarts from zero is still caught (checked by introducing one in transfers.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): a name an LGPL file declares itself is its own bindingChristophe Besson4 days1-0/+7
| | | | | | | | | | | The LGPL closure check excluded only names declared at the top level of the LGPL files, so keyring.js's `const state = () => ...`, local to createKeyring, read as a call to photo-sync.js's top-level `state` once that file was added. A name the file declares at any depth now shadows an AGPL global for that file. A real call into an AGPL module is still caught (checked by adding one to keyring.js). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): say when the account's node is elsewhere, and let go of itChristophe Besson4 days15-4/+288
| | | | | | | | | | | | | | | | | | | | | | Signing in on a desktop links this machine's node to the account, but never over a key already linked (that would cut off the user's other machine) nor a node set up for another account. On a real install both left the node reading "Running" while the hub refused it in a loop, and nothing said why. And the only way to unlink was the linked machine's own Node page, of no use once that machine is gone. - The Node page works out, from the node and the hub each time it looks, whether this node can serve the signed-in account (nodeLinkProblem), and says why not: "Link this node instead" (asked first) puts this node's key on the account; "Use this node for my account" switches a node set up for another account through node:start, which takeOver now lets past a node that answers "running". The first version went through node:start for both, and clicking it on a real install did nothing: a node signed in before the account was linked elsewhere answers "running". - The Profile page unlinks the account's node (DELETE /v1/users/me/node_key), from any machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(packaging): the Store package declares what the NSIS scripts doChristophe Besson4 days19-95/+624
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | A test-signed install of the MSIX build, in the WindowsApps folder a Store install uses, showed that every script-made piece of the NSIS model breaks there, because each names the install folder and every update deletes it: the firewall rules went stale, the PATH entries piled up pointing at deleted folders, and the Startup-folder .vbs was refused ("Permission denied") right after sign-in. The network capabilities the manifest declared covered nothing: they make rules for sandboxed apps only, and a listener in the package still got the Windows firewall prompt. The package's own startup task was on by default, started the node whatever mode the Node page said, and ran the console executable, whose window stopped the node when closed. The package now declares what Windows then creates at install, carries across updates and removes with the app, all without an administrator prompt (each measured on the real install, through an update and a reboot): - firewall rules for the node, in a custom manifest template, since only a package-level element can hold them; - the startup task, off by default, running meshbay-nodew.exe, a new build of the daemon without a console; - an execution alias for meshbay-node.exe, so the app adds no PATH entry. The node's CLI switches the startup task (platform.startup_task, ctypes over the WinRT ABI): Windows gives the package's identity to the executables in it, not to a powershell.exe the app starts, which got "Element not found". `meshbay-node autostart install | remove | status` therefore works in the Store package from the app and a terminal alike; the app caches the answer, since the Node page polls. Starting at boot stays the .exe installer's: the Store package offers no service mode, and the CLI refuses `service install` there. Process listings count both image names. The Node page's status poll cleared the message of a refused action within five seconds; the two errors are kept apart now (all Windows builds). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: back up the phone's photos to a group, once a day on Wi-FiChristophe Besson4 days36-13/+2676
| | | | | | | | | | | | | | | | | | | | | | | | | The Android application sends the photos taken on the phone to one folder of one group chosen by the member (docs/MESHBAY_DESIGN.md §9.12). The phone lists MediaStore, keeps a ledger of what was sent and hands each photo's bytes to the page by an opaque token on the packaged origin; the page decides when a run is due and uploads through the existing path, one photo at a time under a slot. - Once a day from the last finished run, on an unmetered network only; "Back up now" asks first on mobile data. Leaving Wi-Fi stops after the file in flight. - Photos already on the phone are sent by default, newest first, under <folder>/YYYY/MM; edits are sent beside the original as -edited-<date>. - Additive by construction: nothing is ever deleted, renamed or replaced on the node, and a photo deleted on the node is not sent again. - A confirmation names the group, owner, members, folder and size when the destination or starting point changes; a lasting refusal (disk full, folder read-only or gone, no longer a member) is said once and retried a day later. - No ACCESS_MEDIA_LOCATION, so the platform redacts photo locations. - A dataSync foreground service keeps a run going with the screen off. HEIC/HEIF photos are sent but not shown in Photos yet (§15.2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): refuse an upload on a full disk with a stated reasonChristophe Besson4 days16-5/+206
| | | | | | | | | | | | Nothing on the upload path knew about ENOSPC: a write that found no room raised out of the handler, the catch-all answered "Request failed", and the .part stayed behind holding the space that had run out. The node now refuses with `disk_full` at chunk 0 when the announced size would leave less than 1 GiB free, and at any write that fails with ENOSPC/EDQUOT, dropping the partial. The client carries the code on the error and the transfers panel says "The node's disk is full" in every catalogue. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): an open application looks for new notifications when you come backChristophe Besson5 days2-0/+77
| | | | | | | | | | | | | | The page asked for the notification list at sign-in and at a token renewal, and at no other time. A notification created after the application started, such as a chat line the hub wrote 20 ms after the message, stayed unseen until the next launch. The hub has no channel to the page and is not polled on a timer, so the list is asked for again on a gesture: the application returning to the foreground (an Android phone included) and the home page, where the list is shown. Two requests less than 30 s apart count as one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: let the operator purge a group's chat (MNP 6.1)Christophe Besson5 days28-43/+699
| | | | | | | | Signed chat_purge from the Chat settings deletes every stored message; epoch keys and attachments stay. The ack is broadcast so open chat panels empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: notifications on Android while closed, with nothing to installChristophe Besson5 days38-6/+1884
| | | | | | | | | | | | | | | | | | | | The phone fetches what is new every fifteen minutes with a poll secret (POST /v1/push/poll) that reads notification lines and nothing else. When a UnifiedPush distributor is already installed, the hub also pushes at once, encrypted to the phone (RFC 8291); losing the distributor falls back to fetching. The hub now honours "disable all notifications" itself: create_notification creates nothing for that account, as it already did for a muted group, so neither switch lets anything reach a phone. The interface used to be the only reader of the account-wide switch. Push endpoints are member-supplied URLs: a send refuses non-public addresses, connects to the address it checked, and follows no redirect. Android build untested here (no SDK on this machine). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: sign Android releases with the release keyChristophe Besson6 days3-5/+53
| | | | | | | assembleRelease reads the key from ~/.gradle/gradle.properties and fails without it instead of falling back to the debug key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(node): sample 9 MB with the size above 9 MB, keep known ids0.19Christophe Besson6 days5-54/+84
| | | | | | | | hash_version 3: size + first 4 MB + last 4 MB + 1 MB at the middle, 5.5x faster cold on a USB disk than the 45 MB sample. The cache now serves a hit under whatever version it holds, so no existing id moves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): re-read the roster before saying a key changedChristophe Besson6 days4-20/+194
| | | | | | | | A member invited after the roster was read showed "key changed" on each message until a reload. Read it again once per account and device on the connection, shared by concurrent messages, and pin only the final verdict. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): name a root after its drive when its basename is takenChristophe Besson6 days4-3/+80
| | | | | | | | | Two drives with a folder of the same name made the second add fail, and no screen could supply another name. add_root now names it "Name (H)" or "Name (parent)"; a name the operator typed is still refused on a clash. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* chore: bump version to 0.19.0Christophe Besson6 days8-8/+8
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): stop the node at Quit in "only while open", whoever started itChristophe Besson6 days3-12/+81
| | | | | | | | | | | | | | | | | | | Switching from "at sign-in" to "only while MeshBay is open" left the node the sign-in launcher had started running after Quit: only a node this process had started was stopped. In that mode the app owns the node, so Quit stops the one that is there. The start with the app and the sign-in's own start (ensureNode) also both ran `autostart start` at launch -- three meshbay-node.exe were seen racing for the port. The sign-in's start and node:start now wait for the launch's. The end-to-end test covers the mode: Quit leaves no node, opening the app starts one. It launches the app with the environment it was imported with: the suite's conftest points HOME, USERPROFILE, LOCALAPPDATA and APPDATA at a throwaway directory per test, and the app started under that crashed at once (0x80000003), which first looked like a crash of the app itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test(node): check the startup mode in the Windows end-to-end testChristophe Besson6 days1-5/+22
| | | | | | | | The boot task or the sign-in launcher, as chosen, and the node in session 0 for the service's S4U logon or in the signed-in session otherwise. Passed in service and sign-in modes on the installed 0.18.0 build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: set the Windows node up at sign-in, and stop it for realChristophe Besson6 days23-34/+850
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Found by the first Windows beta tester, then reproduced on a clean install. After a service-mode install nothing set the node up for the account that signed in: the boot task started a node that quit ("hub.username not set"), and the sidebar showed Node / Create group only once the hub held a node key. The only way to the wizard that provisions was the home page's welcome card, which an account already in a group never sees. The way out was `meshbay-node init` and the key pasted on the profile page -- which is also what PACKAGING-GUIDE.md told people to do. - main.js `node:ensure`, called by app.js at sign-in: provisions, starts and links the node this build ships (Windows, bundled node only). A node set up for another account, or an account linked to another node, is left alone. node:start waits for it, so the two never race. - The sidebar shows the Node section when a node exists on this machine. - The Node page's status is the node's: its control API and the process list, not the service task's state (a node started from a terminal ran while the page said Stopped). Stop says Stopped only once no meshbay-node.exe is left, and stays offered for a process that answers nothing. - CLI stop kills the pid that answered when a graceful stop does not finish, and fails with the reason when a node process is still there. - The daemon ends its process 3s after _shutdown(): Python's exit waited for a busy indexer thread, with the control API already closed. Armed by main() only, never by a daemon run inside a test. - node.toml is read as utf-8-sig (PowerShell 5.1 writes a BOM), and a config that cannot be read is logged instead of dying silently in service mode. - "Pair this browser" queues the code for the next group of this node to open instead of saying "Paired successfully"; no banner before a group. - test_e2e_windows_app.py (opt-in, MESHBAY_WIN_E2E=1) drives the installed app against a throwaway hub: fresh account to linked node, Stop, Start, Restart, checked against the real processes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): drop a directory moved out of the root on Windows tooChristophe Besson6 days2-0/+19
| | | | | | | | | On Windows watchdog cannot tell what a deleted path was -- it is gone -- and reports a directory moved out of the root as a file deleted, so the fix in 3d5a168 never ran there and test_the_watcher_reports_a_directory_moved_out failed. A deleted path the index still holds entries below was a directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* test: read UTF-8 explicitly in the licensing and keyring-vector testsChristophe Besson6 days2-26/+42
| | | | | | | | | | On Windows a bare `read_text()` or `subprocess.run(text=True)` decodes with the locale's code page (cp1252), not UTF-8. `test_licensing.py` then failed on a byte of the vendored LICENSES.txt, and `test_keyring_vectors.py` decoded the generator's output, which carries CJK test strings, into something that no longer matched keyring.json. Both files are UTF-8; say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(node): name members admitted without an invitation nameChristophe Besson6 days9-6/+89
| | | | | | | | | | | A member who joined by link, by a new device or into an open group was pinned in the roster with no name, so the audit log showed only the first characters of their id. The hub's MNP token now carries the account's username, and after the handshake the node writes it into the roster for an account whose name is empty. An invitation's name is never overwritten; the name is a label, authority stays on `sub`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* perf(node): list a group's directories off the event loopChristophe Besson6 days7-120/+167
| | | | | | | | Every full index walked all roots on the loop, and a node with several large roots stopped answering for seconds. Walk directories only, on the roots' disk thread; index_sync is spawned and still answers on failure. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): keep "add a directory" when one node check is missedChristophe Besson6 days2-13/+125
| | | | | | | | A node busy indexing could miss the single 3 s check after a root was added, and the button stayed hidden until a reload. Ask up to four times before deciding there is no node on this machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>