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.
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.
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.
Contacts list and detail, pairing consent prompts, incoming offer prompt,
and the grant lifetime setting, in all nine supported languages. Added to
strings.json and regenerated; targeted at both platforms so the Compose
resources exist when the KMP side is built.
Delete the TelemetryRecorder, CrashReporter, PendingCrashStore and platform
crash hooks along with their models, JSON encoders and the diagnostics opt-in
preference. The DiagnosticsTransport interface is narrowed to sendBugReport, and
DiagnosticsCoordinator now only wires the bug-report service and install id.
Bug reporting, the breadcrumb buffer, log redaction and the diagnostics endpoint
config are kept. Regenerate localization after dropping the diagnostics_* keys.
The merge kept this branch's reworded notifications_description and
storage_delete_transfers_description over master's, but the merged KMP code is
master's, so its FoundationComposeTest assertions (and the shipped KMP copy) expect
master's wording. Restore both to master's committed text (pulling the storage one
from master's XML, since master's own strings.json was stale for it). Verified the
two failing KMP Compose tests pass locally.
Another key master referenced from Kotlin but kept only in the generated Compose
XML, so regeneration dropped it. Verified exhaustively this time: every
Res.string.* reference in shared/src/commonMain/kotlin now resolves against the
regenerated values/strings.xml, so no further keys are missing.
Master referenced storage_clear_transfer_cache(_description) and
storage_transfer_cache_cleared from Kotlin but never added them to strings.json —
they lived only in the committed Compose XML. Regenerating l10n from the merged
strings.json dropped them, breaking the shared-kmp build. Add them (kmp target,
all 9 languages, text carried over from master) so generation restores them.
Brings in custom relays, relay connection policies, storage cache clearing, and
receiver-failure reporting. Apple-side conflict resolutions:
- CoreRepository: keep CoreDispatcher, adopt master's relay factory + network
transition guard, drop the now-unused serial queue.
- TransferDetailsView: keep the toolbar-share layout; adopt master's
invitationPresentation-based QR panel and the new .failed receiver case (typed).
- SettingsModel/SettingsScreen: typed L10n titleKey with master's .network case;
relay controls and the Free up space / storage redesign coexist.
- Add the missing transfer_receiver_failed localization key.
- Regenerate l10n from the merged strings.json; keep the Apple catalog untracked.
- Drop the notificationsEnabled test assertion (notifications preference was
intentionally removed on this branch).
The core initializes asynchronously at launch, so for a moment the transfer lists
look empty and the app feels stalled. Show a full-window overlay (centered spinner
+ "Starting…") as the top layer of the root stack while coreState.isInitialized is
false; it fades out once the core is ready.
Redesign the Storage screen for clarity: an "On this device" usage header with a
manual Refresh control, symbol-led action buttons, and a caption under each action
spelling out exactly what it does (Free up space = temp + trash, non-destructive;
Delete all transfers = clears history + cached share content, keeps received
files).
Fix the summary sticking on "Calculating…": it loaded only on .onAppear and bailed
when opened before the core finished its async launch, leaving the loading branch
showing with nothing running (only a manual refresh recovered it). loadStorageUsage
now waits for the core to become ready before reading usage, distinguishes a real
failure (retry) from loading, loads via .task, and can be refreshed on demand. Use
plain button styling with explicit tints so pressing an action no longer flips the
label to the white selection highlight.
Delete all transfers only clears core records, and the blob-store cache is
reclaimed by the core's own timer. Neither touches the app's temporary directory
(leftover picker/staging copies — hundreds of MB on macOS) or the stray .Trash
folders that accumulate in app-owned directories and can't be removed via
Files/Finder. Add a non-destructive Free up space button that empties the temp
directory and removes .Trash folders under the core data dir (and, on iOS, the
fixed Documents receive folder), reporting the bytes reclaimed. Guarded against
running while a transfer is in flight; never touches received files, the core
database, or user-chosen macOS receive folders.
Adds background notifications for the "the thing you were waiting for is
done" moments, alongside the existing incoming-approval-request one:
- a receive finished downloading (receive -> done)
- a receive failed / was interrupted (receive -> failed)
- a share you own failed (send -> failed)
- a receiver finished downloading your share (receiver status completed)
A new TransferNotificationCoordinator observes core state + signals and
publishes these; the decision of which moments notify is a pure function
(plannedTransferNotifications / plannedReceiverNotifications), unit-tested
independently. The first state snapshot only primes existing history as seen
so launch doesn't spam.
Notification permission is now the single source of truth. The in-app
notifications toggle and its decoupled UserDefaults preference are gone;
the Settings section shows an "Allow notifications" button that requests the
OS permission (or deep-links to Settings once decided), and notifications
gate purely on `permission == .granted`.
macOS delivery fixes:
- add a UNUserNotificationCenterDelegate so banners present even while the
app is active (the app window is usually open on macOS)
- present-when-active on macOS, suppress-when-foregrounded on iOS
- reserve the notification id before awaiting publish: the CombineLatest
fired several times and re-added the same identifier, which macOS
coalesces into a silent update with no banner
- LocalNotificationService seeds its permission at init so gating can't
race a not-yet-refreshed .notDetermined
Eight localized title/body strings added (apple-only); the shared
notifications_description copy is generalized from "receive requests" to
"transfer activity".
Add strict custom Iroh relay profiles with safe restart and rollback across the Rust core, Compose apps, and Apple apps. Preserve multi-relay invitations and fail closed on configuration or recovery mismatches.
Replace "раздача"/"раздаваться" (literally "seeding") with neutral
"общий доступ"/"отправка" phrasing across the Russian strings, to avoid
BitTorrent connotations for App Store review.
Translate all 253 user-facing strings to Russian (professional UI register;
count strings use number-neutral "label: {count}" form). transfer_file_count
uses full CLDR plural categories (one/few/many/other). Generates
values-ru/strings.xml for KMP, ru localizations in the Apple catalog, and adds
ru to CFBundleLocalizations. Completes the initial 8-language set.