aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
Commit message (Collapse)AuthorAgeFilesLines
* chore: bump version to 0.20.0Christophe Besson16 hours2-2/+2
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(android): no text messages backup, and one build againChristophe Besson19 hours15-77/+9
| | | | | | | READ_SMS sits behind restricted settings outside a store and Play keeps it for the default SMS application: not worth a second build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(auth): the sign-up notice links the privacy statement, with a spaceChristophe Besson19 hours12-3/+25
| | | | | | | | | | | | | | | The link under the Register button was glued to the sentence before it: htm drops whitespace that holds a line break, so "deleted.Legal information". It also pointed at the hub's /legal/ index. Where an account is created is where GDPR art. 13 owes the person the information about their data, so it now links the privacy statement itself — /legal/confidentialite.fr.txt in French, /legal/privacy-statement.en.txt in every other language — on the hub that served the page, like every legal link. The sidebar's "Legal information" is unchanged. Checked in Chrome and Firefox (headless), in English and French. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(auth): the recovery key never reaches the hubChristophe Besson20 hours15-83/+112
| | | | | | | | | | | | | | | | | | | | | | | | | | | | Ticking "also e-mail it to me" at registration sent the recovery key R in the register body, and the hub appended it to the verification e-mail: the hub saw R in clear on its way out, and the resend door — no captcha — let anyone who knew a pending username and address have the hub mail a "recovery key" of their choosing. The box is gone. The recovery screen offers "E-mail it to myself", a mailto: link that opens the person's own mail application with the key filled in; the desktop shell hands mailto: to the system (http, https and mailto only) and the Android shell to a mail application (ACTION_SENDTO). The CLI drops --email-recovery-key and prints the key, as before. The hub mails the code alone, and refuses a register that still carries a recovery_key rather than dropping it, so an older client offering the option cannot tell someone their key was sent when it was not. A mailbox holding R remains, with the reset code that reaches the same mailbox, enough to recover the account's identities; the link says so, and it is the person's choice. Checked in Chrome and Firefox (headless, the real page against a stubbed hub) and by the CLI's end-to-end registration against a local hub. The Android change was not built here, and the desktop link was not clicked in the application. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): a phone's poll secret ends with the account's sessionsChristophe Besson20 hours3-6/+22
| | | | | | | | | | | | | | | | The secret a phone polls /v1/push/poll with is a credential of its own, outside any session, and it outlived the ones that should have ended it: "sign out everywhere" — what someone who lost a phone presses — a passphrase change and an admin revocation left every push row in place, so the lost phone went on reading "X posted in <group>" lines. Each now deletes the account's rows, as a reset and an erasure already did, and the poll refuses a revoked or deleted account whatever deleted the row or not. A suspended one still reads: the line telling it so is one of them. A phone still in hand is not affected for long: a refused poll makes it forget the row, and the page registers it again at its next start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* refactor(chat): no thread_id — chat has no threadsChristophe Besson21 hours2-9/+4
| | | | | | | | | | | | | | Nothing ever set it (the page and the CLI sent null) and nothing read it (the node's get_thread had no caller). It is no longer sent by the page or the CLI, in clear or in the sealed payload, nor relayed or served by the node; get_thread and its index are gone, and the protocol and design docs no longer name it. The node still accepts a chat_msg carrying one, from the desktop apps and CLIs already installed, and drops it. The column stays, empty, in chat.db: a node downgraded to an older version names it in every query. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): one account, several linked nodesChristophe Besson24 hours24-229/+649
| | | | | | | | | | | | | | | | | | | | | | | | | An account links up to MAX_NODES_PER_ACCOUNT node keys (node_keys table) instead of the one users.pk_node_ed25519; the migration carries each account's key over and the downgrade keeps the last one linked. /v1/nodes/auth accepts any linked key, and its token names the key that won it (nk). PUT /me/node_key adds a key, idempotently, 409 past the limit; DELETE /me/node_key names the key: a browser session any of its own, a node only its own. Unlinking a key closes that node's socket and refuses its tokens at once, on REST and on the socket; the account's other nodes are untouched. /pubkeys no longer gives a node key to other accounts, and gives the caller its last linked one, which installed desktop apps read. The node reports waiting_for_node_key while its key is unlinked and signs in again as soon as the hub says so, so a key linked again is back within seconds; unlinking itself signs in first to hold a token naming its key. Profile -> Linked nodes lists each node (connected, last seen, IP, linked since, This machine in the desktop app) with Copy and Unlink, and adds a key by hand. The Node page banner, Create group and the desktop app add this machine's key instead of replacing another's. Messages that pointed at Settings -> Link Node point at Profile -> Linked nodes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: a browser is not linked as a deviceChristophe Besson34 hours10-50/+50
| | | | | | | | | | A browser keeps no identity between connections, so a key approved for it is gone by the next one: the guide, the design and the interface no longer offer it. With browser access off, the application and the command-line client open the account's groups; the linking form, shown in the application only, now says "this computer". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(client): link a device from the application; approve one match onlyChristophe Besson35 hours3-12/+86
| | | | | | | | | | | | | | | "Get a linking code" called the connection the page had just closed on the node's unknown_device refusal, so it never filed anything. It now retries with the request filed on that connection, before the proof (MNP 6.3), and the page tries the group again after each refusal until a linked device approves the code. Offered only in the desktop application: a browser's minted identity is gone by its next connection. Approving refuses a code that matches more than one pending request: filing needs only the account's token, so somebody holding the passphrase who saw a code could file their own keys under it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): replace reCAPTCHA with a self-hosted proof of workChristophe Besson44 hours19-241/+233
| | | | | | | | | | | | | | | | | Sign-up and passphrase reset sent every visitor's address and browser details to Google, which needed a consent the sign-up form could not ask for freely. The hub now issues its own challenge (ALTCHA's scheme, signed with a key derived from the hub's private key, single-use, 30 minutes) and the page solves it with SubtleCrypto while the form is filled in. - GET /v1/users/captcha issues a challenge; 404 captcha_disabled when off. - [captcha] enabled / max_number replace site_key / secret_key, which are now ignored with a warning; MESHBAY_CAPTCHA_ENABLED replaces the old variables. - No Google origin left in the CSP of the hub, the desktop shell or the Android shell; captcha_site_key leaves /v1/hub/info. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(node): skip a file the disk stops answering for, then pause its rootChristophe Besson48 hours14-2/+150
| | | | | | | | | | | | | | | | | | | | | | | | | | A Windows tester's scan stuck for good on one directory of a large disk: one read that never returned held the indexer's only thread, and with it every root behind that one. - A disk call is timed by its silence, not its length: each directory entry listed and each megabyte read counts as an answer. After 60 s of silence the call is abandoned on its daemon thread, the file is noted unread and the scan goes on. - Three silent files in a row pause the root (not persisted). Resume skips the files already known to give no answer; a restart, a replug or a change to the file retries them. - The walk is per directory with os.scandir: a subdirectory that cannot be listed costs itself, and a listing that stops answering pauses the root at once. A scan no longer stats anything from the event loop. - The log names the file after 10 s of silence, the indexing band shows it, and Group settings shows the paused root, where it stuck and the unread files with their reason, on the node's machine only. - generate_http_api.py writes LF on Windows too. Verified with real kernel-blocked opens (an unacknowledged oplock) in the installed frozen app. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: keep the photo lightbox's close button on a phone screenChristophe Besson4 days2-8/+15
| | | | | | | | | 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 Besson4 days12-70/+79
| | | | | | | | | | | | | | | | | | | | | | | 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 Besson4 days17-24/+175
| | | | | | | | | | | | | | | 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 Besson4 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>
* feat(node): carry connections on several coresChristophe Besson5 days11-0/+30
| | | | | | | | 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 Besson5 days2-3/+120
| | | | | | | | | 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>
* fix(android): what the first backup on a phone showedChristophe Besson6 days17-17/+81
| | | | | | | | | 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 Besson6 days15-6/+288
| | | | | | | | 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 Besson6 days2-2/+25
| | | | | | | | 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 Besson6 days17-215/+552
| | | | | | | | 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 Besson6 days12-9/+133
| | | | | | | | 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 Besson6 days13-19/+118
| | | | | | | | 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 Besson6 days14-11/+49
| | | | | | | 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 text messages up, in a build Play does not getChristophe Besson6 days14-11/+51
| | | | | | | | 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 Besson6 days18-422/+535
| | | | | | | | 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 Besson6 days1-8/+49
| | | | | | | 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 Besson6 days16-12/+739
| | | | | | | | 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 Besson6 days15-10/+57
| | | | | | | 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(android): back photos up under YYYY/YYYY-MM, not YYYY/MMChristophe Besson6 days1-1/+1
| | | | | | A month folder named 08 alone read like an album number. 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 Besson6 days12-2/+190
| | | | | | | | | | | | | | | | | | | | | | 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 Besson6 days11-6/+23
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 Besson7 days16-0/+1092
| | | | | | | | | | | | | | | | | | | | | | | | | 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 Besson7 days13-2/+19
| | | | | | | | | | | | 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 Besson7 days1-0/+29
| | | | | | | | | | | | | | 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 Besson7 days14-3/+60
| | | | | | | | 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 Besson7 days21-3/+783
| | | | | | | | | | | | | | | | | | | | 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>
* fix(hub): re-read the roster before saying a key changedChristophe Besson8 days3-20/+62
| | | | | | | | 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>
* chore: bump version to 0.19.0Christophe Besson8 days2-2/+2
| | | | Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: set the Windows node up at sign-in, and stop it for realChristophe Besson8 days13-12/+61
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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): name members admitted without an invitation nameChristophe Besson8 days2-2/+10
| | | | | | | | | | | 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>
* fix(hub): keep "add a directory" when one node check is missedChristophe Besson9 days1-13/+38
| | | | | | | | 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>
* fix(hub): let a download wait out a reconnect instead of failingChristophe Besson9 days2-7/+40
| | | | | | | | Chunks sent while the reconnect's connect() runs throw at once, and six retries 1.5 s apart ran out before the reconnect landed. Wait for it, up to two minutes, without spending retries. Follow-ups parked in §15.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): move "clear finished" into the finished group's headChristophe Besson9 days2-14/+24
| | | | | | | | 9269374 let the transfers header wrap, which broke the one-line header test_layout_measured enforces. The button now sits beside what it clears, and the header is title and summary only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): stop the transfers panel scrolling sidewaysChristophe Besson9 days1-1/+7
| | | | | | | The header (title, summary, clear button) overflowed the 330 px panel in French. Let it wrap, with a gap, and hide horizontal overflow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(hub): play video on iPhone through ManagedMediaSourceChristophe Besson9 days11-2/+24
| | | | | | | An iPhone has no MediaSource; every film was refused as an unsupported codec. A browser with neither now says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat(hub): say "N groups unreachable" on Search for a few seconds, not for good0.18Christophe Besson11 days4-33/+41
| | | | | | | | | | | | The line sat above the results for as long as the page was open. It is now said once a cross-group pass is over, for five seconds, in the same passing note as "Link copied" — moved out of copy-link.js into note.js (`say(text, ms)`), one note at a time for the whole page. The `.search-unreachable` rule goes with the line it styled. The copy-link probe now also checks in Chrome that the note goes by itself. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: copy a file's or folder's #/name@owner link from Files, Music, Photos ↵Christophe Besson11 days19-20/+198
| | | | | | | | | | | | | | | | | | | | | | | and Search "Copy link" puts the address group-link.js resolves on the clipboard, on the hub's origin rather than the page's, so a link copied in the desktop application is not app://meshbay. Files offers it for one row, from the right-click menu or the toolbar with one row ticked (a phone's way in); Music on one track's menu, whose dots a phone has; Photos on a right-clicked tile and in the lightbox's bar. The video player and the file preview carry a link button next to Download. Applications get a `linkFor(entry | folderPath)` prop (MESHBAY_DESIGN.md §9.2) and offer the action only when it names a link. The group page builds it from the hub's row; Search from each result's own group and its path before the merged views prefixed it, and names no link for a folder of the merged tree, which a group name alone does not identify. harness/copy_link_probe.py mounts the three applications in Chrome and reads what reached the clipboard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* feat: open a group, a folder or a file from a #/name@owner linkChristophe Besson11 days15-11/+202
| | | | | | | | | | | | | | | | | | | A group can now be reached by the handle shown under its name, and a path after it points inside the group: #/name@owner/root/dir/file downloads the file and opens Files on its folder; a folder opens Files there. The handle is resolved in the client against the account's own /v1/groups/mine, so no hub route answers for a name and nobody can probe for one. While a group is open the address shows the handle (replace, no history entry); a linked path is taken out of the address once acted on, so a reload does not download twice. Signing in no longer sends everyone home: the form stood in for the page the address named, and that is where a link opened signed out was going. group-link.js holds the parsing and lookups, executed whole by test_group_link.py; harness/group_link_probe.py drives the router in Chrome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix: list as members only the accounts the node has admittedChristophe Besson11 days12-4/+53
| | | | | | | | | | | | | | | | The Members list showed the hub's membership, which an account gains when it accepts the invitation or redeems a link, before it has presented its code to the node. The node's roster is the authority (MESHBAY_DESIGN.md §3.4), so the list now crosses the hub's members with the sealed group roster the node already sends every connected member. An account the node has not admitted yet is shown to the owner alone, as waiting for its code, with the Remove button; other members do not see it. When the roster cannot be read, the hub's list is shown as before. groupRoster() takes { fresh: true } so the page sees who joined since the connection opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>