- map the new VnidropError cases (DeviceUnavailable, OfferTimeout,
RelayPolicyIncompatible, ProtocolIncompatible) to existing catalog keys
- drop the stale .map(\.share) now that sharePickedFiles returns Share
- remove the duplicate .transfersChanged pattern in the signal switch
- replace the deprecated String(cString:) sysctl decode
- close the CoreGateway protocol declaration
Run Secret Service IO on spawn_blocking so Linux zbus cannot nest Tokio
runtimes during init, and wait for core initialize before experimental
saved-device coordinators refresh.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use flock for profile locks, hash secret record filenames under NAME_MAX,
and dismiss the starting overlay after the first init attempt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Stop returning grant strings across UniFFI; approve yields typed outcomes and
pull/resume use transfer id plus path or ReceiveOutputSink. Document the
pairing/targeted event catalog and cover Android MediaStore-style sink contracts.
Co-authored-by: Cursor <cursoragent@cursor.com>
Peel relationships, eligibility, and secrets off the shared pool into
AppDataStores adapters, split pairing service/protocol, and move the
invitation Repository into its own module so open_all owns schemas.
Co-authored-by: Cursor <cursoragent@cursor.com>
Introduce persistence::open_all so domain stores (invitation, targeted,
blocked) are constructed once. Extract TargetedTransferStore and own it on
CoreInner; soft-close raw pool access for new callers.
Co-authored-by: Cursor <cursoragent@cursor.com>
Prove the DPAPI-backed Windows bridge can drive the full public saved-device
and targeted-transfer contract via an injectable API fake on non-Windows hosts
and real DPAPI under cfg(windows), without product UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
Prove Keystore-backed Android secret storage can drive the full public
saved-device and targeted-transfer contract without product UI.
Co-authored-by: Cursor <cursoragent@cursor.com>
Platform contract harnesses need monotonic event revisions for at-least-once
dedup and a typed rename API that survives listing.
Co-authored-by: Cursor <cursoragent@cursor.com>
Preserve approved transfer auth and state across restart, resume without
re-approval, and keep cancel/delete from leaving usable orphan authorization.
Co-authored-by: Cursor <cursoragent@cursor.com>
Targeted offers and pairing now validate relay-policy compatibility and
reject protocol downgrades with typed unavailable/timeout/incompatibility
errors, without falling back to ordinary shares.
Co-authored-by: Cursor <cursoragent@cursor.com>
Cancel targeted protocol shares synchronously before await, and treat block
lookup errors as denied so store failures cannot admit blocked peers.
Co-authored-by: Cursor <cursoragent@cursor.com>
Combine ticket 09 lifecycle APIs with ticket 10 targeted-transfer protocol
and wire forget/block to real cancel_targeted_transfers_for_peer.
Co-authored-by: Cursor <cursoragent@cursor.com>
Give saved-device owners immediate local control over grants and identity-wide
denies, with minimal tombstones for replay rejection and a ticket-10 hook for
targeted-transfer cancellation.
Co-authored-by: Cursor <cursoragent@cursor.com>
Establish PendingOutgoing/PendingIncoming relationships over a token-bound
pairing protocol, exchange directional grants with challenge-response proofs
and a final ack before Saved, and merge simultaneous initiations without a
second prompt.
Co-authored-by: Cursor <cursoragent@cursor.com>
Add the experimental eligibility control plane so either endpoint of a
fully completed authenticated invitation transfer can start one single-use
pairing attempt within 24 hours, with secrets held in custody and invalid
requests rejected silently.
Co-authored-by: Cursor <cursoragent@cursor.com>
The fingerprint row reused the no-name placeholder as its label, so it read
"A nearby device — aaab1c58", and it duplicated the device id shown
directly beneath it. Now one selectable full id, which is what someone
comparing devices actually needs.
Two causes. The connect step had no timeout, so an unreachable device was
retried indefinitely instead of falling through to hold-for-later; it now
gives up after 15s and holds the offer as designed.
The picker also waited on the whole exchange, which includes a person on the
other device deciding — up to two minutes. It now closes on tap and reports
the outcome as a message, and a decline or an unanswered offer is shown as
information rather than an error, since the offer did arrive.
The Settings stack has a typed path of [SettingsSection], so a
NavigationLink carrying a String could never push onto it: tapping a device
in the list did nothing. Contact detail is now a SettingsSection case, and
the path maps it to the two-level push the way the bug report screen
already does.
Send to a device now sits alongside the QR code, NFC, and export actions,
since an offer is another way to deliver the same invitation. Picking a
device pushes the existing transfer rather than re-sharing the files.
The picker lists only devices holding a live grant, so nothing offered there
can fail on tap, and it distinguishes accepted from waiting for that device
to open the app.
Also fixes the deprecated SF Symbol and the two Sendable warnings introduced
with the contacts screen: the sections now talk to the model directly rather
than storing view callbacks that a Binding setter has to convert.
Another way to deliver an invitation the user already created, alongside the
QR code, rather than a second share of the same files: the ticket handed over
is the stored one and the transfer id is unchanged.
Only an active share can be offered. A stopped one no longer serves its
content, so handing out its ticket would promise nothing.
Adds the opt-in foreground check and an explicit Check now, the waiting-to-
be-delivered list on the sender side, and honest reporting when a send could
not be delivered: a closed app is a delay, not a success nobody received.
The setting is off by default and its footer states that checking reveals
app-open times to remembered devices, since that is the reason it is a
setting at all.
Records in the design doc that this shipped as one global toggle rather than
the per-contact opt-in originally specified.