docs/v10-release-plan.md
Companion to docs/v10-release-test-plan.md (what to test) and
docs/post-v10-maintenance.md (what's deliberately deferred). This doc holds the draft
release notes and the ship checklist.
Branch: shared-network, both repos. 74 commits, ~11,900 insertions over main.
Versions already bumped: Rust core 10.0.0, Tauri app 10.0.0, Android versionName 10.0.0
/ versionCode 22. Apple repo versions still need checking (see checklist).
Flying Carpet 10 adds Shared Network mode and rebuilds the encryption on the Noise Protocol Framework.
⚠️ Version 10 is a breaking change. v10 devices cannot transfer with v9 or earlier — you'll get a clear version-mismatch message instead of a hang. Update every device you transfer between.
Until now Flying Carpet always created its own ad-hoc WiFi hotspot. You can now transfer over a WiFi or wired network that both devices are already on — useful at home or in an office, and much faster to start when there's a network handy.
Every transfer, in both modes, now runs a
Noise_NNpsk0_25519_ChaChaPoly_SHA256 handshake (X25519 + ChaCha20-Poly1305 + SHA-256), with
the pre-shared key derived from your password by PBKDF2-HMAC-SHA256 at 600,000 iterations.
docs/shared-network-crypto.md. All three implementations (Rust,
Kotlin, Swift) are held together by shared known-answer test vectors.Hotspot mode keeps WPA2 underneath, so it's now encrypted twice over.
Hotspot transfers between a Mac and a Linux machine previously required manually pairing the two in System Settings first, and often failed afterward with "Peer removed pairing information".
Root cause: macOS advertises with a public address and dual-mode flags, and BlueZ's bearer tiebreak prefers classic BR/EDR on a tie — so Linux was connecting over classic Bluetooth, which macOS serves no GATT over. Linux now bonds over an LE socket first, which pins the bearer to LE permanently, and keeps the bond for macOS peers so their rotating address stays resolvable. Pairing also surfaces the 6-digit code in the app for confirmation (real MITM protection), and declining now aborts the transfer cleanly instead of hanging.
Desktop (Windows/Linux)
netsh with literal quotes around it, so it
never matched and the rule was re-added every time. Both rules are also added under a
single elevated command now, so the one-time prompt is one prompt, not two.0x8000FFFF) against
already-paired iPhones — retries, then re-pairs once within the same transfer, rather than
failing the run. Advertising is now explicitly stopped after the credential exchange.flyingCarpet_* NetworkManager connections are pruned at startup and the
hotspot is torn down on window close (#51).Android
FlyingCarpet, so a full transfer log can finally be pulled with
adb logcat -s FlyingCarpet for bug reports (#130).Security hardening
is_local_url() misclassifying remote URLs as trusted local origins on Windows/Android,
allowing a remote page to invoke local-only IPC commands). Also CVE-2026-25727 (time),
CVE-2026-25541 (bytes), and fixes in serde_with and rand.Send Folder is consistent everywhere
Sending a folder now recreates that folder inside the destination the receiving device chose, with the contents inside, on all five platforms. Previously only macOS did this — everywhere else the folder's contents were dumped loose into the destination. Sending individual files is unchanged: they still arrive flat.
This also fixes sending a folder that contains sub-folders from Android to Windows or Linux, which used to fail outright, and two cases where the desktop app aborted a transfer: selecting a folder whose top level holds only sub-folders, and dropping two folders (or files from two different directories) at once.
The in-app instructions were also wrong about how to send a folder — they told you to drag it onto the window, which Android and macOS have no handler for, and which is not something a screen reader user can do (#122). Each platform's help text now describes the control it actually has, and says what arrives on the other end.
Nine open issues are resolved or materially addressed by this branch, plus three closed feature requests that v10 actually delivers. Only four (#51, #101, #115, #118) are cited in commit messages — the rest were matched by reading the issue against the code, so the confidence column matters.
| # | Title | Fixed by | Confidence |
|---|---|---|---|
| #101 | Bluetooth Button is Greyed Out | a85d2c4 | Fixed |
| #118 | Windows receiver freezes | a85d2c4 | Fixed |
| #124 | Support wired connection on one device | d4883ea | Fixed |
| #129 | Firewall UAC prompt every time | 25050cb, 04da20b | Fixed |
| #131 | Android to iOS not working | shared network mode | Fixed (you already told the thread this was the plan) |
| #106 | Bluer: GATT services not resolved | 967ed6b | Likely — ask reporter to retest |
| #51 | Does not clean up after itself (Debian) | a85d2c4 | Partial — no uninstall purge |
| #115 | Failed to start WiFi Direct AP | a85d2c4 + shared network | Worked around, not fixed |
| #130 | WiFi drops midway on large transfers | shared network, 478ee4d | Partial + diagnosis unblocked |
Closed feature requests v10 delivers — worth a courtesy follow-up, since the requesters never got an answer: #93 (detect same network, skip the hotspot), #61 (static network option), #122 (folder-select button for screen reader users).
#129 was a real bug, not a design choice. check_for_firewall_rule built its query as
format!("name=\"{}\"", file_name) and passed it to process::Command as one argument, so
netsh searched for a rule whose name literally contained quote characters. It never
matched, the app concluded the rule was missing, and re-added it — raising UAC on every
single transfer. Fixed in 25050cb; 04da20b then collapsed the two netsh invocations
into one elevated cmd.exe so the first run costs one prompt rather than two.
#106's log identifies its own cause. The device it connected to advertises service UUIDs
1101/110b/110c/110d/110e/111e — Serial Port, Audio Sink, AV Remote Control,
Handsfree. That's a classic Bluetooth audio device, not the reporter's iPhone. 967ed6b
records that bluer's discover_devices() pre-seeds results with every device BlueZ already
knows, ahead of the discovery filter, so a previously-paired peripheral could be returned as
the peer. The same commit purges those cached entries, scans LE-only, and retries/rescans on
enumeration failure.
#115 is not fixed and shouldn't be closed as such. "Failed to start WiFi Direct AP" is a card/driver limitation. v10 only offers a route that avoids it.
docs/shared-network-crypto.md §7). Worth answering kindly and explaining why, rather than
leaving it open indefinitely.Copy-paste ready. Post after the release is live so the download links work. Where a response asks the reporter a question, that's deliberate — several of these are worth confirming before closing.
Should be fixed in v10.
The transfer task could panic or be aborted without ever re-enabling the UI, which left the window looking frozen — the app was still running, but every control stayed disabled. v10 emits the re-enable from a drop guard so the UI recovers even when the transfer task dies unexpectedly, and the setup paths that used to panic now print an error instead.
One thing that would help me confirm: when it freezes, is the window unresponsive to clicks entirely, or are the controls just greyed out and unclickable? Those are two different problems and it's the second one I've fixed. If it's the first, and especially in the "kept open for a long time" case rather than "after completing a transfer", I'd like to keep this open.
Fixed in v10.
The app was treating "Bluetooth permissions haven't been granted yet" and "this device has no Bluetooth hardware" as the same state, and showed "Device can't use Bluetooth" for both. That's also why the switch came to life after you tapped "Select file" — that's the point where the permission request actually fired.
v10 tells the two apart, keeps the switch tappable so it can re-request the permission, and re-checks when the app returns to the foreground, so granting it in Settings now takes effect without restarting the app.
Fixed in v10 — and it was a bug, not a design decision. The rule was only ever meant to be added once.
The check for "do I already have a firewall rule?" passed the rule name to
netshwith literal quote characters around it, sonetshwent looking for a rule whose name actually contained quotes. It never matched, so the app concluded the rule was missing and re-added it — every transfer, hence the prompt every transfer. v10 passes the name unquoted and finds the existing rule.One caveat: v10 adds a second (UDP) rule for shared-network discovery, so the first run after upgrading will prompt once to add it. That's a single prompt covering both rules now rather than one each. After that it should be silent.
This works in v10.
Shared Network mode transfers over a network both devices are already on, and it supports wired interfaces explicitly — so your exact setup (Windows on ethernet, Android on WiFi, same network) is supported. The interface picker lists wired adapters alongside wireless ones, labelled with their IP.
The "not connected to WiFi" error you hit came from hotspot mode needing a WiFi card to host the hotspot with. In shared network mode nothing is hosted, so that requirement is gone.
v10 is the fix for this, and it's close to release.
As I mentioned above, the iOS WiFi side was broken and the fix is the shared network mode I've been building. Join both devices to the same WiFi network and start the transfer: the receiving device shows a password (with a QR code) that you enter on the sender. No hotspot, no Bluetooth, and the two devices' operating systems no longer need to be selected.
@B5-SA this should cover Linux Mint ↔ iOS too. v10 separately fixes Linux failing to enumerate GATT services over Bluetooth, which is likely what you hit on the pairing side — see #106.
I think v10 fixes this, and your log is what convinced me — thank you for pasting the whole thing.
Look at the device it picked up: service UUIDs
1101,110b,110c,110d,110e,111e— Serial Port, Audio Sink, A/V Remote Control, Handsfree. That's a classic Bluetooth audio device, not your iPhone. bluer'sdiscover_devices()pre-seeds its results with every device BlueZ already knows about, before the discovery filter applies, so a previously-paired peripheral could come back as the "peer" — and naturally it has no Flying Carpet GATT service on it.v10 scans LE-only, purges unpaired cached BlueZ entries carrying our service UUID before discovery starts, retries service enumeration, and removes + rescans once if it still fails. Would you be willing to retest once v10 is out?
Partly addressed in v10, and I'd rather be straight about which part.
Fixed: Flying Carpet no longer accumulates
flyingCarpet_*connections as you use it. v10 prunes stale ones at startup and tears the hotspot down when the window closes, so a crashed or force-quit transfer doesn't leave one behind indefinitely.Not fixed: there's still no uninstall-time purge. The AppImage has no uninstall hook to attach one to, and the
.debwould need apostrmscript.Before I add a
--purgeflag people would have to know exists: would the startup pruning have been enough for your case, or were the leftovers specifically a problem after you'd uninstalled? If it's the latter I'll do thepostrmscript for the.debat least.
v10 gives you a way around this, though I want to be clear it isn't a fix for the underlying problem.
"Failed to start WiFi Direct AP" means the WiFi card or its driver won't host a software access point. That's a hardware/driver limitation Flying Carpet can't work around, which is why the message says what it says.
What v10 adds is Shared Network mode: if both devices are already on the same WiFi or wired network, no hotspot is created and the WiFi Direct path is never used. The error message now points there too. Worth a try when v10 lands.
Two things in v10 for this.
The direct one is Shared Network mode: if both devices are already on the same network, no hotspot is created, so there's no ad-hoc WiFi connection to drop midway. For multi-GB transfers from Android that's the path I'd recommend.
The other is the thing you actually asked for — "I can't find any way to log/share error out of the box as an end user." Fair, and fixed. Every line the Android app prints now also goes to logcat under the tag
FlyingCarpet, so you can pull a full transfer log withadb logcat -s FlyingCarpet. The log also survives screen rotation now; it could previously be wiped mid-transfer.v10 also fixes an intermittent Bluetooth credential-exchange race on Android, which may well be the "hit and miss" half of what you were seeing.
Following up a year and a half later: this is shipping in v10, more or less exactly as you described it. If both devices are on the same network there's no hotspot at all — and yes, that makes transfers to desktops without WiFi cards work, since wired interfaces are supported.
Following up: v10's shared network mode should remove the need for this. Instead of a fixed SSID and password per peer, if both devices are already on a network there's no hotspot to name — the receiving device shows a one-time password (with a QR code) and that's the only thing to type. The long SSID + password entry for Mac ↔ Android goes away entirely.
Following up, because v10 improves this and you deserved a better answer at the time.
There's a "Send Folder" checkbox now, so sending a folder no longer requires drag-and-drop. More to the point, the instructions text still said "To send a folder, drag it onto the window" — which was exactly the wrong thing to tell a screen reader user, and it stayed that way far too long. It now describes the checkbox instead.
v10 also makes a sent folder arrive as a folder on the receiving device on every platform. Previously most platforms scattered the contents loose into the destination folder, which I imagine was its own kind of unpleasant to sort out without sight.
docs/send-folder-behavior.md. Still needs the Tier 6 hardware rows run.generatePassword() returns 10
(shared/Transfer.swift), iOS prompt requires 10, macOS uses minLength: 10 for shared
network and 8 for hotspot join (correct — an Android host's WPA2 passphrase isn't ours
to size).shared/Transfer.swift and the iOS storyboard changed and
have not been compiled here (no Mac).DiscoveryTests, Apple commit 8d73710) was written on Windows 2026-07-25 and
has never been compiled. Tier 0's "Apple unit tests pass" checkmark predates it.10).docs/v10-release-test-plan.md — Tier 2 is green; Tiers 3–6 and the release
gate remain (Tier 3 lacks only wrong-password-over-hotspot).CFBundleShortVersionString to 10.0MARKETING_VERSION = 10.0.0 (verified 2026-07-25 and re-checked).
CURRENT_PROJECT_VERSION = 1 is fine for a new marketing version unless a 10.0.0
build was already uploaded to App Store Connect — bump it only in that case.versionCode 22 is greater than what's live on Play and F-Droid.tauri.conf.json's stale icons/icon.icns entry stays (per CLAUDE.md it
is intentionally left alone — confirm, don't silently "fix").Every screenshot in screenshots/ predates the mode switch, the interface dropdown, and the
password-box removal.
screenshots/windows.png — retake showing the Shared Network / Hotspot mode switchscreenshots/linux.png — retake; show the interface dropdown with IP labelsscreenshots/mac.png — retake with the current segmented-control layoutscreenshots/android.png — retake (mode switch + Send Folder checkbox)screenshots/ios.png — retake, ideally showing shared network modeget_key_and_ssid's callers all discard the key half, so SHA-256 of
the password really is SSID-only.ARCHITECTURE.md is accurate for the final state of the branch.msi installer + standalone FlyingCarpet.exe, both signed.AppImage + .deb.dmg, signed and notarized; verify Gatekeeper on a clean machinebrew install flying-carpet)shared-network → main in both repos, close together (they must stay
wire-compatible)v10.0.0 in both reposdocs/post-v10-maintenance.md — the windows crate 0.44 pin firstglib 0.18.5 advisory at the next Tauri upgrade (Linux/GTK only)