Adds LocalSend-compatible text messaging alongside file transfer.
Wire format (matches the LocalSend app): a message is a single "file" with
fileType "text/plain" whose content rides in the prepare-upload `preview`
field. The receiver returns an empty token set, so nothing is uploaded — the
text is read straight from the preview.
- client: SendMessage builds that single-file prepare-upload (no body upload).
- server: detect a message (one text file with non-empty preview), surface it
on a new Messages() channel instead of saving a file, and respond with an
empty file set. Messages bypass the accept prompt (auto-received); the PIN
gate still applies.
- app: bridge the server's messages channel to the TUI as MessageMsg.
- tui: a new Messages tab lists received messages (enter to read full, d to
delete); press `m` on a device to compose and send one. Incoming messages
show a footer notice.
Tests: end-to-end send→receive (text intact, sender preserved, nothing written
to disk) and unit coverage of the message-detection rule.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sender only handled regular files: a staged directory would fail at
upload time, so users could only send individual files.
Now a staged directory is walked recursively and each file is advertised
with a name relative to the folder's parent (e.g. "Trip/day1/img.jpg"),
which is the LocalSend-compatible way to carry structure. The receiver
recreates those subdirectories under the receive dir, creating parents as
needed. The path-traversal guard is preserved: names are cleaned against a
leading "/" to collapse "..", and a containment check ensures the result
stays within the receive dir.
In the TUI send picker, "a" stages the folder currently being browsed;
staged folders are tagged in the panel and the help text is updated.
Tests: directory expansion produces relative names; an end-to-end folder
send recreates the structure on the receiver; destPath preserves subdirs,
rejects traversal, and de-duplicates within subfolders.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Previously the advertised device name defaulted to the machine hostname.
Since the alias is broadcast in plaintext over multicast to the whole
subnet every few seconds while the TUI is open, that leaks the hostname
to anyone on the network — a privacy problem on a laptop joining
untrusted Wi-Fi (the reason LocalSend randomises its aliases).
Now first run generates a random "Colour Object" name from public-domain
word lists (e.g. "Crimson Quasar"): 56 colours x 44 celestial objects =
2464 combinations. It is generated once and persisted, so a device keeps
the same name across restarts. The hostname is still carried in
DeviceModel, and the alias remains overridable via --alias or Settings.
Word lists are original and use only generic colours and public-domain
astronomy terms, so there is no copyright or trademark exposure.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>