R1-1 New chat clears its online results on a profile switch, as the chat list does
R1-3 the search spinner follows its two flags through property observers
R1-4 the join verb keeps one line, so the name truncates instead
Run: 002-ios-directory-search-ui, review round 1
The New chat sheet gets the chat list's online search: a "Search in
Directory" row, the keyboard Search key for names and the directory,
and Names and Directory sections after the contacts. Tapping a result
connects and closes the sheet, as other New chat actions do. A contact
the list already shows is not repeated as a name result, and the
contacts list's empty state stays hidden while a search runs or shows
results. Archived contacts, which share the search bar and the list,
are unchanged.
Run: 002-ios-directory-search-ui, slice 4
The keyboard Search key on the chat list now also resolves a typed
SimpleX name online, alongside the directory search, and shows what it
finds as a Names part between the local chats and the directory
results. Resolution never raises an alert: apiConnectPlan gains an
onFailure mode, and existing callers keep alerting by default.
Each directory entry is looked up in the store (store-only, as entries
are now group or channel links only) so a chat the user already sees
locally, or found by name, is not shown again; names already in the
local list are hidden the same way. A directory row is matched to a
name only by the link the core resolved, never by a profile field the
name's owner writes, so a name cannot hide a genuine directory row.
The "Search in Directory" row, retry and "Show more" keep the names
already shown. One helper drives the spinner from whether anything is
in flight, and every publish checks the search generation, so a late
reply after a text change or cancel writes nothing. The first-search
alert now also names the server that resolves names.
Run: 002-ios-directory-search-ui, slice 3
A directory result is untrusted input: an entry is kept only when its
link is a group or channel link, because the core fetches a one-time
invitation link even when asked for a store-only plan, and the next
slice looks every entry up in the store. The entry's link is no longer
optional, so the tap needs no unwrap and the id no fallback.
The Directory part now renders only when it has a row: no header while
loading, no "No results" row; the empty-state overlay shows when the
search finished with nothing. Its header is a row rather than a sticky
section header, which in the flipped one-hand list would pin at an
unverified position. Rows say "Join channel" or "Join group". A pasted
link in one-hand mode leaves the tags in place.
Run: 002-ios-directory-search-ui, slice 2
The search model is about to hold name results beside directory
results, so it becomes OnlineSearchModel. The "Show more" and retry
rows, the first-search consent check and the contacts list's shown-chat
derivation move to where the New chat sheet can reuse them. No
behaviour change.
Run: 002-ios-directory-search-ui, slice 1
* rfc: invite member of one group to another group
* update
* update
* implement
* version
* update
* do not show error if contact was removed when joining group
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* ios: delete temp data from storage view
The temp folder is the XFTP agent work directory and holds scratch
files for received-file encryption and roster transfers, so deleting it
under a running chat breaks those writes mid-flight. The button is only
enabled while chat is stopped, the same precondition core deleteStorage
relies on, and the row says how to stop chat.
* ios: show delete temp data alert over settings sheet
AlertManager presents from ContentView, which is behind the settings
sheet, so the confirmation never appeared. The alert now also warns
that interrupted transfers never complete and that videos in unsent
drafts, which live in the temp folder, are lost, and the caption names
the screen where chat is stopped.
* ios: say unsent draft videos are sent without the file
A draft video whose converted file was deleted from the temp folder is
still sent, as a preview with no file, so "lost" understated it.
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* core, ui: remove database backups after key changes
* ui: delete database backups on chat start without delaying encryption marker reset
Keeping encryptionStartedAt set until chat start made DatabaseErrorView show
"The attempt to change database passphrase was not completed" with a Restore
button after every successful passphrase change when the passphrase is not
stored. Restore the marker handling from master and delete backups
unconditionally after chat starts.
* another flag
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
Co-authored-by: Narasimha-sc <166327228+Narasimha-sc@users.noreply.github.com>
- core: reject service payloads nested deeper than 32 levels, so a hostile service cannot crash the app's JSON decoder
- core: one parseServiceBody (decompress, decode, depth check) at all three decode points: incoming requests, sendServiceRequestTo, redeemBadgeCode
- directory: '%' and '_' in search text match literally instead of acting as LIKE wildcards
- directory: search text over 100 characters is rejected over RPC
- directory: a new directory creates its address with DR keys, so service requests work without a manual key rotation
- directory: service requests are always processed; the --service-requests switch is no longer needed for the directory
- directory: the in-flight service request cap is a DirectoryOpts field (default 8) instead of a hardcoded constant
- directory: the page-fit check compresses the exact response object that is sent, not a proxy with a different key order and cursor
- directory: search entries omit the full link when a short link exists, saving hundreds of bytes per entry in the envelope
- ios: add DirectorySearch.swift and DirectorySearchView.swift to the Xcode project; the app did not build without them
- ios: in the onboarding state the search bar is at the bottom in one-hand mode, as in the chat list
- ios: in the onboarding state a failed search shows the retry row instead of returning to the cards
- ios: a search with no results shows a "No results" row
- ios: the "No chats found" overlay no longer covers directory results, the retry row or the no-results row
- ios: directory rows are rendered by one builder for both the onboarding and chat-list branches
- ios: retrying after a failed "Show more" resumes from the cursor instead of restarting the search
- ios: cancelling the spinner, or tapping a result mid-search, drops the pending request without clearing results already shown
- ios: typing during a search stops the spinner, and a stale response no longer clears a newer search's spinner
- ios: repeated Search taps for the same text while a search is in flight are ignored
- ios: channel results show the subscriber count and channel icon; group results show the group icon
- ios: remove the unused searchText parameter of SearchInDirectoryRow
- android: a directory search timeout no longer pops the retry alert (the UI will show a retry row)
- tests: RPC paging by the echoed cursor, literal wildcards and the length bound, rejection over the cap, page fitting to the envelope, nested payload rejection
- redeem errors are typed (CEBadgeRedeemError) instead of matched by text in the apps
- service timeout (A_SERVICE) decodes in the apps and offers Retry via the existing retry alert
- unexpected redeem errors show the error itself instead of a generic message
- service error codes are a typed enum in the apps (BadgeServiceErrorCode)
- CRBadgeRedeemed returns badge state, so the apps skip a second round-trip after redeem
- setBadgeAlertAcked is scoped to user_id
- badgeChanged updates non-active profiles, so other profiles' badges don't go stale
- pitch banner is not shown to a profile that already has a badge
- one badgeTypeName per platform, used by the badge screen and the badge info alert
- BadgeAlertKind and BadgeAlertPrice decode via standard JSON, no custom decoders
- dead "Support ended" title branch removed from Your Badge view
- kotlin: users from badge responses carry remoteHostId
- kotlin: redeem code field keeps the IME's cursor and composition state
- kotlin: "Get your code" shown in all flavours
- kotlin: "Don't show again" -> "Dismiss", matching iOS
- kotlin: parseBadgeCode moved next to its FFI in platform/Core.kt
- kotlin: BadgesView no longer cross-fades on badge state updates
- kotlin: section title not uppercased
- ios: redeem code field parses once per change
- ios: A_SERVICE rejected reason is not decoded
- CLI: "cannot redeem badge code: ..." with the source of the error
- comments clarified
* ios, android, desktop: do not add loaded items of another chat to the opened chat
apiLoadMessages could apply a loaded page to a chat it was not loaded for,
mixing messages of two chats in one list:
- the chat id was not checked at all for .last pagination, and the loading
coroutine is not cancelled when the chat is closed (PreloadItems wraps it
into NonCancellable, apiFindMessages runs in a scope of its own), so a .last
page loaded for the previously opened chat was appended to the items of the
chat opened while it was loading;
- for the other paginations the chat id was checked before the items were
loaded, but they are applied on the main thread after a dispatch, so the chat
could be switched in between.
Check the chat id (and the remote host id in kotlin) right before the items are
applied, in every pagination except .initial and opening around item that set
chat id themselves. Empty chatItems remain allowed for .last that is used for
searching.
* android, desktop: test that loaded items of another chat are not added to the opened chat
Reproduces the bug deterministically, without depending on the timing of the
race: processLoadedChat is called with a page loaded for another chat while the
opened chat has its own items.
Without the fix the items of the two chats end up in one list:
lastPageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[101, 102, 201, 202]>
beforePageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[201, 202, 101, 102]>
aroundPageLoadedForAnotherChatIsNotAddedToOpenedChat
expected:<[101, 102]> but was:<[101, 102, 201, 202]>