()
}
+// Spec: spec/services/files.md#CryptoFileArgs
public struct CryptoFileArgs: Codable, Hashable {
public var fileKey: String
public var fileNonce: String
@@ -4651,6 +4819,7 @@ public enum Format: Decodable, Equatable, Hashable {
case strikeThrough
case snippet
case secret
+ case small
case colored(color: FormatColor)
case uri
case hyperLink(showText: String?, linkUri: String)
@@ -4684,7 +4853,7 @@ public enum SimplexLinkType: String, Decodable, Hashable {
case .invitation: return NSLocalizedString("SimpleX one-time invitation", comment: "simplex link type")
case .group: return NSLocalizedString("SimpleX group link", comment: "simplex link type")
case .channel: return NSLocalizedString("SimpleX channel link", comment: "simplex link type")
- case .relay: return NSLocalizedString("SimpleX relay link", comment: "simplex link type")
+ case .relay: return NSLocalizedString("SimpleX relay address", comment: "simplex link type")
}
}
}
@@ -4990,7 +5159,9 @@ public enum RcvGroupEvent: Decodable, Hashable {
case memberProfileUpdated(fromProfile: Profile, toProfile: Profile)
case newMemberPendingReview
- var text: String {
+ var text: String { text(isChannel: false) }
+
+ func text(isChannel: Bool) -> String {
switch self {
case let .memberAdded(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.profileViewName)
@@ -5012,8 +5183,12 @@ public enum RcvGroupEvent: Decodable, Hashable {
case let .memberDeleted(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("removed %@", comment: "rcv group event chat item"), profile.profileViewName)
case .userDeleted: return NSLocalizedString("removed you", comment: "rcv group event chat item")
- case .groupDeleted: return NSLocalizedString("deleted group", comment: "rcv group event chat item")
- case .groupUpdated: return NSLocalizedString("updated group profile", comment: "rcv group event chat item")
+ case .groupDeleted: return isChannel
+ ? NSLocalizedString("deleted channel", comment: "rcv group event chat item")
+ : NSLocalizedString("deleted group", comment: "rcv group event chat item")
+ case .groupUpdated: return isChannel
+ ? NSLocalizedString("updated channel profile", comment: "rcv group event chat item")
+ : NSLocalizedString("updated group profile", comment: "rcv group event chat item")
case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item")
case .memberCreatedContact: return NSLocalizedString("requested connection", comment: "rcv group event chat item")
case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
@@ -5045,7 +5220,9 @@ public enum SndGroupEvent: Decodable, Hashable {
case memberAccepted(groupMemberId: Int64, profile: Profile)
case userPendingReview
- var text: String {
+ var text: String { text(isChannel: false) }
+
+ func text(isChannel: Bool) -> String {
switch self {
case let .memberRole(_, profile, role):
return String.localizedStringWithFormat(NSLocalizedString("you changed role of %@ to %@", comment: "snd group event chat item"), profile.profileViewName, role.text)
@@ -5060,7 +5237,9 @@ public enum SndGroupEvent: Decodable, Hashable {
case let .memberDeleted(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName)
case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item")
- case .groupUpdated: return NSLocalizedString("group profile updated", comment: "snd group event chat item")
+ case .groupUpdated: return isChannel
+ ? NSLocalizedString("channel profile updated", comment: "snd group event chat item")
+ : NSLocalizedString("group profile updated", comment: "snd group event chat item")
case .memberAccepted: return NSLocalizedString("you accepted this member", comment: "snd group event chat item")
case .userPendingReview:
return NSLocalizedString("Please wait for group moderators to review your request to join the group.", comment: "snd group event chat item")
diff --git a/apps/ios/SimpleXChat/CryptoFile.swift b/apps/ios/SimpleXChat/CryptoFile.swift
index dfe833f832..5a0d48dced 100644
--- a/apps/ios/SimpleXChat/CryptoFile.swift
+++ b/apps/ios/SimpleXChat/CryptoFile.swift
@@ -4,6 +4,7 @@
//
// Created by Evgeny on 05/09/2023.
// Copyright © 2023 SimpleX Chat. All rights reserved.
+// Spec: spec/services/files.md
//
import Foundation
@@ -13,6 +14,7 @@ enum WriteFileResult: Decodable {
case error(writeError: String)
}
+// Spec: spec/services/files.md#writeCryptoFile
public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
let ptr: UnsafeMutableRawPointer = malloc(data.count)
memcpy(ptr, (data as NSData).bytes, data.count)
@@ -25,6 +27,7 @@ public func writeCryptoFile(path: String, data: Data) throws -> CryptoFileArgs {
}
}
+// Spec: spec/services/files.md#readCryptoFile
public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> Data {
var cPath = path.cString(using: .utf8)!
var cKey = cryptoArgs.fileKey.cString(using: .utf8)!
@@ -47,6 +50,7 @@ public func readCryptoFile(path: String, cryptoArgs: CryptoFileArgs) throws -> D
}
}
+// Spec: spec/services/files.md#encryptCryptoFile
public func encryptCryptoFile(fromPath: String, toPath: String) throws -> CryptoFileArgs {
var cFromPath = fromPath.cString(using: .utf8)!
var cToPath = toPath.cString(using: .utf8)!
@@ -58,6 +62,7 @@ public func encryptCryptoFile(fromPath: String, toPath: String) throws -> Crypto
}
}
+// Spec: spec/services/files.md#decryptCryptoFile
public func decryptCryptoFile(fromPath: String, cryptoArgs: CryptoFileArgs, toPath: String) throws {
var cFromPath = fromPath.cString(using: .utf8)!
var cKey = cryptoArgs.fileKey.cString(using: .utf8)!
diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift
index 2341eb4a4f..3d0dd663c1 100644
--- a/apps/ios/SimpleXChat/FileUtils.swift
+++ b/apps/ios/SimpleXChat/FileUtils.swift
@@ -5,6 +5,7 @@
// Created by JRoberts on 15.04.2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/files.md
import Foundation
import OSLog
@@ -13,14 +14,19 @@ import UIKit
let logger = Logger()
// image file size for complession
+// Spec: spec/services/files.md#MAX_IMAGE_SIZE
public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255KB
+// Spec: spec/services/files.md#MAX_IMAGE_SIZE_AUTO_RCV
public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
+// Spec: spec/services/files.md#MAX_VOICE_SIZE_AUTO_RCV
public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = MAX_IMAGE_SIZE * 2
+// Spec: spec/services/files.md#MAX_VIDEO_SIZE_AUTO_RCV
public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023KB
+// Spec: spec/services/files.md#MAX_FILE_SIZE_XFTP
public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1GB
public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max
@@ -37,10 +43,12 @@ private let CHAT_DB_BAK: String = "_chat.db.bak"
private let AGENT_DB_BAK: String = "_agent.db.bak"
+// Spec: spec/database.md#getDocumentsDirectory
public func getDocumentsDirectory() -> URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}
+// Spec: spec/database.md#getGroupContainerDirectory
public func getGroupContainerDirectory() -> URL {
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)!
}
@@ -51,12 +59,14 @@ func getAppDirectory() -> URL {
: getDocumentsDirectory()
}
+// Spec: spec/database.md#DB_FILE_PREFIX
let DB_FILE_PREFIX = "simplex_v1"
func getLegacyDatabasePath() -> URL {
getDocumentsDirectory().appendingPathComponent("mobile_v1", isDirectory: false)
}
+// Spec: spec/database.md#getAppDatabasePath
public func getAppDatabasePath() -> URL {
dbContainerGroupDefault.get() == .group
? getGroupContainerDirectory().appendingPathComponent(DB_FILE_PREFIX, isDirectory: false)
@@ -72,6 +82,7 @@ func fileModificationDate(_ path: String) -> Date? {
}
}
+// Spec: spec/services/files.md#deleteAppDatabaseAndFiles
public func deleteAppDatabaseAndFiles() {
let fm = FileManager.default
let dbPath = getAppDatabasePath().path
@@ -93,6 +104,7 @@ public func deleteAppDatabaseAndFiles() {
storeDBPassphraseGroupDefault.set(true)
}
+// Spec: spec/services/files.md#deleteAppFiles
public func deleteAppFiles() {
let fm = FileManager.default
do {
@@ -183,6 +195,7 @@ public func removeLegacyDatabaseAndFiles() -> Bool {
return r1 && r2
}
+// Spec: spec/services/files.md#getTempFilesDirectory
public func getTempFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("temp_files", isDirectory: true)
}
@@ -191,6 +204,7 @@ public func getMigrationTempFilesDirectory() -> URL {
getDocumentsDirectory().appendingPathComponent("migration_temp_files", isDirectory: true)
}
+// Spec: spec/services/files.md#getAppFilesDirectory
public func getAppFilesDirectory() -> URL {
getAppDirectory().appendingPathComponent("app_files", isDirectory: true)
}
@@ -199,6 +213,7 @@ public func getAppFilePath(_ fileName: String) -> URL {
getAppFilesDirectory().appendingPathComponent(fileName)
}
+// Spec: spec/services/files.md#getWallpaperDirectory
public func getWallpaperDirectory() -> URL {
getAppDirectory().appendingPathComponent("assets", isDirectory: true).appendingPathComponent("wallpapers", isDirectory: true)
}
@@ -207,6 +222,7 @@ public func getWallpaperFilePath(_ filename: String) -> URL {
getWallpaperDirectory().appendingPathComponent(filename)
}
+// Spec: spec/services/files.md#saveFile
public func saveFile(_ data: Data, _ fileName: String, encrypted: Bool) -> CryptoFile? {
let filePath = getAppFilePath(fileName)
do {
@@ -223,6 +239,7 @@ public func saveFile(_ data: Data, _ fileName: String, encrypted: Bool) -> Crypt
}
}
+// Spec: spec/services/files.md#removeFile
public func removeFile(_ url: URL) {
do {
try FileManager.default.removeItem(atPath: url.path)
@@ -239,12 +256,14 @@ public func removeFile(_ fileName: String) {
}
}
+// Spec: spec/services/files.md#cleanupDirectFile
public func cleanupDirectFile(_ aChatItem: AChatItem) {
if aChatItem.chatInfo.chatType == .direct {
cleanupFile(aChatItem)
}
}
+// Spec: spec/services/files.md#cleanupFile
public func cleanupFile(_ aChatItem: AChatItem) {
let cItem = aChatItem.chatItem
let mc = cItem.content.msgContent
diff --git a/apps/ios/SimpleXChat/ImageUtils.swift b/apps/ios/SimpleXChat/ImageUtils.swift
index c70ca5edd8..f93b090517 100644
--- a/apps/ios/SimpleXChat/ImageUtils.swift
+++ b/apps/ios/SimpleXChat/ImageUtils.swift
@@ -402,6 +402,11 @@ extension UIImage {
}
}
+// Max image height/width ratio for chat item display, taller images are cropped
+public func heightRatio(_ size: CGSize) -> CGFloat {
+ size.width > 0 ? min(size.height / size.width, 2.33) : 1
+}
+
public func imageFromBase64(_ base64Encoded: String?) -> UIImage? {
if let base64Encoded {
if let img = imageCache.object(forKey: base64Encoded as NSString) {
diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift
index 31b7ef83ff..a40e8eda99 100644
--- a/apps/ios/SimpleXChat/Notifications.swift
+++ b/apps/ios/SimpleXChat/Notifications.swift
@@ -5,6 +5,7 @@
// Created by Evgeny on 28/04/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
+// Spec: spec/services/notifications.md
import Foundation
import UserNotifications
@@ -22,6 +23,7 @@ public let appNotificationId = "chat.simplex.app.notification"
let contactHidden = NSLocalizedString("Contact hidden:", comment: "notification")
+// Spec: spec/services/notifications.md#createContactRequestNtf
public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: UserContactRequest, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
@@ -40,6 +42,7 @@ public func createContactRequestNtf(_ user: any UserLike, _ contactRequest: User
)
}
+// Spec: spec/services/notifications.md#createContactConnectedNtf
public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
return createNotification(
@@ -59,6 +62,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact,
)
}
+// Spec: spec/services/notifications.md#createMessageReceivedNtf
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
@@ -70,7 +74,7 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _
return createNotification(
categoryIdentifier: ntfCategoryMessageReceived,
title: title,
- body: previewMode == .message ? hideSecrets(cItem) : NSLocalizedString("new message", comment: "notification"),
+ body: previewMode == .message ? hideSecrets(cItem, isChannel: cInfo.isChannel) : NSLocalizedString("new message", comment: "notification"),
targetContentIdentifier: cInfo.id,
userInfo: ["userId": user.userId],
// userInfo: ["chatId": cInfo.id, "chatItemId": cItem.id]
@@ -78,6 +82,7 @@ public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _
)
}
+// Spec: spec/services/notifications.md#createCallInvitationNtf
public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCount: Int) -> UNMutableNotificationContent {
let text = invitation.callType.media == .video
? NSLocalizedString("Incoming video call", comment: "notification")
@@ -93,6 +98,7 @@ public func createCallInvitationNtf(_ invitation: RcvCallInvitation, _ badgeCoun
)
}
+// Spec: spec/services/notifications.md#createConnectionEventNtf
public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntity, _ badgeCount: Int) -> UNMutableNotificationContent {
let hideContent = ntfPreviewModeGroupDefault.get() == .hidden
var title: String
@@ -124,6 +130,7 @@ public func createConnectionEventNtf(_ user: User, _ connEntity: ConnectionEntit
)
}
+// Spec: spec/services/notifications.md#createErrorNtf
public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) -> UNMutableNotificationContent {
var title: String
switch dbStatus {
@@ -149,6 +156,7 @@ public func createErrorNtf(_ dbStatus: DBMigrationResult, _ badgeCount: Int) ->
)
}
+// Spec: spec/services/notifications.md#createAppStoppedNtf
public func createAppStoppedNtf(_ badgeCount: Int) -> UNMutableNotificationContent {
return createNotification(
categoryIdentifier: ntfCategoryConnectionEvent,
@@ -163,6 +171,7 @@ private func groupMsgNtfTitle(_ groupInfo: GroupInfo, _ groupMember: GroupMember
: "#\(groupInfo.displayName) \(groupMember.chatViewName):"
}
+// Spec: spec/services/notifications.md#createNotification
public func createNotification(
categoryIdentifier: String,
title: String,
@@ -187,7 +196,8 @@ public func createNotification(
return content
}
-func hideSecrets(_ cItem: ChatItem) -> String {
+// Spec: spec/services/notifications.md#hideSecrets
+func hideSecrets(_ cItem: ChatItem, isChannel: Bool = false) -> String {
if let md = cItem.formattedText {
var res = ""
for ft in md {
@@ -203,7 +213,7 @@ func hideSecrets(_ cItem: ChatItem) -> String {
if case let .report(text, reason) = mc {
return String.localizedStringWithFormat(NSLocalizedString("Report: %@", comment: "report in notification"), text.isEmpty ? reason.text : text)
} else {
- return cItem.text
+ return cItem.text(isChannel: isChannel)
}
}
}
diff --git a/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift b/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
index 662f8b43d1..2b64627dc2 100644
--- a/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
+++ b/apps/ios/SimpleXChat/Theme/ChatWallpaperTypes.swift
@@ -9,6 +9,7 @@
import Foundation
import SwiftUI
+// Spec: spec/services/theme.md#PresetWallpaper
public enum PresetWallpaper: CaseIterable {
case cats
case flowers
@@ -306,6 +307,7 @@ public enum WallpaperScaleType: String, Codable, CaseIterable {
}
}
+// Spec: spec/services/theme.md#WallpaperType
public enum WallpaperType: Equatable {
public var image: SwiftUI.Image? {
if let uiImage {
diff --git a/apps/ios/SimpleXChat/Theme/ThemeTypes.swift b/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
index 4074382543..a4e8050c6e 100644
--- a/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
+++ b/apps/ios/SimpleXChat/Theme/ThemeTypes.swift
@@ -9,6 +9,7 @@
import Foundation
import SwiftUI
+// Spec: spec/services/theme.md#DefaultTheme
public enum DefaultTheme: String, Codable, Equatable {
case LIGHT
case DARK
@@ -39,6 +40,7 @@ public enum DefaultThemeMode: String, Codable {
case dark
}
+// Spec: spec/services/theme.md#Colors
public class Colors: ObservableObject, NSCopying, Equatable {
@Published public var primary: Color
@Published public var primaryVariant: Color
@@ -84,6 +86,7 @@ public class Colors: ObservableObject, NSCopying, Equatable {
public func clone() -> Colors { copy() as! Colors }
}
+// Spec: spec/services/theme.md#AppColors
public class AppColors: ObservableObject, NSCopying, Equatable {
@Published public var title: Color
@Published public var primaryVariant2: Color
@@ -135,6 +138,7 @@ public class AppColors: ObservableObject, NSCopying, Equatable {
}
}
+// Spec: spec/services/theme.md#AppWallpaper
public class AppWallpaper: ObservableObject, NSCopying, Equatable {
public static func == (lhs: AppWallpaper, rhs: AppWallpaper) -> Bool {
lhs.background == rhs.background &&
@@ -222,6 +226,7 @@ public enum ThemeColor {
}
}
+// Spec: spec/services/theme.md#ThemeColors
public struct ThemeColors: Codable, Equatable, Hashable {
public var primary: String? = nil
public var primaryVariant: String? = nil
@@ -293,6 +298,7 @@ public struct ThemeColors: Codable, Equatable, Hashable {
}
}
+// Spec: spec/services/theme.md#ThemeWallpaper
public struct ThemeWallpaper: Codable, Equatable, Hashable {
public var preset: String?
public var scale: Float?
@@ -375,6 +381,7 @@ public struct ThemeWallpaper: Codable, Equatable, Hashable {
/// If you add new properties, make sure they serialized to YAML correctly, see:
/// encodeThemeOverrides()
+// Spec: spec/services/theme.md#ThemeOverrides
public struct ThemeOverrides: Codable, Equatable, Hashable {
public var themeId: String = UUID().uuidString
public var base: DefaultTheme
@@ -559,6 +566,7 @@ extension [ThemeOverrides] {
}
+// Spec: spec/services/theme.md#ThemeModeOverrides
public struct ThemeModeOverrides: Codable, Hashable {
public var light: ThemeModeOverride? = nil
public var dark: ThemeModeOverride? = nil
@@ -573,6 +581,7 @@ public struct ThemeModeOverrides: Codable, Hashable {
}
}
+// Spec: spec/services/theme.md#ThemeModeOverride
public struct ThemeModeOverride: Codable, Equatable, Hashable {
public var mode: DefaultThemeMode// = CurrentColors.base.mode
public var colors: ThemeColors = ThemeColors()
diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings
index 038546f889..fb8529fb88 100644
--- a/apps/ios/bg.lproj/Localizable.strings
+++ b/apps/ios/bg.lproj/Localizable.strings
@@ -1613,7 +1613,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Изтрий съобщението?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Изтрий съобщенията";
/* No comment provided by engineer. */
@@ -2750,7 +2751,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Ролята на члена ще бъде променена на \"%@\". Членът ще получи нова покана.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Членът ще бъде премахнат от групата - това не може да бъде отменено!";
/* No comment provided by engineer. */
@@ -3417,13 +3418,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay сървърът защитава вашия IP адрес, но може да наблюдава продължителността на разговора.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Премахване";
/* No comment provided by engineer. */
"Remove member" = "Острани член";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Острани член?";
/* No comment provided by engineer. */
diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings
index dd486001c7..9e1fe7139c 100644
--- a/apps/ios/cs.lproj/Localizable.strings
+++ b/apps/ios/cs.lproj/Localizable.strings
@@ -1258,7 +1258,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Smazat zprávu?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Smazat zprávy";
/* No comment provided by engineer. */
@@ -1753,7 +1754,7 @@ snd error text */
"Find chats faster" = "Najděte chaty rychleji";
/* server test error */
-"Fingerprint in server address does not match certificate." = "Je možné, že otisk certifikátu v adrese serveru je nesprávný";
+"Fingerprint in server address does not match certificate." = "Otisk certifikátu v adrese serveru neodpovídá.";
/* No comment provided by engineer. */
"Fix" = "Opravit";
@@ -2193,7 +2194,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Role člena se změní na \"%@\". Člen obdrží novou pozvánku.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Člen bude odstraněn ze skupiny - toto nelze vzít zpět!";
/* No comment provided by engineer. */
@@ -2389,7 +2390,7 @@ snd error text */
"no text" = "žádný text";
/* No comment provided by engineer. */
-"No user identifiers." = "Bez uživatelských identifikátorů";
+"No user identifiers." = "Bez uživatelských identifikátorů.";
/* No comment provided by engineer. */
"Notifications" = "Oznámení";
@@ -2728,13 +2729,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Přenosový server chrání vaši IP adresu, ale může sledovat dobu trvání hovoru.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Odstranit";
/* No comment provided by engineer. */
"Remove member" = "Odstranit člena";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Odebrat člena?";
/* No comment provided by engineer. */
@@ -2979,10 +2980,10 @@ chat item action */
"Sent messages will be deleted after set time." = "Odeslané zprávy se po uplynutí nastavené doby odstraní.";
/* server test error */
-"Server requires authorization to create queues, check password." = "Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo";
+"Server requires authorization to create queues, check password." = "Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo.";
/* server test error */
-"Server requires authorization to upload, check password." = "Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo";
+"Server requires authorization to upload, check password." = "Server vyžaduje autorizaci pro nahrávání, zkontrolujte heslo.";
/* No comment provided by engineer. */
"Server test failed!" = "Test serveru se nezdařil!";
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index caf58399de..e3979abc37 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -512,6 +512,9 @@ swipe action */
/* feature role */
"all members" = "Alle Mitglieder";
+/* No comment provided by engineer. */
+"All messages" = "Alle Nachrichten";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Alle Nachrichten und Dateien werden **Ende-zu-Ende verschlüsselt** versendet - in Direkt-Nachrichten mit Post-Quantum-Security.";
@@ -734,6 +737,9 @@ swipe action */
/* No comment provided by engineer. */
"Audio and video calls" = "Audio- und Videoanrufe";
+/* No comment provided by engineer. */
+"Audio call" = "Audioanruf";
+
/* No comment provided by engineer. */
"audio call (not e2e encrypted)" = "Audioanruf (nicht E2E verschlüsselt)";
@@ -1730,10 +1736,17 @@ swipe action */
/* No comment provided by engineer. */
"Delete member message?" = "Nachricht des Mitglieds löschen?";
+/* No comment provided by engineer. */
+"Delete member messages" = "Mitgliedsnachrichten löschen";
+
+/* alert title */
+"Delete member messages?" = "Mitgliedsnachrichten löschen?";
+
/* No comment provided by engineer. */
"Delete message?" = "Die Nachricht löschen?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Nachrichten löschen";
/* No comment provided by engineer. */
@@ -2564,6 +2577,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "Dateien und Medien sind nicht erlaubt!";
+/* No comment provided by engineer. */
+"Filter" = "Filter";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "Nach ungelesenen und favorisierten Chats filtern.";
@@ -2867,6 +2883,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "Das Bild wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder schauen Sie später nochmal nach!";
+/* No comment provided by engineer. */
+"Images" = "Bilder";
+
/* No comment provided by engineer. */
"Immediately" = "Sofort";
@@ -3050,6 +3069,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Freunde einladen";
+/* No comment provided by engineer. */
+"Invite member" = "Mitglied einladen";
+
/* No comment provided by engineer. */
"Invite members" = "Mitglieder einladen";
@@ -3203,6 +3225,9 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "Verknüpfte Desktops";
+/* No comment provided by engineer. */
+"Links" = "Links";
+
/* swipe action */
"List" = "Liste";
@@ -3296,6 +3321,9 @@ snd error text */
/* No comment provided by engineer. */
"Member is deleted - can't accept request" = "Mitglied ist gelöscht - Anfrage kann nicht angenommen werden";
+/* alert message */
+"Member messages will be deleted - this cannot be undone!" = "Mitgliedsnachrichten werden gelöscht. Dies kann nicht rückgängig gemacht werden!";
+
/* chat feature */
"Member reports" = "Mitglieder-Meldungen";
@@ -3308,10 +3336,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Die Mitgliederrolle wird auf \"%@\" geändert. Das Mitglied wird eine neue Einladung erhalten.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Das Mitglied wird aus dem Chat entfernt. Dies kann nicht rückgängig gemacht werden!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!";
/* alert message */
@@ -4380,9 +4408,12 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relais-Server schützen Ihre IP-Adresse, aber sie können die Anrufdauer erfassen.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Entfernen";
+/* alert action */
+"Remove and delete messages" = "Mitglied entfernen und Nachrichten löschen";
+
/* No comment provided by engineer. */
"Remove archive?" = "Archiv entfernen?";
@@ -4395,7 +4426,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Mitglied entfernen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Das Mitglied entfernen?";
/* No comment provided by engineer. */
@@ -4690,9 +4721,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks angenommen.";
+/* No comment provided by engineer. */
+"Search files" = "Dateien suchen";
+
+/* No comment provided by engineer. */
+"Search images" = "Bilder suchen";
+
+/* No comment provided by engineer. */
+"Search links" = "Links suchen";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "Suchen oder SimpleX-Link einfügen";
+/* No comment provided by engineer. */
+"Search videos" = "Videos suchen";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "Sprachnachrichten suchen";
+
/* network option */
"sec" = "sek";
@@ -5898,6 +5944,9 @@ report reason */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "Das Video wird heruntergeladen, sobald Ihr Kontakt online ist. Bitte warten oder überprüfen Sie es später!";
+/* No comment provided by engineer. */
+"Videos" = "Videos";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "Videos und Dateien bis zu 1GB";
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index 5ce7ab6843..a05bc9f4b6 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -257,7 +257,7 @@
"`a + b`" = "\\`a + b`";
/* email text */
-"Hi!
\nConnect to me via SimpleX Chat
" = "¡Hola!
\n Conecta conmigo a través de SimpleX Chat
";
+"Hi!
\nConnect to me via SimpleX Chat
" = "¡Hola!
\nConecta conmigo a través de SimpleX Chat
";
/* No comment provided by engineer. */
"~strike~" = "\\~strike~";
@@ -384,7 +384,7 @@ swipe action */
"accepted invitation" = "invitación aceptada";
/* rcv group event chat item */
-"accepted you" = "te ha aceptado";
+"accepted you" = "te ha admitido";
/* No comment provided by engineer. */
"Acknowledged" = "Confirmaciones";
@@ -512,6 +512,9 @@ swipe action */
/* feature role */
"all members" = "todos los miembros";
+/* No comment provided by engineer. */
+"All messages" = "Todos los mensajes";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Todos los mensajes y archivos son enviados **cifrados de extremo a extremo** y con seguridad de cifrado postcuántico en mensajes directos.";
@@ -734,6 +737,9 @@ swipe action */
/* No comment provided by engineer. */
"Audio and video calls" = "Llamadas y videollamadas";
+/* No comment provided by engineer. */
+"Audio call" = "Llamada";
+
/* No comment provided by engineer. */
"audio call (not e2e encrypted)" = "llamada (sin cifrar)";
@@ -1730,10 +1736,17 @@ swipe action */
/* No comment provided by engineer. */
"Delete member message?" = "¿Eliminar el mensaje de miembro?";
+/* No comment provided by engineer. */
+"Delete member messages" = "Eliminar mensajes del miembro";
+
+/* alert title */
+"Delete member messages?" = "¿Eliminar mensajes del miembro?";
+
/* No comment provided by engineer. */
"Delete message?" = "¿Eliminar mensaje?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Activar";
/* No comment provided by engineer. */
@@ -2564,6 +2577,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "¡Archivos y multimedia no permitidos!";
+/* No comment provided by engineer. */
+"Filter" = "Filtro";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "Filtra chats no leídos y favoritos.";
@@ -2867,6 +2883,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "La imagen se recibirá cuando el contacto esté en línea, ¡por favor espera o revisa más tarde!";
+/* No comment provided by engineer. */
+"Images" = "Imágenes";
+
/* No comment provided by engineer. */
"Immediately" = "Inmediatamente";
@@ -3050,6 +3069,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Invitar amigos";
+/* No comment provided by engineer. */
+"Invite member" = "Invitar miembro";
+
/* No comment provided by engineer. */
"Invite members" = "Invitar miembros";
@@ -3203,6 +3225,9 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "Ordenadores enlazados";
+/* No comment provided by engineer. */
+"Links" = "Enlaces";
+
/* swipe action */
"List" = "Lista";
@@ -3296,6 +3321,9 @@ snd error text */
/* No comment provided by engineer. */
"Member is deleted - can't accept request" = "Miembro eliminado, no puede aceptar solicitudes";
+/* alert message */
+"Member messages will be deleted - this cannot be undone!" = "Los mensajes del miembro serán eliminados. ¡No puede deshacerse!";
+
/* chat feature */
"Member reports" = "Informes de miembros";
@@ -3308,10 +3336,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "El rol del miembro cambiará a \"%@\" y recibirá una invitación nueva.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "El miembro será eliminado del chat. ¡No puede deshacerse!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "El miembro será expulsado del grupo. ¡No puede deshacerse!";
/* alert message */
@@ -3654,7 +3682,7 @@ snd error text */
"No device token!" = "¡Sin dispositivo token!";
/* item status description */
-"No direct connection yet, message is forwarded by admin." = "Aún no hay conexión directa con este miembro, el mensaje es reenviado por el administrador.";
+"No direct connection yet, message is forwarded by admin." = "Aún no hay conexión directa, los mensajes son reenviados por el administrador.";
/* No comment provided by engineer. */
"no e2e encryption" = "sin cifrar";
@@ -3938,7 +3966,7 @@ new chat action */
"Or securely share this file link" = "O comparte de forma segura este enlace al archivo";
/* No comment provided by engineer. */
-"Or show this code" = "O muestra el código QR";
+"Or show this code" = "O muestra este código";
/* No comment provided by engineer. */
"Or to share privately" = "O para compartir en privado";
@@ -4380,9 +4408,12 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Eliminar";
+/* alert action */
+"Remove and delete messages" = "Eliminar miembro y sus mensajes";
+
/* No comment provided by engineer. */
"Remove archive?" = "¿Eliminar archivo?";
@@ -4395,7 +4426,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Expulsar miembro";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "¿Expulsar miembro?";
/* No comment provided by engineer. */
@@ -4690,9 +4721,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "La barra de búsqueda acepta enlaces de invitación.";
+/* No comment provided by engineer. */
+"Search files" = "Buscar archivos";
+
+/* No comment provided by engineer. */
+"Search images" = "Buscar imágenes";
+
+/* No comment provided by engineer. */
+"Search links" = "Buscar enlaces";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "Buscar o pegar enlace SimpleX";
+/* No comment provided by engineer. */
+"Search videos" = "Buscar vídeos";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "Buscar mensajes de voz";
+
/* network option */
"sec" = "seg";
@@ -5686,7 +5732,7 @@ report reason */
"Unmute" = "Activar audio";
/* No comment provided by engineer. */
-"unprotected" = "con IP desprotegida";
+"unprotected" = "desprotegida";
/* swipe action */
"Unread" = "No leído";
@@ -5898,6 +5944,9 @@ report reason */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "El vídeo se recibirá cuando el contacto esté en línea, por favor espera o revisa más tarde.";
+/* No comment provided by engineer. */
+"Videos" = "Vídeos";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "Vídeos y archivos de hasta 1Gb";
@@ -6052,7 +6101,7 @@ report reason */
"You accepted connection" = "Has aceptado la conexión";
/* snd group event chat item */
-"you accepted this member" = "has aceptado al miembro";
+"you accepted this member" = "has admitido al miembro";
/* No comment provided by engineer. */
"You allow" = "Permites";
diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings
index 884be40cc1..ea3f9c4386 100644
--- a/apps/ios/fi.lproj/Localizable.strings
+++ b/apps/ios/fi.lproj/Localizable.strings
@@ -943,7 +943,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Poista viesti?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Poista viestit";
/* No comment provided by engineer. */
@@ -1869,7 +1870,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Jäsenen rooli muutetaan muotoon \"%@\". Jäsen saa uuden kutsun.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Jäsen poistetaan ryhmästä - tätä ei voi perua!";
/* No comment provided by engineer. */
@@ -2398,13 +2399,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Välityspalvelin suojaa IP-osoitteesi, mutta se voi tarkkailla puhelun kestoa.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Poista";
/* No comment provided by engineer. */
"Remove member" = "Poista jäsen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Poista jäsen?";
/* No comment provided by engineer. */
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index dbfac375d1..2b2a1e98e5 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -1661,7 +1661,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Supprimer le message ?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Supprimer les messages";
/* No comment provided by engineer. */
@@ -3104,10 +3105,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Le rôle du membre sera changé pour \"%@\". Ce membre recevra une nouvelle invitation.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Le membre sera retiré de la discussion - cela ne peut pas être annulé !";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Ce membre sera retiré du groupe - impossible de revenir en arrière !";
/* No comment provided by engineer. */
@@ -4005,7 +4006,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Le serveur relais protège votre adresse IP, mais il peut observer la durée de l'appel.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Supprimer";
/* No comment provided by engineer. */
@@ -4017,7 +4018,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Retirer le membre";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Retirer ce membre ?";
/* No comment provided by engineer. */
diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings
index 451bdfc699..56277f4fd3 100644
--- a/apps/ios/hu.lproj/Localizable.strings
+++ b/apps/ios/hu.lproj/Localizable.strings
@@ -5,7 +5,7 @@
"_italic_" = "\\_dőlt_";
/* No comment provided by engineer. */
-"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- kapcsolódás a [könyvtár szolgáltatáshoz](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- kézbesítési jelentések (legfeljebb 20 tag).\n- gyorsabb és stabilabb.";
+"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- kapcsolódás a [könyvtárszolgáltatáshoz](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- kézbesítési jelentések (legfeljebb 20 tagig).\n- gyorsabb és stabilabb.";
/* No comment provided by engineer. */
"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilabb üzenetkézbesítés.\n- picit továbbfejlesztett csoportok.\n- és még sok más!";
@@ -41,10 +41,10 @@
"**Create group**: to create a new group." = "**Csoport létrehozása:** új csoport létrehozásához.";
/* No comment provided by engineer. */
-"**e2e encrypted** audio call" = "**e2e titkosított** hanghívás";
+"**e2e encrypted** audio call" = "**végpontok között titkosított** hanghívás";
/* No comment provided by engineer. */
-"**e2e encrypted** video call" = "**e2e titkosított** videóhívás";
+"**e2e encrypted** video call" = "**végpontok között titkosított** videóhívás";
/* No comment provided by engineer. */
"**More private**: check new messages every 20 minutes. Only device token is shared with our push server. It doesn't see how many contacts you have, or any message metadata." = "**Privátabb:** 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken meg lesz osztva a SimpleX Chat kiszolgálóval, de az nem, hogy hány partnere vagy üzenete van.";
@@ -65,7 +65,7 @@
"**Scan / Paste link**: to connect via a link you received." = "**Hivatkozás beolvasása / beillesztése**: egy kapott hivatkozáson keresztüli kapcsolódáshoz.";
/* No comment provided by engineer. */
-"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali push-értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.";
+"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés:** Az azonnali leküldéses értesítésekhez a kulcstartóban tárolt jelmondat megadása szükséges.";
/* No comment provided by engineer. */
"**Warning**: the archive will be removed." = "**Figyelmeztetés:** az archívum el lesz távolítva.";
@@ -119,10 +119,10 @@
"%@ is connected!" = "%@ kapcsolódott!";
/* No comment provided by engineer. */
-"%@ is not verified" = "%@ nincs hitelesítve";
+"%@ is not verified" = "%@ nincs ellenőrizve";
/* No comment provided by engineer. */
-"%@ is verified" = "%@ hitelesítve";
+"%@ is verified" = "%@ ellenőrizve";
/* No comment provided by engineer. */
"%@ server" = "%@ kiszolgáló";
@@ -194,7 +194,7 @@
"%lld %@" = "%lld %@";
/* No comment provided by engineer. */
-"%lld contact(s) selected" = "%lld partner kijelölve";
+"%lld contact(s) selected" = "%lld partner kiválasztva";
/* No comment provided by engineer. */
"%lld file(s) with total size of %@" = "%lld fájl, %@ összméretben";
@@ -507,11 +507,14 @@ swipe action */
"All data is kept private on your device." = "Az összes adat privát módon van tárolva az eszközén.";
/* No comment provided by engineer. */
-"All group members will remain connected." = "Az összes csoporttag kapcsolatban marad.";
+"All group members will remain connected." = "Az összes csoporttag továbbra is kapcsolatban marad.";
/* feature role */
"all members" = "összes tag";
+/* No comment provided by engineer. */
+"All messages" = "Összes üzenet";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Az összes üzenet és fájl **végpontok közötti titkosítással**, a közvetlen üzenetek továbbá kvantumbiztos titkosítással is rendelkeznek.";
@@ -534,10 +537,10 @@ swipe action */
"All servers" = "Összes kiszolgáló";
/* No comment provided by engineer. */
-"All your contacts will remain connected." = "Az összes partnerével kapcsolatban marad.";
+"All your contacts will remain connected." = "Az összes partnerével továbbra is kapcsolatban marad.";
/* No comment provided by engineer. */
-"All your contacts will remain connected. Profile update will be sent to your contacts." = "A partnereivel kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.";
+"All your contacts will remain connected. Profile update will be sent to your contacts." = "Az összes partnerével továbbra is kapcsolatban marad. A profilfrissítés el lesz küldve a partnerei számára.";
/* No comment provided by engineer. */
"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Az összes partnere, -beszélgetése és -fájlja biztonságosan titkosítva lesz, majd töredékekre bontva feltöltődnek a beállított XFTP-továbbítókiszolgálókra.";
@@ -609,7 +612,7 @@ swipe action */
"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Az elküldött üzenetek végleges törlése engedélyezve van a partnerei számára. (24 óra)";
/* No comment provided by engineer. */
-"Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldésének engedélyezése a partnerei számára.";
+"Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van a partnerei számára.";
/* No comment provided by engineer. */
"Allow your contacts to send files and media." = "A fájlok és a médiatartalmak küldése engedélyezve van a partnerei számára.";
@@ -630,10 +633,10 @@ swipe action */
"always" = "mindig";
/* No comment provided by engineer. */
-"Always use private routing." = "Mindig használjon privát útválasztást.";
+"Always use private routing." = "Mindig legyen használva privát útválasztás.";
/* No comment provided by engineer. */
-"Always use relay" = "Mindig használjon továbbítókiszolgálót";
+"Always use relay" = "Mindig legyen használva továbbítókiszolgáló";
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "Egy üres csevegési profil lesz létrehozva a megadott névvel, és az alkalmazás a szokásos módon megnyílik.";
@@ -735,7 +738,10 @@ swipe action */
"Audio and video calls" = "Hang- és videóhívások";
/* No comment provided by engineer. */
-"audio call (not e2e encrypted)" = "hanghívás (nem e2e titkosított)";
+"Audio call" = "Hanghívás";
+
+/* No comment provided by engineer. */
+"audio call (not e2e encrypted)" = "hanghívás (végpontok között NEM titkosított)";
/* chat feature */
"Audio/video calls" = "Hang- és videóhívások";
@@ -819,10 +825,10 @@ swipe action */
"Better user experience" = "Továbbfejlesztett felhasználói élmény";
/* No comment provided by engineer. */
-"Bio" = "Névjegy";
+"Bio" = "Életrajz";
/* alert title */
-"Bio too large" = "A névjegy túl hosszú";
+"Bio too large" = "Az életrajz túl hosszú";
/* No comment provided by engineer. */
"Black" = "Fekete";
@@ -913,7 +919,7 @@ marked deleted chat item preview text */
"call" = "hívás";
/* No comment provided by engineer. */
-"Call already ended!" = "A hívás már befejeződött!";
+"Call already ended!" = "A hívás már véget ért!";
/* call status */
"call error" = "híváshiba";
@@ -1120,7 +1126,7 @@ set passcode view */
"Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület";
/* No comment provided by engineer. */
-"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközén és olvassa be a QR-kódot.";
+"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ beállítást az új eszközén és olvassa be a QR-kódot.";
/* No comment provided by engineer. */
"Choose file" = "Fájl kiválasztása";
@@ -1138,25 +1144,25 @@ set passcode view */
"Chunks uploaded" = "Feltöltött töredékek";
/* swipe action */
-"Clear" = "Kiürítés";
+"Clear" = "Ürítés";
/* No comment provided by engineer. */
-"Clear conversation" = "Üzenetek kiürítése";
+"Clear conversation" = "Üzenetek ürítése";
/* No comment provided by engineer. */
-"Clear conversation?" = "Kiüríti az üzeneteket?";
+"Clear conversation?" = "Üríti a beszélgetés üzeneteit?";
/* No comment provided by engineer. */
-"Clear group?" = "Kiüríti a csoportot?";
+"Clear group?" = "Üríti a csoport üzeneteit?";
/* No comment provided by engineer. */
-"Clear or delete group?" = "Csoport kiürítése vagy törlése?";
+"Clear or delete group?" = "Csoport ürítése vagy törlése?";
/* No comment provided by engineer. */
-"Clear private notes?" = "Kiüríti a privát jegyzeteket?";
+"Clear private notes?" = "Üríti a privát jegyzetek tartalmát?";
/* No comment provided by engineer. */
-"Clear verification" = "Hitelesítés törlése";
+"Clear verification" = "Ellenőrzés törlése";
/* No comment provided by engineer. */
"Color chats with the new themes." = "Csevegések színezése új témákkal.";
@@ -1177,7 +1183,7 @@ set passcode view */
"Compare security codes with your contacts." = "Biztonsági kódok összehasonlítása a partnerekével.";
/* No comment provided by engineer. */
-"complete" = "befejezett";
+"complete" = "kész";
/* No comment provided by engineer. */
"Completed" = "Elkészült";
@@ -1273,7 +1279,7 @@ set passcode view */
"Connect via link" = "Kapcsolódás egy hivatkozáson keresztül";
/* new chat sheet title */
-"Connect via one-time link" = "Kapcsolódás egyszer használható meghívón keresztül";
+"Connect via one-time link" = "Kapcsolódás az egyszer használható meghívón keresztül";
/* new chat action */
"Connect with %@" = "Kapcsolódás a következővel: %@";
@@ -1291,7 +1297,7 @@ set passcode view */
"Connected servers" = "Kapcsolódott kiszolgálók";
/* No comment provided by engineer. */
-"Connected to desktop" = "Kapcsolódva a számítógéphez";
+"Connected to desktop" = "Társítva a számítógéppel";
/* No comment provided by engineer. */
"connecting" = "kapcsolódás";
@@ -1312,7 +1318,7 @@ set passcode view */
"connecting (introduction invitation)" = "kapcsolódás (bemutatkozó meghívó)";
/* call status */
-"connecting call" = "kapcsolódási hívás…";
+"connecting call" = "hívás kapcsolása…";
/* No comment provided by engineer. */
"Connecting server…" = "Kapcsolódás a kiszolgálóhoz…";
@@ -1324,7 +1330,7 @@ set passcode view */
"Connecting to contact, please wait or check later!" = "Kapcsolódás a partnerhez, várjon vagy ellenőrizze később!";
/* No comment provided by engineer. */
-"Connecting to desktop" = "Kapcsolódás a számítógéphez";
+"Connecting to desktop" = "Társítás számítógéppel";
/* No comment provided by engineer. */
"connecting…" = "kapcsolódás…";
@@ -1399,10 +1405,10 @@ set passcode view */
"contact disabled" = "partner letiltva";
/* No comment provided by engineer. */
-"contact has e2e encryption" = "a partner e2e titkosítással rendelkezik";
+"contact has e2e encryption" = "a partner végpontok közötti titkosítással rendelkezik";
/* No comment provided by engineer. */
-"contact has no e2e encryption" = "a partner nem rendelkezik e2e titkosítással";
+"contact has no e2e encryption" = "a partner nem rendelkezik végpontok közötti titkosítással";
/* notification */
"Contact hidden:" = "Rejtett név:";
@@ -1486,7 +1492,7 @@ set passcode view */
"Create list" = "Lista létrehozása";
/* No comment provided by engineer. */
-"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Új profil létrehozása a [számítógép-alkalmazásban](https://simplex.chat/downloads/). 💻";
+"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Új profil létrehozása a [számítógépes alkalmazásban](https://simplex.chat/downloads/). 💻";
/* No comment provided by engineer. */
"Create profile" = "Profil létrehozása";
@@ -1656,7 +1662,7 @@ swipe action */
"Delete after" = "Törlés ennyi idő után";
/* No comment provided by engineer. */
-"Delete all files" = "Az összes fájl törlése";
+"Delete all files" = "Összes fájl törlése";
/* No comment provided by engineer. */
"Delete and notify contact" = "Törlés, és a partner értesítése";
@@ -1730,10 +1736,17 @@ swipe action */
/* No comment provided by engineer. */
"Delete member message?" = "Törli a tag üzenetét?";
+/* No comment provided by engineer. */
+"Delete member messages" = "Tag üzeneteinek törlése";
+
+/* alert title */
+"Delete member messages?" = "Törli a tag üzeneteit?";
+
/* No comment provided by engineer. */
"Delete message?" = "Törli az üzenetet?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Üzenetek törlése";
/* No comment provided by engineer. */
@@ -1815,7 +1828,7 @@ swipe action */
"Desktop address" = "Számítógép címe";
/* No comment provided by engineer. */
-"Desktop app version %@ is not compatible with this app." = "A számítógép-alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.";
+"Desktop app version %@ is not compatible with this app." = "A számítógépes alkalmazás verziója (%@) nem kompatibilis ezzel az alkalmazással.";
/* No comment provided by engineer. */
"Desktop devices" = "Számítógépek";
@@ -1935,7 +1948,7 @@ swipe action */
"Do not use credentials with proxy." = "Ne használja a hitelesítési adatokat proxyval.";
/* No comment provided by engineer. */
-"Do NOT use private routing." = "NE használjon privát útválasztást.";
+"Do NOT use private routing." = "NE legyen használva privát útválasztás.";
/* No comment provided by engineer. */
"Do NOT use SimpleX for emergency calls." = "NE használja a SimpleXet segélyhívásokhoz.";
@@ -1947,13 +1960,13 @@ swipe action */
"Don't create address" = "Ne hozzon létre címet";
/* No comment provided by engineer. */
-"Don't enable" = "Ne engedélyezze";
+"Don't enable" = "Nem engedélyezem";
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne maradjon le a fontos üzenetekről.";
/* alert action */
-"Don't show again" = "Ne mutasd újra";
+"Don't show again" = "Ne jelenjen meg újra";
/* No comment provided by engineer. */
"Done" = "Kész";
@@ -2002,10 +2015,10 @@ chat item action */
"Duration" = "Időtartam";
/* No comment provided by engineer. */
-"e2e encrypted" = "e2e titkosított";
+"e2e encrypted" = "végpontok között titkosított";
/* No comment provided by engineer. */
-"E2E encrypted notifications." = "Végpontok közötti titkosított értesítések.";
+"E2E encrypted notifications." = "Végpontok között titkosított értesítések.";
/* chat item action */
"Edit" = "Szerkesztés";
@@ -2080,7 +2093,7 @@ chat item action */
"enabled for you" = "engedélyezve az Ön számára";
/* No comment provided by engineer. */
-"Encrypt" = "Titkosít";
+"Encrypt" = "Titkosítás";
/* No comment provided by engineer. */
"Encrypt database?" = "Titkosítja az adatbázist?";
@@ -2149,10 +2162,10 @@ chat item action */
"Encryption renegotiation in progress." = "A titkosítás újraegyeztetése folyamatban van.";
/* No comment provided by engineer. */
-"ended" = "befejeződött";
+"ended" = "hívás vége";
/* call status */
-"ended call %@" = "%@ hívása befejeződött";
+"ended call %@" = "%@ hívása véget ért";
/* No comment provided by engineer. */
"Enter correct passphrase." = "Adja meg a helyes jelmondatot.";
@@ -2431,7 +2444,7 @@ chat item action */
"Error uploading the archive" = "Hiba történt az archívum feltöltésekor";
/* No comment provided by engineer. */
-"Error verifying passphrase:" = "Hiba történt a jelmondat hitelesítésekor:";
+"Error verifying passphrase:" = "Hiba történt a jelmondat ellenőrzésekor:";
/* No comment provided by engineer. */
"Error: " = "Hiba: ";
@@ -2564,6 +2577,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "A fájlok és a médiatartalmak küldése le van tiltva!";
+/* No comment provided by engineer. */
+"Filter" = "Szűrő";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "Olvasatlan és kedvenc csevegésekre való szűrés.";
@@ -2832,7 +2848,7 @@ snd error text */
"How SimpleX works" = "Hogyan működik a SimpleX";
/* No comment provided by engineer. */
-"How to" = "Hogyan";
+"How to" = "Útmutató";
/* No comment provided by engineer. */
"How to use it" = "Használati útmutató";
@@ -2856,7 +2872,7 @@ snd error text */
"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:";
/* No comment provided by engineer. */
-"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson alább a **Befejezés később** lehetőségre (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).";
+"If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson lentebb a **Befejezés később** beállításra (az alkalmazás újraindításakor fel lesz ajánlva az adatbázis átköltöztetése).";
/* No comment provided by engineer. */
"Ignore" = "Mellőzés";
@@ -2867,6 +2883,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!";
+/* No comment provided by engineer. */
+"Images" = "Képek";
+
/* No comment provided by engineer. */
"Immediately" = "Azonnal";
@@ -2979,7 +2998,7 @@ snd error text */
"Instant" = "Azonnali";
/* No comment provided by engineer. */
-"Instant push notifications will be hidden!\n" = "Az azonnali push-értesítések el lesznek rejtve!\n";
+"Instant push notifications will be hidden!\n" = "Az azonnali leküldéses értesítések el lesznek rejtve!\n";
/* No comment provided by engineer. */
"Interface" = "Kezelőfelület";
@@ -3050,6 +3069,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Barátok meghívása";
+/* No comment provided by engineer. */
+"Invite member" = "Tag meghívása";
+
/* No comment provided by engineer. */
"Invite members" = "Tagok meghívása";
@@ -3072,10 +3094,10 @@ snd error text */
"invited via your group link" = "meghíva a saját csoporthivatkozásán keresztül";
/* No comment provided by engineer. */
-"iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a push-értesítések fogadását.";
+"iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstartó a jelmondat biztonságos tárolására szolgál – lehetővé teszi a leküldéses értesítések fogadását.";
/* No comment provided by engineer. */
-"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a push-értesítések fogadását.";
+"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Az iOS kulcstartó biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat módosítása után – lehetővé teszi a leküldéses értesítések fogadását.";
/* No comment provided by engineer. */
"IP address" = "IP-cím";
@@ -3141,7 +3163,7 @@ snd error text */
"Keep conversation" = "Beszélgetés megtartása";
/* No comment provided by engineer. */
-"Keep the app open to use it from desktop" = "A számítógépről való használathoz tartsd nyitva az alkalmazást";
+"Keep the app open to use it from desktop" = "Alkalmazás megnyitva tartása a számítógépről való használathoz";
/* alert title */
"Keep unused invitation?" = "Megtartja a fel nem használt meghívót?";
@@ -3195,7 +3217,7 @@ snd error text */
"Limitations" = "Korlátozások";
/* No comment provided by engineer. */
-"Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és számítógépes alkalmazásokat! 🔗";
+"Link mobile and desktop apps! 🔗" = "Társítsa össze a hordozható eszköz- és a számítógépes alkalmazásokat! 🔗";
/* No comment provided by engineer. */
"Linked desktop options" = "Társított számítógép beállítások";
@@ -3203,6 +3225,9 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "Társított számítógépek";
+/* No comment provided by engineer. */
+"Links" = "Hivatkozások";
+
/* swipe action */
"List" = "Lista";
@@ -3252,7 +3277,7 @@ snd error text */
"Mark read" = "Megjelölés olvasottként";
/* No comment provided by engineer. */
-"Mark verified" = "Hitelesítés";
+"Mark verified" = "Megjelölés ellenőrzöttként";
/* No comment provided by engineer. */
"Markdown in messages" = "Markdown az üzenetekben";
@@ -3261,7 +3286,7 @@ snd error text */
"marked deleted" = "törlésre jelölve";
/* No comment provided by engineer. */
-"Max 30 seconds, received instantly." = "Max. 30 másodperc, azonnal érkezett.";
+"Max 30 seconds, received instantly." = "Legfeljebb 30 másodperc, azonnal megérkezik.";
/* No comment provided by engineer. */
"Media & file servers" = "Fájl- és médiakiszolgálók";
@@ -3296,6 +3321,9 @@ snd error text */
/* No comment provided by engineer. */
"Member is deleted - can't accept request" = "A tag törölve lett – nem lehet elfogadni a kérést";
+/* alert message */
+"Member messages will be deleted - this cannot be undone!" = "A tag üzenetei törölve lesznek – ez a művelet nem vonható vissza!";
+
/* chat feature */
"Member reports" = "Tagok jelentései";
@@ -3308,10 +3336,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre a következőre fog módosulni: „%@”. A tag új meghívást fog kapni.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "A tag el lesz távolítva a csevegésből – ez a művelet nem vonható vissza!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!";
/* alert message */
@@ -3360,7 +3388,7 @@ snd error text */
"Message delivery warning" = "Üzenetkézbesítési figyelmeztetés";
/* No comment provided by engineer. */
-"Message draft" = "Üzenetvázlat";
+"Message draft" = "Piszkozatok";
/* item status text */
"Message forwarded" = "Továbbított üzenet";
@@ -3432,7 +3460,7 @@ snd error text */
"Messages sent" = "Elküldött üzenetek";
/* alert message */
-"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket.";
+"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kiváasztotta őket.";
/* No comment provided by engineer. */
"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
@@ -3462,16 +3490,16 @@ snd error text */
"Migrating database archive…" = "Adatbázis-archívum átköltöztetése…";
/* No comment provided by engineer. */
-"Migration complete" = "Átköltöztetés befejezve";
+"Migration complete" = "Átköltöztetés kész";
/* No comment provided by engineer. */
"Migration error:" = "Átköltöztetési hiba:";
/* No comment provided by engineer. */
-"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** lehetőségre a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).";
+"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen átköltöztetés. Koppintson a **Kihagyás** beállításra a jelenlegi adatbázis használatának folytatásához. Jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat).";
/* No comment provided by engineer. */
-"Migration is completed" = "Az átköltöztetés befejeződött";
+"Migration is completed" = "Az átköltöztetés elkészült";
/* No comment provided by engineer. */
"Migrations:" = "Átköltöztetések:";
@@ -3573,10 +3601,10 @@ snd error text */
"New contact request" = "Új partneri kapcsolatkérés";
/* notification */
-"New contact:" = "Új kapcsolat:";
+"New contact:" = "Új partner:";
/* No comment provided by engineer. */
-"New desktop app!" = "Új számítógép-alkalmazás!";
+"New desktop app!" = "Új számítógépes alkalmazás!";
/* No comment provided by engineer. */
"New display name" = "Új megjelenítendő név";
@@ -3642,7 +3670,7 @@ snd error text */
"No chats with members" = "Nincsenek csevegések a tagokkal";
/* No comment provided by engineer. */
-"No contacts selected" = "Nincs partner kijelölve";
+"No contacts selected" = "Nincs partner kiválasztva";
/* No comment provided by engineer. */
"No contacts to add" = "Nincs hozzáadandó partner";
@@ -3657,7 +3685,7 @@ snd error text */
"No direct connection yet, message is forwarded by admin." = "Még nincs közvetlen kapcsolat, az üzenetet az adminisztrátor továbbítja.";
/* No comment provided by engineer. */
-"no e2e encryption" = "nincs e2e titkosítás";
+"no e2e encryption" = "nincs végpontok közötti titkosítás";
/* No comment provided by engineer. */
"No filtered chats" = "Nincsenek szűrt csevegések";
@@ -3696,7 +3724,7 @@ snd error text */
"No private routing session" = "Nincs privát útválasztási munkamenet";
/* No comment provided by engineer. */
-"No push server" = "Helyi";
+"No push server" = "Nincs kiszolgáló a leküldéses értesítésekhez";
/* No comment provided by engineer. */
"No received or sent files" = "Nincsenek fogadott vagy küldött fájlok";
@@ -3714,7 +3742,7 @@ snd error text */
"No servers to send files." = "Nincsenek fájlküldési kiszolgálók.";
/* No comment provided by engineer. */
-"no subscription" = "nincs előfizetés";
+"no subscription" = "nincs feliratkozás";
/* copied message info in history */
"no text" = "nincs szöveg";
@@ -3738,7 +3766,7 @@ snd error text */
"Notes" = "Jegyzetek";
/* No comment provided by engineer. */
-"Nothing selected" = "Nincs semmi kijelölve";
+"Nothing selected" = "Nincs semmi kiválasztva";
/* alert title */
"Nothing to forward!" = "Nincs mit továbbítani!";
@@ -3833,37 +3861,37 @@ new chat action */
"Only you can add message reactions." = "Csak Ön adhat hozzá reakciókat az üzenetekhez.";
/* No comment provided by engineer. */
-"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Véglegesen csak Ön törölhet üzeneteket (partnere csak törlésre jelölheti meg őket ). (24 óra)";
+"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Csak Ön törölheti véglegesen az üzeneteket (partnere csak törlésre jelölheti meg azokat ). (24 óra)";
/* No comment provided by engineer. */
-"Only you can make calls." = "Csak Ön tud hívásokat indítani.";
+"Only you can make calls." = "Csak Ön kezdeményezhet hívásokat.";
/* No comment provided by engineer. */
-"Only you can send disappearing messages." = "Csak Ön tud eltűnő üzeneteket küldeni.";
+"Only you can send disappearing messages." = "Csak Ön küldhet eltűnő üzeneteket.";
/* No comment provided by engineer. */
"Only you can send files and media." = "Csak Ön küldhet fájlokat és médiatartalmakat.";
/* No comment provided by engineer. */
-"Only you can send voice messages." = "Csak Ön tud hangüzeneteket küldeni.";
+"Only you can send voice messages." = "Csak Ön küldhet hangüzeneteket.";
/* No comment provided by engineer. */
"Only your contact can add message reactions." = "Csak a partnere adhat hozzá reakciókat az üzenetekhez.";
/* No comment provided by engineer. */
-"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Csak a partnere tudja az üzeneteket véglegesen törölni (Ön csak törlésre jelölheti meg azokat). (24 óra)";
+"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Csak a partnere törölheti véglegesen az üzeneteket (Ön csak törlésre jelölheti meg azokat). (24 óra)";
/* No comment provided by engineer. */
-"Only your contact can make calls." = "Csak a partnere tud hívást indítani.";
+"Only your contact can make calls." = "Csak a partnere kezdeményezhet hívásokat.";
/* No comment provided by engineer. */
-"Only your contact can send disappearing messages." = "Csak a partnere tud eltűnő üzeneteket küldeni.";
+"Only your contact can send disappearing messages." = "Csak a partnere küldhet eltűnő üzeneteket.";
/* No comment provided by engineer. */
"Only your contact can send files and media." = "Csak a partnere küldhet fájlokat és médiatartalmakat.";
/* No comment provided by engineer. */
-"Only your contact can send voice messages." = "Csak a partnere tud hangüzeneteket küldeni.";
+"Only your contact can send voice messages." = "Csak a partnere küldhet hangüzeneteket.";
/* alert action */
"Open" = "Megnyitás";
@@ -4070,7 +4098,7 @@ new chat action */
"Please report it to the developers." = "Jelentse a fejlesztőknek.";
/* No comment provided by engineer. */
-"Please restart the app and migrate the database to enable push notifications." = "Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges push-értesítések engedélyezéséhez.";
+"Please restart the app and migrate the database to enable push notifications." = "Indítsa újra az alkalmazást az adatbázis-átköltöztetéséhez szükséges leküldéses értesítések engedélyezéséhez.";
/* No comment provided by engineer. */
"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez.";
@@ -4085,7 +4113,7 @@ new chat action */
"Please wait for group moderators to review your request to join the group." = "Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérését.";
/* token info */
-"Please wait for token activation to complete." = "Várjon, amíg a token aktiválása befejeződik.";
+"Please wait for token activation to complete." = "Várjon, amíg a token aktiválása elkészül.";
/* token info */
"Please wait for token to be registered." = "Várjon a token regisztrálására.";
@@ -4181,7 +4209,7 @@ new chat action */
"Prohibit messages reactions." = "A reakciók hozzáadása az üzenetekhez le van tiltva.";
/* No comment provided by engineer. */
-"Prohibit reporting messages to moderators." = "Az üzenetek a moderátorok felé történő jelentésének megtiltása.";
+"Prohibit reporting messages to moderators." = "Az üzenetek jelentése a moderátorok felé le van tiltva.";
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése a tagok között le van tiltva.";
@@ -4229,10 +4257,10 @@ new chat action */
"Proxy requires password" = "A proxy jelszót igényel";
/* No comment provided by engineer. */
-"Push notifications" = "Push-értesítések";
+"Push notifications" = "Leküldéses értesítések";
/* No comment provided by engineer. */
-"Push server" = "Push-kiszolgáló";
+"Push server" = "Leküldéses értesítéskiszolgáló";
/* chat item text */
"quantum resistant e2e encryption" = "végpontok közötti kvantumbiztos titkosítás";
@@ -4241,13 +4269,13 @@ new chat action */
"Quantum resistant encryption" = "Kvantumbiztos titkosítás";
/* No comment provided by engineer. */
-"Rate the app" = "Értékelje az alkalmazást";
+"Rate the app" = "Alkalmazás értékelése";
/* No comment provided by engineer. */
"Reachable chat toolbar" = "Könnyen elérhető csevegési eszköztár";
/* chat item menu */
-"React…" = "Reagálj…";
+"React…" = "Reagálás…";
/* swipe action */
"Read" = "Olvasott";
@@ -4274,7 +4302,7 @@ new chat action */
"Receive errors" = "Üzenetfogadási hibák";
/* No comment provided by engineer. */
-"received answer…" = "válasz fogadása…";
+"received answer…" = "válasz érkezett…";
/* No comment provided by engineer. */
"Received at" = "Fogadva";
@@ -4283,7 +4311,7 @@ new chat action */
"Received at: %@" = "Fogadva: %@";
/* No comment provided by engineer. */
-"received confirmation…" = "visszaigazolás fogadása…";
+"received confirmation…" = "visszaigazolás érkezett…";
/* message info title */
"Received message" = "Fogadott üzenetbuborék színe";
@@ -4360,7 +4388,7 @@ swipe action */
"Reject" = "Elutasítás";
/* No comment provided by engineer. */
-"Reject (sender NOT notified)" = "Elutasítás (a kérés küldője NEM fog értesítést kapni)";
+"Reject (sender NOT notified)" = "Elutasítás (a kérés küldője NEM lesz értesítve)";
/* alert title */
"Reject contact request" = "Partneri kapcsolatkérés elutasítása";
@@ -4380,9 +4408,12 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Eltávolítás";
+/* alert action */
+"Remove and delete messages" = "Eltávolítás és az üzeneteinek törlése";
+
/* No comment provided by engineer. */
"Remove archive?" = "Eltávolítja az archívumot?";
@@ -4395,7 +4426,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Eltávolítás";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Eltávolítja a tagot?";
/* No comment provided by engineer. */
@@ -4501,7 +4532,7 @@ swipe action */
"Reset all hints" = "Tippek visszaállítása";
/* No comment provided by engineer. */
-"Reset all statistics" = "Az összes statisztika visszaállítása";
+"Reset all statistics" = "Összes statisztika visszaállítása";
/* No comment provided by engineer. */
"Reset all statistics?" = "Visszaállítja az összes statisztikát?";
@@ -4690,9 +4721,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "A keresősáv elfogadja a meghívási hivatkozásokat.";
+/* No comment provided by engineer. */
+"Search files" = "Fájlok keresése";
+
+/* No comment provided by engineer. */
+"Search images" = "Képek keresése";
+
+/* No comment provided by engineer. */
+"Search links" = "Hivatkozások keresése";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "Keresés vagy SimpleX-hivatkozás beillesztése";
+/* No comment provided by engineer. */
+"Search videos" = "Videók keresése";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "Hangüzenetek keresése";
+
/* network option */
"sec" = "mp";
@@ -4712,7 +4758,7 @@ chat item action */
"Secured" = "Biztosítva";
/* No comment provided by engineer. */
-"Security assessment" = "Biztonsági kiértékelés";
+"Security assessment" = "Biztonsági felmérés";
/* No comment provided by engineer. */
"Security code" = "Biztonsági kód";
@@ -4721,16 +4767,16 @@ chat item action */
"security code changed" = "biztonsági kódja módosult";
/* chat item action */
-"Select" = "Kijelölés";
+"Select" = "Kiválasztás";
/* No comment provided by engineer. */
-"Select chat profile" = "Csevegési profil kijelölése";
+"Select chat profile" = "Csevegési profil kiválasztása";
/* No comment provided by engineer. */
-"Selected %lld" = "%lld kijelölve";
+"Selected %lld" = "%lld kiválasztva";
/* No comment provided by engineer. */
-"Selected chat preferences prohibit this message." = "A kijelölt csevegési beállítások tiltják ezt az üzenetet.";
+"Selected chat preferences prohibit this message." = "A kiválasztott csevegési beállítások tiltják ezt az üzenetet.";
/* No comment provided by engineer. */
"Self-destruct" = "Önmegsemmisítés";
@@ -4823,13 +4869,13 @@ chat item action */
"Sending file will be stopped." = "A fájl küldése le fog állni.";
/* No comment provided by engineer. */
-"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld partnernél";
+"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések le vannak tiltva %lld partner számára";
/* No comment provided by engineer. */
"Sending receipts is disabled for %lld groups" = "A kézbesítési jelentések le vannak tiltva %lld csoportban";
/* No comment provided by engineer. */
-"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések engedélyezve vannak %lld partnernél";
+"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések engedélyezve vannak %lld partner számára";
/* No comment provided by engineer. */
"Sending receipts is enabled for %lld groups" = "A kézbesítési jelentések engedélyezve vannak %lld csoportban";
@@ -4919,7 +4965,7 @@ chat item action */
"Servers statistics will be reset - this cannot be undone!" = "A kiszolgálók statisztikái visszaállnak – ez a művelet nem vonható vissza!";
/* No comment provided by engineer. */
-"Session code" = "Munkamenet kód";
+"Session code" = "Munkamenet kódja";
/* No comment provided by engineer. */
"Set 1 day" = "Beállítva 1 nap";
@@ -4961,7 +5007,7 @@ chat item action */
"Set passphrase to export" = "Jelmondat beállítása az exportáláshoz";
/* No comment provided by engineer. */
-"Set profile bio and welcome message." = "Névjegy és üdvözlőüzenet beállítása a profilokhoz.";
+"Set profile bio and welcome message." = "Életrajz és üdvözlőüzenet beállítása a profilokhoz.";
/* No comment provided by engineer. */
"Set the message shown to new members!" = "Megjelenítendő üzenet beállítása az új tagok számára!";
@@ -5079,7 +5125,7 @@ chat item action */
"SimpleX address or 1-time link?" = "SimpleX-cím vagy egyszer használható meghívó?";
/* alert title */
-"SimpleX address settings" = "Beállítások automatikus elfogadása";
+"SimpleX address settings" = "SimpleX-címbeállítások";
/* simplex link type */
"SimpleX channel link" = "SimpleX-csatornahivatkozás";
@@ -5142,7 +5188,7 @@ chat item action */
"Skipped messages" = "Kihagyott üzenetek";
/* No comment provided by engineer. */
-"Small groups (max 20)" = "Kis csoportok (max. 20 tag)";
+"Small groups (max 20)" = "Kis csoportok (legfeljebb 20 tag)";
/* No comment provided by engineer. */
"SMP server" = "SMP-kiszolgáló";
@@ -5182,7 +5228,7 @@ report reason */
"standard end-to-end encryption" = "szabványos végpontok közötti titkosítás";
/* No comment provided by engineer. */
-"Start chat" = "Csevegés indítása";
+"Start chat" = "Csevegés elindítása";
/* No comment provided by engineer. */
"Start chat?" = "Elindítja a csevegést?";
@@ -5194,7 +5240,7 @@ report reason */
"Starting from %@." = "Statisztikagyűjtés kezdete: %@.";
/* No comment provided by engineer. */
-"starting…" = "indítás…";
+"starting…" = "hívás indítása…";
/* No comment provided by engineer. */
"Statistics" = "Statisztikák";
@@ -5425,10 +5471,10 @@ report reason */
"The second preset operator in the app!" = "A második előre beállított üzemeltető az alkalmazásban!";
/* No comment provided by engineer. */
-"The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅";
+"The second tick we missed! ✅" = "A második pipa, ami már nagyon hiányzott! ✅";
/* alert message */
-"The sender will NOT be notified" = "A kérés küldője NEM fog értesítést kapni";
+"The sender will NOT be notified" = "A kérés küldője NEM lesz értesítve";
/* No comment provided by engineer. */
"The servers for new connections of your current chat profile **%@**." = "A jelenlegi **%@** nevű csevegési profiljához tartozó új kapcsolatok kiszolgálói.";
@@ -5458,10 +5504,10 @@ report reason */
"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza – az összes fogadott és küldött fájl a médiatartalmakkal együtt törölve lesznek. Az alacsony felbontású képek viszont megmaradnak.";
/* No comment provided by engineer. */
-"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza – a kijelöltnél korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.";
+"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek. Ez több percet is igénybe vehet.";
/* alert message */
-"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Ez a művelet nem vonható vissza – a kijelölt üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.";
+"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Ez a művelet nem vonható vissza – a kiválasztott üzenettől korábban küldött és fogadott üzenetek törölve lesznek a csevegésből.";
/* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ez a művelet nem vonható vissza – profiljai, partnerei, üzenetei és fájljai véglegesen törölve lesznek.";
@@ -5557,7 +5603,7 @@ report reason */
"To send commands you must be connected." = "A parancsok küldéséhez kapcsolódva kell lennie.";
/* No comment provided by engineer. */
-"To support instant push notifications the chat database has to be migrated." = "Az azonnali push-értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.";
+"To support instant push notifications the chat database has to be migrated." = "Az azonnali leküldéses értesítések támogatásához a csevegési adatbázis átköltöztetése szükséges.";
/* alert message */
"To use another profile after connection attempt, delete the chat and use the link again." = "Másik profil használatához a kapcsolatfelvételi kísérlet után törölje a csevegést, és használja újra a hivatkozást.";
@@ -5566,7 +5612,7 @@ report reason */
"To use the servers of **%@**, accept conditions of use." = "A(z) **%@** kiszolgálóinak használatához fogadja el a használati feltételeket.";
/* No comment provided by engineer. */
-"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás hitelesítéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.";
+"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) a partnere eszközén lévő kóddal.";
/* No comment provided by engineer. */
"Toggle chat list:" = "Csevegési lista ki/be:";
@@ -5701,7 +5747,7 @@ report reason */
"Update" = "Frissítés";
/* No comment provided by engineer. */
-"Update database passphrase" = "Az adatbázis jelmondatának módosítása";
+"Update database passphrase" = "Adatbázis jelmondatának módosítása";
/* No comment provided by engineer. */
"Update network settings?" = "Módosítja a hálózati beállításokat?";
@@ -5830,7 +5876,7 @@ report reason */
"Use web port" = "Webport használata";
/* No comment provided by engineer. */
-"User selection" = "Felhasználó kijelölése";
+"User selection" = "Felhasználó kiválasztása";
/* No comment provided by engineer. */
"Username" = "Felhasználónév";
@@ -5845,25 +5891,25 @@ report reason */
"v%@ (%@)" = "v%@ (%@)";
/* No comment provided by engineer. */
-"Verify code with desktop" = "Kód hitelesítése a számítógépen";
+"Verify code with desktop" = "Kód ellenőrzése a számítógépen";
/* No comment provided by engineer. */
-"Verify connection" = "Kapcsolat hitelesítése";
+"Verify connection" = "Kapcsolat ellenőrzése";
/* No comment provided by engineer. */
-"Verify connection security" = "Biztonságos kapcsolat hitelesítése";
+"Verify connection security" = "Biztonságos kapcsolat ellenőrzése";
/* No comment provided by engineer. */
-"Verify connections" = "Kapcsolatok hitelesítése";
+"Verify connections" = "Kapcsolatok ellenőrzése";
/* No comment provided by engineer. */
-"Verify database passphrase" = "Az adatbázis jelmondatának hitelesítése";
+"Verify database passphrase" = "Adatbázis jelmondatának ellenőrzése";
/* No comment provided by engineer. */
-"Verify passphrase" = "Jelmondat hitelesítése";
+"Verify passphrase" = "Jelmondat ellenőrzése";
/* No comment provided by engineer. */
-"Verify security code" = "Biztonsági kód hitelesítése";
+"Verify security code" = "Biztonsági kód ellenőrzése";
/* No comment provided by engineer. */
"Via browser" = "Böngészőn keresztül";
@@ -5890,7 +5936,7 @@ report reason */
"Video call" = "Videóhívás";
/* No comment provided by engineer. */
-"video call (not e2e encrypted)" = "videóhívás (nem e2e titkosított)";
+"video call (not e2e encrypted)" = "videóhívás (végpontok között NEM titkosított)";
/* No comment provided by engineer. */
"Video will be received when your contact completes uploading it." = "A videó akkor érkezik meg, amikor a küldője befejezte annak feltöltését.";
@@ -5898,6 +5944,9 @@ report reason */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "A videó akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!";
+/* No comment provided by engineer. */
+"Videos" = "Videók";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "Videók és fájlok legfeljebb 1GB méretig";
@@ -6091,7 +6140,7 @@ report reason */
"You are invited to group" = "Ön meghívást kapott a csoportba";
/* subscription status explanation */
-"You are not connected to the server used to receive messages from this connection (no subscription)." = "Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs előfizetés).";
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "Ön nem kapcsolódott ahhoz a kiszolgálóhoz, amely az adott partnerétől érkező üzenetek fogadására szolgál (nincs feliratkozás).";
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "Ön nem kapcsolódik ezekhez a kiszolgálókhoz. A privát útválasztás az üzenetek kézbesítésére szolgál.";
@@ -6148,7 +6197,7 @@ report reason */
"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a SimpleX-címet a partnereivel, hogy kapcsolatba léphessenek vele: **%@**.";
/* No comment provided by engineer. */
-"You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás „Beállítások / Adatbázis” menüben vagy az alkalmazás újraindításával indíthatja el";
+"You can start chat via app Settings / Database or by restarting the app" = "A csevegés elindítható az alkalmazás „Beállítások / Adatbázis” menüjében vagy az alkalmazás újraindításával";
/* No comment provided by engineer. */
"You can still view conversation with %@ in the list of chats." = "A(z) %@ nevű partnerével folytatott beszélgetéseit továbbra is megtekintheti a csevegések listájában.";
@@ -6181,7 +6230,7 @@ report reason */
"you changed role of %@ to %@" = "Ön a következőre módosította %1$@ szerepkörét: „%2$@”";
/* No comment provided by engineer. */
-"You could not be verified; please try again." = "Nem sikerült hitelesíteni; próbálja meg újra.";
+"You could not be verified; please try again." = "Nem sikerült ellenőrizni; próbálja meg újra.";
/* No comment provided by engineer. */
"You decide who can connect." = "Ön dönti el, hogy kivel beszélget.";
@@ -6262,10 +6311,10 @@ report reason */
"You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak.";
/* No comment provided by engineer. */
-"You will stop receiving messages from this chat. Chat history will be preserved." = "Ön nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.";
+"You will stop receiving messages from this chat. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csevegésből, de a csevegés előzményei megmaradnak.";
/* No comment provided by engineer. */
-"You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.";
+"You will stop receiving messages from this group. Chat history will be preserved." = "Nem fog több üzenetet kapni ebből a csoportból, de a csevegés előzményei megmaradnak.";
/* No comment provided by engineer. */
"You won't lose your contacts if you later delete your address." = "Nem veszíti el a partnereit, ha később törli a címét.";
@@ -6307,13 +6356,13 @@ report reason */
"Your contact" = "Partner";
/* No comment provided by engineer. */
-"Your contact sent a file that is larger than currently supported maximum size (%@)." = "A partnere a jelenleg megengedett maximális méretű (%@) fájlnál nagyobbat küldött.";
+"Your contact sent a file that is larger than currently supported maximum size (%@)." = "A partnere a jelenleg támogatott legnagyobb (%@) fájlméretnél nagyobbat küldött.";
/* No comment provided by engineer. */
"Your contacts can allow full message deletion." = "A partnerei engedélyezhetik a teljes üzenet törlését.";
/* No comment provided by engineer. */
-"Your contacts will remain connected." = "A partnerei továbbra is kapcsolódva maradnak.";
+"Your contacts will remain connected." = "A partnereivel továbbra is kapcsolatban marad.";
/* No comment provided by engineer. */
"Your credentials may be sent unencrypted." = "A hitelesítési adatai titkosítatlanul is elküldhetők.";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index 511a6835e5..3955f267ce 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -512,6 +512,9 @@ swipe action */
/* feature role */
"all members" = "tutti i membri";
+/* No comment provided by engineer. */
+"All messages" = "Tutti i messaggi";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Tutti i messaggi e i file vengono inviati **crittografati end-to-end**, con sicurezza resistenti alla quantistica nei messaggi diretti.";
@@ -734,6 +737,9 @@ swipe action */
/* No comment provided by engineer. */
"Audio and video calls" = "Chiamate audio e video";
+/* No comment provided by engineer. */
+"Audio call" = "Chiamata audio";
+
/* No comment provided by engineer. */
"audio call (not e2e encrypted)" = "chiamata audio (non crittografata e2e)";
@@ -1730,10 +1736,17 @@ swipe action */
/* No comment provided by engineer. */
"Delete member message?" = "Eliminare il messaggio del membro?";
+/* No comment provided by engineer. */
+"Delete member messages" = "Elimina i messaggi del membro";
+
+/* alert title */
+"Delete member messages?" = "Eliminare i messaggi del membro?";
+
/* No comment provided by engineer. */
"Delete message?" = "Eliminare il messaggio?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Elimina messaggi";
/* No comment provided by engineer. */
@@ -2564,6 +2577,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "File e contenuti multimediali vietati!";
+/* No comment provided by engineer. */
+"Filter" = "Filtro";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "Filtra le chat non lette e preferite.";
@@ -2867,6 +2883,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "L'immagine verrà ricevuta quando il tuo contatto sarà in linea, aspetta o controlla più tardi!";
+/* No comment provided by engineer. */
+"Images" = "Immagini";
+
/* No comment provided by engineer. */
"Immediately" = "Immediatamente";
@@ -3050,6 +3069,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Invita amici";
+/* No comment provided by engineer. */
+"Invite member" = "Invita membro";
+
/* No comment provided by engineer. */
"Invite members" = "Invita membri";
@@ -3203,6 +3225,9 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "Desktop collegati";
+/* No comment provided by engineer. */
+"Links" = "Link";
+
/* swipe action */
"List" = "Elenco";
@@ -3296,6 +3321,9 @@ snd error text */
/* No comment provided by engineer. */
"Member is deleted - can't accept request" = "Il membro è eliminato - impossibile accettare la richiesta";
+/* alert message */
+"Member messages will be deleted - this cannot be undone!" = "I messaggi del membro verranno eliminati. Non è reversibile!";
+
/* chat feature */
"Member reports" = "Segnalazioni dei membri";
@@ -3308,10 +3336,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Il ruolo del membro verrà cambiato in \"%@\". Il membro riceverà un invito nuovo.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Il membro verrà rimosso dalla chat, non è reversibile!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!";
/* alert message */
@@ -3899,7 +3927,7 @@ new chat action */
"Open new chat" = "Apri una chat nuova";
/* new chat action */
-"Open new group" = "Apri un gruppo nuovo";
+"Open new group" = "Apri il nuovo gruppo";
/* No comment provided by engineer. */
"Open Settings" = "Apri le impostazioni";
@@ -4380,9 +4408,12 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Il server relay protegge il tuo indirizzo IP, ma può osservare la durata della chiamata.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Rimuovi";
+/* alert action */
+"Remove and delete messages" = "Rimuovi ed elimina i messaggi";
+
/* No comment provided by engineer. */
"Remove archive?" = "Rimuovere l'archivio?";
@@ -4395,7 +4426,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Rimuovi membro";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Rimuovere il membro?";
/* No comment provided by engineer. */
@@ -4690,9 +4721,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "La barra di ricerca accetta i link di invito.";
+/* No comment provided by engineer. */
+"Search files" = "Cerca file";
+
+/* No comment provided by engineer. */
+"Search images" = "Cerca immagini";
+
+/* No comment provided by engineer. */
+"Search links" = "Cerca link";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "Cerca o incolla un link SimpleX";
+/* No comment provided by engineer. */
+"Search videos" = "Cerca video";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "Cerca messaggi vocali";
+
/* network option */
"sec" = "sec";
@@ -5898,6 +5944,9 @@ report reason */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "Il video verrà ricevuto quando il tuo contatto sarà in linea, attendi o controlla più tardi!";
+/* No comment provided by engineer. */
+"Videos" = "Video";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "Video e file fino a 1 GB";
diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings
index d4510af72f..480eb39d36 100644
--- a/apps/ios/ja.lproj/Localizable.strings
+++ b/apps/ios/ja.lproj/Localizable.strings
@@ -374,9 +374,18 @@ swipe action */
/* No comment provided by engineer. */
"Acknowledged" = "了承済み";
+/* No comment provided by engineer. */
+"Active connections" = "アクティブな接続";
+
/* No comment provided by engineer. */
"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "プロフィールにアドレスを追加し、連絡先があなたのアドレスを他の人と共有できるようにします。プロフィールの更新は連絡先に送信されます。";
+/* No comment provided by engineer. */
+"Add friends" = "友達を追加";
+
+/* No comment provided by engineer. */
+"Add list" = "リストを追加";
+
/* No comment provided by engineer. */
"Add profile" = "プロフィールを追加";
@@ -386,9 +395,15 @@ swipe action */
/* No comment provided by engineer. */
"Add servers by scanning QR codes." = "QRコードでサーバを追加する。";
+/* No comment provided by engineer. */
+"Add team members" = "チームメンバーを追加";
+
/* No comment provided by engineer. */
"Add to another device" = "別の端末に追加";
+/* No comment provided by engineer. */
+"Add to list" = "リストに追加";
+
/* No comment provided by engineer. */
"Add welcome message" = "ウェルカムメッセージを追加";
@@ -404,6 +419,9 @@ swipe action */
/* No comment provided by engineer. */
"Address change will be aborted. Old receiving address will be used." = "アドレス変更は中止されます。古い受信アドレスが使用されます。";
+/* No comment provided by engineer. */
+"Address settings" = "アドレス設定";
+
/* member role */
"admin" = "管理者";
@@ -422,6 +440,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "暗号化に同意しています…";
+/* No comment provided by engineer. */
+"All" = "すべて";
+
/* No comment provided by engineer. */
"All app data is deleted." = "すべてのアプリデータが削除されます。";
@@ -434,6 +455,9 @@ swipe action */
/* No comment provided by engineer. */
"All group members will remain connected." = "グループ全員の接続が継続します。";
+/* No comment provided by engineer. */
+"All messages will be deleted - this cannot be undone!" = "すべてのメッセージが削除されます。この操作は元に戻せません!";
+
/* No comment provided by engineer. */
"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "全てのメッセージが削除されます(※注意:元に戻せません!※)。削除されるのは片方あなたのメッセージのみ。";
@@ -455,9 +479,15 @@ swipe action */
/* No comment provided by engineer. */
"Allow calls only if your contact allows them." = "連絡先が通話を許可している場合のみ通話を許可する。";
+/* No comment provided by engineer. */
+"Allow calls?" = "通話を許可しますか?";
+
/* No comment provided by engineer. */
"Allow disappearing messages only if your contact allows it to you." = "連絡先が許可している場合のみ消えるメッセージを許可する。";
+/* No comment provided by engineer. */
+"Allow downgrade" = "ダウングレードを許可する";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "送信相手も永久メッセージ削除を許可する時のみに許可する。(24時間)";
@@ -530,6 +560,9 @@ swipe action */
/* No comment provided by engineer. */
"An empty chat profile with the provided name is created, and the app opens as usual." = "指定された名前の空のチャット プロファイルが作成され、アプリが通常どおり開きます。";
+/* report reason */
+"Another reason" = "他の理由";
+
/* No comment provided by engineer. */
"Answer call" = "通話に応答";
@@ -569,9 +602,15 @@ swipe action */
/* No comment provided by engineer. */
"Apply to" = "に適用する";
+/* No comment provided by engineer. */
+"Archive" = "アーカイブ";
+
/* No comment provided by engineer. */
"Archive and upload" = "アーカイブとアップロード";
+/* No comment provided by engineer. */
+"Archived contacts" = "アーカイブされた連絡先";
+
/* No comment provided by engineer. */
"Attach" = "添付する";
@@ -668,6 +707,9 @@ swipe action */
/* No comment provided by engineer. */
"Calls" = "通話";
+/* alert title */
+"Can't change profile" = "プロフィールを変更できません";
+
/* No comment provided by engineer. */
"Can't invite contact!" = "連絡先を招待できません!";
@@ -679,12 +721,18 @@ alert button
new chat action */
"Cancel" = "中止";
+/* No comment provided by engineer. */
+"Cancel migration" = "移行を中止する";
+
/* feature offered item */
"cancelled %@" = "キャンセルされました %@";
/* No comment provided by engineer. */
"Cannot access keychain to save database password" = "データベースのパスワードを保存するためのキーチェーンにアクセスできません";
+/* No comment provided by engineer. */
+"Cannot forward message" = "メッセージを転送できません";
+
/* alert title */
"Cannot receive file" = "ファイル受信ができません";
@@ -734,6 +782,9 @@ set passcode view */
/* chat item text */
"changing address…" = "アドレスを変更しています…";
+/* No comment provided by engineer. */
+"Chat" = "チャット";
+
/* No comment provided by engineer. */
"Chat console" = "チャットのコンソール";
@@ -752,6 +803,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat is stopped" = "チャットが停止してます";
+/* No comment provided by engineer. */
+"Chat list" = "チャット一覧";
+
/* No comment provided by engineer. */
"Chat preferences" = "チャット設定";
@@ -764,6 +818,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chats" = "チャット";
+/* No comment provided by engineer. */
+"Check messages every 20 min." = "20分おきにメッセージを確認する。";
+
/* alert title */
"Check server address and try again." = "サーバのアドレスを確認してから再度試してください。";
@@ -1168,7 +1225,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "メッセージを削除しますか?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "メッセージを削除";
/* No comment provided by engineer. */
@@ -2103,7 +2161,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "メンバーの役割が \"%@\" に変更されます。 メンバーは新たな招待を受け取ります。";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "メンバーをグループから除名する (※元に戻せません※)!";
/* No comment provided by engineer. */
@@ -2644,13 +2702,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "リレー サーバーは IP アドレスを保護しますが、通話時間は監視されます。";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "削除";
/* No comment provided by engineer. */
"Remove member" = "メンバーを除名する";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "メンバーを除名しますか?";
/* No comment provided by engineer. */
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index 79e3da3b01..29f8bb5b3f 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -1688,7 +1688,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Verwijder bericht?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Verwijder berichten";
/* No comment provided by engineer. */
@@ -3197,10 +3198,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "De rol van lid wordt gewijzigd in \"%@\". Het lid ontvangt een nieuwe uitnodiging.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Lid wordt verwijderd uit de chat - dit kan niet ongedaan worden gemaakt!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!";
/* alert message */
@@ -4218,7 +4219,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay server beschermt uw IP-adres, maar kan de duur van het gesprek observeren.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Verwijderen";
/* No comment provided by engineer. */
@@ -4230,7 +4231,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Lid verwijderen";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Lid verwijderen?";
/* No comment provided by engineer. */
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 34c79eeef4..ed1f8850d8 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -257,7 +257,7 @@
"`a + b`" = "\\`a + b`";
/* email text */
-"Hi!
\nConnect to me via SimpleX Chat
" = "Cześć!
\nPołącz się ze mną poprzez SimpleX Chat.
";
+"Hi!
\nConnect to me via SimpleX Chat
" = "Cześć!
\nPołącz się ze mną poprzez SimpleX Chat
";
/* No comment provided by engineer. */
"~strike~" = "\\~strajk~";
@@ -346,12 +346,21 @@ alert action
swipe action */
"Accept" = "Akceptuj";
+/* alert action */
+"Accept as member" = "Zaakceptuj jako członka";
+
+/* alert action */
+"Accept as observer" = "Zaakceptuj jako obserwatora";
+
/* No comment provided by engineer. */
"Accept conditions" = "Zaakceptuj warunki";
/* No comment provided by engineer. */
"Accept connection request?" = "Zaakceptować prośbę o połączenie?";
+/* alert title */
+"Accept contact request" = "Zaakceptuj prośby o kontakt";
+
/* notification body */
"Accept contact request from %@?" = "Zaakceptuj prośbę o kontakt od %@?";
@@ -359,12 +368,24 @@ swipe action */
swipe action */
"Accept incognito" = "Akceptuj incognito";
+/* alert title */
+"Accept member" = "Zaakceptuj członka";
+
+/* rcv group event chat item */
+"accepted %@" = "zaakceptowano %@";
+
/* call status */
"accepted call" = "zaakceptowane połączenie";
/* No comment provided by engineer. */
"Accepted conditions" = "Zaakceptowano warunki";
+/* chat list item title */
+"accepted invitation" = "zaproszenie zaakceptowane";
+
+/* rcv group event chat item */
+"accepted you" = "przyjął cię";
+
/* No comment provided by engineer. */
"Acknowledged" = "Potwierdzono";
@@ -386,6 +407,9 @@ swipe action */
/* No comment provided by engineer. */
"Add list" = "Dodaj listę";
+/* placeholder for sending contact request */
+"Add message" = "Dodaj wiadomość";
+
/* No comment provided by engineer. */
"Add profile" = "Dodaj profil";
@@ -461,6 +485,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "uzgadnianie szyfrowania…";
+/* member criteria value */
+"all" = "wszystkie";
+
/* No comment provided by engineer. */
"All" = "Wszystko";
@@ -485,6 +512,9 @@ swipe action */
/* feature role */
"all members" = "wszyscy członkowie";
+/* No comment provided by engineer. */
+"All messages" = "Wszystkie wiadomości";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Wszystkie wiadomości i pliki są wysyłane **z szyfrowaniem end-to-end**, z bezpieczeństwem postkwantowym w wiadomościach bezpośrednich.";
@@ -503,6 +533,9 @@ swipe action */
/* No comment provided by engineer. */
"All reports will be archived for you." = "Wszystkie raporty zostaną dla Ciebie zarchiwizowane.";
+/* No comment provided by engineer. */
+"All servers" = "Wszystkie serwery";
+
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Wszystkie Twoje kontakty pozostaną połączone.";
@@ -527,6 +560,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow downgrade" = "Zezwól na obniżenie wersji";
+/* No comment provided by engineer. */
+"Allow files and media only if your contact allows them." = "Zezwalaj na pliki i media tylko wtedy, gdy Twój kontakt na to pozwala.";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny)";
@@ -578,6 +614,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow your contacts to send disappearing messages." = "Zezwól swoim kontaktom na wysyłanie znikających wiadomości.";
+/* No comment provided by engineer. */
+"Allow your contacts to send files and media." = "Pozwól kontaktom wysyłać pliki i media.";
+
/* No comment provided by engineer. */
"Allow your contacts to send voice messages." = "Zezwól swoim kontaktom na wysyłanie wiadomości głosowych.";
@@ -680,6 +719,9 @@ swipe action */
/* No comment provided by engineer. */
"Archived contacts" = "Zarchiwizowane kontakty";
+/* No comment provided by engineer. */
+"archived report" = "zarchiwizowany raport";
+
/* No comment provided by engineer. */
"Archiving database" = "Archiwizowanie bazy danych";
@@ -695,6 +737,9 @@ swipe action */
/* No comment provided by engineer. */
"Audio and video calls" = "Połączenia audio i wideo";
+/* No comment provided by engineer. */
+"Audio call" = "Połączenie audio";
+
/* No comment provided by engineer. */
"audio call (not e2e encrypted)" = "połączenie audio (nie szyfrowane e2e)";
@@ -755,6 +800,9 @@ swipe action */
/* No comment provided by engineer. */
"Better groups" = "Lepsze grupy";
+/* No comment provided by engineer. */
+"Better groups performance" = "Lepsze działanie grup";
+
/* No comment provided by engineer. */
"Better message dates." = "Lepsze daty wiadomości.";
@@ -767,12 +815,21 @@ swipe action */
/* No comment provided by engineer. */
"Better notifications" = "Lepsze powiadomienia";
+/* No comment provided by engineer. */
+"Better privacy and security" = "Lepsza prywatność i bezpieczeństwo";
+
/* No comment provided by engineer. */
"Better security ✅" = "Lepsze zabezpieczenia ✅";
/* No comment provided by engineer. */
"Better user experience" = "Lepszy interfejs użytkownika";
+/* No comment provided by engineer. */
+"Bio" = "Bio";
+
+/* alert title */
+"Bio too large" = "Bio jest za długie";
+
/* No comment provided by engineer. */
"Black" = "Czarny";
@@ -816,6 +873,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"bold" = "pogrubiona";
+/* No comment provided by engineer. */
+"Bot" = "Bot";
+
/* No comment provided by engineer. */
"Both you and your contact can add message reactions." = "Zarówno Ty, jak i Twój kontakt możecie dodawać reakcje wiadomości.";
@@ -828,6 +888,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Both you and your contact can send disappearing messages." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać znikające wiadomości.";
+/* No comment provided by engineer. */
+"Both you and your contact can send files and media." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać pliki i media.";
+
/* No comment provided by engineer. */
"Both you and your contact can send voice messages." = "Zarówno Ty, jak i Twój kontakt możecie wysyłać wiadomości głosowe.";
@@ -840,12 +903,18 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Business chats" = "Czaty biznesowe";
+/* No comment provided by engineer. */
+"Business connection" = "Kontakty biznesowe";
+
/* No comment provided by engineer. */
"Businesses" = "Firmy";
/* No comment provided by engineer. */
"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Według profilu czatu (domyślnie) lub [według połączenia](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA).";
+/* No comment provided by engineer. */
+"By using SimpleX Chat you agree to:\n- send only legal content in public groups.\n- respect other users – no spam." = "Korzystając z SimpleX Chat, zgadzasz się:\n- wysyłać tylko legalne treści w grupach publicznych.\n- szanować innych użytkowników – nie spamować.";
+
/* No comment provided by engineer. */
"call" = "zadzwoń";
@@ -876,6 +945,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't call member" = "Nie można zadzwonić do członka";
+/* alert title */
+"Can't change profile" = "Nie można zmienić profilu";
+
/* No comment provided by engineer. */
"Can't invite contact!" = "Nie można zaprosić kontaktu!";
@@ -885,6 +957,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Nie można wysłać wiadomości do członka";
+/* No comment provided by engineer. */
+"can't send messages" = "nie można wysłać wiadomości";
+
/* alert action
alert button
new chat action */
@@ -914,6 +989,9 @@ new chat action */
/* No comment provided by engineer. */
"Change" = "Zmień";
+/* alert title */
+"Change automatic message deletion?" = "Zmienić automatyczne usuwanie wiadomości?";
+
/* authentication reason */
"Change chat profiles" = "Zmień profil czatu";
@@ -1020,9 +1098,21 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć!";
+/* chat toolbar */
+"Chat with admins" = "Czatuj z administratorami";
+
+/* No comment provided by engineer. */
+"Chat with member" = "Czatuj z członkiem";
+
+/* No comment provided by engineer. */
+"Chat with members before they join." = "Porozmawiaj z członkami, zanim dołączą.";
+
/* No comment provided by engineer. */
"Chats" = "Czaty";
+/* No comment provided by engineer. */
+"Chats with members" = "Czaty z członkami";
+
/* No comment provided by engineer. */
"Check messages every 20 min." = "Sprawdzaj wiadomości co 20 min.";
@@ -1062,6 +1152,12 @@ set passcode view */
/* No comment provided by engineer. */
"Clear conversation?" = "Wyczyścić rozmowę?";
+/* No comment provided by engineer. */
+"Clear group?" = "Wyczyścić grupę?";
+
+/* No comment provided by engineer. */
+"Clear or delete group?" = "Wyczyścić lub usunąć grupę?";
+
/* No comment provided by engineer. */
"Clear private notes?" = "Wyczyścić prywatne notatki?";
@@ -1077,6 +1173,9 @@ set passcode view */
/* No comment provided by engineer. */
"colored" = "kolorowy";
+/* report reason */
+"Community guidelines violation" = "Naruszenie zasad społeczności";
+
/* server test step */
"Compare file" = "Porównaj plik";
@@ -1101,9 +1200,21 @@ set passcode view */
/* alert button */
"Conditions of use" = "Warunki użytkowania";
+/* No comment provided by engineer. */
+"Conditions will be accepted for the operator(s): **%@**." = "Warunki zostaną zaakceptowane dla operatora(-ów): **%@**.";
+
+/* No comment provided by engineer. */
+"Conditions will be accepted on: %@." = "Warunki zostaną zaakceptowane w dniu: %@.";
+
+/* No comment provided by engineer. */
+"Conditions will be automatically accepted for enabled operators on: %@." = "Warunki zostaną automatycznie zaakceptowane dla aktywnych operatorów w dniu: %@.";
+
/* No comment provided by engineer. */
"Configure ICE servers" = "Skonfiguruj serwery ICE";
+/* No comment provided by engineer. */
+"Configure server operators" = "Skonfiguruj operatorów serwerów";
+
/* No comment provided by engineer. */
"Confirm" = "Potwierdź";
@@ -1134,12 +1245,18 @@ set passcode view */
/* No comment provided by engineer. */
"Confirm upload" = "Potwierdź wgranie";
+/* token status text */
+"Confirmed" = "Potwierdzony";
+
/* server test step */
"Connect" = "Połącz";
/* No comment provided by engineer. */
"Connect automatically" = "Łącz automatycznie";
+/* No comment provided by engineer. */
+"Connect faster! 🚀" = "Połącz się szybciej! 🚀";
+
/* No comment provided by engineer. */
"Connect to desktop" = "Połącz do komputera";
@@ -1224,6 +1341,9 @@ set passcode view */
/* No comment provided by engineer. */
"Connection and servers status." = "Stan połączenia i serwerów.";
+/* No comment provided by engineer. */
+"Connection blocked" = "Połączenie zablokowane";
+
/* alert title */
"Connection error" = "Błąd połączenia";
@@ -1233,12 +1353,24 @@ set passcode view */
/* chat list item title (it should not be shown */
"connection established" = "połączenie ustanowione";
+/* No comment provided by engineer. */
+"Connection is blocked by server operator:\n%@" = "Połączenie zostało zablokowane przez operatora serwera:\n%@";
+
+/* No comment provided by engineer. */
+"Connection not ready." = "Połączenie nie jest gotowe.";
+
/* No comment provided by engineer. */
"Connection notifications" = "Powiadomienia o połączeniu";
/* No comment provided by engineer. */
"Connection request sent!" = "Prośba o połączenie wysłana!";
+/* No comment provided by engineer. */
+"Connection requires encryption renegotiation." = "Połączenie wymaga renegocjacji szyfrowania.";
+
+/* No comment provided by engineer. */
+"Connection security" = "Bezpieczeństwo połączenia";
+
/* No comment provided by engineer. */
"Connection terminated" = "Połączenie zakończone";
@@ -1263,9 +1395,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Kontakt już istnieje";
+/* No comment provided by engineer. */
+"contact deleted" = "kontakt usunięty";
+
/* No comment provided by engineer. */
"Contact deleted!" = "Kontakt usunięty!";
+/* No comment provided by engineer. */
+"contact disabled" = "kontakt wyłączony";
+
/* No comment provided by engineer. */
"contact has e2e encryption" = "kontakt posiada szyfrowanie e2e";
@@ -1284,9 +1422,18 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Nazwa kontaktu";
+/* No comment provided by engineer. */
+"contact not ready" = "kontakt nie gotowy";
+
/* No comment provided by engineer. */
"Contact preferences" = "Preferencje kontaktu";
+/* No comment provided by engineer. */
+"Contact requests from groups" = "Prośby o kontakt od grup";
+
+/* No comment provided by engineer. */
+"contact should accept…" = "kontakt powinien zaakceptować…";
+
/* No comment provided by engineer. */
"Contact will be deleted - this cannot be undone!" = "Kontakt zostanie usunięty – nie można tego cofnąć!";
@@ -1296,6 +1443,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contacts can mark messages for deletion; you will be able to view them." = "Kontakty mogą oznaczać wiadomości do usunięcia; będziesz mógł je zobaczyć.";
+/* blocking reason */
+"Content violates conditions of use" = "Treść narusza warunki użytkowania";
+
/* No comment provided by engineer. */
"Continue" = "Kontynuuj";
@@ -1320,6 +1470,9 @@ set passcode view */
/* No comment provided by engineer. */
"Create" = "Utwórz";
+/* No comment provided by engineer. */
+"Create 1-time link" = "Utwórz jednorazowy link";
+
/* No comment provided by engineer. */
"Create a group using a random profile." = "Utwórz grupę używając losowego profilu.";
@@ -1335,6 +1488,9 @@ set passcode view */
/* No comment provided by engineer. */
"Create link" = "Utwórz link";
+/* No comment provided by engineer. */
+"Create list" = "Utwórz listę";
+
/* No comment provided by engineer. */
"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Utwórz nowy profil w [aplikacji desktopowej](https://simplex.chat/downloads/). 💻";
@@ -1347,6 +1503,9 @@ set passcode view */
/* No comment provided by engineer. */
"Create SimpleX address" = "Utwórz adres SimpleX";
+/* No comment provided by engineer. */
+"Create your address" = "Utwórz swój adres";
+
/* No comment provided by engineer. */
"Create your profile" = "Utwórz swój profil";
@@ -1368,6 +1527,9 @@ set passcode view */
/* No comment provided by engineer. */
"creator" = "twórca";
+/* No comment provided by engineer. */
+"Current conditions text couldn't be loaded, you can review conditions via this link:" = "Nie można załadować tekstu dotyczącego aktualnych warunków. Możesz zapoznać się z warunkami, klikając ten link:";
+
/* No comment provided by engineer. */
"Current Passcode" = "Aktualny Pin";
@@ -1386,6 +1548,9 @@ set passcode view */
/* No comment provided by engineer. */
"Custom time" = "Niestandardowy czas";
+/* No comment provided by engineer. */
+"Customizable message shape." = "Konfigurowalny kształt wiadomości.";
+
/* No comment provided by engineer. */
"Customize theme" = "Dostosuj motyw";
@@ -1502,12 +1667,24 @@ swipe action */
/* No comment provided by engineer. */
"Delete and notify contact" = "Usuń i powiadom kontakt";
+/* No comment provided by engineer. */
+"Delete chat" = "Usuń czat";
+
+/* No comment provided by engineer. */
+"Delete chat messages from your device." = "Usuń wiadomości czatu ze swojego urządzenia.";
+
/* No comment provided by engineer. */
"Delete chat profile" = "Usuń profil czatu";
/* No comment provided by engineer. */
"Delete chat profile?" = "Usunąć profil czatu?";
+/* alert title */
+"Delete chat with member?" = "Usunąć czat z członkiem?";
+
+/* No comment provided by engineer. */
+"Delete chat?" = "Usunąć czat?";
+
/* No comment provided by engineer. */
"Delete connection" = "Usuń połączenie";
@@ -1553,13 +1730,23 @@ swipe action */
/* No comment provided by engineer. */
"Delete link?" = "Usunąć link?";
+/* alert title */
+"Delete list?" = "Usunąć listę?";
+
/* No comment provided by engineer. */
"Delete member message?" = "Usunąć wiadomość członka?";
+/* No comment provided by engineer. */
+"Delete member messages" = "Usuń wiadomości członków";
+
+/* alert title */
+"Delete member messages?" = "Usunąć wiadomości członków?";
+
/* No comment provided by engineer. */
"Delete message?" = "Usunąć wiadomość?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Usuń wiadomości";
/* No comment provided by engineer. */
@@ -1571,6 +1758,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete old database?" = "Usunąć starą bazę danych?";
+/* No comment provided by engineer. */
+"Delete or moderate up to 200 messages." = "Usuń lub moderuj do 200 wiadomości.";
+
/* No comment provided by engineer. */
"Delete pending connection?" = "Usunąć oczekujące połączenie?";
@@ -1580,6 +1770,9 @@ swipe action */
/* server test step */
"Delete queue" = "Usuń kolejkę";
+/* No comment provided by engineer. */
+"Delete report" = "Usuń raport";
+
/* No comment provided by engineer. */
"Delete up to 20 messages at once." = "Usuń do 20 wiadomości na raz.";
@@ -1610,6 +1803,9 @@ swipe action */
/* No comment provided by engineer. */
"Deletion errors" = "Błędy usuwania";
+/* No comment provided by engineer. */
+"Delivered even when Apple drops them." = "Dostarczane nawet wtedy, gdy Apple je wycofa.";
+
/* No comment provided by engineer. */
"Delivery" = "Dostarczenie";
@@ -1619,9 +1815,15 @@ swipe action */
/* No comment provided by engineer. */
"Delivery receipts!" = "Potwierdzenia dostawy!";
+/* No comment provided by engineer. */
+"Deprecated options" = "Opcje wycofane";
+
/* No comment provided by engineer. */
"Description" = "Opis";
+/* alert title */
+"Description too large" = "Opis jest zbyt długi";
+
/* No comment provided by engineer. */
"Desktop address" = "Adres komputera";
@@ -1676,12 +1878,21 @@ swipe action */
/* chat feature */
"Direct messages" = "Bezpośrednie wiadomości";
+/* No comment provided by engineer. */
+"Direct messages between members are prohibited in this chat." = "W tym czacie zabronione jest wysyłanie bezpośrednich wiadomości między członkami.";
+
/* No comment provided by engineer. */
"Direct messages between members are prohibited." = "Bezpośrednie wiadomości między członkami są zabronione w tej grupie.";
/* No comment provided by engineer. */
"Disable (keep overrides)" = "Wyłącz (zachowaj nadpisania)";
+/* alert title */
+"Disable automatic message deletion?" = "Wyłączyć automatyczne usuwanie wiadomości?";
+
+/* alert button */
+"Disable delete messages" = "Wyłącz usuwanie wiadomości";
+
/* No comment provided by engineer. */
"Disable for all" = "Wyłącz dla wszystkich";
@@ -1742,15 +1953,24 @@ swipe action */
/* No comment provided by engineer. */
"Do NOT use SimpleX for emergency calls." = "NIE używaj SimpleX do połączeń alarmowych.";
+/* No comment provided by engineer. */
+"Documents:" = "Dokumenty:";
+
/* No comment provided by engineer. */
"Don't create address" = "Nie twórz adresu";
/* No comment provided by engineer. */
"Don't enable" = "Nie włączaj";
+/* No comment provided by engineer. */
+"Don't miss important messages." = "Nie przegap ważnych wiadomości.";
+
/* alert action */
"Don't show again" = "Nie pokazuj ponownie";
+/* No comment provided by engineer. */
+"Done" = "Gotowe";
+
/* No comment provided by engineer. */
"Downgrade and open chat" = "Obniż wersję i otwórz czat";
@@ -1797,12 +2017,18 @@ chat item action */
/* No comment provided by engineer. */
"e2e encrypted" = "zaszyfrowany e2e";
+/* No comment provided by engineer. */
+"E2E encrypted notifications." = "Powiadomienia szyfrowane E2E.";
+
/* chat item action */
"Edit" = "Edytuj";
/* No comment provided by engineer. */
"Edit group profile" = "Edytuj profil grupy";
+/* No comment provided by engineer. */
+"Empty message!" = "Pusta wiadomość!";
+
/* No comment provided by engineer. */
"Enable" = "Włącz";
@@ -1815,6 +2041,12 @@ chat item action */
/* No comment provided by engineer. */
"Enable camera access" = "Włącz dostęp do kamery";
+/* No comment provided by engineer. */
+"Enable disappearing messages by default." = "Włącz domyślnie znikające wiadomości.";
+
+/* No comment provided by engineer. */
+"Enable Flux in Network & servers settings for better metadata privacy." = "Włącz opcję Flux w ustawieniach sieci i serwerów, aby zapewnić lepszą prywatność metadanych.";
+
/* No comment provided by engineer. */
"Enable for all" = "Włącz dla wszystkich";
@@ -1926,6 +2158,9 @@ chat item action */
/* chat item text */
"encryption re-negotiation required for %@" = "renegocjacja szyfrowania wymagana dla %@";
+/* No comment provided by engineer. */
+"Encryption renegotiation in progress." = "Trwa renegocjacja szyfrowania.";
+
/* No comment provided by engineer. */
"ended" = "zakończona";
@@ -1974,15 +2209,30 @@ chat item action */
/* No comment provided by engineer. */
"Error aborting address change" = "Błąd przerwania zmiany adresu";
+/* alert title */
+"Error accepting conditions" = "Błąd podczas akceptacji warunków";
+
/* No comment provided by engineer. */
"Error accepting contact request" = "Błąd przyjmowania prośby o kontakt";
+/* alert title */
+"Error accepting member" = "Błąd podczas akceptacji członka";
+
/* No comment provided by engineer. */
"Error adding member(s)" = "Błąd dodawania członka(ów)";
+/* alert title */
+"Error adding server" = "Błąd podczas dodawania serwera";
+
+/* No comment provided by engineer. */
+"Error adding short link" = "Błąd dodawania krótkiego linku";
+
/* No comment provided by engineer. */
"Error changing address" = "Błąd zmiany adresu";
+/* alert title */
+"Error changing chat profile" = "Błąd zmiany profilu czatu";
+
/* No comment provided by engineer. */
"Error changing connection profile" = "Błąd zmiany połączenia profilu";
@@ -1995,9 +2245,15 @@ chat item action */
/* No comment provided by engineer. */
"Error changing to incognito!" = "Błąd zmiany na incognito!";
+/* No comment provided by engineer. */
+"Error checking token status" = "Błąd sprawdzania statusu tokenu";
+
/* alert message */
"Error connecting to forwarding server %@. Please try later." = "Błąd połączenia z serwerem przekierowania %@. Spróbuj ponownie później.";
+/* subscription status explanation */
+"Error connecting to the server used to receive messages from this connection: %@" = "Błąd połączenia z serwerem używanym do odbierania wiadomości z tego połączenia: %@";
+
/* No comment provided by engineer. */
"Error creating address" = "Błąd tworzenia adresu";
@@ -2007,6 +2263,9 @@ chat item action */
/* No comment provided by engineer. */
"Error creating group link" = "Błąd tworzenia linku grupy";
+/* alert title */
+"Error creating list" = "Błąd tworzenia listy";
+
/* No comment provided by engineer. */
"Error creating member contact" = "Błąd tworzenia kontaktu członka";
@@ -2016,9 +2275,15 @@ chat item action */
/* No comment provided by engineer. */
"Error creating profile!" = "Błąd tworzenia profilu!";
+/* No comment provided by engineer. */
+"Error creating report" = "Błąd tworzenia raportu";
+
/* No comment provided by engineer. */
"Error decrypting file" = "Błąd odszyfrowania pliku";
+/* alert title */
+"Error deleting chat" = "Błąd usuwania czatu";
+
/* alert title */
"Error deleting chat database" = "Błąd usuwania bazy danych czatu";
@@ -2064,12 +2329,18 @@ chat item action */
/* No comment provided by engineer. */
"Error joining group" = "Błąd dołączenia do grupy";
+/* alert title */
+"Error loading servers" = "Błąd ładowania serwerów";
+
/* No comment provided by engineer. */
"Error migrating settings" = "Błąd migracji ustawień";
/* No comment provided by engineer. */
"Error opening chat" = "Błąd otwierania czatu";
+/* No comment provided by engineer. */
+"Error opening group" = "Błąd otwierania grupy";
+
/* alert title */
"Error receiving file" = "Błąd odbioru pliku";
@@ -2079,12 +2350,24 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Błąd ponownego łączenia serwerów";
+/* alert title */
+"Error registering for notifications" = "Błąd rejestracji powiadomień";
+
+/* alert title */
+"Error rejecting contact request" = "Błąd odrzucenia prośby o kontakt";
+
/* alert title */
"Error removing member" = "Błąd usuwania członka";
+/* alert title */
+"Error reordering lists" = "Błąd ponownego porządkowania list";
+
/* No comment provided by engineer. */
"Error resetting statistics" = "Błąd resetowania statystyk";
+/* alert title */
+"Error saving chat list" = "Błąd zapisywania listy czatów";
+
/* No comment provided by engineer. */
"Error saving group profile" = "Błąd zapisu profilu grupy";
@@ -2097,6 +2380,9 @@ chat item action */
/* No comment provided by engineer. */
"Error saving passphrase to keychain" = "Błąd zapisu hasła do pęku kluczy";
+/* alert title */
+"Error saving servers" = "Błąd zapisywania serwerów";
+
/* when migrating */
"Error saving settings" = "Błąd zapisywania ustawień";
@@ -2115,6 +2401,9 @@ chat item action */
/* No comment provided by engineer. */
"Error sending message" = "Błąd wysyłania wiadomości";
+/* No comment provided by engineer. */
+"Error setting auto-accept" = "Błąd ustawiania automatycznego akceptowania";
+
/* No comment provided by engineer. */
"Error setting delivery receipts!" = "Błąd ustawiania potwierdzeń dostawy!";
@@ -2133,12 +2422,18 @@ chat item action */
/* No comment provided by engineer. */
"Error synchronizing connection" = "Błąd synchronizacji połączenia";
+/* No comment provided by engineer. */
+"Error testing server connection" = "Błąd testowania połączenia z serwerem";
+
/* No comment provided by engineer. */
"Error updating group link" = "Błąd aktualizacji linku grupy";
/* No comment provided by engineer. */
"Error updating message" = "Błąd aktualizacji wiadomości";
+/* alert title */
+"Error updating server" = "Błąd aktualizacji serwera";
+
/* No comment provided by engineer. */
"Error updating settings" = "Błąd aktualizacji ustawień";
@@ -2159,6 +2454,9 @@ file error text
snd error text */
"Error: %@" = "Błąd: %@";
+/* server test error */
+"Error: %@." = "Błąd: %@.";
+
/* No comment provided by engineer. */
"Error: no database file" = "Błąd: brak pliku bazy danych";
@@ -2168,6 +2466,9 @@ snd error text */
/* No comment provided by engineer. */
"Errors" = "Błędy";
+/* servers error */
+"Errors in servers configuration." = "Błędy w konfiguracji serwerów.";
+
/* No comment provided by engineer. */
"Even when disabled in the conversation." = "Nawet po wyłączeniu w rozmowie.";
@@ -2180,6 +2481,9 @@ snd error text */
/* No comment provided by engineer. */
"expired" = "wygasły";
+/* token status text */
+"Expired" = "Wygasło";
+
/* No comment provided by engineer. */
"Export database" = "Eksportuj bazę danych";
@@ -2204,18 +2508,30 @@ snd error text */
/* No comment provided by engineer. */
"Fast and no wait until the sender is online!" = "Szybko i bez czekania aż nadawca będzie online!";
+/* No comment provided by engineer. */
+"Faster deletion of groups." = "Szybsze usuwanie grup.";
+
/* No comment provided by engineer. */
"Faster joining and more reliable messages." = "Szybsze dołączenie i bardziej niezawodne wiadomości.";
+/* No comment provided by engineer. */
+"Faster sending messages." = "Szybsze wysyłanie wiadomości.";
+
/* swipe action */
"Favorite" = "Ulubione";
+/* No comment provided by engineer. */
+"Favorites" = "Ulubione";
+
/* file error alert title */
"File error" = "Błąd pliku";
/* alert message */
"File errors:\n%@" = "Błędy pliku:\n%@";
+/* file error text */
+"File is blocked by server operator:\n%@." = "Plik jest zablokowany przez operatora serwera:\n%@.";
+
/* file error text */
"File not found - most likely file was deleted or cancelled." = "Nie odnaleziono pliku - najprawdopodobniej plik został usunięty lub anulowany.";
@@ -2249,6 +2565,9 @@ snd error text */
/* chat feature */
"Files and media" = "Pliki i media";
+/* No comment provided by engineer. */
+"Files and media are prohibited in this chat." = "W tym czacie nie wolno przesyłać plików ani multimediów.";
+
/* No comment provided by engineer. */
"Files and media are prohibited." = "Pliki i media są zabronione w tej grupie.";
@@ -2258,6 +2577,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "Pliki i media zabronione!";
+/* No comment provided by engineer. */
+"Filter" = "Filtr";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "Filtruj nieprzeczytane i ulubione czaty.";
@@ -2273,8 +2595,17 @@ snd error text */
/* No comment provided by engineer. */
"Find chats faster" = "Szybciej znajduj czaty";
+/* No comment provided by engineer. */
+"Fingerprint in destination server address does not match certificate: %@." = "Odcisk palca w adresie serwera docelowego nie zgadza się z certyfikatem: %@.";
+
+/* No comment provided by engineer. */
+"Fingerprint in forwarding server address does not match certificate: %@." = "Odcisk palca w adresie serwera przekazującego nie zgadza się z certyfikatem: %@.";
+
+/* No comment provided by engineer. */
+"Fingerprint in server address does not match certificate: %@." = "Odcisk palca w adresie serwera nie zgadza się z certyfikatem: %@.";
+
/* server test error */
-"Fingerprint in server address does not match certificate." = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy";
+"Fingerprint in server address does not match certificate." = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy.";
/* No comment provided by engineer. */
"Fix" = "Napraw";
@@ -2294,9 +2625,27 @@ snd error text */
/* No comment provided by engineer. */
"Fix not supported by group member" = "Naprawa nie jest obsługiwana przez członka grupy";
+/* No comment provided by engineer. */
+"For all moderators" = "Dla wszystkich moderatorów";
+
+/* servers error */
+"For chat profile %@:" = "Dla profilu czatu %@:";
+
/* No comment provided by engineer. */
"For console" = "Dla konsoli";
+/* No comment provided by engineer. */
+"For example, if your contact receives messages via a SimpleX Chat server, your app will deliver them via a Flux server." = "Na przykład, jeśli Twój kontakt odbiera wiadomości za pośrednictwem serwera SimpleX Chat, Twoja aplikacja będzie je dostarczać za pośrednictwem serwera Flux.";
+
+/* No comment provided by engineer. */
+"For me" = "Dla mnie";
+
+/* No comment provided by engineer. */
+"For private routing" = "Dla prywatnego routingu";
+
+/* No comment provided by engineer. */
+"For social media" = "Dla mediów społecznościowych";
+
/* chat item action */
"Forward" = "Przekaż dalej";
@@ -2312,6 +2661,9 @@ snd error text */
/* alert message */
"Forward messages without files?" = "Przekazać wiadomości bez plików?";
+/* No comment provided by engineer. */
+"Forward up to 20 messages at once." = "Przekaż jednocześnie do 20 wiadomości.";
+
/* No comment provided by engineer. */
"forwarded" = "przekazane dalej";
@@ -2360,6 +2712,9 @@ snd error text */
/* No comment provided by engineer. */
"Further reduced battery usage" = "Jeszcze mniejsze zużycie baterii";
+/* No comment provided by engineer. */
+"Get notified when mentioned." = "Otrzymuj powiadomienia, gdy ktoś wspomni o Tobie.";
+
/* No comment provided by engineer. */
"GIFs and stickers" = "GIF-y i naklejki";
@@ -2369,6 +2724,9 @@ snd error text */
/* message preview */
"Good morning!" = "Dzień dobry!";
+/* shown on group welcome message */
+"group" = "grupa";
+
/* No comment provided by engineer. */
"Group" = "Grupa";
@@ -2399,6 +2757,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "Zaproszenie do grupy jest już nieważne, zostało usunięte przez nadawcę.";
+/* No comment provided by engineer. */
+"group is deleted" = "grupa została usunięta";
+
/* No comment provided by engineer. */
"Group link" = "Link do grupy";
@@ -2423,6 +2784,9 @@ snd error text */
/* snd group event chat item */
"group profile updated" = "zaktualizowano profil grupy";
+/* alert message */
+"Group profile was changed. If you save it, the updated profile will be sent to group members." = "Profil grupy został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do członków grupy.";
+
/* No comment provided by engineer. */
"Group welcome message" = "Wiadomość powitalna grupy";
@@ -2432,9 +2796,15 @@ snd error text */
/* No comment provided by engineer. */
"Group will be deleted for you - this cannot be undone!" = "Grupa zostanie usunięta dla Ciebie - nie można tego cofnąć!";
+/* No comment provided by engineer. */
+"Groups" = "Grupy";
+
/* No comment provided by engineer. */
"Help" = "Pomoc";
+/* No comment provided by engineer. */
+"Help admins moderating their groups." = "Pomóż administratorom moderować ich grupy.";
+
/* No comment provided by engineer. */
"Hidden" = "Ukryte";
@@ -2465,6 +2835,15 @@ snd error text */
/* time unit */
"hours" = "godziny";
+/* No comment provided by engineer. */
+"How it affects privacy" = "Jak to wpływa na prywatność";
+
+/* No comment provided by engineer. */
+"How it helps privacy" = "Jak to pomaga chronić prywatność";
+
+/* alert button */
+"How it works" = "Jak to działa";
+
/* No comment provided by engineer. */
"How SimpleX works" = "Jak działa SimpleX";
@@ -2504,6 +2883,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "Obraz zostanie odebrany, gdy kontakt będzie online, poczekaj lub sprawdź później!";
+/* No comment provided by engineer. */
+"Images" = "Zdjęcia";
+
/* No comment provided by engineer. */
"Immediately" = "Natychmiast";
@@ -2528,6 +2910,9 @@ snd error text */
/* No comment provided by engineer. */
"Importing archive" = "Importowanie archiwum";
+/* No comment provided by engineer. */
+"Improved delivery, reduced traffic usage.\nMore improvements are coming soon!" = "Ulepszona dostawa, mniejsze zużycie ruchu.\nWkrótce pojawią się kolejne ulepszenia!";
+
/* No comment provided by engineer. */
"Improved message delivery" = "Ulepszona dostawa wiadomości";
@@ -2549,6 +2934,12 @@ snd error text */
/* No comment provided by engineer. */
"inactive" = "nieaktywny";
+/* report reason */
+"Inappropriate content" = "Nieodpowiednia treść";
+
+/* report reason */
+"Inappropriate profile" = "Nieodpowiedni profil";
+
/* No comment provided by engineer. */
"Incognito" = "Incognito";
@@ -2615,6 +3006,21 @@ snd error text */
/* No comment provided by engineer. */
"Interface colors" = "Kolory interfejsu";
+/* token status text */
+"Invalid" = "Nieprawidłowy";
+
+/* token status text */
+"Invalid (bad token)" = "Nieprawidłowy (zły token)";
+
+/* token status text */
+"Invalid (expired)" = "Nieważny (wygasły)";
+
+/* token status text */
+"Invalid (unregistered)" = "Nieprawidłowy (niezarejestrowany)";
+
+/* token status text */
+"Invalid (wrong topic)" = "Nieprawidłowy (niewłaściwy temat)";
+
/* invalid chat data */
"invalid chat" = "nieprawidłowy czat";
@@ -2663,9 +3069,15 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Zaproś znajomych";
+/* No comment provided by engineer. */
+"Invite member" = "Zaproś członka";
+
/* No comment provided by engineer. */
"Invite members" = "Zaproś członków";
+/* No comment provided by engineer. */
+"Invite to chat" = "Zaproś do czatu";
+
/* No comment provided by engineer. */
"Invite to group" = "Zaproś do grupy";
@@ -2756,6 +3168,9 @@ snd error text */
/* alert title */
"Keep unused invitation?" = "Zachować nieużyte zaproszenie?";
+/* No comment provided by engineer. */
+"Keep your chats clean" = "Utrzymuj czystość swoich czatów";
+
/* No comment provided by engineer. */
"Keep your connections" = "Zachowaj swoje połączenia";
@@ -2774,6 +3189,12 @@ snd error text */
/* swipe action */
"Leave" = "Opuść";
+/* No comment provided by engineer. */
+"Leave chat" = "Opuść czat";
+
+/* No comment provided by engineer. */
+"Leave chat?" = "Opuścić czat?";
+
/* No comment provided by engineer. */
"Leave group" = "Opuść grupę";
@@ -2783,6 +3204,9 @@ snd error text */
/* rcv group event chat item */
"left" = "opuścił";
+/* No comment provided by engineer. */
+"Less traffic on mobile networks." = "Mniejszy ruch w sieciach komórkowych.";
+
/* email subject */
"Let's talk in SimpleX Chat" = "Porozmawiajmy w SimpleX Chat";
@@ -2801,6 +3225,18 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "Połączone komputery";
+/* No comment provided by engineer. */
+"Links" = "Linki";
+
+/* swipe action */
+"List" = "Lista";
+
+/* No comment provided by engineer. */
+"List name and emoji should be different for all lists." = "Nazwa listy i emoji powinny być różne dla wszystkich list.";
+
+/* No comment provided by engineer. */
+"List name..." = "Nazwa listy...";
+
/* No comment provided by engineer. */
"LIVE" = "NA ŻYWO";
@@ -2810,6 +3246,9 @@ snd error text */
/* No comment provided by engineer. */
"Live messages" = "Wiadomości na żywo";
+/* in progress text */
+"Loading profile…" = "Ładowanie profilu…";
+
/* No comment provided by engineer. */
"Local name" = "Nazwa lokalna";
@@ -2861,30 +3300,60 @@ snd error text */
/* No comment provided by engineer. */
"Member" = "Członek";
+/* past/unknown group member */
+"Member %@" = "Członek %@";
+
/* profile update event chat item */
"member %@ changed to %@" = "członek %1$@ zmieniony na %2$@";
+/* No comment provided by engineer. */
+"Member admission" = "Przyjmowanie członków";
+
/* rcv group event chat item */
"member connected" = "połączony";
+/* No comment provided by engineer. */
+"member has old version" = "członek posiada starą wersję";
+
/* item status text */
"Member inactive" = "Członek nieaktywny";
+/* No comment provided by engineer. */
+"Member is deleted - can't accept request" = "Członek został usunięty – nie można zaakceptować prośby";
+
+/* alert message */
+"Member messages will be deleted - this cannot be undone!" = "Wiadomości członków zostaną usunięte – nie można tego cofnąć!";
+
+/* chat feature */
+"Member reports" = "Raporty członków";
+
+/* No comment provided by engineer. */
+"Member role will be changed to \"%@\". All chat members will be notified." = "Rola członka zostanie zmieniona na \"%@\". Wszyscy członkowie czatu zostaną o tym poinformowani.";
+
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". All group members will be notified." = "Rola członka grupy zostanie zmieniona na \"%@\". Wszyscy członkowie grupy zostaną powiadomieni.";
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Rola członka zostanie zmieniona na \"%@\". Członek otrzyma nowe zaproszenie.";
-/* No comment provided by engineer. */
+/* alert message */
+"Member will be removed from chat - this cannot be undone!" = "Członek zostanie usunięty z czatu – nie można tego cofnąć!";
+
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Członek zostanie usunięty z grupy - nie można tego cofnąć!";
+/* alert message */
+"Member will join the group, accept member?" = "Członek dołączy do grupy, zaakceptować członka?";
+
/* No comment provided by engineer. */
"Members can add message reactions." = "Członkowie grupy mogą dodawać reakcje wiadomości.";
/* No comment provided by engineer. */
"Members can irreversibly delete sent messages. (24 hours)" = "Członkowie grupy mogą nieodwracalnie usuwać wysłane wiadomości. (24 godziny)";
+/* No comment provided by engineer. */
+"Members can report messsages to moderators." = "Członkowie mogą zgłaszać wiadomości moderatorom.";
+
/* No comment provided by engineer. */
"Members can send direct messages." = "Członkowie grupy mogą wysyłać bezpośrednie wiadomości.";
@@ -2900,6 +3369,9 @@ snd error text */
/* No comment provided by engineer. */
"Members can send voice messages." = "Członkowie grupy mogą wysyłać wiadomości głosowe.";
+/* No comment provided by engineer. */
+"Mention members 👋" = "Wspomnij członków 👋";
+
/* No comment provided by engineer. */
"Menus" = "Menu";
@@ -2921,6 +3393,9 @@ snd error text */
/* item status text */
"Message forwarded" = "Wiadomość przekazana";
+/* No comment provided by engineer. */
+"Message instantly once you tap Connect." = "Wysyłaj wiadomości natychmiast po dotknięciu przycisku „Połącz”.";
+
/* item status description */
"Message may be delivered later if member becomes active." = "Wiadomość może zostać dostarczona później jeśli członek stanie się aktywny.";
@@ -2969,9 +3444,15 @@ snd error text */
/* No comment provided by engineer. */
"Messages & files" = "Wiadomości i pliki";
+/* No comment provided by engineer. */
+"Messages are protected by **end-to-end encryption**." = "Wiadomości są chronione przez **szyfrowanie typu end-to-end**.";
+
/* No comment provided by engineer. */
"Messages from %@ will be shown!" = "Wiadomości od %@ zostaną pokazane!";
+/* alert message */
+"Messages in this chat will never be deleted." = "Wiadomości na tym czacie nigdy nie zostaną usunięte.";
+
/* No comment provided by engineer. */
"Messages received" = "Otrzymane wiadomości";
@@ -3044,15 +3525,24 @@ snd error text */
/* marked deleted chat item preview text */
"moderated by %@" = "moderowany przez %@";
+/* member role */
+"moderator" = "moderator";
+
/* time unit */
"months" = "miesiące";
+/* swipe action */
+"More" = "Więcej";
+
/* No comment provided by engineer. */
"More improvements are coming soon!" = "Więcej ulepszeń już wkrótce!";
/* No comment provided by engineer. */
"More reliable network connection." = "Bardziej niezawodne połączenia sieciowe.";
+/* No comment provided by engineer. */
+"More reliable notifications" = "Bardziej niezawodne powiadomienia";
+
/* item status description */
"Most likely this connection is deleted." = "Najprawdopodobniej to połączenie jest usunięte.";
@@ -3062,6 +3552,9 @@ snd error text */
/* notification label action */
"Mute" = "Wycisz";
+/* notification label action */
+"Mute all" = "Wycisz wszystko";
+
/* No comment provided by engineer. */
"Muted when inactive!" = "Wyciszony, gdy jest nieaktywny!";
@@ -3074,12 +3567,18 @@ snd error text */
/* No comment provided by engineer. */
"Network connection" = "Połączenie z siecią";
+/* No comment provided by engineer. */
+"Network decentralization" = "Decentralizacja sieci";
+
/* snd error text */
"Network issues - message expired after many attempts to send it." = "Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej.";
/* No comment provided by engineer. */
"Network management" = "Zarządzenie sieciowe";
+/* No comment provided by engineer. */
+"Network operator" = "Operator sieci";
+
/* No comment provided by engineer. */
"Network settings" = "Ustawienia sieci";
@@ -3089,6 +3588,9 @@ snd error text */
/* delete after time */
"never" = "nigdy";
+/* token status text */
+"New" = "Nowy";
+
/* No comment provided by engineer. */
"New chat" = "Nowy czat";
@@ -3107,6 +3609,12 @@ snd error text */
/* No comment provided by engineer. */
"New display name" = "Nowa wyświetlana nazwa";
+/* notification */
+"New events" = "Nowe wydarzenia";
+
+/* No comment provided by engineer. */
+"New group role: Moderator" = "Nowa rola w grupie: Moderator";
+
/* No comment provided by engineer. */
"New in %@" = "Nowość w %@";
@@ -3116,6 +3624,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Nowa rola członka";
+/* rcv group event chat item */
+"New member wants to join the group." = "Nowy członek chce dołączyć do grupy.";
+
/* notification */
"new message" = "nowa wiadomość";
@@ -3128,6 +3639,9 @@ snd error text */
/* No comment provided by engineer. */
"New passphrase…" = "Nowe hasło…";
+/* No comment provided by engineer. */
+"New server" = "Nowy serwer";
+
/* No comment provided by engineer. */
"New SOCKS credentials will be used every time you start the app." = "Nowe poświadczenia SOCKS będą używane przy każdym uruchomieniu aplikacji.";
@@ -3143,6 +3657,18 @@ snd error text */
/* Authentication unavailable */
"No app password" = "Brak hasła aplikacji";
+/* No comment provided by engineer. */
+"No chats" = "Żadnych czatów";
+
+/* No comment provided by engineer. */
+"No chats found" = "Nie znaleziono żadnych czatów";
+
+/* No comment provided by engineer. */
+"No chats in list %@" = "Brak czatów na liście %@";
+
+/* No comment provided by engineer. */
+"No chats with members" = "Żadnych rozmów z członkami";
+
/* No comment provided by engineer. */
"No contacts selected" = "Nie wybrano kontaktów";
@@ -3173,6 +3699,15 @@ snd error text */
/* No comment provided by engineer. */
"No info, try to reload" = "Brak informacji, spróbuj przeładować";
+/* servers error */
+"No media & file servers." = "Brak mediów i serwerów plików multimedialnych.";
+
+/* No comment provided by engineer. */
+"No message" = "Brak wiadomości";
+
+/* servers error */
+"No message servers." = "Brak serwerów wiadomości.";
+
/* No comment provided by engineer. */
"No network connection" = "Brak połączenia z siecią";
@@ -3185,21 +3720,51 @@ snd error text */
/* No comment provided by engineer. */
"No permission to record voice message" = "Brak uprawnień do nagrywania wiadomości głosowej";
+/* alert title */
+"No private routing session" = "Brak prywatnej sesji routingu";
+
/* No comment provided by engineer. */
"No push server" = "Lokalnie";
/* No comment provided by engineer. */
"No received or sent files" = "Brak odebranych lub wysłanych plików";
+/* servers error */
+"No servers for private message routing." = "Brak serwerów prywatnej sesji routingu.";
+
+/* servers error */
+"No servers to receive files." = "Brak serwerów do otrzymania plików.";
+
+/* servers error */
+"No servers to receive messages." = "Brak serwerów aby otrzymać wiadomości.";
+
+/* servers error */
+"No servers to send files." = "Brak serwerów do wysyłania plików.";
+
+/* No comment provided by engineer. */
+"no subscription" = "brak subskrypcji";
+
/* copied message info in history */
"no text" = "brak tekstu";
+/* alert title */
+"No token!" = "Brak tokenu!";
+
+/* No comment provided by engineer. */
+"No unread chats" = "Brak nieprzeczytanych czatów";
+
/* No comment provided by engineer. */
"No user identifiers." = "Brak identyfikatorów użytkownika.";
/* No comment provided by engineer. */
"Not compatible!" = "Nie kompatybilny!";
+/* No comment provided by engineer. */
+"not synchronized" = "nie zsynchronizowano";
+
+/* No comment provided by engineer. */
+"Notes" = "Notatki";
+
/* No comment provided by engineer. */
"Nothing selected" = "Nic nie jest zaznaczone";
@@ -3212,6 +3777,15 @@ snd error text */
/* No comment provided by engineer. */
"Notifications are disabled!" = "Powiadomienia są wyłączone!";
+/* alert title */
+"Notifications error" = "Błąd powiadomień";
+
+/* No comment provided by engineer. */
+"Notifications privacy" = "Prywatność powiadomień";
+
+/* alert title */
+"Notifications status" = "Stan powiadomień";
+
/* No comment provided by engineer. */
"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Teraz administratorzy mogą:\n- usuwać wiadomości członków.\n- wyłączyć członków (rola \"obserwatora\")";
@@ -3259,6 +3833,9 @@ new chat action */
/* No comment provided by engineer. */
"Onion hosts will not be used." = "Hosty onion nie będą używane.";
+/* No comment provided by engineer. */
+"Only chat owners can change preferences." = "Tylko właściciele czatu mogą zmieniać preferencje.";
+
/* No comment provided by engineer. */
"Only client devices store user profiles, contacts, groups, and messages." = "Tylko urządzenia klienckie przechowują profile użytkowników, kontakty, grupy i wiadomości wysyłane za pomocą **2-warstwowego szyfrowania end-to-end**.";
@@ -3274,6 +3851,12 @@ new chat action */
/* No comment provided by engineer. */
"Only group owners can enable voice messages." = "Tylko właściciele grup mogą włączyć wiadomości głosowe.";
+/* No comment provided by engineer. */
+"Only sender and moderators see it" = "Widzą to tylko nadawca i moderatorzy";
+
+/* No comment provided by engineer. */
+"Only you and moderators see it" = "Widzisz to tylko Ty i moderatorzy";
+
/* No comment provided by engineer. */
"Only you can add message reactions." = "Tylko Ty możesz dodawać reakcje wiadomości.";
@@ -3286,6 +3869,9 @@ new chat action */
/* No comment provided by engineer. */
"Only you can send disappearing messages." = "Tylko Ty możesz wysyłać znikające wiadomości.";
+/* No comment provided by engineer. */
+"Only you can send files and media." = "Tylko Ty możesz wysyłać pliki i multimedia.";
+
/* No comment provided by engineer. */
"Only you can send voice messages." = "Tylko Ty możesz wysyłać wiadomości głosowe.";
@@ -3301,30 +3887,75 @@ new chat action */
/* No comment provided by engineer. */
"Only your contact can send disappearing messages." = "Tylko Twój kontakt może wysyłać znikające wiadomości.";
+/* No comment provided by engineer. */
+"Only your contact can send files and media." = "Tylko Twój kontakt może wysyłać pliki i multimedia.";
+
/* No comment provided by engineer. */
"Only your contact can send voice messages." = "Tylko Twój kontakt może wysyłać wiadomości głosowe.";
/* alert action */
"Open" = "Otwórz";
+/* No comment provided by engineer. */
+"Open changes" = "Otwórz zmiany";
+
/* new chat action */
"Open chat" = "Otwórz czat";
/* authentication reason */
"Open chat console" = "Otwórz konsolę czatu";
+/* alert action */
+"Open clean link" = "Otwórz czysty link";
+
+/* No comment provided by engineer. */
+"Open conditions" = "Otwórz warunki";
+
+/* alert action */
+"Open full link" = "Otwórz pełny link";
+
/* new chat action */
"Open group" = "Grupa otwarta";
+/* alert title */
+"Open link?" = "Otworzyć link?";
+
/* authentication reason */
"Open migration to another device" = "Otwórz migrację na innym urządzeniu";
+/* new chat action */
+"Open new chat" = "Otwórz nowy czat";
+
+/* new chat action */
+"Open new group" = "Otwórz nową grupę";
+
/* No comment provided by engineer. */
"Open Settings" = "Otwórz Ustawienia";
+/* No comment provided by engineer. */
+"Open to accept" = "Otwórz by zaakceptować";
+
+/* No comment provided by engineer. */
+"Open to connect" = "Otwórz aby się połączyć";
+
+/* No comment provided by engineer. */
+"Open to join" = "Otwórz aby dołączyć";
+
+/* No comment provided by engineer. */
+"Open to use bot" = "Otwórz aby skorzystać z bota";
+
/* No comment provided by engineer. */
"Opening app…" = "Otwieranie aplikacji…";
+/* No comment provided by engineer. */
+"Operator" = "Operator";
+
+/* alert title */
+"Operator server" = "Serwer Operatora";
+
+/* No comment provided by engineer. */
+"Or import archive file" = "Lub zaimportuj plik archiwalny";
+
/* No comment provided by engineer. */
"Or paste archive link" = "Lub wklej link archiwum";
@@ -3337,6 +3968,12 @@ new chat action */
/* No comment provided by engineer. */
"Or show this code" = "Lub pokaż ten kod";
+/* No comment provided by engineer. */
+"Or to share privately" = "Lub udostępnij prywatnie";
+
+/* No comment provided by engineer. */
+"Organize chats into lists" = "Organizuj czaty jako listy";
+
/* No comment provided by engineer. */
"other" = "inne";
@@ -3391,9 +4028,18 @@ new chat action */
/* No comment provided by engineer. */
"peer-to-peer" = "peer-to-peer";
+/* No comment provided by engineer. */
+"pending" = "oczekuje";
+
/* No comment provided by engineer. */
"Pending" = "Oczekujące";
+/* No comment provided by engineer. */
+"pending approval" = "oczekuje na zatwierdzenie";
+
+/* No comment provided by engineer. */
+"pending review" = "oczekuje na ocenę";
+
/* No comment provided by engineer. */
"Periodic" = "Okresowo";
@@ -3460,6 +4106,18 @@ new chat action */
/* No comment provided by engineer. */
"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Przechowuj kod dostępu w bezpieczny sposób, w przypadku jego utraty NIE będzie można go zmienić.";
+/* token info */
+"Please try to disable and re-enable notfications." = "Spróbuj wyłączyć, a następnie ponownie włączyć powiadomienia.";
+
+/* snd group event chat item */
+"Please wait for group moderators to review your request to join the group." = "Poczekaj, aż moderatorzy grupy rozpatrzą Twoją prośbę o dołączenie do grupy.";
+
+/* token info */
+"Please wait for token activation to complete." = "Proszę poczekać na zakończenie aktywacji tokenu.";
+
+/* token info */
+"Please wait for token to be registered." = "Proszę poczekać na zarejestrowanie tokenu.";
+
/* No comment provided by engineer. */
"Polish interface" = "Polski interfejs";
@@ -3472,6 +4130,9 @@ new chat action */
/* No comment provided by engineer. */
"Preset server address" = "Wstępnie ustawiony adres serwera";
+/* No comment provided by engineer. */
+"Preset servers" = "Domyślne serwery";
+
/* No comment provided by engineer. */
"Preview" = "Podgląd";
@@ -3481,12 +4142,24 @@ new chat action */
/* No comment provided by engineer. */
"Privacy & security" = "Prywatność i bezpieczeństwo";
+/* No comment provided by engineer. */
+"Privacy for your customers." = "Prywatność dla Twoich klientów.";
+
+/* No comment provided by engineer. */
+"Privacy policy and conditions of use." = "Polityka prywatności i warunki korzystania.";
+
/* No comment provided by engineer. */
"Privacy redefined" = "Redefinicja prywatności";
+/* No comment provided by engineer. */
+"Private chats, groups and your contacts are not accessible to server operators." = "Prywatne czaty, grupy i Twoje kontakty nie są dostępne dla operatorów serwerów.";
+
/* No comment provided by engineer. */
"Private filenames" = "Prywatne nazwy plików";
+/* No comment provided by engineer. */
+"Private media file names." = "Nazwy prywatnych plików multimedialnych.";
+
/* No comment provided by engineer. */
"Private message routing" = "Trasowanie prywatnych wiadomości";
@@ -3502,6 +4175,9 @@ new chat action */
/* alert title */
"Private routing error" = "Błąd prywatnego trasowania";
+/* alert title */
+"Private routing timeout" = "Limit czasu routingu prywatnego";
+
/* No comment provided by engineer. */
"Profile and server connections" = "Profil i połączenia z serwerem";
@@ -3532,6 +4208,9 @@ new chat action */
/* No comment provided by engineer. */
"Prohibit messages reactions." = "Zabroń reakcje wiadomości.";
+/* No comment provided by engineer. */
+"Prohibit reporting messages to moderators." = "Zabroń raportowania wiadomości moderatorom.";
+
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "Zabroń wysyłania bezpośrednich wiadomości do członków.";
@@ -3559,6 +4238,9 @@ new chat action */
/* No comment provided by engineer. */
"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Chroni Twój adres IP przed przekaźnikami wiadomości wybranych przez Twoje kontakty.\nWłącz w ustawianiach *Sieć i serwery* .";
+/* No comment provided by engineer. */
+"Protocol background timeout" = "Limit czasu protokołu w tle";
+
/* No comment provided by engineer. */
"Protocol timeout" = "Limit czasu protokołu";
@@ -3691,6 +4373,15 @@ new chat action */
/* No comment provided by engineer. */
"Reduced battery usage" = "Zmniejszone zużycie baterii";
+/* No comment provided by engineer. */
+"Register" = "Zarejestruj";
+
+/* token info */
+"Register notification token?" = "Zarejestrować token powiadomień?";
+
+/* token status text */
+"Registered" = "Zarejestrowany";
+
/* alert action
reject incoming call via notification
swipe action */
@@ -3702,6 +4393,12 @@ swipe action */
/* alert title */
"Reject contact request" = "Odrzuć prośbę kontaktu";
+/* alert title */
+"Reject member?" = "Odrzucić członka?";
+
+/* No comment provided by engineer. */
+"rejected" = "odrzucono";
+
/* call status */
"rejected call" = "odrzucone połączenie";
@@ -3711,9 +4408,12 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Serwer przekaźnikowy chroni Twój adres IP, ale może obserwować czas trwania połączenia.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Usuń";
+/* alert action */
+"Remove and delete messages" = "Usuń i skasuj wiadomości";
+
/* No comment provided by engineer. */
"Remove archive?" = "Usunąć archiwum?";
@@ -3721,9 +4421,12 @@ swipe action */
"Remove image" = "Usuń obraz";
/* No comment provided by engineer. */
-"Remove member" = "Usuń członka";
+"Remove link tracking" = "Usuń śledzenie linków";
/* No comment provided by engineer. */
+"Remove member" = "Usuń członka";
+
+/* alert title */
"Remove member?" = "Usunąć członka?";
/* No comment provided by engineer. */
@@ -3738,12 +4441,18 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "usunięto adres kontaktu";
+/* No comment provided by engineer. */
+"removed from group" = "usunięty z grupy";
+
/* profile update event chat item */
"removed profile picture" = "usunięto zdjęcie profilu";
/* rcv group event chat item */
"removed you" = "usunął cię";
+/* No comment provided by engineer. */
+"Removes messages and blocks members." = "Usuwa wiadomości i blokuje członków.";
+
/* No comment provided by engineer. */
"Renegotiate" = "Renegocjuj";
@@ -3765,6 +4474,54 @@ swipe action */
/* chat item action */
"Reply" = "Odpowiedz";
+/* chat item action */
+"Report" = "Zgłoś";
+
+/* report reason */
+"Report content: only group moderators will see it." = "Zgłoś treść: zobaczą ją tylko moderatorzy grupy.";
+
+/* report reason */
+"Report member profile: only group moderators will see it." = "Zgłoś profil członka: będą go widzieć tylko moderatorzy grupy.";
+
+/* report reason */
+"Report other: only group moderators will see it." = "Zgłoś inne: zobaczą to tylko moderatorzy grupy.";
+
+/* No comment provided by engineer. */
+"Report reason?" = "Jaki jest powód zgłoszenia?";
+
+/* alert title */
+"Report sent to moderators" = "Zgłoszenia wysłane do moderatorów";
+
+/* report reason */
+"Report spam: only group moderators will see it." = "Zgłoś spam: tylko moderatorzy grupy będą to widzieć.";
+
+/* report reason */
+"Report violation: only group moderators will see it." = "Zgłoś naruszenie: zobaczą je tylko moderatorzy grupy.";
+
+/* report in notification */
+"Report: %@" = "Zgłoszenie: %@";
+
+/* No comment provided by engineer. */
+"Reporting messages to moderators is prohibited." = "Zgłaszanie wiadomości moderatorom jest zabronione.";
+
+/* No comment provided by engineer. */
+"Reports" = "Zgłoszenia";
+
+/* No comment provided by engineer. */
+"request is sent" = "prośba została wysłana";
+
+/* No comment provided by engineer. */
+"request to join rejected" = "prośba o dołączenie została odrzucona";
+
+/* rcv group event chat item */
+"requested connection" = "prośba o połączenie";
+
+/* rcv direct event chat item */
+"requested connection from group %@" = "prośba o połączenie od grupy %@";
+
+/* chat list item title */
+"requested to connect" = "poproszono o połączenie";
+
/* No comment provided by engineer. */
"Required" = "Wymagane";
@@ -3816,6 +4573,24 @@ swipe action */
/* chat item action */
"Reveal" = "Ujawnij";
+/* No comment provided by engineer. */
+"review" = "ocena";
+
+/* No comment provided by engineer. */
+"Review conditions" = "Przejrzyj warunki";
+
+/* No comment provided by engineer. */
+"Review group members" = "Przejrzyj członków grupy";
+
+/* admission stage */
+"Review members" = "Przejrzyj członków";
+
+/* admission stage description */
+"Review members before admitting (\"knocking\")." = "Przejrzyj członków przed dopuszczeniem (\"zapukaj\").";
+
+/* No comment provided by engineer. */
+"reviewed by admins" = "sprawdzone przez administratorów";
+
/* No comment provided by engineer. */
"Revoke" = "Odwołaj";
@@ -3844,6 +4619,12 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Zapisz (i powiadom kontakty)";
+/* alert button */
+"Save (and notify members)" = "Zapisz (i powiadom członków)";
+
+/* alert title */
+"Save admission settings?" = "Zapisać ustawienia wstępu?";
+
/* alert button */
"Save and notify contact" = "Zapisz i powiadom kontakt";
@@ -3859,6 +4640,12 @@ chat item action */
/* No comment provided by engineer. */
"Save group profile" = "Zapisz profil grupy";
+/* alert title */
+"Save group profile?" = "Zapisać profil grupy?";
+
+/* No comment provided by engineer. */
+"Save list" = "Zapisz listę";
+
/* No comment provided by engineer. */
"Save passphrase and open chat" = "Zapisz hasło i otwórz czat";
@@ -3934,9 +4721,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "Pasek wyszukiwania akceptuje linki zaproszenia.";
+/* No comment provided by engineer. */
+"Search files" = "Szukaj plików";
+
+/* No comment provided by engineer. */
+"Search images" = "Szukaj zdjęć";
+
+/* No comment provided by engineer. */
+"Search links" = "Szukaj linków";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "Wyszukaj lub wklej link SimpleX";
+/* No comment provided by engineer. */
+"Search videos" = "Szukaj wideo";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "Szukaj wiadomości głosowych";
+
/* network option */
"sec" = "sek";
@@ -3994,6 +4796,9 @@ chat item action */
/* No comment provided by engineer. */
"Send a live message - it will update for the recipient(s) as you type it" = "Wysyłaj wiadomości na żywo - będą one aktualizowane dla odbiorcy(ów) w trakcie ich wpisywania";
+/* No comment provided by engineer. */
+"Send contact request?" = "Wysłać prośbę o kontakt?";
+
/* No comment provided by engineer. */
"Send delivery receipts to" = "Wyślij potwierdzenia dostawy do";
@@ -4024,18 +4829,30 @@ chat item action */
/* No comment provided by engineer. */
"Send notifications" = "Wyślij powiadomienia";
+/* No comment provided by engineer. */
+"Send private reports" = "Wyślij prywatne zgłoszenia";
+
/* No comment provided by engineer. */
"Send questions and ideas" = "Wyślij pytania i pomysły";
/* No comment provided by engineer. */
"Send receipts" = "Wyślij potwierdzenia";
+/* No comment provided by engineer. */
+"Send request" = "Wyślij prośbę";
+
+/* No comment provided by engineer. */
+"Send request without message" = "Wyślij prośbę bez wiadomości";
+
/* No comment provided by engineer. */
"Send them from gallery or custom keyboards." = "Wyślij je z galerii lub niestandardowych klawiatur.";
/* No comment provided by engineer. */
"Send up to 100 last messages to new members." = "Wysyłaj do 100 ostatnich wiadomości do nowych członków.";
+/* No comment provided by engineer. */
+"Send your private feedback to groups." = "Wyślij swoją prywatną opinię do grup.";
+
/* alert message */
"Sender cancelled file transfer." = "Nadawca anulował transfer pliku.";
@@ -4096,6 +4913,9 @@ chat item action */
/* No comment provided by engineer. */
"Server" = "Serwer";
+/* alert message */
+"Server added to operator %@." = "Serwer został dodany do operatora %@.";
+
/* No comment provided by engineer. */
"Server address" = "Adres serwera";
@@ -4105,14 +4925,23 @@ chat item action */
/* srv error text. */
"Server address is incompatible with network settings." = "Adres serwera jest niekompatybilny z ustawieniami sieciowymi.";
+/* alert title */
+"Server operator changed." = "Operator serwera został zmieniony.";
+
+/* No comment provided by engineer. */
+"Server operators" = "Operatorzy serwera";
+
+/* alert title */
+"Server protocol changed." = "Protokół serwera zmieniony.";
+
/* queue info */
"server queue info: %@\n\nlast received msg: %@" = "Informacje kolejki serwera: %1$@\n\nostatnia otrzymana wiadomość: %2$@";
/* server test error */
-"Server requires authorization to create queues, check password." = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło";
+"Server requires authorization to create queues, check password." = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło.";
/* server test error */
-"Server requires authorization to upload, check password." = "Serwer wymaga autoryzacji do przesłania, sprawdź hasło";
+"Server requires authorization to upload, check password." = "Serwer wymaga autoryzacji do przesłania, sprawdź hasło.";
/* No comment provided by engineer. */
"Server test failed!" = "Test serwera nie powiódł się!";
@@ -4141,6 +4970,9 @@ chat item action */
/* No comment provided by engineer. */
"Set 1 day" = "Ustaw 1 dzień";
+/* No comment provided by engineer. */
+"Set chat name…" = "Ustaw nazwę czatu…";
+
/* No comment provided by engineer. */
"Set contact name…" = "Ustaw nazwę kontaktu…";
@@ -4153,6 +4985,12 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Ustaw go zamiast uwierzytelniania systemowego.";
+/* No comment provided by engineer. */
+"Set member admission" = "Ustaw przyjmowanie członków";
+
+/* No comment provided by engineer. */
+"Set message expiration in chats." = "Ustaw datę wygaśnięcia wiadomości na czatach.";
+
/* profile update event chat item */
"set new contact address" = "ustaw nowy adres kontaktu";
@@ -4168,6 +5006,9 @@ chat item action */
/* No comment provided by engineer. */
"Set passphrase to export" = "Ustaw hasło do eksportu";
+/* No comment provided by engineer. */
+"Set profile bio and welcome message." = "Ustaw biografię profilu i wiadomość powitalną.";
+
/* No comment provided by engineer. */
"Set the message shown to new members!" = "Ustaw wiadomość wyświetlaną nowym członkom!";
@@ -4190,9 +5031,15 @@ chat item action */
/* No comment provided by engineer. */
"Share 1-time link" = "Udostępnij 1-razowy link";
+/* No comment provided by engineer. */
+"Share 1-time link with a friend" = "Udostępnij jednorazowy link znajomemu";
+
/* No comment provided by engineer. */
"Share address" = "Udostępnij adres";
+/* No comment provided by engineer. */
+"Share address publicly" = "Udostępnij adres publicznie";
+
/* alert title */
"Share address with contacts?" = "Udostępnić adres kontaktom?";
@@ -4202,9 +5049,18 @@ chat item action */
/* No comment provided by engineer. */
"Share link" = "Udostępnij link";
+/* alert button */
+"Share old address" = "Udostępnij stary adres";
+
+/* alert button */
+"Share old link" = "Udostępnij stary link";
+
/* No comment provided by engineer. */
"Share profile" = "Udostępnij profil";
+/* No comment provided by engineer. */
+"Share SimpleX address on social media." = "Udostępnij adres SimpleX w mediach społecznościowych.";
+
/* No comment provided by engineer. */
"Share this 1-time invite link" = "Udostępnij ten jednorazowy link";
@@ -4214,6 +5070,18 @@ chat item action */
/* No comment provided by engineer. */
"Share with contacts" = "Udostępnij kontaktom";
+/* No comment provided by engineer. */
+"Share your address" = "Udostępnij swój adres";
+
+/* No comment provided by engineer. */
+"Short description" = "Krótki opis";
+
+/* No comment provided by engineer. */
+"Short link" = "Krótki link";
+
+/* No comment provided by engineer. */
+"Short SimpleX address" = "Krótki adres SimpleX";
+
/* No comment provided by engineer. */
"Show → on messages sent via private routing." = "Pokaż → na wiadomościach wysłanych przez prywatne trasowanie.";
@@ -4250,9 +5118,21 @@ chat item action */
/* No comment provided by engineer. */
"SimpleX Address" = "Adres SimpleX";
+/* No comment provided by engineer. */
+"SimpleX address and 1-time links are safe to share via any messenger." = "Adres SimpleX i jednorazowe linki są bezpieczne do udostępniania przez dowolny komunikator.";
+
+/* No comment provided by engineer. */
+"SimpleX address or 1-time link?" = "Adres SimpleX czy link jednorazowy?";
+
/* alert title */
"SimpleX address settings" = "Ustawienia automatycznej akceptacji";
+/* simplex link type */
+"SimpleX channel link" = "Link do kanału na SimpleX";
+
+/* No comment provided by engineer. */
+"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat i Flux zawarły umowę na włączenie do aplikacji serwerów obsługiwanych przez Flux.";
+
/* No comment provided by engineer. */
"SimpleX Chat security was audited by Trail of Bits." = "Bezpieczeństwo SimpleX Chat zostało zaudytowane przez Trail of Bits.";
@@ -4289,6 +5169,12 @@ chat item action */
/* simplex link type */
"SimpleX one-time invitation" = "Zaproszenie jednorazowe SimpleX";
+/* No comment provided by engineer. */
+"SimpleX protocols reviewed by Trail of Bits." = "Protokoły SimpleX sprawdzone przez Trail of Bits.";
+
+/* simplex link type */
+"SimpleX relay link" = "łącze przekaźnikowe SimpleX";
+
/* No comment provided by engineer. */
"Simplified incognito mode" = "Uproszczony tryb incognito";
@@ -4325,9 +5211,16 @@ chat item action */
/* No comment provided by engineer. */
"Some non-fatal errors occurred during import:" = "Podczas importu wystąpiły niekrytyczne błędy:";
+/* alert message */
+"Some servers failed the test:\n%@" = "Niektóre serwery nie przeszły testu:\n%@";
+
/* notification title */
"Somebody" = "Ktoś";
+/* blocking reason
+report reason */
+"Spam" = "Spam";
+
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "Kwadrat, okrąg lub cokolwiek pomiędzy.";
@@ -4385,6 +5278,9 @@ chat item action */
/* No comment provided by engineer. */
"Stopping chat" = "Zatrzymywanie czatu";
+/* No comment provided by engineer. */
+"Storage" = "Magazyn";
+
/* No comment provided by engineer. */
"strike" = "strajk";
@@ -4406,6 +5302,12 @@ chat item action */
/* No comment provided by engineer. */
"Support SimpleX Chat" = "Wspieraj SimpleX Chat";
+/* No comment provided by engineer. */
+"Switch audio and video during the call." = "Przełączanie audio i wideo podczas połączenia.";
+
+/* No comment provided by engineer. */
+"Switch chat profile for 1-time invitations." = "Przełącz profil czatu dla zaproszeń jednorazowych.";
+
/* No comment provided by engineer. */
"System" = "System";
@@ -4421,6 +5323,21 @@ chat item action */
/* No comment provided by engineer. */
"Tap button " = "Naciśnij przycisk ";
+/* No comment provided by engineer. */
+"Tap Connect to chat" = "Dotknij Połącz aby rozpocząć czat";
+
+/* No comment provided by engineer. */
+"Tap Connect to send request" = "Dotknij Połącz, aby wysłać prośbę";
+
+/* No comment provided by engineer. */
+"Tap Connect to use bot" = "Dotknij Połącz aby użyć bota";
+
+/* No comment provided by engineer. */
+"Tap Create SimpleX address in the menu to create it later." = "Dotknij Stwórz adres SimpleX w menu aby utworzyć go później.";
+
+/* No comment provided by engineer. */
+"Tap Join group" = "Dotknij Dołącz do grupy";
+
/* No comment provided by engineer. */
"Tap to activate profile." = "Dotknij, aby aktywować profil.";
@@ -4442,9 +5359,15 @@ chat item action */
/* No comment provided by engineer. */
"TCP connection" = "Połączenie TCP";
+/* No comment provided by engineer. */
+"TCP connection bg timeout" = "Przekroczono limit czasu połączenia TCP";
+
/* No comment provided by engineer. */
"TCP connection timeout" = "Limit czasu połączenia TCP";
+/* No comment provided by engineer. */
+"TCP port for messaging" = "Port TCP dla wiadomości";
+
/* No comment provided by engineer. */
"TCP_KEEPCNT" = "TCP_KEEPCNT";
@@ -4460,6 +5383,9 @@ chat item action */
/* server test failure */
"Test failed at step %@." = "Test nie powiódł się na etapie %@.";
+/* No comment provided by engineer. */
+"Test notifications" = "Powiadomienia testowe";
+
/* No comment provided by engineer. */
"Test server" = "Przetestuj serwer";
@@ -4478,9 +5404,15 @@ chat item action */
/* No comment provided by engineer. */
"Thanks to the users – contribute via Weblate!" = "Podziękowania dla użytkowników - wkład za pośrednictwem Weblate!";
+/* alert message */
+"The address will be short, and your profile will be shared via the address." = "Adres będzie krótki, a Twój profil zostanie udostępniony za pośrednictwem adresu.";
+
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć.";
+/* No comment provided by engineer. */
+"The app protects your privacy by using different operators in each conversation." = "Aplikacja chroni Twoją prywatność, korzystając z różnych operatorów w każdej rozmowie.";
+
/* No comment provided by engineer. */
"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Aplikacja zapyta o potwierdzenie pobierania od nieznanych serwerów plików (poza .onion).";
@@ -4490,6 +5422,9 @@ chat item action */
/* No comment provided by engineer. */
"The code you scanned is not a SimpleX link QR code." = "Kod, który zeskanowałeś nie jest kodem QR linku SimpleX.";
+/* No comment provided by engineer. */
+"The connection reached the limit of undelivered messages, your contact may be offline." = "Połączenie osiągnęło limit niedostarczonych wiadomości, Twój kontakt może być offline.";
+
/* No comment provided by engineer. */
"The connection you accepted will be cancelled!" = "Zaakceptowane przez Ciebie połączenie zostanie anulowane!";
@@ -4511,6 +5446,9 @@ chat item action */
/* No comment provided by engineer. */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Identyfikator następnej wiadomości jest nieprawidłowy (mniejszy lub równy poprzedniej).\nMoże się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skompromitowane.";
+/* alert message */
+"The link will be short, and group profile will be shared via the link." = "Link będzie krótki, a profil grupowy zostanie udostępniony poprzez link.";
+
/* No comment provided by engineer. */
"The message will be deleted for all members." = "Wiadomość zostanie usunięta dla wszystkich członków.";
@@ -4526,6 +5464,12 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć.";
+/* No comment provided by engineer. */
+"The same conditions will apply to operator **%@**." = "Te same warunki będą miały zastosowanie do operatora **%@**.";
+
+/* No comment provided by engineer. */
+"The second preset operator in the app!" = "Drugi predefiniowany operator w aplikacji!";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅";
@@ -4535,6 +5479,9 @@ chat item action */
/* No comment provided by engineer. */
"The servers for new connections of your current chat profile **%@**." = "Serwery dla nowych połączeń bieżącego profilu czatu **%@**.";
+/* No comment provided by engineer. */
+"The servers for new files of your current chat profile **%@**." = "Serwery dla nowych plików Twojego bieżącego profilu czatu **%@**.";
+
/* No comment provided by engineer. */
"The text you pasted is not a SimpleX link." = "Tekst, który wkleiłeś nie jest linkiem SimpleX.";
@@ -4544,6 +5491,9 @@ chat item action */
/* No comment provided by engineer. */
"Themes" = "Motywy";
+/* No comment provided by engineer. */
+"These conditions will also apply for: **%@**." = "Warunki te będą miały również zastosowanie w przypadku: **%@**.";
+
/* No comment provided by engineer. */
"These settings are for your current profile **%@**." = "Te ustawienia dotyczą Twojego bieżącego profilu **%@**.";
@@ -4556,6 +5506,9 @@ chat item action */
/* No comment provided by engineer. */
"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Tego działania nie można cofnąć - wiadomości wysłane i odebrane wcześniej niż wybrane zostaną usunięte. Może to potrwać kilka minut.";
+/* alert message */
+"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "Tej akcji nie można cofnąć - wiadomości wysłane i otrzymane na tym czacie wcześniej niż wybrane zostaną usunięte.";
+
/* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone.";
@@ -4580,12 +5533,24 @@ chat item action */
/* No comment provided by engineer. */
"This group no longer exists." = "Ta grupa już nie istnieje.";
+/* No comment provided by engineer. */
+"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Ten link wymaga nowszej wersji aplikacji. Zaktualizuj aplikację lub poproś osobę kontaktową o przesłanie kompatybilnego łącza.";
+
/* No comment provided by engineer. */
"This link was used with another mobile device, please create a new link on the desktop." = "Ten link dostał użyty z innym urządzeniem mobilnym, proszę stworzyć nowy link na komputerze.";
+/* No comment provided by engineer. */
+"This message was deleted or not received yet." = "Ta wiadomość została usunięta lub jeszcze nie otrzymana.";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "To ustawienie dotyczy wiadomości Twojego bieżącego profilu czatu **%@**.";
+/* No comment provided by engineer. */
+"This setting is for your current profile **%@**." = "To ustawienie jest dla Twojego obecnego profilu **%@**.";
+
+/* No comment provided by engineer. */
+"Time to disappear is set only for new contacts." = "Czas zniknięcia jest ustawiony tylko dla nowych kontaktów.";
+
/* No comment provided by engineer. */
"Title" = "Tytuł";
@@ -4601,6 +5566,9 @@ chat item action */
/* No comment provided by engineer. */
"To make a new connection" = "Aby nawiązać nowe połączenie";
+/* No comment provided by engineer. */
+"To protect against your link being replaced, you can compare contact security codes." = "Aby zabezpieczyć się przed wymianą łącza, możesz porównać kody bezpieczeństwa kontaktu.";
+
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "Aby chronić strefę czasową, pliki obrazów/głosów używają UTC.";
@@ -4613,6 +5581,9 @@ chat item action */
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "Aby chronić prywatność, zamiast identyfikatorów użytkowników używanych przez wszystkie inne platformy, SimpleX ma identyfikatory dla kolejek wiadomości, oddzielne dla każdego z Twoich kontaktów.";
+/* No comment provided by engineer. */
+"To receive" = "Żeby odebrać";
+
/* No comment provided by engineer. */
"To record speech please grant permission to use Microphone." = "Aby nagrać rozmowę, proszę zezwolić na użycie Mikrofonu.";
@@ -4625,9 +5596,21 @@ chat item action */
/* No comment provided by engineer. */
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Aby ujawnić Twój ukryty profil, wprowadź pełne hasło w pole wyszukiwania na stronie **Twoich profili czatu**.";
+/* No comment provided by engineer. */
+"To send" = "Żeby wysłać";
+
+/* alert message */
+"To send commands you must be connected." = "Aby wysyłać polecenia, musisz być podłączony.";
+
/* No comment provided by engineer. */
"To support instant push notifications the chat database has to be migrated." = "Aby obsługiwać natychmiastowe powiadomienia push, należy zmigrować bazę danych czatu.";
+/* alert message */
+"To use another profile after connection attempt, delete the chat and use the link again." = "Aby po próbie połączenia skorzystać z innego profilu, usuń czat i użyj linku ponownie.";
+
+/* No comment provided by engineer. */
+"To use the servers of **%@**, accept conditions of use." = "Aby korzystać z serwerów **%@**, należy zaakceptować warunki użytkowania.";
+
/* No comment provided by engineer. */
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "Aby zweryfikować szyfrowanie end-to-end z Twoim kontaktem porównaj (lub zeskanuj) kod na waszych urządzeniach.";
@@ -4637,6 +5620,9 @@ chat item action */
/* No comment provided by engineer. */
"Toggle incognito when connecting." = "Przełącz incognito przy połączeniu.";
+/* token status */
+"Token status: %@." = "Stan tokena: %@.";
+
/* No comment provided by engineer. */
"Toolbar opacity" = "Nieprzezroczystość paska narzędzi";
@@ -4649,6 +5635,9 @@ chat item action */
/* No comment provided by engineer. */
"Transport sessions" = "Sesje transportowe";
+/* subscription status explanation */
+"Trying to connect to the server used to receive messages from this connection." = "Próba połączenia z serwerem, który służył do odbierania wiadomości z tego połączenia.";
+
/* No comment provided by engineer. */
"Turkish interface" = "Turecki interfejs";
@@ -4679,6 +5668,9 @@ chat item action */
/* rcv group event chat item */
"unblocked %@" = "odblokowano %@";
+/* No comment provided by engineer. */
+"Undelivered messages" = "Niedostarczone wiadomości";
+
/* No comment provided by engineer. */
"Unexpected migration state" = "Nieoczekiwany stan migracji";
@@ -4745,6 +5737,9 @@ chat item action */
/* swipe action */
"Unread" = "Nieprzeczytane";
+/* No comment provided by engineer. */
+"Unsupported connection link" = "Nieobsługiwane łącze połączenia";
+
/* No comment provided by engineer. */
"Up to 100 last messages are sent to new members." = "Do nowych członków wysyłanych jest do 100 ostatnich wiadomości.";
@@ -4760,6 +5755,9 @@ chat item action */
/* No comment provided by engineer. */
"Update settings?" = "Zaktualizować ustawienia?";
+/* No comment provided by engineer. */
+"Updated conditions" = "Zaktualizowane warunki";
+
/* rcv group event chat item */
"updated group profile" = "zaktualizowano profil grupy";
@@ -4769,9 +5767,27 @@ chat item action */
/* No comment provided by engineer. */
"Updating settings will re-connect the client to all servers." = "Aktualizacja ustawień spowoduje ponowne połączenie klienta ze wszystkimi serwerami.";
+/* alert button */
+"Upgrade" = "Zaktualizuj";
+
+/* No comment provided by engineer. */
+"Upgrade address" = "Uaktualnij adres";
+
+/* alert message */
+"Upgrade address?" = "Uaktualnić adres?";
+
/* No comment provided by engineer. */
"Upgrade and open chat" = "Zaktualizuj i otwórz czat";
+/* alert message */
+"Upgrade group link?" = "Uaktualnić link do grupy?";
+
+/* No comment provided by engineer. */
+"Upgrade link" = "Uaktualnij link";
+
+/* No comment provided by engineer. */
+"Upgrade your address" = "Zaktualizuj swój adres";
+
/* No comment provided by engineer. */
"Upload errors" = "Błędy przesłania";
@@ -4793,18 +5809,30 @@ chat item action */
/* No comment provided by engineer. */
"Use .onion hosts" = "Użyj hostów .onion";
+/* No comment provided by engineer. */
+"Use %@" = "Użyj %@";
+
/* No comment provided by engineer. */
"Use chat" = "Użyj czatu";
/* new chat action */
"Use current profile" = "Użyj obecnego profilu";
+/* No comment provided by engineer. */
+"Use for files" = "Użyj dla plików";
+
+/* No comment provided by engineer. */
+"Use for messages" = "Użyj dla wiadomości";
+
/* No comment provided by engineer. */
"Use for new connections" = "Użyj dla nowych połączeń";
/* No comment provided by engineer. */
"Use from desktop" = "Użyj z komputera";
+/* No comment provided by engineer. */
+"Use incognito profile" = "Użyj profilu incognito";
+
/* No comment provided by engineer. */
"Use iOS call interface" = "Użyj interfejsu połączeń iOS";
@@ -4823,18 +5851,30 @@ chat item action */
/* No comment provided by engineer. */
"Use server" = "Użyj serwera";
+/* No comment provided by engineer. */
+"Use servers" = "Użyj serwerów";
+
/* No comment provided by engineer. */
"Use SimpleX Chat servers?" = "Użyć serwerów SimpleX Chat?";
/* No comment provided by engineer. */
"Use SOCKS proxy" = "Użyj proxy SOCKS";
+/* No comment provided by engineer. */
+"Use TCP port %@ when no port is specified." = "Jeśli nie podano portu, należy użyć portu TCP %@.";
+
+/* No comment provided by engineer. */
+"Use TCP port 443 for preset servers only." = "Używaj portu TCP 443 tylko dla domyślnych serwerów.";
+
/* No comment provided by engineer. */
"Use the app while in the call." = "Używaj aplikacji podczas połączenia.";
/* No comment provided by engineer. */
"Use the app with one hand." = "Korzystaj z aplikacji jedną ręką.";
+/* No comment provided by engineer. */
+"Use web port" = "Użyj portu internetowego";
+
/* No comment provided by engineer. */
"User selection" = "Wybór użytkownika";
@@ -4904,12 +5944,21 @@ chat item action */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "Film zostanie odebrany, gdy kontakt będzie online, poczekaj lub sprawdź później!";
+/* No comment provided by engineer. */
+"Videos" = "Wideo";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "Filmy i pliki do 1gb";
+/* No comment provided by engineer. */
+"View conditions" = "Zobacz warunki";
+
/* No comment provided by engineer. */
"View security code" = "Pokaż kod bezpieczeństwa";
+/* No comment provided by engineer. */
+"View updated conditions" = "Zobacz zaktualizowane warunki";
+
/* chat feature */
"Visible history" = "Widoczna historia";
@@ -4979,6 +6028,9 @@ chat item action */
/* No comment provided by engineer. */
"Welcome message is too long" = "Wiadomość powitalna jest zbyt długa";
+/* No comment provided by engineer. */
+"Welcome your contacts 👋" = "Powitaj swoje kontakty 👋";
+
/* No comment provided by engineer. */
"What's new" = "Co nowego";
@@ -4991,6 +6043,9 @@ chat item action */
/* No comment provided by engineer. */
"when IP hidden" = "gdy IP ukryty";
+/* No comment provided by engineer. */
+"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "Gdy włączony jest więcej niż jeden operator, żaden z nich nie ma metadanych pozwalających dowiedzieć się, kto się z kim komunikuje.";
+
/* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Gdy udostępnisz komuś profil incognito, będzie on używany w grupach, do których Cię zaprosi.";
@@ -5045,6 +6100,9 @@ chat item action */
/* No comment provided by engineer. */
"You accepted connection" = "Zaakceptowałeś połączenie";
+/* snd group event chat item */
+"you accepted this member" = "zaakceptowałeś tego członka";
+
/* No comment provided by engineer. */
"You allow" = "Pozwalasz";
@@ -5054,6 +6112,9 @@ chat item action */
/* No comment provided by engineer. */
"You are already connected to %@." = "Jesteś już połączony z %@.";
+/* No comment provided by engineer. */
+"You are already connected with %@." = "Zostałeś już połączony z %@.";
+
/* new chat sheet message */
"You are already connecting to %@." = "Już się łączysz z %@.";
@@ -5072,9 +6133,15 @@ chat item action */
/* new chat sheet title */
"You are already joining the group!\nRepeat join request?" = "Już dołączasz do grupy!\nPowtórzyć prośbę dołączenia?";
+/* subscription status explanation */
+"You are connected to the server used to receive messages from this connection." = "Jesteś połączony z serwerem służącym do odbierania wiadomości z tego połączenia.";
+
/* No comment provided by engineer. */
"You are invited to group" = "Jesteś zaproszony do grupy";
+/* subscription status explanation */
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "Nie masz połączenia z serwerem służącym do odbierania wiadomości w ramach tego połączenia (brak subskrypcji).";
+
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "Nie jesteś połączony z tymi serwerami. Prywatne trasowanie jest używane do dostarczania do nich wiadomości.";
@@ -5090,6 +6157,9 @@ chat item action */
/* No comment provided by engineer. */
"You can change it in Appearance settings." = "Możesz to zmienić w ustawieniach wyglądu.";
+/* No comment provided by engineer. */
+"You can configure servers via settings." = "Serwery można skonfigurować w ustawieniach.";
+
/* No comment provided by engineer. */
"You can create it later" = "Możesz go utworzyć później";
@@ -5114,6 +6184,9 @@ chat item action */
/* No comment provided by engineer. */
"You can send messages to %@ from Archived contacts." = "Możesz wysyłać wiadomości do %@ ze zarchiwizowanych kontaktów.";
+/* No comment provided by engineer. */
+"You can set connection name, to remember who the link was shared with." = "Możesz ustawić nazwę połączenia, aby zapamiętać, z kim link został udostępniony.";
+
/* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "Podgląd powiadomień na ekranie blokady można ustawić w ustawieniach.";
@@ -5138,6 +6211,9 @@ chat item action */
/* alert message */
"You can view invitation link again in connection details." = "Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia.";
+/* alert message */
+"You can view your reports in Chat with admins." = "Możesz przeglądać swoje raporty w czacie z administratorami.";
+
/* alert title */
"You can't send messages!" = "Nie możesz wysyłać wiadomości!";
@@ -5207,9 +6283,15 @@ chat item action */
/* chat list item description */
"you shared one-time link incognito" = "udostępniłeś jednorazowy link incognito";
+/* token info */
+"You should receive notifications." = "Powinieneś otrzymywać powiadomienia.";
+
/* snd group event chat item */
"you unblocked %@" = "odblokowałeś %@";
+/* No comment provided by engineer. */
+"You will be able to send messages **only after your request is accepted**." = "Będziesz mógł wysyłać wiadomości **dopiero po zaakceptowaniu Twojej prośby**.";
+
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "Zostaniesz połączony do grupy, gdy urządzenie gospodarza grupy będzie online, proszę czekać lub sprawdzić później!";
@@ -5228,6 +6310,9 @@ chat item action */
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Nadal będziesz otrzymywać połączenia i powiadomienia z wyciszonych profili, gdy są one aktywne.";
+/* No comment provided by engineer. */
+"You will stop receiving messages from this chat. Chat history will be preserved." = "Przestaniesz otrzymywać wiadomości z tego czatu. Historia czatu zostanie zachowana.";
+
/* No comment provided by engineer. */
"You will stop receiving messages from this group. Chat history will be preserved." = "Przestaniesz otrzymywać wiadomości od tej grupy. Historia czatu zostanie zachowana.";
@@ -5243,6 +6328,9 @@ chat item action */
/* No comment provided by engineer. */
"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Używasz profilu incognito dla tej grupy - aby zapobiec udostępnianiu głównego profilu zapraszanie kontaktów jest zabronione";
+/* No comment provided by engineer. */
+"Your business contact" = "Twój kontakt biznesowy";
+
/* No comment provided by engineer. */
"Your calls" = "Twoje połączenia";
@@ -5258,9 +6346,15 @@ chat item action */
/* No comment provided by engineer. */
"Your chat profiles" = "Twoje profile czatu";
+/* alert message */
+"Your chat was moved to %@ but an unexpected error occurred while redirecting you to the profile." = "Twoja rozmowa została przeniesiona do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd.";
+
/* No comment provided by engineer. */
"Your connection was moved to %@ but an error happened when switching profile." = "Twoje połączenie zostało przeniesione do %@, ale podczas przekierowywania do profilu wystąpił nieoczekiwany błąd.";
+/* No comment provided by engineer. */
+"Your contact" = "Twój kontakt";
+
/* No comment provided by engineer. */
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Twój kontakt wysłał plik, który jest większy niż obecnie obsługiwany maksymalny rozmiar (%@).";
@@ -5279,6 +6373,9 @@ chat item action */
/* No comment provided by engineer. */
"Your current profile" = "Twój obecny profil";
+/* No comment provided by engineer. */
+"Your group" = "Twoja grupa";
+
/* No comment provided by engineer. */
"Your ICE servers" = "Twoje serwery ICE";
diff --git a/apps/ios/product/README.md b/apps/ios/product/README.md
new file mode 100644
index 0000000000..107c0e6569
--- /dev/null
+++ b/apps/ios/product/README.md
@@ -0,0 +1,258 @@
+# SimpleX Chat iOS -- Product Overview
+
+> SimpleX Chat iOS product specification. Bidirectional code links: product docs reference source files, source files reference product docs.
+>
+> **Related spec:** [spec/README.md](../spec/README.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Vision](#vision)
+2. [Target Users](#target-users)
+3. [Capability Map](#capability-map)
+4. [Navigation Map](#navigation-map)
+5. [Related Specifications](#related-specifications)
+
+## Executive Summary
+
+SimpleX Chat is the first messaging platform with no user identifiers of any kind -- not even random numbers. It provides end-to-end encrypted messaging (with optional post-quantum cryptography), audio/video calls, file sharing, and group communication through a fully decentralized architecture where users control their own SMP relay servers. The iOS app is a native SwiftUI application backed by a Haskell core library.
+
+---
+
+## Vision
+
+SimpleX Chat is the first messaging platform that has no user identifiers -- not even random numbers. It uses double-ratchet end-to-end encryption with optional post-quantum cryptography. The system is fully decentralized with user-controlled SMP relay servers.
+
+The protocol design ensures that no server or network observer can determine who communicates with whom. Each conversation uses separate unidirectional messaging queues on potentially different servers, and there is no shared identifier between the sender and receiver queues.
+
+---
+
+## Target Users
+
+- **Privacy-conscious individuals** wanting secure messaging without phone-number or email-based identity
+- **Groups and communities** needing encrypted group communication with role-based access control
+- **Users avoiding identity linkage** who want to communicate without any persistent user identifier
+- **Organizations** needing self-hosted messaging infrastructure with full control over relay servers
+
+---
+
+## Capability Map
+
+### 1. Messaging
+
+Core message composition, delivery, and interaction features.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Text with markdown | Rich text formatting with SimpleX markdown syntax | `Shared/Views/Chat/ComposeMessage/ComposeView.swift` |
+| Images | Compressed inline images (up to 255KB) | `Shared/Views/Chat/ChatItem/CIImageView.swift` |
+| Video | Video message recording and playback | `Shared/Views/Chat/ChatItem/CIVideoView.swift` |
+| Voice messages | Audio recording and playback (5min / 510KB limit) | `Shared/Views/Chat/ChatItem/CIVoiceView.swift` |
+| File sharing | Files up to 1GB via XFTP protocol | `Shared/Views/Chat/ChatItem/CIFileView.swift` |
+| Link previews | OpenGraph metadata extraction and display | `Shared/Views/Chat/ChatItem/CILinkView.swift` |
+| Message reactions | Emoji reactions on sent/received messages | `Shared/Views/Chat/ChatItem/EmojiItemView.swift` |
+| Message editing | Edit previously sent messages | `Shared/Views/Chat/ComposeMessage/ComposeView.swift` |
+| Message deletion | Broadcast delete (for recipient) or internal-only delete | `Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift` |
+| Timed messages | Self-destructing messages with configurable TTL | `Shared/Views/Chat/ChatItem/CIChatFeatureView.swift` |
+| Quoted replies | Reply to specific messages with quote context | `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` |
+| Forwarding | Forward messages between chats | `Shared/Views/Chat/ChatItemForwardingView.swift` |
+| Search | Full-text search within conversations | `Shared/Views/Chat/ChatView.swift` |
+| Message reports | Report messages to group moderators | `Shared/Views/Chat/ChatView.swift` |
+
+### 2. Contacts
+
+Establishing, managing, and verifying contacts.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Add via SimpleX address | Connect using a SimpleX contact address | `Shared/Views/NewChat/NewChatView.swift` |
+| Add via QR code | Scan QR code to establish connection | `Shared/Views/Chat/ScanCodeView.swift` |
+| Contact requests | Accept or reject incoming contact requests | `Shared/Views/ChatList/ContactRequestView.swift` |
+| Local aliases | Set private display names for contacts | `Shared/Views/Chat/ChatInfoView.swift` |
+| Contact verification | Compare security codes out-of-band | `Shared/Views/Chat/VerifyCodeView.swift` |
+| Blocking | Block contacts from sending messages | `Shared/Views/Chat/ChatInfoView.swift` |
+| Incognito mode | Per-contact random profile generation | `Shared/Views/UserSettings/IncognitoHelp.swift` |
+| Bot detection | Identify automated/bot contacts | `SimpleXChat/ChatTypes.swift` |
+
+### 3. Groups
+
+Multi-party encrypted conversations with role-based management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Create groups | Create new group with initial members | `Shared/Views/NewChat/AddGroupView.swift` |
+| Invite members | Invite by individual contact or link | `Shared/Views/Chat/Group/AddGroupMembersView.swift` |
+| Member roles | Owner, admin, moderator, member, observer | `SimpleXChat/ChatTypes.swift` |
+| Member admission | Queue-based admission with review workflow | `Shared/Views/Chat/Group/MemberAdmissionView.swift` |
+| Group links | Shareable invite links for groups | `Shared/Views/Chat/Group/GroupLinkView.swift` |
+| Business chat mode | Structured business communication groups | `Shared/Views/Chat/Group/GroupChatInfoView.swift` |
+| Content moderation | Member reports and moderator actions | `Shared/Views/Chat/Group/MemberSupportView.swift` |
+| Group preferences | Configure group-level feature settings | `Shared/Views/Chat/Group/GroupPreferencesView.swift` |
+| Member direct contacts | Establish direct chats from group membership | `Shared/Views/Chat/Group/GroupMemberInfoView.swift` |
+
+### 4. Calling
+
+End-to-end encrypted audio and video communication.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| E2E encrypted calls | Audio/video calls via WebRTC with E2E encryption | `Shared/Views/Call/WebRTCClient.swift` |
+| CallKit integration | Native iOS system call UI (ring, answer, decline) | `Shared/Views/Call/CallController.swift` |
+| Audio device switching | Switch between speaker, earpiece, Bluetooth | `Shared/Views/Call/CallAudioDeviceManager.swift` |
+| Call history | Call events displayed as chat items | `Shared/Views/Chat/ChatItem/CICallItemView.swift` |
+| Incoming call view | Dedicated UI for incoming call notifications | `Shared/Views/Call/IncomingCallView.swift` |
+
+### 5. Privacy & Security
+
+Encryption, authentication, and privacy controls.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| E2E encryption | Double-ratchet encryption for all messages | `SimpleXChat/API.swift` |
+| Post-quantum encryption | Optional PQ key exchange for direct chats | `SimpleXChat/ChatTypes.swift` |
+| Local authentication | Face ID, Touch ID, or app passcode lock | `Shared/Views/LocalAuth/LocalAuthView.swift` |
+| Hidden profiles | Password-protected profiles invisible in UI | `Shared/Views/UserSettings/HiddenProfileView.swift` |
+| Database encryption | AES encryption of local SQLite database | `Shared/Views/Database/DatabaseEncryptionView.swift` |
+| Screen privacy | Blur app content when in app switcher | `Shared/Views/UserSettings/PrivacySettings.swift` |
+| Encrypted file storage | Local files encrypted at rest | `SimpleXChat/CryptoFile.swift` |
+| Delivery receipts control | Toggle delivery/read receipts per contact/group | `Shared/Views/UserSettings/SetDeliveryReceiptsView.swift` |
+
+### 6. User Management
+
+Multiple profiles and identity management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Multiple profiles | Multiple user profiles within one app | `Shared/Views/UserSettings/UserProfilesView.swift` |
+| Active user switching | Switch between profiles via user picker | `Shared/Views/ChatList/UserPicker.swift` |
+| Incognito contacts | Per-contact random identities | `Shared/Views/UserSettings/IncognitoHelp.swift` |
+| Profile sharing | Share profile via contact address link | `Shared/Views/UserSettings/UserAddressView.swift` |
+| User muting | Mute notifications for specific profiles | `Shared/Views/ChatList/UserPicker.swift` |
+
+### 7. Network
+
+Server configuration, proxy support, and connectivity.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Custom SMP servers | Configure personal SMP relay servers | `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` |
+| Custom XFTP servers | Configure personal XFTP file servers | `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` |
+| Tor/onion support | Route traffic through Tor .onion addresses | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+| SOCKS5 proxy | Route connections through SOCKS5 proxy | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+| Custom ICE servers | Configure WebRTC ICE/TURN servers | `Shared/Views/UserSettings/RTCServers.swift` |
+| Network timeouts | Configure connection timeout parameters | `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` |
+
+### 8. Customization
+
+Visual appearance and UI preferences.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Themes | Light, dark, SimpleX, black, and custom themes | `Shared/Theme/ThemeManager.swift` |
+| Wallpapers | Preset and custom chat wallpapers | `Shared/Views/Helpers/ChatWallpaper.swift` |
+| Chat bubble styling | Customize message bubble appearance | `SimpleXChat/Theme/ThemeTypes.swift` |
+| One-handed UI mode | Compact layout for single-hand use | `Shared/Views/ChatList/OneHandUICard.swift` |
+| Language selection | In-app language override | `Shared/Views/UserSettings/AppearanceSettings.swift` |
+
+### 9. Data Management
+
+Import, export, encryption, and storage management.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Export/import profiles | Full database export and import | `Shared/Views/Database/DatabaseView.swift` |
+| Database encryption | Encrypt/decrypt local database with passphrase | `Shared/Views/Database/DatabaseEncryptionView.swift` |
+| Local file encryption | Encrypt stored media and attachments | `SimpleXChat/CryptoFile.swift` |
+| Storage breakdown | View storage usage by category | `Shared/Views/UserSettings/StorageView.swift` |
+| Device-to-device migration | Migrate full profile between iOS devices | `Shared/Views/Migration/MigrateFromDevice.swift` |
+
+### 10. Desktop Integration
+
+Remote control of the mobile app from a desktop client.
+
+| Feature | Description | Key Source (Swift) |
+|---------|-------------|--------------------|
+| Remote control pairing | Pair with desktop app via QR code | `Shared/Views/RemoteAccess/ConnectDesktopView.swift` |
+| Session management | Manage active desktop control sessions | `Shared/Views/RemoteAccess/ConnectDesktopView.swift` |
+
+---
+
+## Navigation Map
+
+```
+Onboarding
+ OnboardingView.swift
+ -> SimpleXInfo -> CreateProfile -> ChooseServerOperators -> SetNotificationsMode -> CreateSimpleXAddress
+ -> ChatListView (home)
+
+ChatListView (home)
+ Shared/Views/ChatList/ChatListView.swift
+ -> ChatView .................. (tap conversation row)
+ -> NewChatMenuButton ......... (+ button)
+ -> SettingsView .............. (gear icon)
+ -> UserPicker ................ (avatar tap)
+ -> TagListView ............... (tag filter bar)
+ -> ServersSummaryView ........ (server status)
+
+ChatView
+ Shared/Views/Chat/ChatView.swift
+ -> ChatInfoView .............. (contact name tap, direct chat)
+ -> GroupChatInfoView ......... (group name tap, group chat)
+ -> ActiveCallView ............ (call button)
+ -> ComposeView ............... (message input area)
+ -> ChatItemInfoView .......... (long press -> info)
+ -> ChatItemForwardingView .... (long press -> forward)
+ -> SecondaryChatView ......... (member support thread)
+
+ChatInfoView
+ Shared/Views/Chat/ChatInfoView.swift
+ -> ContactPreferencesView .... (preferences)
+ -> VerifyCodeView ............ (verify security code)
+
+GroupChatInfoView
+ Shared/Views/Chat/Group/GroupChatInfoView.swift
+ -> GroupProfileView .......... (edit profile)
+ -> AddGroupMembersView ....... (invite members)
+ -> GroupLinkView ............. (manage group link)
+ -> MemberAdmissionView ....... (admission settings)
+ -> GroupPreferencesView ...... (group feature settings)
+ -> GroupMemberInfoView ....... (tap member)
+ -> GroupWelcomeView .......... (welcome message)
+
+NewChatMenuButton
+ Shared/Views/NewChat/NewChatMenuButton.swift
+ -> NewChatView ............... (QR scanner / paste link)
+ -> AddGroupView .............. (create group)
+ -> UserAddressView ........... (create SimpleX address)
+
+SettingsView
+ Shared/Views/UserSettings/SettingsView.swift
+ -> AppearanceSettings ........ (themes, wallpapers, UI)
+ -> NetworkAndServers ......... (SMP/XFTP/proxy config)
+ -> PrivacySettings ........... (privacy toggles)
+ -> NotificationsView ......... (push notification mode)
+ -> DatabaseView .............. (export/import/encrypt)
+ -> CallSettings .............. (call preferences)
+ -> StorageView ............... (storage usage)
+ -> VersionView ............... (about/version)
+ -> DeveloperView ............. (developer options)
+
+UserPicker
+ Shared/Views/ChatList/UserPicker.swift
+ -> UserProfilesView .......... (manage all profiles)
+ -> UserAddressView ........... (SimpleX address)
+ -> PreferencesView ........... (user preferences)
+ -> SettingsView .............. (app settings)
+ -> ConnectDesktopView ........ (pair with desktop)
+```
+
+---
+
+## Related Specifications
+
+- [concepts.md](concepts.md) -- Feature concept index with bidirectional code links
+- [glossary.md](glossary.md) -- Domain term glossary
+- [spec/README.md](../spec/README.md) -- Technical specification overview
+- [spec/architecture.md](../spec/architecture.md) -- Architecture specification
+- Haskell core: `../../src/Simplex/Chat/Controller.hs`, `../../src/Simplex/Chat/Types.hs`
+- Swift model: `Shared/Model/ChatModel.swift`, `SimpleXChat/ChatTypes.swift`
+- Swift API bridge: `SimpleXChat/API.swift`, `Shared/Model/SimpleXAPI.swift`
diff --git a/apps/ios/product/concepts.md b/apps/ios/product/concepts.md
new file mode 100644
index 0000000000..3fa722d47a
--- /dev/null
+++ b/apps/ios/product/concepts.md
@@ -0,0 +1,84 @@
+# SimpleX Chat iOS -- Concept Index
+
+> SimpleX Chat iOS concept index. Maps every product concept to its documentation and source code with bidirectional links.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/state.md](../spec/state.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Feature Concepts](#section-1-feature-concepts)
+2. [Entity Index](#section-2-entity-index)
+
+## Executive Summary
+
+This document provides a structured mapping between product-level concepts, their documentation, and their implementation in both the Swift iOS layer and the Haskell core library. All source paths are relative to `apps/ios/` for Swift and use `../../src/` prefix for Haskell files (relative to `apps/ios/`).
+
+---
+
+## Section 1: Feature Concepts
+
+| # | Concept | Product Docs | Spec Docs | Source Files (Swift) | Source Files (Haskell) |
+|---|---------|-------------|-----------|---------------------|----------------------|
+| 1 | Chat List | [views/chat-list.md](views/chat-list.md), [views/onboarding.md](views/onboarding.md) | [spec/client/chat-list.md](../spec/client/chat-list.md) | `Shared/Views/ChatList/ChatListView.swift` | `Controller.hs` (`APIGetChats`) |
+| 2 | Direct Chat | [views/chat.md](views/chat.md), [flows/messaging.md](flows/messaging.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `Shared/Views/Chat/ChatView.swift`, `ChatInfoView.swift` | `Types.hs` (`Contact`), `Messages.hs` |
+| 3 | Group Chat | [views/chat.md](views/chat.md), [views/group-info.md](views/group-info.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `Shared/Views/Chat/ChatView.swift`, `Group/GroupChatInfoView.swift` | `Types.hs` (`GroupInfo`, `GroupMember`) |
+| 4 | Message Composition | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ComposeMessage/ComposeView.swift`, `SendMessageView.swift` | `Controller.hs` (`APISendMessages`) |
+| 5 | Message Reactions | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/EmojiItemView.swift` | `Controller.hs` (`APIChatItemReaction`) |
+| 6 | Message Editing | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ComposeMessage/ComposeView.swift`, `ChatItemInfoView.swift` | `Controller.hs` (`APIUpdateChatItem`) |
+| 7 | Message Deletion | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/MarkedDeletedItemView.swift`, `DeletedItemView.swift` | `Controller.hs` (`APIDeleteChatItem`) |
+| 8 | Timed Messages | [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md) | `ChatItem/CIChatFeatureView.swift` | `Types/Preferences.hs` (`TimedMessagesPreference`) |
+| 9 | Voice Messages | [views/chat.md](views/chat.md) | [spec/client/compose.md](../spec/client/compose.md) | `ChatItem/CIVoiceView.swift`, `ComposeVoiceView.swift` | `Protocol.hs` (`MCVoice`) |
+| 10 | File Transfer | [flows/file-transfer.md](flows/file-transfer.md) | [spec/services/files.md](../spec/services/files.md) | `ChatItem/CIFileView.swift`, `SimpleXChat/FileUtils.swift` | `Files.hs`, `Store/Files.hs` |
+| 11 | Link Previews | [views/chat.md](views/chat.md) | [spec/client/chat-view.md](../spec/client/chat-view.md) | `ChatItem/CILinkView.swift`, `ComposeLinkView.swift` | `Protocol.hs` (`MCLink`) |
+| 12 | Contact Connection | [flows/connection.md](flows/connection.md), [views/new-chat.md](views/new-chat.md) | [spec/api.md](../spec/api.md) | `NewChat/NewChatView.swift`, `QRCode.swift` | `Controller.hs` (`APIConnect`, `APIAddContact`) |
+| 13 | Contact Verification | [views/contact-info.md](views/contact-info.md) | [spec/api.md](../spec/api.md) | `Shared/Views/Chat/VerifyCodeView.swift` | `Controller.hs` (`APIVerifyContact`) |
+| 14 | Group Management | [flows/group-lifecycle.md](flows/group-lifecycle.md) | [spec/api.md](../spec/api.md), [spec/database.md](../spec/database.md) | `NewChat/AddGroupView.swift`, `Group/GroupChatInfoView.swift` | `Controller.hs` (`APINewGroup`), `Store/Groups.hs` |
+| 15 | Group Links | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `Group/GroupLinkView.swift` | `Controller.hs` (`APICreateGroupLink`) |
+| 16 | Member Roles | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `SimpleXChat/ChatTypes.swift`, `Group/GroupMemberInfoView.swift` | `Types/Shared.hs` (`GroupMemberRole`) |
+| 17 | Audio/Video Calls | [views/call.md](views/call.md), [flows/calling.md](flows/calling.md) | [spec/services/calls.md](../spec/services/calls.md) | `Call/ActiveCallView.swift`, `CallController.swift`, `WebRTCClient.swift` | `Call.hs` (`RcvCallInvitation`, `CallType`) |
+| 18 | Push Notifications | [views/settings.md](views/settings.md) | [spec/services/notifications.md](../spec/services/notifications.md) | `Model/NtfManager.swift`, `SimpleX NSE/NotificationService.swift` | `Controller.hs` |
+| 19 | User Profiles | [views/user-profiles.md](views/user-profiles.md) | [spec/state.md](../spec/state.md), [spec/client/navigation.md](../spec/client/navigation.md) | `UserSettings/UserProfilesView.swift`, `ChatList/UserPicker.swift` | `Types.hs` (`User`), `Store/Profiles.hs` |
+| 20 | Incognito Mode | [views/contact-info.md](views/contact-info.md) | [spec/api.md](../spec/api.md) | `UserSettings/IncognitoHelp.swift` | `ProfileGenerator.hs`, `Types.hs` |
+| 21 | Hidden Profiles | [views/user-profiles.md](views/user-profiles.md) | [spec/api.md](../spec/api.md) | `UserSettings/HiddenProfileView.swift` | `Controller.hs` (`APIHideUser`, `APIUnhideUser`) |
+| 22 | Local Authentication | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `LocalAuth/LocalAuthView.swift`, `PasscodeView.swift` | N/A (iOS-only) |
+| 23 | Database Encryption | [views/settings.md](views/settings.md) | [spec/database.md](../spec/database.md) | `Database/DatabaseEncryptionView.swift`, `DatabaseView.swift` | `Controller.hs` (`APIExportArchive`) |
+| 24 | Theme System | [views/settings.md](views/settings.md) | [spec/services/theme.md](../spec/services/theme.md) | `Theme/ThemeManager.swift`, `SimpleXChat/Theme/ThemeTypes.swift` | `Types/UITheme.hs` |
+| 25 | Network Configuration | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `NetworkAndServers/NetworkAndServers.swift`, `ProtocolServersView.swift` | `Controller.hs` (`APISetNetworkConfig`) |
+| 26 | Device Migration | [flows/onboarding.md](flows/onboarding.md) | [spec/database.md](../spec/database.md) | `Migration/MigrateFromDevice.swift`, `MigrateToDevice.swift` | `Archive.hs` |
+| 27 | Remote Desktop | [views/settings.md](views/settings.md) | [spec/architecture.md](../spec/architecture.md) | `RemoteAccess/ConnectDesktopView.swift` | `Remote.hs`, `Remote/Types.hs` |
+| 28 | Chat Tags | [views/chat-list.md](views/chat-list.md) | [spec/state.md](../spec/state.md) | `ChatList/TagListView.swift`, `ChatListView.swift` | `Types.hs` (`ChatTag`), `Controller.hs` |
+| 29 | User Address | [views/settings.md](views/settings.md) | [spec/api.md](../spec/api.md) | `UserSettings/UserAddressView.swift`, `Onboarding/AddressCreationCard.swift` | `Controller.hs` (`APICreateMyAddress`) |
+| 30 | Member Support Chat | [views/group-info.md](views/group-info.md) | [spec/api.md](../spec/api.md) | `Group/MemberSupportView.swift`, `MemberAdmissionView.swift` | `Messages.hs` (`GroupChatScope`), `Controller.hs` |
+| 31 | Channels (Relays) | [glossary.md](glossary.md), [views/chat.md](views/chat.md) | [spec/api.md](../spec/api.md), [spec/state.md](../spec/state.md), [spec/client/chat-view.md](../spec/client/chat-view.md), [spec/client/compose.md](../spec/client/compose.md) | `SimpleXChat/ChatTypes.swift` (`RelayStatus`, `RelayStatus.text`, `GroupRelay`, `GroupMemberRole.relay`, `CIDirection.channelRcv`, `GroupInfo.chatIconName`, `userCantSendReason`), `Shared/Views/Chat/ChatView.swift` (channel message rendering), `Shared/Views/Chat/ComposeMessage/ComposeView.swift` (`sendAsGroup`, Broadcast placeholder), `Shared/Views/Chat/Group/GroupChatInfoView.swift` (channel info adaptations), `Shared/Views/Chat/Group/ChannelMembersView.swift`, `Shared/Views/Chat/Group/ChannelRelaysView.swift`, `Shared/Model/AppAPITypes.swift` (`GroupShortLinkInfo`, `UserChatRelay`), `Shared/Model/SimpleXAPI.swift` (`apiNewPublicGroup`), `SimpleX SE/ShareAPI.swift` (channel `sendAsGroup`) | `Controller.hs` (`APINewPublicGroup`) |
+
+---
+
+## Section 2: Entity Index
+
+Core data entities, their storage, and the operations that manage their lifecycle.
+
+| Entity | DB Table (Haskell) | Created By | Read By | Mutated By | Deleted By |
+|--------|-------------------|------------|---------|------------|------------|
+| **User** | `users` | `CreateActiveUser` in `Controller.hs` | `ListUsers`, `APISetActiveUser` in `Controller.hs` | `APISetActiveUser`, `APIHideUser`, `APIUnhideUser`, `APIMuteUser`, `APIUpdateProfile` in `Controller.hs` | `APIDeleteUser` in `Controller.hs`; `Store/Profiles.hs` |
+| **Contact** | `contacts`, `contact_profiles` | `APIAddContact`, `APIConnect` in `Controller.hs` | `APIGetChat` in `Controller.hs`; `Store/Direct.hs` (getContact) | `APISetContactAlias`, `APISetConnectionAlias` in `Controller.hs`; `Store/Direct.hs` | `APIDeleteChat` in `Controller.hs`; `Store/Direct.hs` (deleteContact) |
+| **GroupInfo** | `groups`, `group_profiles` | `APINewGroup` in `Controller.hs`; `Store/Groups.hs` (createNewGroup) | `APIGetChat`, `APIGroupInfo` in `Controller.hs`; `Store/Groups.hs` | `APIUpdateGroupProfile` in `Controller.hs`; `Store/Groups.hs` (updateGroupProfile) | `APIDeleteChat` in `Controller.hs`; `Store/Groups.hs` (deleteGroup) |
+| **GroupMember** | `group_members`, `contact_profiles` | `APIAddMember`, `APIJoinGroup` in `Controller.hs`; `Store/Groups.hs` (createNewGroupMember) | `APIListMembers` in `Controller.hs`; `Store/Groups.hs` (getGroupMembers) | `APIMembersRole` in `Controller.hs`; `Store/Groups.hs` (updateGroupMemberRole) | `APIRemoveMembers` in `Controller.hs`; `Store/Groups.hs` (deleteGroupMember) |
+| **ChatItem** | `chat_items`, `chat_item_versions` | `APISendMessages` in `Controller.hs`; `Store/Messages.hs` (createNewChatItem) | `APIGetChat`, `APIGetChatItems` in `Controller.hs`; `Store/Messages.hs` (getChatItems) | `APIUpdateChatItem`, `APIChatItemReaction` in `Controller.hs`; `Store/Messages.hs` (updateChatItem) | `APIDeleteChatItem` in `Controller.hs`; `Store/Messages.hs` (deleteChatItem) |
+| **Connection** | `connections` | `createConnection` via SMP agent; `Store/Connections.hs` | `Store/Connections.hs` (getConnectionEntity) | `Store/Connections.hs` (updateConnectionStatus) | `Store/Connections.hs` (deleteConnection) |
+| **FileTransfer** | `files`, `snd_files`, `rcv_files`, `xftp_file_descriptions` | `APISendMessages` (with file), `ReceiveFile` in `Controller.hs`; `Store/Files.hs` | `Store/Files.hs` (getFileTransfer) | `Store/Files.hs` (updateFileStatus, updateFileProgress) | `Store/Files.hs` (deleteFileTransfer) |
+| **GroupLink** | `user_contact_links` | `APICreateGroupLink` in `Controller.hs`; `Store/Groups.hs` | `APIGetGroupLink` in `Controller.hs`; `Store/Groups.hs` | N/A (recreated on change) | `APIDeleteGroupLink` in `Controller.hs`; `Store/Groups.hs` |
+| **ChatTag** | `chat_tags`, `chat_tags_chats` | `APICreateChatTag` in `Controller.hs` | `APIGetChats` in `Controller.hs` | `APIUpdateChatTag`, `APISetChatTags` in `Controller.hs` | `APIDeleteChatTag` in `Controller.hs` |
+| **RcvCallInvitation** | In-memory (not persisted) | Received via `XCallInv` message in `Library/Subscriber.hs`; stored in `ChatModel.callInvitations` | `CallController.swift`, `IncomingCallView.swift` | Updated on call accept/reject in `CallManager.swift` | Removed on call end/reject; `Controller.hs` |
+
+---
+
+## Cross-References
+
+- Product overview: [README.md](README.md)
+- Glossary: [glossary.md](glossary.md)
+- Haskell core controller: `../../src/Simplex/Chat/Controller.hs`
+- Haskell core types: `../../src/Simplex/Chat/Types.hs`
+- Haskell store layer: `../../src/Simplex/Chat/Store/` (Direct.hs, Groups.hs, Messages.hs, Files.hs, Profiles.hs, Connections.hs)
+- Swift model: `Shared/Model/ChatModel.swift`
+- Swift API types: `SimpleXChat/APITypes.swift`, `SimpleXChat/ChatTypes.swift`
+- Swift API bridge: `SimpleXChat/API.swift`, `Shared/Model/SimpleXAPI.swift`
diff --git a/apps/ios/product/flows/calling.md b/apps/ios/product/flows/calling.md
new file mode 100644
index 0000000000..86cb026625
--- /dev/null
+++ b/apps/ios/product/flows/calling.md
@@ -0,0 +1,179 @@
+# Audio/Video Call Flow
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Overview
+
+WebRTC-based audio and video calling in SimpleX Chat iOS. Calls are end-to-end encrypted with an additional shared key negotiated over the E2E encrypted SMP channel. The iOS app integrates with CallKit for native call UI (incoming call screen, lock screen integration) with a fallback mode for regions where CallKit is restricted (China). Call signaling (offer/answer/ICE candidates) is exchanged via SMP messages, not through a central signaling server.
+
+## Prerequisites
+
+- Established direct contact connection (calls are 1:1 only, not available in groups)
+- Microphone permission granted (audio calls)
+- Camera permission granted (video calls)
+- Network connectivity for WebRTC peer-to-peer or relay
+
+## Step-by-Step Processes
+
+### 1. Initiate Call
+
+1. User opens a direct chat in `ChatView`.
+2. Taps the audio or video call button in the navigation bar.
+3. `CallController` determines call type: `CallType(media: .audio/.video, capabilities: CallCapabilities(encryption: true))`.
+4. If CallKit is enabled (`CallController.useCallKit()`):
+ - `CXStartCallAction` is requested via `CXCallController`.
+ - CallKit reports the outgoing call.
+ - `provider(perform: CXStartCallAction)` fulfills and reports `reportOutgoingCall(startedConnectingAt:)`.
+5. Calls `apiSendCallInvitation(contact:callType:)`:
+ ```swift
+ func apiSendCallInvitation(_ contact: Contact, _ callType: CallType) async throws
+ ```
+6. Sends `ChatCommand.apiSendCallInvitation(contact:callType:)`.
+7. Core sends the call invitation to the contact via SMP.
+8. `ChatModel.shared.activeCall` is set with the call state.
+
+### 2. Receive Call
+
+1. `ChatReceiver` receives `ChatEvent.callInvitation(callInvitation: RcvCallInvitation)`.
+2. `RcvCallInvitation` contains: `user`, `contact`, `callType`, `sharedKey`, `callUUID`, `callTs`.
+3. Processing in `processReceivedMsg`:
+ - Call invitation is stored in `chatModel.callInvitations`.
+4. If CallKit is enabled:
+ - `CXProvider.reportNewIncomingCall` presents the native iOS incoming call UI.
+ - Works even on lock screen and in background.
+5. If CallKit is disabled (China / user preference):
+ - `IncomingCallView` is shown as an in-app overlay.
+ - `SoundPlayer` plays the ringtone.
+6. User chooses to accept or reject.
+
+### 3. Accept Call
+
+1. **Via CallKit**: User swipes to accept on the native incoming call screen.
+ - `provider(perform: CXAnswerCallAction)` is triggered.
+ - Waits for chat to be started if needed (`waitUntilChatStarted(timeoutMs: 30_000)`).
+ - `callManager.answerIncomingCall(callUUID:)` begins WebRTC setup.
+ - `fulfillOnConnect` is set -- the action is fulfilled only when WebRTC reaches connected state (required for audio/mic to work on lock screen).
+2. **Via in-app UI**: User taps "Accept" in `IncomingCallView`.
+ - Directly starts WebRTC setup.
+
+### 4. Reject Call
+
+1. **Via CallKit**: User taps "Decline" on native UI.
+ - `provider(perform: CXEndCallAction)` is triggered.
+ - `callManager.endCall(callUUID:)` cleans up.
+2. **Via API**: `apiRejectCall(contact:)` sends rejection to peer.
+3. Call invitation is removed from `chatModel.callInvitations`.
+
+### 5. WebRTC Setup (Signaling)
+
+All signaling messages are exchanged via E2E encrypted SMP messages (no central signaling server).
+
+**Caller side:**
+1. `WebRTCClient` creates a `RTCPeerConnection`.
+2. Creates SDP offer.
+3. Calls `apiSendCallOffer(contact:rtcSession:rtcIceCandidates:media:capabilities:)`:
+ ```swift
+ func apiSendCallOffer(_ contact: Contact, _ rtcSession: String, _ rtcIceCandidates: String,
+ media: CallMediaType, capabilities: CallCapabilities) async throws
+ ```
+4. Constructs `WebRTCCallOffer(callType:rtcSession:)` and sends via `ChatCommand.apiSendCallOffer`.
+5. Gathers ICE candidates and sends via `apiSendCallExtraInfo(contact:rtcIceCandidates:)`.
+
+**Callee side:**
+1. Receives the offer via SMP.
+2. `WebRTCClient` sets remote description from the offer.
+3. Creates SDP answer.
+4. Calls `apiSendCallAnswer(contact:rtcSession:rtcIceCandidates:)`:
+ ```swift
+ func apiSendCallAnswer(_ contact: Contact, _ rtcSession: String, _ rtcIceCandidates: String) async throws
+ ```
+5. Constructs `WebRTCSession(rtcSession:rtcIceCandidates:)` and sends.
+6. Gathers and sends additional ICE candidates via `apiSendCallExtraInfo`.
+
+### 6. Media Streaming
+
+1. WebRTC peer connection transitions to connected state.
+2. If CallKit is used, `fulfillOnConnect` action is fulfilled (enables audio hardware).
+3. Audio/video streams are active.
+4. `ActiveCallView` displays:
+ - Remote video (full screen)
+ - Local video preview (picture-in-picture corner)
+ - Call controls: mute, speaker, camera toggle, end call
+ - Call duration timer
+5. `CallViewRenderers` manages WebRTC video rendering surfaces.
+6. Call status updates are sent via `apiCallStatus(contact:status:)`.
+
+### 7. Audio Routing
+
+1. `CallAudioDeviceManager` handles audio device selection.
+2. Options: earpiece (receiver), speaker, Bluetooth devices.
+3. `AudioDevicePicker` provides UI for device selection during call.
+4. Uses `AVAudioSession` for routing configuration.
+
+### 8. End Call
+
+1. Either party taps "End" button.
+2. Calls `apiEndCall(contact:)`:
+ ```swift
+ func apiEndCall(_ contact: Contact) async throws
+ ```
+3. Sends `ChatCommand.apiEndCall(contact:)` via SMP to notify peer.
+4. `WebRTCClient` closes peer connection, releases media resources.
+5. If CallKit: `CXEndCallAction` is requested, `provider(perform: CXEndCallAction)` fulfills.
+6. `ChatModel.shared.activeCall` is cleared.
+7. A `CICallItemView` event item is added to the chat history (call duration, type).
+
+### 9. CallKit-Free Mode
+
+1. `CallController.isInChina` checks `SKStorefront().countryCode == "CHN"`.
+2. If in China or user disabled CallKit (`callKitEnabledGroupDefault`): `useCallKit()` returns `false`.
+3. Incoming calls use `IncomingCallView` overlay instead of native CallKit UI.
+4. `SoundPlayer` handles ringtone playback.
+5. No lock-screen call answering; app must be in foreground or notified via push.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CallType` | `SimpleXChat/CallTypes.swift` | `media: CallMediaType` (.audio/.video), `capabilities: CallCapabilities` |
+| `CallMediaType` | `SimpleXChat/CallTypes.swift` | `.audio` or `.video` |
+| `CallCapabilities` | `SimpleXChat/CallTypes.swift` | `encryption: Bool` for E2E call encryption support |
+| `RcvCallInvitation` | `SimpleXChat/CallTypes.swift` | Incoming call: user, contact, callType, sharedKey, callUUID, callTs |
+| `WebRTCCallOffer` | `SimpleXChat/CallTypes.swift` | SDP offer with call type and WebRTC session data |
+| `WebRTCSession` | `SimpleXChat/CallTypes.swift` | `rtcSession` (SDP) and `rtcIceCandidates` (serialized) |
+| `WebRTCExtraInfo` | `SimpleXChat/CallTypes.swift` | Additional ICE candidates sent after initial offer/answer |
+| `WebRTCCallStatus` | `SimpleXChat/CallTypes.swift` | Call lifecycle states for status reporting |
+| `CallMediaSource` | `SimpleXChat/CallTypes.swift` | `.mic`, `.camera`, `.screenAudio`, `.screenVideo`, `.unknown` |
+| `VideoCamera` | `SimpleXChat/CallTypes.swift` | `.user` (front) or `.environment` (rear) camera |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| Chat not ready on CallKit answer | App suspended, slow startup | `waitUntilChatStarted` with 30s timeout; `action.fail()` on timeout |
+| Call invitation not found | Race condition between notification and event processing | `justRefreshCallInvitations()` retry |
+| WebRTC peer connection failure | NAT traversal, network issues | Call ends with error status |
+| CallKit action fail | Internal state mismatch | `action.fail()` called, call cleaned up |
+| No camera/mic permission | User denied permissions | Permission request dialog shown |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/Call/CallController.swift` | CallKit integration, CXProvider delegate, PKPushRegistry, call lifecycle management |
+| `Shared/Views/Call/CallManager.swift` | Call state management, starting/answering/ending calls |
+| `Shared/Views/Call/WebRTCClient.swift` | WebRTC peer connection, SDP offer/answer, ICE candidate handling |
+| `Shared/Views/Call/ActiveCallView.swift` | Active call UI: video renderers, controls, duration |
+| `Shared/Views/Call/CallViewRenderers.swift` | WebRTC video rendering surfaces |
+| `Shared/Views/Call/IncomingCallView.swift` | Non-CallKit incoming call overlay |
+| `Shared/Views/Call/CallAudioDeviceManager.swift` | Audio routing: speaker, earpiece, Bluetooth |
+| `Shared/Views/Call/AudioDevicePicker.swift` | Audio device selection UI |
+| `Shared/Views/Call/SoundPlayer.swift` | Ringtone and call sound playback |
+| `Shared/Views/Call/WebRTC.swift` | WebRTC configuration and utilities |
+| `SimpleXChat/CallTypes.swift` | All call-related type definitions |
+| `Shared/Model/SimpleXAPI.swift` | Call API functions: `apiSendCallInvitation`, `apiSendCallOffer`, `apiSendCallAnswer`, `apiSendCallExtraInfo`, `apiEndCall`, `apiRejectCall`, `apiCallStatus` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Calls capability
+- `apps/ios/product/flows/connection.md` -- Calls require an established direct connection
diff --git a/apps/ios/product/flows/connection.md b/apps/ios/product/flows/connection.md
new file mode 100644
index 0000000000..c621dc5124
--- /dev/null
+++ b/apps/ios/product/flows/connection.md
@@ -0,0 +1,178 @@
+# Connection Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/architecture.md](../../spec/architecture.md)
+
+## Overview
+
+Establishing contact between two SimpleX Chat users. SimpleX uses no user identifiers; connections are formed through one-time invitation links or permanent SimpleX addresses. Each connection creates unique unidirectional SMP queues, ensuring no server can correlate sender and receiver. Supports incognito mode for per-contact random profile generation.
+
+## Prerequisites
+
+- User profile created and chat engine running
+- Network connectivity to SMP relay servers
+- For QR code scanning: camera permission granted
+
+## Step-by-Step Processes
+
+### 1. Create Invitation Link
+
+1. User taps "+" button in `ChatListView` -> `NewChatMenuButton` -> "Add contact".
+2. `NewChatView` is presented.
+3. Calls `apiAddContact(incognito:)`:
+ ```swift
+ func apiAddContact(incognito: Bool) async
+ -> ((CreatedConnLink, PendingContactConnection)?, Alert?)
+ ```
+4. Internally sends `ChatCommand.apiAddContact(userId:incognito:)` to core.
+5. Core creates SMP queues and returns `ChatResponse1.invitation(user, connLinkInv, connection)`.
+6. Returns `(CreatedConnLink, PendingContactConnection)`.
+7. `CreatedConnLink` contains the invitation URI (both full and short link forms).
+8. UI displays:
+ - QR code rendered by `QRCode` view (scannable by peer)
+ - Share button to send link via system share sheet
+ - Copy button for clipboard
+9. A `PendingContactConnection` appears in the chat list while awaiting peer.
+
+### 2. Connect via Link
+
+1. User receives a SimpleX link (pasted, scanned, or opened via URL scheme).
+2. If opened via deep link: `SimpleXApp.onOpenURL` sets `chatModel.appOpenUrl`.
+3. For manual entry: User pastes link in `NewChatView`.
+4. First, `apiConnectPlan(connLink:inProgress:)` is called to validate:
+ ```swift
+ func apiConnectPlan(connLink: String, inProgress: BoxedValue) async
+ -> ((CreatedConnLink, ConnectionPlan)?, Alert?)
+ ```
+5. Returns `ConnectionPlan` indicating whether it is an invitation, contact address, or group link, and whether connection is already established.
+6. If valid, calls `apiConnect(incognito:connLink:)`:
+ ```swift
+ func apiConnect(incognito: Bool, connLink: CreatedConnLink) async
+ -> (ConnReqType, PendingContactConnection)?
+ ```
+7. Core creates the connection and returns one of:
+ - `ChatResponse1.sentConfirmation(user, connection)` -- for invitation links (type: `.invitation`)
+ - `ChatResponse1.sentInvitation(user, connection)` -- for contact address links (type: `.contact`)
+ - `ChatResponse1.contactAlreadyExists(user, contact)` -- duplicate
+8. `PendingContactConnection` appears in chat list while awaiting peer confirmation.
+
+### 3. Prepared Contact/Group Flow (Short Links)
+
+1. For short links with embedded profile data, the app uses a two-phase flow.
+2. `apiPrepareContact(connLink:contactShortLinkData:)` or `apiPrepareGroup(connLink:directLink:groupShortLinkData:)` creates a local prepared chat. `directLink` is `true` for standard group links, `false` for channel relay links.
+3. Returns `ChatData` with the prepared contact/group shown in UI before connecting.
+4. User can switch profiles or set incognito before committing.
+5. `apiConnectPreparedContact(contactId:incognito:msg:)` finalizes the connection.
+6. Returns `ChatResponse1.startedConnectionToContact(user, contact)`.
+
+### 4. Accept Contact Request
+
+1. When a peer connects via the user's SimpleX address, core generates a `ChatEvent.receivedContactRequest`.
+2. `processReceivedMsg` handles the event, adding a `UserContactRequest` to `ChatModel`.
+3. Contact request appears in `ChatListView` as a special `ContactRequestView` row.
+4. User taps "Accept":
+ ```swift
+ func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact?
+ ```
+5. Sends `ChatCommand.apiAcceptContact(incognito:contactReqId:)`.
+6. Core returns `ChatResponse1.acceptingContactRequest(user, contact)`.
+7. Connection handshake proceeds asynchronously.
+8. User can also reject: `apiRejectContactRequest(contactReqId:)` -> `ChatResponse1.contactRequestRejected`.
+
+### 5. Connection Established
+
+1. Both sides complete the SMP handshake asynchronously.
+2. Core sends `ChatEvent.contactConnected(user, contact, userCustomProfile)`.
+3. `processReceivedMsg` updates `ChatModel`:
+ - Contact status transitions from pending to active.
+ - Chat becomes available for messaging.
+4. `NtfManager` may post a notification: "Contact connected".
+5. The `PendingContactConnection` in the chat list is replaced by the full contact chat.
+
+### 6. Create SimpleX Address
+
+1. User navigates to Settings or taps "Create SimpleX address" during onboarding.
+2. Calls `apiCreateUserAddress()`:
+ ```swift
+ func apiCreateUserAddress() async throws -> CreatedConnLink?
+ ```
+3. Core creates a permanent address (unlike one-time invitations).
+4. Address is stored in `ChatModel.shared.userAddress`.
+5. Can be shared publicly; multiple contacts can connect via the same address.
+6. User must accept each incoming contact request individually.
+7. To delete: `apiDeleteUserAddress()` removes the address and associated SMP queues.
+
+### 7a. Relay Link Rejection
+
+1. User scans, pastes, or opens a relay address link (URL path `/r` or `SimplexLinkType.relay`).
+2. In `ContentView.connectViaUrl_()`: early return with alert "Relay address" / "This is a chat relay address, it cannot be used to connect."
+3. In `NewChatView.planAndConnect()`: `.simplexLink(_, .relay, _, _)` pattern triggers the same alert.
+4. The link is NOT processed further. No connection is attempted.
+
+### 7b. Channel Prepared Group Flow
+
+1. When connecting to a channel link (`GroupShortLinkInfo.direct == false`):
+2. `apiPrepareGroup(connLink:directLink:groupShortLinkData:)` is called with `directLink: false`, preparing the channel locally.
+3. `groupShortLinkInfo.groupRelays` (hostnames) stored in `ChatModel.shared.channelRelayHostnames[groupId]`.
+4. Pre-join UI shows channel icon and "Open new channel" (not "Open new group").
+5. `apiConnectPreparedGroup(groupId:incognito:msg:)` returns `(GroupInfo, [RelayConnectionResult])`.
+6. `RelayConnectionResult` contains `relayMember: GroupMember` and optional `relayError: ChatError?` per relay.
+7. Relay members are upserted to `chatModel.groupMembers`; `channelRelayHostnames` entry is cleared.
+
+### 7. Incognito Connection
+
+1. Before connecting, user toggles "Incognito" in the connection UI.
+2. `incognito: true` is passed to `apiAddContact`, `apiConnect`, or `apiAcceptContactRequest`.
+3. Core generates a random display name for this connection only.
+4. The random profile is stored per-connection; the user's real profile is never shared.
+5. Incognito status is shown with a mask icon in the chat.
+6. Can also be toggled for pending connections via `apiSetConnectionIncognito(connId:incognito:)`.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CreatedConnLink` | `SimpleXChat/APITypes.swift` | Contains `connFullLink` (URI) and optional `connShortLink` |
+| `PendingContactConnection` | `SimpleXChat/ChatTypes.swift` | Represents an in-progress connection before contact is established |
+| `ConnectionPlan` | `Shared/Model/AppAPITypes.swift` | Enum describing what a link will do: connect contact, join group, already connected, etc. |
+| `ConnReqType` | `Shared/Views/NewChat/NewChatView.swift` | `.invitation`, `.contact`, or `.groupLink` -- type of connection request |
+| `Contact` | `SimpleXChat/ChatTypes.swift` | Full contact model with profile, connection status, preferences |
+| `UserContactRequest` | `SimpleXChat/ChatTypes.swift` | Incoming contact request awaiting acceptance |
+| `ChatType` | `SimpleXChat/ChatTypes.swift` | `.direct`, `.group`, `.local`, `.contactRequest`, `.contactConnection` |
+| `GroupShortLinkInfo` | `Shared/Model/AppAPITypes.swift` | Contains `direct: Bool`, `groupRelays: [String]`, `publicGroupId: String?`; transient data returned by prepare |
+| `RelayConnectionResult` | `Shared/Model/AppAPITypes.swift` | Contains `relayMember: GroupMember`, `relayError: ChatError?`; per-relay join outcome |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `ChatError.invalidConnReq` | Malformed or expired link | Alert: "Invalid connection link" |
+| `ChatError.unsupportedConnReq` | Link requires newer app version | Alert: "Unsupported connection link" |
+| `ChatError.errorAgent(.SMP(_, .AUTH))` | Link already used or deleted | Alert: "Connection error (AUTH)" |
+| `ChatError.errorAgent(.SMP(_, .BLOCKED(info)))` | Server operator blocked connection | Alert: "Connection blocked" with reason |
+| `ChatError.errorAgent(.SMP(_, .QUOTA))` | Too many undelivered messages | Alert: "Undelivered messages" |
+| `ChatError.errorAgent(.INTERNAL("SEUniqueID"))` | Duplicate connection attempt | Alert: "Already connected?" |
+| `ChatError.errorAgent(.BROKER(_, .TIMEOUT))` | Server timeout | Retryable via `chatApiSendCmdWithRetry` |
+| `ChatError.errorAgent(.BROKER(_, .NETWORK))` | Network failure | Retryable via `chatApiSendCmdWithRetry` |
+| `contactAlreadyExists` | Connecting to existing contact | Alert: "Contact already exists" with contact name |
+| `errorAgent(.SMP(_, .AUTH))` on accept | Sender deleted request | Alert: "Sender may have deleted the connection request" |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/NewChat/NewChatView.swift` | Main connection UI: create link, paste link, QR scan |
+| `Shared/Views/NewChat/NewChatMenuButton.swift` | "+" button menu in chat list |
+| `Shared/Views/NewChat/QRCode.swift` | QR code rendering for invitation links |
+| `Shared/Views/NewChat/AddContactLearnMore.swift` | Help text explaining connection process |
+| `Shared/Views/ChatList/ContactRequestView.swift` | Incoming contact request display |
+| `Shared/Views/ChatList/ContactConnectionView.swift` | Pending connection display |
+| `Shared/Views/ChatList/ContactConnectionInfo.swift` | Connection details sheet |
+| `Shared/Model/SimpleXAPI.swift` | API functions: `apiAddContact`, `apiConnect`, `apiConnectPlan`, `apiAcceptContactRequest`, `apiCreateUserAddress` |
+| `Shared/Model/AppAPITypes.swift` | `ConnectionPlan` enum, `GroupLink` struct |
+| `SimpleXChat/APITypes.swift` | `CreatedConnLink`, `ComposedMessage`, command/response types |
+| `SimpleXChat/ChatTypes.swift` | `Contact`, `PendingContactConnection`, `UserContactRequest` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Contacts capability map
+- `apps/ios/product/flows/messaging.md` -- Messaging after connection is established
diff --git a/apps/ios/product/flows/file-transfer.md b/apps/ios/product/flows/file-transfer.md
new file mode 100644
index 0000000000..0b4b0538cc
--- /dev/null
+++ b/apps/ios/product/flows/file-transfer.md
@@ -0,0 +1,209 @@
+# File Transfer Flow
+
+> **Related spec:** [spec/services/files.md](../../spec/services/files.md)
+
+## Overview
+
+File and media sharing in SimpleX Chat iOS. Small files are sent inline within SMP messages; large files use the XFTP (eXtended File Transfer Protocol) for chunked, encrypted uploads up to 1GB. All files are encrypted end-to-end. Optional local encryption protects downloaded files at rest using AES via `CryptoFile`.
+
+## Prerequisites
+
+- Established contact or group conversation
+- For sending: photo library or file picker access permission
+- For receiving: sufficient device storage
+- XFTP relay servers configured (default servers or custom)
+
+## Size Limits
+
+| Category | Limit | Constant |
+|----------|-------|----------|
+| Inline image (compressed) | 255 KB | `MAX_IMAGE_SIZE` = 261,120 bytes |
+| Auto-receive image | 510 KB | `MAX_IMAGE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
+| Auto-receive voice | 510 KB | `MAX_VOICE_SIZE_AUTO_RCV` = MAX_IMAGE_SIZE * 2 |
+| Auto-receive video | 1,023 KB | `MAX_VIDEO_SIZE_AUTO_RCV` = 1,047,552 bytes |
+| Max file via XFTP | 1 GB | `MAX_FILE_SIZE_XFTP` = 1,073,741,824 bytes |
+| Max file via SMP | ~8 MB | `MAX_FILE_SIZE_SMP` = 8,000,000 bytes |
+| Max voice message length | 5 min | `MAX_VOICE_MESSAGE_LENGTH` = 300s |
+
+## Step-by-Step Processes
+
+### 1. Send Image
+
+1. User taps the attachment button in `ComposeView` and selects an image.
+2. `ComposeImageView` displays the selected image preview.
+3. Image is compressed to fit within `MAX_IMAGE_SIZE` (255KB).
+4. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: compressedImagePath),
+ msgContent: .image(text: captionText, image: base64Thumbnail)
+ )
+ ```
+5. `apiSendMessages(type:id:scope:composedMessages:)` is called.
+6. For images <=255KB: sent inline within the SMP message.
+7. For larger images: XFTP upload is used (see XFTP transfer below).
+8. Recipient auto-receives images up to 510KB (`MAX_IMAGE_SIZE_AUTO_RCV`).
+
+### 2. Send Video
+
+1. User picks a video from the library.
+2. Thumbnail is generated from the first frame.
+3. Video duration is calculated.
+4. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: videoFilePath),
+ msgContent: .video(text: captionText, image: base64Thumbnail, duration: durationSeconds)
+ )
+ ```
+5. `apiSendMessages(...)` is called.
+6. Video files are typically >255KB, so XFTP upload is used.
+7. Recipient auto-receives videos up to 1,023KB (`MAX_VIDEO_SIZE_AUTO_RCV`).
+8. `CIVideoView` displays thumbnail with play button; video downloads on tap if not auto-received.
+
+### 3. Send File
+
+1. User taps the attachment button and selects a document via the system file picker.
+2. `ComposeFileView` shows the file name and size.
+3. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: filePath),
+ msgContent: .file(fileName)
+ )
+ ```
+4. `apiSendMessages(...)` is called.
+5. If file <=255KB: sent inline via SMP.
+6. If file >255KB and <=1GB: uploaded via XFTP.
+7. Files >1GB: rejected (prevented in UI).
+8. `CIFileView` displays file icon, name, and size for the recipient.
+
+### 4. Send Voice Message
+
+1. User taps and holds the microphone button in `ComposeView`.
+2. `AudioRecPlay` records audio to a temporary file.
+3. `ComposeVoiceView` shows recording waveform and duration.
+4. On release (or tapping stop), recording ends.
+5. Duration is checked against `MAX_VOICE_MESSAGE_LENGTH` (5 minutes / 300 seconds).
+6. `ComposedMessage` is built:
+ ```swift
+ ComposedMessage(
+ fileSource: CryptoFile(filePath: voiceFilePath),
+ msgContent: .voice(text: "", duration: durationSeconds)
+ )
+ ```
+7. `apiSendMessages(...)` is called.
+8. Voice messages <=510KB are sent inline.
+9. Recipient auto-receives voice up to 510KB (`MAX_VOICE_SIZE_AUTO_RCV`).
+10. `CIVoiceView` renders waveform with playback controls.
+
+### 5. Receive File
+
+1. Core receives a message with a file reference via SMP.
+2. `ChatEvent.newChatItems` delivers the chat item with file metadata.
+3. Auto-receive logic checks:
+ - File type and size against auto-receive thresholds.
+ - User's auto-receive preferences.
+4. If auto-received or user taps "Download":
+ ```swift
+ func receiveFile(user: any UserLike, fileId: Int64, userApprovedRelays: Bool = false, auto: Bool = false) async
+ ```
+5. Internally calls `receiveFiles(user:fileIds:userApprovedRelays:auto:)`.
+6. Sends `ChatCommand.receiveFile(fileId:userApprovedRelays:encrypted:inline:)`.
+7. `encrypted` is determined by `privacyEncryptLocalFilesGroupDefault`.
+8. `userApprovedRelays` controls whether unknown XFTP relay servers are trusted.
+9. On success: `ChatResponse2.rcvFileAccepted(user, chatItem)` -- file download begins.
+10. On sender cancelled: `ChatResponse2.rcvFileAcceptedSndCancelled(user, rcvFileTransfer)`.
+11. Download progress is tracked and shown in the UI.
+12. Completed files are stored in the app's `Documents/files/` directory.
+
+### 6. XFTP Transfer (Large Files)
+
+**Upload (sender side):**
+1. File is encrypted locally with a random symmetric key.
+2. Encrypted file is split into chunks.
+3. Chunks are uploaded to one or more XFTP relay servers.
+4. A file description (URI with encryption key and chunk locations) is created.
+5. The file description is sent to the recipient via the SMP message.
+
+**Download (recipient side):**
+1. Recipient receives the file description via SMP.
+2. Chunks are downloaded from XFTP relay servers.
+3. Chunks are reassembled and decrypted locally.
+4. File is available at the local path.
+
+**Standalone file operations** (used for database migration):
+- `uploadStandaloneFile(user:file:ctrl:)` -- upload without a chat message
+- `downloadStandaloneFile(user:url:file:ctrl:)` -- download from a standalone URL
+- `standaloneFileInfo(url:ctrl:)` -- get metadata for a standalone file URL
+
+### 7. Local File Encryption
+
+1. If `privacyEncryptLocalFilesGroupDefault` is enabled in privacy settings:
+ - Downloaded files are encrypted at rest using AES via `CryptoFile`.
+ - `CryptoFile` wraps a file path with encryption metadata.
+2. Encryption key is derived and stored securely.
+3. Files are decrypted on-the-fly when accessed for viewing/playback.
+4. This protects files even if the device storage is accessed externally.
+
+### 8. Unknown Relay Server Approval
+
+1. When receiving a file, XFTP relay servers are checked against known/approved servers.
+2. If unknown servers are detected: `ChatError.error(.fileNotApproved(fileId, unknownServers))`.
+3. If not auto-receiving, user is shown an alert:
+ - "Unknown servers! Without Tor or VPN, your IP address will be visible to these XFTP relays: [server list]."
+ - Option to "Download" (approve) or cancel.
+4. On approval: `receiveFiles(user:fileIds:userApprovedRelays: true)` retries with approval.
+5. If `privacyAskToApproveRelaysGroupDefault` is disabled, relays are auto-approved.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `CryptoFile` | `SimpleXChat/CryptoFile.swift` | File path with optional encryption key and nonce for local AES encryption |
+| `MsgContent.image` | `SimpleXChat/ChatTypes.swift` | `.image(text: String, image: String)` -- text caption + base64 thumbnail |
+| `MsgContent.video` | `SimpleXChat/ChatTypes.swift` | `.video(text: String, image: String, duration: Int)` -- caption + thumbnail + duration |
+| `MsgContent.voice` | `SimpleXChat/ChatTypes.swift` | `.voice(text: String, duration: Int)` -- empty text + duration in seconds |
+| `MsgContent.file` | `SimpleXChat/ChatTypes.swift` | `.file(String)` -- file name |
+| `ComposedMessage` | `SimpleXChat/APITypes.swift` | Outgoing message with fileSource, quotedItemId, msgContent, mentions |
+| `FileTransferMeta` | `SimpleXChat/ChatTypes.swift` | Metadata for an ongoing file transfer |
+| `RcvFileTransfer` | `SimpleXChat/ChatTypes.swift` | State of a file being received |
+| `MigrationFileLinkData` | Used for standalone file transfers during database migration |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `fileNotApproved(fileId, unknownServers)` | Unknown XFTP relay servers | Alert with option to approve and retry |
+| `fileCancelled` | File transfer was cancelled | Silently ignored in `receiveFiles` |
+| `fileAlreadyReceiving` | Duplicate receive request | Silently ignored |
+| `rcvFileAcceptedSndCancelled` | Sender cancelled after acceptance | Alert: "Sender cancelled file transfer" |
+| File too large | Exceeds 1GB XFTP limit | Prevented in UI picker |
+| Network errors | XFTP server unreachable | Standard retry mechanism |
+| Storage full | Insufficient device storage | System-level error |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `SimpleXChat/FileUtils.swift` | File size constants, path utilities, database file management |
+| `SimpleXChat/CryptoFile.swift` | Local file encryption/decryption with AES |
+| `SimpleXChat/ImageUtils.swift` | Image compression and thumbnail generation |
+| `Shared/Views/Chat/ComposeMessage/ComposeView.swift` | File/media attachment selection and composition |
+| `Shared/Views/Chat/ComposeMessage/ComposeImageView.swift` | Image preview in compose area |
+| `Shared/Views/Chat/ComposeMessage/ComposeFileView.swift` | File preview in compose area |
+| `Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift` | Voice recording UI with waveform |
+| `Shared/Views/Chat/ChatItem/CIFileView.swift` | File message display: icon, name, size, download action |
+| `Shared/Views/Chat/ChatItem/CIImageView.swift` | Image message display: thumbnail, full-screen tap |
+| `Shared/Views/Chat/ChatItem/CIVideoView.swift` | Video message display: thumbnail, play button, inline playback |
+| `Shared/Views/Chat/ChatItem/CIVoiceView.swift` | Voice message display: waveform, playback controls |
+| `Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift` | Voice message inside a framed (quoted/forwarded) context |
+| `Shared/Views/Chat/ChatItem/FullScreenMediaView.swift` | Full-screen image/video viewer |
+| `Shared/Model/SimpleXAPI.swift` | `apiSendMessages`, `receiveFile`, `receiveFiles`, `uploadStandaloneFile`, `downloadStandaloneFile` |
+| `Shared/Model/AudioRecPlay.swift` | Audio recording and playback engine for voice messages |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Messaging capability (file sharing)
+- `apps/ios/product/flows/messaging.md` -- File transfer is part of the message send flow
+- `apps/ios/product/views/chat.md` -- Chat view file/media display
diff --git a/apps/ios/product/flows/group-lifecycle.md b/apps/ios/product/flows/group-lifecycle.md
new file mode 100644
index 0000000000..e102fa982a
--- /dev/null
+++ b/apps/ios/product/flows/group-lifecycle.md
@@ -0,0 +1,228 @@
+# Group Lifecycle Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/database.md](../../spec/database.md)
+
+## Overview
+
+Complete group management in SimpleX Chat iOS: creating groups, inviting members, joining via links, managing roles and admission, and group deletion. Groups use the same E2E encryption as direct messages -- each member pair has independent encrypted channels. Group metadata (name, image, preferences) is distributed via the group protocol.
+
+## Prerequisites
+
+- User profile created and chat engine running
+- At least one established contact (to invite to a group)
+- For joining via link: a valid group link or invitation
+
+## Step-by-Step Processes
+
+### 1. Create Group
+
+1. User taps "+" in `ChatListView` -> `NewChatMenuButton` -> "Create group".
+2. `AddGroupView` is presented for entering group name, optional image, and description.
+3. User fills in `GroupProfile(displayName:fullName:image:description:)` and taps "Create".
+4. Calls `apiNewGroup(incognito:groupProfile:)`:
+ ```swift
+ func apiNewGroup(incognito: Bool, groupProfile: GroupProfile) throws -> GroupInfo
+ ```
+5. Sends `ChatCommand.apiNewGroup(userId:incognito:groupProfile:)` to core (synchronous).
+6. Core returns `ChatResponse2.groupCreated(user, groupInfo)`.
+7. `GroupInfo` contains the new group's ID, profile, and the creator as owner.
+8. User is navigated to `AddGroupMembersView` to optionally invite contacts.
+9. User can also create a group link at this stage.
+
+### 1a. Create Public Group (Channel)
+
+1. Alternative to standard group creation for relay-backed channels.
+2. Calls `apiNewPublicGroup(incognito:relayIds:groupProfile:)`:
+ ```swift
+ func apiNewPublicGroup(incognito: Bool, relayIds: [Int64], groupProfile: GroupProfile) async throws -> (GroupInfo, GroupLink, [GroupRelay])
+ ```
+3. Sends `ChatCommand.apiNewPublicGroup(userId:incognito:relayIds:groupProfile:)` to core.
+4. Core returns `ChatResponse2.publicGroupCreated(user, groupInfo, groupLink, groupRelays)`.
+5. The resulting `GroupInfo` has `useRelays == true` and includes a group link.
+6. Channel relay members (with role `.relay`) are managed by the core.
+
+### 2. Invite Members
+
+1. From `GroupChatInfoView`, user taps "Add members" -> `AddGroupMembersView`.
+2. `filterMembersToAdd` filters contacts already in the group.
+3. User selects contacts and assigns roles (default: `.member`).
+4. For each selected contact, calls `apiAddMember(groupId:contactId:memberRole:)`:
+ ```swift
+ func apiAddMember(_ groupId: Int64, _ contactId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember
+ ```
+5. Core sends group invitation to the contact and returns `ChatResponse2.sentGroupInvitation(user, _, _, member)`.
+6. The invited contact receives a `CIGroupInvitationView` in their chat.
+7. Invited member's status is `.invited` until they accept.
+
+### 3. Join via Link
+
+1. User receives a group link (scanned or pasted).
+2. `apiConnectPlan` validates the link and identifies it as a group link.
+3. For prepared groups (short links): `apiPrepareGroup(connLink:directLink:groupShortLinkData:)` shows group info before joining. `directLink` is `true` for standard group links, `false` for channel relay links.
+4. `apiConnectPreparedGroup(groupId:incognito:msg:)` or `apiConnect(incognito:connLink:)` initiates joining.
+5. Core processes the join request. Depending on group admission settings:
+ - **Auto-join**: Member is added immediately.
+ - **Approval required**: Member enters pending admission queue.
+6. `apiJoinGroup(groupId:)` is called for invitation-based joins:
+ ```swift
+ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult?
+ ```
+7. Returns one of:
+ - `.joined(groupInfo:)` -- successfully joined
+ - `.invitationRemoved` -- invitation was revoked (SMP AUTH error)
+ - `.groupNotFound` -- group no longer exists
+
+### 4. Member Admission
+
+1. Group has admission settings configured via `MemberAdmissionView`.
+2. When a new member joins a group requiring approval, admins see pending members.
+3. Admin reviews pending member in the member list.
+4. To accept: `apiAcceptMember(groupId:groupMemberId:memberRole:)`:
+ ```swift
+ func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember)
+ ```
+5. Core returns `ChatResponse2.memberAccepted(user, groupInfo, member)`.
+6. To reject: remove the pending member (same as member removal).
+7. Member support chat (`MemberSupportView`, `MemberSupportChatToolbar`) allows admins to communicate with pending members.
+
+### 5. Change Member Roles
+
+1. Admin/owner navigates to member info in `GroupChatInfoView`.
+2. Selects new role for the member.
+3. Calls `apiMembersRole(groupId:memberIds:memberRole:)`:
+ ```swift
+ func apiMembersRole(_ groupId: Int64, _ memberIds: [Int64], _ memberRole: GroupMemberRole) async throws -> [GroupMember]
+ ```
+4. Core returns `ChatResponse2.membersRoleUser(user, _, members, _)`.
+5. Available roles (in hierarchy order):
+ - `.owner` -- full control, can delete group
+ - `.admin` -- can manage members, change roles (below admin)
+ - `.moderator` -- can delete messages, moderate content
+ - `.member` -- standard participant, can send messages
+ - `.observer` -- read-only access
+6. Role changes are broadcast to all group members as group events.
+
+### 6. Remove Member
+
+1. Admin/owner navigates to member info -> taps "Remove".
+2. Calls `apiRemoveMembers(groupId:memberIds:withMessages:)`:
+ ```swift
+ func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool) async throws -> (GroupInfo, [GroupMember])
+ ```
+3. `withMessages: true` also deletes all messages from that member.
+4. Core returns `ChatResponse2.userDeletedMembers(user, updatedGroupInfo, members, withMessages)`.
+5. Removed member receives notification and loses access.
+
+### 7. Block Member for All
+
+1. Admin can block a member's messages from being visible to all group members.
+2. Calls `apiBlockMembersForAll(groupId:memberIds:blocked:)`:
+ ```swift
+ func apiBlockMembersForAll(_ groupId: Int64, _ memberIds: [Int64], _ blocked: Bool) async throws -> [GroupMember]
+ ```
+3. Core returns `ChatResponse2.membersBlockedForAllUser(user, _, members, _)`.
+
+### 8. Leave Group
+
+1. User navigates to `GroupChatInfoView` -> taps "Leave group".
+2. Confirmation dialog is presented.
+3. Calls `leaveGroup(groupId:)` which wraps `apiLeaveGroup(groupId:)`:
+ ```swift
+ func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo
+ ```
+4. Core returns `ChatResponse2.leftMemberUser(user, groupInfo)`.
+5. `ChatModel.shared.updateGroup(groupInfo)` updates the UI.
+6. User retains local chat history but can no longer send/receive.
+
+### 9. Delete Group
+
+1. Owner navigates to `GroupChatInfoView` -> taps "Delete group".
+2. Calls `apiDeleteChat(type: .group, id: groupId)`:
+ ```swift
+ func apiDeleteChat(type: ChatType, id: Int64, chatDeleteMode: ChatDeleteMode = .full(notify: true)) async throws
+ ```
+3. Core notifies all members and removes the group.
+4. Chat is removed from `ChatModel.shared.chats`.
+
+### 10. Group Link Management
+
+**Create group link:**
+1. From `GroupLinkView` (accessible via `GroupChatInfoView`).
+2. Calls `apiCreateGroupLink(groupId:memberRole:)`:
+ ```swift
+ func apiCreateGroupLink(_ groupId: Int64, memberRole: GroupMemberRole = .member) async throws -> GroupLink?
+ ```
+3. Returns `GroupLink` containing the link URI and member role.
+4. Optional: `apiAddGroupShortLink(groupId:)` generates an additional short link.
+
+**Update link role:**
+- `apiGroupLinkMemberRole(groupId:memberRole:)` changes the default role for new joiners.
+
+**Delete group link:**
+- `apiDeleteGroupLink(groupId:)` invalidates the link.
+
+**Get existing link:**
+- `apiGetGroupLink(groupId:)` retrieves the current link (returns `nil` if none exists).
+
+### 11. Group Preferences
+
+1. `GroupPreferencesView` allows configuring per-feature preferences.
+2. Features controlled include:
+ - Timed/disappearing messages
+ - Message reactions
+ - Voice messages
+ - File sharing
+ - Direct messages between members
+ - Full message deletion
+ - Message history visibility for new members
+3. Changes are saved via `apiUpdateGroup(groupId:groupProfile:)` with updated preferences.
+4. `GroupWelcomeView` manages the welcome message shown to new joiners.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `GroupInfo` | `SimpleXChat/ChatTypes.swift` | Full group model: ID, profile, membership, preferences, business chat info |
+| `GroupProfile` | `SimpleXChat/ChatTypes.swift` | Name, full name, image, description, preferences |
+| `GroupMember` | `SimpleXChat/ChatTypes.swift` | Member model: role, status, profile, connection info |
+| `GroupMemberRole` | `SimpleXChat/ChatTypes.swift` | `.owner`, `.admin`, `.moderator`, `.member`, `.observer`, `.relay` |
+| `GroupMemberStatus` | `SimpleXChat/ChatTypes.swift` | Member lifecycle: `.invited`, `.accepted`, `.connected`, `.complete`, etc. |
+| `GroupLink` | `Shared/Model/AppAPITypes.swift` | Group link with URI, member role, and short link data |
+| `BusinessChatInfo` | `SimpleXChat/ChatTypes.swift` | Business chat metadata for commercial group chats |
+| `JoinGroupResult` | `Shared/Model/SimpleXAPI.swift` | `.joined(groupInfo)`, `.invitationRemoved`, `.groupNotFound` |
+| `GMember` | `Shared/Views/Chat/Group/` | View-layer wrapper around `GroupMember` for list display |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `errorStore(.groupNotFound)` | Group deleted or not accessible | `JoinGroupResult.groupNotFound` |
+| `errorAgent(.SMP(_, .AUTH))` | Invitation revoked | `JoinGroupResult.invitationRemoved` |
+| `errorStore(.groupLinkNotFound)` | No group link exists | `apiGetGroupLink` returns `nil` |
+| `duplicateGroupLink` | Link already exists for group | Show alert |
+| `errorAgent(.NOTICE(server, preset, expires))` | Server notice during link creation | `showClientNotice` alert |
+| Network errors | SMP/XFTP server unreachable | Retryable via `chatApiSendCmdWithRetry` |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/NewChat/AddGroupView.swift` | Group creation UI |
+| `Shared/Views/Chat/Group/AddGroupMembersView.swift` | Member invitation UI |
+| `Shared/Views/Chat/Group/GroupLinkView.swift` | Group link management UI |
+| `Shared/Views/Chat/Group/GroupProfileView.swift` | Group profile editing |
+| `Shared/Views/Chat/Group/GroupPreferencesView.swift` | Feature preferences UI |
+| `Shared/Views/Chat/Group/GroupWelcomeView.swift` | Welcome message editing |
+| `Shared/Views/Chat/Group/MemberAdmissionView.swift` | Admission settings UI |
+| `Shared/Views/Chat/Group/MemberSupportView.swift` | Admin-to-pending-member chat |
+| `Shared/Views/Chat/Group/MemberSupportChatToolbar.swift` | Support chat accept/reject toolbar |
+| `Shared/Views/Chat/Group/SecondaryChatView.swift` | Secondary chat view for member support |
+| `Shared/Model/SimpleXAPI.swift` | All group API functions |
+| `Shared/Model/AppAPITypes.swift` | `GroupLink`, `ConnectionPlan` |
+| `SimpleXChat/ChatTypes.swift` | `GroupInfo`, `GroupProfile`, `GroupMember`, `GroupMemberRole` |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: Groups capability map
+- `apps/ios/product/flows/connection.md` -- Connection flow (group links use the same connect mechanism)
+- `apps/ios/product/flows/messaging.md` -- Messaging within groups
diff --git a/apps/ios/product/flows/messaging.md b/apps/ios/product/flows/messaging.md
new file mode 100644
index 0000000000..d37fefdd7d
--- /dev/null
+++ b/apps/ios/product/flows/messaging.md
@@ -0,0 +1,178 @@
+# Messaging Flow
+
+> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/client/chat-view.md](../../spec/client/chat-view.md) | [spec/client/compose.md](../../spec/client/compose.md)
+
+## Overview
+
+Complete message lifecycle in SimpleX Chat iOS: composing, sending, receiving, editing, deleting, reacting to, replying to, and forwarding messages. All messages are end-to-end encrypted via the SMP protocol. The Haskell core handles encryption, routing, and persistence; the Swift UI layer drives composition and display.
+
+## Prerequisites
+
+- User profile created and chat engine running (`startChat()` completed)
+- At least one established contact or group conversation
+- `ChatModel.shared` populated with chat list data
+
+## Step-by-Step Processes
+
+### 1. Send Text Message
+
+1. User navigates to a conversation (direct or group) via `ChatListView` -> `ChatView`.
+2. User types text into `ComposeView`'s `SendMessageView` text editor.
+3. Link previews are detected and fetched asynchronously (`ComposeLinkView`).
+4. User taps the send button.
+5. `ComposeView` builds a `ComposedMessage`:
+ ```swift
+ ComposedMessage(
+ fileSource: nil,
+ quotedItemId: nil,
+ msgContent: .text("Hello"),
+ mentions: [:]
+ )
+ ```
+6. Calls `apiSendMessages(type:id:scope:live:ttl:composedMessages:sendAsGroup:)` (where `sendAsGroup` defaults to `false`; set to `true` when a channel owner sends as the channel identity).
+7. Internally dispatches `ChatCommand.apiSendMessages(...)` to the Haskell core.
+8. Core encrypts, queues via SMP, and returns `ChatResponse1.newChatItems(user, aChatItems)`.
+9. `processSendMessageCmd` extracts `[ChatItem]` from response.
+10. For direct chats, a background task tracks delivery via `chatModel.messageDelivery`.
+11. `ChatModel` updates, UI refreshes to show the new message.
+
+### 2. Send Media (Image/Video/File)
+
+1. User taps the attachment button in `ComposeView`.
+2. **Image**: Picked via `PhotosPicker` or camera. Compressed to <=255KB. Sent inline with `.image(text, base64Image)` content type.
+3. **Video**: Picked from library. Thumbnail generated. Video file sent via XFTP for files >255KB. Content type: `.video(text, thumbnail, duration)`.
+4. **File**: Picked via document picker. If <=255KB, sent inline. If >255KB, uploaded via XFTP (up to 1GB). Content type: `.file(text)`.
+5. `ComposedMessage` includes `fileSource: CryptoFile(filePath:)`.
+6. `apiSendMessages(...)` called with the composed message array.
+7. Core handles XFTP upload for large files (chunked, encrypted upload to XFTP servers).
+8. Recipient receives file reference and can download.
+
+### 3. Receive Message
+
+1. `ChatReceiver.shared` runs `receiveMsgLoop()` continuously calling `chatRecvMsg()`.
+2. Core delivers events via `APIResult`.
+3. On `ChatEvent.newChatItems(user, chatItems)`:
+ - `processReceivedMsg` is called.
+ - For the active user, `ChatModel` is updated with new items.
+ - If the chat is currently open, `ItemsModel` appends to `reversedChatItems`.
+ - `NtfManager` posts a local notification if the app is in the background.
+4. Small files/images attached to incoming messages are auto-received if within size thresholds.
+
+### 4. Edit Message
+
+1. User long-presses a sent message -> selects "Edit" from context menu.
+2. `ComposeView` enters edit mode with the original text pre-filled.
+3. User modifies text and taps send.
+4. Calls `apiUpdateChatItem(type:id:scope:itemId:updatedMessage:live:)`.
+5. Dispatches `ChatCommand.apiUpdateChatItem(...)`.
+6. Core returns `ChatResponse1.chatItemUpdated(user, aChatItem)` or `.chatItemNotChanged(user, aChatItem)`.
+7. `ChatModel` updates the item in place. Edit timestamp is shown in the UI.
+
+### 5. Delete Message
+
+1. User long-presses a message -> selects "Delete".
+2. Presented with options:
+ - **Delete for me** (`CIDeleteMode.cidmInternal`) -- removes locally only.
+ - **Delete for everyone** (`CIDeleteMode.cidmBroadcast`) -- sends deletion to recipient(s).
+3. Calls `apiDeleteChatItems(type:id:scope:itemIds:mode:)`.
+4. Dispatches `ChatCommand.apiDeleteChatItem(type:id:scope:itemIds:mode:)`.
+5. Core returns `ChatResponse1.chatItemsDeleted(user, items, _)` containing `[ChatItemDeletion]`.
+6. For group messages from other members, admin/owner can call `apiDeleteMemberChatItems(groupId:itemIds:)`.
+7. `ChatModel` removes or replaces items with "deleted" placeholders.
+
+### 6. React to Message
+
+1. User long-presses a message -> selects "React" -> picks an emoji.
+2. Calls `apiChatItemReaction(type:id:scope:itemId:add:reaction:)`.
+3. `reaction` is `MsgReaction` (e.g., `.emoji(.heart)`).
+4. `add: true` to add, `add: false` to remove.
+5. Core returns `ChatResponse1.chatItemReaction(user, _, reaction)`.
+6. The reaction is displayed below the message bubble.
+
+### 7. Reply to Message
+
+1. User long-presses a message -> selects "Reply".
+2. `ComposeView` enters reply mode, showing quoted message in `ContextItemView`.
+3. User types reply text and taps send.
+4. `ComposedMessage` is created with `quotedItemId: originalItem.id`.
+5. `apiSendMessages(...)` sends with the quote reference.
+6. Recipient sees the reply with the quoted context rendered above.
+
+### 8. Forward Message
+
+1. User long-presses a message -> selects "Forward".
+2. `ChatItemForwardingView` is presented for destination chat selection.
+3. `apiPlanForwardChatItems(type:id:scope:itemIds:)` validates what can be forwarded, returns `([Int64], ForwardConfirmation?)`.
+4. User confirms and selects destination chat.
+5. Calls `apiForwardChatItems(toChatType:toChatId:toScope:fromChatType:fromChatId:fromScope:itemIds:ttl:sendAsGroup:)` (where `sendAsGroup` defaults to `false`).
+6. Core returns `ChatResponse1.newChatItems(...)` with the forwarded items in the destination chat.
+
+### 9. Voice Message
+
+1. User taps and holds the microphone button in `ComposeView`.
+2. `AudioRecPlay` starts recording to a temporary file.
+3. On release, recording stops. Duration is calculated (max 5 minutes / 300 seconds).
+4. `ComposedMessage` created with:
+ - `fileSource: CryptoFile` pointing to the audio file
+ - `msgContent: .voice(text: "", duration: seconds)`
+5. `apiSendMessages(...)` sends the voice message.
+6. Voice messages <=510KB sent inline; larger via XFTP.
+7. Recipient sees `CIVoiceView` with waveform and playback controls.
+
+### 10. Delivery Tracking
+
+1. On send, message status starts as `CIStatus.sndNew`.
+2. After SMP delivery: `CIStatus.sndSent(sndProgress)`.
+3. When delivered to recipient's agent: status updates to delivered.
+4. If delivery receipts are enabled by both parties, read status is reported.
+5. Failed delivery results in `CIStatus.sndError*` or `CIStatus.sndWarning*`.
+6. Status is displayed via `CIMetaView` (checkmarks/indicators).
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `ComposedMessage` | `SimpleXChat/APITypes.swift` | Outgoing message: fileSource, quotedItemId, msgContent, mentions |
+| `MsgContent` | `SimpleXChat/ChatTypes.swift` | Enum: `.text`, `.link`, `.image`, `.video`, `.voice`, `.file` |
+| `CIContent` | `SimpleXChat/ChatTypes.swift` | Chat item content wrapper with sent/received variants |
+| `CIStatus` | `SimpleXChat/ChatTypes.swift` | Delivery status: sndNew, sndSent, sndError, rcvNew, rcvRead |
+| `CIDirection` | `SimpleXChat/ChatTypes.swift` | `.directSnd`, `.directRcv`, `.groupSnd`, `.groupRcv(groupMember)`, `.channelRcv` |
+| `ChatItem` | `SimpleXChat/ChatTypes.swift` | Full message model: content, meta, status, direction, quotedItem |
+| `ChatItemDeletion` | `SimpleXChat/ChatTypes.swift` | Deleted item info with old/new item pairs |
+| `CIDeleteMode` | `SimpleXChat/ChatTypes.swift` | `.cidmInternal` (local) or `.cidmBroadcast` (for everyone) |
+| `MsgReaction` | `SimpleXChat/ChatTypes.swift` | Reaction type (emoji-based) |
+| `UpdatedMessage` | `SimpleXChat/APITypes.swift` | Edited message content for update API |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `ChatError.errorAgent(.SMP(_, .AUTH))` | Recipient queue issue | Show "Connection error (AUTH)" alert |
+| `ChatError.errorAgent(.BROKER(_, .TIMEOUT))` | Server timeout | Retryable: show retry dialog via `chatApiSendCmdWithRetry` |
+| `ChatError.errorAgent(.BROKER(_, .NETWORK))` | Network failure | Retryable: show retry dialog |
+| Send message error | Core processing failure | `sendMessageErrorAlert` shown to user |
+| `chatItemNotChanged` | Edit with identical content | No error, item returned unchanged |
+| File too large (>1GB) | XFTP limit exceeded | Prevented in UI file picker |
+| `fileNotApproved` | Unknown XFTP relay servers | Show "Unknown servers!" alert with approve option |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/Views/Chat/ComposeMessage/ComposeView.swift` | Message composition UI and send logic |
+| `Shared/Views/Chat/ComposeMessage/SendMessageView.swift` | Text input and send button |
+| `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` | Reply/edit context display |
+| `Shared/Views/Chat/ChatItemView.swift` | Per-message rendering dispatcher |
+| `Shared/Views/Chat/ChatItem/MsgContentView.swift` | Text message content with markdown |
+| `Shared/Views/Chat/ChatItem/CIMetaView.swift` | Delivery status indicators |
+| `Shared/Views/Chat/ChatItemForwardingView.swift` | Forward destination picker |
+| `Shared/Views/Chat/ChatItemInfoView.swift` | Message info (delivery details, timestamps) |
+| `Shared/Model/SimpleXAPI.swift` | API functions: `apiSendMessages`, `apiUpdateChatItem`, `apiDeleteChatItems`, `apiChatItemReaction`, `apiForwardChatItems` |
+| `SimpleXChat/APITypes.swift` | `ComposedMessage`, `ChatCommand` enum, response types |
+| `SimpleXChat/ChatTypes.swift` | `MsgContent`, `CIContent`, `CIStatus`, `CIDirection`, `ChatItem` |
+| `Shared/Model/AudioRecPlay.swift` | Voice message recording/playback engine |
+
+## Related Specifications
+
+- `apps/ios/product/views/chat.md` -- Chat view UI specification
+- `apps/ios/product/README.md` -- Product overview and capability map
diff --git a/apps/ios/product/flows/onboarding.md b/apps/ios/product/flows/onboarding.md
new file mode 100644
index 0000000000..5e2e04d42a
--- /dev/null
+++ b/apps/ios/product/flows/onboarding.md
@@ -0,0 +1,239 @@
+# Onboarding Flow
+
+> **Related spec:** [spec/architecture.md](../../spec/architecture.md) | [spec/database.md](../../spec/database.md)
+
+## Overview
+
+First-time setup and migration flows for SimpleX Chat iOS. Covers app initialization, profile creation, server operator selection, notification configuration, and database import/export for device migration. The app uses a Haskell runtime for its core chat engine, with SQLite databases shared between the main app and the Notification Service Extension (NSE).
+
+## Prerequisites
+
+- Fresh install of SimpleX Chat from the App Store, or
+- Existing install with database archive for import/migration
+- iOS 15+ with App Group entitlement configured
+
+## Step-by-Step Processes
+
+### 1. App Initialization Sequence
+
+On every app launch, `SimpleXApp.init()` executes the following in order:
+
+```
+1. haskell_init() -- Start Haskell runtime system (GHC RTS)
+2. UserDefaults.standard.register(defaults:) -- Set default preferences (appDefaults)
+3. setGroupDefaults() -- Configure app group shared defaults
+4. registerGroupDefaults() -- Register group container defaults
+5. setDbContainer() -- Configure database paths in app group container
+6. BGManager.shared.register() -- Register background task handlers
+7. NtfManager.shared.registerCategories() -- Register notification action categories
+```
+
+Then in `ContentView.onAppear`:
+- If no migration is in progress and authentication is set up, `initChatAndMigrate()` is called.
+- This triggers `chatMigrateInit()` to initialize/migrate databases.
+- Then `startChat()` is called to start the chat engine.
+
+### 2. Fresh Install -- Onboarding Steps
+
+Onboarding is managed by `OnboardingStage` enum and `OnboardingView`:
+
+**Step 1: SimpleX Info** (`step1_SimpleXInfo`)
+1. `SimpleXInfo` view is presented.
+2. Explains SimpleX's architecture: no user identifiers, E2E encryption, decentralized servers.
+3. User taps "Create your profile" to proceed.
+
+**Step 2: Create Profile** (`step2_CreateProfile` -- now inline in step 1)
+1. `CreateFirstProfile` view (embedded in the onboarding flow).
+2. User enters display name (required). Full name is set to empty string.
+3. Display name is validated via `mkValidName()` and `canCreateProfile()`.
+4. On "Create":
+ ```swift
+ AppChatState.shared.set(.active)
+ m.currentUser = try apiCreateActiveUser(profile)
+ try startChat()
+ ```
+5. `apiCreateActiveUser(Profile(displayName:fullName:shortDescr:))` creates the user in the Haskell core.
+6. `startChat()` initializes the chat engine.
+7. Onboarding advances to `step3_ChooseServerOperators`.
+
+**Step 3: Choose Server Operators** (`step3_ChooseServerOperators`)
+1. `OnboardingConditionsView` is presented (simplified conditions acceptance).
+2. User reviews and accepts server operator conditions.
+3. This configures which SMP/XFTP server operators to use.
+4. Advances to `step4_SetNotificationsMode`.
+
+**Step 4: Set Notifications** (`step4_SetNotificationsMode`)
+1. `SetNotificationsMode` view is presented.
+2. Three options:
+ - **Instant**: Requires Apple Push Notification service. Registers device token via `apiRegisterToken(token:notificationMode:)`.
+ - **Periodic**: Uses iOS background app refresh. No push token needed.
+ - **Off**: No notifications.
+3. For instant mode: `apiRegisterToken` sends `ChatCommand.apiRegisterToken(token:notificationMode:)` and receives `ChatResponse2.ntfTokenStatus(status)`.
+4. On completion: `onboardingStageDefault.set(.onboardingComplete)`.
+
+**Onboarding Complete** (`onboardingComplete`)
+1. `ChatListView` is shown.
+2. Empty state displays "Add contact" prompt via `ChatHelp`.
+3. If delivery receipts haven't been configured: `chatModel.setDeliveryReceipts = true` triggers a prompt.
+
+### 3. startChat() -- Chat Engine Startup
+
+Called after profile creation or on subsequent app launches:
+
+```swift
+func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws {
+ 1. setNetworkConfig(getNetCfg()) -- Apply network configuration
+ 2. apiCheckChatRunning() -- Check if already running
+ 3. listUsers() -- Load all user profiles
+ 4. getUserChatData() -- Load chats, tags, address, TTL
+ 5. NtfManager.shared.setNtfBadgeCount(...) -- Set badge count
+ 6. refreshCallInvitations() -- Check pending call invitations
+ 7. apiGetNtfToken() -- Get notification token status
+ 8. apiStartChat() -- Start the Haskell chat engine
+ 9. registerToken(token:) -- Register push token if available
+ 10. ChatReceiver.shared.start() -- Start message receive loop
+}
+```
+
+### 4. Database Setup
+
+**Location:**
+- App group container (shared with NSE): determined by `dbContainerGroupDefault`
+- Path prefix: `simplex_v1` (`DB_FILE_PREFIX`)
+- Chat database: `simplex_v1_chat.db` (messages, contacts, groups, settings)
+- Agent database: `simplex_v1_agent.db` (SMP connections, encryption keys, queues)
+
+**Initialization:**
+- `chatMigrateInit(useKey:confirmMigrations:backgroundMode:)` in `SimpleXChat/API.swift`.
+- Creates databases if they do not exist.
+- Runs pending migrations with confirmation mode.
+- Handles database encryption:
+ - If keychain storage enabled: generates random DB key on first run (`randomDatabasePassword()`).
+ - Stores key in keychain via `kcDatabasePassword`.
+ - `initialRandomDBPassphraseGroupDefault` tracks whether using auto-generated key.
+
+**Encryption:**
+- Optional database encryption passphrase via `DatabaseEncryptionView`.
+- `apiStorageEncryption(currentKey:newKey:)` changes encryption key.
+- `testStorageEncryption(key:)` validates a key against the database.
+
+### 5. Database Export (Source Device)
+
+1. User navigates to Settings -> Database -> "Export database".
+2. Chat must be stopped first for data consistency.
+3. Calls `apiExportArchive(config: ArchiveConfig)`:
+ ```swift
+ func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
+ ```
+4. Core creates a ZIP archive containing both databases and file attachments.
+5. Returns any non-fatal `[ArchiveError]` (e.g., file access issues).
+6. User transfers the archive to the new device via AirDrop, file share, etc.
+
+### 6. Database Import (Destination Device)
+
+1. On new device: during onboarding or Settings -> Database -> "Import database".
+2. User selects the archive file.
+3. Calls `apiImportArchive(config: ArchiveConfig)`:
+ ```swift
+ func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
+ ```
+4. Core extracts the archive, replacing local databases.
+5. Returns any non-fatal `[ArchiveError]`.
+6. Chat engine is restarted with the imported data.
+7. All contacts, groups, messages, and settings are restored.
+
+### 7. In-App Device Migration
+
+An alternative to manual export/import using direct device-to-device transfer.
+
+**Source device** (`MigrateFromDevice` view):
+1. User navigates to Settings -> Database -> "Migrate to another device".
+2. App creates a temporary database and uploads archive via XFTP standalone file.
+3. Generates a migration link containing the file URL and encryption key.
+4. Displays QR code / share link for the destination device.
+
+**Destination device** (`MigrateToDevice` view):
+1. On new device: onboarding detects migration state or user selects "Migrate".
+2. Scans/pastes the migration link.
+3. `downloadStandaloneFile(user:url:file:ctrl:)` downloads the archive from XFTP.
+4. `standaloneFileInfo(url:ctrl:)` validates the file metadata.
+5. Archive is imported, databases are restored.
+6. `chatInitTemporaryDatabase(url:key:confirmation:)` may be used for temporary DB operations during migration.
+7. Chat engine starts with the migrated data.
+
+If migration is interrupted:
+- `chatModel.migrationState` preserves state across app restarts.
+- On next launch, `ContentView.onAppear` detects pending migration and resumes.
+
+### 8. Additional Profile Creation (Multi-Account)
+
+1. From `UserPicker` (profile switcher) -> "Add profile".
+2. `CreateProfile` view is presented (distinct from `CreateFirstProfile`).
+3. User enters display name and optional bio (max 160 bytes JSON-encoded, `MAX_BIO_LENGTH_BYTES`).
+4. `apiCreateActiveUser(profile)` creates additional user.
+5. `listUsers()` and `getUserChatData()` refresh the model.
+6. No onboarding steps -- goes directly to chat list.
+
+## Data Structures
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `OnboardingStage` | `Shared/Views/Onboarding/OnboardingView.swift` | Enum: `step1_SimpleXInfo`, `step2_CreateProfile`, `step3_ChooseServerOperators`, `step4_SetNotificationsMode`, `onboardingComplete` |
+| `Profile` | `SimpleXChat/ChatTypes.swift` | `displayName`, `fullName`, `image`, `shortDescr` |
+| `User` | `SimpleXChat/ChatTypes.swift` | Full user model with profile, userId, and settings |
+| `ArchiveConfig` | `SimpleXChat/APITypes.swift` | Configuration for database export/import |
+| `DBMigrationResult` | `SimpleXChat/API.swift` | Result of database migration: `.ok`, `.errorNotADatabase`, `.errorKeychain`, etc. |
+| `MigrationConfirmation` | `SimpleXChat/API.swift` | Migration confirmation mode: `.error`, `.yesUp`, `.yesUpDown` |
+| `DeviceToken` | `SimpleXChat/ChatTypes.swift` | Apple push notification device token |
+| `NtfTknStatus` | `SimpleXChat/ChatTypes.swift` | Notification token status: registered, active, expired, etc. |
+| `NotificationsMode` | `SimpleXChat/ChatTypes.swift` | `.off`, `.periodic`, `.instant` |
+| `MigrationFileLinkData` | Used in standalone file transfers for device migration |
+| `AppChatState` | `SimpleXChat/` | Shared state: `.active`, `.stopped`, `.suspended` |
+
+## Error Cases
+
+| Error | Cause | Handling |
+|-------|-------|----------|
+| `DBMigrationResult.errorNotADatabase` | Wrong encryption key or corrupt DB | Show `DatabaseErrorView` with options |
+| `DBMigrationResult.errorKeychain` | Keychain access failed | Show error, offer to re-enter passphrase |
+| `DBMigrationResult.errorMigration` | Schema migration failure | Show error with migration details |
+| `duplicateUserError` | Display name already in use | `UserProfileAlert.duplicateUserError` |
+| `invalidDisplayNameError` | Invalid characters in display name | `UserProfileAlert.invalidDisplayNameError` |
+| `createUserError` | Core failed to create user | Alert with error details |
+| `invalidNameError(validName)` | Name needs normalization | Alert suggesting the valid name |
+| Archive import errors | Missing files, version mismatch | Non-fatal `[ArchiveError]` displayed |
+| Migration interrupted | Network failure, app killed | State preserved in `chatModel.migrationState`, resumed on next launch |
+
+## Key Files
+
+| File | Purpose |
+|------|---------|
+| `Shared/SimpleXApp.swift` | App entry point: `haskell_init`, defaults registration, DB container setup, BG tasks |
+| `Shared/AppDelegate.swift` | Push notification registration, URL handling |
+| `Shared/ContentView.swift` | Root view: authentication, onboarding routing, chat initialization |
+| `Shared/Views/Onboarding/OnboardingView.swift` | Onboarding step router, `OnboardingStage` enum |
+| `Shared/Views/Onboarding/SimpleXInfo.swift` | Step 1: Privacy architecture explanation |
+| `Shared/Views/Onboarding/CreateProfile.swift` | Profile creation: `CreateProfile` (additional) and `CreateFirstProfile` (onboarding) |
+| `Shared/Views/Onboarding/ChooseServerOperators.swift` | Step 3: Server operator conditions |
+| `Shared/Views/Onboarding/SetNotificationsMode.swift` | Step 4: Notification mode selection |
+| `Shared/Views/Onboarding/CreateSimpleXAddress.swift` | Optional address creation during onboarding |
+| `Shared/Views/Onboarding/HowItWorks.swift` | Educational content about SimpleX protocol |
+| `Shared/Views/Migration/MigrateFromDevice.swift` | Source device migration UI |
+| `Shared/Views/Migration/MigrateToDevice.swift` | Destination device migration UI |
+| `Shared/Views/Database/DatabaseView.swift` | Database management: export, import, encryption |
+| `Shared/Views/Database/DatabaseEncryptionView.swift` | Database passphrase management |
+| `Shared/Views/Database/DatabaseErrorView.swift` | Database error recovery UI |
+| `Shared/Views/Database/MigrateToAppGroupView.swift` | Legacy migration from Documents to App Group container |
+| `Shared/Model/SimpleXAPI.swift` | `startChat`, `apiCreateActiveUser`, `apiExportArchive`, `apiImportArchive`, `apiRegisterToken` |
+| `SimpleXChat/API.swift` | `chatMigrateInit`, `chatInitTemporaryDatabase`, low-level DB initialization |
+| `SimpleXChat/FileUtils.swift` | DB file paths, constants (`DB_FILE_PREFIX`, `CHAT_DB`, `AGENT_DB`) |
+| `SimpleXChat/AppGroup.swift` | App group container configuration |
+| `SimpleXChat/KeyChain.swift` | Keychain access for DB passphrase and app passwords |
+| `Shared/Model/BGManager.swift` | Background task registration and scheduling |
+| `Shared/Model/NtfManager.swift` | Notification management and badge counts |
+
+## Related Specifications
+
+- `apps/ios/product/README.md` -- Product overview: architecture and capabilities
+- `apps/ios/product/flows/connection.md` -- After onboarding, user establishes first connections
+- `apps/ios/product/flows/messaging.md` -- Messaging starts after profile creation
diff --git a/apps/ios/product/gaps.md b/apps/ios/product/gaps.md
new file mode 100644
index 0000000000..50d6bf2938
--- /dev/null
+++ b/apps/ios/product/gaps.md
@@ -0,0 +1,64 @@
+# SimpleX Chat iOS -- Known Gaps & Recommendations
+
+> Aggregation of `[GAP]` and `[REC]` annotations discovered during specification analysis. Organized by product area.
+>
+> **Related spec:** [spec/README.md](../spec/README.md)
+
+---
+
+## UI: Error Feedback
+
+### GAP: No user-visible error on FFI command failure
+**Source:** [spec/architecture.md](../spec/architecture.md)
+API calls via `chatApiSendCmd` return `APIResult` which can be `.error(ChatError)`. Not all error cases surface user-visible feedback in the UI.
+
+**REC:** Audit all `chatApiSendCmd` call sites and ensure `.error` cases show appropriate alerts or banners.
+
+---
+
+## UI: Loading States
+
+### GAP: No loading indicator during initial chat list population
+**Source:** [spec/client/chat-list.md](../spec/client/chat-list.md)
+When `ChatModel.chatInitialized` transitions to `true`, the chat list appears fully formed. There is no intermediate loading state for users with large numbers of chats.
+
+**REC:** Add a progress indicator during `apiGetChats` for users with 100+ conversations.
+
+---
+
+## Flows: Group Lifecycle
+
+### GAP: Bulk member role change — API supports batch but UI uses single-member calls
+**Source:** [spec/api.md](../spec/api.md)
+`APIMembersRole` accepts `NonEmpty GroupMemberId`, supporting batch role changes at the API level. However, the iOS UI (`GroupMemberInfoView.swift`) currently invokes it with a single member at a time.
+
+**REC:** Expose batch role change in the UI for group admins managing large groups.
+
+---
+
+## Security
+
+### GAP: Database passphrase not enforced by default
+**Source:** [spec/database.md](../spec/database.md)
+Database encryption is optional and requires the user to manually set a passphrase. New installations start with an unencrypted database.
+
+**REC:** Consider prompting users to set a database passphrase during onboarding, especially on devices without hardware encryption.
+
+### GAP: No forward secrecy indicator in UI
+**Source:** [product/glossary.md](glossary.md)
+While the double-ratchet protocol provides forward secrecy, there is no UI indicator showing whether a specific conversation has achieved forward secrecy (i.e., completed initial key exchange ratcheting).
+
+**REC:** Add a security indicator in contact/group info showing ratchet state.
+
+---
+
+## Documentation
+
+### GAP: Haskell Store layer not fully specified
+**Source:** [spec/database.md](../spec/database.md)
+The Haskell Store modules (`Store/Direct.hs`, `Store/Groups.hs`, `Store/Messages.hs`, etc.) are referenced by function name but not fully specified with parameter types and return types.
+
+**REC:** Expand database spec with key Store function signatures as the specification matures.
+
+---
+
diff --git a/apps/ios/product/glossary.md b/apps/ios/product/glossary.md
new file mode 100644
index 0000000000..7b2e227ffa
--- /dev/null
+++ b/apps/ios/product/glossary.md
@@ -0,0 +1,253 @@
+# SimpleX Chat iOS -- Glossary
+
+> SimpleX Chat iOS domain glossary. Defines all domain terms used in SimpleX Chat with links to relevant specifications and source code.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/architecture.md](../spec/architecture.md)
+
+## Table of Contents
+
+1. [Protocols & Cryptography](#protocols--cryptography)
+2. [Core Data Types](#core-data-types)
+3. [Commands & Events](#commands--events)
+4. [Connection & Identity](#connection--identity)
+5. [Messaging Features](#messaging-features)
+6. [Calling & Media](#calling--media)
+7. [Notifications & Background](#notifications--background)
+8. [Application Architecture](#application-architecture)
+9. [Configuration & Preferences](#configuration--preferences)
+
+---
+
+## Protocols & Cryptography
+
+### SMP (Simplex Messaging Protocol)
+The core messaging protocol used for asynchronous message delivery through relay servers. Each conversation uses separate unidirectional queues, and sender and receiver queues have no shared identifier. Defined in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/simplex-messaging.md`, implementation `simplexmq/src/Simplex/Messaging/Protocol.hs`*
+
+### SMP Server
+A relay server that stores and forwards encrypted messages between parties. Users can configure custom SMP servers or use defaults. Servers cannot see message contents or correlate senders with receivers. *See: `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift`*
+
+### XFTP (eXtended File Transfer Protocol)
+A protocol for transferring large files (up to 1GB) through relay servers. Files are encrypted, split into chunks, and uploaded to XFTP servers. Recipients download and reassemble chunks independently. Defined in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/xftp.md`, implementation `simplexmq/src/Simplex/FileTransfer/Protocol.hs`; chat-level integration `../../src/Simplex/Chat/Files.hs`*
+
+### XFTP Server
+A relay server that stores encrypted file chunks for asynchronous file transfer. Like SMP servers, users can configure custom XFTP servers. *See: `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift`*
+
+### SMP Agent
+The lower-level agent library (in [simplexmq](https://github.com/simplex-chat/simplexmq)) that manages SMP connections, queue creation/rotation, duplex connection establishment, message delivery, and the double-ratchet encryption protocol. The chat application layer communicates with the agent via its functional API. *See: protocol spec `simplexmq/protocol/agent-protocol.md`, implementation `simplexmq/src/Simplex/Messaging/Agent.hs`; chat-level integration `../../src/Simplex/Chat/Controller.hs`*
+
+### Double Ratchet
+The key agreement protocol used for E2E encryption. Provides forward secrecy and break-in recovery by deriving new encryption keys for each message. Based on the Signal protocol's double-ratchet algorithm, augmented with post-quantum KEM (PQDR). Implemented in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/pqdr.md`, implementation `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs`*
+
+### Post-Quantum Encryption
+Optional quantum-resistant key exchange (PQ) available for direct chats. Uses a hybrid scheme combining classical X25519 with Streamlined NTRU-Prime 761 (sntrup761) KEM. The hybrid secret is SHA3-256(DH_secret || KEM_shared_secret). Implemented in the [simplexmq](https://github.com/simplex-chat/simplexmq) library. *See: protocol spec `simplexmq/protocol/pqdr.md`, implementation `simplexmq/src/Simplex/Messaging/Crypto/SNTRUP761.hs`; Swift types `SimpleXChat/ChatTypes.swift` (PQEncryption, PQSupport)*
+
+### E2E Encryption
+End-to-end encryption ensuring that only the communicating parties can read message contents. Neither SMP relay servers nor any network observer can decrypt messages. All SimpleX Chat messages are E2E encrypted by default using the double-ratchet protocol. *See: `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs` (ratchet implementation), `simplexmq/src/Simplex/Messaging/Agent/Protocol.hs` (E2E message envelopes)*
+
+### Forward Secrecy
+A property of the double-ratchet protocol ensuring that compromise of current encryption keys does not compromise past session keys. Each message uses a derived key that is deleted after use. *See: `simplexmq/protocol/pqdr.md`, `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs`*
+
+### Chat Protocol (x-events)
+The chat-level protocol defining message envelopes and content types exchanged between chat participants. Includes x-events (XMsgNew, XMsgUpdate, XMsgDel, XCallInv, XFileCancel, XGrpMemNew, etc.), MsgContent (text, image, video, voice, file, link), and message encoding (Binary/JSON). This is distinct from the lower-level SMP transport protocol. *See: `../../src/Simplex/Chat/Protocol.hs`*
+
+### Security Code
+A hash of the shared encryption session displayed as a numeric code and QR code. Contacts can compare security codes out-of-band to verify they have an uncompromised E2E session. *See: `Shared/Views/Chat/VerifyCodeView.swift`, `../../src/Simplex/Chat/Controller.hs` (APIVerifyContact)*
+
+---
+
+## Core Data Types
+
+### ChatItem
+The fundamental unit of content in a conversation. Represents a single message, event, call record, or system notification within a chat. Each ChatItem has direction (sent/received), content, metadata, and optional quoted context. *See: `../../src/Simplex/Chat/Messages.hs` (data ChatItem), `SimpleXChat/ChatTypes.swift`*
+
+### ChatInfo
+A type-safe wrapper identifying a conversation and its metadata. Variants: DirectChat (1:1 with Contact), GroupChat (with GroupInfo), LocalChat (note folder), ContactRequest, ContactConnection. *See: `../../src/Simplex/Chat/Messages.hs` (data ChatInfo), `SimpleXChat/ChatTypes.swift`*
+
+### CIContent
+The content payload of a ChatItem. Differentiates sent vs. received content types: message content (text/image/file/voice/link), deletion markers, call records, group events, and feature preference changes. *See: `../../src/Simplex/Chat/Messages/CIContent.hs` (data CIContent)*
+
+### User
+A local user profile within the app. Each user has an independent set of contacts, groups, and connections. Multiple users can exist in one app installation. Fields include userId, profile, display name, and optional view password hash for hidden profiles. *See: `../../src/Simplex/Chat/Types.hs` (data User), `Shared/Model/ChatModel.swift`*
+
+### Contact
+A remote party with whom the user has an established E2E encrypted connection. Stores the contact's profile, local alias, connection status, feature preferences, and UI settings. *See: `../../src/Simplex/Chat/Types.hs` (data Contact), `SimpleXChat/ChatTypes.swift`*
+
+### GroupInfo
+Metadata for a group conversation including group profile, member count, preferences, and membership status. Contains the user's own membership record as a GroupMember. *See: `../../src/Simplex/Chat/Types.hs` (data GroupInfo)*
+
+### GroupMember
+A participant in a group conversation. Each member has a role, status, profile, and optionally a direct connection. The user's own membership is also represented as a GroupMember within GroupInfo. *See: `../../src/Simplex/Chat/Types.hs` (data GroupMember)*
+
+### Connection
+A low-level SMP agent connection between two parties. Each connection has a status (new, joined, ready, deleted), an agent connection ID, and is associated with a specific contact or group member. *See: `../../src/Simplex/Chat/Types.hs` (data Connection)*
+
+### ConnStatus
+The lifecycle state of a Connection: ConnNew (created, awaiting join), ConnJoined (joined, handshake in progress), ConnReady (fully established), ConnDeleted (terminated). *See: `../../src/Simplex/Chat/Types.hs` (data ConnStatus)*
+
+### ContactStatus
+The status of a contact record: CSActive (normal), CSDeleted (deleted by contact), CSDeletedByUser (deleted by user). *See: `../../src/Simplex/Chat/Types.hs` (data ContactStatus)*
+
+### GroupMemberRole
+Hierarchical role assigned to a group member. From most to least privileged: GROwner, GRAdmin, GRModerator, GRMember, GRObserver, GRRelay. Roles determine permissions for sending messages, managing members, and moderating content. The `.relay` role is below `.observer` and is used for relay members in channels. *See: `../../src/Simplex/Chat/Types/Shared.hs` (data GroupMemberRole), `SimpleXChat/ChatTypes.swift` L2806*
+
+### GroupMemberStatus
+The lifecycle state of a group member: GSMemRejected, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown, GSMemInvited, GSMemIntroduced, GSMemIntroInvited, GSMemAccepted, GSMemAnnounced, GSMemConnected, GSMemComplete, GSMemCreator, GSMemPendingReview, GSMemPendingApproval. *See: `../../src/Simplex/Chat/Types.hs` (data GroupMemberStatus)*
+
+### FileTransfer
+Represents an in-progress or completed file transfer. Variants: FTSnd (sending, with metadata and per-recipient transfer records) and FTRcv (receiving). Tracks protocol (SMP inline or XFTP), progress, and encryption parameters. *See: `../../src/Simplex/Chat/Types.hs` (data FileTransfer)*
+
+### ChatTag
+A user-defined label for organizing conversations in the chat list. Each tag has a text label and optional emoji. Chats can have multiple tags, and the chat list can be filtered by tag. *See: `../../src/Simplex/Chat/Types.hs` (data ChatTag), `Shared/Views/ChatList/TagListView.swift`*
+
+### Channel
+A group that uses relay infrastructure for message delivery (`groupInfo.useRelays == true`). Channels decouple the message sender from direct group membership connections, routing messages through relay members instead. Channels display the `antenna.radiowaves.left.and.right` SF Symbol as their icon and render received messages with the group avatar and "channel" role label. *See: [spec/state.md](../spec/state.md) (Relay-Related Data Model), [spec/client/chat-view.md](../spec/client/chat-view.md) (Channel Message Rendering), `SimpleXChat/ChatTypes.swift` (GroupInfo.useRelays, GroupInfo.chatIconName)*
+
+### RelayStatus
+The lifecycle state of a relay member in a channel: `.rsNew` (created), `.rsInvited` (invitation sent), `.rsAccepted` (accepted by relay), `.rsActive` (fully operational). *See: `SimpleXChat/ChatTypes.swift` L2506*
+
+### GroupRelay
+A struct representing a relay instance for a group. Contains the relay's database ID (`groupRelayId`), associated group member ID, user chat relay ID, relay status, and optional relay link (per-group link for subscribers). *See: `SimpleXChat/ChatTypes.swift` L2555*
+
+### UserChatRelay
+A struct representing a user's chat relay configuration. Contains the relay's database ID (`chatRelayId`), SMP server address, name, domains, and flags for preset/tested/enabled/deleted status. *See: `SimpleXChat/ChatTypes.swift` L2513*
+
+### GroupShortLinkInfo
+Information about a group's short link including whether it's a direct link, associated relay hostnames, and shared group identifier. Transient data returned by `APIConnectPreparedGroup` — not persisted on GroupInfo. *See: `Shared/Model/AppAPITypes.swift` L1352*
+
+### CIDirection.channelRcv
+A chat item direction case for messages received via a channel relay, as opposed to `.groupRcv` for standard group messages. *See: `SimpleXChat/ChatTypes.swift` L3529*
+
+---
+
+## Commands & Events
+
+### ChatCommand
+A sum type representing all commands the UI can send to the chat controller. Examples: APISendMessages, APIGetChat, APIConnect, APINewGroup, APIDeleteChatItem. Commands are serialized and dispatched through the FFI bridge. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatCommand)*
+
+### ChatResponse
+A sum type representing synchronous responses from the chat controller to the UI after processing a ChatCommand. Examples: CRActiveUser, CRNewChatItems, CRChatItemUpdated. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatResponse)*
+
+### ChatEvent
+A sum type representing asynchronous events pushed from the chat controller to the UI. These are unsolicited notifications about state changes: incoming messages, connection status changes, call invitations, etc. *See: `../../src/Simplex/Chat/Controller.hs` (data ChatEvent)*
+
+### ChatError
+Error types returned by the chat controller. Variants: ChatError (application-level), ChatErrorAgent (SMP agent errors), ChatErrorStore (database errors), ChatErrorRemoteHost (remote desktop errors). *See: `../../src/Simplex/Chat/Controller.hs` (data ChatError)*
+
+---
+
+## Connection & Identity
+
+### SimpleX Address
+A long-lived contact address that others can use to send connection requests. Unlike one-time invitation links, an address can be reused by multiple contacts. The user can accept or reject each incoming request. *See: `Shared/Views/UserSettings/UserAddressView.swift`, `../../src/Simplex/Chat/Controller.hs` (APICreateMyAddress)*
+
+### Contact Link
+A one-time or reusable URI that initiates a contact connection. When scanned or opened, it triggers the SMP handshake to establish an E2E encrypted channel between two parties. *See: `Shared/Views/NewChat/NewChatView.swift`*
+
+### Group Link
+A shareable URI that allows new members to join a group. The link connects to the group host, who then introduces the new member to existing members. Configurable with a default member role. *See: `Shared/Views/Chat/Group/GroupLinkView.swift`, `../../src/Simplex/Chat/Types.hs` (data GroupLink)*
+
+### Short Link
+A compact version of SimpleX contact or group links, using a shorter URI format for easier sharing. Contains encoded connection parameters with reduced character length. *See: `../../src/Simplex/Chat/Controller.hs`*
+
+### Incognito Mode
+A privacy feature that generates a random profile (display name and avatar) for each new contact connection. The real user profile is never shared with incognito contacts. Can be toggled per-connection at invitation time. *See: `Shared/Views/UserSettings/IncognitoHelp.swift`, `../../src/Simplex/Chat/ProfileGenerator.hs`*
+
+### Hidden Profile
+A user profile protected by a separate password. Hidden profiles do not appear in the user picker or profile list. To access a hidden profile, the user enters its password in the search field of the user picker. *See: `Shared/Views/UserSettings/HiddenProfileView.swift`, `../../src/Simplex/Chat/Controller.hs` (APIHideUser)*
+
+---
+
+## Messaging Features
+
+### Delivery Receipt
+A confirmation that a message was successfully delivered to the recipient's device. Displayed as a double-check indicator on sent messages. Can be enabled or disabled per contact or globally. *See: `Shared/Views/UserSettings/SetDeliveryReceiptsView.swift`, `../../src/Simplex/Chat/Controller.hs`*
+
+### Read Receipt
+An indicator that a recipient has viewed a received message. Currently not implemented as a separate feature; delivery receipts serve as the primary delivery confirmation. *See: `Shared/Views/UserSettings/PrivacySettings.swift`*
+
+### Timed Message
+A message with a configurable time-to-live (TTL). After the TTL expires, the message is automatically deleted from both sender and recipient devices. The TTL is set as a chat feature preference. Also referred to as a disappearing message. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (TimedMessagesPreference)*
+
+### Disappearing Message
+Synonym for Timed Message. A message that self-destructs after a configured duration. The timer starts when the message is read by the recipient. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (TimedMessagesPreference)*
+
+### Message Integrity
+Verification that messages are received in order and without gaps. The system detects skipped messages and decryption failures, displaying integrity error indicators in the chat. *See: `Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift`, `../../src/Simplex/Chat/Messages/CIContent.hs`*
+
+### Decryption Error
+An error occurring when a received message cannot be decrypted, typically due to ratchet synchronization issues. The UI displays a specific error view with recovery options. *See: `Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift`, `../../src/Simplex/Chat/Messages/CIContent.hs`*
+
+---
+
+## Calling & Media
+
+### CallKit
+Apple's framework for integrating VoIP calls with the native iOS call UI. SimpleX Chat uses CallKit to display incoming calls on the lock screen, support call answering from the system UI, and manage audio sessions. *See: `Shared/Views/Call/CallController.swift`, `Shared/Views/Call/CallManager.swift`*
+
+### WebRTC
+The real-time communication framework used for audio/video calls. SimpleX Chat wraps WebRTC in an E2E encrypted layer, with signaling performed through the existing SMP message channel rather than a central server. *See: `Shared/Views/Call/WebRTC.swift`, `Shared/Views/Call/WebRTCClient.swift`*
+
+### ICE Server
+An Interactive Connectivity Establishment server used by WebRTC to discover network paths between call participants. SimpleX Chat supports configuring custom ICE servers. *See: `Shared/Views/UserSettings/RTCServers.swift`, `SimpleXChat/CallTypes.swift`*
+
+### TURN Server
+A Traversal Using Relays around NAT server that relays WebRTC media when direct peer-to-peer connection is not possible. A specific type of ICE server. SimpleX Chat allows configuring custom TURN servers for call relay. *See: `Shared/Views/UserSettings/RTCServers.swift`*
+
+### RcvCallInvitation
+An in-memory data structure representing an incoming call invitation. Contains the calling contact, call type (audio/video), encryption keys, and shared key for the WebRTC session. Not persisted to database. *See: `../../src/Simplex/Chat/Call.hs` (data RcvCallInvitation)*
+
+---
+
+## Notifications & Background
+
+### Notification Service Extension (NSE)
+An iOS app extension that processes incoming push notifications while the main app is not running. The NSE starts a temporary chat controller, decrypts the incoming message, and displays a notification with the message preview. *See: `SimpleX NSE/NotificationService.swift`, `SimpleX NSE/NSEAPITypes.swift`*
+
+### Background Task
+An iOS background execution context used for periodic message fetching when instant notifications are not enabled. Managed by BGManager to check for new messages at system-determined intervals. *See: `Shared/Model/BGManager.swift`*
+
+---
+
+## Application Architecture
+
+### chat_ctrl
+The opaque C pointer to the Haskell chat controller, obtained via FFI initialization. All chat operations are dispatched through this controller handle. The main app and NSE maintain separate chat_ctrl instances. *See: `SimpleXChat/API.swift` (chatController, getChatCtrl)*
+
+### ComposeState
+A Swift struct holding the current state of the message composition area. Tracks the message text, parsed markdown, preview, attached media, editing context, quote context, and voice recording state. *See: `Shared/Views/Chat/ComposeMessage/ComposeView.swift` (struct ComposeState)*
+
+### ChatModel
+The central observable model object for the iOS app. Holds all reactive state: current user, chat list, active chat, call state, app preferences, and navigation state. Published properties drive SwiftUI view updates. *See: `Shared/Model/ChatModel.swift` (class ChatModel)*
+
+### ItemsModel
+An observable model managing the list of ChatItems displayed in a conversation view. Handles item loading, pagination, merging of new items, and secondary chat filtering. *See: `Shared/Model/ChatModel.swift` (class ItemsModel)*
+
+### AppTheme
+An observable object encapsulating the current visual theme: name, base theme, color overrides, app-specific colors, and wallpaper configuration. Shared as an environment object across the SwiftUI view hierarchy. *See: `Shared/Theme/Theme.swift` (class AppTheme)*
+
+---
+
+## Configuration & Preferences
+
+### FeaturePreference
+A type class (Haskell) / protocol pattern representing a user's preference for a specific chat feature (e.g., timed messages, voice messages, calls). Each preference has an allow/enable setting and optional parameters. Feature preferences are negotiated between contacts. *See: `../../src/Simplex/Chat/Types/Preferences.hs` (class FeatureI, type FeaturePreference)*
+
+### ChatSettings
+Per-chat configuration including notification mode (all/mentions/off), send receipts toggle, favorite flag, and tag assignments. Stored per contact and per group. *See: `../../src/Simplex/Chat/Types.hs` (data ChatSettings)*
+
+### UserDefaults / GroupDefaults
+iOS persistent key-value storage for app preferences. GroupDefaults (UserDefaults with the app group suite name) is shared between the main app and the NSE extension. Stores settings like notification mode, appearance preferences, and runtime flags. *See: `SimpleXChat/AppGroup.swift` (groupDefaults)*
+
+---
+
+## Cross-References
+
+- Product overview: [README.md](README.md)
+- Concept index: [concepts.md](concepts.md)
+- Haskell core types: `../../src/Simplex/Chat/Types.hs`
+- Haskell controller: `../../src/Simplex/Chat/Controller.hs`
+- Haskell chat protocol (x-events): `../../src/Simplex/Chat/Protocol.hs`
+- Haskell messages: `../../src/Simplex/Chat/Messages.hs`
+- Swift model: `Shared/Model/ChatModel.swift`
+- Swift API types: `SimpleXChat/APITypes.swift`, `SimpleXChat/ChatTypes.swift`
+- simplexmq library (SMP, XFTP, Agent, encryption): [github.com/simplex-chat/simplexmq](https://github.com/simplex-chat/simplexmq)
diff --git a/apps/ios/product/rules.md b/apps/ios/product/rules.md
new file mode 100644
index 0000000000..0cb3f8e96a
--- /dev/null
+++ b/apps/ios/product/rules.md
@@ -0,0 +1,148 @@
+# SimpleX Chat iOS -- Business Rules
+
+> Business invariants enforced by the SimpleX Chat iOS app and Haskell core. Each rule states the invariant, where it is enforced, and links to the relevant spec.
+>
+> **Related spec:** [spec/api.md](../spec/api.md) | [spec/architecture.md](../spec/architecture.md) | [spec/state.md](../spec/state.md)
+
+---
+
+## Security & Privacy
+
+### RULE-01: No user identifiers
+**Rule:** The system MUST NOT assign, generate, or expose any persistent user identifier (phone number, email, username, UUID) that could be used to correlate a user across conversations.
+**Enforced by:** SMP protocol design in simplexmq library; each connection uses independent unidirectional queues with no shared identifier.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-02: End-to-end encryption on all messages
+**Rule:** All message content MUST be encrypted end-to-end using double-ratchet (with optional post-quantum KEM). The SMP server MUST NOT have access to plaintext.
+**Enforced by:** simplexmq library (`Simplex.Messaging.Crypto.Ratchet`); encryption happens before `chat_send_cmd_retry` FFI call.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-03: Database encryption at rest
+**Rule:** Both SQLite databases (chat and agent) MUST be encrypted with SQLCipher when the user sets a database passphrase.
+**Enforced by:** `chat_migrate_init_key` in Haskell core via SQLCipher; `DatabaseEncryptionView.swift` in UI.
+**Spec:** [spec/database.md](../spec/database.md)
+
+### RULE-04: Local authentication before content access
+**Rule:** When app lock is enabled, the app MUST authenticate the user (Face ID, Touch ID, or passcode) before displaying any chat content.
+**Enforced by:** `LocalAuthView.swift`, `ContentView.swift` (`contentViewAccessAuthenticated` guard on `ChatModel`).
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+### RULE-05: Incognito profiles are per-connection
+**Rule:** When incognito mode is used for a connection, the generated random profile MUST be unique to that connection and MUST NOT be reused across connections.
+**Enforced by:** `ProfileGenerator.hs` generates fresh profile per connection; stored on the connection entity.
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## Message Integrity
+
+### RULE-06: Message order preservation
+**Rule:** Messages within a single connection MUST be displayed in the order determined by the SMP agent's sequence numbers, not by local timestamps.
+**Enforced by:** `Store/Messages.hs` (`createNewChatItem` uses agent-assigned ordering); `ItemsModel` in `ChatModel.swift` preserves this order.
+**Spec:** [spec/state.md](../spec/state.md)
+
+### RULE-07: Edited messages retain history
+**Rule:** When a message is edited, the previous version MUST be preserved in `chat_item_versions` and accessible via the item info view.
+**Enforced by:** `Controller.hs` (`APIUpdateChatItem`); `Store/Messages.hs` (`updateChatItem` creates version record); `ChatItemInfoView.swift` displays history.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-08: Deleted messages respect deletion mode
+**Rule:** `CIDeleteMode.cidmBroadcast` sends deletion to recipient; `cidmInternal` only deletes locally. Moderation deletion (`cidmInternalMark`) marks the item but retains a placeholder.
+**Enforced by:** `Controller.hs` (`APIDeleteChatItem` checks `CIDeleteMode`); `MarkedDeletedItemView.swift` renders moderation placeholders.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-09: Timed messages auto-delete after TTL
+**Rule:** Messages with a TTL MUST be automatically deleted from local storage after the configured time-to-live expires.
+**Enforced by:** `Controller.hs` (background task scheduling); `Store/Messages.hs` (TTL-based cleanup).
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## Group Integrity
+
+### RULE-10: Role hierarchy enforcement
+**Rule:** A member can only modify members with strictly lower roles. Owner > Admin > Moderator > Member > Observer.
+**Enforced by:** `Controller.hs` (`APIMembersRole` validates role hierarchy); `GroupMemberInfoView.swift` restricts available actions in UI.
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-11: Group creator is always owner
+**Rule:** The user who creates a group MUST be assigned the `GROwner` role and cannot be demoted.
+**Enforced by:** `Controller.hs` (`APINewGroup`); `Store/Groups.hs` (`createNewGroup` assigns owner role).
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-12: Group link role assignment
+**Rule:** Members joining via group link MUST receive the role configured on the link (default: `GRMember`). Only admins and owners can create group links.
+**Enforced by:** `Controller.hs` (`APICreateGroupLink` takes `memberRole` parameter); `GroupLinkView.swift` UI restricts to admin+.
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## File Transfer
+
+### RULE-13: File size limits
+**Rule:** Files up to 1GB are transferred via XFTP. The system MUST reject files exceeding the configured maximum.
+**Enforced by:** Haskell core (`Files.hs` checks file size); XFTP protocol enforces chunk limits.
+**Spec:** [spec/services/files.md](../spec/services/files.md)
+
+### RULE-14: File encryption at rest
+**Rule:** When `privacyEncryptLocalFiles` is enabled, downloaded files MUST be encrypted locally using AES with per-file random key/nonce stored in `CryptoFile`.
+**Enforced by:** `CryptoFile.swift` (`encryptCryptoFile`, `decryptCryptoFile`); `Library/Commands.hs` uses `CryptoFileArgs` for file encryption.
+**Spec:** [spec/services/files.md](../spec/services/files.md)
+
+---
+
+## Notification Delivery
+
+### RULE-15: Notification preview respects privacy setting
+**Rule:** Notification content MUST respect `NotificationPreviewMode`: `.message` shows full content, `.contact` shows sender only, `.hidden` shows generic alert.
+**Enforced by:** `Notifications.swift` (notification content creation checks `ntfPreviewModeGroupDefault`); `NotificationService.swift` (NSE content generation).
+**Spec:** [spec/services/notifications.md](../spec/services/notifications.md)
+
+### RULE-16: NSE database coordination
+**Rule:** The NSE and main app MUST NOT write to the database simultaneously. File locks coordinate access.
+**Enforced by:** `chat_close_store` / `chat_reopen_store` FFI calls; NSE uses short-lived database sessions.
+**Spec:** [spec/architecture.md](../spec/architecture.md)
+
+---
+
+## Channel Integrity
+
+### RULE-19: Channel owner cannot leave own channel
+**Rule:** A channel owner (`groupInfo.useRelays && groupInfo.isOwner`) who is the sole owner MUST NOT be able to leave the channel. The leave button is hidden in both swipe actions and context menu.
+**Enforced by:** `ChatListNavLink.swift` (swipe/context menu guards), `GroupChatInfoView.swift` (leave button conditional).
+**Spec:** [spec/client/chat-view.md](../spec/client/chat-view.md) | [spec/client/chat-list.md](../spec/client/chat-list.md)
+
+### RULE-20: Relay members cannot be removed
+**Rule:** Members with role `.relay` MUST NOT be removable through the member info UI. The remove button is hidden for relay members.
+**Enforced by:** `GroupMemberInfoView.swift` (`mem.memberRole != .relay` guard on remove button).
+**Spec:** [spec/client/chat-view.md](../spec/client/chat-view.md)
+
+### RULE-21: Relay links cannot be used to connect
+**Rule:** SimpleX links with path `/r` (relay addresses) MUST be rejected when users attempt to connect. An explanatory alert is shown instead.
+**Enforced by:** `ContentView.swift` (`connectViaUrl_` early return for `/r` path), `NewChatView.swift` (`planAndConnect` guard for `.simplexLink(_, .relay, _, _)`).
+**Spec:** [spec/client/navigation.md](../spec/client/navigation.md)
+
+### RULE-22: Channel subscribers default to observer role
+**Rule:** Members joining a channel via its link MUST receive the `.observer` role. The initial role picker is hidden for channels.
+**Enforced by:** `AddChannelView.swift` (`groupLinkMemberRole: .observer` hardcoded), `GroupLinkView.swift` (role picker hidden when `isChannel`).
+**Spec:** [spec/api.md](../spec/api.md)
+
+### RULE-23: Channels default to history enabled
+**Rule:** Newly created channels MUST have message history enabled by default (`GroupPreference(enable: .on)`).
+**Enforced by:** `AddChannelView.swift` (`createChannel()` sets history preference).
+**Spec:** [spec/api.md](../spec/api.md)
+
+---
+
+## Call Integrity
+
+### RULE-17: Call encryption key exchange
+**Rule:** WebRTC call encryption keys MUST be negotiated over the existing E2E encrypted SMP channel, not through any external signaling server.
+**Enforced by:** `ActiveCallView.swift` sends call signaling via `apiSendCallInvitation`/`apiSendCallAnswer` which use SMP; `Call.hs` defines call protocol.
+**Spec:** [spec/services/calls.md](../spec/services/calls.md)
+
+### RULE-18: CallKit region restriction
+**Rule:** CallKit MUST be disabled in regions where it is restricted (China). The app uses in-app call UI as fallback.
+**Enforced by:** `CallController.swift` checks `useCallKit()` based on region; `ActiveCallView.swift` provides fallback UI.
+**Spec:** [spec/services/calls.md](../spec/services/calls.md)
diff --git a/apps/ios/product/views/call.md b/apps/ios/product/views/call.md
new file mode 100644
index 0000000000..f32f7ec243
--- /dev/null
+++ b/apps/ios/product/views/call.md
@@ -0,0 +1,122 @@
+# Audio / Video Call
+
+> **Related spec:** [spec/services/calls.md](../../spec/services/calls.md)
+
+## Purpose
+
+Make and receive end-to-end encrypted audio and video calls over WebRTC. Supports CallKit integration for native iOS call UI, picture-in-picture for video calls, audio device selection, and collapsible call overlay.
+
+## Route / Navigation
+
+- **Entry point (outgoing)**: Tap audio or video call button in `ChatInfoView` action buttons or `ChatView` toolbar
+- **Entry point (incoming)**: `IncomingCallView` banner appears at top of screen; or native CallKit UI if enabled
+- **Presented by**: `ActiveCallView` is overlaid on the main app view when `chatModel.activeCall` is set
+- **Collapsible**: Call view can be collapsed via `chatModel.activeCallViewIsCollapsed` to return to chat while call continues
+- **Dismiss**: Call ends when user taps end button or remote party disconnects
+
+## Page Sections
+
+### Incoming Call Banner (`IncomingCallView`)
+
+Displayed as an overlay banner when `CallController.activeCallInvitation` is set:
+
+| Element | Description |
+|---|---|
+| Profile avatar | User profile image (shown when multiple profiles exist) |
+| Call type icon | `video.fill` (green) for video calls, `phone.fill` (green) for audio |
+| Call type text | "Audio call" or "Video call" with caller info |
+| Caller profile | `ProfilePreview` showing caller name and image |
+| Reject button | Red `phone.down.fill` icon -- ends the invitation |
+| Ignore button | Neutral `multiply` icon -- dismisses the banner without rejecting |
+| Accept button | Green `checkmark` icon -- accepts the call; if another call is active, ends it first |
+
+Sound: Ringtone plays via `SoundPlayer.startRingtone()` while banner is visible (unless call view is already showing).
+
+### Active Call View (`ActiveCallView`)
+
+Full-screen overlay with black background:
+
+| Element | Description |
+|---|---|
+| Remote video | Full-screen `CallViewRemote` showing remote party's camera feed; tap toggles between `scaleAspectFill` and `scaleAspectFit` |
+| Local video preview | Small floating `CallViewLocal` in top-right corner (30% width); shows local camera with rounded corners |
+| Call overlay | `ActiveCallOverlay` with call controls (hidden when PiP is active for video calls) |
+| Screen keep-on | `AppDelegate.keepScreenOn(true)` prevents screen dimming during calls |
+
+### Call Controls (`ActiveCallOverlay`)
+
+Bottom bar of the active call:
+
+| Control | Description |
+|---|---|
+| Mute toggle | Microphone on/off |
+| Speaker toggle | Speaker/receiver switch |
+| Camera switch | Front/back camera toggle (video calls) |
+| Video toggle | Enable/disable video during call |
+| End call | Red phone-down button to terminate |
+| Audio device picker | `AudioDevicePicker` / `CallAudioDeviceManager` for selecting output (receiver, speaker, Bluetooth, AirPods) |
+
+### Picture-in-Picture (PiP)
+
+- When `pipShown == true` and call has video, the call overlay is hidden
+- PiP window shows the remote video feed
+- User can interact with the app normally while call continues
+
+### CallKit Integration
+
+Managed by `CallController`:
+
+| Feature | Description |
+|---|---|
+| Native incoming call UI | iOS system call screen for incoming calls (when CallKit is enabled) |
+| Call history | Optionally shown in Phone app recents (`DEFAULT_CALL_KIT_CALLS_IN_RECENTS`) |
+| System audio routing | CallKit manages audio session configuration |
+| Lock screen answering | Call can be answered from lock screen via system UI |
+
+When CallKit is not used, the app falls back to `IncomingCallView` banner.
+
+### WebRTC Client
+
+| Component | Description |
+|---|---|
+| `WebRTCClient` | Manages peer connection, ICE candidates, media tracks |
+| `WebRTC.swift` | Bridge between native code and WebRTC JavaScript via `WKWebView` |
+| `CallViewRenderers` | `CallViewLocal` and `CallViewRemote` SwiftUI wrappers for video renderers |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Permissions required | Prompts for microphone (and camera for video) permissions on first call |
+| Connecting | Call overlay shows connecting state; `SoundPlayer` plays connecting tone |
+| WebRTC client creation | `createWebRTCClient()` called on appear and when `canConnectCall` changes |
+| Call ended | `CallSoundsPlayer.vibrate(long: true)` on disconnect if was connected; audio session reset to `.soloAmbient` |
+| Call failed | Call dismissed; WebRTC client cleaned up |
+| No call invitation | `IncomingCallView` body is empty when no active invitation |
+
+## Audio Session Management
+
+- During call: Audio session configured for voice chat
+- Camera permissions: `AVFoundation.AVCaptureDevice` authorization checked
+- Audio device management: `CallAudioDeviceManager` handles routing changes and device enumeration
+- Post-call cleanup: Audio session reverted to `.soloAmbient`
+
+## Related Specs
+
+- `spec/services/calls.md` -- Call service specification
+- [Chat](chat.md) -- Call buttons in chat navigation bar
+- [Contact Info](contact-info.md) -- Call buttons in contact info action row
+- [Settings](settings.md) -- Call settings (CallKit, ICE servers, relay policy)
+
+## Source Files
+
+- `Shared/Views/Call/ActiveCallView.swift` -- Main active call view with video renderers and overlay
+- `Shared/Views/Call/IncomingCallView.swift` -- Incoming call notification banner
+- `Shared/Views/Call/CallController.swift` -- CallKit integration and call lifecycle management
+- `Shared/Views/Call/CallManager.swift` -- Call state management and CXProvider delegate
+- `Shared/Views/Call/CallAudioDeviceManager.swift` -- Audio device enumeration and routing
+- `Shared/Views/Call/AudioDevicePicker.swift` -- Audio output device picker UI
+- `Shared/Views/Call/WebRTC.swift` -- WebRTC signaling bridge via WKWebView
+- `Shared/Views/Call/WebRTCClient.swift` -- WebRTC peer connection management
+- `Shared/Views/Call/CallViewRenderers.swift` -- SwiftUI wrappers for local and remote video views
+- `Shared/Views/Call/SoundPlayer.swift` -- Ringtone and call sound playback
diff --git a/apps/ios/product/views/chat-list.md b/apps/ios/product/views/chat-list.md
new file mode 100644
index 0000000000..04d19bef9e
--- /dev/null
+++ b/apps/ios/product/views/chat-list.md
@@ -0,0 +1,130 @@
+# Chat List (Home Screen)
+
+> **Related spec:** [spec/client/chat-list.md](../../spec/client/chat-list.md)
+
+## Purpose
+
+Main screen of the SimpleX Chat app. Displays all conversations sorted by last activity, serves as the navigation root, and provides access to user profiles, settings, and new chat creation.
+
+## Route / Navigation
+
+- **Entry point**: App launch (root view), or back-navigation from any chat
+- **Presented by**: `ContentView` as the default view when `chatModel.chatId == nil`
+- **Navigation stack**: `NavStackCompat` wrapping `chatListView` with destination `chatView`
+- **UserPicker sheet**: Triggered by tapping the user avatar in the toolbar; presents `UserPicker` as a custom sheet, which links to `UserPickerSheetView` sub-sheets (address, preferences, profiles, current profile, use from desktop, settings)
+
+## Page Sections
+
+### Toolbar
+
+| Element | Location | Behavior |
+|---|---|---|
+| User avatar button | Leading | Opens `UserPicker` sheet (profile switcher, address, settings, preferences, connect to desktop) |
+| Connection status indicator | Center (`SubsStatusIndicator`) | Shows server subscription status; taps navigate to `ServersSummaryView` |
+| New chat button (pencil icon) | Trailing | Opens `NewChatSheet` modal |
+
+The toolbar supports two layout modes:
+- **Standard (top)**: Navigation bar with `.topBarLeading`, `.principal`, `.topBarTrailing` placements
+- **One-hand UI (bottom)**: Toolbar items placed in `.bottomBar` with the list vertically flipped via `scaleEffect(y: -1)`
+
+### Search Bar
+
+- Text field with magnifying glass icon
+- When active, `searchMode = true` hides the navigation bar and shows inline search
+- Filters chat list in real-time by contact/group name and message content
+- Detects pasted SimpleX links (`searchShowingSimplexLink`) and offers to connect
+
+### Chat Filter Tabs (Tags)
+
+Managed by `ChatTagsModel` and `TagListView`:
+
+| Filter | PresetTag | Description |
+|---|---|---|
+| All | (none) | No filter, shows all chats |
+| Unread | `.unread` | Chats with unread messages |
+| Favorites | `.favorites` | User-favorited chats |
+| Groups | `.groups` | Group conversations only |
+| Contacts | `.contacts` | Direct contacts only |
+| Business | `.business` | Business chat conversations |
+| Notes | `.notes` | Notes to self |
+| Group Reports | `.groupReports` | Moderation reports (non-collapsible) |
+| Custom tags | `.userTag(ChatTag)` | User-created tags with custom names |
+
+### Chat Preview Rows
+
+Each row rendered by `ChatPreviewView` inside `ChatListNavLink`:
+
+| Element | Description |
+|---|---|
+| Avatar | Profile image or colored initials circle; online status indicator for contacts |
+| Chat name | Display name (contact, group, or note-to-self) |
+| Last message preview | Truncated text of most recent message; supports markdown rendering |
+| Timestamp | Relative time of last activity (e.g., "2m", "1h", "Yesterday") |
+| Unread badge | Numeric count badge for unread messages; distinct styling for mentions |
+| Muted indicator | Bell-slash icon when notifications are muted |
+| Pinned indicator | Pin icon for pinned chats |
+| Incognito indicator | Shows when connected via incognito profile |
+| Connection status | Shows connecting/pending state for incomplete connections |
+
+### Channel Adaptations
+
+When a group has `groupInfo.useRelays == true` (channel):
+
+| Element | Channel behavior |
+|---|---|
+| Chat icon | Antenna icon (`antenna.radiowaves.left.and.right.circle.fill`) instead of group icon |
+| Swipe "Leave" | Hidden for channel owners (`useRelays && isOwner`) |
+| Context menu "Leave" | Hidden for channel owners |
+| Delete alert | "Delete channel?" (not "Delete group?") |
+| Leave alert title | "Leave channel?" (not "Leave group?") |
+| Leave alert message | "You will stop receiving messages from this channel. Chat history will be preserved." |
+
+### Relay URL Handling
+
+When a relay address link (`/r` path) is opened via URL deep link, `ContentView.connectViaUrl_()` intercepts it and shows an alert: "Relay address" / "This is a chat relay address, it cannot be used to connect." The link is not processed further.
+
+### Swipe Actions
+
+- **Trailing swipe**: Mute/unmute, pin/unpin, tag management
+- **Leading swipe**: Mark as read/unread
+- **Context menu** (long press): Full set of actions including delete, clear chat, toggle favorite
+
+### Floating Elements
+
+- **One-hand UI card** (`OneHandUICard`): Dismissible card shown to introduce bottom toolbar mode
+- **Address creation card** (`AddressCreationCard`): Prompts user to create a SimpleX address
+
+### Pull-to-Refresh
+
+Triggers `reconnectAllServers()` after user confirmation alert ("Reconnect servers?"). Uses additional traffic to force message delivery.
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Chat database not started | Settings row shows exclamation icon; chat running == false disables interactions |
+| No chats | `ChatHelp` view displayed with onboarding guidance |
+| Connection in progress | `ConnectProgressManager` overlay with connecting text |
+| Search with no results | Empty list with no special empty-state view |
+
+## Related Specs
+
+- `spec/client/chat-list.md` -- Chat list feature specification
+- `spec/state.md` -- Application state management
+- [User Profiles](user-profiles.md) -- Profile switching from UserPicker
+- [Settings](settings.md) -- Settings accessed via UserPicker
+- [New Chat](new-chat.md) -- New chat sheet triggered from toolbar
+- [Chat](chat.md) -- Navigated to when tapping a chat row
+
+## Source Files
+
+- `Shared/Views/ChatList/ChatListView.swift` -- Main view, toolbar, search, filter logic
+- `Shared/Views/ChatList/ChatPreviewView.swift` -- Individual chat row rendering
+- `Shared/Views/ChatList/ChatListNavLink.swift` -- Navigation link wrapper with swipe actions
+- `Shared/Views/ChatList/TagListView.swift` -- Filter tab bar (preset + custom tags)
+- `Shared/Views/ChatList/UserPicker.swift` -- User profile picker sheet
+- `Shared/Views/ChatList/ChatHelp.swift` -- Empty-state help view
+- `Shared/Views/ChatList/ContactRequestView.swift` -- Contact request row rendering
+- `Shared/Views/ChatList/ContactConnectionView.swift` -- Pending connection row rendering
+- `Shared/Views/ChatList/OneHandUICard.swift` -- One-hand UI introduction card
+- `Shared/Views/ChatList/ServersSummaryView.swift` -- Server subscription summary
diff --git a/apps/ios/product/views/chat.md b/apps/ios/product/views/chat.md
new file mode 100644
index 0000000000..bf84bf4feb
--- /dev/null
+++ b/apps/ios/product/views/chat.md
@@ -0,0 +1,174 @@
+# Chat View (Conversation)
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md) | [spec/client/compose.md](../../spec/client/compose.md)
+
+## Purpose
+
+Full conversation view for displaying and interacting with messages in a direct contact chat, group chat, or note-to-self. Supports text messaging with markdown, media attachments, voice messages, E2E encrypted calls, message reactions, replies, forwarding, and content search/filtering.
+
+## Route / Navigation
+
+- **Entry point**: Tap a chat row in `ChatListView`
+- **Presented by**: `NavStackCompat` destination from `ChatListView`, bound to `chatModel.chatId`
+- **Back navigation**: Dismiss sets `chatModel.chatId = nil`, returning to chat list
+- **Sub-navigation**: Info button navigates to `ChatInfoView` (contact) or `GroupChatInfoView` (group); member avatars navigate to `GroupMemberInfoView`
+
+## Page Sections
+
+### Navigation Bar
+
+Custom toolbar overlaying the chat with themed material background:
+
+| Element | Description |
+|---|---|
+| Back button | Returns to chat list |
+| Contact/Group avatar | Small profile image |
+| Chat name | Display name; tappable to open info sheet |
+| Encryption badge | Shows PQ (post-quantum) or standard E2E status |
+| Call buttons | Audio and video call icons (direct chats only) |
+| Search button | Toggles in-chat message search |
+| Info button | Opens `ChatInfoView` or `GroupChatInfoView` |
+
+### Message List
+
+Rendered by `EndlessScrollView` with lazy loading and pagination:
+
+| Feature | Description |
+|---|---|
+| Scroll direction | Bottom-to-top (newest messages at bottom) |
+| Pagination | Loads more items on scroll to top (`loadingTopItems`) and bottom (`loadingBottomItems`) |
+| Merged items | Adjacent messages from the same sender are visually merged via `MergedItems` |
+| Floating buttons | Scroll-to-bottom button with unread count; scroll-to-first-unread button |
+| Date separators | Sticky date headers between messages from different days |
+| Wallpaper | Themed background image with tint and opacity from `theme.wallpaper` |
+| Content filter | Filter messages by type: `.images`, `.files`, `.links` |
+
+### Message Types
+
+Each type has a dedicated view in `Shared/Views/Chat/ChatItem/`:
+
+| Type | View | Description |
+|---|---|---|
+| Text | `MsgContentView` | Rendered with markdown (bold, italic, code, links, mentions) |
+| Image | `CIImageView` | Thumbnail with tap-to-fullscreen via `FullScreenMediaView` |
+| Video | `CIVideoView` | Video thumbnail with play button; inline playback |
+| Voice | `CIVoiceView` / `FramedCIVoiceView` | Waveform visualization with playback controls and duration |
+| File | `CIFileView` | File icon, name, size; download/open actions |
+| Link preview | `CILinkView` | URL preview card with title, description, image |
+| Emoji-only | `EmojiItemView` | Large emoji rendering without message bubble |
+| Call event | `CICallItemView` | Call status (missed, ended, duration) |
+| Group event | `CIEventView` | Member joined/left, role changes, group updates |
+| E2EE info | `CIChatFeatureView` | Encryption status and feature change notifications |
+| Group invitation | `CIGroupInvitationView` | Inline group join invitation card |
+| Deleted | `DeletedItemView` / `MarkedDeletedItemView` | Placeholder for deleted messages |
+| Decryption error | `CIRcvDecryptionError` | Error with ratchet sync suggestion |
+| Invalid JSON | `CIInvalidJSONView` | Developer fallback for malformed items |
+| Integrity error | `IntegrityErrorItemView` | Message integrity/gap warnings |
+
+### Message Interactions
+
+Long-press context menu on any message:
+
+| Action | Description |
+|---|---|
+| Reply | Sets compose bar to reply mode with quoted message |
+| Forward | Opens `forwardedChatItems` sheet to pick destination chat |
+| Copy | Copies message text to clipboard |
+| Edit | Enters edit mode in compose bar (own messages, within edit window) |
+| Delete | Delete for self or delete for everyone (with confirmation) |
+| React | Opens emoji reaction picker |
+| Select multiple | Enters multi-select mode (`selectedChatItems`) with bulk delete/forward |
+| Info | Shows delivery status and timestamps |
+
+Emoji reactions bar displayed below messages with reaction counts.
+
+### Compose Bar (`ComposeView`)
+
+| Element | Description |
+|---|---|
+| Text input | `NativeTextEditor` with markdown support and auto-growing height |
+| Attachment button | Opens picker for images, videos, files, camera |
+| Send button | Sends composed message; changes to voice record button when empty |
+| Voice record | Hold-to-record with waveform preview; swipe-to-cancel |
+| Reply quote | Shows quoted message above input when replying |
+| Edit indicator | Shows "editing" label when editing a previous message |
+| Link preview | Auto-generated preview card for detected URLs (`ComposeLinkView`) |
+| Image/Video preview | Thumbnail strip for selected media (`ComposeImageView`) |
+| File preview | File name and size for attached file (`ComposeFileView`) |
+| Voice preview | Waveform of recorded voice message (`ComposeVoiceView`) |
+| Live message | Real-time typing broadcast (optional, with alert on first use) |
+| Context actions | `ContextContactRequestActionsView` for accepting/rejecting contact requests; `ContextPendingMemberActionsView` for pending group member actions |
+| Commands menu | `CommandsMenuView` for bot/menu commands in chats with `menuCommands` |
+| Group mentions | `GroupMentionsView` autocomplete popup when typing `@` in groups |
+| Profile picker | `ContextProfilePickerView` for choosing incognito/main profile |
+
+### Channel Messages
+
+In channel conversations (`groupInfo.useRelays == true`), received messages (`.channelRcv` direction) display with:
+- The **channel icon** (`antenna.radiowaves.left.and.right`) instead of the standard group icon
+- The **channel name** as sender, with "channel" as the role label
+- The **group profile image** as the avatar (tapping opens group info, not member info)
+- Consecutive channel messages are grouped without repeating the avatar
+- Channel messages cannot be moderated per-member (no member identity)
+
+### Member Support Chat (Groups)
+
+For groups with member support enabled:
+- `MemberSupportView` and `MemberSupportChatToolbar` shown as secondary chat within group
+- `SecondaryChatView` for scoped group chat views (reports, member support)
+- User knocking state: `userMemberKnockingTitleBar()` shown when user is pending admission
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Initial load | Messages load from `ItemsModel` with merged items; `allowLoadMoreItems` throttles pagination |
+| Loading more (top) | `loadingTopItems` spinner at top of scroll view |
+| Loading more (bottom) | `loadingBottomItems` spinner at bottom |
+| Connection in progress | `ConnectProgressManager` shows connecting text below compose bar |
+| Connecting text | "connecting..." label shown below message list when chat not yet ready |
+| Send disabled | Compose bar shows `disabledText` reason when `userCantSendReason` is set |
+| Empty chat | No messages placeholder (implicit -- empty scroll view) |
+
+## Related Specs
+
+- `spec/client/chat-view.md` -- Chat view feature specification
+- `spec/client/compose.md` -- Compose bar specification
+- [Chat List](chat-list.md) -- Parent navigation
+- [Contact Info](contact-info.md) -- Info sheet for direct chats
+- [Group Info](group-info.md) -- Info sheet for group chats
+- [Call](call.md) -- Audio/video calls initiated from toolbar
+
+## Source Files
+
+- `Shared/Views/Chat/ChatView.swift` -- Main chat view, message list, navigation, state management
+- `Shared/Views/Chat/ChatItemView.swift` -- Individual message item rendering dispatcher
+- `Shared/Views/Chat/ComposeMessage/ComposeView.swift` -- Compose bar container
+- `Shared/Views/Chat/ComposeMessage/SendMessageView.swift` -- Send button and voice record
+- `Shared/Views/Chat/ComposeMessage/NativeTextEditor.swift` -- Text input with markdown
+- `Shared/Views/Chat/ComposeMessage/ComposeImageView.swift` -- Image attachment preview
+- `Shared/Views/Chat/ComposeMessage/ComposeFileView.swift` -- File attachment preview
+- `Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift` -- Voice recording preview
+- `Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift` -- Link preview generation
+- `Shared/Views/Chat/ComposeMessage/ContextItemView.swift` -- Reply/edit context display
+- `Shared/Views/Chat/ComposeMessage/ContextContactRequestActionsView.swift` -- Contact request accept/reject
+- `Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift` -- Pending member actions
+- `Shared/Views/Chat/ComposeMessage/ContextProfilePickerView.swift` -- Profile picker for incognito
+- `Shared/Views/Chat/ChatItem/FramedItemView.swift` -- Framed message bubble rendering
+- `Shared/Views/Chat/ChatItem/MsgContentView.swift` -- Text message content with markdown
+- `Shared/Views/Chat/ChatItem/CIImageView.swift` -- Image message view
+- `Shared/Views/Chat/ChatItem/CIVideoView.swift` -- Video message view
+- `Shared/Views/Chat/ChatItem/CIVoiceView.swift` -- Voice message view
+- `Shared/Views/Chat/ChatItem/CIFileView.swift` -- File message view
+- `Shared/Views/Chat/ChatItem/CILinkView.swift` -- Link preview view
+- `Shared/Views/Chat/ChatItem/EmojiItemView.swift` -- Large emoji view
+- `Shared/Views/Chat/ChatItem/CICallItemView.swift` -- Call event view
+- `Shared/Views/Chat/ChatItem/CIEventView.swift` -- Group/system event view
+- `Shared/Views/Chat/ChatItem/CIChatFeatureView.swift` -- Feature change notification
+- `Shared/Views/Chat/ChatItem/CIMetaView.swift` -- Timestamp and delivery status
+- `Shared/Views/Chat/ChatItem/FullScreenMediaView.swift` -- Fullscreen image/video viewer
+- `Shared/Views/Chat/ChatItem/AnimatedImageView.swift` -- Animated GIF rendering
+- `Shared/Views/Chat/Group/GroupMentions.swift` -- @mention autocomplete
+- `Shared/Views/Chat/Group/MemberSupportView.swift` -- Member support scoped chat
+- `Shared/Views/Chat/Group/MemberSupportChatToolbar.swift` -- Support chat toolbar
+- `Shared/Views/Chat/Group/SecondaryChatView.swift` -- Secondary scoped chat view
diff --git a/apps/ios/product/views/contact-info.md b/apps/ios/product/views/contact-info.md
new file mode 100644
index 0000000000..5223bfcae4
--- /dev/null
+++ b/apps/ios/product/views/contact-info.md
@@ -0,0 +1,154 @@
+# Contact Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View contact details, manage per-contact preferences, verify security codes for E2E encryption, manage connection settings, and perform destructive actions like blocking or deleting a contact.
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a direct contact chat)
+- **Presented by**: `NavigationView` sheet from `ChatView` via `showChatInfoSheet`
+- **Sub-navigation**:
+ - Contact preferences -> `ContactPreferencesView`
+ - Security code verification -> `VerifyCodeView`
+ - Chat wallpaper -> `ChatWallpaperEditorSheet`
+
+## Page Sections
+
+### Contact Info Header
+
+| Element | Description |
+|---|---|
+| Profile image | Large circular avatar; tappable |
+| Display name | Contact's display name |
+| Full name | Optional full name below display name |
+| Connection status | Shows if contact is ready, connecting, or has issues |
+
+### Local Alias
+
+Editable text field (`aliasTextFieldFocused`) for setting a local-only name visible only on this device. Not shared with the contact.
+
+### Action Buttons
+
+Horizontal row of quick-action buttons (width divided by 4):
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearch` to search messages in chat |
+| Audio call | Initiate audio call (`AudioCallButton`) |
+| Video call | Initiate video call (`VideoButton`) |
+| Mute/Unmute | Toggle notification mode (`nextNtfMode`) |
+
+Call buttons check `connectionStats` and show alerts if connection state prevents calling.
+
+### Incognito Section
+
+Shown only when `customUserProfile` is set (connected via incognito):
+
+| Element | Description |
+|---|---|
+| "Your random profile" label | Shows the incognito display name used for this contact |
+
+### Connection Settings Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Verify security code | `connectionCode` available | Navigate to `VerifyCodeView` for QR-based code verification |
+| Contact preferences | Always | Navigate to `ContactPreferencesView` |
+| Send receipts | Always | Toggle: yes / no / default(yes) / default(no) |
+| Synchronize connection | `ratchetSyncAllowed` | Fix encryption ratchet desynchronization |
+| Chat theme | Always | Navigate to `ChatWallpaperEditorSheet` |
+
+All items disabled when `!contact.ready || !contact.active`.
+
+### Chat TTL Section
+
+| Element | Description |
+|---|---|
+| Chat TTL option | `ChatTTLOption` -- auto-delete timer for messages on this device |
+
+Footer: "Delete chat messages from your device."
+
+### Encryption Info Section
+
+Shown when `contact.activeConn` exists:
+
+| Element | Description |
+|---|---|
+| E2E encryption | "Quantum resistant" (PQ enabled) or "Standard" |
+
+### Contact Address Section
+
+Shown when `contact.contactLink` exists:
+
+| Element | Description |
+|---|---|
+| QR code | `SimpleXLinkQRCode` displaying the contact's address |
+| Share address | Share button for the contact's SimpleX address link |
+
+Footer: "You can share this address with your contacts to let them connect with **[name]**."
+
+### Servers Section
+
+Shown when `contact.ready && contact.active`:
+
+| Element | Description |
+|---|---|
+| Subscription status | `SubStatusRow` showing connection health; tappable for details |
+| Change receiving address | Button to switch SMP receiving queue (disabled during switch) |
+| Abort changing address | Button to cancel in-progress address switch |
+| Receiving via | SMP server hostnames for receiving queues |
+| Sending via | SMP server hostnames for sending queues |
+
+### Danger Zone Section
+
+| Action | Description |
+|---|---|
+| Clear chat | Delete all messages locally (confirmation alert) |
+| Delete contact | Remove contact entirely (confirmation alert) |
+
+### Developer Section
+
+Shown when `developerTools` is enabled:
+
+| Element | Description |
+|---|---|
+| Local name | Internal local display name |
+| Database ID | API entity ID |
+| Debug delivery | Button to fetch queue info via `apiContactQueueInfo` |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Loading connection info | `apiContactInfo` and `apiGetContactCode` called on appear; stats and code populated asynchronously |
+| Progress indicator | `ProgressView` overlay during TTL changes |
+| Contact not ready | Settings section disabled with reduced opacity |
+| Contact inactive | Settings section disabled |
+| Errors | Alert with localized error title and message |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `clearChatAlert` | Tap clear chat |
+| `subStatusAlert` | Tap subscription status row |
+| `switchAddressAlert` | Tap change receiving address |
+| `abortSwitchAddressAlert` | Tap abort address change |
+| `syncConnectionForceAlert` | Force ratchet sync |
+| `queueInfo` | Debug delivery results |
+| `someAlert` | Various sub-component alerts |
+
+## Related Specs
+
+- `spec/api.md` -- Contact API commands (info, code verification, preferences, delete)
+- [Chat](chat.md) -- Parent chat view
+- [Group Info](group-info.md) -- Similar pattern for group info
+
+## Source Files
+
+- `Shared/Views/Chat/ChatInfoView.swift` -- Main contact info view with all sections
+- `Shared/Views/Chat/ContactPreferencesView.swift` -- Per-contact feature preferences (timed messages, reactions, voice, calls, file transfer, full delete)
+- `Shared/Views/Chat/VerifyCodeView.swift` -- Security code verification via QR scan or visual comparison
diff --git a/apps/ios/product/views/group-info.md b/apps/ios/product/views/group-info.md
new file mode 100644
index 0000000000..ee0c449c68
--- /dev/null
+++ b/apps/ios/product/views/group-info.md
@@ -0,0 +1,244 @@
+# Group Chat Info
+
+> **Related spec:** [spec/client/chat-view.md](../../spec/client/chat-view.md)
+
+## Purpose
+
+View and manage group settings, member list, group preferences, group links, member admission, welcome messages, and moderation features. The scope of available actions depends on the user's role within the group (member, moderator, admin, owner).
+
+## Route / Navigation
+
+- **Entry point**: Tap the info button in `ChatView` navigation bar (when viewing a group chat)
+- **Presented by**: `NavigationView` sheet from `ChatView` via `showChatInfoSheet`
+- **Sub-navigation**:
+ - Edit group profile -> `GroupProfileView`
+ - Add members -> `AddGroupMembersView`
+ - Group link -> `GroupLinkView`
+ - Group preferences -> `GroupPreferencesView` (via `GroupPreferencesButton`)
+ - Welcome message -> `GroupWelcomeView`
+ - Member info -> `GroupMemberInfoView`
+ - Chat wallpaper -> `ChatWallpaperEditorSheet`
+ - Member support -> `MemberSupportView`
+ - Group reports -> `GroupReportsChatNavLink`
+
+## Page Sections
+
+### Group Info Header
+
+| Element | Description |
+|---|---|
+| Group image | Large circular profile image |
+| Group name | Display name (editable by owners) |
+| Member count | "N members" label |
+| Full name | Optional secondary name |
+| Description | Group description text (if set) |
+
+### Local Alias
+
+Editable text field for a local-only alias (not shared with other members). Focused via `aliasTextFieldFocused`.
+
+### Action Buttons
+
+Horizontal row of action buttons:
+
+| Button | Description |
+|---|---|
+| Search | Triggers `onSearch` callback to search messages in chat |
+| Mute/Unmute | Toggle notification mode (`nextNtfMode`) |
+
+### Group Management Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Group link | `canAddMembers` and not business chat | Navigate to `GroupLinkView` to create/manage invitation link |
+| Member support | Not business chat, role >= moderator | Navigate to member support chat view |
+| Group reports | `canModerate` | Navigate to group reports chat |
+| User support chat | Member active, role < moderator or has support chat | Navigate to own support chat with moderators |
+
+### Group Profile Section
+
+| Element | Condition | Description |
+|---|---|---|
+| Edit group | Owner, not business chat | Navigate to `GroupProfileView` for editing name, image, description |
+| Welcome message | Has description or is owner (not business) | Navigate to `GroupWelcomeView` for add/edit |
+| Group preferences | Always | Navigate to `GroupPreferencesView` -- timed messages, reactions, voice, files, direct messages, history visibility |
+
+Footer: "Only group owners can change group preferences." (or "Only chat owners can change preferences." for business chats)
+
+### Chat Settings Section
+
+| Element | Description |
+|---|---|
+| Send receipts | Toggle delivery receipts; disabled for groups > 20 current members with explanation |
+| Chat theme | Navigate to `ChatWallpaperEditorSheet` |
+| Chat TTL | `ChatTTLOption` -- set auto-deletion timer for messages on device |
+
+Footer: "Delete chat messages from your device."
+
+### Member List Section
+
+Header shows total member count (e.g., "25 members").
+
+| Element | Description |
+|---|---|
+| Invite members button | Shown if `canAddMembers`; disabled with tap alert if incognito |
+| Search field | Filter members by name (`searchText`) |
+| Member rows | Each shows: avatar, display name, role badge (owner/admin/moderator/observer), online status indicator, connection status |
+| Member tap | Navigates to `GroupMemberInfoView` |
+| Member swipe actions | Block/unblock member, block/unblock for all (moderators) |
+
+Member list is sorted by role (owners first) and filtered to exclude `memLeft` and `memRemoved` statuses.
+
+### Danger Zone Section
+
+| Action | Description |
+|---|---|
+| Clear chat | Deletes all messages locally (with confirmation alert) |
+| Leave group | Leave the group (with confirmation alert) |
+| Delete group | Delete entire group -- only for owners (with confirmation alert) |
+
+### Developer Section
+
+Shown when `developerTools` is enabled:
+
+| Element | Description |
+|---|---|
+| Local name | Internal chat local display name |
+| Database ID | API entity ID |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Loading members | Member list populated from `chatModel.groupMembers` |
+| Progress indicator | `ProgressView` overlay when `progressIndicator` is true (during TTL changes) |
+| Large group receipts | Receipts option disabled with "Disabled for large groups" label and info alert |
+| Incognito invite blocked | Alert: "Can't invite contacts when incognito" |
+| Errors | Alert with localized title and error description |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `deleteGroupAlert` | Tap delete group |
+| `clearChatAlert` | Tap clear chat |
+| `leaveGroupAlert` | Tap leave group |
+| `cantInviteIncognitoAlert` | Tap invite members while incognito |
+| `largeGroupReceiptsDisabled` | Tap receipts info on large group |
+| `blockMemberAlert` / `unblockMemberAlert` | Block/unblock member actions |
+| `blockForAllAlert` / `unblockForAllAlert` | Moderator block/unblock for all members |
+
+## Channel Adaptations
+
+When `groupInfo.useRelays == true`, the group info view adapts to channel semantics. All sections below describe differences from the standard group behavior above.
+
+### Channel Info Layout
+
+The top section splits into a channel-specific branch:
+
+| Element | Owner | Non-owner |
+|---|---|---|
+| Channel link | NavigationLink "Channel link" to `GroupLinkView` | Inline QR code (`SimpleXLinkQRCode`) + "Share link" button (if `groupProfile.publicGroup?.groupLink` exists) |
+| Members | NavigationLink "Owners & subscribers" to `ChannelMembersView` | NavigationLink "Owners" to `ChannelMembersView` |
+| Relays | NavigationLink "Chat relays" to `ChannelRelaysView` | NavigationLink "Chat relays" to `ChannelRelaysView` |
+
+### Channel Action Bar
+
+| Button | Channel behavior |
+|---|---|
+| Link button | Replaces "Add members" for channel owners; navigates to `GroupLinkView` |
+| Add members | Hidden for channels |
+
+### Hidden Sections for Channels
+
+The following are hidden when `groupInfo.useRelays == true`:
+
+- Group preferences button and footer
+- Send receipts toggle
+- Member list section (replaced by ChannelMembersView navigation)
+- Non-admin block section (in GroupMemberInfoView)
+
+### Channel Leave/Delete Rules
+
+- Sole channel owner cannot leave (button hidden when `isOwner && no other owners`)
+- "Leave group" -> "Leave channel"; "Delete group" -> "Delete channel"; "Edit group profile" -> "Edit channel profile"
+- `deleteGroupAlert`: "Delete channel?" / "Channel will be deleted for all subscribers - this cannot be undone!" (current member) or "Channel will be deleted for you - this cannot be undone!" (non-current member)
+- `leaveGroupAlert`: "Leave channel?" / "You will stop receiving messages from this channel. Chat history will be preserved."
+- `showRemoveMemberAlert`: "Remove subscriber?" / "Subscriber will be removed from channel - this cannot be undone!"
+
+### Channel Members View (`ChannelMembersView`)
+
+New view accessible from channel info, showing:
+
+| Section | Content | Visibility |
+|---|---|---|
+| Owners | Members with role >= `.owner`, plus current user if owner | Always |
+| Subscribers | Members with role < `.owner` and != `.relay` | Owner only |
+
+- Excludes `memLeft`, `memRemoved`, and current user from member list
+- Each row: profile image, verified badge, name; taps navigate to `GroupMemberInfoView`
+- Empty state: "No subscribers" when subscriber list is empty
+
+### Channel Relays View (`ChannelRelaysView`)
+
+New view accessible from channel info, showing relay members (role == `.relay`):
+
+| Element | Description |
+|---|---|
+| Relay list | Filtered from `chatModel.groupMembers` by `.relay` role |
+| Relay row | Profile image, relay display name, status text (`RelayStatus` or connection status) |
+| Relay tap | NavigationLink to `GroupMemberInfoView` with `groupRelay:` parameter |
+| Empty state | "No chat relays" |
+| Footer | "Chat relays forward messages to channel subscribers." |
+
+Owner sees relay status from `apiGetGroupRelays`; non-owner sees connection status only.
+
+### Channel Link View (`GroupLinkView` with `isChannel: true`)
+
+| Change | Channel behavior |
+|---|---|
+| Title | "Channel link" (not "Group link") |
+| Description | "Anybody will be able to join the channel" (omits "You won't lose members...") |
+| Initial role picker | Hidden |
+| Upgrade link button | Hidden |
+| Delete link button | Hidden (channel link deletion only via channel deletion) |
+| Short/full link toggle | Hidden |
+| Share button | Shares directly (no upgrade-and-share alert) |
+
+### Channel Member Info (`GroupMemberInfoView` adaptations)
+
+| Change | Channel behavior |
+|---|---|
+| Section header | "Relay" / "Owner" / "Subscriber" (based on member role) instead of "Member" |
+| Group label | "Channel" instead of "Group" / "Chat" |
+| Action buttons | Hidden (message/audio/video/search) |
+| Role change picker | Hidden |
+| Verify code button | Hidden for relay members |
+| Block section | Hidden for non-moderator users |
+| Remove button | Hidden for relay members |
+| "Remove member" label | "Remove subscriber" |
+| "Block for all?" alert | "Block subscriber for all?" |
+| "Unblock for all?" alert | "Unblock subscriber for all?" |
+| Relay link info row | Shown when `member.relayLink` exists, displays `hostFromRelayLink(link)` |
+| Relay address info row | Shown when `groupRelay?.userChatRelay.address` exists, with "Share relay address" button |
+| Relay footer | Owner: "Subscribers use relay link to connect to the channel. Relay address was used to set up this relay for the channel." Non-owner: "You connected to the channel via this relay link." |
+
+## Related Specs
+
+- `spec/api.md` -- Group API commands (create, update, add/remove members, roles, links)
+- [Chat](chat.md) -- Parent chat view
+- [Contact Info](contact-info.md) -- Similar pattern for direct contact info
+
+## Source Files
+
+- `Shared/Views/Chat/Group/GroupChatInfoView.swift` -- Main group info view with all sections
+- `Shared/Views/Chat/Group/GroupProfileView.swift` -- Edit group name, image, description
+- `Shared/Views/Chat/Group/AddGroupMembersView.swift` -- Member invitation view
+- `Shared/Views/Chat/Group/GroupLinkView.swift` -- Group link creation and management
+- `Shared/Views/Chat/Group/GroupPreferencesView.swift` -- Group feature preferences
+- `Shared/Views/Chat/Group/GroupWelcomeView.swift` -- Welcome message editor
+- `Shared/Views/Chat/Group/MemberAdmissionView.swift` -- Member admission policy settings
+- `Shared/Views/Chat/Group/GroupMemberInfoView.swift` -- Individual member info and actions
+- `Shared/Views/Chat/Group/GroupMentions.swift` -- @mention support in groups
+- `Shared/Views/Chat/Group/ChannelMembersView.swift` -- Channel owners/subscribers list
+- `Shared/Views/Chat/Group/ChannelRelaysView.swift` -- Channel relay status list
diff --git a/apps/ios/product/views/new-chat.md b/apps/ios/product/views/new-chat.md
new file mode 100644
index 0000000000..2ab5f9ba8f
--- /dev/null
+++ b/apps/ios/product/views/new-chat.md
@@ -0,0 +1,144 @@
+# New Chat / Connection
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md)
+
+## Purpose
+
+Create new contacts, groups, or connect with others via one-time invitation links or by scanning/pasting SimpleX links. This is the primary onramp for establishing new E2E encrypted connections.
+
+## Route / Navigation
+
+- **Entry point**: Tap the new chat button (pencil icon) in `ChatListView` toolbar
+- **Presented by**: `NewChatSheet` modal from `ChatListView`
+- **Internal navigation**: `NewChatMenuButton` provides a dropdown with options:
+ - "New chat" -- opens `NewChatView`
+ - "Create group" -- opens `AddGroupView`
+- **Tabs within NewChatView**: Segmented picker toggles between `.invite` (1-time link) and `.connect` (connect via link)
+- **Swipe gesture**: Left/right swipe switches between invite and connect tabs
+- **Dismiss behavior**: On dismiss, `showKeepInvitationAlert()` asks whether to keep an unused invitation link or delete it
+
+## Page Sections
+
+### Segmented Picker
+
+| Tab | Icon | Description |
+|---|---|---|
+| 1-time link | `link` | Generate and share a one-time invitation link |
+| Connect via link | `qrcode` | Scan QR code or paste a received link |
+
+### Invite Tab (1-time Link)
+
+Displayed when `selection == .invite`:
+
+| Element | Description |
+|---|---|
+| QR code display | Generated QR code for the invitation link (`SimpleXLinkQRCode`) |
+| Short/full link toggle | Switch between short and full link display |
+| Share button | System share sheet for the invitation link |
+| Copy button | Copy link to clipboard |
+| Incognito toggle | Option to connect with a random profile |
+| Loading state | `creatingLinkProgressView` spinner while `creatingConnReq` is true |
+| Retry button | Shown if link creation fails |
+
+Link creation calls `apiAddContact` which returns a `CreatedConnLink` with both `connFullLink` and optional `connShortLink`.
+
+### Connect Tab (Connect via Link)
+
+Displayed when `selection == .connect`:
+
+| Element | Description |
+|---|---|
+| QR code scanner | Camera-based `CodeScanner` view for scanning SimpleX QR codes |
+| Paste link field | Text input for pasting a SimpleX link manually |
+| Connect button | Initiates connection via the pasted/scanned link |
+
+Handled by `ConnectView` sub-view with `showQRCodeScanner` state.
+
+### Info Sheet
+
+Toolbar trailing button opens `AddContactLearnMore` info sheet explaining how SimpleX connections work.
+
+### Add Group
+
+Accessed via `NewChatMenuButton` dropdown:
+
+| Element | Description |
+|---|---|
+| Group name | Required text field |
+| Group image | Optional profile image picker |
+| Incognito option | Create group with random profile |
+| Create button | Creates group via API and navigates to group chat |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Creating invitation | `ProgressView` spinner shown; buttons disabled |
+| Link creation failure | Retry button displayed |
+| Invalid link pasted | Alert shown via `NewChatViewAlert.newChatSomeAlert` |
+| Connection in progress | Chat list shows pending connection entry |
+| Unused invitation on dismiss | Alert: "Keep unused invitation?" with Keep/Delete options |
+
+## Create Channel (`AddChannelView`)
+
+Accessed via `NewChatMenuButton` dropdown: "Create channel (BETA)" with antenna icon (`antenna.radiowaves.left.and.right.circle.fill`).
+
+### Three-Step Channel Creation Wizard
+
+| Step | View | Description |
+|---|---|---|
+| 1. Profile | `profileStepView()` | Channel name input with validation, optional profile image. "Configure relays" link navigates to `NetworkAndServers`. Warning footer if no relays enabled. |
+| 2. Progress | `progressStepView(_:)` | Relay connection progress: circular indicator (active/total), expandable relay list with status indicators (green=active, orange=invited/accepted, red=new). Cancel button deletes channel. |
+| 3. Link | `linkStepView(_:)` | Wraps `GroupLinkView(isChannel: true)` showing the channel link for sharing. |
+
+### Channel Creation Defaults
+
+- History preference auto-enabled (`GroupPreference(enable: .on)`)
+- Group link member role hardcoded to `.observer`
+- Up to 3 random enabled relays selected from user's configured relays
+
+### Channel Creation API
+
+Calls `apiNewPublicGroup(incognito:relayIds:groupProfile:)` which returns `publicGroupCreated` response with group info, link, and relay list. On cancel, `apiDeleteChat` deletes the channel.
+
+### Relay Validation
+
+- `checkHasRelays()`: validates at least one enabled, non-deleted relay exists
+- Warning footer: "Enable at least one chat relay in Network & Servers."
+- `getEnabledRelays()`: filters enabled/non-deleted relays from user's server config
+
+## Channel-Specific Connection Behavior
+
+### Relay Link Blocking
+
+When `planAndConnect` encounters a `.simplexLink(_, .relay, _, _)`, it shows a "Relay address" alert: "This is a chat relay address, it cannot be used to connect." Connection is blocked.
+
+### Channel Prepare/Join Alerts
+
+| Context | Channel behavior | Group behavior |
+|---|---|---|
+| Prepare alert icon | `antenna.radiowaves.left.and.right.circle.fill` | `person.2.circle.fill` |
+| Prepare alert title | "Open new channel" | "Open new group" |
+| Error text | "Error opening channel" | "Error opening group" |
+| Own-link confirm | "This is your link for channel" with only "Open channel" + "Cancel" (no incognito/profile options) | Full incognito/profile selection |
+| Known group alert | "Open channel" / "Open new channel" | "Open group" / "Open new group" |
+
+### Pre-Join Relay Info
+
+When preparing a channel link, `groupShortLinkInfo.groupRelays` (hostnames) are stored in `ChatModel.shared.channelRelayHostnames` for display in the subscriber relay bar before joining.
+
+## Related Specs
+
+- `spec/api.md` -- API commands: `APIAddContact`, `APIConnect`, `APICreateUserAddress`
+- `spec/client/navigation.md` -- Navigation architecture for channel creation flow
+- [Chat List](chat-list.md) -- Parent view that presents this sheet
+- [Chat](chat.md) -- Navigated to after successful connection
+
+## Source Files
+
+- `Shared/Views/NewChat/NewChatView.swift` -- Main view with invite/connect tabs, link generation
+- `Shared/Views/NewChat/NewChatMenuButton.swift` -- Dropdown menu (new chat, create group, create channel)
+- `Shared/Views/NewChat/QRCode.swift` -- QR code generation and display
+- `Shared/Views/NewChat/AddGroupView.swift` -- Group creation form
+- `Shared/Views/NewChat/AddChannelView.swift` -- Channel creation wizard (3 steps)
+- `Shared/Views/NewChat/AddContactLearnMore.swift` -- Info sheet explaining connection process
diff --git a/apps/ios/product/views/onboarding.md b/apps/ios/product/views/onboarding.md
new file mode 100644
index 0000000000..a283c25a19
--- /dev/null
+++ b/apps/ios/product/views/onboarding.md
@@ -0,0 +1,147 @@
+# Onboarding
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/architecture.md](../../spec/architecture.md)
+
+## Purpose
+
+First-time setup flow for new users. Guides through app introduction, profile creation, server operator conditions acceptance, and notification configuration. Also provides an entry point for device migration.
+
+## Route / Navigation
+
+- **Entry point**: App launch when `onboardingStageDefault` is not `.onboardingComplete`
+- **Presented by**: `OnboardingView` renders the appropriate step based on `OnboardingStage` enum
+- **Flow direction**: Linear progression; back navigation hidden on later steps (`.navigationBarBackButtonHidden(true)`)
+- **Completion**: Sets `onboardingStageDefault` to `.onboardingComplete` and updates `chatModel.onboardingStage`
+
+## Onboarding Steps
+
+### Step 1: Welcome / SimpleX Info (`SimpleXInfo`)
+
+**Stage**: `step1_SimpleXInfo`
+
+| Element | Description |
+|---|---|
+| Logo | SimpleX Chat logo (light/dark variant based on color scheme) |
+| "The future of messaging" | Info button opening `HowItWorks` sheet |
+| Privacy redefined | "No user identifiers." with privacy icon |
+| Immune to spam | "You decide who can connect." with shield icon |
+| Decentralized | "Anybody can host servers." with decentralized icon |
+| **Create your profile** button | Primary action; navigates to `CreateFirstProfile` |
+| **Migrate from another device** button | Secondary action; opens `MigrateToDevice` sheet |
+
+The "How it works" sheet (`HowItWorks`) explains SimpleX's privacy model with an option to proceed to profile creation.
+
+### Step 2: Create Profile (`CreateFirstProfile`)
+
+**Stage**: `step2_CreateProfile` (deprecated -- now part of step 1 flow)
+
+| Element | Description |
+|---|---|
+| Display name field | Required; auto-focused after 1 second delay |
+| Validation | `mkValidName` check; alerts for invalid/duplicate names |
+| Create button | Calls profile creation API; advances to next step |
+
+Profile is stored locally and only shared with contacts. Footer explains this privacy property.
+
+### Step 3: Server Operator Conditions (`OnboardingConditionsView`)
+
+**Stage**: `step3_ChooseServerOperators` (changed to simplified conditions view)
+
+| Element | Description |
+|---|---|
+| "Conditions of use" title | Large title header |
+| Privacy explanation | "Private chats, groups and your contacts are not accessible to server operators." |
+| Operator selection | Toggle operators (with `selectedOperatorIds`) |
+| Show conditions | Sheet to view full conditions (`ConditionsWebView`) |
+| Configure operators | Sheet to customize operator settings |
+| **Accept** button | Accepts conditions and advances to notifications step |
+
+Previous deprecated step `step3_CreateSimpleXAddress` (`CreateSimpleXAddress`) is no longer in the active flow.
+
+### Step 4: Set Notification Mode (`SetNotificationsMode`)
+
+**Stage**: `step4_SetNotificationsMode`
+
+| Element | Description |
+|---|---|
+| "Push notifications" title | Large title header |
+| Info text | Explanation of notification modes |
+| Mode selector | `NtfModeSelector` for each `NotificationsMode.values` |
+| **Enable notifications** / **Use chat** button | Sets notification mode and completes onboarding |
+| Info sheet | `NotificationsInfoView` accessible for detailed explanation |
+
+Notification modes:
+
+| Mode | Description |
+|---|---|
+| Instant | Background connection maintained; real-time notifications |
+| Periodic | Checks every 10 minutes; battery-friendly |
+| Off | No push notifications; messages received only when app is open |
+
+On completion, `onboardingStageDefault.set(.onboardingComplete)` is called.
+
+### Completion
+
+**Stage**: `onboardingComplete`
+
+`OnboardingView` renders `EmptyView()` and the app proceeds to `ChatListView`.
+
+## Optional Paths
+
+### Migrate from Another Device
+
+- Triggered from Step 1 via "Migrate from another device" button
+- Sets `chatModel.migrationState = .pasteOrScanLink`
+- Opens `MigrateToDevice` in a sheet within `NavigationView`
+- User pastes or scans a migration link from the source device
+- Imports database and settings from the linked device
+
+### What's New (`WhatsNewView`)
+
+- Not part of the linear onboarding flow
+- Shown when `DEFAULT_WHATS_NEW_VERSION` differs from current version
+- Accessible later from Settings > Help > What's new
+- Displays changelog with feature descriptions
+
+## Onboarding Stage Enum
+
+```
+enum OnboardingStage: String {
+ case step1_SimpleXInfo
+ case step2_CreateProfile // deprecated
+ case step3_CreateSimpleXAddress // deprecated
+ case step3_ChooseServerOperators // conditions acceptance
+ case step4_SetNotificationsMode
+ case onboardingComplete
+}
+```
+
+Persisted via `DEFAULT_ONBOARDING_STAGE` in `UserDefaults`.
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| No device token | Alert "No device token!" if trying to set notification mode without token |
+| Profile creation error | Alert with error description |
+| Migration failure | Error handling within `MigrateToDevice` flow |
+| Conditions loading | Async fetch of operator conditions |
+
+## Related Specs
+
+- `spec/architecture.md` -- App architecture and initialization flow
+- [Chat List](chat-list.md) -- Destination after onboarding completes
+- [User Profiles](user-profiles.md) -- Profile created during onboarding; additional profiles later
+- [Settings](settings.md) -- Notification and server settings revisitable after onboarding
+
+## Source Files
+
+- `Shared/Views/Onboarding/OnboardingView.swift` -- Step router and `OnboardingStage` enum definition
+- `Shared/Views/Onboarding/SimpleXInfo.swift` -- Step 1: Welcome screen with privacy highlights and migration entry
+- `Shared/Views/Onboarding/CreateProfile.swift` -- Profile creation form (shared between onboarding and user profiles)
+- `Shared/Views/Onboarding/CreateSimpleXAddress.swift` -- Deprecated step 3: SimpleX address creation
+- `Shared/Views/Onboarding/ChooseServerOperators.swift` -- Step 3: Server operator conditions and selection
+- `Shared/Views/Onboarding/SetNotificationsMode.swift` -- Step 4: Push notification mode selection
+- `Shared/Views/Onboarding/HowItWorks.swift` -- "How it works" info sheet from step 1
+- `Shared/Views/Onboarding/WhatsNewView.swift` -- Changelog / what's new display
+- `Shared/Views/Onboarding/AddressCreationCard.swift` -- Address creation prompt card
diff --git a/apps/ios/product/views/settings.md b/apps/ios/product/views/settings.md
new file mode 100644
index 0000000000..3cc4da5d2b
--- /dev/null
+++ b/apps/ios/product/views/settings.md
@@ -0,0 +1,201 @@
+# Settings
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/services/theme.md](../../spec/services/theme.md) | [spec/services/notifications.md](../../spec/services/notifications.md)
+
+## Purpose
+
+Configure all aspects of app behavior including notifications, network/servers, privacy, appearance, database management, call settings, and developer tools. Accessed from the UserPicker sheet on the chat list.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> Settings option
+- **Presented by**: `UserPickerSheetView(sheet: .settings)` wrapping `SettingsView` in a `NavigationView`
+- **Navigation title**: "Your settings"
+- **Sub-navigation**: Each settings row is a `NavigationLink` to a dedicated settings view
+
+## Page Sections
+
+### Settings Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Notifications | `bolt` (color varies by token status) | `NotificationsView` | Push notification mode and preview settings |
+| Network & servers | `externaldrive.connected.to.line.below` | `NetworkAndServers` | SMP/XFTP servers, proxy, .onion hosts, advanced network |
+| Audio & video calls | `video` | `CallSettings` | WebRTC relay policy, ICE servers, CallKit options |
+| Privacy & security | `lock` | `PrivacySettings` | SimpleX Lock, screen protection, delivery receipts, auto-accept |
+| Appearance | `sun.max` | `AppearanceSettings` | Theme, language, wallpapers, chat bubbles, toolbar opacity |
+
+All rows disabled when `chatModel.chatRunning != true`. Appearance row only shown when `UIApplication.shared.supportsAlternateIcons`.
+
+#### Notifications (`NotificationsView`)
+
+| Setting | Options |
+|---|---|
+| Notification mode | Instant (background connection) / Periodic (every 10 min) / Off |
+| Notification preview | Hidden / Contact name only / Message preview |
+| Token status indicator | Icon color reflects: new, registered, confirmed (yellow), active (green), expired, invalid |
+
+#### Network & Servers (`NetworkAndServers`)
+
+| Setting | Description |
+|---|---|
+| SMP servers | Messaging relay servers; per-operator configuration |
+| XFTP servers | File transfer servers; per-operator configuration |
+| Server operators | `OperatorView` for each configured operator |
+| Advanced network | `AdvancedNetworkSettings` -- timeouts, TCP keep-alive, reconnect intervals |
+| Proxy configuration | SOCKS proxy, .onion host settings |
+| Show sent via proxy | Toggle to show proxy indicator on sent messages |
+| Show subscription % | Toggle to show server subscription percentage |
+
+Sub-files: `NetworkAndServers.swift`, `ProtocolServersView.swift`, `ProtocolServerView.swift`, `NewServerView.swift`, `ScanProtocolServer.swift`, `AdvancedNetworkSettings.swift`, `OperatorView.swift`, `ConditionsWebView.swift`, `ChatRelayView.swift`
+
+##### Chat Relays
+
+Chat relays forward messages to channel subscribers. They appear in two locations:
+
+- **Operator View** (`OperatorView`): "Chat relays" section lists relays for each operator with `ChatRelayViewLink` rows. Footer: "Chat relays forward messages in channels you create."
+- **Your Servers** (`YourServersView` in `ProtocolServersView`): "Chat relays" section for non-operator relays. "Add server" dialog includes a "Chat relay" option.
+
+Each relay is managed via `ChatRelayView`:
+
+| Element | Preset relay | Custom relay |
+|---|---|---|
+| Name | Read-only display | Editable text field |
+| Address | Read-only display | Editable text field (validates as `.simplexLink(_, .relay, _, _)`) |
+| Test button | "Test relay" (shows "Not implemented" alert) | Same |
+| Enable toggle | "Use for new channels" | Same |
+| Delete | Not available | "Delete relay" button |
+
+Adding a relay: `NewChatRelayView` form with name, address, test, and enable toggle. Back-button validates name/address and shows alerts for invalid input.
+
+##### Server Warnings
+
+`ServersWarningView` displays an orange exclamation triangle with warning text when `UserServersWarning.noChatRelays` is detected. Appears in:
+- Network & Servers footer (`globalServersWarning`)
+- Operator view footer
+- Your servers footer
+
+Server validation (`validateServers_`) now returns both errors and warnings.
+
+#### Privacy & Security (`PrivacySettings`)
+
+| Setting | Description |
+|---|---|
+| SimpleX Lock | Enable biometric (Face ID / Touch ID) or passcode lock |
+| Lock mode | System biometric or custom passcode |
+| Lock timeout | Delay before lock activates (0s to 30min) |
+| Self-destruct | Optional self-destruct passcode that wipes all data |
+| Screen protection | Hide app content in app switcher |
+| Encrypt local files | Encrypt media and files stored on device |
+| Auto-accept images | Automatically download received images |
+| Link previews | Generate link previews for sent URLs |
+| SimpleX link mode | Description / Full link / Via browser |
+| Chat previews | Show message previews in chat list |
+| Save last draft | Remember unsent message drafts |
+| Delivery receipts | Enable/disable read receipts globally |
+| Media blur radius | Blur level for received media before tapping |
+
+#### Appearance (`AppearanceSettings`)
+
+| Setting | Description |
+|---|---|
+| App icon | Alternative app icon selection |
+| Language | Interface language |
+| Theme | System / Light / Dark |
+| Dark theme variant | Dark / SimpleX / Black |
+| Active theme colors | Accent color, chat bubble colors, text colors |
+| Wallpapers | Chat background wallpaper selection and customization |
+| Profile image corner radius | Adjust avatar roundness |
+| Chat bubble roundness | Adjust message bubble corner radius |
+| Chat bubble tail | Toggle message bubble tail/pointer |
+| Toolbar opacity | `ToolbarMaterial` transparency setting |
+| One-hand UI | Bottom toolbar layout for reachability |
+
+#### Audio & Video Calls (`CallSettings`)
+
+| Setting | Description |
+|---|---|
+| WebRTC relay policy | Always relay / Allow direct |
+| ICE servers | Custom STUN/TURN server configuration |
+| CallKit integration | Enable/disable native iOS call UI |
+| Calls in recents | Show/hide calls in Phone app history |
+| Lock screen calls | Show/accept on lock screen options |
+
+### Chat Database Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Database passphrase & export | `internaldrive` (orange if unencrypted) | `DatabaseView` | Passphrase management, export/import database, file storage stats |
+| Migrate to another device | `tray.and.arrow.up` | `MigrateFromDevice` | Export database and generate migration link |
+
+Database row shows exclamation octagon icon in red when `chatRunning == false`.
+
+### Help Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| How to use it | `questionmark` | `ChatHelp` | Usage guide with user's display name |
+| What's new | `plus` | `WhatsNewView` | Changelog and new features |
+| About SimpleX Chat | `info` | `SimpleXInfo` | About page with privacy explanation |
+| Send questions and ideas | `number` | Opens SimpleX team chat link | Direct contact with developers |
+| Send us email | `envelope` | `mailto:chat@simplex.chat` | Email link |
+
+### Support SimpleX Chat Section
+
+| Row | Icon | Action |
+|---|---|---|
+| Contribute | `keyboard` | Opens GitHub contribution guide |
+| Rate the app | `star` | `SKStoreReviewController.requestReview` |
+| Star on GitHub | GitHub icon | Opens GitHub repository |
+
+### Develop Section
+
+| Row | Icon | Destination | Description |
+|---|---|---|---|
+| Developer tools | `chevron.left.forwardslash.chevron.right` | `DeveloperView` | Chat console/terminal, log level, confirm DB upgrades |
+| App version | (none) | `VersionView` | Shows "v{version} ({build})" |
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Chat not running | Most navigation links disabled; database row shows warning |
+| Database not encrypted | Database icon shown in orange |
+| Migration in progress | `showProgress` overlays `ProgressView` on entire settings view |
+| Terminal cleanup | On disappear: `chatModel.showingTerminal = false`, terminal items cleared |
+
+## App Defaults
+
+Key `UserDefaults` / `AppStorage` keys managed by settings:
+- `DEFAULT_PERFORM_LA`, `DEFAULT_LA_MODE`, `DEFAULT_LA_LOCK_DELAY`, `DEFAULT_LA_SELF_DESTRUCT`
+- `DEFAULT_PRIVACY_ACCEPT_IMAGES`, `DEFAULT_PRIVACY_LINK_PREVIEWS`, `DEFAULT_PRIVACY_PROTECT_SCREEN`
+- `DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS`, `DEFAULT_PRIVACY_SAVE_LAST_DRAFT`
+- `DEFAULT_PRIVACY_DELIVERY_RECEIPTS_SET`, `DEFAULT_PRIVACY_MEDIA_BLUR_RADIUS`
+- `DEFAULT_WEBRTC_POLICY_RELAY`, `DEFAULT_WEBRTC_ICE_SERVERS`, `DEFAULT_CALL_KIT_CALLS_IN_RECENTS`
+- `DEFAULT_CURRENT_THEME`, `DEFAULT_SYSTEM_DARK_THEME`, `DEFAULT_THEME_OVERRIDES`
+- `DEFAULT_PROFILE_IMAGE_CORNER_RADIUS`, `DEFAULT_CHAT_ITEM_ROUNDNESS`, `DEFAULT_CHAT_ITEM_TAIL`
+- `DEFAULT_TOOLBAR_MATERIAL`, `DEFAULT_ONE_HAND_UI_CARD_SHOWN`
+- `DEFAULT_DEVELOPER_TOOLS`, `DEFAULT_SHOW_SENT_VIA_RPOXY`, `DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE`
+
+## Related Specs
+
+- `spec/architecture.md` -- App architecture overview
+- `spec/services/theme.md` -- Theme system specification
+- [Chat List](chat-list.md) -- Parent view via UserPicker
+- [User Profiles](user-profiles.md) -- Profile management (separate UserPicker option)
+
+## Source Files
+
+- `Shared/Views/UserSettings/SettingsView.swift` -- Main settings view, section layout, app defaults definitions
+- `Shared/Views/UserSettings/NotificationsView.swift` -- Notification mode and preview settings
+- `Shared/Views/UserSettings/AppearanceSettings.swift` -- Theme, wallpaper, UI customization
+- `Shared/Views/UserSettings/PrivacySettings.swift` -- Privacy and security settings
+- `Shared/Views/UserSettings/NetworkAndServers/NetworkAndServers.swift` -- Server and network configuration
+- `Shared/Views/UserSettings/NetworkAndServers/AdvancedNetworkSettings.swift` -- TCP/timeout settings
+- `Shared/Views/UserSettings/NetworkAndServers/ProtocolServersView.swift` -- SMP/XFTP server list
+- `Shared/Views/UserSettings/NetworkAndServers/ProtocolServerView.swift` -- Individual server edit
+- `Shared/Views/UserSettings/NetworkAndServers/NewServerView.swift` -- Add new server
+- `Shared/Views/UserSettings/NetworkAndServers/ScanProtocolServer.swift` -- Scan server QR code
+- `Shared/Views/UserSettings/NetworkAndServers/OperatorView.swift` -- Server operator configuration
+- `Shared/Views/UserSettings/NetworkAndServers/ChatRelayView.swift` -- Chat relay detail/edit/add views
+- `Shared/Views/UserSettings/NetworkAndServers/ConditionsWebView.swift` -- Operator conditions display
diff --git a/apps/ios/product/views/user-profiles.md b/apps/ios/product/views/user-profiles.md
new file mode 100644
index 0000000000..5a38db1816
--- /dev/null
+++ b/apps/ios/product/views/user-profiles.md
@@ -0,0 +1,137 @@
+# User Profiles
+
+> **Related spec:** [spec/client/navigation.md](../../spec/client/navigation.md) | [spec/state.md](../../spec/state.md)
+
+## Purpose
+
+Manage multiple chat profiles within a single app instance. Users can create, switch between, hide, mute, and delete profiles. Hidden profiles are protected by password and support a self-destruct password option.
+
+## Route / Navigation
+
+- **Entry point**: Tap user avatar in `ChatListView` toolbar -> `UserPicker` -> "Your chat profiles"
+- **Presented by**: `UserPickerSheetView(sheet: .chatProfiles)` wrapping `UserProfilesView` in a `NavigationView`
+- **Navigation title**: "Your chat profiles"
+- **Sub-navigation**:
+ - Create profile -> `CreateProfile`
+ - Edit profile -> profile detail view (via `selectedUser`)
+ - User address -> `UserAddressView` (via UserPicker `.address` sheet)
+
+## Page Sections
+
+### Search / Password Field
+
+Combined text field at the top (`searchTextOrPassword`):
+- In normal mode: Filters visible profiles by name
+- For hidden profiles: Acts as password entry to reveal hidden profiles
+- Trimmed search text compared against profile names and hidden profile passwords
+
+### Profile List
+
+Each row rendered by `userView()`:
+
+| Element | Description |
+|---|---|
+| Active indicator | Checkmark or highlighted state for the current active profile |
+| Profile image | Avatar circle with profile image or colored initials |
+| Display name | Profile's display name |
+| Unread count | Badge showing unread message count across all chats for this profile |
+| Muted indicator | Bell-slash icon if profile notifications are muted |
+| Hidden indicator | Lock icon for hidden profiles (only shown when revealed via password) |
+
+### Profile Actions
+
+Available via tap on a profile row:
+
+| Action | Condition | Description |
+|---|---|---|
+| Switch active | Different from current | Activates the selected profile; all chats switch context |
+| Mute / Unmute | Any profile | Toggle notification muting for the profile; shows alert on first mute (`showMuteProfileAlert`) |
+| Hide / Unhide | Non-active profile | Hide with password or reveal a hidden profile |
+| Delete | Non-active profile | Delete with confirmation; option to delete data from servers |
+
+### Add Profile Button
+
+| Element | Description |
+|---|---|
+| "Add profile" label | `Label("Add profile", systemImage: "plus")` |
+| Navigation | `NavigationLink` to `CreateProfile` view |
+| Auth required | Requires local authentication before creating |
+
+Only shown when `trimmedSearchTextOrPassword` is empty (not searching/entering password).
+
+### Hidden Profile Banner
+
+Shown when `profileHidden` is true (a profile was just hidden):
+
+| Element | Description |
+|---|---|
+| Lock icon | `lock.open` system image |
+| Message | "Enter password above to show!" |
+| Tap action | Dismisses the banner with animation |
+
+### Create Profile (`CreateProfile`)
+
+| Field | Description |
+|---|---|
+| Display name | Required text field with validation (`mkValidName`) |
+| Bio | Optional bio text (max 160 bytes) |
+| Create button | Disabled until valid name entered and bio within limit |
+
+Validation alerts: `duplicateUserError`, `invalidDisplayNameError`, `createUserError`, `invalidNameError`.
+
+## Profile Visibility
+
+| Visibility | Description |
+|---|---|
+| Public | Normal profile, always visible in the list |
+| Hidden | Protected by password; not shown unless password entered in search field |
+| Muted | Notifications suppressed; visual indicator in profile list |
+
+### Hidden Profile Password Management
+
+- Set password when hiding a profile
+- Password verified when entering in the search/password field
+- `UserProfileAction.unhideUser` requires password entry
+- Self-destruct password: Optional secondary password (`DEFAULT_LA_SELF_DESTRUCT`) that wipes all app data when entered
+
+### Delete Profile
+
+Two-stage confirmation:
+
+1. `confirmDeleteUser()` shows initial confirmation
+2. `UserProfilesAlert.deleteUser(user:, delSMPQueues:)` with option to delete queues from servers
+3. Requires local authentication (`withAuth`) before proceeding
+
+## Loading / Error States
+
+| State | Behavior |
+|---|---|
+| Authentication required | `authorized` state; prompts biometric/passcode before profile operations |
+| Profile switch | Async operation; profile switch errors shown via `activateUserError` alert |
+| Delete in progress | Profile removed from list; server queue deletion is async |
+| Errors | Alert with localized error title and description |
+
+## Alerts
+
+| Alert | Trigger |
+|---|---|
+| `deleteUser` | Confirm profile deletion |
+| `hiddenProfilesNotice` | First-time hidden profiles explanation (`showHiddenProfilesNotice`) |
+| `muteProfileAlert` | First-time mute explanation (`showMuteProfileAlert`) |
+| `activateUserError` | Profile switch failure |
+| `error` | General error display |
+
+## Related Specs
+
+- `spec/api.md` -- User management API commands (create user, delete user, activate user, hide user)
+- `spec/state.md` -- Application state: `chatModel.users`, `chatModel.currentUser`
+- [Chat List](chat-list.md) -- Reflects active profile's chats
+- [Settings](settings.md) -- Accessed from same UserPicker menu
+- [Onboarding](onboarding.md) -- Initial profile creation during first launch
+
+## Source Files
+
+- `Shared/Views/UserSettings/UserProfilesView.swift` -- Main profiles list, search/password, profile actions, delete confirmation
+- `Shared/Views/Onboarding/CreateProfile.swift` -- Profile creation form (shared with onboarding and profiles view)
+- `Shared/Views/UserSettings/UserAddressView.swift` -- User's SimpleX address management (create, share, delete)
+- `Shared/Views/ChatList/UserPicker.swift` -- Profile switcher sheet that navigates to this view
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 0826bca4a3..87a47ec2ab 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -164,7 +164,7 @@
"%d file(s) were not downloaded." = "%d файлов не было загружено.";
/* time interval */
-"%d hours" = "%d час.";
+"%d hours" = "%d ч.";
/* alert title */
"%d messages not forwarded" = "%d сообщений не переслано";
@@ -729,7 +729,7 @@ swipe action */
"attempts" = "попытки";
/* No comment provided by engineer. */
-"Audio & video calls" = "Аудио- и видеозвонки";
+"Audio & video calls" = "Аудио и видеозвонки";
/* No comment provided by engineer. */
"Audio and video calls" = "Аудио и видео звонки";
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Удалить сообщение?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Удалить сообщения";
/* No comment provided by engineer. */
@@ -2238,6 +2239,9 @@ chat item action */
/* alert message */
"Error connecting to forwarding server %@. Please try later." = "Ошибка подключения к пересылающему серверу %@. Попробуйте позже.";
+/* subscription status explanation */
+"Error connecting to the server used to receive messages from this connection: %@" = "Ошибка подключения к серверу, используемому для получения сообщений от этого соединения: %@";
+
/* No comment provided by engineer. */
"Error creating address" = "Ошибка при создании адреса";
@@ -3305,10 +3309,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!";
/* alert message */
@@ -3710,6 +3714,9 @@ snd error text */
/* servers error */
"No servers to send files." = "Нет серверов для отправки файлов.";
+/* No comment provided by engineer. */
+"no subscription" = "нет подписки";
+
/* copied message info in history */
"no text" = "нет текста";
@@ -4374,7 +4381,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Relay сервер защищает Ваш IP адрес, но может отслеживать продолжительность звонка.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Удалить";
/* No comment provided by engineer. */
@@ -4389,7 +4396,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Удалить члена группы";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Удалить члена группы?";
/* No comment provided by engineer. */
@@ -5545,7 +5552,7 @@ report reason */
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Чтобы показать Ваш скрытый профиль, введите его пароль в поле поиска на странице **Ваши профили чата**.";
/* No comment provided by engineer. */
-"To send" = "Для оправки";
+"To send" = "Для отправки";
/* alert message */
"To send commands you must be connected." = "Вы должны быть соединены, чтобы отправлять команды.";
@@ -5583,6 +5590,9 @@ report reason */
/* No comment provided by engineer. */
"Transport sessions" = "Транспортные сессии";
+/* subscription status explanation */
+"Trying to connect to the server used to receive messages from this connection." = "Попытка подключиться к серверу, используемому для получения сообщений от этого соединения.";
+
/* No comment provided by engineer. */
"Turkish interface" = "Турецкий интерфейс";
@@ -6075,9 +6085,15 @@ report reason */
/* new chat sheet title */
"You are already joining the group!\nRepeat join request?" = "Вы уже вступаете в группу!\nПовторить запрос на вступление?";
+/* subscription status explanation */
+"You are connected to the server used to receive messages from this connection." = "Вы подключены к серверу, используемому для приема сообщений от этого соединения.";
+
/* No comment provided by engineer. */
"You are invited to group" = "Вы приглашены в группу";
+/* subscription status explanation */
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "Вы не подключены к серверу, используемому для получения сообщений по этому соединению (нет подписки).";
+
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "Вы не подключены к этим серверам. Для доставки сообщений на них используется конфиденциальная доставка.";
diff --git a/apps/ios/spec/README.md b/apps/ios/spec/README.md
new file mode 100644
index 0000000000..eca6103582
--- /dev/null
+++ b/apps/ios/spec/README.md
@@ -0,0 +1,74 @@
+# SimpleX Chat iOS -- Specification Overview
+
+> Technical specification suite for the SimpleX Chat iOS application. Each document provides bidirectional links to product documentation and source code.
+
+## Executive Summary
+
+The SimpleX Chat iOS app is a native SwiftUI frontend that communicates with a Haskell core library via C FFI. All chat logic, encryption, protocol handling, and database operations happen in the Haskell core (`chat_ctrl`). The iOS layer handles UI rendering, system integration (CallKit, Push Notifications, Background Tasks), local preferences, and theming. The app shares its database with a Notification Service Extension (NSE) for decrypting push payloads while the main app is inactive.
+
+## Dependency Graph
+
+```
+SimpleXApp (root entry point)
+├── ChatModel (ObservableObject state) <-> SimpleXAPI (FFI bridge) <-> Haskell Core (chat_ctrl)
+├── Views (SwiftUI)
+│ ├── ChatListView -> ChatView -> ComposeView
+│ ├── ChatItemView (renders individual messages)
+│ ├── Settings, UserProfiles, Onboarding
+│ └── ActiveCallView (WebRTC + CallKit)
+├── Models
+│ ├── ChatModel (global app state -- singleton)
+│ ├── ItemsModel (per-chat message list state -- singleton + secondary instances)
+│ ├── ChatTagsModel (tag filtering state)
+│ └── Chat (per-conversation observable state)
+├── Services
+│ ├── NtfManager (push notification coordination)
+│ ├── BGManager (background task scheduling)
+│ ├── CallController (CallKit + VoIP push)
+│ └── ThemeManager (theme resolution engine)
+└── Extensions
+ ├── SimpleX NSE (Notification Service Extension -- decrypts push payloads)
+ └── SimpleX SE (Share Extension)
+```
+
+## Specification Documents
+
+| Document | Description |
+|----------|-------------|
+| [Architecture](architecture.md) | System architecture, FFI bridge, app lifecycle, extension model |
+| [Chat API Reference](api.md) | Complete ChatCommand, ChatResponse, ChatEvent, ChatError type reference |
+| [State Management](state.md) | ChatModel, ItemsModel, Chat, ChatInfo, preference storage |
+| [Database & Storage](database.md) | SQLite databases, encryption, file storage, export/import |
+| [Chat View](client/chat-view.md) | Message rendering, chat item types, context menu actions |
+| [Chat List](client/chat-list.md) | Conversation list, filtering, search, swipe actions |
+| [Message Composition](client/compose.md) | Compose bar, attachments, reply/edit/forward modes, voice recording |
+| [Navigation](client/navigation.md) | Navigation stack, deep linking, sheet presentation, call overlay |
+| [Push Notifications](services/notifications.md) | NtfManager, NSE, notification modes, token lifecycle |
+| [WebRTC Calling](services/calls.md) | CallController, WebRTCClient, CallKit, signaling via SMP |
+| [File Transfer](services/files.md) | Inline/XFTP transfer, auto-receive, CryptoFile, file constants |
+| [Theme Engine](services/theme.md) | ThemeManager, default themes, customization layers, wallpapers |
+| [Impact Graph](impact.md) | Source file → product concept mapping, risk levels |
+
+## Related Product Documentation
+
+- [Product Overview](../product/README.md)
+- [Concept Index](../product/concepts.md)
+- [Business Rules](../product/rules.md)
+- [Known Gaps](../product/gaps.md)
+- [Glossary](../product/glossary.md)
+- [Chat List View](../product/views/chat-list.md)
+- [Chat View](../product/views/chat.md)
+
+## Source Code Entry Points
+
+| File | Role |
+|------|------|
+| `Shared/SimpleXApp.swift` | App entry point, Haskell init, lifecycle management |
+| `Shared/AppDelegate.swift` | UIApplicationDelegate for push token registration |
+| `Shared/ContentView.swift` | Root view -- authentication gate, call overlay, navigation |
+| `Shared/Model/ChatModel.swift` | Primary observable state (ChatModel, ItemsModel, Chat) |
+| `Shared/Model/SimpleXAPI.swift` | FFI bridge -- chatSendCmd, chatApiSendCmd, sendSimpleXCmd |
+| `Shared/Model/AppAPITypes.swift` | ChatCommand, ChatResponse, ChatEvent enums (iOS app layer) |
+| `SimpleXChat/APITypes.swift` | APIResult, ChatError, ChatCmdProtocol (shared framework) |
+| `SimpleXChat/ChatTypes.swift` | User, ChatInfo, Contact, GroupInfo, ChatItem data types |
+| `SimpleXChat/SimpleX.h` | C header for Haskell FFI functions |
diff --git a/apps/ios/spec/api.md b/apps/ios/spec/api.md
new file mode 100644
index 0000000000..45a06c371f
--- /dev/null
+++ b/apps/ios/spec/api.md
@@ -0,0 +1,609 @@
+# SimpleX Chat iOS -- Chat API Reference
+
+> Complete specification of the ChatCommand, ChatResponse, ChatEvent, and ChatError types that form the API between the Swift UI layer and the Haskell core.
+>
+> Related specs: [Architecture](architecture.md) | [State Management](state.md) | [README](README.md)
+> Related product: [Concept Index](../product/concepts.md)
+
+**Source:** [`AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) | [`SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) | [`APITypes.swift`](../SimpleXChat/APITypes.swift) | [`API.swift`](../SimpleXChat/API.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Command Categories (ChatCommand)](#2-command-categories)
+3. [Response Types (ChatResponse)](#3-response-types)
+4. [Event Types (ChatEvent)](#4-event-types)
+5. [Error Types (ChatError)](#5-error-types)
+6. [FFI Bridge Functions](#6-ffi-bridge-functions)
+7. [Result Type (APIResult)](#7-result-type)
+
+---
+
+## 1. Overview
+
+The iOS app communicates with the Haskell core exclusively through a command/response protocol:
+
+1. Swift constructs a `ChatCommand` enum value
+2. The command's `cmdString` property serializes it to a text command
+3. The FFI bridge sends the string to Haskell via `chat_send_cmd_retry`
+4. Haskell returns a JSON response, decoded as `APIResult`
+5. Async events arrive separately via `chat_recv_msg_wait`, decoded as `ChatEvent`
+
+**Source files**:
+- [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift) -- `ChatCommand` ([L15](../Shared/Model/AppAPITypes.swift#L15)), `ChatResponse0` ([L657](../Shared/Model/AppAPITypes.swift#L657)), `ChatResponse1` ([L779](../Shared/Model/AppAPITypes.swift#L779)), `ChatResponse2` ([L919](../Shared/Model/AppAPITypes.swift#L919)), `ChatEvent` ([L1069](../Shared/Model/AppAPITypes.swift#L1069)) enums
+- [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift) -- `APIResult` ([L27](../SimpleXChat/APITypes.swift#L27)), `ChatAPIResult` ([L65](../SimpleXChat/APITypes.swift#L65)), `ChatError` ([L699](../SimpleXChat/APITypes.swift#L699))
+- [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) -- FFI bridge functions (`chatSendCmd` [L121](../Shared/Model/SimpleXAPI.swift#L121), `chatRecvMsg` [L237](../Shared/Model/SimpleXAPI.swift#L237))
+- [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) -- Low-level FFI (`sendSimpleXCmd` [L115](../SimpleXChat/API.swift#L115), `recvSimpleXMsg` [L137](../SimpleXChat/API.swift#L137))
+- `SimpleXChat/ChatTypes.swift` -- Data types used in commands/responses (User, Contact, GroupInfo, ChatItem, etc.)
+- `../../src/Simplex/Chat/Controller.hs` -- Haskell controller (function `chat_send_cmd_retry`, `chat_recv_msg_wait`)
+
+---
+
+## 2. Command Categories
+
+The `ChatCommand` enum ([`AppAPITypes.swift` L15](../Shared/Model/AppAPITypes.swift#L15)) contains all commands the iOS app can send to the Haskell core. Commands are organized below by functional area.
+
+### 2.1 User Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `showActiveUser` | -- | Get current active user | [L16](../Shared/Model/AppAPITypes.swift#L16) |
+| `createActiveUser` | `profile: Profile?, pastTimestamp: Bool` | Create new user profile | [L17](../Shared/Model/AppAPITypes.swift#L17) |
+| `listUsers` | -- | List all user profiles | [L18](../Shared/Model/AppAPITypes.swift#L18) |
+| `apiSetActiveUser` | `userId: Int64, viewPwd: String?` | Switch active user | [L19](../Shared/Model/AppAPITypes.swift#L19) |
+| `apiHideUser` | `userId: Int64, viewPwd: String` | Hide user behind password | [L24](../Shared/Model/AppAPITypes.swift#L24) |
+| `apiUnhideUser` | `userId: Int64, viewPwd: String` | Unhide hidden user | [L25](../Shared/Model/AppAPITypes.swift#L25) |
+| `apiMuteUser` | `userId: Int64` | Mute notifications for user | [L26](../Shared/Model/AppAPITypes.swift#L26) |
+| `apiUnmuteUser` | `userId: Int64` | Unmute notifications for user | [L27](../Shared/Model/AppAPITypes.swift#L27) |
+| `apiDeleteUser` | `userId: Int64, delSMPQueues: Bool, viewPwd: String?` | Delete user profile | [L28](../Shared/Model/AppAPITypes.swift#L28) |
+| `apiUpdateProfile` | `userId: Int64, profile: Profile` | Update user display name/image | [L141](../Shared/Model/AppAPITypes.swift#L141) |
+| `setAllContactReceipts` | `enable: Bool` | Set delivery receipts for all contacts | [L20](../Shared/Model/AppAPITypes.swift#L20) |
+| `apiSetUserContactReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user contact receipt settings | [L21](../Shared/Model/AppAPITypes.swift#L21) |
+| `apiSetUserGroupReceipts` | `userId: Int64, userMsgReceiptSettings` | Per-user group receipt settings | [L22](../Shared/Model/AppAPITypes.swift#L22) |
+| `apiSetUserAutoAcceptMemberContacts` | `userId: Int64, enable: Bool` | Auto-accept group member contacts | [L23](../Shared/Model/AppAPITypes.swift#L23) |
+
+### 2.2 Chat Lifecycle Control
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `startChat` | `mainApp: Bool, enableSndFiles: Bool` | Start chat engine | [L29](../Shared/Model/AppAPITypes.swift#L29) |
+| `checkChatRunning` | -- | Check if chat is running | [L30](../Shared/Model/AppAPITypes.swift#L30) |
+| `apiStopChat` | -- | Stop chat engine | [L31](../Shared/Model/AppAPITypes.swift#L31) |
+| `apiActivateChat` | `restoreChat: Bool` | Resume from background | [L32](../Shared/Model/AppAPITypes.swift#L32) |
+| `apiSuspendChat` | `timeoutMicroseconds: Int` | Suspend for background | [L33](../Shared/Model/AppAPITypes.swift#L33) |
+| `apiSetAppFilePaths` | `filesFolder, tempFolder, assetsFolder` | Set file storage paths | [L34](../Shared/Model/AppAPITypes.swift#L34) |
+| `apiSetEncryptLocalFiles` | `enable: Bool` | Toggle local file encryption | [L35](../Shared/Model/AppAPITypes.swift#L35) |
+
+### 2.3 Chat & Message Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetChats` | `userId: Int64` | Get all chat previews for user | [L44](../Shared/Model/AppAPITypes.swift#L44) |
+| `apiGetChat` | `chatId, scope, contentTag, pagination, search` | Get messages for a chat | [L45](../Shared/Model/AppAPITypes.swift#L45) |
+| `apiGetChatContentTypes` | `chatId, scope` | Get content type counts for a chat | [L46](../Shared/Model/AppAPITypes.swift#L46) |
+| `apiGetChatItemInfo` | `type, id, scope, itemId` | Get detailed info for a message | [L47](../Shared/Model/AppAPITypes.swift#L47) |
+| `apiSendMessages` | `type, id, scope, sendAsGroup, live, ttl, composedMessages` | Send one or more messages; `sendAsGroup` sends as channel owner | [L48](../Shared/Model/AppAPITypes.swift#L48) |
+| `apiCreateChatItems` | `noteFolderId, composedMessages` | Create items in notes folder | [L54](../Shared/Model/AppAPITypes.swift#L54) |
+| `apiUpdateChatItem` | `type, id, scope, itemId, updatedMessage, live` | Edit a sent message | [L56](../Shared/Model/AppAPITypes.swift#L56) |
+| `apiDeleteChatItem` | `type, id, scope, itemIds, mode` | Delete messages | [L57](../Shared/Model/AppAPITypes.swift#L57) |
+| `apiDeleteMemberChatItem` | `groupId, itemIds` | Moderate group messages | [L58](../Shared/Model/AppAPITypes.swift#L58) |
+| `apiChatItemReaction` | `type, id, scope, itemId, add, reaction` | Add/remove emoji reaction | [L61](../Shared/Model/AppAPITypes.swift#L61) |
+| `apiGetReactionMembers` | `userId, groupId, itemId, reaction` | Get who reacted | [L62](../Shared/Model/AppAPITypes.swift#L62) |
+| `apiPlanForwardChatItems` | `fromChatType, fromChatId, fromScope, itemIds` | Plan message forwarding | [L63](../Shared/Model/AppAPITypes.swift#L63) |
+| `apiForwardChatItems` | `toChatType, toChatId, toScope, sendAsGroup, from..., itemIds, ttl` | Forward messages; `sendAsGroup` forwards as channel owner | [L64](../Shared/Model/AppAPITypes.swift#L64) |
+| `apiReportMessage` | `groupId, chatItemId, reportReason, reportText` | Report group message | [L55](../Shared/Model/AppAPITypes.swift#L55) |
+| `apiChatRead` | `type, id, scope` | Mark entire chat as read | [L166](../Shared/Model/AppAPITypes.swift#L166) |
+| `apiChatItemsRead` | `type, id, scope, itemIds` | Mark specific items as read | [L167](../Shared/Model/AppAPITypes.swift#L167) |
+| `apiChatUnread` | `type, id, unreadChat` | Toggle unread badge | [L168](../Shared/Model/AppAPITypes.swift#L168) |
+
+### 2.4 Contact Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiAddContact` | `userId, incognito` | Create invitation link | [L126](../Shared/Model/AppAPITypes.swift#L126) |
+| `apiConnect` | `userId, incognito, connLink` | Connect via link | [L136](../Shared/Model/AppAPITypes.swift#L136) |
+| `apiConnectPlan` | `userId, connLink` | Plan connection (preview) | [L129](../Shared/Model/AppAPITypes.swift#L129) |
+| `apiPrepareContact` | `userId, connLink, contactShortLinkData` | Prepare contact from link | [L130](../Shared/Model/AppAPITypes.swift#L130) |
+| `apiPrepareGroup` | `userId, connLink, directLink, groupShortLinkData` | Prepare group from link; `directLink` (required, no default) indicates whether link is a direct (non-relay) group link | [L131](../Shared/Model/AppAPITypes.swift#L131) |
+| `apiConnectPreparedContact` | `contactId, incognito, msg` | Connect prepared contact | [L134](../Shared/Model/AppAPITypes.swift#L134) |
+| `apiConnectPreparedGroup` | `groupId, incognito, msg` | Connect to a prepared group/channel; returns `(GroupInfo, [RelayConnectionResult])?` | [L135](../Shared/Model/AppAPITypes.swift#L135) |
+| `apiConnectContactViaAddress` | `userId, incognito, contactId` | Connect via address | [L137](../Shared/Model/AppAPITypes.swift#L137) |
+| `apiAcceptContact` | `incognito, contactReqId` | Accept contact request | [L154](../Shared/Model/AppAPITypes.swift#L154) |
+| `apiRejectContact` | `contactReqId` | Reject contact request | [L155](../Shared/Model/AppAPITypes.swift#L155) |
+| `apiDeleteChat` | `type, id, chatDeleteMode` | Delete conversation | [L138](../Shared/Model/AppAPITypes.swift#L138) |
+| `apiClearChat` | `type, id` | Clear conversation history | [L139](../Shared/Model/AppAPITypes.swift#L139) |
+| `apiListContacts` | `userId` | List all contacts | [L140](../Shared/Model/AppAPITypes.swift#L140) |
+| `apiSetContactPrefs` | `contactId, preferences` | Set contact preferences | [L142](../Shared/Model/AppAPITypes.swift#L142) |
+| `apiSetContactAlias` | `contactId, localAlias` | Set local alias | [L143](../Shared/Model/AppAPITypes.swift#L143) |
+| `apiSetConnectionAlias` | `connId, localAlias` | Set pending connection alias | [L145](../Shared/Model/AppAPITypes.swift#L145) |
+| `apiContactInfo` | `contactId` | Get contact info + connection stats | [L112](../Shared/Model/AppAPITypes.swift#L112) |
+| `apiSetConnectionIncognito` | `connId, incognito` | Toggle incognito on pending connection | [L127](../Shared/Model/AppAPITypes.swift#L127) |
+
+### 2.5 Group Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiNewGroup` | `userId, incognito, groupProfile` | Create new group | [L72](../Shared/Model/AppAPITypes.swift#L72) |
+| `apiNewPublicGroup` | `userId, incognito, relayIds, groupProfile` | Create new public group (channel) with chat relays | [L73](../Shared/Model/AppAPITypes.swift#L73) |
+| `apiGetGroupRelays` | `groupId` | Get group relay list with status (owner only) | [L74](../Shared/Model/AppAPITypes.swift#L74) |
+| `apiAddMember` | `groupId, contactId, memberRole` | Invite contact to group | [L75](../Shared/Model/AppAPITypes.swift#L75) |
+| `apiJoinGroup` | `groupId` | Accept group invitation | [L76](../Shared/Model/AppAPITypes.swift#L76) |
+| `apiAcceptMember` | `groupId, groupMemberId, memberRole` | Accept member (knocking) | [L77](../Shared/Model/AppAPITypes.swift#L77) |
+| `apiRemoveMembers` | `groupId, memberIds, withMessages` | Remove members | [L81](../Shared/Model/AppAPITypes.swift#L81) |
+| `apiLeaveGroup` | `groupId` | Leave group | [L82](../Shared/Model/AppAPITypes.swift#L82) |
+| `apiListMembers` | `groupId` | List group members | [L83](../Shared/Model/AppAPITypes.swift#L83) |
+| `apiUpdateGroupProfile` | `groupId, groupProfile` | Update group name/image/description | [L84](../Shared/Model/AppAPITypes.swift#L84) |
+| `apiMembersRole` | `groupId, memberIds, memberRole` | Change member roles | [L79](../Shared/Model/AppAPITypes.swift#L79) |
+| `apiBlockMembersForAll` | `groupId, memberIds, blocked` | Block members for all | [L80](../Shared/Model/AppAPITypes.swift#L80) |
+| `apiCreateGroupLink` | `groupId, memberRole` | Create shareable group link | [L85](../Shared/Model/AppAPITypes.swift#L85) |
+| `apiGroupLinkMemberRole` | `groupId, memberRole` | Change group link default role | [L86](../Shared/Model/AppAPITypes.swift#L86) |
+| `apiDeleteGroupLink` | `groupId` | Delete group link | [L87](../Shared/Model/AppAPITypes.swift#L87) |
+| `apiGetGroupLink` | `groupId` | Get existing group link | [L88](../Shared/Model/AppAPITypes.swift#L88) |
+| `apiAddGroupShortLink` | `groupId` | Add short link to group | [L89](../Shared/Model/AppAPITypes.swift#L89) |
+| `apiCreateMemberContact` | `groupId, groupMemberId` | Create direct contact from group member | [L90](../Shared/Model/AppAPITypes.swift#L90) |
+| `apiSendMemberContactInvitation` | `contactId, msg` | Send contact invitation to member | [L91](../Shared/Model/AppAPITypes.swift#L91) |
+| `apiGroupMemberInfo` | `groupId, groupMemberId` | Get member info + connection stats | [L113](../Shared/Model/AppAPITypes.swift#L113) |
+| `apiDeleteMemberSupportChat` | `groupId, groupMemberId` | Delete member support chat | [L78](../Shared/Model/AppAPITypes.swift#L78) |
+| `apiSetMemberSettings` | `groupId, groupMemberId, memberSettings` | Set per-member settings | [L111](../Shared/Model/AppAPITypes.swift#L111) |
+| `apiSetGroupAlias` | `groupId, localAlias` | Set local group alias | [L144](../Shared/Model/AppAPITypes.swift#L144) |
+
+### 2.6 Chat Tags
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetChatTags` | `userId` | Get all user tags | [L43](../Shared/Model/AppAPITypes.swift#L43) |
+| `apiCreateChatTag` | `tag: ChatTagData` | Create a new tag | [L49](../Shared/Model/AppAPITypes.swift#L49) |
+| `apiSetChatTags` | `type, id, tagIds` | Assign tags to a chat | [L50](../Shared/Model/AppAPITypes.swift#L50) |
+| `apiDeleteChatTag` | `tagId` | Delete a tag | [L51](../Shared/Model/AppAPITypes.swift#L51) |
+| `apiUpdateChatTag` | `tagId, tagData` | Update tag name/emoji | [L52](../Shared/Model/AppAPITypes.swift#L52) |
+| `apiReorderChatTags` | `tagIds` | Reorder tags | [L53](../Shared/Model/AppAPITypes.swift#L53) |
+
+### 2.7 File Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `receiveFile` | `fileId, userApprovedRelays, encrypted, inline` | Accept and download file | [L169](../Shared/Model/AppAPITypes.swift#L169) |
+| `setFileToReceive` | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive | [L170](../Shared/Model/AppAPITypes.swift#L170) |
+| `cancelFile` | `fileId` | Cancel file transfer | [L171](../Shared/Model/AppAPITypes.swift#L171) |
+| `apiUploadStandaloneFile` | `userId, file: CryptoFile` | Upload file to XFTP (no chat) | [L181](../Shared/Model/AppAPITypes.swift#L181) |
+| `apiDownloadStandaloneFile` | `userId, url, file: CryptoFile` | Download from XFTP URL | [L182](../Shared/Model/AppAPITypes.swift#L182) |
+| `apiStandaloneFileInfo` | `url` | Get file metadata from XFTP URL | [L183](../Shared/Model/AppAPITypes.swift#L183) |
+
+### 2.8 WebRTC Call Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSendCallInvitation` | `contact, callType` | Initiate call | [L157](../Shared/Model/AppAPITypes.swift#L157) |
+| `apiRejectCall` | `contact` | Reject incoming call | [L158](../Shared/Model/AppAPITypes.swift#L158) |
+| `apiSendCallOffer` | `contact, callOffer: WebRTCCallOffer` | Send SDP offer | [L159](../Shared/Model/AppAPITypes.swift#L159) |
+| `apiSendCallAnswer` | `contact, answer: WebRTCSession` | Send SDP answer | [L160](../Shared/Model/AppAPITypes.swift#L160) |
+| `apiSendCallExtraInfo` | `contact, extraInfo: WebRTCExtraInfo` | Send ICE candidates | [L161](../Shared/Model/AppAPITypes.swift#L161) |
+| `apiEndCall` | `contact` | End active call | [L162](../Shared/Model/AppAPITypes.swift#L162) |
+| `apiGetCallInvitations` | -- | Get pending call invitations | [L163](../Shared/Model/AppAPITypes.swift#L163) |
+| `apiCallStatus` | `contact, callStatus` | Report call status change | [L164](../Shared/Model/AppAPITypes.swift#L164) |
+
+### 2.9 Push Notifications
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetNtfToken` | -- | Get current notification token | [L65](../Shared/Model/AppAPITypes.swift#L65) |
+| `apiRegisterToken` | `token, notificationMode` | Register device token with server | [L66](../Shared/Model/AppAPITypes.swift#L66) |
+| `apiVerifyToken` | `token, nonce, code` | Verify token registration | [L67](../Shared/Model/AppAPITypes.swift#L67) |
+| `apiCheckToken` | `token` | Check token status | [L68](../Shared/Model/AppAPITypes.swift#L68) |
+| `apiDeleteToken` | `token` | Unregister token | [L69](../Shared/Model/AppAPITypes.swift#L69) |
+| `apiGetNtfConns` | `nonce, encNtfInfo` | Get notification connections (NSE) | [L70](../Shared/Model/AppAPITypes.swift#L70) |
+| `apiGetConnNtfMessages` | `connMsgReqs` | Get notification messages (NSE) | [L71](../Shared/Model/AppAPITypes.swift#L71) |
+
+### 2.10 Settings & Configuration
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSaveSettings` | `settings: AppSettings` | Save app settings to core | [L41](../Shared/Model/AppAPITypes.swift#L41) |
+| `apiGetSettings` | `settings: AppSettings` | Get settings from core | [L42](../Shared/Model/AppAPITypes.swift#L42) |
+| `apiSetChatSettings` | `type, id, chatSettings` | Per-chat notification settings | [L110](../Shared/Model/AppAPITypes.swift#L110) |
+| `apiSetChatItemTTL` | `userId, seconds` | Set global message TTL | [L102](../Shared/Model/AppAPITypes.swift#L102) |
+| `apiGetChatItemTTL` | `userId` | Get global message TTL | [L103](../Shared/Model/AppAPITypes.swift#L103) |
+| `apiSetChatTTL` | `userId, type, id, seconds` | Per-chat message TTL | [L104](../Shared/Model/AppAPITypes.swift#L104) |
+| `apiSetNetworkConfig` | `networkConfig: NetCfg` | Set network configuration | [L105](../Shared/Model/AppAPITypes.swift#L105) |
+| `apiGetNetworkConfig` | -- | Get network configuration | [L106](../Shared/Model/AppAPITypes.swift#L106) |
+| `apiSetNetworkInfo` | `networkInfo: UserNetworkInfo` | Set network type/status | [L107](../Shared/Model/AppAPITypes.swift#L107) |
+| `reconnectAllServers` | -- | Force reconnect all servers | [L108](../Shared/Model/AppAPITypes.swift#L108) |
+| `reconnectServer` | `userId, smpServer` | Reconnect specific server | [L109](../Shared/Model/AppAPITypes.swift#L109) |
+
+### 2.11 Database & Storage
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiStorageEncryption` | `config: DBEncryptionConfig` | Set/change database encryption | [L39](../Shared/Model/AppAPITypes.swift#L39) |
+| `testStorageEncryption` | `key: String` | Test encryption key | [L40](../Shared/Model/AppAPITypes.swift#L40) |
+| `apiExportArchive` | `config: ArchiveConfig` | Export database archive | [L36](../Shared/Model/AppAPITypes.swift#L36) |
+| `apiImportArchive` | `config: ArchiveConfig` | Import database archive | [L37](../Shared/Model/AppAPITypes.swift#L37) |
+| `apiDeleteStorage` | -- | Delete all storage | [L38](../Shared/Model/AppAPITypes.swift#L38) |
+
+### 2.12 Server Operations
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetServerOperators` | -- | Get server operators | [L94](../Shared/Model/AppAPITypes.swift#L94) |
+| `apiSetServerOperators` | `operators` | Set server operators | [L95](../Shared/Model/AppAPITypes.swift#L95) |
+| `apiGetUserServers` | `userId` | Get user's configured servers | [L96](../Shared/Model/AppAPITypes.swift#L96) |
+| `apiSetUserServers` | `userId, userServers` | Set user's servers | [L97](../Shared/Model/AppAPITypes.swift#L97) |
+| `apiValidateServers` | `userId, userServers` | Validate server configuration; returns errors and warnings | [L98](../Shared/Model/AppAPITypes.swift#L98) |
+| `apiGetUsageConditions` | -- | Get usage conditions | [L99](../Shared/Model/AppAPITypes.swift#L99) |
+| `apiAcceptConditions` | `conditionsId, operatorIds` | Accept usage conditions | [L101](../Shared/Model/AppAPITypes.swift#L101) |
+| `apiTestProtoServer` | `userId, server` | Test server connectivity | [L93](../Shared/Model/AppAPITypes.swift#L93) |
+
+### 2.13 Theme & UI
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiSetUserUIThemes` | `userId, themes: ThemeModeOverrides?` | Set per-user theme | [L146](../Shared/Model/AppAPITypes.swift#L146) |
+| `apiSetChatUIThemes` | `chatId, themes: ThemeModeOverrides?` | Set per-chat theme | [L147](../Shared/Model/AppAPITypes.swift#L147) |
+
+### 2.14 Remote Desktop
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `setLocalDeviceName` | `displayName` | Set device name for pairing | [L173](../Shared/Model/AppAPITypes.swift#L173) |
+| `connectRemoteCtrl` | `xrcpInvitation` | Connect to desktop via QR code | [L174](../Shared/Model/AppAPITypes.swift#L174) |
+| `findKnownRemoteCtrl` | -- | Find previously paired desktops | [L175](../Shared/Model/AppAPITypes.swift#L175) |
+| `confirmRemoteCtrl` | `remoteCtrlId` | Confirm known remote controller | [L176](../Shared/Model/AppAPITypes.swift#L176) |
+| `verifyRemoteCtrlSession` | `sessionCode` | Verify session code | [L177](../Shared/Model/AppAPITypes.swift#L177) |
+| `listRemoteCtrls` | -- | List known remote controllers | [L178](../Shared/Model/AppAPITypes.swift#L178) |
+| `stopRemoteCtrl` | -- | Stop remote session | [L179](../Shared/Model/AppAPITypes.swift#L179) |
+| `deleteRemoteCtrl` | `remoteCtrlId` | Delete known controller | [L180](../Shared/Model/AppAPITypes.swift#L180) |
+
+### 2.15 Diagnostics
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `showVersion` | -- | Get core version info | [L185](../Shared/Model/AppAPITypes.swift#L185) |
+| `getAgentSubsTotal` | `userId` | Get total SMP subscriptions | [L186](../Shared/Model/AppAPITypes.swift#L186) |
+| `getAgentServersSummary` | `userId` | Get server summary stats | [L187](../Shared/Model/AppAPITypes.swift#L187) |
+| `resetAgentServersStats` | -- | Reset server statistics | [L188](../Shared/Model/AppAPITypes.swift#L188) |
+
+### 2.16 Address Management
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiCreateMyAddress` | `userId` | Create SimpleX address | [L148](../Shared/Model/AppAPITypes.swift#L148) |
+| `apiDeleteMyAddress` | `userId` | Delete SimpleX address | [L149](../Shared/Model/AppAPITypes.swift#L149) |
+| `apiShowMyAddress` | `userId` | Show current address | [L150](../Shared/Model/AppAPITypes.swift#L150) |
+| `apiAddMyAddressShortLink` | `userId` | Add short link to address | [L151](../Shared/Model/AppAPITypes.swift#L151) |
+| `apiSetProfileAddress` | `userId, on: Bool` | Toggle address in profile | [L152](../Shared/Model/AppAPITypes.swift#L152) |
+| `apiSetAddressSettings` | `userId, addressSettings` | Configure address settings | [L153](../Shared/Model/AppAPITypes.swift#L153) |
+
+### 2.17 Connection Security
+
+| Command | Parameters | Description | Source |
+|---------|-----------|-------------|--------|
+| `apiGetContactCode` | `contactId` | Get verification code | [L122](../Shared/Model/AppAPITypes.swift#L122) |
+| `apiGetGroupMemberCode` | `groupId, groupMemberId` | Get member verification code | [L123](../Shared/Model/AppAPITypes.swift#L123) |
+| `apiVerifyContact` | `contactId, connectionCode` | Verify contact identity | [L124](../Shared/Model/AppAPITypes.swift#L124) |
+| `apiVerifyGroupMember` | `groupId, groupMemberId, connectionCode` | Verify group member identity | [L125](../Shared/Model/AppAPITypes.swift#L125) |
+| `apiSwitchContact` | `contactId` | Switch contact connection (key rotation) | [L116](../Shared/Model/AppAPITypes.swift#L116) |
+| `apiSwitchGroupMember` | `groupId, groupMemberId` | Switch group member connection | [L117](../Shared/Model/AppAPITypes.swift#L117) |
+| `apiAbortSwitchContact` | `contactId` | Abort contact switch | [L118](../Shared/Model/AppAPITypes.swift#L118) |
+| `apiAbortSwitchGroupMember` | `groupId, groupMemberId` | Abort member switch | [L119](../Shared/Model/AppAPITypes.swift#L119) |
+| `apiSyncContactRatchet` | `contactId, force` | Sync double-ratchet state | [L120](../Shared/Model/AppAPITypes.swift#L120) |
+| `apiSyncGroupMemberRatchet` | `groupId, groupMemberId, force` | Sync member ratchet | [L121](../Shared/Model/AppAPITypes.swift#L121) |
+
+---
+
+## 3. Response Types
+
+Responses are split across three enums due to Swift enum size limitations:
+
+### ChatResponse0
+
+Synchronous query responses ([`AppAPITypes.swift` L657](../Shared/Model/AppAPITypes.swift#L657)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `activeUser` | `user: User` | Current active user | [L658](../Shared/Model/AppAPITypes.swift#L658) |
+| `usersList` | `users: [UserInfo]` | All user profiles | [L659](../Shared/Model/AppAPITypes.swift#L659) |
+| `chatStarted` | -- | Chat engine started | [L660](../Shared/Model/AppAPITypes.swift#L660) |
+| `chatRunning` | -- | Chat is already running | [L661](../Shared/Model/AppAPITypes.swift#L661) |
+| `chatStopped` | -- | Chat engine stopped | [L662](../Shared/Model/AppAPITypes.swift#L662) |
+| `apiChats` | `user, chats: [ChatData]` | All chat previews | [L663](../Shared/Model/AppAPITypes.swift#L663) |
+| `apiChat` | `user, chat: ChatData, navInfo` | Single chat with messages | [L664](../Shared/Model/AppAPITypes.swift#L664) |
+| `chatTags` | `user, userTags: [ChatTag]` | User's chat tags | [L666](../Shared/Model/AppAPITypes.swift#L666) |
+| `chatItemInfo` | `user, chatItem, chatItemInfo` | Message detail info | [L667](../Shared/Model/AppAPITypes.swift#L667) |
+| `serverTestResult` | `user, testServer, testFailure` | Server test result | [L668](../Shared/Model/AppAPITypes.swift#L668) |
+| `userServersValidation` | `user, serverErrors: [UserServersError], serverWarnings: [UserServersWarning]` | Server validation result with errors and warnings | [L671](../Shared/Model/AppAPITypes.swift#L671) |
+| `networkConfig` | `networkConfig: NetCfg` | Current network config | [L674](../Shared/Model/AppAPITypes.swift#L674) |
+| `contactInfo` | `user, contact, connectionStats, customUserProfile` | Contact details | [L675](../Shared/Model/AppAPITypes.swift#L675) |
+| `groupMemberInfo` | `user, groupInfo, member, connectionStats` | Member details | [L676](../Shared/Model/AppAPITypes.swift#L676) |
+| `connectionVerified` | `verified, expectedCode` | Verification result | [L686](../Shared/Model/AppAPITypes.swift#L686) |
+| `tagsUpdated` | `user, userTags, chatTags` | Tags changed | [L687](../Shared/Model/AppAPITypes.swift#L687) |
+
+### ChatResponse1
+
+Contact, message, and profile responses ([`AppAPITypes.swift` L779](../Shared/Model/AppAPITypes.swift#L779)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `invitation` | `user, connLinkInvitation, connection` | Created invitation link | [L780](../Shared/Model/AppAPITypes.swift#L780) |
+| `connectionPlan` | `user, connLink, connectionPlan` | Connection plan preview | [L783](../Shared/Model/AppAPITypes.swift#L783) |
+| `newPreparedChat` | `user, chat: ChatData` | Prepared contact/group | [L784](../Shared/Model/AppAPITypes.swift#L784) |
+| `startedConnectionToGroup` | `user, groupInfo, relayResults: [RelayConnectionResult]` | Group/channel join initiated; relay results indicate per-relay connection success/failure | [L790](../Shared/Model/AppAPITypes.swift#L790) |
+| `contactDeleted` | `user, contact` | Contact deleted | [L793](../Shared/Model/AppAPITypes.swift#L793) |
+| `newChatItems` | `user, chatItems: [AChatItem]` | New messages sent/received | [L811](../Shared/Model/AppAPITypes.swift#L811) |
+| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited | [L814](../Shared/Model/AppAPITypes.swift#L814) |
+| `chatItemReaction` | `user, added, reaction` | Reaction change | [L816](../Shared/Model/AppAPITypes.swift#L816) |
+| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L818](../Shared/Model/AppAPITypes.swift#L818) |
+| `contactsList` | `user, contacts: [Contact]` | All contacts list | [L819](../Shared/Model/AppAPITypes.swift#L819) |
+| `userProfileUpdated` | `user, fromProfile, toProfile` | Profile changed | [L799](../Shared/Model/AppAPITypes.swift#L799) |
+| `userContactLinkCreated` | `user, connLinkContact` | Address created | [L807](../Shared/Model/AppAPITypes.swift#L807) |
+| `forwardPlan` | `user, chatItemIds, forwardConfirmation` | Forward plan result | [L813](../Shared/Model/AppAPITypes.swift#L813) |
+| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L812](../Shared/Model/AppAPITypes.swift#L812) |
+
+### ChatResponse2
+
+Group, file, call, notification, and misc responses ([`AppAPITypes.swift` L919](../Shared/Model/AppAPITypes.swift#L919)):
+
+| Response | Key Fields | Description | Source |
+|----------|-----------|-------------|--------|
+| `groupCreated` | `user, groupInfo` | New group created | [L921](../Shared/Model/AppAPITypes.swift#L921) |
+| `publicGroupCreated` | `user, groupInfo, groupLink, groupRelays: [GroupRelay]` | New public group (channel) created with relay info | [L922](../Shared/Model/AppAPITypes.swift#L922) |
+| `groupRelays` | `user, groupInfo, groupRelays: [GroupRelay]` | Group relay list (owner API response) | [L923](../Shared/Model/AppAPITypes.swift#L923) |
+| `sentGroupInvitation` | `user, groupInfo, contact, member` | Group invitation sent | [L924](../Shared/Model/AppAPITypes.swift#L924) |
+| `groupMembers` | `user, group: Group` | Group member list | [L928](../Shared/Model/AppAPITypes.swift#L928) |
+| `membersRoleUser` | `user, groupInfo, members, toRole` | Role changed | [L932](../Shared/Model/AppAPITypes.swift#L932) |
+| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile updated | [L934](../Shared/Model/AppAPITypes.swift#L934) |
+| `groupLinkCreated` | `user, groupInfo, groupLink` | Group link created | [L935](../Shared/Model/AppAPITypes.swift#L935) |
+| `rcvFileAccepted` | `user, chatItem` | File download started | [L942](../Shared/Model/AppAPITypes.swift#L942) |
+| `callInvitations` | `callInvitations: [RcvCallInvitation]` | Pending calls | [L951](../Shared/Model/AppAPITypes.swift#L951) |
+| `ntfToken` | `token, status, ntfMode, ntfServer` | Notification token info | [L954](../Shared/Model/AppAPITypes.swift#L954) |
+| `versionInfo` | `versionInfo, chatMigrations, agentMigrations` | Core version | [L962](../Shared/Model/AppAPITypes.swift#L962) |
+| `cmdOk` | `user_` | Generic success | [L963](../Shared/Model/AppAPITypes.swift#L963) |
+| `archiveExported` | `archiveErrors: [ArchiveError]` | Export result | [L967](../Shared/Model/AppAPITypes.swift#L967) |
+| `archiveImported` | `archiveErrors: [ArchiveError]` | Import result | [L968](../Shared/Model/AppAPITypes.swift#L968) |
+| `appSettings` | `appSettings: AppSettings` | Retrieved settings | [L969](../Shared/Model/AppAPITypes.swift#L969) |
+
+---
+
+## 4. Event Types
+
+The `ChatEvent` enum ([`AppAPITypes.swift` L1069](../Shared/Model/AppAPITypes.swift#L1069)) represents async events from the Haskell core. These arrive via `chat_recv_msg_wait` polling, not as responses to commands.
+
+Event processing entry point: [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2282) in `SimpleXAPI.swift`.
+
+### Connection Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `contactConnected` | `user, contact, userCustomProfile` | Contact connection established | [L1076](../Shared/Model/AppAPITypes.swift#L1076) |
+| `contactConnecting` | `user, contact` | Contact connecting in progress | [L1077](../Shared/Model/AppAPITypes.swift#L1077) |
+| `contactSndReady` | `user, contact` | Ready to send to contact | [L1078](../Shared/Model/AppAPITypes.swift#L1078) |
+| `contactDeletedByContact` | `user, contact` | Contact deleted by other party | [L1075](../Shared/Model/AppAPITypes.swift#L1075) |
+| `contactUpdated` | `user, toContact` | Contact profile updated | [L1080](../Shared/Model/AppAPITypes.swift#L1080) |
+| `receivedContactRequest` | `user, contactRequest, chat_` | Incoming contact request | [L1079](../Shared/Model/AppAPITypes.swift#L1079) |
+| `subscriptionStatus` | `subscriptionStatus, connections` | Connection subscription change | [L1082](../Shared/Model/AppAPITypes.swift#L1082) |
+
+### Message Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `newChatItems` | `user, chatItems: [AChatItem]` | New messages received | [L1084](../Shared/Model/AppAPITypes.swift#L1084) |
+| `chatItemUpdated` | `user, chatItem: AChatItem` | Message edited remotely | [L1086](../Shared/Model/AppAPITypes.swift#L1086) |
+| `chatItemReaction` | `user, added, reaction: ACIReaction` | Reaction added/removed | [L1087](../Shared/Model/AppAPITypes.swift#L1087) |
+| `chatItemsDeleted` | `user, chatItemDeletions, byUser` | Messages deleted | [L1088](../Shared/Model/AppAPITypes.swift#L1088) |
+| `chatItemsStatusesUpdated` | `user, chatItems: [AChatItem]` | Delivery status changed | [L1085](../Shared/Model/AppAPITypes.swift#L1085) |
+| `groupChatItemsDeleted` | `user, groupInfo, chatItemIDs, byUser, member_` | Group items deleted | [L1090](../Shared/Model/AppAPITypes.swift#L1090) |
+| `chatInfoUpdated` | `user, chatInfo` | Chat metadata changed | [L1083](../Shared/Model/AppAPITypes.swift#L1083) |
+
+### Group Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `receivedGroupInvitation` | `user, groupInfo, contact, memberRole` | Group invitation received | [L1091](../Shared/Model/AppAPITypes.swift#L1091) |
+| `userAcceptedGroupSent` | `user, groupInfo, hostContact` | Joined group | [L1092](../Shared/Model/AppAPITypes.swift#L1092) |
+| `groupLinkConnecting` | `user, groupInfo, hostMember` | Connecting via group link | [L1093](../Shared/Model/AppAPITypes.swift#L1093) |
+| `joinedGroupMemberConnecting` | `user, groupInfo, hostMember, member` | Member joining | [L1095](../Shared/Model/AppAPITypes.swift#L1095) |
+| `memberRole` | `user, groupInfo, byMember, member, fromRole, toRole` | Role changed | [L1097](../Shared/Model/AppAPITypes.swift#L1097) |
+| `memberBlockedForAll` | `user, groupInfo, byMember, member, blocked` | Member blocked | [L1098](../Shared/Model/AppAPITypes.swift#L1098) |
+| `deletedMemberUser` | `user, groupInfo, member, withMessages` | Current user removed | [L1099](../Shared/Model/AppAPITypes.swift#L1099) |
+| `deletedMember` | `user, groupInfo, byMember, deletedMember` | Member removed | [L1100](../Shared/Model/AppAPITypes.swift#L1100) |
+| `leftMember` | `user, groupInfo, member` | Member left | [L1101](../Shared/Model/AppAPITypes.swift#L1101) |
+| `groupDeleted` | `user, groupInfo, member` | Group deleted | [L1102](../Shared/Model/AppAPITypes.swift#L1102) |
+| `userJoinedGroup` | `user, groupInfo, hostMember` | Successfully joined; `hostMember` is upserted into group members | [L1103](../Shared/Model/AppAPITypes.swift#L1103) |
+| `joinedGroupMember` | `user, groupInfo, member` | New member joined | [L1104](../Shared/Model/AppAPITypes.swift#L1104) |
+| `connectedToGroupMember` | `user, groupInfo, member, memberContact` | E2E session established with member | [L1105](../Shared/Model/AppAPITypes.swift#L1105) |
+| `groupUpdated` | `user, toGroup: GroupInfo` | Group profile changed | [L1106](../Shared/Model/AppAPITypes.swift#L1106) |
+| `groupLinkRelaysUpdated` | `user, groupInfo, groupLink, groupRelays: [GroupRelay]` | Channel relay configuration changed | [L1107](../Shared/Model/AppAPITypes.swift#L1107) |
+| `groupMemberUpdated` | `user, groupInfo, fromMember, toMember` | Member info updated | [L1081](../Shared/Model/AppAPITypes.swift#L1081) |
+
+### File Transfer Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `rcvFileStart` | `user, chatItem` | Download started | [L1112](../Shared/Model/AppAPITypes.swift#L1112) |
+| `rcvFileProgressXFTP` | `user, chatItem_, receivedSize, totalSize` | Download progress | [L1113](../Shared/Model/AppAPITypes.swift#L1113) |
+| `rcvFileComplete` | `user, chatItem` | Download complete | [L1114](../Shared/Model/AppAPITypes.swift#L1114) |
+| `rcvFileSndCancelled` | `user, chatItem, rcvFileTransfer` | Sender cancelled | [L1116](../Shared/Model/AppAPITypes.swift#L1116) |
+| `rcvFileError` | `user, chatItem_, agentError, rcvFileTransfer` | Download error | [L1117](../Shared/Model/AppAPITypes.swift#L1117) |
+| `sndFileStart` | `user, chatItem, sndFileTransfer` | Upload started | [L1120](../Shared/Model/AppAPITypes.swift#L1120) |
+| `sndFileComplete` | `user, chatItem, sndFileTransfer` | Upload complete (inline) | [L1121](../Shared/Model/AppAPITypes.swift#L1121) |
+| `sndFileProgressXFTP` | `user, chatItem_, fileTransferMeta, sentSize, totalSize` | Upload progress | [L1123](../Shared/Model/AppAPITypes.swift#L1123) |
+| `sndFileCompleteXFTP` | `user, chatItem, fileTransferMeta` | XFTP upload complete | [L1125](../Shared/Model/AppAPITypes.swift#L1125) |
+| `sndFileError` | `user, chatItem_, fileTransferMeta, errorMessage` | Upload error | [L1127](../Shared/Model/AppAPITypes.swift#L1127) |
+
+### Call Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `callInvitation` | `callInvitation: RcvCallInvitation` | Incoming call | [L1130](../Shared/Model/AppAPITypes.swift#L1130) |
+| `callOffer` | `user, contact, callType, offer, sharedKey, askConfirmation` | SDP offer received | [L1131](../Shared/Model/AppAPITypes.swift#L1131) |
+| `callAnswer` | `user, contact, answer` | SDP answer received | [L1132](../Shared/Model/AppAPITypes.swift#L1132) |
+| `callExtraInfo` | `user, contact, extraInfo` | ICE candidates received | [L1133](../Shared/Model/AppAPITypes.swift#L1133) |
+| `callEnded` | `user, contact` | Call ended by remote | [L1134](../Shared/Model/AppAPITypes.swift#L1134) |
+
+### Connection Security Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `contactSwitch` | `user, contact, switchProgress` | Key rotation progress | [L1071](../Shared/Model/AppAPITypes.swift#L1071) |
+| `groupMemberSwitch` | `user, groupInfo, member, switchProgress` | Member key rotation | [L1072](../Shared/Model/AppAPITypes.swift#L1072) |
+| `contactRatchetSync` | `user, contact, ratchetSyncProgress` | Ratchet sync progress | [L1073](../Shared/Model/AppAPITypes.swift#L1073) |
+| `groupMemberRatchetSync` | `user, groupInfo, member, ratchetSyncProgress` | Member ratchet sync | [L1074](../Shared/Model/AppAPITypes.swift#L1074) |
+
+### System Events
+
+| Event | Key Fields | Description | Source |
+|-------|-----------|-------------|--------|
+| `chatSuspended` | -- | Core suspended | [L1070](../Shared/Model/AppAPITypes.swift#L1070) |
+
+---
+
+## 5. Error Types
+
+Defined in [`SimpleXChat/APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699):
+
+```swift
+public enum ChatError: Decodable, Hashable, Error {
+ case error(errorType: ChatErrorType)
+ case errorAgent(agentError: AgentErrorType)
+ case errorStore(storeError: StoreError)
+ case errorDatabase(databaseError: DatabaseError)
+ case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError)
+ case invalidJSON(json: Data?)
+ case unexpectedResult(type: String)
+}
+```
+
+### Error Categories
+
+| Category | Enum | Description | Source |
+|----------|------|-------------|--------|
+| Chat logic | `ChatErrorType` | Business logic errors (e.g., invalid state, permission denied, `chatRelayExists`) | [`APITypes.swift` L722](../SimpleXChat/APITypes.swift#L722) |
+| SMP Agent | `AgentErrorType` | Protocol/network errors from the SMP agent layer | [`APITypes.swift` L884](../SimpleXChat/APITypes.swift#L884) |
+| Database store | `StoreError` | SQLite query/constraint errors (includes relay-related: `relayUserNotFound`, `duplicateMemberId`, `userChatRelayNotFound`, `groupRelayNotFound`, `groupRelayNotFoundByMemberId`) | [`APITypes.swift` L802](../SimpleXChat/APITypes.swift#L802) |
+| Database engine | `DatabaseError` | DB open/migration/encryption errors | [`APITypes.swift` L871](../SimpleXChat/APITypes.swift#L871) |
+| Remote control | `RemoteCtrlError` | Remote desktop session errors | [`APITypes.swift` L1054](../SimpleXChat/APITypes.swift#L1054) |
+| Parse failure | `invalidJSON` | Failed to decode response JSON | [`APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699) |
+| Unexpected | `unexpectedResult` | Response type does not match expected | [`APITypes.swift` L699](../SimpleXChat/APITypes.swift#L699) |
+
+---
+
+## 6. FFI Bridge Functions
+
+Defined in [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift):
+
+### Synchronous (blocking current thread)
+
+```swift
+// Throws on error, returns typed result
+func chatSendCmdSync( // SimpleXAPI.swift L93
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ log: Bool = true
+) throws -> R
+
+// Returns APIResult (caller handles error)
+func chatApiSendCmdSync( // SimpleXAPI.swift L99
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ retryNum: Int32 = 0,
+ log: Bool = true
+) -> APIResult
+```
+
+### Asynchronous (Swift concurrency)
+
+```swift
+// Throws on error, returns typed result
+func chatSendCmd( // SimpleXAPI.swift L121
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ ctrl: chat_ctrl? = nil,
+ log: Bool = true
+) async throws -> R
+
+// Returns APIResult with optional retry on network errors
+func chatApiSendCmdWithRetry( // SimpleXAPI.swift L127
+ _ cmd: ChatCommand,
+ bgTask: Bool = true,
+ bgDelay: Double? = nil,
+ inProgress: BoxedValue? = nil,
+ retryNum: Int32 = 0
+) async -> APIResult?
+```
+
+### Low-Level FFI
+
+```swift
+// Direct C FFI call -- serializes cmd.cmdString, calls chat_send_cmd_retry, decodes response
+public func sendSimpleXCmd( // API.swift L115
+ _ cmd: ChatCmdProtocol,
+ _ ctrl: chat_ctrl?,
+ retryNum: Int32 = 0
+) -> APIResult
+```
+
+### Event Receiver
+
+```swift
+// Polls for async events from the Haskell core
+func chatRecvMsg( // SimpleXAPI.swift L237
+ _ ctrl: chat_ctrl? = nil
+) async -> APIResult?
+
+// Processes a received event and updates app state
+func processReceivedMsg( // SimpleXAPI.swift L2282
+ _ res: ChatEvent
+) async
+```
+
+---
+
+## 7. Result Type
+
+Defined in [`SimpleXChat/APITypes.swift` L27](../SimpleXChat/APITypes.swift#L27):
+
+```swift
+public enum APIResult: Decodable where R: Decodable, R: ChatAPIResult {
+ case result(R) // Successful response
+ case error(ChatError) // Error response from core
+ case invalid(type: String, json: Data) // Undecodable response
+
+ public var responseType: String { ... }
+ public var unexpected: ChatError { ... }
+}
+
+public protocol ChatAPIResult: Decodable { // APITypes.swift L65
+ var responseType: String { get }
+ var details: String { get }
+ static func fallbackResult(_ type: String, _ json: NSDictionary) -> Self?
+}
+```
+
+The `decodeAPIResult` function ([`APITypes.swift` L86](../SimpleXChat/APITypes.swift#L86)) handles JSON decoding with fallback logic:
+1. Try standard `JSONDecoder.decode(APIResult.self, from: data)`
+2. If that fails, try manual JSON parsing via `JSONSerialization`
+3. Check for `"error"` key -- return `.error`
+4. Check for `"result"` key -- try `R.fallbackResult` or return `.invalid`
+5. Last resort: return `.invalid(type: "invalid", json: ...)`
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| ChatCommand enum | [`Shared/Model/AppAPITypes.swift` L15](../Shared/Model/AppAPITypes.swift#L15) |
+| ChatResponse0/1/2 enums | [`Shared/Model/AppAPITypes.swift` L657, L779, L919](../Shared/Model/AppAPITypes.swift#L657) |
+| ChatEvent enum | [`Shared/Model/AppAPITypes.swift` L1069](../Shared/Model/AppAPITypes.swift#L1069) |
+| APIResult, ChatError | [`SimpleXChat/APITypes.swift` L27, L699](../SimpleXChat/APITypes.swift#L27) |
+| FFI bridge functions | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift) |
+| Low-level FFI | [`SimpleXChat/API.swift`](../SimpleXChat/API.swift) |
+| Data types | `SimpleXChat/ChatTypes.swift` |
+| C header | `SimpleXChat/SimpleX.h` |
+| Haskell controller | `../../src/Simplex/Chat/Controller.hs` |
diff --git a/apps/ios/spec/architecture.md b/apps/ios/spec/architecture.md
new file mode 100644
index 0000000000..9ab3eb1fd2
--- /dev/null
+++ b/apps/ios/spec/architecture.md
@@ -0,0 +1,347 @@
+# SimpleX Chat iOS -- System Architecture
+
+> Technical specification for the iOS app's layered architecture, FFI bridge, event system, and extension model.
+>
+> Related specs: [README](README.md) | [API Reference](api.md) | [State Management](state.md) | [Database](database.md)
+> Related product: [Product Overview](../product/README.md)
+
+**Source:** [`SimpleXApp.swift`](../Shared/SimpleXApp.swift#L1-L183) | [`AppDelegate.swift`](../Shared/AppDelegate.swift#L1-L209) | [`ContentView.swift`](../Shared/ContentView.swift#L1-L513) | [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1373) | [`SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L1-L2915) | [`AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L1-L2357) | [`APITypes.swift`](../SimpleXChat/APITypes.swift#L1-L1071) | [`API.swift`](../SimpleXChat/API.swift#L1-L388)
+
+---
+
+## Table of Contents
+
+1. [Layered Architecture](#1-layered-architecture)
+2. [FFI Bridge](#2-ffi-bridge)
+3. [Event Streaming](#3-event-streaming)
+4. [Database Architecture](#4-database-architecture)
+5. [App Lifecycle](#5-app-lifecycle)
+6. [Extension Architecture](#6-extension-architecture)
+7. [Remote Desktop Control](#7-remote-desktop-control)
+
+---
+
+## [1. Layered Architecture](../Shared/SimpleXApp.swift#L17-L184)
+
+The app follows a strict layered model where each layer communicates only with its immediate neighbor:
+
+```
+┌─────────────────────────────────────────┐
+│ SwiftUI Views │ Rendering, user interaction
+│ (ChatListView, ChatView, ComposeView) │
+├─────────────────────────────────────────┤
+│ ChatModel (ObservableObject) │ App state, @Published properties
+│ ItemsModel, Chat, ChatTagsModel │ Per-chat state, tag filtering
+├─────────────────────────────────────────┤
+│ SimpleXAPI (FFI Bridge) │ chatSendCmd/chatApiSendCmd
+│ AppAPITypes (ChatCommand/Response) │ JSON serialization/deserialization
+├─────────────────────────────────────────┤
+│ C FFI Layer │ chat_send_cmd_retry, chat_recv_msg_wait
+│ (SimpleX.h, libsimplex.a) │ Compiled Haskell via GHC cross-compiler
+├─────────────────────────────────────────┤
+│ Haskell Core (chat_ctrl) │ Chat logic, chat protocol (x-events),
+│ (Simplex.Chat.Controller) │ database operations, file management
+├─────────────────────────────────────────┤
+│ simplexmq library (external) │ SMP/XFTP protocols, SMP Agent,
+│ (github.com/simplex-chat/simplexmq) │ double-ratchet (PQDR), transport (TLS)
+└─────────────────────────────────────────┘
+```
+
+**Key invariant**: No SwiftUI view directly calls FFI functions. All communication flows through `ChatModel` or dedicated API functions in `SimpleXAPI.swift`.
+
+### Source Files
+
+| Layer | File | Role | Line |
+|-------|------|------|------|
+| Views | [`Shared/Views/ChatList/ChatListView.swift`](../Shared/Views/ChatList/ChatListView.swift) | Chat list rendering | |
+| Views | [`Shared/Views/Chat/ChatView.swift`](../Shared/Views/Chat/ChatView.swift) | Conversation rendering | |
+| State | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L337) | `ChatModel`, `ItemsModel`, `Chat` classes | L337, L74, L1271 |
+| API | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L93) | FFI bridge functions | L93 |
+| API | [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L15) | `ChatCommand`, `ChatResponse`, `ChatEvent` enums | L15, L649, L1055 |
+| FFI | [`SimpleXChat/SimpleX.h`](../SimpleXChat/SimpleX.h#L1-L49) | C header declaring Haskell exports | |
+| FFI | [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift#L27) | `APIResult`, `ChatError`, `ChatCmdProtocol` | L27, L699, L17 |
+| Core | `../../src/Simplex/Chat/Controller.hs` | Haskell command processor — see `processCommand` in `Controller.hs` | |
+
+---
+
+## [2. FFI Bridge](../SimpleXChat/SimpleX.h#L1-L49)
+
+### [C Functions (SimpleX.h)](../SimpleXChat/SimpleX.h#L1-L49)
+
+The Haskell core exposes these C functions, declared in `SimpleXChat/SimpleX.h`:
+
+```c
+typedef void* chat_ctrl;
+
+// Initialize database, apply migrations, return controller
+char *chat_migrate_init_key(char *path, char *key, int keepKey, char *confirm,
+ int backgroundMode, chat_ctrl *ctrl);
+
+// Send command string, return JSON response string
+char *chat_send_cmd_retry(chat_ctrl ctl, char *cmd, int retryNum);
+
+// Block until next async event arrives (or timeout)
+char *chat_recv_msg_wait(chat_ctrl ctl, int wait);
+
+// Close/reopen database store
+char *chat_close_store(chat_ctrl ctl);
+char *chat_reopen_store(chat_ctrl ctl);
+
+// Utility: markdown parsing, server validation, password hashing
+char *chat_parse_markdown(char *str);
+char *chat_parse_server(char *str);
+char *chat_password_hash(char *pwd, char *salt);
+
+// File encryption/decryption
+char *chat_write_file(chat_ctrl ctl, char *path, char *data, int len);
+char *chat_read_file(char *path, char *key, char *nonce);
+char *chat_encrypt_file(chat_ctrl ctl, char *fromPath, char *toPath);
+char *chat_decrypt_file(char *fromPath, char *key, char *nonce, char *toPath);
+```
+
+### [Swift Bridge Functions (SimpleXAPI.swift)](../Shared/Model/SimpleXAPI.swift#L93-L221)
+
+```swift
+// Synchronous send -- blocks calling thread
+func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true,
+ bgDelay: Double? = nil, ctrl: chat_ctrl? = nil) throws -> R // L91
+
+// Async send -- dispatches to background
+func chatApiSendCmd(_ cmd: ChatCommand, bgTask: Bool = true,
+ bgDelay: Double? = nil, ctrl: chat_ctrl? = nil) async -> APIResult // L215
+
+// Low-level FFI call -- serializes command to string, calls chat_send_cmd_retry, decodes JSON
+func sendSimpleXCmd(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl?,
+ retryNum: Int32 = 0) -> APIResult // SimpleXChat/API.swift L114
+```
+
+### Data Flow
+
+1. Swift constructs a `ChatCommand` enum value (e.g., `.apiSendMessages(type:id:scope:live:ttl:composedMessages:)`)
+2. [`ChatCommand.cmdString`](../Shared/Model/AppAPITypes.swift#L15) serializes it to a command string (e.g., `"/_send @1 json {...}"`)
+3. [`sendSimpleXCmd`](../SimpleXChat/API.swift#L115) passes the string to `chat_send_cmd_retry` via C FFI
+4. Haskell core processes the command, returns JSON response string
+5. Swift decodes JSON into [`APIResult`](../SimpleXChat/APITypes.swift#L27) where `R: ChatAPIResult`
+6. Result is either `.result(R)`, `.error(ChatError)`, or `.invalid(type, json)`
+
+### [Background Task Protection](../Shared/Model/SimpleXAPI.swift#L54-L79)
+
+All FFI calls are wrapped in [`beginBGTask()`](../Shared/Model/SimpleXAPI.swift#L54) / `endBackgroundTask()` to prevent iOS from killing the app mid-operation. The `maxTaskDuration` is 15 seconds.
+
+---
+
+## [3. Event Streaming](../Shared/Model/SimpleXAPI.swift#L2220-L2916)
+
+The Haskell core emits async events (new messages, connection status changes, file progress, etc.) that are not direct responses to commands. These are received via polling:
+
+```
+Haskell Core --[chat_recv_msg_wait]--> Swift event loop --> ChatModel update --> SwiftUI re-render
+```
+
+The event loop is implemented in [`ChatReceiver`](../Shared/Model/SimpleXAPI.swift#L2220-L2263), and events are dispatched by [`processReceivedMsg`](../Shared/Model/SimpleXAPI.swift#L2266).
+
+### [Event Types (ChatEvent enum)](../Shared/Model/AppAPITypes.swift#L1055-L1129)
+
+Key async events delivered from core to UI:
+
+| Event | Description | Line |
+|-------|-------------|------|
+| `newChatItems` | New messages received | [L1070](../Shared/Model/AppAPITypes.swift#L1070) |
+| `chatItemUpdated` | Message edited by sender | [L1072](../Shared/Model/AppAPITypes.swift#L1072) |
+| `chatItemsDeleted` | Messages deleted | [L1074](../Shared/Model/AppAPITypes.swift#L1074) |
+| `chatItemReaction` | Reaction added/removed | [L1073](../Shared/Model/AppAPITypes.swift#L1073) |
+| `contactConnected` | New contact connected | [L1062](../Shared/Model/AppAPITypes.swift#L1062) |
+| `contactUpdated` | Contact profile changed | [L1066](../Shared/Model/AppAPITypes.swift#L1066) |
+| `receivedGroupInvitation` | Group invitation received | [L1077](../Shared/Model/AppAPITypes.swift#L1077) |
+| `groupMemberUpdated` | Group member info changed | [L1067](../Shared/Model/AppAPITypes.swift#L1067) |
+| `callInvitation` | Incoming call | [L1115](../Shared/Model/AppAPITypes.swift#L1115) |
+| `chatSuspended` | Core suspended (background) | [L1056](../Shared/Model/AppAPITypes.swift#L1056) |
+| `rcvFileComplete` | File download finished | [L1099](../Shared/Model/AppAPITypes.swift#L1099) |
+| `sndFileCompleteXFTP` | File upload finished | [L1110](../Shared/Model/AppAPITypes.swift#L1110) |
+
+Events are decoded as [`ChatEvent`](../Shared/Model/AppAPITypes.swift#L1055) enum in `Shared/Model/AppAPITypes.swift` and dispatched to update `ChatModel` / `ItemsModel` properties, triggering SwiftUI view re-renders via `@Published` property observation.
+
+---
+
+## [4. Database Architecture](../SimpleXChat/FileUtils.swift#L70-L294)
+
+Two SQLite databases in the app group container (shared with NSE):
+
+| Database | File | Contents |
+|----------|------|----------|
+| Chat DB | `simplex_v1_chat.db` | Messages, contacts, groups, profiles, files, tags, preferences |
+| Agent DB | `simplex_v1_agent.db` | SMP connections, keys, queues, server info |
+
+Both databases use the `DB_FILE_PREFIX = "simplex_v1"` prefix. The database path is resolved via [`getAppDatabasePath()`](../SimpleXChat/FileUtils.swift#L70) in `SimpleXChat/FileUtils.swift`, which checks `dbContainerGroupDefault` to determine whether to use the app group container or legacy documents directory.
+
+See [Database & Storage specification](database.md) for full details.
+
+---
+
+## [5. App Lifecycle](../Shared/SimpleXApp.swift#L17-L184)
+
+### [Initialization Sequence (SimpleXApp.swift)](../Shared/SimpleXApp.swift#L17-L38)
+
+```swift
+// SimpleXApp.init()
+1. haskell_init() // Initialize Haskell RTS (background queue, sync)
+2. UserDefaults.register(defaults:) // Register app preference defaults
+3. setGroupDefaults() // Sync preferences to app group container
+4. setDbContainer() // Set database path L122
+5. BGManager.shared.register() // Register background task handlers
+6. NtfManager.shared.registerCategories() // Register notification action categories
+```
+
+### State Transitions
+
+```
+ ┌──────────┐
+ │ Launched │
+ └─────┬─────┘
+ │ initChatAndMigrate()
+ v
+ ┌──────────┐
+ │ DB Setup │ chat_migrate_init_key()
+ └─────┬─────┘
+ │ startChat() SimpleXAPI.swift L2098
+ v
+ ┌──────────┐
+ │ Active │ apiActivateChat() SimpleXAPI.swift L358
+ └─────┬─────┘
+ │ scenePhase == .background
+ v
+ ┌──────────┐
+ │Background │ apiSuspendChat(timeoutMicroseconds:) SimpleXAPI.swift L368
+ └─────┬─────┘
+ │ scenePhase == .active
+ v
+ ┌──────────┐
+ │ Active │ startChatAndActivate()
+ └──────────┘
+```
+
+### [Scene Phase Handling (SimpleXApp.swift)](../Shared/SimpleXApp.swift#L38-L123)
+
+- **`.active`**: Calls `startChatAndActivate()`, processes pending notification responses, refreshes chat list and call invitations
+- **`.background`**: Records authentication timestamp, calls `suspendChat()` (unless CallKit call active), schedules `BGManager` background refresh, updates badge count
+- **`.inactive`**: No explicit handling (transitional state)
+
+### CallKit Exception
+
+When a CallKit call is active during backgrounding, chat suspension is deferred (`CallController.shared.shouldSuspendChat = true`) until the call ends, to maintain the WebRTC session.
+
+---
+
+## [6. Extension Architecture](../SimpleX%20NSE/NotificationService.swift#L1-L1228)
+
+### [Notification Service Extension (NSE)](../SimpleX%20NSE/NotificationService.swift#L1-L1228)
+
+The NSE ([`SimpleX NSE/NotificationService.swift`](../SimpleX%20NSE/NotificationService.swift#L1-L1228)) is a separate process that:
+
+1. Receives encrypted push notification payload from APNs
+2. Initializes its own Haskell core instance (`chat_ctrl`) with shared database access
+3. Decrypts the push payload using stored keys
+4. Generates a visible `UNMutableNotificationContent` with the decrypted message preview
+5. Delivers the notification to the user
+
+**Database sharing**: Both main app and NSE access the same database files in the app group container (`APP_GROUP_NAME`). Coordination uses file locks to prevent concurrent write conflicts.
+
+**Lifecycle**: The NSE has a ~30-second execution window per notification. It must initialize Haskell RTS, open the database, decrypt, and deliver within this window.
+
+### Share Extension (SE)
+
+The Share Extension (`SimpleX SE/`) allows sharing content (text, images, files) from other apps into SimpleX conversations.
+
+---
+
+## [7. Remote Desktop Control](../Shared/Views/RemoteAccess/ConnectDesktopView.swift#L1-L545)
+
+Optional desktop pairing allows controlling the mobile app from a desktop client:
+
+- **Pairing**: Encrypted QR code scanned by desktop client establishes a session
+- **Commands**: [`connectRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1613), [`findKnownRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1620), [`confirmRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1624), [`verifyRemoteCtrlSession`](../Shared/Model/SimpleXAPI.swift#L1630), [`listRemoteCtrls`](../Shared/Model/SimpleXAPI.swift#L1636), [`stopRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1642), [`deleteRemoteCtrl`](../Shared/Model/SimpleXAPI.swift#L1646)
+- **State**: [`ChatModel.remoteCtrlSession`](../Shared/Model/ChatModel.swift#L395)`: RemoteCtrlSession?` tracks the active session
+- **Transport**: Encrypted reverse HTTP transport between mobile and desktop
+- **Source**: [`Shared/Views/RemoteAccess/ConnectDesktopView.swift`](../Shared/Views/RemoteAccess/ConnectDesktopView.swift#L1-L545), see `Remote.hs` in `../../src/Simplex/Chat/`
+
+---
+
+## 8. Chat Relay Management
+
+### Overview
+
+Chat relays are SMP servers that forward messages to channel subscribers. They are configured in the Network & Servers settings and selected during channel creation.
+
+### Data Model
+
+| Type | Location | Description |
+|------|----------|-------------|
+| `UserChatRelay` | `ChatTypes.swift` | Relay server config: chatRelayId, address, name, domains, preset, tested, enabled, deleted |
+| `UserOperatorServers.chatRelays` | `AppAPITypes.swift` | Array of `UserChatRelay` per operator |
+| `UserServersWarning` | `AppAPITypes.swift` | Enum with `.noChatRelays(user:)` case |
+| `ServerSettings.serverWarnings` | `ChatListView.swift` | `[UserServersWarning]` field on `ServerSettings` struct (exposed via `SaveableSettings.servers`) |
+
+### Relay Management Views
+
+| View | File | Description |
+|------|------|-------------|
+| `ChatRelayView` | `ChatRelayView.swift` | Edit/view relay: name, address, test, enable toggle, delete |
+| `ChatRelayViewLink` | `ChatRelayView.swift` | NavigationLink row showing relay status icon + display name |
+| `NewChatRelayView` | `ChatRelayView.swift` | Form to add new relay (name + address + test + enable toggle) |
+| `ServersWarningView` | `NetworkAndServers.swift` | Orange exclamation triangle + warning text |
+
+### Key Functions
+
+| Function | File | Description |
+|----------|------|-------------|
+| `addChatRelay(...)` | `ChatRelayView.swift` | Validates name/address, appends to `userServers[nil operator].chatRelays`, calls `validateServers_` |
+| `deleteChatRelay(...)` | `ProtocolServersView.swift` | Marks relay as deleted or removes if no `chatRelayId` |
+| `validRelayName(_:)` | `ChatRelayView.swift` | Non-empty + valid display name check |
+| `validRelayAddress(_:)` | `ChatRelayView.swift` | Parses via `parseSimpleXMarkdown`, validates `.simplexLink(_, .relay, _, _)` |
+| `showRelayTestStatus(relay:)` | `ChatRelayView.swift` | ViewBuilder returning checkmark/multiply/clear icons |
+| `validateServers_` | `NetworkAndServers.swift` | Extended signature: now accepts optional `Binding<[UserServersWarning]>?`; calls `validateServers` which returns `([UserServersError], [UserServersWarning])` tuple |
+| `globalServersWarning(_:)` | `NetworkAndServers.swift` | Extracts `.noChatRelays` warning text for display |
+| `bindingForChatRelays(_:_:)` | `NetworkAndServers.swift` | Creates binding for `chatRelays` at operator index |
+
+### Relay Sections in Settings
+
+"Chat relays" sections appear in:
+- `OperatorView`: lists relays for the operator, with header and footer
+- `YourServersView` (in `ProtocolServersView`): lists relays for non-operator servers, with delete support and "Add server" -> "Chat relay" option
+
+### serverWarnings Plumbing
+
+`Binding<[UserServersWarning]>` is threaded through: `NetworkAndServers` -> `OperatorView` -> `ProtocolServersView` -> `ProtocolServerView` / `NewServerView` / `ScanProtocolServer`. All `validateServers_` calls pass the warnings binding.
+
+---
+
+## Source Files
+
+| File | Path | Line |
+|------|------|------|
+| App entry point | [`Shared/SimpleXApp.swift`](../Shared/SimpleXApp.swift#L17) | L17 |
+| App delegate | [`Shared/AppDelegate.swift`](../Shared/AppDelegate.swift#L15) | L15 |
+| Root view | [`Shared/ContentView.swift`](../Shared/ContentView.swift#L24) | L24 |
+| FFI bridge | [`Shared/Model/SimpleXAPI.swift`](../Shared/Model/SimpleXAPI.swift#L93) | L93 |
+| Low-level FFI | [`SimpleXChat/API.swift`](../SimpleXChat/API.swift#L115) | L115 |
+| App state | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L337) | L337 |
+| API types | [`Shared/Model/AppAPITypes.swift`](../Shared/Model/AppAPITypes.swift#L15) | L15 |
+| Shared types | [`SimpleXChat/APITypes.swift`](../SimpleXChat/APITypes.swift#L27) | L27 |
+| C header | [`SimpleXChat/SimpleX.h`](../SimpleXChat/SimpleX.h#L1-L49) | |
+| NSE | [`SimpleX NSE/NotificationService.swift`](../SimpleX%20NSE/NotificationService.swift#L1-L1228) | |
+| Haskell core | `../../src/Simplex/Chat/Controller.hs` — see `processCommand` in `Controller.hs` | |
+| Chat protocol (x-events, message envelopes) | `../../src/Simplex/Chat/Protocol.hs` | |
+
+### External: simplexmq Library
+
+The lower-level protocol and encryption layers are in the separate [simplexmq](https://github.com/simplex-chat/simplexmq) library:
+
+| Component | Spec | Implementation |
+|-----------|------|----------------|
+| SMP protocol | `simplexmq/protocol/simplex-messaging.md` | `simplexmq/src/Simplex/Messaging/Protocol.hs` |
+| XFTP protocol | `simplexmq/protocol/xftp.md` | `simplexmq/src/Simplex/FileTransfer/Protocol.hs` |
+| SMP Agent (duplex connections) | `simplexmq/protocol/agent-protocol.md` | `simplexmq/src/Simplex/Messaging/Agent.hs` |
+| Double ratchet (PQDR) | `simplexmq/protocol/pqdr.md` | `simplexmq/src/Simplex/Messaging/Crypto/Ratchet.hs` |
+| Post-quantum KEM (sntrup761) | `simplexmq/protocol/pqdr.md` | `simplexmq/src/Simplex/Messaging/Crypto/SNTRUP761.hs` |
+| TLS transport | — | `simplexmq/src/Simplex/Messaging/Transport.hs` |
+| File encryption | — | `simplexmq/src/Simplex/Messaging/Crypto/File.hs` |
diff --git a/apps/ios/spec/client/chat-list.md b/apps/ios/spec/client/chat-list.md
new file mode 100644
index 0000000000..d35de1f80a
--- /dev/null
+++ b/apps/ios/spec/client/chat-list.md
@@ -0,0 +1,296 @@
+# SimpleX Chat iOS -- Chat List Module
+
+> Technical specification for the conversation list, filtering, search, swipe actions, and user picker.
+>
+> Related specs: [Chat View](chat-view.md) | [Navigation](navigation.md) | [State Management](../state.md) | [README](../README.md)
+> Related product: [Chat List View](../../product/views/chat-list.md)
+
+**Source:** [`ChatListView.swift`](../../Shared/Views/ChatList/ChatListView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatListView](#2-chatlistview)
+3. [ChatPreviewView](#3-chatpreviewview)
+4. [ChatListNavLink](#4-chatlistnavlink)
+5. [Filtering & Tags](#5-filtering--tags)
+6. [Search](#6-search)
+7. [Swipe Actions](#7-swipe-actions)
+8. [UserPicker](#8-userpicker)
+9. [Floating Action Button](#9-floating-action-button)
+
+---
+
+## 1. Overview
+
+The chat list is the main screen of the app, displaying all conversations for the current user. It provides:
+
+- Conversation previews with unread badges
+- Filter tabs (All, Unread, Favorites, Groups, Contacts, Business, user-defined tags)
+- Search across chat names and message content
+- Swipe actions for quick operations
+- User profile switcher
+- Floating action button for new conversations
+
+```
+ChatListView
+├── Navigation Bar
+│ ├── User avatar (tap → UserPicker)
+│ └── Filter tabs (TagListView)
+├── Search bar (on pull-down or tap)
+├── Chat List (List/LazyVStack)
+│ └── ChatListNavLink (per conversation)
+│ └── ChatPreviewView
+│ ├── Avatar
+│ ├── Chat name + last message preview
+│ ├── Timestamp
+│ └── Unread badge
+├── FAB (New Chat button)
+└── Pending connection cards
+```
+
+---
+
+## 2. [`ChatListView`](../../Shared/Views/ChatList/ChatListView.swift#L142) {#2-chatlistview}
+
+**File**: `Shared/Views/ChatList/ChatListView.swift`
+
+The root list view. Key responsibilities:
+
+### Data Source
+- Reads `ChatModel.shared.chats` (all conversations)
+- Applies active filter from `ChatTagsModel.shared.activeFilter`
+- Applies search query filtering via [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480)
+- Sorts by last activity (most recent first), with pinned chats at top
+
+### Layout
+- Uses SwiftUI `List` with `ForEach` over filtered chats
+- Each row is a `ChatListNavLink` wrapping a `ChatPreviewView`
+- Pull-to-refresh triggers `updateChats()` API call
+- Empty state: `ChatHelp` view with getting-started guidance
+
+### Connection Cards
+- Pending contact connections (`ChatInfo.contactConnection`) shown as cards
+- Contact requests (`ChatInfo.contactRequest`) shown with accept/reject UI via `ContactRequestView`
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/ChatList/ChatListView.swift#L168) | 163 | Main view body |
+| [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480) | 472 | Applies active filter and search to chat list |
+| [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L523) | 514 | Normalizes search text for comparison |
+| [`unreadBadge()`](../../Shared/Views/ChatList/ChatListView.swift#L454) | 448 | Renders unread count circle badge |
+| [`stopAudioPlayer()`](../../Shared/Views/ChatList/ChatListView.swift#L474) | 467 | Stops any playing voice message |
+
+---
+
+## 3. [`ChatPreviewView`](../../Shared/Views/ChatList/ChatPreviewView.swift#L13) {#3-chatpreviewview}
+
+**File**: `Shared/Views/ChatList/ChatPreviewView.swift`
+
+Renders a single row in the chat list. Shows:
+
+| Element | Source | Description |
+|---------|--------|-------------|
+| Avatar | `chatInfo.image` | Profile image or default icon |
+| Chat name | `chatInfo.displayName` | Contact name, group name, or connection label |
+| Last message | `chat.chatItems.last` | Preview text of most recent message |
+| Timestamp | `chat.chatItems.last?.timestampText` | Relative time of last message |
+| Unread badge | `chat.chatStats.unreadCount` | Circular badge with unread count |
+| Mute icon | `chatInfo.chatSettings?.enableNtfs` | Bell-slash icon if notifications muted |
+| Pin icon | -- | Pin indicator for pinned chats |
+| Incognito icon | Contact.contactConnIncognito | Incognito mode indicator |
+| Delivery status | Last sent item's `meta.itemStatus` | Check marks for delivery confirmation |
+
+### Preview Text Rendering
+- Text messages: first line of message content
+- Images: camera icon + caption (if any)
+- Files: paperclip icon + filename
+- Voice: microphone icon + duration
+- Calls: phone icon + call status
+- Group events: system event description
+- Encrypted/deleted: placeholder text
+
+---
+
+## 4. [`ChatListNavLink`](../../Shared/Views/ChatList/ChatListNavLink.swift#L44) {#4-chatlistnavlink}
+
+**File**: `Shared/Views/ChatList/ChatListNavLink.swift`
+
+Wraps `ChatPreviewView` in a navigation link with tap and swipe behavior:
+
+### Tap Behavior
+- Direct chat: navigates to `ChatView` via `ItemsModel.loadOpenChat(chatId)` -- [`contactNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L95) L93
+- Group chat: navigates to `ChatView` -- [`groupNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L217) L214
+- Contact request: shows `ContactRequestView` with accept/reject -- [`contactRequestNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L495) L486
+- Contact connection: shows `ContactConnectionInfo` -- [`contactConnectionNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L530) L520
+- Notes folder: navigates to `ChatView` -- [`noteFolderNavLink()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L302) L298
+
+### Navigation
+- Uses `NavigationLink` (iOS 15) or programmatic navigation (iOS 16+)
+- Sets `ChatModel.chatId` to trigger navigation
+- `ItemsModel.loadOpenChat()` loads messages with a 250ms navigation delay for smooth animation
+
+### Channel Adaptations in ChatListNavLink
+
+When `groupInfo.useRelays == true`:
+
+| Change | Behavior |
+|--------|----------|
+| Swipe "Leave" | Hidden when `useRelays && isOwner` |
+| Context menu "Leave" | Hidden under same condition |
+| `deleteGroupAlert` label | "Delete channel?" |
+| `leaveGroupAlert` title | "Leave channel?" |
+| `leaveGroupAlert` message | "You will stop receiving messages from this channel. Chat history will be preserved." |
+
+### ServerSettings
+
+`ServerSettings` struct (defined in `ChatListView.swift`) includes `serverWarnings: [UserServersWarning]` field, initialized to `[]`. This field stores validation warnings from `validateServers` and is consumed by NetworkAndServers views.
+
+---
+
+## 5. Filtering & Tags
+
+### Filter Tabs ([`TagListView`](../../Shared/Views/ChatList/TagListView.swift#L20))
+
+**File**: `Shared/Views/ChatList/TagListView.swift`
+
+Horizontal scrolling tab bar below the navigation bar. Tabs:
+
+| Tab | Filter | Shows |
+|-----|--------|-------|
+| All | `nil` | All conversations |
+| Unread | `.unread` | Conversations with unread messages |
+| Favorites | `.presetTag(.favorites)` | Favorited conversations |
+| Groups | `.presetTag(.groups)` | Group conversations |
+| Contacts | `.presetTag(.contacts)` | Direct conversations |
+| Business | `.presetTag(.business)` | Business conversations |
+| Group Reports | `.presetTag(.groupReports)` | Groups with pending reports |
+| User tags | `.userTag(ChatTag)` | User-defined custom tags |
+
+Filter matching is handled by [`presetTagMatchesChat()`](../../Shared/Views/ChatList/ChatListView.swift#L910) (L910) and the in-view [`TagsView`](../../Shared/Views/ChatList/ChatListView.swift#L705) struct (L705).
+
+### ChatTagsModel State
+
+Filtering state is managed by [`ChatTagsModel`](../../Shared/Model/ChatModel.swift#L189) (`ChatModel.swift` L183):
+
+```swift
+class ChatTagsModel: ObservableObject {
+ @Published var userTags: [ChatTag] = []
+ @Published var activeFilter: ActiveFilter? = nil
+ @Published var presetTags: [PresetTag: Int] = [:] // count per preset tag
+ @Published var unreadTags: [Int64: Int] = [:] // unread count per user tag
+}
+```
+
+- `presetTags` counts are updated whenever `chats` changes via [`updateChatTags()`](../../Shared/Model/ChatModel.swift#L197) (L197)
+- Tags with zero matching chats are auto-hidden
+- Active filter is auto-cleared when its tag has no matching chats
+
+### Supporting Types
+
+| Type | File | Line | Description |
+|------|------|------|-------------|
+| [`PresetTag`](../../Shared/Views/ChatList/ChatListView.swift#L36) | ChatListView.swift | 34 | Enum of built-in filter categories |
+| [`ActiveFilter`](../../Shared/Views/ChatList/ChatListView.swift#L52) | ChatListView.swift | 49 | Enum wrapping preset, user-tag, or unread filter |
+| [`setActiveFilter()`](../../Shared/Views/ChatList/ChatListView.swift#L889) | ChatListView.swift | 878 | Applies a filter and persists selection |
+
+### Tag Management Commands
+- `apiCreateChatTag(tag: ChatTagData)` -- create tag
+- `apiSetChatTags(type:, id:, tagIds:)` -- assign tags to a chat
+- `apiDeleteChatTag(tagId:)` -- delete tag
+- `apiUpdateChatTag(tagId:, tagData:)` -- rename tag
+- `apiReorderChatTags(tagIds:)` -- reorder tags
+
+---
+
+## 6. Search
+
+Search is available via pull-down gesture or search button in the navigation bar.
+
+**Search bar UI:** [`ChatListSearchBar`](../../Shared/Views/ChatList/ChatListView.swift#L587) (ChatListView.swift L578)
+
+### Filtering Logic
+- Filters `ChatModel.chats` by matching search text against:
+ - `chatInfo.displayName` (contact/group name)
+ - `chatInfo.localAlias` (local alias)
+ - `chatInfo.fullName` (full name)
+- For deeper message content search, uses `apiGetChat(chatId:, search:)` parameter
+- Core logic in [`filteredChats()`](../../Shared/Views/ChatList/ChatListView.swift#L480) (L480) and [`searchString()`](../../Shared/Views/ChatList/ChatListView.swift#L523) (L523)
+
+### Search Results
+- Matching chats are displayed in the same list format
+- Results update as the user types (debounced)
+- Clearing search restores the full filtered list
+
+---
+
+## 7. Swipe Actions
+
+`ChatListNavLink` provides swipe actions on each row:
+
+### Leading Swipe (left-to-right)
+
+| Action | Icon | Handler | Line | API | Condition |
+|--------|------|---------|------|-----|-----------|
+| Pin / Unpin | pin | [`toggleFavoriteButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L353) | 347 | `apiSetChatSettings` (favorite) | Always |
+| Read / Unread | envelope | [`markReadButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L333) | 328 | `apiChatRead` / `apiChatUnread` | Always |
+
+### Trailing Swipe (right-to-left)
+
+| Action | Icon | Handler | Line | API | Condition |
+|--------|------|---------|------|-----|-----------|
+| Mute / Unmute | bell.slash | [`toggleNtfsButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L372) | 365 | `apiSetChatSettings` (enableNtfs) | Always |
+| Clear | trash | [`clearChatButton()`](../../Shared/Views/ChatList/ChatListNavLink.swift#L393) | 385 | `apiClearChat` | Has messages |
+| Delete | trash.fill | -- | -- | `apiDeleteChat` | Not active chat |
+| Tag | tag | -- | -- | `apiSetChatTags` | Always |
+
+---
+
+## 8. [`UserPicker`](../../Shared/Views/ChatList/UserPicker.swift#L10) {#8-userpicker}
+
+**File**: `Shared/Views/ChatList/UserPicker.swift`
+
+Triggered by tapping the user avatar in the navigation bar. Presented as a sheet with:
+
+| Section | Contents |
+|---------|----------|
+| User list | All non-hidden users with unread counts |
+| Active user | Highlighted with checkmark |
+| Actions | Settings, Your SimpleX address, User profiles |
+
+### User Switching
+- Tapping a different user calls `apiSetActiveUser(userId:)`
+- Triggers `apiGetChats` for the new user
+- `ChatModel.currentUser` updates, causing full UI refresh
+- Hidden users are not shown (require password entry via settings)
+
+---
+
+## 9. Floating Action Button
+
+The FAB (floating action button) in the bottom-right corner opens the new chat flow:
+
+- Tap: opens `NewChatView` sheet for creating a new contact connection or group
+- Shows options: Create link, Scan QR code, Paste link, Create group
+
+---
+
+## Source Files
+
+| File | Path | Key struct | Line |
+|------|------|------------|------|
+| Chat list view | [`ChatListView.swift`](../../Shared/Views/ChatList/ChatListView.swift) | `ChatListView` | [138](../../Shared/Views/ChatList/ChatListView.swift#L142) |
+| Chat preview row | [`ChatPreviewView.swift`](../../Shared/Views/ChatList/ChatPreviewView.swift) | `ChatPreviewView` | [12](../../Shared/Views/ChatList/ChatPreviewView.swift#L13) |
+| Navigation link wrapper | [`ChatListNavLink.swift`](../../Shared/Views/ChatList/ChatListNavLink.swift) | `ChatListNavLink` | [43](../../Shared/Views/ChatList/ChatListNavLink.swift#L44) |
+| Tag filter tabs | [`TagListView.swift`](../../Shared/Views/ChatList/TagListView.swift) | `TagListView` | [19](../../Shared/Views/ChatList/TagListView.swift#L20) |
+| User picker sheet | [`UserPicker.swift`](../../Shared/Views/ChatList/UserPicker.swift) | `UserPicker` | [9](../../Shared/Views/ChatList/UserPicker.swift#L10) |
+| Getting started help | [`ChatHelp.swift`](../../Shared/Views/ChatList/ChatHelp.swift) | | |
+| Contact request view | [`ContactRequestView.swift`](../../Shared/Views/ChatList/ContactRequestView.swift) | | |
+| Contact connection info | [`ContactConnectionInfo.swift`](../../Shared/Views/ChatList/ContactConnectionInfo.swift) | | |
+| Contact connection view | [`ContactConnectionView.swift`](../../Shared/Views/ChatList/ContactConnectionView.swift) | | |
+| Server summary | [`ServersSummaryView.swift`](../../Shared/Views/ChatList/ServersSummaryView.swift) | | |
+| One-hand UI card | [`OneHandUICard.swift`](../../Shared/Views/ChatList/OneHandUICard.swift) | | |
diff --git a/apps/ios/spec/client/chat-view.md b/apps/ios/spec/client/chat-view.md
new file mode 100644
index 0000000000..182e7b7ce9
--- /dev/null
+++ b/apps/ios/spec/client/chat-view.md
@@ -0,0 +1,391 @@
+# SimpleX Chat iOS -- Chat View Module
+
+> Technical specification for the message rendering, chat item types, and context menu actions in the conversation view.
+>
+> Related specs: [Compose Module](compose.md) | [State Management](../state.md) | [API Reference](../api.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`ChatView.swift`](../../Shared/Views/Chat/ChatView.swift) | [`ChatInfoView.swift`](../../Shared/Views/Chat/ChatInfoView.swift) | [`GroupChatInfoView.swift`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift) | [`ChannelMembersView.swift`](../../Shared/Views/Chat/Group/ChannelMembersView.swift) | [`ChannelRelaysView.swift`](../../Shared/Views/Chat/Group/ChannelRelaysView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatView](#2-chatview)
+3. [ChatItemView -- Message Routing](#3-chatitemview)
+4. [Message Renderers](#4-message-renderers)
+5. [Media Views](#5-media-views)
+6. [Metadata & Info](#6-metadata--info)
+7. [Context Menu Actions](#7-context-menu-actions)
+8. [Selection Mode](#8-selection-mode)
+
+---
+
+## 1. Overview
+
+The chat view module renders individual conversations. It consists of:
+
+- **ChatView** -- The main conversation screen with message list, compose bar, and navigation
+- **ChatItemView** -- Router that dispatches each chat item to the appropriate renderer
+- **Specialized renderers** -- FramedItemView (standard messages), EmojiItemView (emoji-only), CICallItemView (calls), event views, etc.
+- **Media views** -- CIImageView, CIVideoView, CIVoiceView, CIFileView for attachments
+
+```
+ChatView
+├── Message List (ScrollView / LazyVStack)
+│ ├── ChatItemView (per message)
+│ │ ├── FramedItemView (text/media bubbles)
+│ │ │ ├── MsgContentView (text with markdown)
+│ │ │ ├── CIImageView / CIVideoView / CIVoiceView
+│ │ │ └── CIMetaView (timestamp, status)
+│ │ ├── EmojiItemView (emoji-only messages)
+│ │ ├── CICallItemView (call events)
+│ │ ├── CIEventView (system events)
+│ │ ├── CIGroupInvitationView (group invitations)
+│ │ ├── DeletedItemView / MarkedDeletedItemView
+│ │ └── CIInvalidJSONView (decode errors)
+│ └── ... (more items)
+├── ComposeView (message input)
+└── Navigation bar (contact/group info)
+```
+
+---
+
+## [2. ChatView](../../Shared/Views/Chat/ChatView.swift#L18-L3210)
+
+**File**: [`Shared/Views/Chat/ChatView.swift`](../../Shared/Views/Chat/ChatView.swift)
+
+The main conversation view. Key responsibilities:
+
+### State
+- Uses `ItemsModel.shared.reversedChatItems` for the primary message list
+- `ChatModel.shared.chatId` identifies the active conversation
+- Manages compose state, scroll position, keyboard visibility
+- Tracks selection mode for multi-message actions
+
+### Message List
+- Renders messages in a `ScrollViewReader` with `LazyVStack`
+- Items are in reverse chronological order (newest at bottom)
+- Supports infinite scroll: preloads older messages when scrolling up via `ItemsModel.preloadState`
+- Handles pagination splits (`chatState.splits`) for non-contiguous loaded ranges
+
+### Navigation Bar
+- Title: contact name / group name with connection status indicator
+- Trailing button: navigates to [`ChatInfoView`](../../Shared/Views/Chat/ChatInfoView.swift#L93) (direct) or [`GroupChatInfoView`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L16) (group)
+- Search button: toggles in-chat message search
+
+### Scroll Behavior
+- Auto-scrolls to bottom on new sent/received messages (if already near bottom)
+- "Scroll to bottom" floating button when scrolled up
+- `openAroundItemId` support: scrolls to a specific message (e.g., from search or notification)
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/Chat/ChatView.swift#L75) | L75 | Main view body |
+| [`initChatView()`](../../Shared/Views/Chat/ChatView.swift#L660) | L660 | Initializes chat view state on appear |
+| [`chatItemsList()`](../../Shared/Views/Chat/ChatView.swift#L817) | L817 | Builds the scrollable message list |
+| [`scrollToItem(_:)`](../../Shared/Views/Chat/ChatView.swift#L731) | L731 | Scrolls to a specific message by ID |
+| [`searchToolbar()`](../../Shared/Views/Chat/ChatView.swift#L765) | L765 | In-chat search toolbar UI |
+| [`searchTextChanged(_:)`](../../Shared/Views/Chat/ChatView.swift#L1095) | L1095 | Handles search query changes |
+| [`loadChatItems(_:_:)`](../../Shared/Views/Chat/ChatView.swift#L1531) | L1531 | Loads chat items with pagination |
+| [`filtered(_:)`](../../Shared/Views/Chat/ChatView.swift#L803) | L803 | Filters items by content type |
+| [`callButton(_:_:imageName:)`](../../Shared/Views/Chat/ChatView.swift#L1273) | L1273 | Audio/video call toolbar button |
+| [`searchButton()`](../../Shared/Views/Chat/ChatView.swift#L1293) | L1293 | Search toggle toolbar button |
+| [`addMembersButton()`](../../Shared/Views/Chat/ChatView.swift#L1361) | L1361 | Group add-members toolbar button |
+| [`forwardSelectedMessages()`](../../Shared/Views/Chat/ChatView.swift#L1420) | L1420 | Forwards batch-selected messages |
+| [`deletedSelectedMessages()`](../../Shared/Views/Chat/ChatView.swift#L1411) | L1411 | Deletes batch-selected messages |
+| [`onChatItemsUpdated()`](../../Shared/Views/Chat/ChatView.swift#L1572) | L1572 | Reacts to chat items model changes |
+| [`contentFilterMenu(withLabel:)`](../../Shared/Views/Chat/ChatView.swift#L1301) | L1301 | Content filter dropdown menu |
+
+### Supporting Types
+
+| Type | Line | Description |
+|------|------|-------------|
+| [`ChatItemWithMenu`](../../Shared/Views/Chat/ChatView.swift#L1600) | L1600 | Wraps each chat item with context menu |
+| [`FloatingButtonModel`](../../Shared/Views/Chat/ChatView.swift#L2787) | L2787 | Manages scroll-to-bottom button state |
+| [`ReactionContextMenu`](../../Shared/Views/Chat/ChatView.swift#L2974) | L2974 | Reaction picker context menu |
+| [`ToggleNtfsButton`](../../Shared/Views/Chat/ChatView.swift#L3072) | L3072 | Mute/unmute notifications button |
+| [`ContentFilter`](../../Shared/Views/Chat/ChatView.swift#L3124) | L3124 | Enum for message content filter types |
+| [`deleteMessages()`](../../Shared/Views/Chat/ChatView.swift#L2870) | L2870 | Deletes messages with confirmation |
+| [`archiveReports()`](../../Shared/Views/Chat/ChatView.swift#L2917) | L2917 | Archives report messages |
+
+---
+
+## [3. ChatItemView](../../Shared/Views/Chat/ChatItemView.swift#L42)
+
+**File**: [`Shared/Views/Chat/ChatItemView.swift`](../../Shared/Views/Chat/ChatItemView.swift)
+
+Routes each `ChatItem` to the appropriate renderer based on its `CIContent` type:
+
+### Content Types (CIContent enum)
+
+| Content Type | Renderer | Line | Description |
+|-------------|----------|------|-------------|
+| `sndMsgContent` / `rcvMsgContent` | [`FramedItemView`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14) | L14 | Standard sent/received text+media message |
+| `sndDeleted` / `rcvDeleted` | [`DeletedItemView`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14) | L14 | Locally deleted message placeholder |
+| `sndCall` / `rcvCall` | [`CICallItemView`](../../Shared/Views/Chat/ChatItem/CICallItemView.swift#L13) | L13 | Call event (missed, ended, duration) |
+| `rcvIntegrityError` | [`IntegrityErrorItemView`](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift#L14) | L14 | Message integrity error |
+| `rcvDecryptionError` | [`CIRcvDecryptionError`](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift#L16) | L16 | Decryption failure |
+| `sndGroupInvitation` / `rcvGroupInvitation` | [`CIGroupInvitationView`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14) | L14 | Group invite |
+| `sndGroupEvent` / `rcvGroupEvent` | [`CIEventView`](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) | L14 | Group system event |
+| `rcvConnEvent` / `sndConnEvent` | [`CIEventView`](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) | L14 | Connection event |
+| `rcvChatFeature` / `sndChatFeature` | [`CIChatFeatureView`](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift#L14) | L14 | Feature toggle event |
+| `rcvChatPreference` / `sndChatPreference` | [`CIFeaturePreferenceView`](../../Shared/Views/Chat/ChatItem/CIFeaturePreferenceView.swift#L14) | L14 | Preference change |
+| `invalidJSON` | [`CIInvalidJSONView`](../../Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift#L14) | L14 | Failed to decode |
+
+### Bubble Direction
+- Sent messages: aligned right, sender-colored bubble
+- Received messages: aligned left, receiver-colored bubble
+- Events/system messages: centered, no bubble
+
+### Appearance Dependencies
+Each [`ChatItemWithMenu`](../../Shared/Views/Chat/ChatView.swift#L1600) may depend on the previous and next items for visual decisions:
+- Whether to show the sender name (group messages, different sender than previous)
+- Whether to show the tail on the bubble (last consecutive message from same sender)
+- Date separator between messages on different days
+
+`ChatItemDummyModel.shared.sendUpdate()` forces a re-render of all items when global appearance changes.
+
+### Channel Message Rendering (`.channelRcv`)
+
+Channel messages (`CIDirection.channelRcv`) are rendered with the group avatar and group name as sender, with "channel" as the role label. This mirrors the `.groupRcv` path's `showGroupAsSender` visual but uses a dedicated code branch in [`chatItemListView()`](../../Shared/Views/Chat/ChatView.swift#L1846).
+
+Key differences from `.groupRcv`:
+- No `prevMember`/`memCount` logic — channels have no per-member identity
+- Always shows group avatar (via `ProfileImage` with `groupInfo.image` / `groupInfo.chatIconName`)
+- Tapping avatar opens `showChatInfoSheet` (not member info)
+- [`shouldShowAvatar()`](../../Shared/Views/Chat/ChatView.swift#L1670) treats consecutive `.channelRcv` items as same sender
+- [`getItemSeparation()`](../../Shared/Views/Chat/ChatView.swift#L1649) treats consecutive `.channelRcv` items as `sameMemberAndDirection`
+- [`showMemberImage()`](../../Shared/Views/Chat/ChatView.swift#L2116) returns `true` when previous item is `.channelRcv` (different sender type)
+- [`memberToModerate()`](../../SimpleXChat/ChatTypes.swift#L3297) returns `nil` for `.channelRcv` (no per-member moderation)
+
+---
+
+## 4. Message Renderers
+
+### [FramedItemView](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/FramedItemView.swift`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift)
+
+The standard message bubble. Renders:
+- Quote/reply preview (if replying to another message)
+- Forwarded indicator
+- Sender name (in groups)
+- Message content (`MsgContentView` with markdown)
+- Attached media (image, video, voice, file, link preview)
+- Reaction summary bar
+- Metadata line (`CIMetaView`)
+
+### [EmojiItemView](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/EmojiItemView.swift`](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift)
+
+Renders emoji-only messages (messages containing only emoji characters) in a larger font without a bubble background.
+
+### [MsgContentView](../../Shared/Views/Chat/ChatItem/MsgContentView.swift#L28)
+
+**File**: [`Shared/Views/Chat/ChatItem/MsgContentView.swift`](../../Shared/Views/Chat/ChatItem/MsgContentView.swift)
+
+Renders message text with SimpleX markdown formatting (bold, italic, code, links, mentions).
+
+### DeletedItemView / MarkedDeletedItemView
+
+**Files**: [`Shared/Views/Chat/ChatItem/DeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift) | [`Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift)
+
+- [`DeletedItemView`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14): Placeholder for locally deleted messages
+- [`MarkedDeletedItemView`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift#L14): Shows "message deleted" with optional moderation info (who deleted, when)
+
+### [CIEventView](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIEventView.swift`](../../Shared/Views/Chat/ChatItem/CIEventView.swift)
+
+Centered system event text for group events (member joined, left, role changed) and connection events.
+
+### [CIGroupInvitationView](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift)
+
+Renders group invitation with accept/reject buttons.
+
+---
+
+## 5. Media Views
+
+### [CIImageView](../../Shared/Views/Chat/ChatItem/CIImageView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift)
+
+Renders inline images. Tapping opens `FullScreenMediaView` for zooming/panning. Images are compressed to `MAX_IMAGE_SIZE` (255KB) before sending.
+
+### [CIVideoView](../../Shared/Views/Chat/ChatItem/CIVideoView.swift#L16)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift)
+
+Renders video thumbnails with play button. Tapping opens video player. Videos above auto-receive threshold require manual download.
+
+### CIVoiceView / FramedCIVoiceView
+
+**Files**: [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | [`Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift)
+
+Renders voice messages with waveform visualization, play/pause control, and duration. [`FramedCIVoiceView`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift#L16) is the version inside a message bubble with additional context.
+
+### [CIFileView](../../Shared/Views/Chat/ChatItem/CIFileView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift)
+
+Renders file attachments with filename, size, and download/open actions. Shows transfer progress during upload/download.
+
+### [CILinkView](../../Shared/Views/Chat/ChatItem/CILinkView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CILinkView.swift`](../../Shared/Views/Chat/ChatItem/CILinkView.swift)
+
+Renders link preview cards with OpenGraph metadata (title, description, image).
+
+### [AnimatedImageView](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift#L11)
+
+**File**: [`Shared/Views/Chat/ChatItem/AnimatedImageView.swift`](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift)
+
+Renders animated GIF images.
+
+### [FullScreenMediaView](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift#L16)
+
+**File**: [`Shared/Views/Chat/ChatItem/FullScreenMediaView.swift`](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift)
+
+Full-screen media viewer with zoom, pan, and share actions. Supports images and videos.
+
+---
+
+## 6. Metadata & Info
+
+### [CIMetaView](../../Shared/Views/Chat/ChatItem/CIMetaView.swift#L14)
+
+**File**: [`Shared/Views/Chat/ChatItem/CIMetaView.swift`](../../Shared/Views/Chat/ChatItem/CIMetaView.swift)
+
+Displays message metadata inline at the bottom of the bubble:
+- Timestamp (sent time)
+- Delivery status icon (sending, sent, delivered, read, error)
+- Edit indicator (pencil icon if message was edited)
+- Disappearing message timer (if timed message)
+
+### [ChatItemInfoView](../../Shared/Views/Chat/ChatItemInfoView.swift#L13)
+
+**File**: [`Shared/Views/Chat/ChatItemInfoView.swift`](../../Shared/Views/Chat/ChatItemInfoView.swift)
+
+Detailed message information sheet (accessed via long-press menu "Info"):
+- Full delivery history (per-member delivery status in groups)
+- Edit history (all previous versions of edited messages)
+- Forward chain info
+- Message timestamps (created, updated, deleted)
+
+---
+
+## 7. Context Menu Actions
+
+Long-pressing a message shows a context menu with actions based on message type and ownership:
+
+| Action | Available For | API Command |
+|--------|--------------|-------------|
+| Reply | All messages | Sets compose state to `.replying` |
+| Forward | Sent/received content messages | `apiForwardChatItems` |
+| Copy | Text messages | Copies to clipboard |
+| Edit | Own sent messages (within edit window) | `apiUpdateChatItem` |
+| Delete for me | All messages | `apiDeleteChatItem(mode: .cidmInternal)` |
+| Delete for everyone | Own sent messages | `apiDeleteChatItem(mode: .cidmBroadcast)` |
+| Moderate | Group admin/owner for others' messages | `apiDeleteMemberChatItem` |
+| React | Content messages (if reactions enabled) | `apiChatItemReaction` |
+| Select | All messages | Enters multi-select mode |
+| Info | All messages | Opens [`ChatItemInfoView`](../../Shared/Views/Chat/ChatItemInfoView.swift#L13) |
+| Save | Media messages | Saves to photo library / files |
+| Share | Content messages | iOS share sheet |
+
+---
+
+## 8. Selection Mode
+
+Multi-selection mode allows batch operations on messages:
+
+- Enter via long-press "Select" action
+- Toggle individual messages with tap
+- Toolbar appears with batch actions: Delete, Forward
+- Exit via cancel button or completing batch action
+
+---
+
+## GroupChatInfoView — Channel Adaptations
+
+When `groupInfo.useRelays == true`, [`GroupChatInfoView`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L16) adapts its sections:
+
+### Section Structure (Channel)
+
+| Section | Owner | Subscriber |
+|---------|-------|-----------|
+| 1. Links & Members | Channel link (manage via GroupLinkView), Owners & subscribers | Channel link (read-only QR from `groupProfile.publicGroup?.groupLink`), Owners |
+| 2. Profile & Welcome | Edit channel profile, Welcome message | Welcome message (if exists) |
+| 3. Theme & TTL | Chat theme, Delete messages after | Chat theme, Delete messages after |
+| 4. Actions | Chat relays, Clear chat, Delete channel | Chat relays, Clear chat, Leave channel |
+
+**Hidden for channels:** Member support, group reports, user support chat, send receipts, inline members list, group preferences.
+
+### Label Replacements
+
+All "group" labels are replaced with "channel" equivalents via `groupInfo.useRelays ? "Channel..." :` ternary prepended before existing `businessChat` ternary. Affected: delete/leave buttons, delete/leave alerts, remove member alert, edit profile button, group link nav title. Channel link button uses a separate `channelLinkButton()` with hardcoded "Channel link" label.
+
+### [`channelMembersButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L627) → [`ChannelMembersView`](../../Shared/Views/Chat/Group/ChannelMembersView.swift)
+
+Navigates to a dedicated members view with two sections:
+- **Owners**: current user (if owner) + members with `memberRole >= .owner`
+- **Subscribers** (admin+ only): members with `memberRole < .owner`
+
+Member rows show profile image, display name (with verified shield), connection status, and role badge. Non-user rows link to `GroupMemberInfoView`.
+
+### Channel Link
+
+Owner sees [`channelLinkButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L605) (navigates to `GroupLinkView` for full link management), guarded by `groupInfo.isOwner && groupLink != nil` — channel links can only be created during channel creation, not from the info view. A TODO marks the need for protocol changes to allow other owners to manage the same channel link. Non-owner sees read-only QR code displaying `groupProfile.publicGroup?.groupLink` via `SimpleXLinkQRCode`. `apiGetGroupLink` is skipped in `onAppear` for non-owner channels.
+
+Groups use separate [`groupLinkButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L593) which supports both "Create group link" and "Group link" labels.
+
+### [`channelRelaysButton()`](../../Shared/Views/Chat/Group/GroupChatInfoView.swift#L639) → [`ChannelRelaysView`](../../Shared/Views/Chat/Group/ChannelRelaysView.swift)
+
+Navigates to relay list view with role-based branches:
+- **Owner**: loads `[GroupRelay]` via [`apiGetGroupRelays`](../../Shared/Model/SimpleXAPI.swift#L1839) (owner-only API, guarded by `assertUserGroupRole GROwner` on backend). Joins with `chatModel.groupMembers` by `groupMemberId` for display names. Shows status indicators (colored circle + `RelayStatus.text`).
+- **Member**: filters `chatModel.groupMembers` by `.memberRole == .relay`. Shows relay member display names only (no status data).
+
+### Leave Button Logic
+
+Sole channel owner cannot leave (only delete). Guard: `members.filter({ $0.wrapped.memberRole == .owner && $0.wrapped.groupMemberId != groupInfo.membership.groupMemberId }).count > 0`.
+
+---
+
+## Source Files
+
+| File | Path | Line |
+|------|------|------|
+| Chat view | [`Shared/Views/Chat/ChatView.swift`](../../Shared/Views/Chat/ChatView.swift) | [L18](../../Shared/Views/Chat/ChatView.swift#L18) |
+| Item router | [`Shared/Views/Chat/ChatItemView.swift`](../../Shared/Views/Chat/ChatItemView.swift) | [L42](../../Shared/Views/Chat/ChatItemView.swift#L42) |
+| Framed bubble | [`Shared/Views/Chat/ChatItem/FramedItemView.swift`](../../Shared/Views/Chat/ChatItem/FramedItemView.swift) | [L14](../../Shared/Views/Chat/ChatItem/FramedItemView.swift#L14) |
+| Emoji message | [`Shared/Views/Chat/ChatItem/EmojiItemView.swift`](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift) | [L14](../../Shared/Views/Chat/ChatItem/EmojiItemView.swift#L14) |
+| Image view | [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIImageView.swift#L14) |
+| Video view | [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift) | [L16](../../Shared/Views/Chat/ChatItem/CIVideoView.swift#L16) |
+| Voice view | [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift#L14) |
+| File view | [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIFileView.swift#L14) |
+| Link preview | [`Shared/Views/Chat/ChatItem/CILinkView.swift`](../../Shared/Views/Chat/ChatItem/CILinkView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CILinkView.swift#L14) |
+| Call event | [`Shared/Views/Chat/ChatItem/CICallItemView.swift`](../../Shared/Views/Chat/ChatItem/CICallItemView.swift) | [L13](../../Shared/Views/Chat/ChatItem/CICallItemView.swift#L13) |
+| Metadata | [`Shared/Views/Chat/ChatItem/CIMetaView.swift`](../../Shared/Views/Chat/ChatItem/CIMetaView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIMetaView.swift#L14) |
+| Message info | [`Shared/Views/Chat/ChatItemInfoView.swift`](../../Shared/Views/Chat/ChatItemInfoView.swift) | [L13](../../Shared/Views/Chat/ChatItemInfoView.swift#L13) |
+| System event | [`Shared/Views/Chat/ChatItem/CIEventView.swift`](../../Shared/Views/Chat/ChatItem/CIEventView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIEventView.swift#L14) |
+| Deleted placeholder | [`Shared/Views/Chat/ChatItem/DeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift) | [L14](../../Shared/Views/Chat/ChatItem/DeletedItemView.swift#L14) |
+| Moderated placeholder | [`Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift`](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift) | [L14](../../Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift#L14) |
+| Text content | [`Shared/Views/Chat/ChatItem/MsgContentView.swift`](../../Shared/Views/Chat/ChatItem/MsgContentView.swift) | [L28](../../Shared/Views/Chat/ChatItem/MsgContentView.swift#L28) |
+| Group invitation | [`Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift`](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIGroupInvitationView.swift#L14) |
+| Feature event | [`Shared/Views/Chat/ChatItem/CIChatFeatureView.swift`](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIChatFeatureView.swift#L14) |
+| Decryption error | [`Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift`](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift) | [L16](../../Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift#L16) |
+| Integrity error | [`Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift`](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift) | [L14](../../Shared/Views/Chat/ChatItem/IntegrityErrorItemView.swift#L14) |
+| Full-screen media | [`Shared/Views/Chat/ChatItem/FullScreenMediaView.swift`](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift) | [L16](../../Shared/Views/Chat/ChatItem/FullScreenMediaView.swift#L16) |
+| Animated image | [`Shared/Views/Chat/ChatItem/AnimatedImageView.swift`](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift) | [L11](../../Shared/Views/Chat/ChatItem/AnimatedImageView.swift#L11) |
+| Framed voice | [`Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift) | [L16](../../Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift#L16) |
+| Member contact | [`Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift`](../../Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift) | [L14](../../Shared/Views/Chat/ChatItem/CIMemberCreatedContactView.swift#L14) |
+| Channel members | [`Shared/Views/Chat/Group/ChannelMembersView.swift`](../../Shared/Views/Chat/Group/ChannelMembersView.swift) | [L12](../../Shared/Views/Chat/Group/ChannelMembersView.swift#L12) |
+| Channel relays | [`Shared/Views/Chat/Group/ChannelRelaysView.swift`](../../Shared/Views/Chat/Group/ChannelRelaysView.swift) | [L12](../../Shared/Views/Chat/Group/ChannelRelaysView.swift#L12) |
diff --git a/apps/ios/spec/client/compose.md b/apps/ios/spec/client/compose.md
new file mode 100644
index 0000000000..f86e323ade
--- /dev/null
+++ b/apps/ios/spec/client/compose.md
@@ -0,0 +1,372 @@
+# SimpleX Chat iOS -- Message Composition Module
+
+> Technical specification for the compose bar, attachment types, reply/edit/forward modes, voice recording, and mentions.
+>
+> Related specs: [Chat View](chat-view.md) | [File Transfer](../services/files.md) | [API Reference](../api.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`ComposeView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ComposeView](#2-composeview)
+3. [ComposeState Machine](#3-composestate-machine)
+4. [Attachment Types](#4-attachment-types)
+5. [Reply Mode](#5-reply-mode)
+6. [Edit Mode](#6-edit-mode)
+7. [Forward Mode](#7-forward-mode)
+8. [Live Messages](#8-live-messages)
+9. [Voice Recording](#9-voice-recording)
+10. [Link Previews](#10-link-previews)
+11. [Mentions](#11-mentions)
+
+---
+
+## 1. Overview
+
+The compose module handles all message creation, editing, and forwarding. It sits at the bottom of `ChatView` and adapts its UI based on the current compose state.
+
+```
+ComposeView
+├── Context banner (reply quote / edit indicator / forward indicator)
+├── Attachment preview (image / video / file / voice waveform)
+├── Text input (NativeTextEditor with markdown support)
+├── Action buttons
+│ ├── Attachment menu (camera, photo library, file picker)
+│ ├── Voice record button (hold or toggle)
+│ └── Send button (or live message indicator)
+└── Link preview (auto-generated when URL detected)
+```
+
+---
+
+## 2. [ComposeView](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L329) (`struct ComposeView: View`)
+
+**File**: `Shared/Views/Chat/ComposeMessage/ComposeView.swift`
+
+### Layout
+- Fixed at the bottom of ChatView
+- Expands vertically as text input grows (up to a maximum height)
+- Context banner appears above the text field when in reply/edit/forward mode
+- Attachment preview appears between context banner and text field
+
+### Key Properties
+- Reads `ChatModel.shared.draft` / `draftChatId` for persisted drafts
+- Manages its own internal compose state
+- Coordinates with `ChatView` for scroll-to-bottom behavior on send
+
+### Send Flow
+1. User taps send button
+2. ComposeView constructs `[ComposedMessage]` from current state
+3. Calls `apiSendMessages(type:, id:, scope:, live:, ttl:, composedMessages:)`
+4. On success: clears compose state, scrolls to bottom
+5. On failure: shows error alert, preserves compose state
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`body`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L371) | L371 | Main view body |
+| [`sendMessageView()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L870) | L870 | Builds the send-message UI |
+| [`sendMessage(ttl:)`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1286) | L1286 | Entry point: initiates send |
+| [`sendMessageAsync()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1295) | L1295 | Async send implementation |
+| [`clearState(live:)`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1649) | L1649 | Resets compose state after send |
+| [`addMediaContent()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1073) | L1073 | Adds media attachment |
+| [`connectCheckLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1046) | L1046 | Checks link preview before connect |
+| [`commandsButton()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L931) | L931 | Builds commands menu button |
+
+### Draft Persistence
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`saveCurrentDraft()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1663) | L1663 | Saves compose state to `ChatModel.draft` |
+| [`clearCurrentDraft()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1669) | L1669 | Clears persisted draft |
+
+- When navigating away from a chat, compose state is saved to `ChatModel.draft` / `ChatModel.draftChatId`
+- When returning to the same chat, draft is restored
+- Drafts are not persisted across app restarts
+
+---
+
+## 3. [ComposeState](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L45) Machine (`struct ComposeState`)
+
+The compose bar operates as a state machine with these primary states:
+
+```
+ ┌──────────┐
+ │ .empty │ ← initial / after send
+ └─────┬────┘
+ │ user types / attaches / quotes
+ v
+ ┌─────────────────────────────────────┐
+ │ │
+ ┌────▼────┐ ┌──────────────┐ ┌──────────▼───┐
+ │ .text │ │ .mediaPending │ │ .voiceRecording │
+ └─────────┘ └──────────────┘ └───────────────┘
+ │ │
+ │ long-press reply│ tap edit
+ v v
+ ┌──────────┐ ┌──────────┐ ┌───────────┐
+ │ .replying │ │ .editing │ │ .forwarding│
+ └──────────┘ └──────────┘ └───────────┘
+```
+
+### Supporting Types
+
+| Type | Line | Description |
+|------|------|-------------|
+| [`enum ComposePreview`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L11) | L11 | Preview variants (image, voice, file, etc.) |
+| [`enum ComposeContextItem`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L20) | L20 | Context item for reply/quote |
+| [`enum VoiceMessageRecordingState`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L29) | L29 | Recording state enum |
+| [`struct ComposeState`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L45) | L45 | Full compose state struct |
+| [`copy()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L98) | L98 | Copy compose state with overrides |
+| [`mentionMemberName()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L118) | L118 | Format mention display name |
+| [`chatItemPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L266) | L266 | Build preview from chat item |
+| [`enum UploadContent`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L287) | L287 | Upload content variants |
+
+### States
+
+| State | Description | UI |
+|-------|-------------|-----|
+| `.empty` | No input, no attachments | Placeholder text, attachment button |
+| `.text` | Text entered, no attachments | Send button visible |
+| `.mediaPending` | Media/file selected, optionally with text | Preview visible, send button |
+| `.voiceRecording` | Voice recording in progress | Waveform animation, stop/send |
+| `.replying` | Replying to a specific message | Quote banner above input |
+| `.editing` | Editing a previously sent message | Edit banner, pre-filled text |
+| `.forwarding` | Forwarding selected messages | Forward banner, item previews |
+
+### Transitions
+
+| From | Trigger | To |
+|------|---------|-----|
+| `.empty` | User types text | `.text` |
+| `.empty` | User selects media | `.mediaPending` |
+| `.empty` | User holds voice button | `.voiceRecording` |
+| `.empty` | User long-presses message "Reply" | `.replying` |
+| `.empty` | User long-presses message "Edit" | `.editing` |
+| `.empty` | User selects "Forward" | `.forwarding` |
+| Any | User taps send | `.empty` |
+| Any | User taps cancel (X) | `.empty` |
+
+---
+
+## 4. Attachment Types
+
+### [ComposeImageView](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift#L12)
+
+**File**: [`ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) (struct at L12)
+
+Preview of selected image(s) before sending. Shows thumbnail with remove button. Images are compressed to `MAX_IMAGE_SIZE` (255KB) before sending.
+
+### [ComposeFileView](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift#L11)
+
+**File**: [`ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) (struct at L11)
+
+Preview of selected file or video. Shows filename, size, and remove button. Videos show a thumbnail frame.
+
+### [ComposeVoiceView](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift#L26)
+
+**File**: [`ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) (struct at L26)
+
+Voice message recording/playback preview. Shows waveform visualization, duration, and play/delete buttons.
+
+### Attachment Menu Options
+
+| Option | Picker | Max Size | Transfer Method |
+|--------|--------|----------|-----------------|
+| Camera photo | UIImagePickerController | Compressed to 255KB | Inline in SMP message |
+| Photo library | PHPickerViewController | Compressed to 255KB | Inline or XFTP |
+| Video | PHPickerViewController | Up to 1GB | XFTP |
+| File | UIDocumentPickerViewController | Up to 1GB | XFTP |
+
+---
+
+## 5. Reply Mode
+
+Activated via long-press context menu "Reply" on any message.
+
+### UI
+- Quote banner above text input showing original message preview
+- X button to cancel reply
+- Original message reference stored in compose state
+
+### API
+- Reply is sent as part of `ComposedMessage` with `quotedItemId` parameter
+- `apiSendMessages(composedMessages: [ComposedMessage(quotedItemId: originalItem.id, ...)])`
+
+---
+
+## 6. Edit Mode
+
+Activated via long-press context menu "Edit" on own sent messages (within the edit window).
+
+### UI
+- Edit banner above text input with pencil icon
+- Text field pre-filled with original message content
+- Send button changes to "Save" / checkmark
+
+### API
+- `apiUpdateChatItem(type:, id:, scope:, itemId:, updatedMessage:, live:)`
+- Response: `ChatResponse1.chatItemUpdated(user:, chatItem:)`
+
+### Constraints
+- Only own sent messages can be edited
+- Edit is available within a server-defined time window
+- Edited messages show a pencil indicator in `CIMetaView`
+- Edit history is visible in `ChatItemInfoView`
+
+---
+
+## 7. Forward Mode
+
+Activated via long-press context menu "Forward" or via multi-select toolbar.
+
+### Flow
+1. User selects "Forward" on message(s)
+2. `apiPlanForwardChatItems(fromChatType:, fromChatId:, fromScope:, itemIds:)` is called to plan
+3. Response: `ChatResponse1.forwardPlan(user:, chatItemIds:, forwardConfirmation:)`
+4. User selects destination chat
+5. `apiForwardChatItems(toChatType:, toChatId:, toScope:, fromChatType:, fromChatId:, fromScope:, itemIds:, ttl:)` executes the forward
+6. Forwarded messages appear with a forwarded indicator
+
+### ForwardConfirmation
+The plan response may include a `forwardConfirmation` requiring user confirmation (e.g., forwarding to a less secure chat).
+
+---
+
+## 8. [Live Messages](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L36) (`struct LiveMessage`)
+
+Optional feature where the recipient sees typing in real-time.
+
+### How It Works
+- User enables live message mode (lightning icon)
+- As user types, `apiSendMessages(live: true)` is called repeatedly
+- Each call sends the current text as an update to the same message
+- Recipient sees the message being composed in real-time
+- Final send marks the message as complete
+
+### Key Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`sendLiveMessage()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1102) | L1102 | Initiates a live message |
+| [`updateLiveMessage()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1120) | L1120 | Sends incremental live update |
+| [`liveMessageToSend()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1139) | L1139 | Determines text diff to send |
+| [`truncateToWords()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1144) | L1144 | Truncates text at word boundary |
+
+### API
+- Initial: `apiSendMessages(live: true, composedMessages: [...])` -- creates live message
+- Updates: `apiUpdateChatItem(live: true)` -- updates content as user types
+- Final: `apiUpdateChatItem(live: false)` -- marks as complete
+
+---
+
+## 9. Voice Recording
+
+### Recording Flow
+1. User taps (or holds) the microphone button
+2. `AVAudioRecorder` starts recording in compressed format
+3. Waveform visualization shows real-time audio levels
+4. User taps stop (or releases hold) to finish recording
+5. Preview with playback shown in compose area
+6. User taps send to deliver
+
+### Voice Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`startVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1564) | L1564 | Begins audio recording |
+| [`finishVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1605) | L1605 | Stops recording, shows preview |
+| [`allowVoiceMessagesToContact()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1616) | L1616 | Enables voice messages for contact |
+| [`updateComposeVMRFinished()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1623) | L1623 | Updates state after recording finishes |
+| [`cancelCurrentVoiceRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1635) | L1635 | Cancels in-progress recording |
+| [`cancelVoiceMessageRecording()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1642) | L1642 | Cancels and cleans up recording file |
+
+### Constraints
+- Maximum duration: `MAX_VOICE_MESSAGE_LENGTH = 300` seconds (5 minutes)
+- Auto-receive threshold: `MAX_VOICE_SIZE_AUTO_RCV = 522,240` bytes (510KB)
+- Compressed audio format for small file sizes
+
+### Audio Management
+- [`AudioRecorder`](../../Shared/Model/AudioRecPlay.swift#L14) (`Shared/Model/AudioRecPlay.swift` L14) manages recording and playback
+- `ChatModel.stopPreviousRecPlay` coordinates exclusive audio playback (only one audio source plays at a time)
+
+---
+
+## 10. [Link Previews](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift#L13) (`ComposeLinkView`)
+
+**File**: [`ComposeLinkView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift) (struct at L13)
+
+### Auto-Detection
+- As user types, URLs in the text are detected
+- When a URL is found, `ComposeLinkView` fetches OpenGraph metadata
+- Preview card shows title, description, and thumbnail image
+
+### Link Preview Functions
+
+| Function | Line | Description |
+|----------|------|-------------|
+| [`showLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1677) | L1677 | Triggers link preview loading |
+| [`getMessageLinks()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1697) | L1697 | Extracts URLs from formatted text |
+| [`isSimplexLink()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1708) | L1708 | Checks if URL is a SimpleX link |
+| [`cancelLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1712) | L1712 | Cancels pending preview |
+| [`loadLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1724) | L1724 | Fetches OpenGraph metadata |
+| [`resetLinkPreview()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1741) | L1741 | Resets preview state |
+
+### Behavior
+- Only the first URL in the message generates a preview
+- Preview can be dismissed by the user
+- Link preview data is included in the `ComposedMessage` sent to the core
+- Toggle in privacy settings to disable auto-preview generation
+
+---
+
+## 11. Mentions
+
+In group chats, typing `@` triggers member name autocomplete:
+
+### Flow
+1. User types `@` in the text field
+2. Autocomplete dropdown appears with matching group members
+3. User selects a member
+4. `@displayName` is inserted into the text
+5. Mention is rendered with special formatting in the sent message
+
+### Data
+- Group members loaded from `ChatModel.groupMembers`
+- Mention metadata included in `ComposedMessage`
+
+---
+
+## Channel Compose Behavior
+
+When `chat.chatInfo.groupInfo?.useRelays == true` (channel mode), compose behaves differently:
+
+### Owner/Admin Compose
+- [`send()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1498) passes `sendAsGroup: true` to `apiSendMessages` when `useRelays && memberRole >= .owner`
+- [`forwardItems()`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L1526) passes `sendAsGroup: true` to `apiForwardChatItems` under same condition
+- Placeholder text shows "Broadcast" instead of "Message" (via `sendMessageView()` `placeholder:` parameter)
+- Share Extension ([`ShareAPI.swift`](../../SimpleX%20SE/ShareAPI.swift#L71)) uses the same `sendAsGroup` expression
+
+### Subscriber Compose
+- [`userCantSendReason`](../../SimpleXChat/ChatTypes.swift#L1566) returns `("you are subscriber", nil)` when `useRelays && memberRole == .observer`
+- This check is evaluated after `memberPending` (which takes priority) but replaces the `observer` message
+- Compose field is disabled; tapping shows "You can't send messages!" alert with no body text
+
+---
+
+## Source Files
+
+| File | Path | Struct/Class | Line |
+|------|------|--------------|------|
+| Compose view | [`ComposeView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift) | `ComposeView` | [L329](../../Shared/Views/Chat/ComposeMessage/ComposeView.swift#L329) |
+| Send message UI | [`SendMessageView.swift`](../../Shared/Views/Chat/ComposeMessage/SendMessageView.swift) | `SendMessageView` | [L15](../../Shared/Views/Chat/ComposeMessage/SendMessageView.swift#L15) |
+| Image preview | [`ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) | `ComposeImageView` | [L12](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift#L12) |
+| File preview | [`ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) | `ComposeFileView` | [L11](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift#L11) |
+| Voice preview | [`ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) | `ComposeVoiceView` | [L26](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift#L26) |
+| Link preview | [`ComposeLinkView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift) | `ComposeLinkView` | [L13](../../Shared/Views/Chat/ComposeMessage/ComposeLinkView.swift#L13) |
+| Audio recording | [`AudioRecPlay.swift`](../../Shared/Model/AudioRecPlay.swift) | `AudioRecorder` | [L14](../../Shared/Model/AudioRecPlay.swift#L14) |
diff --git a/apps/ios/spec/client/navigation.md b/apps/ios/spec/client/navigation.md
new file mode 100644
index 0000000000..22985c6fe1
--- /dev/null
+++ b/apps/ios/spec/client/navigation.md
@@ -0,0 +1,380 @@
+# SimpleX Chat iOS -- Navigation Architecture
+
+> Technical specification for the navigation stack, deep linking, sheet presentation, and call overlay.
+>
+> Related specs: [Chat List](chat-list.md) | [Chat View](chat-view.md) | [State Management](../state.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`ContentView.swift`](../../Shared/ContentView.swift) | [`NewChatView.swift`](../../Shared/Views/NewChat/NewChatView.swift) | [`SettingsView.swift`](../../Shared/Views/UserSettings/SettingsView.swift) | [`OnboardingView.swift`](../../Shared/Views/Onboarding/OnboardingView.swift) | [`UserProfilesView.swift`](../../Shared/Views/UserSettings/UserProfilesView.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Root View -- ContentView](#2-root-view)
+3. [Navigation Stack](#3-navigation-stack)
+4. [Sheet Presentation](#4-sheet-presentation)
+5. [Deep Linking](#5-deep-linking)
+6. [Call Overlay](#6-call-overlay)
+7. [Authentication Gate](#7-authentication-gate)
+8. [Onboarding Flow](#8-onboarding-flow)
+
+---
+
+## 1. Overview
+
+The app's navigation follows a hierarchical model with a single navigation stack rooted in `ContentView`. Modal sheets and full-screen overlays augment the primary navigation path.
+
+```
+SimpleXApp
+└── ContentView (root)
+ ├── Authentication gate (LocalAuthView / SetAppPasscodeView)
+ ├── Onboarding flow (if first launch / migration)
+ ├── Main content
+ │ └── NavigationStack / NavigationView
+ │ ├── ChatListView (root of stack)
+ │ │ ├── ChatView (pushed)
+ │ │ │ ├── ChatInfoView / GroupChatInfoView (pushed)
+ │ │ │ └── ChatItemInfoView (pushed)
+ │ │ └── ContactConnectionInfo (pushed)
+ │ └── Settings views (pushed)
+ ├── Sheets (modal)
+ │ ├── UserPicker
+ │ ├── NewChatView
+ │ ├── WhatsNew / Notices
+ │ └── Settings sub-views
+ └── Overlays (always on top)
+ ├── Active call banner (when call active)
+ └── ActiveCallView (full-screen call)
+```
+
+---
+
+## 2. Root View -- [`ContentView`](../../Shared/ContentView.swift#L24)
+
+**File**: [`Shared/ContentView.swift`](../../Shared/ContentView.swift)
+
+`ContentView` is the root view injected by `SimpleXApp`. It manages:
+
+### [Environment](../../Shared/ContentView.swift#L25-L37)
+- `@EnvironmentObject var chatModel: ChatModel`
+- `@EnvironmentObject var theme: AppTheme`
+- `@Environment(\.scenePhase) var scenePhase`
+
+### [Key State](../../Shared/ContentView.swift#L35-L52)
+| Property | Type | Purpose |
+|----------|------|---------|
+| [`contentAccessAuthenticationExtended`](../../Shared/ContentView.swift#L35) | `Bool` | Passed at init to avoid re-render timing issues |
+| [`automaticAuthenticationAttempted`](../../Shared/ContentView.swift#L38) | `Bool` | Whether biometric auth was auto-attempted |
+| [`waitingForOrPassedAuth`](../../Shared/ContentView.swift#L51) | `Bool` | Whether auth gate should show |
+| [`chatListUserPickerSheet`](../../Shared/ContentView.swift#L52) | `UserPickerSheet?` | Active user picker sheet |
+
+### [View Selection Logic](../../Shared/ContentView.swift#L60-L80)
+
+```swift
+// Simplified decision tree in ContentView.body:
+if !prefPerformLA || accessAuthenticated {
+ contentView() // Main app content
+} else {
+ lockButton() // Authentication required
+}
+```
+
+The [`contentView()`](../../Shared/ContentView.swift#L169) function further decides:
+- If `chatModel.onboardingStage != .onboardingComplete`: show [onboarding](../../Shared/ContentView.swift#L174)
+- If `chatModel.migrationState != nil`: show migration UI
+- Otherwise: show `ChatListView` in a navigation container
+
+---
+
+## 3. Navigation Stack
+
+### iOS Version Compatibility
+
+**File**: [`Shared/Views/Helpers/NavStackCompat.swift`](../../Shared/Views/Helpers/NavStackCompat.swift)
+
+The app supports iOS 15+ and uses a compatibility wrapper ([`NavStackCompat`](../../Shared/Views/Helpers/NavStackCompat.swift#L11)):
+
+```swift
+// NavStackCompat provides:
+// - NavigationStack (iOS 16+): programmatic navigation via NavigationPath
+// - NavigationView (iOS 15): classic NavigationLink-based navigation
+```
+
+### Primary Navigation Path
+
+```
+ChatListView
+ │
+ ├─[tap chat]─→ ChatView
+ │ │
+ │ ├─[tap info]─→ ChatInfoView (direct)
+ │ │ └─→ VerifyCodeView, etc.
+ │ │
+ │ ├─[tap info]─→ GroupChatInfoView (group)
+ │ │ ├─→ GroupMemberInfoView
+ │ │ ├─→ GroupProfileView
+ │ │ └─→ GroupLinkView
+ │ │
+ │ └─[tap message info]─→ ChatItemInfoView
+ │
+ ├─[tap connection]─→ ContactConnectionInfo
+ │
+ └─[settings]─→ SettingsView
+ ├─→ NotificationsView
+ ├─→ NetworkAndServers
+ ├─→ AppearanceSettings
+ ├─→ PrivacySettings
+ ├─→ DatabaseView
+ └─→ UserProfilesView
+```
+
+### Navigation Trigger
+
+Chat navigation is triggered by setting `ChatModel.chatId`:
+
+```swift
+// In ChatListNavLink:
+ItemsModel.shared.loadOpenChat(chatId) {
+ // This sets ChatModel.chatId = chatId after a 250ms delay
+ // allowing navigation animation to start smoothly
+}
+```
+
+---
+
+## 4. Sheet Presentation
+
+Sheets are presented modally on top of the navigation stack:
+
+| Sheet | Trigger | Content |
+|-------|---------|---------|
+| UserPicker | Tap user avatar in nav bar | User list, settings shortcuts |
+| [`NewChatView`](../../Shared/Views/NewChat/NewChatView.swift#L78) | Tap FAB / "+" button | Create link, scan QR, paste link, new group |
+| WhatsNew | App update detected | Release notes |
+| AddGroupView | "New Group" action | Group creation wizard |
+| ConnectDesktopView | Settings > Desktop | Remote desktop pairing |
+| MigrateFromDevice | Settings > Migration | Device export |
+| MigrateToDevice | Onboarding migration | Device import |
+| [LocalAuthView](../../Shared/ContentView.swift#L95) | App foreground after background | Biometric/passcode auth |
+
+### Sheet Management
+
+Sheets use SwiftUI `.sheet(item:)` or `.sheet(isPresented:)` modifiers on `ContentView` and `ChatListView`. Some sheets use the centralized [`AppSheetState.shared`](../../Shared/ContentView.swift#L29) observable for coordination:
+
+```swift
+class AppSheetState: ObservableObject {
+ static let shared = AppSheetState()
+ var scenePhaseActive: Bool = false
+ // ... sheet state coordination
+}
+```
+
+---
+
+## 5. Deep Linking
+
+### Notification Deep Link
+
+When the user taps a notification:
+
+1. `NtfManager.processNotificationResponse()` extracts the `chatId` from notification payload
+2. If a different user: calls `changeActiveUser(userId:)`
+3. Sets `ChatModel.chatId = chatId` to navigate to the conversation
+4. If the app was in background: the notification response is stored in `ChatModel.notificationResponse` and processed when the app becomes active
+
+### [URL Deep Link](../../Shared/ContentView.swift#L281)
+
+SimpleX links (`simplex:/chat#...`) are handled via [`connectViaUrl()`](../../Shared/ContentView.swift#L439):
+
+```swift
+.onOpenURL { url in
+ if AppChatState.shared.value == .active {
+ chatModel.appOpenUrl = url // Process immediately
+ } else {
+ chatModel.appOpenUrlLater = url // Process when active
+ }
+}
+```
+
+URL processing routes to the appropriate connection flow (join group, add contact, etc.) via [`planAndConnect()`](../../Shared/Views/NewChat/NewChatView.swift#L1181).
+
+### Call Deep Link
+
+Call invitations from notifications:
+1. `NtfManager` detects `ntfActionAcceptCall` action
+2. Sets `ChatModel.ntfCallInvitationAction = (chatId, .accept)`
+3. `ContentView` picks up the pending action and initiates the call
+
+---
+
+## 6. Call Overlay
+
+The call UI overlays the entire app when a call is active:
+
+### [Call Banner](../../Shared/ContentView.swift#L203)
+
+When `ChatModel.activeCall != nil` and call is in connecting/active state:
+- A banner appears at the top of ContentView (height: [`callTopPadding = 40`](../../Shared/ContentView.swift#L54))
+- Shows contact name, call duration, tap to return to full-screen call
+- Main content is padded down to accommodate the banner
+
+### [Full-Screen Call View](../../Shared/ContentView.swift#L185)
+
+When `ChatModel.showCallView == true`:
+- `ActiveCallView` covers the entire screen as a ZStack overlay
+- Contains local/remote video, controls (mute, camera, speaker, end)
+- PiP mode: `ChatModel.activeCallViewIsCollapsed` collapses to mini view
+- Call view is always rendered on top of navigation and sheets
+
+```swift
+// In ContentView.allViews():
+ZStack {
+ contentView()
+ .padding(.top, showCallArea ? callTopPadding : 0)
+
+ if showCallArea, let call = chatModel.activeCall {
+ VStack {
+ activeCallInteractiveArea(call)
+ Spacer()
+ }
+ }
+
+ if chatModel.showCallView, let call = chatModel.activeCall {
+ callView(call) // Full screen overlay
+ }
+}
+```
+
+---
+
+## 7. Authentication Gate
+
+### [Local Authentication](../../Shared/ContentView.swift#L359)
+
+When [`DEFAULT_PERFORM_LA`](../../Shared/ContentView.swift#L44) is enabled:
+
+1. App enters background: `chatModel.contentViewAccessAuthenticated = false`
+2. App returns to foreground: `ContentView` shows [`lockButton()`](../../Shared/ContentView.swift#L238) instead of content
+3. User taps lock button: [`LocalAuthView`](../../Shared/ContentView.swift#L95) presented
+4. On successful auth: `chatModel.contentViewAccessAuthenticated = true`, content revealed
+
+### Authentication Methods
+- Face ID / Touch ID (via `LocalAuthentication` framework)
+- Custom numeric passcode
+- Custom alphanumeric passcode
+
+### [Extended Authentication](../../Shared/ContentView.swift#L351)
+- After successful auth, a grace period prevents re-auth for brief background/foreground cycles ([`unlockedRecently()`](../../Shared/ContentView.swift#L351))
+- [`contentAccessAuthenticationExtended`](../../Shared/ContentView.swift#L35) is computed at `ContentView.init` to avoid render-time race conditions
+- The `enteredBackgroundAuthenticated` timestamp tracks when the app was last authenticated in background
+
+---
+
+## 8. [Onboarding Flow](../../Shared/Views/Onboarding/OnboardingView.swift#L13)
+
+First-launch experience controlled by [`ChatModel.onboardingStage`](../../Shared/Views/Onboarding/OnboardingView.swift#L46):
+
+```swift
+enum OnboardingStage: String, Identifiable {
+ case step1_SimpleXInfo // Welcome screen
+ case step2_CreateProfile // deprecated
+ case step3_CreateSimpleXAddress // deprecated
+ case step3_ChooseServerOperators // Choose server operators
+ case step4_SetNotificationsMode // Set notification preferences
+ case onboardingComplete // Normal operation
+}
+```
+
+Each stage is a dedicated view presented in place of `ChatListView` within [`ContentView`](../../Shared/ContentView.swift#L174).
+
+Migration state (`ChatModel.migrationState != nil`) takes precedence over onboarding.
+
+---
+
+## 9. Channel Creation Flow (`AddChannelView`)
+
+**Source:** [`Shared/Views/NewChat/AddChannelView.swift`](../../Shared/Views/NewChat/AddChannelView.swift)
+
+### Entry Point
+
+`NewChatMenuButton` includes a NavigationLink "Create channel (BETA)" with antenna icon, navigating to `AddChannelView`.
+
+### Three-Step Wizard
+
+| Step | Function | Description |
+|------|----------|-------------|
+| 1. Profile | `profileStepView()` | Channel name input (`channelNameTextField()`), profile image picker. "Configure relays" link to `NetworkAndServers`. Validates via `canCreateProfile()` (non-empty + valid display name) and `checkHasRelays()`. |
+| 2. Progress | `progressStepView(_:)` | Relay connection progress with `RelayProgressIndicator` (circular active/total or spinner). Expandable relay list with `relayStatusIndicator(_:)` (green/red/orange dots). Cancel via `cancelChannelCreation(_:)` which calls `apiDeleteChat`. |
+| 3. Link | `linkStepView(_:)` | Wraps `GroupLinkView(isChannel: true)` for channel link sharing. |
+
+### Key Functions
+
+| Function | Scope | Description |
+|----------|-------|-------------|
+| `createChannel()` | private | Calls `apiNewPublicGroup(incognito:relayIds:groupProfile:)`, sets `ChannelRelaysModel` |
+| `getEnabledRelays()` | private | Filters enabled/non-deleted relays, selects random 3 |
+| `checkHasRelays()` | private | Validates at least one relay exists |
+| `relayDisplayName(_:)` | module | name > domain > link host > fallback |
+| `relayStatusIndicator(_:)` | module | Green/red/orange dot + status text |
+| `RelayProgressIndicator` | module | Circular progress (active/total) or spinner |
+
+## 10. Relay URL Interception
+
+**Source:** [`Shared/ContentView.swift`](../../Shared/ContentView.swift#L454)
+
+In `connectViaUrl_()`, relay address links (URL path `/r`) are intercepted before processing:
+
+```swift
+if path == "/r" {
+ showAlert(NSLocalizedString("Relay address", ...),
+ message: NSLocalizedString("This is a chat relay address, it cannot be used to connect.", ...))
+ return
+}
+```
+
+Similarly, in `planAndConnect()` (`NewChatView.swift`), `.simplexLink(_, .relay, _, _)` patterns trigger the same alert and block connection.
+
+## 11. Channel-Specific NewChatView Behavior
+
+**Source:** [`Shared/Views/NewChat/NewChatView.swift`](../../Shared/Views/NewChat/NewChatView.swift)
+
+### Prepared Group Alert (`showPrepareGroupAlert`)
+
+When `groupShortLinkInfo?.direct == false` (channel relay link), the prepare alert uses:
+- Channel icon: `antenna.radiowaves.left.and.right.circle.fill`
+- Title: "Open new channel"
+- Error: "Error opening channel"
+- `apiPrepareGroup` call passes `directLink: false`
+- Stores `groupShortLinkInfo.groupRelays` in `ChatModel.shared.channelRelayHostnames`
+
+### Own Link Confirmation (`showOwnGroupLinkConfirmConnectSheet`)
+
+For channels: shows "This is your link for channel" with only "Open channel" + "Cancel" buttons. No incognito or profile selection options.
+
+### Known Group Alert (`showOpenKnownGroupAlert`)
+
+For channels (`groupInfo.useRelays`): titles become "Open channel" / "Open new channel".
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| Root view | [`Shared/ContentView.swift`](../../Shared/ContentView.swift) |
+| App entry point | `Shared/SimpleXApp.swift` |
+| Navigation compat | [`Shared/Views/Helpers/NavStackCompat.swift`](../../Shared/Views/Helpers/NavStackCompat.swift) |
+| Chat list (nav root) | `Shared/Views/ChatList/ChatListView.swift` |
+| Nav link wrapper | `Shared/Views/ChatList/ChatListNavLink.swift` |
+| User picker | `Shared/Views/ChatList/UserPicker.swift` |
+| New chat view | [`Shared/Views/NewChat/NewChatView.swift`](../../Shared/Views/NewChat/NewChatView.swift) |
+| Channel creation | [`Shared/Views/NewChat/AddChannelView.swift`](../../Shared/Views/NewChat/AddChannelView.swift) |
+| New chat menu | [`Shared/Views/NewChat/NewChatMenuButton.swift`](../../Shared/Views/NewChat/NewChatMenuButton.swift) |
+| Settings view | [`Shared/Views/UserSettings/SettingsView.swift`](../../Shared/Views/UserSettings/SettingsView.swift) |
+| User profiles | [`Shared/Views/UserSettings/UserProfilesView.swift`](../../Shared/Views/UserSettings/UserProfilesView.swift) |
+| Onboarding view | [`Shared/Views/Onboarding/OnboardingView.swift`](../../Shared/Views/Onboarding/OnboardingView.swift) |
+| Active call view | `Shared/Views/Call/ActiveCallView.swift` |
+| Local auth view | `Shared/Views/LocalAuth/LocalAuthView.swift` |
+| Notification manager | `Shared/Model/NtfManager.swift` |
diff --git a/apps/ios/spec/database.md b/apps/ios/spec/database.md
new file mode 100644
index 0000000000..9e5adfcb64
--- /dev/null
+++ b/apps/ios/spec/database.md
@@ -0,0 +1,298 @@
+# SimpleX Chat iOS -- Database & Storage
+
+**Source:** [`FileUtils.swift`](../SimpleXChat/FileUtils.swift)
+
+> Technical specification for the database architecture, encryption, file storage, and export/import functionality.
+>
+> Related specs: [Architecture](architecture.md) | [State Management](state.md) | [README](README.md)
+> Related product: [Product Overview](../product/README.md)
+
+---
+
+## Table of Contents
+
+1. [Database Overview](#1-database-overview)
+2. [Database Files & Paths](#2-database-files--paths)
+3. [Haskell Store Modules](#3-haskell-store-modules)
+4. [Migrations](#4-migrations)
+5. [Database Encryption](#5-database-encryption)
+6. [File Storage](#6-file-storage)
+7. [Export & Import](#7-export--import)
+8. [App Group Sharing](#8-app-group-sharing)
+
+---
+
+## 1. Database Overview
+
+SimpleX Chat uses two SQLite databases managed entirely by the Haskell core. The iOS Swift layer never reads or writes directly to the databases -- all data access goes through the FFI command/response API.
+
+| Database | Suffix | Contents |
+|----------|--------|----------|
+| Chat DB | `_chat.db` | Messages, contacts, groups, user profiles, files, tags, preferences, call history |
+| Agent DB | `_agent.db` | SMP agent connections, cryptographic keys, message queues, server state, XFTP chunks |
+
+Both databases are initialized and migrated via the C FFI function `chat_migrate_init_key()`, which applies pending migrations and returns a `chat_ctrl` pointer.
+
+---
+
+## 2. Database Files & Paths
+
+### [Path Resolution](../SimpleXChat/FileUtils.swift#L63-L73) (FileUtils.swift)
+
+```swift
+let DB_FILE_PREFIX = "simplex_v1"
+
+// Database path depends on container preference
+func getAppDatabasePath() -> URL {
+ dbContainerGroupDefault.get() == .group
+ ? getGroupContainerDirectory().appendingPathComponent(DB_FILE_PREFIX)
+ : getLegacyDatabasePath()
+}
+
+// Full database file paths:
+// Chat: {container}/simplex_v1_chat.db
+// Agent: {container}/simplex_v1_agent.db
+```
+
+### [File Constants](../SimpleXChat/FileUtils.swift#L38-L44)
+
+```swift
+let CHAT_DB: String = "_chat.db"
+let AGENT_DB: String = "_agent.db"
+private let CHAT_DB_BAK: String = "_chat.db.bak"
+private let AGENT_DB_BAK: String = "_agent.db.bak"
+```
+
+### Container Locations
+
+See [`getDocumentsDirectory()`](../SimpleXChat/FileUtils.swift#L47) and [`getGroupContainerDirectory()`](../SimpleXChat/FileUtils.swift#L52).
+
+| Container | Path | Used When |
+|-----------|------|-----------|
+| App Group | `FileManager.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)` | Default (shared with NSE) |
+| Documents | `FileManager.urls(for: .documentDirectory)` | Legacy installations |
+
+The container choice is stored in `dbContainerGroupDefault` (`GroupDefaults`).
+
+---
+
+## 3. Haskell Store Modules
+
+All database operations are implemented in Haskell. Key store modules (paths relative to repo root):
+
+| Module | Path | Size | Description |
+|--------|------|------|-------------|
+| Messages | `src/Simplex/Chat/Store/Messages.hs` | ~178KB | Message CRUD, pagination, search, reactions, delivery receipts |
+| Groups | `src/Simplex/Chat/Store/Groups.hs` | ~126KB | Group CRUD, member management, roles, links, invitations |
+| Direct | `src/Simplex/Chat/Store/Direct.hs` | ~52KB | Direct contact connections, contact requests. See `createDirectChat` in `Store/Direct.hs` |
+| Files | `src/Simplex/Chat/Store/Files.hs` | ~43KB | File transfer state, XFTP chunks, inline files |
+| Profiles | `src/Simplex/Chat/Store/Profiles.hs` | ~42KB | User profiles, contact profiles, incognito profiles |
+| Connections | `src/Simplex/Chat/Store/Connections.hs` | ~17KB | Connection lifecycle, queue management |
+
+### Data Model (key tables)
+
+```
+users -- User profiles (userId, displayName, fullName, image, ...)
+contacts -- Contact records (contactId, userId, localDisplayName, ...)
+groups -- Group records (groupId, userId, groupProfile, ...)
+group_members -- Group membership (groupMemberId, groupId, memberId, role, ...)
+messages -- Message records (messageId, chatItemId, msgBody, ...)
+chat_items -- Chat items (chatItemId, chatType, chatId, content, ...)
+files -- File transfer records (fileId, chatItemId, fileName, fileSize, ...)
+connections -- SMP connections (connId, agentConnId, ...)
+chat_tags -- User-defined chat tags
+chat_tags_chats -- Tag-to-chat assignments
+```
+
+---
+
+## 4. Migrations
+
+Database migrations are managed by the Haskell core. Migration files are located in:
+
+```
+src/Simplex/Chat/Store/SQLite/Migrations/
+```
+
+Migrations are numbered sequentially starting from `M20220101` through `M20260122` (200+ migrations). Each migration is a Haskell module containing SQL statements for schema changes.
+
+The migration process:
+1. `chat_migrate_init_key()` is called with the database path
+2. Haskell reads the current schema version from the database
+3. Pending migrations are applied in order
+4. If migration fails, the function returns an error string (not a `chat_ctrl`)
+5. On success, a `chat_ctrl` pointer is returned
+
+Migration results are decoded in Swift as `DBMigrationResult`:
+- `.ok` -- migrations applied successfully
+- `.invalidConfirmation` -- migration requires user confirmation
+- `.errorNotADatabase(dbFile:)` -- file is not a valid SQLite database
+- `.errorMigration(dbFile:, migrationError:)` -- migration failed
+- `.errorSQL(dbFile:, migrationSQLError:)` -- SQL error during migration
+- `.errorKeychain` -- keychain access failed
+- `.unknown(json:)` -- unrecognized response
+
+---
+
+## 5. Database Encryption
+
+### Encryption Configuration
+
+Database encryption uses SQLCipher (AES-256) and is managed through the API:
+
+```swift
+// Set or change encryption
+ChatCommand.apiStorageEncryption(config: DBEncryptionConfig)
+
+// Test if a key is correct
+ChatCommand.testStorageEncryption(key: String)
+```
+
+`DBEncryptionConfig` contains:
+- `currentKey: String` -- current encryption key (empty if unencrypted)
+- `newKey: String` -- new encryption key (empty to decrypt)
+
+### Key Storage
+
+The encryption key is stored in the iOS Keychain via `kcDatabasePassword`:
+- On first launch with encryption, the key is generated and stored
+- The `storeDBPassphraseGroupDefault` flag controls whether the key is auto-stored
+- If the user opts out of auto-storage, they must enter the key on each launch
+
+### UI
+
+- [`DatabaseEncryptionView.swift`](../Shared/Views/Database/DatabaseEncryptionView.swift) -- Encryption settings UI
+- [`DatabaseView.swift`](../Shared/Views/Database/DatabaseView.swift) -- Database management UI (size, export, import, encryption)
+
+---
+
+## 6. File Storage
+
+### Directory Structure
+
+```
+{App Container}/
+├── Documents/
+│ ├── app_files/ -- Downloaded and sent files
+│ ├── temp_files/ -- Temporary files during transfer
+│ └── assets/wallpapers/ -- Custom wallpaper images
+├── {App Group Container}/
+│ ├── simplex_v1_chat.db -- Chat database
+│ ├── simplex_v1_agent.db -- Agent database
+│ └── ...
+```
+
+### [File Size Constants](../SimpleXChat/FileUtils.swift#L18-L36) (FileUtils.swift)
+
+```swift
+public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255 KB -- inline image compression target
+public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB -- auto-receive images
+public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB -- auto-receive voice
+public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023 KB -- auto-receive video
+public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1 GB -- max XFTP transfer
+public let MAX_FILE_SIZE_SMP: Int64 = 8_000_000 // ~7.6 MB -- max SMP inline
+public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max // No limit for local files
+public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes
+```
+
+### CryptoFile (Encrypted File Storage)
+
+When `apiSetEncryptLocalFiles(enable: true)` is set, files stored on device are AES-encrypted:
+
+- Encryption/decryption uses `chat_encrypt_file` / `chat_decrypt_file` C FFI functions
+- Each file gets a unique key and nonce stored alongside the file reference
+- The `CryptoFile` type wraps `(filePath: String, cryptoArgs: CryptoFileArgs?)` where `CryptoFileArgs` contains `(fileKey: String, fileNonce: String)`
+
+### [File Path Helpers](../SimpleXChat/FileUtils.swift#L219-L221)
+
+```swift
+public func getDocumentsDirectory() -> URL // Standard documents dir
+public func getGroupContainerDirectory() -> URL // App group container
+func getAppFilesDirectory() -> URL // {appDir}/app_files/
+func getTempFilesDirectory() -> URL // {appDir}/temp_files/
+func getWallpaperDirectory() -> URL // {appDir}/assets/wallpapers/
+```
+
+See also [`saveFile()`](../SimpleXChat/FileUtils.swift#L226), [`removeFile()`](../SimpleXChat/FileUtils.swift#L243), and [`getMaxFileSize()`](../SimpleXChat/FileUtils.swift#L276).
+
+### [Cleanup](../SimpleXChat/FileUtils.swift#L86-L116)
+
+- Files are deleted when their associated `ChatItem` is deleted. See [`cleanupFile()`](../SimpleXChat/FileUtils.swift#L267) and [`cleanupDirectFile()`](../SimpleXChat/FileUtils.swift#L260).
+- Timed message expiry triggers file deletion
+- [`deleteAppDatabaseAndFiles()`](../SimpleXChat/FileUtils.swift#L86) removes all databases, files, temp files, and wallpapers
+- [`deleteAppFiles()`](../SimpleXChat/FileUtils.swift#L108) removes only the files directory (preserving databases)
+
+---
+
+## 7. Export & Import
+
+### Export
+
+```swift
+ChatCommand.apiExportArchive(config: ArchiveConfig)
+// Response: ChatResponse2.archiveExported(archiveErrors: [ArchiveError])
+```
+
+`ArchiveConfig` specifies:
+- `archivePath: String` -- destination path for the archive
+- `disableCompression: Bool?` -- optional flag to skip compression
+
+The archive contains both databases and optionally files. The Haskell core handles the actual export, creating a ZIP archive.
+
+### Import
+
+```swift
+ChatCommand.apiImportArchive(config: ArchiveConfig)
+// Response: ChatResponse2.archiveImported(archiveErrors: [ArchiveError])
+```
+
+Import replaces the current databases with the archive contents. The app must be restarted after import.
+
+### Archive Errors
+
+`ArchiveError` is an array returned with both export and import results, listing any non-fatal issues encountered (e.g., missing files, corrupt entries).
+
+---
+
+## 8. App Group Sharing
+
+### Shared Access Model
+
+The main app and NSE share database access through the iOS App Group container:
+
+```
+Main App ──┐
+ ├── {App Group}/simplex_v1_chat.db
+ ├── {App Group}/simplex_v1_agent.db
+NSE ────────┘
+```
+
+### Coordination
+
+- Both processes can initialize their own `chat_ctrl` instance pointing to the same database files
+- SQLite WAL mode allows concurrent reads
+- Write coordination uses `chat_close_store` / `chat_reopen_store` to manage database locks
+- The main app suspends its chat controller when entering background, allowing NSE to access the database
+- NSE is short-lived (~30 seconds per notification) and releases its lock quickly
+
+### App State Communication
+
+The `appStateGroupDefault` in `GroupDefaults` communicates app state between main app and NSE:
+- `.active` -- main app is in foreground
+- `.suspended` -- main app is in background
+- `.stopped` -- main app is terminated
+
+The NSE checks this flag to determine whether to process notifications (it avoids processing if the main app is active).
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| File utilities & constants | [`SimpleXChat/FileUtils.swift`](../SimpleXChat/FileUtils.swift) |
+| Database management UI | [`Shared/Views/Database/DatabaseView.swift`](../Shared/Views/Database/DatabaseView.swift) |
+| Encryption settings UI | [`Shared/Views/Database/DatabaseEncryptionView.swift`](../Shared/Views/Database/DatabaseEncryptionView.swift) |
+| C FFI (migration, file ops) | `SimpleXChat/SimpleX.h` |
+| Haskell store root | `../../src/Simplex/Chat/Store/` |
+| Haskell migrations | `../../src/Simplex/Chat/Store/SQLite/Migrations/` |
diff --git a/apps/ios/spec/impact.md b/apps/ios/spec/impact.md
new file mode 100644
index 0000000000..eaf646e7f4
--- /dev/null
+++ b/apps/ios/spec/impact.md
@@ -0,0 +1,119 @@
+# SimpleX Chat iOS -- Impact Graph
+
+> Source file → product concept mapping. Use this to identify which product documents must be updated when a source file changes.
+>
+> Derived from [CODE.md](../CODE.md) Document Map and [product/concepts.md](../product/concepts.md).
+
+---
+
+## Product Concept Legend
+
+| ID | Concept |
+|----|---------|
+| PC1 | Chat List |
+| PC2 | Direct Chat |
+| PC3 | Group Chat |
+| PC4 | Message Composition |
+| PC5 | Message Reactions |
+| PC6 | Message Editing |
+| PC7 | Message Deletion |
+| PC8 | Timed Messages |
+| PC9 | Voice Messages |
+| PC10 | File Transfer |
+| PC11 | Link Previews |
+| PC12 | Contact Connection |
+| PC13 | Contact Verification |
+| PC14 | Group Management |
+| PC15 | Group Links |
+| PC16 | Member Roles |
+| PC17 | Audio/Video Calls |
+| PC18 | Push Notifications |
+| PC19 | User Profiles |
+| PC20 | Incognito Mode |
+| PC21 | Hidden Profiles |
+| PC22 | Local Authentication |
+| PC23 | Database Encryption |
+| PC24 | Theme System |
+| PC25 | Network Configuration |
+| PC26 | Device Migration |
+| PC27 | Remote Desktop |
+| PC28 | Chat Tags |
+| PC29 | User Address |
+| PC30 | Member Support Chat |
+| PC31 | Channels (Relays) |
+
+---
+
+## 1. Swift Source Impact
+
+| Source File | Product Concepts Affected | Risk Level | Notes |
+|-------------|--------------------------|------------|-------|
+| Shared/ContentView.swift | PC1, PC2, PC3 | High | Root navigation — affects all chat access |
+| Shared/SimpleXApp.swift | PC1 through PC31 | High | App entry point — initialization affects everything |
+| Shared/AppDelegate.swift | PC18 | Medium | Push notification registration |
+| Shared/Views/ChatList/ChatListView.swift | PC1, PC28 | High | Main screen rendering and filtering |
+| Shared/Views/Chat/ChatView.swift | PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC11, PC31 | High | Core conversation UI — most messaging features, channel message rendering |
+| Shared/Views/Chat/ComposeMessage/ComposeView.swift | PC4, PC6, PC9, PC11, PC31 | High | Message composition — send path for all messages, channel sendAsGroup |
+| Shared/Views/Chat/ChatItem/ | PC2, PC3, PC5, PC7, PC8, PC9, PC10, PC11 | Medium | Individual message rendering components |
+| Shared/Views/Chat/ChatInfoView.swift | PC2, PC13, PC20 | Medium | Contact details and verification |
+| Shared/Views/Chat/Group/GroupChatInfoView.swift | PC3, PC14, PC15, PC16, PC30, PC31 | High | Group management hub, channel info adaptations |
+| Shared/Views/Chat/Group/ChannelMembersView.swift | PC31 | Medium | Channel owners/subscribers list |
+| Shared/Views/Chat/Group/ChannelRelaysView.swift | PC31 | Medium | Channel relay status list |
+| Shared/Views/Chat/Group/AddGroupMembersView.swift | PC14, PC16 | Medium | Member invitation flow |
+| Shared/Views/Chat/Group/GroupLinkView.swift | PC15 | Low | Group link creation/sharing |
+| Shared/Views/Chat/Group/GroupMemberInfoView.swift | PC3, PC14, PC16, PC30 | Medium | Member details and role management |
+| Shared/Views/NewChat/NewChatView.swift | PC12, PC31 | High | New connection creation — onramp for all contacts and channels |
+| Shared/Views/NewChat/QRCode.swift | PC12 | Low | QR code display/scanning utility |
+| Shared/Views/Call/ActiveCallView.swift | PC17 | Medium | Call UI rendering |
+| Shared/Views/Call/CallController.swift | PC17 | High | CallKit integration — call lifecycle |
+| Shared/Views/Call/WebRTCClient.swift | PC17 | High | WebRTC session management |
+| Shared/Views/UserSettings/SettingsView.swift | PC18, PC22, PC23, PC24, PC25, PC29 | Medium | Settings navigation hub |
+| Shared/Views/UserSettings/AppearanceSettings.swift | PC24 | Low | Theme customization UI |
+| Shared/Views/UserSettings/NetworkAndServers/ | PC25, PC31 | High | Server configuration — affects connectivity and relay validation |
+| Shared/Views/UserSettings/UserProfilesView.swift | PC19, PC21 | Medium | Profile management |
+| Shared/Views/Onboarding/ | PC1 | Medium | First-time setup — affects initial state |
+| Shared/Views/LocalAuth/ | PC22 | Medium | App lock functionality |
+| Shared/Views/Database/ | PC23, PC26 | High | Database encryption and export |
+| Shared/Views/Migration/ | PC26 | High | Device migration — data portability |
+| Shared/Model/ChatModel.swift | PC1 through PC31 | High | Central state — all features depend on it |
+| Shared/Model/SimpleXAPI.swift | PC1 through PC31 | High | FFI bridge — all commands flow through here |
+| Shared/Model/AppAPITypes.swift | PC1 through PC31 | High | Command/response types — all API communication |
+| Shared/Model/NtfManager.swift | PC18 | High | Notification delivery |
+| Shared/Model/BGManager.swift | PC18 | Medium | Background fetch scheduling |
+| Shared/Theme/ThemeManager.swift | PC24 | Medium | Theme resolution engine |
+| SimpleXChat/ChatTypes.swift | PC1 through PC31 | High | Core data types — all features use them |
+| SimpleXChat/APITypes.swift | PC1 through PC31 | High | API result types and error handling |
+| SimpleXChat/CallTypes.swift | PC17 | Medium | Call-specific data types |
+| SimpleXChat/FileUtils.swift | PC10, PC23, PC26 | Medium | File paths and encryption utilities |
+| SimpleXChat/Notifications.swift | PC18 | Medium | Notification type definitions |
+| SimpleX NSE/NotificationService.swift | PC18 | High | Push notification decryption and display |
+| Shared/Views/Chat/ChatItemsMerger.swift | PC2, PC3, PC31 | Low | Chat item merge categories — added channelRcv hash |
+| SimpleX SE/ShareAPI.swift | PC4, PC31 | Medium | Share extension API — sendAsGroup support |
+
+---
+
+## 2. Haskell Core Impact
+
+| Source File | Product Concepts Affected | Risk Level | Notes |
+|-------------|--------------------------|------------|-------|
+| src/Simplex/Chat/Controller.hs | PC1 through PC31 | High | Command processor — all API commands |
+| src/Simplex/Chat/Types.hs | PC1 through PC31 | High | Core data types shared across all features |
+| src/Simplex/Chat/Core.hs | PC1 through PC31 | High | Chat engine lifecycle |
+| src/Simplex/Chat/Protocol.hs | PC2, PC3, PC4, PC5, PC6, PC7 | High | Chat-level message protocol (x-events) |
+| src/Simplex/Chat/Messages.hs | PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9 | High | Message types and content |
+| src/Simplex/Chat/Messages/CIContent.hs | PC4, PC5, PC6, PC7, PC8, PC9, PC11 | Medium | Chat item content variants |
+| src/Simplex/Chat/Call.hs | PC17 | Medium | Call signaling types |
+| src/Simplex/Chat/Files.hs | PC10 | Medium | File transfer orchestration |
+| src/Simplex/Chat/Store/Messages.hs | PC4, PC5, PC6, PC7, PC8 | High | Message persistence |
+| src/Simplex/Chat/Store/Groups.hs | PC3, PC14, PC15, PC16, PC30 | High | Group persistence |
+| src/Simplex/Chat/Store/Direct.hs | PC2, PC12, PC13 | High | Contact persistence |
+| src/Simplex/Chat/Store/Files.hs | PC10 | Medium | File transfer persistence |
+| src/Simplex/Chat/Store/Profiles.hs | PC19, PC21 | Medium | User profile persistence |
+| src/Simplex/Chat/Store/Connections.hs | PC2, PC12 | High | Connection persistence and entity resolution |
+| src/Simplex/Chat/Archive.hs | PC26 | Medium | Database export/import for migration |
+| src/Simplex/Chat/ProfileGenerator.hs | PC20 | Low | Random profile generation for incognito |
+| src/Simplex/Chat/Remote.hs | PC27 | Medium | Remote desktop protocol handler |
+| src/Simplex/Chat/Remote/Types.hs | PC27 | Low | Remote desktop data types |
+| src/Simplex/Chat/Types/UITheme.hs | PC24 | Low | Theme data types for UI customization |
+| src/Simplex/Chat/Types/Preferences.hs | PC2, PC3, PC8 | Medium | Chat feature preferences (timed messages, etc.) |
+| src/Simplex/Chat/Types/Shared.hs | PC3, PC16 | Medium | Shared types including GroupMemberRole |
diff --git a/apps/ios/spec/services/calls.md b/apps/ios/spec/services/calls.md
new file mode 100644
index 0000000000..6a1d89f6a3
--- /dev/null
+++ b/apps/ios/spec/services/calls.md
@@ -0,0 +1,383 @@
+# SimpleX Chat iOS -- WebRTC Calling Service
+
+> Technical specification for the calling system: CallController, WebRTCClient, CallKit integration, and signaling via SMP.
+>
+> Related specs: [Architecture](../architecture.md) | [API Reference](../api.md) | [Notifications](notifications.md) | [README](../README.md)
+> Related product: [Chat View](../../product/views/chat.md)
+
+**Source:** [`CallController.swift`](../../Shared/Views/Call/CallController.swift) | [`WebRTCClient.swift`](../../Shared/Views/Call/WebRTCClient.swift) | [`ActiveCallView.swift`](../../Shared/Views/Call/ActiveCallView.swift) | [`CallTypes.swift`](../../SimpleXChat/CallTypes.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [CallController](#2-callcontroller)
+3. [WebRTCClient](#3-webrtcclient)
+4. [Call Flow via SMP](#4-call-flow-via-smp)
+5. [CallKit Integration](#5-callkit-integration)
+6. [CallKit-Free Mode](#6-callkit-free-mode)
+7. [Audio Routing](#7-audio-routing)
+8. [Key Types](#8-key-types)
+9. [ActiveCallView](#9-activecallview)
+
+---
+
+## 1. Overview
+
+SimpleX Chat provides end-to-end encrypted audio and video calls using WebRTC. The unique aspect is that all call signaling (SDP offers/answers, ICE candidates) is transmitted through the same encrypted SMP messaging channels used for chat, eliminating the need for a separate signaling server.
+
+```
+Caller SMP Relay Callee
+ │ │ │
+ ├─ apiSendCallInvitation ──────→│──── push/event ──────→│
+ │ │ │
+ │ │←── apiSendCallOffer ──┤
+ │←── ChatEvent.callOffer ───────│ │
+ │ │ │
+ ├─ apiSendCallAnswer ──────────→│──── callAnswer ──────→│
+ │ │ │
+ │←── callExtraInfo (ICE) ───────│←── apiSendCallExtraInfo│
+ ├─ apiSendCallExtraInfo ───────→│──── callExtraInfo ───→│
+ │ │ │
+ │◄══════════ WebRTC P2P Media Stream ═══════════════════►│
+ │ │ │
+ ├─ apiEndCall ─────────────────→│──── callEnded ───────→│
+```
+
+---
+
+## [2. CallController](../../Shared/Views/Call/CallController.swift#L19-L449)
+
+**File**: `Shared/Views/Call/CallController.swift`
+
+Central call coordinator that bridges SimpleX call protocol with iOS CallKit (or non-CallKit fallback).
+
+### [Class Definition](../../Shared/Views/Call/CallController.swift#L19-L48)
+
+```swift
+class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, ObservableObject {
+ static let shared = CallController()
+ static let isInChina = SKStorefront().countryCode == "CHN"
+ static func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
+
+ private let provider: CXProvider // CallKit provider
+ private let controller: CXCallController // CallKit controller
+ private let callManager: CallManager // Internal call state
+ private let registry: PKPushRegistry // VoIP push registration
+
+ @Published var activeCallInvitation: RcvCallInvitation?
+ var shouldSuspendChat: Bool = false
+ var fulfillOnConnect: CXAnswerCallAction? = nil
+}
+```
+
+### Key Responsibilities
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`reportNewIncomingCall()`](../../Shared/Views/Call/CallController.swift#L287) | Reports incoming call to CallKit for native UI | L287 |
+| [`reportOutgoingCall()`](../../Shared/Views/Call/CallController.swift#L328) | Reports outgoing call to CallKit | L328 |
+| [`provider(_:perform: CXAnswerCallAction)`](../../Shared/Views/Call/CallController.swift#L66) | Handles user answering via CallKit UI | L66 |
+| [`provider(_:perform: CXEndCallAction)`](../../Shared/Views/Call/CallController.swift#L96) | Handles user ending via CallKit UI | L96 |
+| [`provider(_:perform: CXStartCallAction)`](../../Shared/Views/Call/CallController.swift#L55) | Handles outgoing call start | L55 |
+| [`pushRegistry(_:didReceiveIncomingPushWith:)`](../../Shared/Views/Call/CallController.swift#L202) | Handles VoIP push tokens | L202 |
+| [`hasActiveCalls()`](../../Shared/Views/Call/CallController.swift#L435) | Checks if any calls are active | L435 |
+
+### Call Manager (internal)
+
+`CallManager` tracks call state internally:
+- Maps call UUIDs to `Call` objects
+- Handles call state transitions
+- Coordinates between CallKit actions and SimpleX API calls
+
+---
+
+## [3. WebRTCClient](../../Shared/Views/Call/WebRTCClient.swift#L13-L676)
+
+**File**: `Shared/Views/Call/WebRTCClient.swift` (~49KB)
+
+Manages the WebRTC peer connection, media streams, and data channels.
+
+### Responsibilities
+
+- Creates and configures `RTCPeerConnection`
+- Manages local audio/video capture (`RTCCameraVideoCapturer`, `RTCAudioTrack`)
+- Handles SDP offer/answer creation and application
+- Processes ICE candidates
+- Manages media stream encryption
+
+### Key Operations
+
+| Operation | Description | Line |
+|-----------|-------------|------|
+| [`initializeCall`](../../Shared/Views/Call/WebRTCClient.swift#L93) | Sets up peer connection, tracks, encryption | L93 |
+| [`createPeerConnection`](../../Shared/Views/Call/WebRTCClient.swift#L139) | Creates and configures RTCPeerConnection | L139 |
+| [`sendCallCommand`](../../Shared/Views/Call/WebRTCClient.swift#L176) | Dispatches WCallCommand (offer/answer/ICE) | L176 |
+| [`addIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L165) | `peerConnection.add(RTCIceCandidate)` | L165 |
+| [`getInitialIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L285) | Collects initial ICE candidates | L285 |
+| [`sendIceCandidates`](../../Shared/Views/Call/WebRTCClient.swift#L305) | Sends gathered ICE candidates | L305 |
+| [`enableMedia`](../../Shared/Views/Call/WebRTCClient.swift#L365) | Enable/disable audio or video track | L365 |
+| [`setupLocalTracks`](../../Shared/Views/Call/WebRTCClient.swift#L423) | Creates audio/video tracks and adds to connection | L423 |
+| [`startCaptureLocalVideo`](../../Shared/Views/Call/WebRTCClient.swift#L581) | Front/back camera toggle and capture start | L581 |
+| [`endCall`](../../Shared/Views/Call/WebRTCClient.swift#L645) | Tears down connection and tracks | L645 |
+| [`setupEncryptionForLocalTracks`](../../Shared/Views/Call/WebRTCClient.swift#L503) | Sets up frame encryption for local media tracks | L503 |
+
+### [Additional Encryption](../../Shared/Views/Call/WebRTCClient.swift#L513-L546)
+
+Beyond WebRTC's built-in SRTP encryption, SimpleX adds an extra encryption layer:
+- A shared key from the E2E SMP channel is used
+- Applied via `chat_encrypt_media` / `chat_decrypt_media` C FFI functions
+- Each media frame is encrypted/decrypted with this additional key
+- Provides defense-in-depth even if SRTP is compromised
+
+---
+
+## 4. Call Flow via SMP
+
+All call signaling travels through the same encrypted SMP message channels used for chat. No separate signaling server is needed.
+
+### Outgoing Call (Caller Side)
+
+```
+1. User initiates call
+ └── apiSendCallInvitation(contact:, callType:)
+ └── Sends CallInvitation via SMP to contact
+
+2. Callee accepts, sends SDP offer
+ └── ChatEvent.callOffer received
+ └── WebRTCClient creates answer
+ └── apiSendCallAnswer(contact:, answer:)
+
+3. ICE candidates exchanged
+ └── ChatEvent.callExtraInfo received → WebRTCClient.addIceCandidate()
+ └── WebRTCClient generates candidates → apiSendCallExtraInfo(contact:, extraInfo:)
+
+4. P2P connection established
+ └── Media streams flowing
+
+5. End call
+ └── apiEndCall(contact:)
+```
+
+### Incoming Call (Callee Side)
+
+```
+1. ChatEvent.callInvitation received (or push notification)
+ └── CallController reports to CallKit (or shows in-app notification)
+
+2. User accepts
+ └── WebRTCClient creates SDP offer (callee creates offer in SimpleX protocol)
+ └── apiSendCallOffer(contact:, callOffer:)
+
+3. Caller sends answer
+ └── ChatEvent.callAnswer received
+ └── WebRTCClient.setRemoteDescription(answer)
+
+4. ICE candidates exchanged (same as above)
+
+5. P2P connection established
+```
+
+### API Commands
+
+| Command | Direction | Purpose |
+|---------|-----------|---------|
+| `apiSendCallInvitation(contact:, callType:)` | Caller -> Callee | Initiate call |
+| `apiRejectCall(contact:)` | Callee -> Caller | Reject call |
+| `apiSendCallOffer(contact:, callOffer:)` | Callee -> Caller | Send SDP offer |
+| `apiSendCallAnswer(contact:, answer:)` | Caller -> Callee | Send SDP answer |
+| `apiSendCallExtraInfo(contact:, extraInfo:)` | Both | Send ICE candidates |
+| `apiEndCall(contact:)` | Either | End call |
+| `apiGetCallInvitations` | -- | Get pending invitations |
+| `apiCallStatus(contact:, callStatus:)` | -- | Report status change |
+
+---
+
+## [5. CallKit Integration](../../Shared/Views/Call/CallController.swift#L24-L155)
+
+CallKit provides the native iOS incoming call experience (lock screen UI, call history, system call handling).
+
+### [CXProvider Configuration](../../Shared/Views/Call/CallController.swift#L24-L37)
+
+```swift
+let configuration = CXProviderConfiguration()
+configuration.supportsVideo = true
+configuration.supportedHandleTypes = [.generic]
+configuration.includesCallsInRecents = UserDefaults.standard.bool(
+ forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS
+)
+configuration.maximumCallGroups = 1
+configuration.maximumCallsPerCallGroup = 1
+configuration.iconTemplateImageData = UIImage(named: "icon-transparent")?.pngData()
+```
+
+### [VoIP Push (PKPushRegistry)](../../Shared/Views/Call/CallController.swift#L207-L284)
+
+CallKit requires VoIP push for incoming calls on locked device:
+- `PKPushRegistry` registers for `.voIP` push type
+- VoIP push token is separate from regular APNs token
+- When VoIP push received, **must** report an incoming call to CallKit within the callback (iOS requirement)
+
+### CallKit Actions
+
+| CXAction | Handler | Description | Line |
+|----------|---------|-------------|------|
+| `CXStartCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L55) | User starts outgoing call | L55 |
+| `CXAnswerCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L66) | User answers incoming call from CallKit UI | L66 |
+| `CXEndCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L96) | User ends call from CallKit UI | L96 |
+| `CXSetMutedCallAction` | [`provider(_:perform:)`](../../Shared/Views/Call/CallController.swift#L112) | User mutes from CallKit UI | L112 |
+
+### [Lock Screen Answer](../../Shared/Views/Call/CallController.swift#L66-L94)
+
+When answering from the lock screen:
+1. `CXAnswerCallAction` fires
+2. CallController waits for chat to be ready ([`waitUntilChatStarted(timeoutMs: 30_000)`](../../Shared/Views/Call/CallController.swift#L183))
+3. WebRTC connection established
+4. `fulfillOnConnect` action is fulfilled only when WebRTC reaches connected state (required for audio to work on lock screen)
+
+---
+
+## [6. CallKit-Free Mode](../../Shared/Views/Call/CallController.swift#L21-L22)
+
+In regions where CallKit is unavailable (e.g., China, determined by `SKStorefront.countryCode == "CHN"`), the app falls back to in-app notifications:
+
+```swift
+static let isInChina = SKStorefront().countryCode == "CHN"
+static func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
+```
+
+### Non-CallKit Behavior
+- Incoming calls shown as in-app banners (via `CallController.activeCallInvitation`)
+- No lock screen call UI
+- No system call integration
+- User can also manually disable CallKit via settings (`callKitEnabledGroupDefault`)
+
+---
+
+## [7. Audio Routing](../../Shared/Views/Call/WebRTCClient.swift#L907-L1005)
+
+### [AVAudioSession Management](../../Shared/Views/Call/WebRTCClient.swift#L907-L950)
+
+Audio routing is managed through `AVAudioSession`:
+- **Receiver**: Default for audio-only calls (ear speaker)
+- **Speaker**: For video calls or when user toggles speaker
+- **Bluetooth**: Detected and used when available
+- **Headphones**: Detected and used when connected
+
+### Route Change Handling
+
+The `WebRTCClient` observes `AVAudioSession.routeChangeNotification` to handle:
+- Bluetooth device connection/disconnection
+- Headphone plug/unplug
+- Speaker/receiver toggle
+
+---
+
+## [8. Key Types](../../SimpleXChat/CallTypes.swift#L1-L115)
+
+### [RcvCallInvitation](../../SimpleXChat/CallTypes.swift#L45-L71)
+
+```swift
+struct RcvCallInvitation {
+ var user: User
+ var contact: Contact
+ var callType: CallType
+ var sharedKey: String? // Optional E2E encryption key
+ var callUUID: String?
+ var callTs: Date
+}
+```
+
+### [CallType](../../SimpleXChat/CallTypes.swift#L74-L82)
+
+```swift
+struct CallType {
+ var media: CallMediaType // .audio or .video
+ var capabilities: CallCapabilities
+}
+
+enum CallMediaType: String {
+ case audio
+ case video
+}
+```
+
+### [WebRTCCallOffer](../../SimpleXChat/CallTypes.swift#L14-L22) / [WebRTCSession](../../SimpleXChat/CallTypes.swift#L25-L33)
+
+```swift
+struct WebRTCCallOffer {
+ var callType: CallType
+ var rtcSession: WebRTCSession
+}
+
+struct WebRTCSession {
+ var rtcSession: String // SDP string
+ var rtcIceCandidates: String // ICE candidates JSON
+}
+```
+
+### [WebRTCExtraInfo](../../SimpleXChat/CallTypes.swift#L36-L42)
+
+```swift
+struct WebRTCExtraInfo {
+ var rtcIceCandidates: String // Additional ICE candidates
+}
+```
+
+### Call (Active Call State)
+
+Stored in `ChatModel.activeCall`:
+- Contact reference
+- Call UUID
+- Call state (enum: `.waitCapabilities`, `.invitationAccepted`, `.offerSent`, `.answerReceived`, `.connected`, etc.)
+- Media type
+- WebRTCClient reference
+
+---
+
+## [9. ActiveCallView](../../Shared/Views/Call/ActiveCallView.swift#L16-L285)
+
+**File**: `Shared/Views/Call/ActiveCallView.swift`
+
+Full-screen call UI when `ChatModel.showCallView == true`:
+
+### UI Elements
+- Remote video (full screen background)
+- Local video (PiP corner, draggable)
+- Contact name and call duration
+- Control buttons: mute, camera toggle, speaker toggle, camera flip, end call
+- Minimize button (collapses to banner)
+
+### [ActiveCallOverlay](../../Shared/Views/Call/ActiveCallView.swift#L288-L522)
+
+| Control | Method | Line |
+|---------|--------|------|
+| Audio call info | [`audioCallInfoView`](../../Shared/Views/Call/ActiveCallView.swift#L357) | L357 |
+| Video call info | [`videoCallInfoView`](../../Shared/Views/Call/ActiveCallView.swift#L377) | L377 |
+| End call | [`endCallButton`](../../Shared/Views/Call/ActiveCallView.swift#L407) | L407 |
+| Mute toggle | [`toggleMicButton`](../../Shared/Views/Call/ActiveCallView.swift#L418) | L418 |
+| Audio device | [`audioDeviceButton`](../../Shared/Views/Call/ActiveCallView.swift#L428) | L428 |
+| Speaker toggle | [`toggleSpeakerButton`](../../Shared/Views/Call/ActiveCallView.swift#L452) | L452 |
+| Camera toggle | [`toggleCameraButton`](../../Shared/Views/Call/ActiveCallView.swift#L464) | L464 |
+| Flip camera | [`flipCameraButton`](../../Shared/Views/Call/ActiveCallView.swift#L475) | L475 |
+
+### PiP (Picture-in-Picture)
+
+When `ChatModel.activeCallViewIsCollapsed == true`:
+- Call view collapses to a small floating overlay
+- User can return to full-screen by tapping the banner
+- Navigation continues normally underneath
+
+---
+
+## Source Files
+
+| File | Path | Lines |
+|------|------|-------|
+| [Call controller](../../Shared/Views/Call/CallController.swift) | `Shared/Views/Call/CallController.swift` | 449 |
+| [WebRTC client](../../Shared/Views/Call/WebRTCClient.swift) | `Shared/Views/Call/WebRTCClient.swift` | 1139 |
+| [Active call UI](../../Shared/Views/Call/ActiveCallView.swift) | `Shared/Views/Call/ActiveCallView.swift` | 528 |
+| WebRTC helpers | `Shared/Views/Call/WebRTC.swift` | |
+| [Call types (Swift)](../../SimpleXChat/CallTypes.swift) | `SimpleXChat/CallTypes.swift` | 115 |
+| Call types (Haskell) | `../../src/Simplex/Chat/Call.hs` | |
diff --git a/apps/ios/spec/services/files.md b/apps/ios/spec/services/files.md
new file mode 100644
index 0000000000..7e1f8a2ad1
--- /dev/null
+++ b/apps/ios/spec/services/files.md
@@ -0,0 +1,368 @@
+# SimpleX Chat iOS -- File Transfer Service
+
+> Technical specification for file transfer: inline/XFTP protocols, auto-receive thresholds, CryptoFile encryption, and file constants.
+>
+> Related specs: [Compose Module](../client/compose.md) | [Chat View](../client/chat-view.md) | [API Reference](../api.md) | [Database](../database.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`FileUtils.swift`](../../SimpleXChat/FileUtils.swift) | [`CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift) | [`ChatTypes.swift`](../../SimpleXChat/ChatTypes.swift) | [`AppAPITypes.swift`](../../Shared/Model/AppAPITypes.swift) | [`SimpleXAPI.swift`](../../Shared/Model/SimpleXAPI.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Transfer Methods](#2-transfer-methods)
+3. [Auto-Receive Thresholds](#3-auto-receive-thresholds)
+4. [File Size Constants](#4-file-size-constants)
+5. [Image Handling](#5-image-handling)
+6. [Voice Messages](#6-voice-messages)
+7. [CryptoFile -- At-Rest Encryption](#7-cryptofile)
+8. [File Storage Paths](#8-file-storage-paths)
+9. [File Lifecycle](#9-file-lifecycle)
+10. [API Commands](#10-api-commands)
+
+---
+
+## 1. Overview
+
+SimpleX Chat supports two file transfer methods depending on file size:
+
+```
+File ≤ 255KB (inline)
+├── Base64 encoded directly in SMP message
+├── Single message delivery
+└── No extra server infrastructure needed
+
+File > 255KB up to 1GB (XFTP)
+├── Encrypted and chunked
+├── Uploaded to XFTP relay servers
+├── Recipient downloads chunks from relays
+└── Files auto-deleted from relays after download or expiry
+```
+
+All files are end-to-end encrypted. The XFTP protocol adds a second encryption layer on top of the SMP channel encryption.
+
+---
+
+## 2. Transfer Methods
+
+### Inline Transfer
+
+- Files up to [`MAX_IMAGE_SIZE`](../../SimpleXChat/FileUtils.swift#L18) (255KB) are base64-encoded and embedded directly in the SMP message body
+- No additional protocol or server needed
+- Delivered with the same reliability guarantees as regular messages
+- Used primarily for compressed images
+
+### XFTP Transfer
+
+For files exceeding the inline threshold (up to [`MAX_FILE_SIZE_XFTP`](../../SimpleXChat/FileUtils.swift#L30) = 1GB):
+
+1. **Sender side**:
+ - File is AES-encrypted with a random key
+ - Encrypted file is split into chunks
+ - Chunks are uploaded to one or more XFTP relay servers
+ - File metadata (key, chunk locations) sent to recipient via SMP message
+
+2. **Recipient side**:
+ - Receives file metadata via SMP
+ - Downloads chunks from XFTP relays
+ - Reassembles and decrypts the file
+
+3. **Cleanup**:
+ - XFTP relays delete chunks after download or after expiry period
+ - No persistent storage on relays
+
+### SMP Transfer (legacy)
+
+[`MAX_FILE_SIZE_SMP`](../../SimpleXChat/FileUtils.swift#L34) (8MB) exists as a constant for larger inline transfers through SMP, used in specific scenarios.
+
+---
+
+## 3. Auto-Receive Thresholds
+
+Files below certain size thresholds are automatically accepted and downloaded without user confirmation:
+
+| Media Type | Auto-Receive Threshold | Constant | Line |
+|------------|----------------------|----------|------|
+| Images | 510 KB | [`MAX_IMAGE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L21) | [L21](../../SimpleXChat/FileUtils.swift#L21) |
+| Voice messages | 510 KB | [`MAX_VOICE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L24) | [L24](../../SimpleXChat/FileUtils.swift#L24) |
+| Video | 1023 KB | [`MAX_VIDEO_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L27) | [L27](../../SimpleXChat/FileUtils.swift#L27) |
+| Other files | Not auto-received | Requires manual acceptance | -- |
+
+### Behavior
+
+- When a message with a file attachment arrives:
+ 1. Check if file size is below the auto-receive threshold for its type
+ 2. If below: automatically call [`setFileToReceive(fileId:, userApprovedRelays:, encrypted:)`](../../Shared/Model/AppAPITypes.swift#L168) followed by download
+ 3. If above: show download button in chat item, wait for user action
+ 4. User manually triggers download via [`receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)`](../../Shared/Model/AppAPITypes.swift#L167)
+
+### Relay Approval
+
+`userApprovedRelays` parameter: when the file is hosted on relays not in the user's configured server list, the user is asked for confirmation before connecting to unknown relays.
+
+---
+
+## [4. File Size Constants](../../SimpleXChat/FileUtils.swift#L18)
+
+Defined in [`SimpleXChat/FileUtils.swift`](../../SimpleXChat/FileUtils.swift):
+
+| Constant | Value | Line |
+|----------|-------|------|
+| `MAX_IMAGE_SIZE` | 261,120 (255 KB) | [L18](../../SimpleXChat/FileUtils.swift#L18) |
+| `MAX_IMAGE_SIZE_AUTO_RCV` | 522,240 (510 KB) | [L21](../../SimpleXChat/FileUtils.swift#L21) |
+| `MAX_VOICE_SIZE_AUTO_RCV` | 522,240 (510 KB) | [L24](../../SimpleXChat/FileUtils.swift#L24) |
+| `MAX_VIDEO_SIZE_AUTO_RCV` | 1,047,552 (1023 KB) | [L27](../../SimpleXChat/FileUtils.swift#L27) |
+| `MAX_FILE_SIZE_XFTP` | 1,073,741,824 (1 GB) | [L30](../../SimpleXChat/FileUtils.swift#L30) |
+| `MAX_FILE_SIZE_LOCAL` | Int64.max (no limit) | [L32](../../SimpleXChat/FileUtils.swift#L32) |
+| `MAX_FILE_SIZE_SMP` | 8,000,000 (~7.6 MB) | [L34](../../SimpleXChat/FileUtils.swift#L34) |
+| `MAX_VOICE_MESSAGE_LENGTH` | 300 s (5 min) | [L36](../../SimpleXChat/FileUtils.swift#L36) |
+
+```swift
+// Image compression target for inline transfer
+public let MAX_IMAGE_SIZE: Int64 = 261_120 // 255 KB
+
+// Auto-receive thresholds
+public let MAX_IMAGE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB (2 * MAX_IMAGE_SIZE)
+public let MAX_VOICE_SIZE_AUTO_RCV: Int64 = 522_240 // 510 KB (2 * MAX_IMAGE_SIZE)
+public let MAX_VIDEO_SIZE_AUTO_RCV: Int64 = 1_047_552 // 1023 KB
+
+// Transfer method limits
+public let MAX_FILE_SIZE_XFTP: Int64 = 1_073_741_824 // 1 GB
+public let MAX_FILE_SIZE_SMP: Int64 = 8_000_000 // ~7.6 MB
+public let MAX_FILE_SIZE_LOCAL: Int64 = Int64.max // No limit (local notes)
+
+// Voice message constraints
+public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) // 5 minutes (300 seconds)
+```
+
+---
+
+## 5. Image Handling
+
+### Compression Pipeline
+
+1. User selects image (camera or photo library)
+2. Image is compressed to fit within [`MAX_IMAGE_SIZE`](../../SimpleXChat/FileUtils.swift#L18) (255KB):
+ - Progressive JPEG compression with decreasing quality
+ - Resize if dimensions are too large
+3. Compressed image is base64-encoded into the message content
+4. For larger images that cannot compress to 255KB: sent via XFTP
+
+### Display
+
+- `CIImageView` renders images in chat bubbles with aspect-fit sizing
+- Tapping opens `FullScreenMediaView` with zoom/pan/share capabilities
+- Thumbnail is displayed immediately; full-size loaded on demand for XFTP images
+
+### Animated Images
+
+- GIFs are handled by `AnimatedImageView`
+- Displayed inline with animation support
+
+---
+
+## 6. Voice Messages
+
+### Recording
+
+1. `ComposeVoiceView` manages the recording UI
+2. `AudioRecPlay` handles `AVAudioRecorder` lifecycle
+3. Recorded in compressed audio format
+4. Maximum duration: [`MAX_VOICE_MESSAGE_LENGTH`](../../SimpleXChat/FileUtils.swift#L36) = 300 seconds (5 minutes)
+5. Waveform data extracted for visualization
+
+### Transfer
+
+- Voice files up to [`MAX_VOICE_SIZE_AUTO_RCV`](../../SimpleXChat/FileUtils.swift#L24) (510KB) are auto-received
+- Larger voice files follow standard file transfer flow
+- Voice messages include waveform metadata for UI rendering
+
+### Playback
+
+- `CIVoiceView` / `FramedCIVoiceView` render voice messages
+- Shows waveform visualization and play/pause control
+- `ChatModel.stopPreviousRecPlay` ensures only one audio source plays at a time
+- Playback position and progress tracked
+
+---
+
+## [7. CryptoFile -- At-Rest Encryption](../../SimpleXChat/ChatTypes.swift#L4241)
+
+When [`apiSetEncryptLocalFiles(enable: true)`](../../Shared/Model/SimpleXAPI.swift#L384) is configured, files stored on the device are AES-encrypted.
+
+### [`CryptoFile`](../../SimpleXChat/ChatTypes.swift#L4241) Type
+
+```swift
+struct CryptoFile {
+ var filePath: String
+ var cryptoArgs: CryptoFileArgs? // nil = unencrypted
+}
+
+struct CryptoFileArgs {
+ var fileKey: String // AES encryption key
+ var fileNonce: String // AES nonce/IV
+}
+```
+
+> Defined in [`ChatTypes.swift` L4241](../../SimpleXChat/ChatTypes.swift#L4241) (`CryptoFile`) and [L4289](../../SimpleXChat/ChatTypes.swift#L4289) (`CryptoFileArgs`).
+
+### Encryption Operations (C FFI)
+
+Implemented in [`CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift):
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`writeCryptoFile`](../../SimpleXChat/CryptoFile.swift#L18) | Write encrypted file, returns `CryptoFileArgs` | [L18](../../SimpleXChat/CryptoFile.swift#L18) |
+| [`readCryptoFile`](../../SimpleXChat/CryptoFile.swift#L31) | Read and decrypt file, returns `Data` | [L31](../../SimpleXChat/CryptoFile.swift#L31) |
+| [`encryptCryptoFile`](../../SimpleXChat/CryptoFile.swift#L54) | Encrypt existing file to new path | [L54](../../SimpleXChat/CryptoFile.swift#L54) |
+| [`decryptCryptoFile`](../../SimpleXChat/CryptoFile.swift#L66) | Decrypt file to new path | [L66](../../SimpleXChat/CryptoFile.swift#L66) |
+
+### Storage
+
+- Encrypted files stored alongside unencrypted files in `Documents/files/`
+- The `CryptoFileArgs` (key + nonce) are stored in the Haskell database, not on the filesystem
+- Toggle via privacy settings: [`apiSetEncryptLocalFiles(enable:)`](../../Shared/Model/SimpleXAPI.swift#L384)
+
+---
+
+## [8. File Storage Paths](../../SimpleXChat/FileUtils.swift#L199)
+
+### Directory Structure
+
+| Function | Path | Line |
+|----------|------|------|
+| [`getAppFilesDirectory()`](../../SimpleXChat/FileUtils.swift#L208) | `Documents/files/` | [L208](../../SimpleXChat/FileUtils.swift#L208) |
+| [`getTempFilesDirectory()`](../../SimpleXChat/FileUtils.swift#L199) | `Documents/temp_files/` | [L199](../../SimpleXChat/FileUtils.swift#L199) |
+| [`getWallpaperDirectory()`](../../SimpleXChat/FileUtils.swift#L217) | `Documents/wallpapers/` | [L217](../../SimpleXChat/FileUtils.swift#L217) |
+| [`getAppFilePath(_:)`](../../SimpleXChat/FileUtils.swift#L212) | `Documents/files/{filename}` | [L212](../../SimpleXChat/FileUtils.swift#L212) |
+| [`getWallpaperFilePath(_:)`](../../SimpleXChat/FileUtils.swift#L221) | `Documents/wallpapers/{filename}` | [L221](../../SimpleXChat/FileUtils.swift#L221) |
+
+```swift
+func getAppFilesDirectory() -> URL // Documents/files/
+func getTempFilesDirectory() -> URL // Documents/temp_files/
+func getWallpaperDirectory() -> URL // Documents/wallpapers/
+```
+
+### Path Management
+
+- Downloaded files: `Documents/files/{filename}`
+- Temporary files during transfer: `Documents/temp_files/`
+- Wallpaper images: `Documents/wallpapers/`
+- File paths are set via [`apiSetAppFilePaths(filesFolder:, tempFolder:, assetsFolder:)`](../../Shared/Model/SimpleXAPI.swift#L377) at startup
+
+---
+
+## 9. File Lifecycle
+
+### Sending
+
+```
+1. User selects file/image/video in compose
+2. ComposeView creates ComposedMessage with file reference
+3. apiSendMessages() → Haskell core processes:
+ a. File ≤ inline threshold: base64 encode into message
+ b. File > inline threshold: start XFTP upload
+4. Upload events:
+ - ChatEvent.sndFileStart
+ - ChatEvent.sndFileProgressXFTP (periodic progress)
+ - ChatEvent.sndFileCompleteXFTP (upload done)
+ - ChatEvent.sndFileError (on failure)
+```
+
+### Receiving
+
+```
+1. Message with file attachment arrives
+2. Auto-receive check:
+ a. Below threshold: automatic download starts
+ b. Above threshold: user sees download button
+3. User triggers download (or auto-triggered):
+ - receiveFile(fileId:, userApprovedRelays:, encrypted:, inline:)
+4. Download events:
+ - ChatEvent.rcvFileStart
+ - ChatEvent.rcvFileProgressXFTP (periodic progress)
+ - ChatEvent.rcvFileComplete (download done)
+ - ChatEvent.rcvFileError (on failure)
+ - ChatEvent.rcvFileSndCancelled (sender cancelled)
+```
+
+### Cancellation
+
+```swift
+ChatCommand.cancelFile(fileId: Int64)
+```
+
+Cancels an in-progress upload or download. For XFTP transfers, also requests chunk deletion from relays.
+
+### Cleanup
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`cleanupFile(_:)`](../../SimpleXChat/FileUtils.swift#L267) | Remove file associated with a chat item | [L267](../../SimpleXChat/FileUtils.swift#L267) |
+| [`cleanupDirectFile(_:)`](../../SimpleXChat/FileUtils.swift#L260) | Remove file only for direct chats | [L260](../../SimpleXChat/FileUtils.swift#L260) |
+| [`removeFile(_:)`](../../SimpleXChat/FileUtils.swift#L243) | Delete file at URL | [L243](../../SimpleXChat/FileUtils.swift#L243) |
+| [`removeFile(_:)`](../../SimpleXChat/FileUtils.swift#L251) | Delete file by name | [L251](../../SimpleXChat/FileUtils.swift#L251) |
+| [`deleteAppFiles()`](../../SimpleXChat/FileUtils.swift#L108) | Remove all app files (preserving databases) | [L108](../../SimpleXChat/FileUtils.swift#L108) |
+| [`deleteAppDatabaseAndFiles()`](../../SimpleXChat/FileUtils.swift#L86) | Remove everything | [L86](../../SimpleXChat/FileUtils.swift#L86) |
+
+- When a `ChatItem` is deleted, its associated file is deleted from disk
+- When a timed message expires, its file is deleted
+- `ChatModel.filesToDelete` queues files for deferred deletion
+- [`deleteAppFiles()`](../../SimpleXChat/FileUtils.swift#L108) removes all files (preserving databases)
+- [`deleteAppDatabaseAndFiles()`](../../SimpleXChat/FileUtils.swift#L86) removes everything
+
+---
+
+## [10. API Commands](../../Shared/Model/AppAPITypes.swift#L167)
+
+| Command | Parameters | Description | Line |
+|---------|-----------|-------------|------|
+| [`receiveFile`](../../Shared/Model/AppAPITypes.swift#L167) | `fileId, userApprovedRelays, encrypted, inline` | Accept and start downloading a file | [L167](../../Shared/Model/AppAPITypes.swift#L167) |
+| [`setFileToReceive`](../../Shared/Model/AppAPITypes.swift#L168) | `fileId, userApprovedRelays, encrypted` | Mark file for auto-receive (no immediate download) | [L168](../../Shared/Model/AppAPITypes.swift#L168) |
+| [`cancelFile`](../../Shared/Model/AppAPITypes.swift#L169) | `fileId` | Cancel in-progress transfer | [L169](../../Shared/Model/AppAPITypes.swift#L169) |
+| [`apiUploadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L179) | `userId, file: CryptoFile` | Upload file to XFTP without a chat context | [L179](../../Shared/Model/AppAPITypes.swift#L179) |
+| [`apiDownloadStandaloneFile`](../../Shared/Model/AppAPITypes.swift#L180) | `userId, url, file: CryptoFile` | Download from XFTP URL | [L180](../../Shared/Model/AppAPITypes.swift#L180) |
+| [`apiStandaloneFileInfo`](../../Shared/Model/AppAPITypes.swift#L181) | `url` | Get metadata for an XFTP URL | [L181](../../Shared/Model/AppAPITypes.swift#L181) |
+
+### File Transfer Events
+
+| Event | Description | Line |
+|-------|-------------|------|
+| [`rcvFileAccepted`](../../Shared/Model/AppAPITypes.swift#L1095) | Download request accepted | [L1095](../../Shared/Model/AppAPITypes.swift#L1095) |
+| [`rcvFileStart`](../../Shared/Model/AppAPITypes.swift#L1097) | Download started | [L1097](../../Shared/Model/AppAPITypes.swift#L1097) |
+| [`rcvFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1098) | Download progress (receivedSize, totalSize) | [L1098](../../Shared/Model/AppAPITypes.swift#L1098) |
+| [`rcvFileComplete`](../../Shared/Model/AppAPITypes.swift#L1099) | Download complete | [L1099](../../Shared/Model/AppAPITypes.swift#L1099) |
+| [`rcvFileSndCancelled`](../../Shared/Model/AppAPITypes.swift#L1101) | Sender cancelled the transfer | [L1101](../../Shared/Model/AppAPITypes.swift#L1101) |
+| [`rcvFileError`](../../Shared/Model/AppAPITypes.swift#L1102) | Download failed | [L1102](../../Shared/Model/AppAPITypes.swift#L1102) |
+| [`rcvFileWarning`](../../Shared/Model/AppAPITypes.swift#L1103) | Download warning (non-fatal) | [L1103](../../Shared/Model/AppAPITypes.swift#L1103) |
+| [`sndFileStart`](../../Shared/Model/AppAPITypes.swift#L1105) | Upload started | [L1105](../../Shared/Model/AppAPITypes.swift#L1105) |
+| [`sndFileComplete`](../../Shared/Model/AppAPITypes.swift#L1106) | Inline upload complete | [L1106](../../Shared/Model/AppAPITypes.swift#L1106) |
+| [`sndFileProgressXFTP`](../../Shared/Model/AppAPITypes.swift#L1108) | XFTP upload progress (sentSize, totalSize) | [L1108](../../Shared/Model/AppAPITypes.swift#L1108) |
+| [`sndFileCompleteXFTP`](../../Shared/Model/AppAPITypes.swift#L1110) | XFTP upload complete | [L1110](../../Shared/Model/AppAPITypes.swift#L1110) |
+| [`sndFileRcvCancelled`](../../Shared/Model/AppAPITypes.swift#L1107) | Receiver cancelled | [L1107](../../Shared/Model/AppAPITypes.swift#L1107) |
+| [`sndFileError`](../../Shared/Model/AppAPITypes.swift#L1112) | Upload failed | [L1112](../../Shared/Model/AppAPITypes.swift#L1112) |
+| [`sndFileWarning`](../../Shared/Model/AppAPITypes.swift#L1113) | Upload warning (non-fatal) | [L1113](../../Shared/Model/AppAPITypes.swift#L1113) |
+
+---
+
+## Source Files
+
+| File | Path | Key Definitions |
+|------|------|-----------------|
+| File utilities & constants | [`SimpleXChat/FileUtils.swift`](../../SimpleXChat/FileUtils.swift) | `MAX_IMAGE_SIZE`, `saveFile`, `removeFile`, `getMaxFileSize` |
+| CryptoFile FFI operations | [`SimpleXChat/CryptoFile.swift`](../../SimpleXChat/CryptoFile.swift) | `writeCryptoFile`, `readCryptoFile`, `encryptCryptoFile`, `decryptCryptoFile` |
+| CryptoFile / CryptoFileArgs types | [`SimpleXChat/ChatTypes.swift`](../../SimpleXChat/ChatTypes.swift) | `CryptoFile` (L4241), `CryptoFileArgs` (L4289) |
+| API command definitions | [`Shared/Model/AppAPITypes.swift`](../../Shared/Model/AppAPITypes.swift) | `receiveFile`, `cancelFile`, `ChatEvent` file events |
+| API implementations | [`Shared/Model/SimpleXAPI.swift`](../../Shared/Model/SimpleXAPI.swift) | `receiveFile` (L1471), `cancelFile` (L1590) |
+| File view (chat item) | [`Shared/Views/Chat/ChatItem/CIFileView.swift`](../../Shared/Views/Chat/ChatItem/CIFileView.swift) | |
+| Image view (chat item) | [`Shared/Views/Chat/ChatItem/CIImageView.swift`](../../Shared/Views/Chat/ChatItem/CIImageView.swift) | |
+| Video view (chat item) | [`Shared/Views/Chat/ChatItem/CIVideoView.swift`](../../Shared/Views/Chat/ChatItem/CIVideoView.swift) | |
+| Voice view (chat item) | [`Shared/Views/Chat/ChatItem/CIVoiceView.swift`](../../Shared/Views/Chat/ChatItem/CIVoiceView.swift) | |
+| Compose file preview | [`Shared/Views/Chat/ComposeMessage/ComposeFileView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeFileView.swift) | |
+| Compose image preview | [`Shared/Views/Chat/ComposeMessage/ComposeImageView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeImageView.swift) | |
+| Compose voice preview | [`Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift`](../../Shared/Views/Chat/ComposeMessage/ComposeVoiceView.swift) | |
+| C FFI (file encryption) | [`SimpleXChat/SimpleX.h`](../../SimpleXChat/SimpleX.h) | `chat_write_file`, `chat_read_file`, `chat_encrypt_file`, `chat_decrypt_file` |
+| Haskell file logic | `../../src/Simplex/Chat/Files.hs` | -- |
+| Haskell file store | `../../src/Simplex/Chat/Store/Files.hs` | -- |
diff --git a/apps/ios/spec/services/notifications.md b/apps/ios/spec/services/notifications.md
new file mode 100644
index 0000000000..1062833f9c
--- /dev/null
+++ b/apps/ios/spec/services/notifications.md
@@ -0,0 +1,390 @@
+# SimpleX Chat iOS -- Push Notification Service
+
+> Technical specification for the notification system: NtfManager, Notification Service Extension (NSE), notification modes, and token lifecycle.
+>
+> Related specs: [Architecture](../architecture.md) | [API Reference](../api.md) | [Navigation](../client/navigation.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`NtfManager.swift`](../../Shared/Model/NtfManager.swift) | [`BGManager.swift`](../../Shared/Model/BGManager.swift) | [`Notifications.swift`](../../SimpleXChat/Notifications.swift) | [`NotificationService.swift`](../../SimpleX NSE/NotificationService.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [Notification Modes](#2-notification-modes)
+3. [NtfManager](#3-ntfmanager)
+4. [Notification Service Extension (NSE)](#4-notification-service-extension)
+5. [Token Lifecycle](#5-token-lifecycle)
+6. [Notification Categories & Actions](#6-notification-categories--actions)
+7. [Badge Management](#7-badge-management)
+8. [Background Tasks (BGManager)](#8-background-tasks)
+
+---
+
+## 1. Overview
+
+SimpleX Chat uses a privacy-preserving notification architecture. Because messages are end-to-end encrypted and the notification server never sees message content, the app uses a Notification Service Extension (NSE) to decrypt push payloads on-device before displaying notifications.
+
+```
+APNs Push → NSE receives encrypted payload
+ → NSE starts Haskell core (own chat_ctrl)
+ → NSE decrypts message using stored keys
+ → NSE creates UNNotificationContent with decrypted preview
+ → iOS displays notification to user
+```
+
+The notification system has three modes of operation, allowing users to choose their privacy/convenience tradeoff.
+
+---
+
+## 2. Notification Modes
+
+| Mode | Description | Mechanism |
+|------|-------------|-----------|
+| **Instant** | Real-time notifications via Apple Push | APNs push triggers NSE, which decrypts and displays |
+| **Periodic** | Background fetch every ~20 minutes | `BGAppRefreshTask` wakes app, checks for new messages |
+| **Off** | No notifications | User must open app to see messages |
+
+### Configuration
+
+Notification mode is set via:
+```swift
+ChatCommand.apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
+```
+
+`NotificationsMode` enum: `.instant`, `.periodic`, `.off`
+
+The mode is stored in `ChatModel.notificationMode` and persisted in `GroupDefaults`.
+
+---
+
+## 3. NtfManager
+
+**File**: [`Shared/Model/NtfManager.swift`](../../Shared/Model/NtfManager.swift)
+
+Central notification coordinator. Singleton: `NtfManager.shared`.
+
+### [Class Definition](../../Shared/Model/NtfManager.swift#L27)
+
+```swift
+class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
+ static let shared = NtfManager()
+ public var navigatingToChat = false
+ private var granted = false
+ private var prevNtfTime: Dictionary = [:]
+}
+```
+
+### Key Responsibilities
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`registerCategories()`](../../Shared/Model/NtfManager.swift#L156) | Registers notification action categories with iOS | [156](../../Shared/Model/NtfManager.swift#L156) |
+| [`requestAuthorization()`](../../Shared/Model/NtfManager.swift#L215) | Requests notification permission from user | [215](../../Shared/Model/NtfManager.swift#L215) |
+| [`setNtfBadgeCount(_:)`](../../Shared/Model/NtfManager.swift#L264) | Updates app icon badge | [264](../../Shared/Model/NtfManager.swift#L264) |
+| [`processNotificationResponse(_:)`](../../Shared/Model/NtfManager.swift#L54) | Handles user interaction with notification | [54](../../Shared/Model/NtfManager.swift#L54) |
+| [`notifyContactRequest(_:)`](../../Shared/Model/NtfManager.swift#L239) | Shows contact request notification | [239](../../Shared/Model/NtfManager.swift#L239) |
+| [`notifyCallInvitation(_:)`](../../Shared/Model/NtfManager.swift#L258) | Shows incoming call notification | [258](../../Shared/Model/NtfManager.swift#L258) |
+| [`notifyMessageReceived(_:)`](../../Shared/Model/NtfManager.swift#L250) | Shows message received notification | [250](../../Shared/Model/NtfManager.swift#L250) |
+
+### [Notification Response Processing](../../Shared/Model/NtfManager.swift#L40)
+
+When user taps a notification:
+
+1. `userNotificationCenter(didReceive:)` delegate method fires
+2. If app is active: calls `processNotificationResponse()` immediately
+3. If app is inactive: stores in `ChatModel.notificationResponse` for later processing
+4. [`processNotificationResponse()`](../../Shared/Model/NtfManager.swift#L54):
+ - Extracts `userId` from `userInfo` -- switches user if needed
+ - Extracts `chatId` -- navigates to the conversation
+ - Handles action identifiers (accept contact, accept/reject call)
+
+### [Rate Limiting](../../Shared/Model/NtfManager.swift#L144)
+
+`prevNtfTime` dictionary prevents notification flooding:
+- Each chat has a timestamp of its last notification
+- New notifications are suppressed if within `ntfTimeInterval` (1 second) of the previous one for the same chat
+
+---
+
+## 4. Notification Service Extension (NSE)
+
+**File**: [`SimpleX NSE/NotificationService.swift`](../../SimpleX NSE/NotificationService.swift)
+
+### Architecture
+
+The NSE is a separate process that iOS launches when a push notification arrives. It has:
+- Its own Haskell runtime instance (`chat_ctrl`)
+- Shared database access (via app group container)
+- ~30 second execution window per notification
+- No access to main app's in-memory state
+
+### [Processing Flow](../../SimpleX NSE/NotificationService.swift#L300)
+
+```
+1. didReceive(request:, withContentHandler:) L300
+ ├── 2. Initialize Haskell core (if not already running)
+ │ └── chat_migrate_init_key() with shared DB path L861
+ ├── 3. Decode encrypted notification payload
+ │ └── apiGetNtfConns(nonce:, encNtfInfo:) L1123
+ ├── 4. Fetch and decrypt messages
+ │ └── apiGetConnNtfMessages(connMsgReqs:) L1140
+ ├── 5. Create notification content
+ │ ├── Contact name as title
+ │ ├── Decrypted message preview as body
+ │ └── Thread identifier for grouping
+ └── 6. Deliver to content handler
+```
+
+### NSE Commands
+
+The NSE uses a subset of the chat API:
+
+| Command | Purpose | Line |
+|---------|---------|------|
+| [`apiGetNtfConns(nonce:, encNtfInfo:)`](../../SimpleX NSE/NotificationService.swift#L1123) | Decrypt notification connection info | [1123](../../SimpleX NSE/NotificationService.swift#L1123) |
+| [`apiGetConnNtfMessages(connMsgReqs:)`](../../SimpleX NSE/NotificationService.swift#L1140) | Fetch messages for notification connections | [1140](../../SimpleX NSE/NotificationService.swift#L1140) |
+
+### Database Coordination
+
+- NSE checks `appStateGroupDefault` before processing
+- If main app is `.active`, NSE may skip processing (main app handles notifications directly)
+- NSE uses `chat_close_store` / `chat_reopen_store` for safe concurrent access
+
+### [Preview Modes](../../SimpleXChat/APITypes.swift#L664)
+
+`NotificationPreviewMode` controls what the NSE shows:
+
+| Mode | Title | Body |
+|------|-------|------|
+| `.message` | Contact name | Message text |
+| `.contact` | Contact name | "New message" |
+| `.hidden` | "SimpleX" | "New message" |
+
+### Key Internal Types
+
+| Type | Purpose | Line |
+|------|---------|------|
+| [`NSENotificationData`](../../SimpleX NSE/NotificationService.swift#L27) | Enum of possible notification payloads | [27](../../SimpleX NSE/NotificationService.swift#L27) |
+| [`NSEThreads`](../../SimpleX NSE/NotificationService.swift#L82) | Concurrency coordinator for multiple NSE instances | [82](../../SimpleX NSE/NotificationService.swift#L82) |
+| [`NotificationEntity`](../../SimpleX NSE/NotificationService.swift#L245) | Per-connection processing state | [245](../../SimpleX NSE/NotificationService.swift#L245) |
+| [`NotificationService`](../../SimpleX NSE/NotificationService.swift#L287) | Main NSE class (`UNNotificationServiceExtension`) | [287](../../SimpleX NSE/NotificationService.swift#L287) |
+| [`NSEChatState`](../../SimpleX NSE/NotificationService.swift#L781) | Singleton managing NSE lifecycle state | [781](../../SimpleX NSE/NotificationService.swift#L781) |
+
+### Key Internal Functions
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`startChat()`](../../SimpleX NSE/NotificationService.swift#L836) | Initializes Haskell core for NSE | [836](../../SimpleX NSE/NotificationService.swift#L836) |
+| [`doStartChat()`](../../SimpleX NSE/NotificationService.swift#L861) | Performs actual chat initialization (migration, config) | [861](../../SimpleX NSE/NotificationService.swift#L861) |
+| [`activateChat()`](../../SimpleX NSE/NotificationService.swift#L907) | Reactivates suspended chat controller | [907](../../SimpleX NSE/NotificationService.swift#L907) |
+| [`suspendChat(_:)`](../../SimpleX NSE/NotificationService.swift#L921) | Suspends chat controller with timeout | [921](../../SimpleX NSE/NotificationService.swift#L921) |
+| [`receiveMessages()`](../../SimpleX NSE/NotificationService.swift#L954) | Main message-receive loop | [954](../../SimpleX NSE/NotificationService.swift#L954) |
+| [`receivedMsgNtf(_:)`](../../SimpleX NSE/NotificationService.swift#L1003) | Maps chat events to notification data | [1003](../../SimpleX NSE/NotificationService.swift#L1003) |
+| [`receiveNtfMessages(_:)`](../../SimpleX NSE/NotificationService.swift#L403) | Orchestrates notification message fetch and delivery | [403](../../SimpleX NSE/NotificationService.swift#L403) |
+| [`deliverBestAttemptNtf()`](../../SimpleX NSE/NotificationService.swift#L604) | Delivers the best available notification content | [604](../../SimpleX NSE/NotificationService.swift#L604) |
+| [`didReceive(_:withContentHandler:)`](../../SimpleX%20NSE/NotificationService.swift#L300) | Main NSE entry point -- processes incoming notification | [300](../../SimpleX%20NSE/NotificationService.swift#L300) |
+
+---
+
+## 5. Token Lifecycle
+
+### Registration Flow
+
+```
+1. App starts → AppDelegate.didRegisterForRemoteNotificationsWithDeviceToken
+ └── ChatModel.deviceToken = token
+
+2. Token registration (when chat running and token available):
+ └── apiRegisterToken(token, notificationMode)
+ └── Response: ntfToken(token, status, ntfMode, ntfServer)
+ └── ChatModel.tokenStatus = status
+
+3. Token verification (if server requires):
+ └── apiVerifyToken(token, nonce, code)
+ └── ChatModel.tokenRegistered = true
+
+4. Token check (periodic):
+ └── apiCheckToken(token)
+ └── Updates ChatModel.tokenStatus
+```
+
+### Token States (NtfTknStatus)
+
+| Status | Description |
+|--------|-------------|
+| `.new` | Token just registered, not yet verified |
+| `.registered` | Token registered with notification server |
+| `.confirmed` | Token confirmed and ready |
+| `.active` | Token actively receiving notifications |
+| `.expired` | Token expired, needs re-registration |
+| `.invalid` | Token invalid, needs new registration |
+| `.invalidBad` | Token invalid due to bad data |
+| `.invalidTopic` | Token invalid due to wrong topic |
+| `.invalidExpired` | Token invalid because it expired |
+| `.invalidUnregistered` | Token invalid, was unregistered |
+
+### Token Deletion
+
+```swift
+ChatCommand.apiDeleteToken(token: DeviceToken)
+```
+
+Called when:
+- User switches to `.off` notification mode
+- User deletes their profile
+- Token becomes invalid and needs replacement
+
+---
+
+## 6. Notification Categories & Actions
+
+Registered in [`NtfManager.registerCategories()`](../../Shared/Model/NtfManager.swift#L156):
+
+### Contact Request Category
+
+```swift
+// Category: "NTF_CAT_CONTACT_REQUEST"
+// Actions:
+// - "NTF_ACT_ACCEPT_CONTACT": Accept contact request
+```
+
+When user taps "Accept" on a contact request notification:
+1. `processNotificationResponse()` detects `ntfActionAcceptContact`
+2. Calls `apiAcceptContact(incognito: false, contactReqId:)`
+3. Navigates to the new contact's chat
+
+### Call Invitation Category
+
+```swift
+// Category: "NTF_CAT_CALL_INVITATION"
+// Actions:
+// - "NTF_ACT_ACCEPT_CALL": Accept incoming call
+// - "NTF_ACT_REJECT_CALL": Reject incoming call
+```
+
+When user taps "Accept" / "Reject" on a call notification:
+1. `processNotificationResponse()` detects the action
+2. Sets `ChatModel.ntfCallInvitationAction = (chatId, .accept/.reject)`
+3. Call controller picks up the pending action
+
+### Message Category
+
+Standard tap-to-open behavior navigates to the chat.
+
+### Many Events Category
+
+Batch notification for multiple events -- navigates to the app without specific chat context.
+
+---
+
+## 7. Badge Management
+
+The app icon badge shows the total unread message count:
+
+```swift
+// Updated when:
+// 1. App enters background:
+NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers())
+
+// 2. Messages are read:
+// Badge is recalculated and updated
+
+// 3. NSE receives notification:
+// NSE updates badge based on its count
+```
+
+`totalUnreadCountForAllUsers()` sums unread counts across all user profiles (not just the active user).
+
+### NSE Badge Handling
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`setBadgeCount()`](../../SimpleX NSE/NotificationService.swift#L592) | Increments badge via `ntfBadgeCountGroupDefault` | [592](../../SimpleX NSE/NotificationService.swift#L592) |
+| [`setNtfBadgeCount(_:)`](../../Shared/Model/NtfManager.swift#L264) | Sets badge on `UIApplication` | [264](../../Shared/Model/NtfManager.swift#L264) |
+| [`changeNtfBadgeCount(by:)`](../../Shared/Model/NtfManager.swift#L270) | Adjusts badge by delta | [270](../../Shared/Model/NtfManager.swift#L270) |
+
+---
+
+## 8. Background Tasks
+
+**File**: [`Shared/Model/BGManager.swift`](../../Shared/Model/BGManager.swift)
+
+### [BGManager](../../Shared/Model/BGManager.swift#L30)
+
+```swift
+class BGManager {
+ static let shared = BGManager()
+ func register() // Register BGAppRefreshTask handlers
+ func schedule() // Schedule next background refresh
+}
+```
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`register()`](../../Shared/Model/BGManager.swift#L38) | Registers `BGAppRefreshTask` handler with iOS | [38](../../Shared/Model/BGManager.swift#L38) |
+| [`schedule()`](../../Shared/Model/BGManager.swift#L46) | Schedules next background refresh request | [46](../../Shared/Model/BGManager.swift#L46) |
+| [`handleRefresh(_:)`](../../Shared/Model/BGManager.swift#L74) | Processes background refresh task | [74](../../Shared/Model/BGManager.swift#L74) |
+| [`completionHandler(_:)`](../../Shared/Model/BGManager.swift#L95) | Creates completion callback with cleanup | [95](../../Shared/Model/BGManager.swift#L95) |
+| [`receiveMessages(_:)`](../../Shared/Model/BGManager.swift#L112) | Activates chat and receives pending messages | [112](../../Shared/Model/BGManager.swift#L112) |
+
+### Background Refresh (Periodic Mode)
+
+When notification mode is `.periodic`:
+
+1. `BGManager.schedule()` is called when app enters background
+2. iOS wakes the app in the background approximately every 20 minutes
+3. `BGAppRefreshTask` handler:
+ - Activates the chat engine: `apiActivateChat(restoreChat: true)`
+ - Checks for new messages
+ - Creates local notifications for any new messages
+ - Suspends chat: `apiSuspendChat(timeoutMicroseconds:)`
+ - Schedules next refresh
+4. Must complete within ~30 seconds or iOS terminates the task
+
+### Background Task Protection
+
+All API calls use `beginBGTask()` / `endBackgroundTask()` to request extra execution time:
+
+```swift
+func beginBGTask(_ handler: (() -> Void)? = nil) -> (() -> Void) {
+ var id: UIBackgroundTaskIdentifier!
+ // ...
+ id = UIApplication.shared.beginBackgroundTask(expirationHandler: endTask)
+ return endTask
+}
+```
+
+Maximum task duration: `maxTaskDuration = 15` seconds.
+
+---
+
+## Notification Content Builders
+
+**File**: [`SimpleXChat/Notifications.swift`](../../SimpleXChat/Notifications.swift)
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`createContactRequestNtf()`](../../SimpleXChat/Notifications.swift#L27) | Builds notification for incoming contact request | [L27](../../SimpleXChat/Notifications.swift#L27) |
+| [`createContactConnectedNtf()`](../../SimpleXChat/Notifications.swift#L46) | Builds notification for contact connected event | [L46](../../SimpleXChat/Notifications.swift#L46) |
+| [`createMessageReceivedNtf()`](../../SimpleXChat/Notifications.swift#L66) | Builds notification for received message | [L66](../../SimpleXChat/Notifications.swift#L66) |
+| [`createCallInvitationNtf()`](../../SimpleXChat/Notifications.swift#L86) | Builds notification for incoming call | [L86](../../SimpleXChat/Notifications.swift#L86) |
+| [`createConnectionEventNtf()`](../../SimpleXChat/Notifications.swift#L102) | Builds notification for connection events | [L102](../../SimpleXChat/Notifications.swift#L102) |
+| [`createErrorNtf()`](../../SimpleXChat/Notifications.swift#L134) | Builds notification for database/encryption errors | [L134](../../SimpleXChat/Notifications.swift#L134) |
+| [`createAppStoppedNtf()`](../../SimpleXChat/Notifications.swift#L160) | Builds notification when app is stopped | [L160](../../SimpleXChat/Notifications.swift#L160) |
+| [`createNotification()`](../../SimpleXChat/Notifications.swift#L175) | Generic notification builder (used by all above) | [L175](../../SimpleXChat/Notifications.swift#L175) |
+| [`hideSecrets()`](../../SimpleXChat/Notifications.swift#L200) | Redacts secret-formatted text in previews | [L200](../../SimpleXChat/Notifications.swift#L200) |
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| Notification manager | [`Shared/Model/NtfManager.swift`](../../Shared/Model/NtfManager.swift) |
+| Background manager | [`Shared/Model/BGManager.swift`](../../Shared/Model/BGManager.swift) |
+| Notification types | [`SimpleXChat/Notifications.swift`](../../SimpleXChat/Notifications.swift) |
+| NSE service | [`SimpleX NSE/NotificationService.swift`](../../SimpleX NSE/NotificationService.swift) |
+| App delegate (token) | `Shared/AppDelegate.swift` |
+| Notification settings UI | `Shared/Views/UserSettings/NotificationsView.swift` |
diff --git a/apps/ios/spec/services/theme.md b/apps/ios/spec/services/theme.md
new file mode 100644
index 0000000000..321f3307f9
--- /dev/null
+++ b/apps/ios/spec/services/theme.md
@@ -0,0 +1,383 @@
+# SimpleX Chat iOS -- Theme Engine
+
+> Technical specification for the theming system: ThemeManager, default themes, customization layers, wallpapers, and YAML export.
+>
+> Related specs: [State Management](../state.md) | [Architecture](../architecture.md) | [README](../README.md)
+> Related product: [Product Overview](../../product/README.md)
+
+**Source:** [`ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift) | [`AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) | [`ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift) | [`ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift) | [`Theme.swift`](../../Shared/Theme/Theme.swift)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ThemeManager](#2-thememanager)
+3. [Default Themes](#3-default-themes)
+4. [Customization Layers](#4-customization-layers)
+5. [Color System](#5-color-system)
+6. [Wallpapers](#6-wallpapers)
+7. [Chat Bubble Styling](#7-chat-bubble-styling)
+8. [Color Scheme Mode](#8-color-scheme-mode)
+9. [YAML Export/Import](#9-yaml-exportimport)
+
+---
+
+## 1. Overview
+
+The theme engine provides a layered customization system where themes can be overridden at multiple levels: global defaults, per-user, and per-chat.
+
+```
+Theme Resolution Order (most specific wins):
+┌─────────────────────┐
+│ Per-chat override │ apiSetChatUIThemes(chatId:, themes:)
+├─────────────────────┤
+│ Per-user override │ apiSetUserUIThemes(userId:, themes:)
+├─────────────────────┤
+│ App settings theme │ themeOverridesDefault (UserDefaults)
+├─────────────────────┤
+│ Base theme │ Light / Dark / SimpleX / Black
+└─────────────────────┘
+```
+
+The resolved theme is published as `AppTheme.shared` and consumed by all SwiftUI views via `@EnvironmentObject`.
+
+---
+
+## 2. [ThemeManager](../../Shared/Theme/ThemeManager.swift) (L15)
+
+**File**: [`Shared/Theme/ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift)
+
+Static utility class that resolves the current theme by merging all customization layers.
+
+### [ActiveTheme](../../Shared/Theme/ThemeManager.swift#L17)
+
+The resolved theme output:
+
+```swift
+struct ActiveTheme: Equatable {
+ let name: String // Theme name (e.g., "light", "dark", "simplex", "black", "system")
+ let base: DefaultTheme // Base theme enum
+ let colors: Colors // Resolved color palette
+ let appColors: AppColors // App-specific colors (sent/received bubbles, etc.)
+ var wallpaper: AppWallpaper // Resolved wallpaper
+}
+```
+
+### Key Static Methods
+
+| Method | Purpose | Line |
+|--------|---------|------|
+| [`applyTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L124) | Apply a theme by name, updates `AppTheme.shared` | [L124](../../Shared/Theme/ThemeManager.swift#L124) |
+| [`currentColors(...)`](../../Shared/Theme/ThemeManager.swift#L64) | Resolve full theme from all layers | [L64](../../Shared/Theme/ThemeManager.swift#L64) |
+| [`defaultActiveTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L48) | Get default theme override from app settings | [L48](../../Shared/Theme/ThemeManager.swift#L48) |
+| [`currentThemeOverridesForExport(...)`](../../Shared/Theme/ThemeManager.swift#L105) | Get current overrides for YAML export | [L105](../../Shared/Theme/ThemeManager.swift#L105) |
+| [`adjustWindowStyle()`](../../Shared/Theme/ThemeManager.swift#L136) | Adjust window style after theme change | [L136](../../Shared/Theme/ThemeManager.swift#L136) |
+| [`changeDarkTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L166) | Change the dark theme variant | [L166](../../Shared/Theme/ThemeManager.swift#L166) |
+| [`saveAndApplyThemeColor(...)`](../../Shared/Theme/ThemeManager.swift#L173) | Save and apply a theme color override | [L173](../../Shared/Theme/ThemeManager.swift#L173) |
+| [`applyThemeColor(...)`](../../Shared/Theme/ThemeManager.swift#L186) | Apply a theme color to a binding | [L186](../../Shared/Theme/ThemeManager.swift#L186) |
+| [`saveAndApplyWallpaper(...)`](../../Shared/Theme/ThemeManager.swift#L191) | Save and apply a wallpaper change | [L191](../../Shared/Theme/ThemeManager.swift#L191) |
+| [`copyFromSameThemeOverrides(...)`](../../Shared/Theme/ThemeManager.swift#L213) | Copy overrides from matching theme | [L213](../../Shared/Theme/ThemeManager.swift#L213) |
+| [`applyWallpaper(...)`](../../Shared/Theme/ThemeManager.swift#L256) | Apply wallpaper to a binding | [L256](../../Shared/Theme/ThemeManager.swift#L256) |
+| [`saveAndApplyThemeOverrides(...)`](../../Shared/Theme/ThemeManager.swift#L267) | Save and apply full theme overrides | [L267](../../Shared/Theme/ThemeManager.swift#L267) |
+| [`resetAllThemeColors(_:)`](../../Shared/Theme/ThemeManager.swift#L288) | Reset all color overrides (CodableDefault) | [L288](../../Shared/Theme/ThemeManager.swift#L288) |
+| [`resetAllThemeColors(_:)`](../../Shared/Theme/ThemeManager.swift#L302) | Reset all color overrides (Binding) | [L302](../../Shared/Theme/ThemeManager.swift#L302) |
+| [`removeTheme(_:)`](../../Shared/Theme/ThemeManager.swift#L311) | Remove a saved theme by ID | [L311](../../Shared/Theme/ThemeManager.swift#L311) |
+
+### Theme Resolution Algorithm
+
+[`currentColors()`](../../Shared/Theme/ThemeManager.swift#L64) in `ThemeManager.swift`:
+
+1. Determine base theme from `currentThemeDefault`:
+ - If `"system"`: use light or dark based on [`systemInDarkThemeCurrently`](../../Shared/Theme/Theme.swift#L95)
+ - Dark mode maps to `systemDarkThemeDefault` (Dark, SimpleX, or Black)
+2. Get base color palette ([`LightColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L650), [`DarkColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L629), [`SimplexColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L671), [`BlackColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L692))
+3. Look up app settings theme override (`themeOverridesDefault`)
+4. Look up per-user theme override (`User.uiThemes`)
+5. Look up per-chat theme override (from ChatInfo)
+6. Look up wallpaper preset colors (if wallpaper has preset color overrides)
+7. Merge layers: base <- app override <- preset wallpaper colors <- per-user <- per-chat
+8. Return `ActiveTheme` with resolved colors, app colors, and wallpaper
+
+---
+
+## 3. Default Themes
+
+Four built-in themes with pre-defined color palettes:
+
+| Theme | Enum | Key Characteristics |
+|-------|------|---------------------|
+| **Light** | `DefaultTheme.LIGHT` | White background, standard colors |
+| **Dark** | `DefaultTheme.DARK` | Dark gray background, light text |
+| **SimpleX** | `DefaultTheme.SIMPLEX` | Brand purple accents, dark background |
+| **Black** | `DefaultTheme.BLACK` | Pure black background (OLED), high contrast |
+
+### [DefaultTheme](../../SimpleXChat/Theme/ThemeTypes.swift#L13) Enum
+
+```swift
+enum DefaultTheme {
+ case LIGHT
+ case DARK
+ case SIMPLEX
+ case BLACK
+
+ static let SYSTEM_THEME_NAME = "SYSTEM"
+
+ var themeName: String { ... }
+ var mode: DefaultThemeMode { ... } // .light or .dark
+}
+```
+
+### Color Palettes
+
+Each base theme defines two palette types:
+- [`Colors`](../../SimpleXChat/Theme/ThemeTypes.swift#L44): Standard UI colors (primary, background, surface, error, onBackground, onSurface)
+- [`AppColors`](../../SimpleXChat/Theme/ThemeTypes.swift#L90): App-specific colors (sentMessage, receivedMessage, title, primaryVariant2)
+
+Palette instances:
+- [`LightColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L650) / [`LightColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L662)
+- [`DarkColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L629) / [`DarkColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L641)
+- [`SimplexColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L671) / [`SimplexColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L683)
+- [`BlackColorPalette`](../../SimpleXChat/Theme/ThemeTypes.swift#L692) / [`BlackColorPaletteApp`](../../SimpleXChat/Theme/ThemeTypes.swift#L704)
+
+---
+
+## 4. Customization Layers
+
+### Layer 1: App Settings Theme
+
+Stored in `themeOverridesDefault` (UserDefaults). Contains `[ThemeOverrides]` -- an array of theme overrides, one per base theme.
+
+#### [`ThemeOverrides`](../../SimpleXChat/Theme/ThemeTypes.swift#L385)
+
+```swift
+struct ThemeOverrides: Codable {
+ var base: DefaultTheme
+ var colors: ThemeColors? // Color overrides
+ var wallpaper: ThemeWallpaper? // Wallpaper setting
+}
+```
+
+### Layer 2: Per-User Theme
+
+Stored on the `User` object (`User.uiThemes: ThemeModeOverrides?`), persisted in the Haskell database via `apiSetUserUIThemes(userId:, themes:)`.
+
+#### [`ThemeModeOverrides`](../../SimpleXChat/Theme/ThemeTypes.swift#L570)
+
+```swift
+struct ThemeModeOverrides: Codable {
+ var light: ThemeModeOverride?
+ var dark: ThemeModeOverride?
+}
+```
+
+#### [`ThemeModeOverride`](../../SimpleXChat/Theme/ThemeTypes.swift#L585)
+
+```swift
+struct ThemeModeOverride: Codable {
+ var mode: DefaultThemeMode?
+ var colors: ThemeColors?
+ var wallpaper: ThemeWallpaper?
+ var type: WallpaperType? // Computed from wallpaper
+}
+```
+
+### Layer 3: Per-Chat Theme
+
+Stored per-chat via `apiSetChatUIThemes(chatId:, themes:)`. Same `ThemeModeOverrides` structure.
+
+### Override Merging
+
+Colors are merged field-by-field: if a more-specific layer defines a color, it overrides; if nil, falls through to the next layer.
+
+---
+
+## 5. Color System
+
+**File**: [`SimpleXChat/Theme/ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift)
+
+### [ThemeColors](../../SimpleXChat/Theme/ThemeTypes.swift#L230)
+
+Overridable color definitions:
+
+```swift
+struct ThemeColors: Codable {
+ var primary: String? // Primary brand color
+ var primaryVariant: String? // Primary variant
+ var secondary: String? // Secondary color
+ var secondaryVariant: String? // Secondary variant
+ var background: String? // Main background
+ var surface: String? // Card/surface background
+ var title: String? // Title text color
+ var primaryVariant2: String? // Additional variant
+ var sentMessage: String? // Sent message bubble
+ var sentQuote: String? // Sent quote background
+ var receivedMessage: String? // Received message bubble
+ var receivedQuote: String? // Received quote background
+}
+```
+
+Colors are stored as hex strings (e.g., `"#FF6600"`) and converted to SwiftUI `Color` values at resolution time.
+
+### [Colors](../../SimpleXChat/Theme/ThemeTypes.swift#L44) (Resolved Palette)
+
+```swift
+struct Colors {
+ var isLight: Bool
+ var primary: Color
+ var primaryVariant: Color
+ var secondary: Color
+ var secondaryVariant: Color
+ var background: Color
+ var surface: Color
+ var error: Color
+ var onBackground: Color
+ var onSurface: Color
+ // ... etc
+}
+```
+
+### [AppColors](../../SimpleXChat/Theme/ThemeTypes.swift#L90) (Resolved App-Specific)
+
+```swift
+struct AppColors {
+ var title: Color
+ var primaryVariant2: Color
+ var sentMessage: Color
+ var sentQuote: Color
+ var receivedMessage: Color
+ var receivedQuote: Color
+}
+```
+
+---
+
+## 6. Wallpapers
+
+**File**: [`SimpleXChat/Theme/ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift)
+
+### [Preset Wallpapers](../../SimpleXChat/Theme/ChatWallpaperTypes.swift#L13)
+
+6 built-in wallpaper presets:
+
+| Preset | ID | Description |
+|--------|-----|-------------|
+| Cats | `cats` | Cat-themed pattern |
+| Flowers | `flowers` | Floral pattern |
+| Hearts | `hearts` | Heart pattern |
+| Kids | `kids` | Children's pattern |
+| School | `school` | School/notebook pattern (default) |
+| Travel | `travel` | Travel-themed pattern |
+
+Each preset defines per-theme color tints (`PresetWallpaper.colors[DefaultTheme]`) that subtly adjust the color palette to complement the wallpaper.
+
+### Custom Wallpapers
+
+Users can set a custom image as wallpaper:
+- Stored in `Documents/wallpapers/` directory
+- Scaled and tiled to fill the chat background
+- Custom wallpapers can be combined with color overrides
+
+### [WallpaperType](../../SimpleXChat/Theme/ChatWallpaperTypes.swift#L311)
+
+```swift
+enum WallpaperType {
+ case preset(filename: String, scale: Float?) // Built-in wallpaper
+ case image(filename: String, scale: Float?) // Custom image
+ case empty // No wallpaper
+}
+```
+
+### [AppWallpaper](../../SimpleXChat/Theme/ThemeTypes.swift#L142) (Resolved)
+
+```swift
+struct AppWallpaper {
+ var background: Color? // Background color override
+ var tint: Color? // Tint/overlay color
+ var type: WallpaperType
+}
+```
+
+---
+
+## 7. Chat Bubble Styling
+
+Configurable bubble appearance properties:
+
+| Property | Description | Stored In |
+|----------|-------------|-----------|
+| `chatItemRoundness` | Corner radius of message bubbles | App settings |
+| `chatItemTail` | Whether bubbles have a tail/arrow | App settings |
+| Avatar corner radius | Roundness of profile avatars | App settings |
+
+These are configured in [`Shared/Views/UserSettings/AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) ([L26](../../Shared/Views/UserSettings/AppearanceSettings.swift#L26)).
+
+---
+
+## 8. Color Scheme Mode
+
+### System Follow
+
+When theme is set to `"system"` (DefaultTheme.SYSTEM_THEME_NAME):
+- Light mode: uses `DefaultTheme.LIGHT` palette
+- Dark mode: uses the configured dark theme (`systemDarkThemeDefault`), which can be Dark, SimpleX, or Black
+
+### Forced Mode
+
+Users can force light or dark mode regardless of system setting by selecting a specific theme other than "system".
+
+### Detection
+
+[`systemInDarkThemeCurrently`](../../Shared/Theme/Theme.swift#L95):
+
+```swift
+var systemInDarkThemeCurrently: Bool {
+ return UITraitCollection.current.userInterfaceStyle == .dark
+}
+```
+
+`ChatModel.currentUser` setter triggers [`ThemeManager.applyTheme()`](../../Shared/Theme/ThemeManager.swift#L124) to handle per-user theme overrides when switching users.
+
+---
+
+## 9. YAML Export/Import
+
+Theme configurations can be exported as YAML for sharing:
+
+### Export
+
+[`ThemeManager.currentThemeOverridesForExport()`](../../Shared/Theme/ThemeManager.swift#L105) generates a `ThemeOverrides` representing the current resolved theme, which is then serialized to YAML using the Yams library.
+
+### Import
+
+YAML theme strings are parsed back into `ThemeOverrides` and applied as app settings theme overrides.
+
+Key functions in [`AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift):
+
+| Function | Purpose | Line |
+|----------|---------|------|
+| [`ImportExportThemeSection`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L603) | UI section for import/export controls | [L603](../../Shared/Views/UserSettings/AppearanceSettings.swift#L603) |
+| [`ThemeImporter`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L640) | ViewModifier for YAML file import | [L640](../../Shared/Views/UserSettings/AppearanceSettings.swift#L640) |
+| [`decodeYAML(_:)`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1150) | Parse YAML string into Decodable type | [L1150](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1150) |
+| [`encodeThemeOverrides(_:)`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1160) | Encode ThemeOverrides to YAML string | [L1160](../../Shared/Views/UserSettings/AppearanceSettings.swift#L1160) |
+
+### Toolbar Material
+
+[`ToolbarMaterial`](../../Shared/Views/UserSettings/AppearanceSettings.swift#L319) controls the navigation bar appearance:
+- Configurable opacity/material (translucent, opaque)
+- Stored in app settings
+
+---
+
+## Source Files
+
+| File | Path | Key Definitions |
+|------|------|-----------------|
+| Theme manager | [`Shared/Theme/ThemeManager.swift`](../../Shared/Theme/ThemeManager.swift) | `ThemeManager` (L15), `ActiveTheme` (L17) |
+| Theme types & colors | [`SimpleXChat/Theme/ThemeTypes.swift`](../../SimpleXChat/Theme/ThemeTypes.swift) | `DefaultTheme` (L13), `Colors` (L44), `AppColors` (L90), `AppWallpaper` (L142), `ThemeColors` (L230), `ThemeWallpaper` (L302), `ThemeOverrides` (L385), `ThemeModeOverrides` (L570), `ThemeModeOverride` (L585) |
+| Wallpaper types | [`SimpleXChat/Theme/ChatWallpaperTypes.swift`](../../SimpleXChat/Theme/ChatWallpaperTypes.swift) | `PresetWallpaper` (L13), `WallpaperType` (L311) |
+| Color utilities | [`SimpleXChat/Theme/Color.swift`](../../SimpleXChat/Theme/Color.swift) | Hex color conversion |
+| App theme observable | [`Shared/Theme/Theme.swift`](../../Shared/Theme/Theme.swift) | `AppTheme` (L22), `CurrentColors` (L14), `systemInDarkThemeCurrently` (L95) |
+| Appearance settings UI | [`Shared/Views/UserSettings/AppearanceSettings.swift`](../../Shared/Views/UserSettings/AppearanceSettings.swift) | `AppearanceSettings` (L26), `ToolbarMaterial` (L319), `ImportExportThemeSection` (L603) |
+| Theme mode editor | `Shared/Views/Helpers/ThemeModeEditor.swift` | Theme mode selection UI |
+| Haskell theme types | `../../src/Simplex/Chat/Types/UITheme.hs` | Server-side theme persistence |
diff --git a/apps/ios/spec/state.md b/apps/ios/spec/state.md
new file mode 100644
index 0000000000..6dda4ba275
--- /dev/null
+++ b/apps/ios/spec/state.md
@@ -0,0 +1,517 @@
+# SimpleX Chat iOS -- State Management
+
+**Source:** [`ChatModel.swift`](../Shared/Model/ChatModel.swift#L1-L1404) | [`ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1-L5377)
+
+> Technical specification for the app's state architecture: ChatModel, ItemsModel, Chat, ChatInfo, and preference storage.
+>
+> Related specs: [Architecture](architecture.md) | [API Reference](api.md) | [README](README.md)
+> Related product: [Concept Index](../product/concepts.md)
+
+---
+
+## Table of Contents
+
+1. [Overview](#1-overview)
+2. [ChatModel -- Primary App State](#2-chatmodel)
+3. [ItemsModel -- Per-Chat Message State](#3-itemsmodel)
+4. [ChatTagsModel -- Tag Filtering State](#4-chattagsmodel)
+5. [ChannelRelaysModel -- Channel Relay State](#5-channelrelaysmodel)
+6. [Chat -- Single Conversation State](#6-chat)
+7. [ChatInfo -- Conversation Metadata](#7-chatinfo)
+8. [State Flow](#8-state-flow)
+9. [Preference Storage](#9-preference-storage)
+
+---
+
+## 1. Overview
+
+The app uses SwiftUI's `ObservableObject` pattern for reactive state management. The state hierarchy is:
+
+```
+ChatModel (singleton -- global app state)
+├── currentUser: User?
+├── users: [UserInfo]
+├── chats: [Chat] (chat list)
+├── chatId: String? (active chat ID)
+├── im: ItemsModel.shared (primary chat items)
+├── secondaryIM: ItemsModel? (secondary chat items, e.g. support scope)
+├── activeCall: Call?
+├── callInvitations: [ChatId: RcvCallInvitation]
+├── deviceToken / savedToken / tokenStatus
+├── notificationMode: NotificationsMode
+├── onboardingStage: OnboardingStage?
+├── migrationState: MigrationToState?
+└── ... (50+ @Published properties)
+
+ItemsModel (singleton + secondary instances -- per-chat message state)
+├── reversedChatItems: [ChatItem] (messages in reverse order)
+├── chatState: ActiveChatState (pagination/split state)
+├── isLoading / showLoadingProgress
+└── preloadState: PreloadState
+
+Chat (per-conversation -- one per entry in chat list)
+├── chatInfo: ChatInfo (type + metadata)
+├── chatItems: [ChatItem] (preview items)
+└── chatStats: ChatStats (unread counts)
+
+ChatTagsModel (singleton -- filter state)
+├── userTags: [ChatTag]
+├── activeFilter: ActiveFilter?
+├── presetTags: [PresetTag: Int]
+└── unreadTags: [Int64: Int]
+```
+
+---
+
+## 2. [ChatModel](../Shared/Model/ChatModel.swift#L353-L1289)
+
+**Class**: `final class ChatModel: ObservableObject`
+**Singleton**: `ChatModel.shared`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L353)
+
+### Key Published Properties
+
+#### App Lifecycle
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `onboardingStage` | `OnboardingStage?` | Current onboarding step | [L354](../Shared/Model/ChatModel.swift#L354) |
+| `chatInitialized` | `Bool` | Whether chat has been initialized | [L363](../Shared/Model/ChatModel.swift#L363) |
+| `chatRunning` | `Bool?` | Whether chat engine is running | [L364](../Shared/Model/ChatModel.swift#L364) |
+| `chatDbChanged` | `Bool` | Whether DB was changed externally | [L365](../Shared/Model/ChatModel.swift#L365) |
+| `chatDbEncrypted` | `Bool?` | Whether DB is encrypted | [L366](../Shared/Model/ChatModel.swift#L366) |
+| `chatDbStatus` | `DBMigrationResult?` | DB migration status | [L367](../Shared/Model/ChatModel.swift#L367) |
+| `ctrlInitInProgress` | `Bool` | Whether controller is initializing | [L368](../Shared/Model/ChatModel.swift#L368) |
+| `migrationState` | `MigrationToState?` | Device migration state | [L417](../Shared/Model/ChatModel.swift#L417) |
+
+#### User State
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `currentUser` | `User?` | Active user profile (triggers theme reapply on change) | [L357](../Shared/Model/ChatModel.swift#L357) |
+| `users` | `[UserInfo]` | All user profiles | [L362](../Shared/Model/ChatModel.swift#L362) |
+| `v3DBMigration` | `V3DBMigrationState` | Legacy DB migration state | [L356](../Shared/Model/ChatModel.swift#L356) |
+
+#### Chat List
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chats` | `[Chat]` (private set) | All conversations for current user | [L374](../Shared/Model/ChatModel.swift#L374) |
+| `deletedChats` | `Set` | Chat IDs pending deletion animation | [L375](../Shared/Model/ChatModel.swift#L375) |
+
+#### Active Chat
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chatId` | `String?` | Currently open chat ID | [L377](../Shared/Model/ChatModel.swift#L377) |
+| `chatAgentConnId` | `String?` | Agent connection ID for active chat | [L378](../Shared/Model/ChatModel.swift#L378) |
+| `chatSubStatus` | `SubscriptionStatus?` | Active chat subscription status | [L379](../Shared/Model/ChatModel.swift#L379) |
+| `openAroundItemId` | `ChatItem.ID?` | Item to scroll to when opening | [L380](../Shared/Model/ChatModel.swift#L380) |
+| `chatToTop` | `String?` | Chat to scroll to top | [L381](../Shared/Model/ChatModel.swift#L381) |
+| `groupMembers` | `[GMember]` | Members of active group | [L382](../Shared/Model/ChatModel.swift#L382) |
+| `groupMembersIndexes` | `[Int64: Int]` | Member ID to index mapping | [L383](../Shared/Model/ChatModel.swift#L383) |
+| `membersLoaded` | `Bool` | Whether members have been loaded | [L384](../Shared/Model/ChatModel.swift#L384) |
+| `secondaryIM` | `ItemsModel?` | Secondary items model (e.g. support chat scope) | [L435](../Shared/Model/ChatModel.swift#L435) |
+
+#### Authentication
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `contentViewAccessAuthenticated` | `Bool` | Whether user has passed authentication | [L371](../Shared/Model/ChatModel.swift#L371) |
+| `laRequest` | `LocalAuthRequest?` | Pending authentication request | [L372](../Shared/Model/ChatModel.swift#L372) |
+
+#### Notifications
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `deviceToken` | `DeviceToken?` | Current APNs device token | [L395](../Shared/Model/ChatModel.swift#L395) |
+| `savedToken` | `DeviceToken?` | Previously saved token | [L396](../Shared/Model/ChatModel.swift#L396) |
+| `tokenRegistered` | `Bool` | Whether token is registered with server | [L397](../Shared/Model/ChatModel.swift#L397) |
+| `tokenStatus` | `NtfTknStatus?` | Token registration status | [L399](../Shared/Model/ChatModel.swift#L399) |
+| `notificationMode` | `NotificationsMode` | Current notification mode (.off/.periodic/.instant) | [L400](../Shared/Model/ChatModel.swift#L400) |
+| `notificationServer` | `String?` | Notification server URL | [L401](../Shared/Model/ChatModel.swift#L401) |
+| `notificationPreview` | `NotificationPreviewMode` | What to show in notifications | [L402](../Shared/Model/ChatModel.swift#L402) |
+| `notificationResponse` | `UNNotificationResponse?` | Pending notification action | [L369](../Shared/Model/ChatModel.swift#L369) |
+| `ntfContactRequest` | `NTFContactRequest?` | Pending contact request from notification | [L404](../Shared/Model/ChatModel.swift#L404) |
+| `ntfCallInvitationAction` | `(ChatId, NtfCallAction)?` | Pending call action from notification | [L405](../Shared/Model/ChatModel.swift#L405) |
+
+#### Calls
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `callInvitations` | `[ChatId: RcvCallInvitation]` | Pending incoming call invitations | [L407](../Shared/Model/ChatModel.swift#L407) |
+| `activeCall` | `Call?` | Currently active call | [L408](../Shared/Model/ChatModel.swift#L408) |
+| `callCommand` | `WebRTCCommandProcessor` | WebRTC command queue | [L409](../Shared/Model/ChatModel.swift#L409) |
+| `showCallView` | `Bool` | Whether to show full-screen call UI | [L410](../Shared/Model/ChatModel.swift#L410) |
+| `activeCallViewIsCollapsed` | `Bool` | Whether call view is in PiP mode | [L411](../Shared/Model/ChatModel.swift#L411) |
+
+#### Remote Desktop
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `remoteCtrlSession` | `RemoteCtrlSession?` | Active remote desktop session | [L414](../Shared/Model/ChatModel.swift#L414) |
+
+#### Misc
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `userAddress` | `UserContactLink?` | User's SimpleX address | [L391](../Shared/Model/ChatModel.swift#L391) |
+| `chatItemTTL` | `ChatItemTTL` | Global message TTL | [L392](../Shared/Model/ChatModel.swift#L392) |
+| `appOpenUrl` | `URL?` | URL opened while app active | [L393](../Shared/Model/ChatModel.swift#L393) |
+| `appOpenUrlLater` | `URL?` | URL opened while app inactive | [L394](../Shared/Model/ChatModel.swift#L394) |
+| `showingInvitation` | `ShowingInvitation?` | Currently displayed invitation | [L416](../Shared/Model/ChatModel.swift#L416) |
+| `draft` | `ComposeState?` | Saved compose draft | [L420](../Shared/Model/ChatModel.swift#L420) |
+| `draftChatId` | `String?` | Chat ID for saved draft | [L421](../Shared/Model/ChatModel.swift#L421) |
+| `networkInfo` | `UserNetworkInfo` | Current network type and status | [L422](../Shared/Model/ChatModel.swift#L422) |
+| `conditions` | `ServerOperatorConditions` | Server usage conditions | [L424](../Shared/Model/ChatModel.swift#L424) |
+| `stopPreviousRecPlay` | `URL?` | Currently playing audio source | [L419](../Shared/Model/ChatModel.swift#L419) |
+
+### Non-Published Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `messageDelivery` | `[Int64: () -> Void]` | Pending delivery confirmation callbacks | [L426](../Shared/Model/ChatModel.swift#L426) |
+| `filesToDelete` | `Set` | Files queued for deletion | [L428](../Shared/Model/ChatModel.swift#L428) |
+| `im` | `ItemsModel` | Reference to `ItemsModel.shared` | [L432](../Shared/Model/ChatModel.swift#L432) |
+
+### Key Methods
+
+| Method | Description | Line |
+|--------|-------------|------|
+| `getUser(_ userId:)` | Find user by ID | [L455](../Shared/Model/ChatModel.swift#L455) |
+| `updateUser(_ user:)` | Update user in list and current | [L466](../Shared/Model/ChatModel.swift#L466) |
+| `removeUser(_ user:)` | Remove user from list | [L476](../Shared/Model/ChatModel.swift#L476) |
+| `getChat(_ id:)` | Find chat by ID | [L487](../Shared/Model/ChatModel.swift#L487) |
+| `addChat(_ chat:)` | Add chat to list | [L542](../Shared/Model/ChatModel.swift#L542) |
+| `updateChatInfo(_ cInfo:)` | Update chat metadata | [L556](../Shared/Model/ChatModel.swift#L556) |
+| `replaceChat(_ id:, _ chat:)` | Replace chat in list | [L608](../Shared/Model/ChatModel.swift#L608) |
+| `removeChat(_ id:)` | Remove chat from list | [L1217](../Shared/Model/ChatModel.swift#L1217) |
+| `popChat(_ id:)` | Move chat to top of list | [L1193](../Shared/Model/ChatModel.swift#L1193) |
+| `totalUnreadCountForAllUsers()` | Sum unread across all users | [L1093](../Shared/Model/ChatModel.swift#L1093) |
+
+---
+
+## 3. [ItemsModel](../Shared/Model/ChatModel.swift#L74-L174)
+
+**Class**: `class ItemsModel: ObservableObject`
+**Primary singleton**: `ItemsModel.shared`
+**Secondary instances**: Created via `ItemsModel.loadSecondaryChat()` for scope-based views (e.g., group member support chat)
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L74)
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `reversedChatItems` | `[ChatItem]` | Messages in reverse chronological order (newest first) | [L80](../Shared/Model/ChatModel.swift#L80) |
+| `itemAdded` | `Bool` | Flag indicating a new item was added | [L83](../Shared/Model/ChatModel.swift#L83) |
+| `chatState` | `ActiveChatState` | Pagination splits and loaded ranges | [L87](../Shared/Model/ChatModel.swift#L87) |
+| `isLoading` | `Bool` | Whether messages are currently loading | [L91](../Shared/Model/ChatModel.swift#L91) |
+| `showLoadingProgress` | `ChatId?` | Chat ID showing loading spinner | [L92](../Shared/Model/ChatModel.swift#L92) |
+| `preloadState` | `PreloadState` | State for infinite-scroll preloading | [L77](../Shared/Model/ChatModel.swift#L77) |
+| `secondaryIMFilter` | `SecondaryItemsModelFilter?` | Filter for secondary instances | [L76](../Shared/Model/ChatModel.swift#L76) |
+
+### Computed Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `lastItemsLoaded` | `Bool` | Whether the oldest messages have been loaded | [L97](../Shared/Model/ChatModel.swift#L97) |
+| `contentTag` | `MsgContentTag?` | Content type filter (if secondary) | [L159](../Shared/Model/ChatModel.swift#L159) |
+| `groupScopeInfo` | `GroupChatScopeInfo?` | Group scope filter (if secondary) | [L167](../Shared/Model/ChatModel.swift#L167) |
+
+### Throttling
+
+`ItemsModel` uses a custom publisher throttle (0.2 seconds) to batch rapid updates to `reversedChatItems` and prevent excessive SwiftUI re-renders:
+
+```swift
+publisher
+ .throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true)
+ .sink { self.objectWillChange.send() }
+ .store(in: &bag)
+```
+
+Direct `@Published` properties (`isLoading`, `showLoadingProgress`) bypass throttling for immediate UI response.
+
+### Key Methods
+
+| Method | Description | Line |
+|--------|-------------|------|
+| `loadOpenChat(_ chatId:)` | Load chat with 250ms navigation delay | [L117](../Shared/Model/ChatModel.swift#L117) |
+| `loadOpenChatNoWait(_ chatId:, _ openAroundItemId:)` | Load chat without delay | [L143](../Shared/Model/ChatModel.swift#L143) |
+| `loadSecondaryChat(_ chatId:, chatFilter:)` | Create secondary ItemsModel instance | [L110](../Shared/Model/ChatModel.swift#L110) |
+
+### [SecondaryItemsModelFilter](../Shared/Model/ChatModel.swift#L58-L70)
+
+Used for secondary chat views (e.g., group member support scope, content type filter):
+
+```swift
+enum SecondaryItemsModelFilter {
+ case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo)
+ case msgContentTagContext(contentTag: MsgContentTag)
+}
+```
+
+---
+
+## 4. [ChatTagsModel](../Shared/Model/ChatModel.swift#L189-L291)
+
+**Class**: `class ChatTagsModel: ObservableObject`
+**Singleton**: `ChatTagsModel.shared`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L189)
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `userTags` | `[ChatTag]` | User-defined tags | [L192](../Shared/Model/ChatModel.swift#L192) |
+| `activeFilter` | `ActiveFilter?` | Currently active filter tab | [L193](../Shared/Model/ChatModel.swift#L193) |
+| `presetTags` | `[PresetTag: Int]` | Preset tag counts (groups, contacts, favorites, etc.) | [L194](../Shared/Model/ChatModel.swift#L194) |
+| `unreadTags` | `[Int64: Int]` | Unread count per user tag | [L195](../Shared/Model/ChatModel.swift#L195) |
+
+### [ActiveFilter](../Shared/Views/ChatList/ChatListView.swift#L52)
+
+```swift
+enum ActiveFilter {
+ case presetTag(PresetTag) // .favorites, .contacts, .groups, .business, .groupReports
+ case userTag(ChatTag) // User-defined tag
+ case unread // Unread conversations
+}
+```
+
+---
+
+## 5. [ChannelRelaysModel](../Shared/Model/ChatModel.swift#L336-L350)
+
+**Class**: `class ChannelRelaysModel: ObservableObject`
+**Singleton**: `ChannelRelaysModel.shared`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L336)
+
+Holds runtime relay state for the currently viewed channel. Used by `ChannelRelaysView` to display and manage relays. Reset when the view is dismissed.
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `groupId` | `Int64?` | Group ID of the channel whose relays are loaded | [L338](../Shared/Model/ChatModel.swift#L338) |
+| `groupRelays` | `[GroupRelay]` | Current relay instances for the channel | [L339](../Shared/Model/ChatModel.swift#L339) |
+
+### Methods
+
+| Method | Description | Line |
+|--------|-------------|------|
+| `set(groupId:groupRelays:)` | Populate all properties at once | [L341](../Shared/Model/ChatModel.swift#L341) |
+| `reset()` | Clear all properties to nil/empty | [L346](../Shared/Model/ChatModel.swift#L346) |
+
+---
+
+## 6. [Chat](../Shared/Model/ChatModel.swift#L1301-L1353)
+
+**Class**: `final class Chat: ObservableObject, Identifiable, ChatLike`
+**Source**: [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift#L1301)
+
+Represents a single conversation in the chat list. Each `Chat` is an independent observable object.
+
+### Properties
+
+| Property | Type | Description | Line |
+|----------|------|-------------|------|
+| `chatInfo` | `ChatInfo` | Conversation type and metadata | [L1302](../Shared/Model/ChatModel.swift#L1302) |
+| `chatItems` | `[ChatItem]` | Preview items (typically last message) | [L1303](../Shared/Model/ChatModel.swift#L1303) |
+| `chatStats` | `ChatStats` | Unread counts and min unread item ID | [L1304](../Shared/Model/ChatModel.swift#L1304) |
+| `created` | `Date` | Creation timestamp | [L1305](../Shared/Model/ChatModel.swift#L1305) |
+
+### [ChatStats](../SimpleXChat/ChatTypes.swift#L1881-L1903)
+
+```swift
+struct ChatStats: Decodable, Hashable {
+ var unreadCount: Int = 0
+ var unreadMentions: Int = 0
+ var reportsCount: Int = 0
+ var minUnreadItemId: Int64 = 0
+ var unreadChat: Bool = false
+}
+```
+
+### Computed Properties
+
+| Property | Description | Line |
+|----------|-------------|------|
+| `id` | Chat ID from `chatInfo.id` | [L1336](../Shared/Model/ChatModel.swift#L1336) |
+| `viewId` | Unique view identity including creation time | [L1338](../Shared/Model/ChatModel.swift#L1338) |
+| `unreadTag` | Whether chat counts as "unread" based on notification settings | [L1328](../Shared/Model/ChatModel.swift#L1328) |
+| `supportUnreadCount` | Unread count for group support scope | [L1340](../Shared/Model/ChatModel.swift#L1340) |
+
+---
+
+## 7. [ChatInfo](../SimpleXChat/ChatTypes.swift#L1374-L1856)
+
+**Enum**: `public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable`
+**Source**: [`SimpleXChat/ChatTypes.swift`](../SimpleXChat/ChatTypes.swift#L1374)
+
+Represents the type and metadata of a conversation:
+
+```swift
+public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
+ case direct(contact: Contact)
+ case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?)
+ case local(noteFolder: NoteFolder)
+ case contactRequest(contactRequest: UserContactRequest)
+ case contactConnection(contactConnection: PendingContactConnection)
+ case invalidJSON(json: Data?)
+}
+```
+
+### Cases
+
+| Case | Associated Value | Description |
+|------|-----------------|-------------|
+| `.direct` | `Contact` | One-to-one conversation |
+| `.group` | `GroupInfo, GroupChatScopeInfo?` | Group conversation (optional scope for member support threads) |
+| `.local` | `NoteFolder` | Local notes (self-chat) |
+| `.contactRequest` | `UserContactRequest` | Incoming contact request |
+| `.contactConnection` | `PendingContactConnection` | Pending connection |
+| `.invalidJSON` | `Data?` | Undecodable chat data |
+
+### Key Computed Properties on ChatInfo
+
+| Property | Type | Description |
+|----------|------|-------------|
+| `chatType` | `ChatType` | `.direct`, `.group`, `.local`, `.contactRequest`, `.contactConnection` |
+| `id` | `ChatId` | Prefixed ID (e.g., `"@1"` for direct, `"#5"` for group) |
+| `displayName` | `String` | Contact/group name |
+| `image` | `String?` | Profile image (base64) |
+| `chatSettings` | `ChatSettings?` | Notification/favorite settings |
+| `chatTags` | `[Int64]?` | Assigned tag IDs |
+
+### Relay-Related Data Model (Channels)
+
+A **channel** is a group with `groupInfo.useRelays == true`. These types support the relay/channel infrastructure:
+
+#### New Fields on Existing Types
+
+| Type | Field | Type | Description | Line |
+|------|-------|------|-------------|------|
+| `User` | `userChatRelay` | `Bool` | Whether user acts as a chat relay | [L46](../SimpleXChat/ChatTypes.swift#L46) |
+| `GroupInfo` | `useRelays` | `Bool` | Whether group uses relay infrastructure (channel mode) | [L2343](../SimpleXChat/ChatTypes.swift#L2343) |
+| `GroupInfo` | `relayOwnStatus` | `RelayStatus?` | Current user's relay status in this group | [L2344](../SimpleXChat/ChatTypes.swift#L2344) |
+| `GroupProfile` | `publicGroup` | `PublicGroupProfile?` | Channel-specific profile data (type, link, ID) | [L2472](../SimpleXChat/ChatTypes.swift#L2472) |
+
+#### New Types
+
+| Type | Kind | Description | Line |
+|------|------|-------------|------|
+| `RelayStatus` | `enum` | Relay lifecycle: `.rsNew`, `.rsInvited`, `.rsAccepted`, `.rsActive` | [L2506](../SimpleXChat/ChatTypes.swift#L2506) |
+| `RelayStatus.text` | `extension` | Localized display text: New/Invited/Accepted/Active | [L2565](../SimpleXChat/ChatTypes.swift#L2565) |
+| `GroupRelay` | `struct` | Relay instance for a group (ID, member ID, relay status). Fetched at runtime via `apiGetGroupRelays` (owner only) | [L2555](../SimpleXChat/ChatTypes.swift#L2555) |
+| `UserChatRelay` | `struct` | User's chat relay configuration (ID, SMP address, name, domains, preset/tested/enabled/deleted flags) | [L2513](../SimpleXChat/ChatTypes.swift#L2513) |
+
+#### New Enum Cases
+
+| Enum | Case | Description | Line |
+|------|------|-------------|------|
+| `GroupMemberRole` | `.relay` | Role for relay members (below `.observer`) | [L2807](../SimpleXChat/ChatTypes.swift#L2807) |
+| `CIDirection` | `.channelRcv` | Message direction for channel-received messages (via relay) | [L3529](../SimpleXChat/ChatTypes.swift#L3529) |
+
+---
+
+## 8. State Flow
+
+### App Start
+```
+SimpleXApp.init()
+ → haskell_init()
+ → initChatAndMigrate()
+ → chat_migrate_init_key() -- creates/opens DB
+ → startChat(mainApp: true) -- starts core
+ → apiGetChats(userId) -- populates ChatModel.chats
+ → UI renders ChatListView
+```
+
+### Opening a Chat
+```
+User taps chat in ChatListView
+ → ItemsModel.loadOpenChat(chatId)
+ → 250ms delay for navigation animation
+ → ChatModel.chatId = chatId
+ → loadChat(chatId:, im:)
+ → apiGetChat(chatId, pagination: .last(count: 50))
+ → ItemsModel.reversedChatItems = [ChatItem]
+ → ChatView renders messages
+```
+
+### Receiving a Message (Event)
+```
+Haskell core generates ChatEvent.newChatItems
+ → Event loop calls chat_recv_msg_wait
+ → Decoded as ChatEvent.newChatItems(user, chatItems)
+ → ChatModel updates:
+ 1. Insert new Chat items into ChatModel.chats (preview)
+ 2. If chat is open: insert into ItemsModel.reversedChatItems
+ 3. Update ChatStats (unread counts)
+ 4. Update ChatTagsModel (tag unread counts)
+ → SwiftUI re-renders affected views via @Published observation
+```
+
+### Sending a Message
+```
+User taps send in ComposeView
+ → apiSendMessages(type, id, scope, live, ttl, composedMessages)
+ → Haskell processes, returns ChatResponse1.newChatItems
+ → ChatModel.chats updated with new preview
+ → ItemsModel.reversedChatItems gets new item
+ → ChatView scrolls to bottom, shows sent message
+```
+
+---
+
+## 9. Preference Storage
+
+### UserDefaults (via @AppStorage)
+
+App-level UI settings stored in `UserDefaults.standard`:
+
+| Key Constant | Type | Description |
+|--------------|------|-------------|
+| `DEFAULT_PERFORM_LA` | `Bool` | Enable local authentication |
+| `DEFAULT_PRIVACY_PROTECT_SCREEN` | `Bool` | Hide screen in app switcher |
+| `DEFAULT_SHOW_LA_NOTICE` | `Bool` | Show LA setup notice |
+| `DEFAULT_NOTIFICATION_ALERT_SHOWN` | `Bool` | Notification permission alert shown |
+| `DEFAULT_CALL_KIT_CALLS_IN_RECENTS` | `Bool` | Show CallKit calls in recents |
+
+### GroupDefaults
+
+Settings shared between main app and extensions (NSE, SE) via app group `UserDefaults`:
+
+| Key | Description |
+|-----|-------------|
+| `appStateGroupDefault` | Current app state (.active/.suspended/.stopped) |
+| `dbContainerGroupDefault` | Database container location (.group/.documents) |
+| `ntfPreviewModeGroupDefault` | Notification preview mode |
+| `storeDBPassphraseGroupDefault` | Whether to store DB passphrase |
+| `callKitEnabledGroupDefault` | Whether CallKit is enabled |
+| `onboardingStageDefault` | Current onboarding stage |
+| `currentThemeDefault` | Current theme name |
+| `systemDarkThemeDefault` | Dark mode theme name |
+| `themeOverridesDefault` | Custom theme overrides |
+| `currentThemeIdsDefault` | Active theme override IDs |
+
+### Keychain (KeyChain wrapper)
+
+Sensitive data stored in iOS Keychain:
+
+| Key | Description |
+|-----|-------------|
+| `kcDatabasePassword` | SQLite database encryption key |
+| `kcAppPassword` | App lock password |
+| `kcSelfDestructPassword` | Self-destruct trigger password |
+
+### Haskell DB (via apiSaveSettings / apiGetSettings)
+
+Chat-level preferences stored in the SQLite database (managed by Haskell core):
+
+- Per-contact preferences (timed messages, voice, calls, etc.)
+- Per-group preferences
+- Per-user notification settings
+- Network configuration
+- Server lists
+
+---
+
+## Source Files
+
+| File | Path |
+|------|------|
+| ChatModel, ItemsModel, Chat, ChatTagsModel, ChannelRelaysModel | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift) |
+| ChatInfo, User, Contact, GroupInfo, ChatItem | [`SimpleXChat/ChatTypes.swift`](../SimpleXChat/ChatTypes.swift) |
+| ActiveFilter | [`Shared/Views/ChatList/ChatListView.swift`](../Shared/Views/ChatList/ChatListView.swift#L52) |
+| Preference defaults | [`Shared/Model/ChatModel.swift`](../Shared/Model/ChatModel.swift), [`SimpleXChat/FileUtils.swift`](../SimpleXChat/FileUtils.swift) |
diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings
index 2700711773..d4b4dfd949 100644
--- a/apps/ios/th.lproj/Localizable.strings
+++ b/apps/ios/th.lproj/Localizable.strings
@@ -910,7 +910,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "ลบข้อความ?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "ลบข้อความ";
/* No comment provided by engineer. */
@@ -1815,7 +1816,7 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "บทบาทของสมาชิกจะถูกเปลี่ยนเป็น \"%@\" สมาชิกจะได้รับคำเชิญใหม่";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!";
/* No comment provided by engineer. */
@@ -2332,13 +2333,13 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "เซิร์ฟเวอร์รีเลย์ปกป้องที่อยู่ IP ของคุณ แต่สามารถสังเกตระยะเวลาของการโทรได้";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "ลบ";
/* No comment provided by engineer. */
"Remove member" = "ลบสมาชิกออก";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "ลบสมาชิกออก?";
/* No comment provided by engineer. */
diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings
index 9acf2cc425..5cccb67170 100644
--- a/apps/ios/tr.lproj/Localizable.strings
+++ b/apps/ios/tr.lproj/Localizable.strings
@@ -1733,7 +1733,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Mesaj silinsin mi?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Mesajları sil";
/* No comment provided by engineer. */
@@ -3293,10 +3294,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Üye rolü \"%@\" olarak değiştirilecektir. Ve üye yeni bir davetiye alacaktır.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Üye sohbetten kaldırılacak - bu geri alınamaz!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Üye gruptan çıkarılacaktır - bu geri alınamaz!";
/* alert message */
@@ -4362,7 +4363,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Yönlendirici sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Sil";
/* No comment provided by engineer. */
@@ -4377,7 +4378,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Kişiyi sil";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Kişi silinsin mi?";
/* No comment provided by engineer. */
diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings
index fe8cfe22a0..305e64fbcf 100644
--- a/apps/ios/uk.lproj/Localizable.strings
+++ b/apps/ios/uk.lproj/Localizable.strings
@@ -1718,7 +1718,8 @@ swipe action */
/* No comment provided by engineer. */
"Delete message?" = "Видалити повідомлення?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "Видалити повідомлення";
/* No comment provided by engineer. */
@@ -3263,10 +3264,10 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль учасника буде змінено на \"%@\". Учасник отримає нове запрошення.";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "Учасника буде видалено з чату – це неможливо скасувати!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "Учасник буде видалений з групи - це неможливо скасувати!";
/* alert message */
@@ -4317,7 +4318,7 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "Сервер ретрансляції захищає вашу IP-адресу, але він може спостерігати за тривалістю дзвінка.";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "Видалити";
/* No comment provided by engineer. */
@@ -4329,7 +4330,7 @@ swipe action */
/* No comment provided by engineer. */
"Remove member" = "Видалити учасника";
-/* No comment provided by engineer. */
+/* alert title */
"Remove member?" = "Видалити учасника?";
/* No comment provided by engineer. */
diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings
index 24d153afd5..d5afea745d 100644
--- a/apps/ios/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/zh-Hans.lproj/Localizable.strings
@@ -346,12 +346,21 @@ alert action
swipe action */
"Accept" = "接受";
+/* alert action */
+"Accept as member" = "接受为成员";
+
+/* alert action */
+"Accept as observer" = "接受为观察员";
+
/* No comment provided by engineer. */
"Accept conditions" = "接受条款";
/* No comment provided by engineer. */
"Accept connection request?" = "接受联系人?";
+/* alert title */
+"Accept contact request" = "接受联络请求";
+
/* notification body */
"Accept contact request from %@?" = "接受来自 %@ 的联系人请求?";
@@ -359,12 +368,21 @@ swipe action */
swipe action */
"Accept incognito" = "接受隐身聊天";
+/* alert title */
+"Accept member" = "接受成员";
+
/* call status */
"accepted call" = "已接受通话";
/* No comment provided by engineer. */
"Accepted conditions" = "已接受的条款";
+/* chat list item title */
+"accepted invitation" = "已接受邀请";
+
+/* rcv group event chat item */
+"accepted you" = "接受了你";
+
/* No comment provided by engineer. */
"Acknowledged" = "确认";
@@ -386,6 +404,9 @@ swipe action */
/* No comment provided by engineer. */
"Add list" = "添加列表";
+/* placeholder for sending contact request */
+"Add message" = "添加信息";
+
/* No comment provided by engineer. */
"Add profile" = "添加个人资料";
@@ -461,6 +482,9 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "同意加密…";
+/* member criteria value */
+"all" = "全部";
+
/* No comment provided by engineer. */
"All" = "全部";
@@ -485,6 +509,9 @@ swipe action */
/* feature role */
"all members" = "所有成员";
+/* No comment provided by engineer. */
+"All messages" = "所有消息";
+
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "所有消息和文件均通过**端到端加密**发送;私信以量子安全方式发送。";
@@ -530,6 +557,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow downgrade" = "允许降级";
+/* No comment provided by engineer. */
+"Allow files and media only if your contact allows them." = "只有你的联系人允许的情况下才允许文件和媒体。";
+
/* No comment provided by engineer. */
"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "仅有您的联系人许可后才允许不可撤回消息移除";
@@ -581,6 +611,9 @@ swipe action */
/* No comment provided by engineer. */
"Allow your contacts to send disappearing messages." = "允许您的联系人发送限时消息。";
+/* No comment provided by engineer. */
+"Allow your contacts to send files and media." = "允许你的联系人发送文件和媒体。";
+
/* No comment provided by engineer. */
"Allow your contacts to send voice messages." = "允许您的联系人发送语音消息。";
@@ -683,6 +716,9 @@ swipe action */
/* No comment provided by engineer. */
"Archived contacts" = "已存档的联系人";
+/* No comment provided by engineer. */
+"archived report" = "已存档的举报";
+
/* No comment provided by engineer. */
"Archiving database" = "正在存档数据库";
@@ -698,6 +734,9 @@ swipe action */
/* No comment provided by engineer. */
"Audio and video calls" = "语音和视频通话";
+/* No comment provided by engineer. */
+"Audio call" = "语音通话";
+
/* No comment provided by engineer. */
"audio call (not e2e encrypted)" = "语音通话(非端到端加密)";
@@ -782,6 +821,12 @@ swipe action */
/* No comment provided by engineer. */
"Better user experience" = "更佳的使用体验";
+/* No comment provided by engineer. */
+"Bio" = "自我介绍";
+
+/* alert title */
+"Bio too large" = "自我介绍过大";
+
/* No comment provided by engineer. */
"Black" = "黑色";
@@ -825,6 +870,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"bold" = "加粗";
+/* No comment provided by engineer. */
+"Bot" = "机器人";
+
/* No comment provided by engineer. */
"Both you and your contact can add message reactions." = "您和您的联系人都可以添加消息回应。";
@@ -837,6 +885,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Both you and your contact can send disappearing messages." = "您和您的联系人都可以发送限时消息。";
+/* No comment provided by engineer. */
+"Both you and your contact can send files and media." = "你和你的联系人都可发送文件和媒体。";
+
/* No comment provided by engineer. */
"Both you and your contact can send voice messages." = "您和您的联系人都可以发送语音消息。";
@@ -849,6 +900,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Business chats" = "企业聊天";
+/* No comment provided by engineer. */
+"Business connection" = "企业连接";
+
/* No comment provided by engineer. */
"Businesses" = "企业";
@@ -888,6 +942,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't call member" = "无法呼叫成员";
+/* alert title */
+"Can't change profile" = "无法更改个人资料";
+
/* No comment provided by engineer. */
"Can't invite contact!" = "无法邀请联系人!";
@@ -897,6 +954,9 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "无法向成员发送消息";
+/* No comment provided by engineer. */
+"can't send messages" = "无法发送消息";
+
/* alert action
alert button
new chat action */
@@ -1035,9 +1095,21 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "将为你删除聊天 - 此操作无法撤销!";
+/* chat toolbar */
+"Chat with admins" = "和管理员聊天";
+
+/* No comment provided by engineer. */
+"Chat with member" = "和成员聊天";
+
+/* No comment provided by engineer. */
+"Chat with members before they join." = "在成员加入前和这些人聊天";
+
/* No comment provided by engineer. */
"Chats" = "聊天";
+/* No comment provided by engineer. */
+"Chats with members" = "和成员聊天";
+
/* No comment provided by engineer. */
"Check messages every 20 min." = "每 20 分钟检查消息。";
@@ -1179,6 +1251,9 @@ set passcode view */
/* No comment provided by engineer. */
"Connect automatically" = "自动连接";
+/* No comment provided by engineer. */
+"Connect faster! 🚀" = "更快地连接!🚀";
+
/* No comment provided by engineer. */
"Connect to desktop" = "连接到桌面";
@@ -1317,9 +1392,15 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "联系人已存在";
+/* No comment provided by engineer. */
+"contact deleted" = "删除了联系人";
+
/* No comment provided by engineer. */
"Contact deleted!" = "联系人已删除!";
+/* No comment provided by engineer. */
+"contact disabled" = "禁用了联系人";
+
/* No comment provided by engineer. */
"contact has e2e encryption" = "联系人具有端到端加密";
@@ -1338,9 +1419,18 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "联系人姓名";
+/* No comment provided by engineer. */
+"contact not ready" = "联系人未就绪";
+
/* No comment provided by engineer. */
"Contact preferences" = "联系人偏好设置";
+/* No comment provided by engineer. */
+"Contact requests from groups" = "来自群的联络请求";
+
+/* No comment provided by engineer. */
+"contact should accept…" = "联系人应当接受…";
+
/* No comment provided by engineer. */
"Contact will be deleted - this cannot be undone!" = "联系人将被删除-这是无法撤消的!";
@@ -1410,6 +1500,9 @@ set passcode view */
/* No comment provided by engineer. */
"Create SimpleX address" = "创建 SimpleX 地址";
+/* No comment provided by engineer. */
+"Create your address" = "创建地址";
+
/* No comment provided by engineer. */
"Create your profile" = "创建您的资料";
@@ -1583,6 +1676,9 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "删除聊天资料?";
+/* alert title */
+"Delete chat with member?" = "删除和成员的聊天吗?";
+
/* No comment provided by engineer. */
"Delete chat?" = "删除聊天?";
@@ -1637,10 +1733,14 @@ swipe action */
/* No comment provided by engineer. */
"Delete member message?" = "删除成员消息?";
+/* No comment provided by engineer. */
+"Delete member messages" = "删除成员消息";
+
/* No comment provided by engineer. */
"Delete message?" = "删除消息吗?";
-/* alert button */
+/* alert action
+alert button */
"Delete messages" = "删除消息";
/* No comment provided by engineer. */
@@ -1709,9 +1809,15 @@ swipe action */
/* No comment provided by engineer. */
"Delivery receipts!" = "送达回执!";
+/* No comment provided by engineer. */
+"Deprecated options" = "已废弃的选项";
+
/* No comment provided by engineer. */
"Description" = "描述";
+/* alert title */
+"Description too large" = "描述过大";
+
/* No comment provided by engineer. */
"Desktop address" = "桌面地址";
@@ -1914,6 +2020,9 @@ chat item action */
/* No comment provided by engineer. */
"Edit group profile" = "编辑群组资料";
+/* No comment provided by engineer. */
+"Empty message!" = "空消息!";
+
/* No comment provided by engineer. */
"Enable" = "启用";
@@ -1926,6 +2035,9 @@ chat item action */
/* No comment provided by engineer. */
"Enable camera access" = "启用相机访问";
+/* No comment provided by engineer. */
+"Enable disappearing messages by default." = "默认启用定时消失消息。";
+
/* No comment provided by engineer. */
"Enable Flux in Network & servers settings for better metadata privacy." = "在“网络&服务器”设置中启用 Flux,更好地保护元数据隐私。";
@@ -2097,15 +2209,24 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "接受联系人请求错误";
+/* alert title */
+"Error accepting member" = "接受成员出错";
+
/* No comment provided by engineer. */
"Error adding member(s)" = "添加成员错误";
/* alert title */
"Error adding server" = "添加服务器出错";
+/* No comment provided by engineer. */
+"Error adding short link" = "添加短链接出错";
+
/* No comment provided by engineer. */
"Error changing address" = "更改地址错误";
+/* alert title */
+"Error changing chat profile" = "更改聊天资料出错";
+
/* No comment provided by engineer. */
"Error changing connection profile" = "更改连接资料出错";
@@ -2118,6 +2239,9 @@ chat item action */
/* No comment provided by engineer. */
"Error changing to incognito!" = "切换至隐身聊天出错!";
+/* No comment provided by engineer. */
+"Error checking token status" = "查询token状态出错";
+
/* alert message */
"Error connecting to forwarding server %@. Please try later." = "连接到转发服务器 %@ 时出错。请稍后尝试。";
@@ -2148,6 +2272,9 @@ chat item action */
/* No comment provided by engineer. */
"Error decrypting file" = "解密文件时出错";
+/* alert title */
+"Error deleting chat" = "删除聊天出错";
+
/* alert title */
"Error deleting chat database" = "删除聊天数据库错误";
@@ -2202,6 +2329,9 @@ chat item action */
/* No comment provided by engineer. */
"Error opening chat" = "打开聊天时出错";
+/* No comment provided by engineer. */
+"Error opening group" = "打开群时出错";
+
/* alert title */
"Error receiving file" = "接收文件错误";
@@ -2214,6 +2344,9 @@ chat item action */
/* alert title */
"Error registering for notifications" = "注册消息推送出错";
+/* alert title */
+"Error rejecting contact request" = "拒绝联络请求出错";
+
/* alert title */
"Error removing member" = "删除成员错误";
@@ -2259,6 +2392,9 @@ chat item action */
/* No comment provided by engineer. */
"Error sending message" = "发送消息错误";
+/* No comment provided by engineer. */
+"Error setting auto-accept" = "设置自动接受出错";
+
/* No comment provided by engineer. */
"Error setting delivery receipts!" = "设置送达回执出错!";
@@ -2309,6 +2445,9 @@ file error text
snd error text */
"Error: %@" = "错误: %@";
+/* server test error */
+"Error: %@." = "错误:%@。";
+
/* No comment provided by engineer. */
"Error: no database file" = "错误:没有数据库文件";
@@ -2417,6 +2556,9 @@ snd error text */
/* chat feature */
"Files and media" = "文件和媒体";
+/* No comment provided by engineer. */
+"Files and media are prohibited in this chat." = "此聊天禁止文件和媒体。";
+
/* No comment provided by engineer. */
"Files and media are prohibited." = "此群组中禁止文件和媒体。";
@@ -2426,6 +2568,9 @@ snd error text */
/* No comment provided by engineer. */
"Files and media prohibited!" = "禁止文件和媒体!";
+/* No comment provided by engineer. */
+"Filter" = "过滤器";
+
/* No comment provided by engineer. */
"Filter unread and favorite chats." = "过滤未读和收藏的聊天记录。";
@@ -2441,6 +2586,15 @@ snd error text */
/* No comment provided by engineer. */
"Find chats faster" = "更快地查找聊天记录";
+/* No comment provided by engineer. */
+"Fingerprint in destination server address does not match certificate: %@." = "目的地服务器的指纹与证书不符:%@。";
+
+/* No comment provided by engineer. */
+"Fingerprint in forwarding server address does not match certificate: %@." = "转发服务器的指纹与证书不符:%@。";
+
+/* No comment provided by engineer. */
+"Fingerprint in server address does not match certificate: %@." = "服务器的指纹与证书不符:%@。";
+
/* server test error */
"Fingerprint in server address does not match certificate." = "服务器地址中的证书指纹可能不正确";
@@ -2561,6 +2715,9 @@ snd error text */
/* message preview */
"Good morning!" = "早上好!";
+/* shown on group welcome message */
+"group" = "群";
+
/* No comment provided by engineer. */
"Group" = "群组";
@@ -2591,6 +2748,9 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "群组邀请不再有效,已被发件人删除。";
+/* No comment provided by engineer. */
+"group is deleted" = "群被删除了";
+
/* No comment provided by engineer. */
"Group link" = "群组链接";
@@ -2615,6 +2775,9 @@ snd error text */
/* snd group event chat item */
"group profile updated" = "群组资料已更新";
+/* alert message */
+"Group profile was changed. If you save it, the updated profile will be sent to group members." = "群资料已修改。如果你进行保存,修改后的群资料将发送给其他群成员。";
+
/* No comment provided by engineer. */
"Group welcome message" = "群欢迎词";
@@ -2711,6 +2874,9 @@ snd error text */
/* No comment provided by engineer. */
"Image will be received when your contact is online, please wait or check later!" = "图片将在您的联系人在线时收到,请稍等或稍后查看!";
+/* No comment provided by engineer. */
+"Images" = "图片";
+
/* No comment provided by engineer. */
"Immediately" = "立即";
@@ -2894,6 +3060,9 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "邀请朋友";
+/* No comment provided by engineer. */
+"Invite member" = "邀请成员";
+
/* No comment provided by engineer. */
"Invite members" = "邀请成员";
@@ -2990,6 +3159,9 @@ snd error text */
/* alert title */
"Keep unused invitation?" = "保留未使用的邀请吗?";
+/* No comment provided by engineer. */
+"Keep your chats clean" = "保持聊天洁净";
+
/* No comment provided by engineer. */
"Keep your connections" = "保持连接";
@@ -3023,6 +3195,9 @@ snd error text */
/* rcv group event chat item */
"left" = "已离开";
+/* No comment provided by engineer. */
+"Less traffic on mobile networks." = "消耗更少的移动网络数据。";
+
/* email subject */
"Let's talk in SimpleX Chat" = "让我们一起在 SimpleX Chat 里聊天";
@@ -3041,6 +3216,9 @@ snd error text */
/* No comment provided by engineer. */
"Linked desktops" = "已链接桌面";
+/* No comment provided by engineer. */
+"Links" = "链接";
+
/* swipe action */
"List" = "列表";
@@ -3059,6 +3237,9 @@ snd error text */
/* No comment provided by engineer. */
"Live messages" = "实时消息";
+/* in progress text */
+"Loading profile…" = "正加载个人资料…";
+
/* No comment provided by engineer. */
"Local name" = "本地名称";
@@ -3110,15 +3291,27 @@ snd error text */
/* No comment provided by engineer. */
"Member" = "成员";
+/* past/unknown group member */
+"Member %@" = "成员 %@";
+
/* profile update event chat item */
"member %@ changed to %@" = "成员 %1$@ 已更改为 %2$@";
+/* No comment provided by engineer. */
+"Member admission" = "成员准入";
+
/* rcv group event chat item */
"member connected" = "已连接";
+/* No comment provided by engineer. */
+"member has old version" = "成员有旧版本";
+
/* item status text */
"Member inactive" = "成员不活跃";
+/* No comment provided by engineer. */
+"Member is deleted - can't accept request" = "成员被删除——无法接受请求";
+
/* chat feature */
"Member reports" = "成员举报";
@@ -3131,12 +3324,15 @@ snd error text */
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". The member will receive a new invitation." = "成员角色将更改为 \"%@\"。该成员将收到一份新的邀请。";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from chat - this cannot be undone!" = "将从聊天中删除成员 - 此操作无法撤销!";
-/* No comment provided by engineer. */
+/* alert message */
"Member will be removed from group - this cannot be undone!" = "成员将被移出群组——此操作无法撤消!";
+/* alert message */
+"Member will join the group, accept member?" = "成员将加入本群,接受成员吗?";
+
/* No comment provided by engineer. */
"Members can add message reactions." = "群组成员可以添加信息回应。";
@@ -3185,6 +3381,9 @@ snd error text */
/* item status text */
"Message forwarded" = "消息已转发";
+/* No comment provided by engineer. */
+"Message instantly once you tap Connect." = "轻按连接后即刻发消息。";
+
/* item status description */
"Message may be delivered later if member becomes active." = "如果 member 变为活动状态,则稍后可能会发送消息。";
@@ -3233,6 +3432,9 @@ snd error text */
/* No comment provided by engineer. */
"Messages & files" = "消息";
+/* No comment provided by engineer. */
+"Messages are protected by **end-to-end encryption**." = "消息已通过**端到端加密**保护。";
+
/* No comment provided by engineer. */
"Messages from %@ will be shown!" = "将显示来自 %@ 的消息!";
@@ -3311,6 +3513,9 @@ snd error text */
/* marked deleted chat item preview text */
"moderated by %@" = "由 %@ 审核";
+/* member role */
+"moderator" = "协管";
+
/* time unit */
"months" = "月";
@@ -3395,6 +3600,9 @@ snd error text */
/* notification */
"New events" = "新事件";
+/* No comment provided by engineer. */
+"New group role: Moderator" = "新的群角色:协管";
+
/* No comment provided by engineer. */
"New in %@" = "%@ 的新内容";
@@ -3404,6 +3612,9 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "新成员角色";
+/* rcv group event chat item */
+"New member wants to join the group." = "新成员要加入本群。";
+
/* notification */
"new message" = "新消息";
@@ -3443,6 +3654,9 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "列表 %@ 中无聊天";
+/* No comment provided by engineer. */
+"No chats with members" = "没有和成员的聊天";
+
/* No comment provided by engineer. */
"No contacts selected" = "未选择联系人";
@@ -3494,6 +3708,9 @@ snd error text */
/* No comment provided by engineer. */
"No permission to record voice message" = "没有录制语音消息的权限";
+/* alert title */
+"No private routing session" = "无私密路由会话";
+
/* No comment provided by engineer. */
"No push server" = "本地";
@@ -3512,6 +3729,9 @@ snd error text */
/* servers error */
"No servers to send files." = "无文件发送服务器。";
+/* No comment provided by engineer. */
+"no subscription" = "无订阅";
+
/* copied message info in history */
"no text" = "无文本";
@@ -3527,6 +3747,9 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "不兼容!";
+/* No comment provided by engineer. */
+"not synchronized" = "未同步";
+
/* No comment provided by engineer. */
"Notes" = "附注";
@@ -3634,6 +3857,9 @@ new chat action */
/* No comment provided by engineer. */
"Only you can send disappearing messages." = "只有您可以发送限时消息。";
+/* No comment provided by engineer. */
+"Only you can send files and media." = "只有你可以发送文件和媒体。";
+
/* No comment provided by engineer. */
"Only you can send voice messages." = "只有您可以发送语音消息。";
@@ -3649,6 +3875,9 @@ new chat action */
/* No comment provided by engineer. */
"Only your contact can send disappearing messages." = "只有您的联系人才可以发送限时消息。";
+/* No comment provided by engineer. */
+"Only your contact can send files and media." = "只有你的联系人可以发送文件和媒体。";
+
/* No comment provided by engineer. */
"Only your contact can send voice messages." = "只有您的联系人可以发送语音消息。";
@@ -3664,18 +3893,45 @@ new chat action */
/* authentication reason */
"Open chat console" = "打开聊天控制台";
+/* alert action */
+"Open clean link" = "打开干净链接";
+
/* No comment provided by engineer. */
"Open conditions" = "打开条款";
+/* alert action */
+"Open full link" = "打开完整链接";
+
/* new chat action */
"Open group" = "打开群";
+/* alert title */
+"Open link?" = "打开链接?";
+
/* authentication reason */
"Open migration to another device" = "打开迁移到另一台设备";
+/* new chat action */
+"Open new chat" = "打开新聊天";
+
+/* new chat action */
+"Open new group" = "打开新群";
+
/* No comment provided by engineer. */
"Open Settings" = "打开设置";
+/* No comment provided by engineer. */
+"Open to accept" = "打开以接受";
+
+/* No comment provided by engineer. */
+"Open to connect" = "打开以连接";
+
+/* No comment provided by engineer. */
+"Open to join" = "打开以加入";
+
+/* No comment provided by engineer. */
+"Open to use bot" = "打开来使用机器人";
+
/* No comment provided by engineer. */
"Opening app…" = "正在打开应用程序…";
@@ -3715,6 +3971,9 @@ new chat action */
/* No comment provided by engineer. */
"other errors" = "其他错误";
+/* alert message */
+"Other file errors:\n%@" = "其他文件错误:\n%@";
+
/* member role */
"owner" = "群主";
@@ -3760,6 +4019,12 @@ new chat action */
/* No comment provided by engineer. */
"Pending" = "待定";
+/* No comment provided by engineer. */
+"pending approval" = "待批准";
+
+/* No comment provided by engineer. */
+"pending review" = "待审核";
+
/* No comment provided by engineer. */
"Periodic" = "定期";
@@ -3826,15 +4091,33 @@ new chat action */
/* No comment provided by engineer. */
"Please store passphrase securely, you will NOT be able to change it if you lose it." = "请安全地保存密码,如果您丢失了密码,您将无法更改它。";
+/* token info */
+"Please try to disable and re-enable notfications." = "请尝试禁用并重新启用通知。";
+
+/* snd group event chat item */
+"Please wait for group moderators to review your request to join the group." = "请等待群的协管审核你加入该群的请求。";
+
+/* token info */
+"Please wait for token activation to complete." = "请等待token激活完成。";
+
+/* token info */
+"Please wait for token to be registered." = "请等待token注册完成。";
+
/* No comment provided by engineer. */
"Polish interface" = "波兰语界面";
+/* No comment provided by engineer. */
+"Port" = "端口";
+
/* No comment provided by engineer. */
"Preserve the last message draft, with attachments." = "保留最后的消息草稿及其附件。";
/* No comment provided by engineer. */
"Preset server address" = "预设服务器地址";
+/* No comment provided by engineer. */
+"Preset servers" = "预设服务器";
+
/* No comment provided by engineer. */
"Preview" = "预览";
@@ -3844,6 +4127,9 @@ new chat action */
/* No comment provided by engineer. */
"Privacy & security" = "隐私和安全";
+/* No comment provided by engineer. */
+"Privacy for your customers." = "客户隐私。";
+
/* No comment provided by engineer. */
"Privacy policy and conditions of use." = "隐私政策和使用条款。";
@@ -3856,6 +4142,9 @@ new chat action */
/* No comment provided by engineer. */
"Private filenames" = "私密文件名";
+/* No comment provided by engineer. */
+"Private media file names." = "私密媒体文件名。";
+
/* No comment provided by engineer. */
"Private message routing" = "私有消息路由";
@@ -3871,6 +4160,9 @@ new chat action */
/* alert title */
"Private routing error" = "专用路由错误";
+/* alert title */
+"Private routing timeout" = "私密路由超时";
+
/* No comment provided by engineer. */
"Profile and server connections" = "资料和服务器连接";
@@ -3901,6 +4193,9 @@ new chat action */
/* No comment provided by engineer. */
"Prohibit messages reactions." = "禁止消息回应。";
+/* No comment provided by engineer. */
+"Prohibit reporting messages to moderators." = "禁止向 协管 举报消息。";
+
/* No comment provided by engineer. */
"Prohibit sending direct messages to members." = "禁止向成员发送私信。";
@@ -3928,6 +4223,9 @@ new chat action */
/* No comment provided by engineer. */
"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "保护您的 IP 地址免受联系人选择的消息中继的攻击。\n在*网络和服务器*设置中启用。";
+/* No comment provided by engineer. */
+"Protocol background timeout" = "协议后台超时";
+
/* No comment provided by engineer. */
"Protocol timeout" = "协议超时";
@@ -3940,6 +4238,9 @@ new chat action */
/* No comment provided by engineer. */
"Proxied servers" = "代理服务器";
+/* No comment provided by engineer. */
+"Proxy requires password" = "代理需要密码";
+
/* No comment provided by engineer. */
"Push notifications" = "推送通知";
@@ -4057,6 +4358,12 @@ new chat action */
/* No comment provided by engineer. */
"Reduced battery usage" = "减少电池使用量";
+/* No comment provided by engineer. */
+"Register" = "注册";
+
+/* token status text */
+"Registered" = "已注册";
+
/* alert action
reject incoming call via notification
swipe action */
@@ -4068,6 +4375,12 @@ swipe action */
/* alert title */
"Reject contact request" = "拒绝联系人请求";
+/* alert title */
+"Reject member?" = "拒绝成员?";
+
+/* No comment provided by engineer. */
+"rejected" = "被拒绝";
+
/* call status */
"rejected call" = "拒接来电";
@@ -4077,16 +4390,25 @@ swipe action */
/* No comment provided by engineer. */
"Relay server protects your IP address, but it can observe the duration of the call." = "中继服务器保护您的 IP 地址,但它可以观察通话的持续时间。";
-/* No comment provided by engineer. */
+/* alert action */
"Remove" = "移除";
+/* alert action */
+"Remove and delete messages" = "移除并删除消息";
+
+/* No comment provided by engineer. */
+"Remove archive?" = "删除存档?";
+
/* No comment provided by engineer. */
"Remove image" = "移除图片";
/* No comment provided by engineer. */
-"Remove member" = "删除成员";
+"Remove link tracking" = "删除链接跟踪";
/* No comment provided by engineer. */
+"Remove member" = "删除成员";
+
+/* alert title */
"Remove member?" = "删除成员吗?";
/* No comment provided by engineer. */
@@ -4101,12 +4423,18 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "删除了联系地址";
+/* No comment provided by engineer. */
+"removed from group" = "从群被删除了";
+
/* profile update event chat item */
"removed profile picture" = "删除了资料图片";
/* rcv group event chat item */
"removed you" = "已将您移除";
+/* No comment provided by engineer. */
+"Removes messages and blocks members." = "删除消息并封禁成员。";
+
/* No comment provided by engineer. */
"Renegotiate" = "重新协商";
@@ -4128,6 +4456,54 @@ swipe action */
/* chat item action */
"Reply" = "回复";
+/* chat item action */
+"Report" = "举报";
+
+/* report reason */
+"Report content: only group moderators will see it." = "举报内容:仅协管会看到。";
+
+/* report reason */
+"Report member profile: only group moderators will see it." = "举报成员个人资料:仅协管会看到。";
+
+/* report reason */
+"Report other: only group moderators will see it." = "举报其他:仅协管会看到。";
+
+/* No comment provided by engineer. */
+"Report reason?" = "举报理由?";
+
+/* alert title */
+"Report sent to moderators" = "举报已发送至 协管";
+
+/* report reason */
+"Report spam: only group moderators will see it." = "举报垃圾信息:仅协管会看到。";
+
+/* report reason */
+"Report violation: only group moderators will see it." = "举报违规:仅协管会看到。";
+
+/* report in notification */
+"Report: %@" = "举报: %@";
+
+/* No comment provided by engineer. */
+"Reporting messages to moderators is prohibited." = "向协管举报消息已被禁止。";
+
+/* No comment provided by engineer. */
+"Reports" = "举报";
+
+/* No comment provided by engineer. */
+"request is sent" = "发送了请求";
+
+/* No comment provided by engineer. */
+"request to join rejected" = "加入请求被拒绝";
+
+/* rcv group event chat item */
+"requested connection" = "已请求连接";
+
+/* rcv direct event chat item */
+"requested connection from group %@" = "来自群组%@的已请求连接";
+
+/* chat list item title */
+"requested to connect" = "被请求连接";
+
/* No comment provided by engineer. */
"Required" = "必须";
@@ -4179,9 +4555,21 @@ swipe action */
/* chat item action */
"Reveal" = "揭示";
+/* No comment provided by engineer. */
+"review" = "审核";
+
/* No comment provided by engineer. */
"Review conditions" = "审阅条款";
+/* No comment provided by engineer. */
+"Review group members" = "审核群成员";
+
+/* admission stage */
+"Review members" = "审核成员";
+
+/* No comment provided by engineer. */
+"reviewed by admins" = "由管理员审核";
+
/* No comment provided by engineer. */
"Revoke" = "吊销";
@@ -4210,6 +4598,12 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "保存(并通知联系人)";
+/* alert button */
+"Save (and notify members)" = "保存(并通知成员)";
+
+/* alert title */
+"Save admission settings?" = "保存入群设置?";
+
/* alert button */
"Save and notify contact" = "保存并通知联系人";
@@ -4225,6 +4619,9 @@ chat item action */
/* No comment provided by engineer. */
"Save group profile" = "保存群组资料";
+/* alert title */
+"Save group profile?" = "保存群资料?";
+
/* No comment provided by engineer. */
"Save list" = "保存列表";
@@ -4303,9 +4700,24 @@ chat item action */
/* No comment provided by engineer. */
"Search bar accepts invitation links." = "搜索栏接受邀请链接。";
+/* No comment provided by engineer. */
+"Search files" = "搜索文件";
+
+/* No comment provided by engineer. */
+"Search images" = "搜索图片";
+
+/* No comment provided by engineer. */
+"Search links" = "搜索链接";
+
/* No comment provided by engineer. */
"Search or paste SimpleX link" = "搜索或粘贴 SimpleX 链接";
+/* No comment provided by engineer. */
+"Search videos" = "搜索视频";
+
+/* No comment provided by engineer. */
+"Search voice messages" = "搜索语音消息";
+
/* network option */
"sec" = "秒";
@@ -4336,6 +4748,9 @@ chat item action */
/* chat item action */
"Select" = "选择";
+/* No comment provided by engineer. */
+"Select chat profile" = "选择聊天个人资料";
+
/* No comment provided by engineer. */
"Selected %lld" = "选定的 %lld";
@@ -4360,6 +4775,9 @@ chat item action */
/* No comment provided by engineer. */
"Send a live message - it will update for the recipient(s) as you type it" = "发送实时消息——它会在您键入时为收件人更新";
+/* No comment provided by engineer. */
+"Send contact request?" = "发送联络请求?";
+
/* No comment provided by engineer. */
"Send delivery receipts to" = "将送达回执发送给";
@@ -4390,18 +4808,30 @@ chat item action */
/* No comment provided by engineer. */
"Send notifications" = "发送通知";
+/* No comment provided by engineer. */
+"Send private reports" = "发送私下举报";
+
/* No comment provided by engineer. */
"Send questions and ideas" = "发送问题和想法";
/* No comment provided by engineer. */
"Send receipts" = "发送回执";
+/* No comment provided by engineer. */
+"Send request" = "发送请求";
+
+/* No comment provided by engineer. */
+"Send request without message" = "发送无消息请求";
+
/* No comment provided by engineer. */
"Send them from gallery or custom keyboards." = "发送它们来自图库或自定义键盘。";
/* No comment provided by engineer. */
"Send up to 100 last messages to new members." = "给新成员发送最多 100 条历史消息。";
+/* No comment provided by engineer. */
+"Send your private feedback to groups." = "向群发送私密反馈。";
+
/* alert message */
"Sender cancelled file transfer." = "发送人已取消文件传输。";
@@ -4459,6 +4889,12 @@ chat item action */
/* No comment provided by engineer. */
"Sent via proxy" = "通过代理发送";
+/* No comment provided by engineer. */
+"Server" = "服务器";
+
+/* alert message */
+"Server added to operator %@." = "服务器已添加到运营方 %@。";
+
/* No comment provided by engineer. */
"Server address" = "服务器地址";
@@ -4468,6 +4904,15 @@ chat item action */
/* srv error text. */
"Server address is incompatible with network settings." = "服务器地址与网络设置不兼容。";
+/* alert title */
+"Server operator changed." = "服务器运营方已更改。";
+
+/* No comment provided by engineer. */
+"Server operators" = "服务器运营方";
+
+/* alert title */
+"Server protocol changed." = "服务器协议已更改。";
+
/* queue info */
"server queue info: %@\n\nlast received msg: %@" = "服务器队列信息: %1$@\n\n上次收到的消息: %2$@";
@@ -4504,6 +4949,9 @@ chat item action */
/* No comment provided by engineer. */
"Set 1 day" = "设定1天";
+/* No comment provided by engineer. */
+"Set chat name…" = "设置聊天名称…";
+
/* No comment provided by engineer. */
"Set contact name…" = "设置联系人姓名……";
@@ -4516,6 +4964,12 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "设置它以代替系统身份验证。";
+/* No comment provided by engineer. */
+"Set member admission" = "设置成员入群准许";
+
+/* No comment provided by engineer. */
+"Set message expiration in chats." = "在聊天中设置消息过期时间。";
+
/* profile update event chat item */
"set new contact address" = "设置新的联系地址";
@@ -4531,6 +4985,9 @@ chat item action */
/* No comment provided by engineer. */
"Set passphrase to export" = "设置密码来导出";
+/* No comment provided by engineer. */
+"Set profile bio and welcome message." = "设置自我介绍和欢迎消息。";
+
/* No comment provided by engineer. */
"Set the message shown to new members!" = "设置向新成员显示的消息!";
@@ -4540,6 +4997,9 @@ chat item action */
/* No comment provided by engineer. */
"Settings" = "设置";
+/* alert message */
+"Settings were changed." = "设置已修改。";
+
/* No comment provided by engineer. */
"Shape profile images" = "改变个人资料图形状";
@@ -4550,9 +5010,15 @@ chat item action */
/* No comment provided by engineer. */
"Share 1-time link" = "分享一次性链接";
+/* No comment provided by engineer. */
+"Share 1-time link with a friend" = "和一位好友分享一次性链接";
+
/* No comment provided by engineer. */
"Share address" = "分享地址";
+/* No comment provided by engineer. */
+"Share address publicly" = "公开分享地址";
+
/* alert title */
"Share address with contacts?" = "与联系人分享地址?";
@@ -4562,6 +5028,18 @@ chat item action */
/* No comment provided by engineer. */
"Share link" = "分享链接";
+/* alert button */
+"Share old address" = "分享旧地址";
+
+/* alert button */
+"Share old link" = "分享旧链接";
+
+/* No comment provided by engineer. */
+"Share profile" = "分享资料";
+
+/* No comment provided by engineer. */
+"Share SimpleX address on social media." = "在社媒上分享 SimpleX 地址。";
+
/* No comment provided by engineer. */
"Share this 1-time invite link" = "分享此一次性邀请链接";
@@ -4571,6 +5049,18 @@ chat item action */
/* No comment provided by engineer. */
"Share with contacts" = "与联系人分享";
+/* No comment provided by engineer. */
+"Share your address" = "分享地址";
+
+/* No comment provided by engineer. */
+"Short description" = "短描述";
+
+/* No comment provided by engineer. */
+"Short link" = "短链接";
+
+/* No comment provided by engineer. */
+"Short SimpleX address" = "SimpleX 短地址";
+
/* No comment provided by engineer. */
"Show → on messages sent via private routing." = "显示 → 通过专用路由发送的信息.";
@@ -4661,6 +5151,9 @@ chat item action */
/* No comment provided by engineer. */
"SimpleX protocols reviewed by Trail of Bits." = "SimpleX 协议由 Trail of Bits 审阅。";
+/* simplex link type */
+"SimpleX relay link" = "SimpleX 中继链接";
+
/* No comment provided by engineer. */
"Simplified incognito mode" = "简化的隐身模式";
@@ -4679,6 +5172,9 @@ chat item action */
/* No comment provided by engineer. */
"SMP server" = "SMP 服务器";
+/* No comment provided by engineer. */
+"SOCKS proxy" = "SOCKS代理";
+
/* blur media */
"Soft" = "软";
@@ -4694,9 +5190,16 @@ chat item action */
/* No comment provided by engineer. */
"Some non-fatal errors occurred during import:" = "导入过程中出现一些非致命错误:";
+/* alert message */
+"Some servers failed the test:\n%@" = "有服务器测试未通过:\n%@";
+
/* notification title */
"Somebody" = "某人";
+/* blocking reason
+report reason */
+"Spam" = "垃圾信息";
+
/* No comment provided by engineer. */
"Square, circle, or anything in between." = "方形、圆形、或两者之间的任意形状.";
@@ -4775,18 +5278,42 @@ chat item action */
/* No comment provided by engineer. */
"Support SimpleX Chat" = "支持 SimpleX Chat";
+/* No comment provided by engineer. */
+"Switch audio and video during the call." = "通话期间切换音频和视频。";
+
+/* No comment provided by engineer. */
+"Switch chat profile for 1-time invitations." = "对一次性邀请切换聊天个人资料。";
+
/* No comment provided by engineer. */
"System" = "系统";
/* No comment provided by engineer. */
"System authentication" = "系统验证";
+/* No comment provided by engineer. */
+"Tail" = "尾部";
+
/* No comment provided by engineer. */
"Take picture" = "拍照";
/* No comment provided by engineer. */
"Tap button " = "点击按钮 ";
+/* No comment provided by engineer. */
+"Tap Connect to chat" = "轻按连接进行聊天";
+
+/* No comment provided by engineer. */
+"Tap Connect to send request" = "轻按连接来发送请求";
+
+/* No comment provided by engineer. */
+"Tap Connect to use bot" = "轻按“连接”使用机器人";
+
+/* No comment provided by engineer. */
+"Tap Create SimpleX address in the menu to create it later." = "要稍后创建 SimpleX 地址,请在菜单中轻按“创建 SimpleX 地址”";
+
+/* No comment provided by engineer. */
+"Tap Join group" = "轻按加入群";
+
/* No comment provided by engineer. */
"Tap to activate profile." = "点击以激活个人资料。";
@@ -4808,9 +5335,15 @@ chat item action */
/* No comment provided by engineer. */
"TCP connection" = "TCP 连接";
+/* No comment provided by engineer. */
+"TCP connection bg timeout" = "TCP 连接后台超时";
+
/* No comment provided by engineer. */
"TCP connection timeout" = "TCP 连接超时";
+/* No comment provided by engineer. */
+"TCP port for messaging" = "用于消息收发的 TCP 端口";
+
/* No comment provided by engineer. */
"TCP_KEEPCNT" = "TCP_KEEPCNT";
@@ -4826,6 +5359,9 @@ chat item action */
/* server test failure */
"Test failed at step %@." = "在步骤 %@ 上测试失败。";
+/* No comment provided by engineer. */
+"Test notifications" = "测试通知";
+
/* No comment provided by engineer. */
"Test server" = "测试服务器";
@@ -4844,9 +5380,15 @@ chat item action */
/* No comment provided by engineer. */
"Thanks to the users – contribute via Weblate!" = "感谢用户——通过 Weblate 做出贡献!";
+/* alert message */
+"The address will be short, and your profile will be shared via the address." = "地址不会长,将通过该简短地址分享个人资料。";
+
/* No comment provided by engineer. */
"The app can notify you when you receive messages or contact requests - please open settings to enable." = "该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。";
+/* No comment provided by engineer. */
+"The app protects your privacy by using different operators in each conversation." = "应用通过在每个对话中使用不同运营方保护你的隐私。";
+
/* No comment provided by engineer. */
"The app will ask to confirm downloads from unknown file servers (except .onion)." = "该应用程序将要求确认从未知文件服务器(.onion 除外)下载。";
@@ -4856,6 +5398,9 @@ chat item action */
/* No comment provided by engineer. */
"The code you scanned is not a SimpleX link QR code." = "您扫描的码不是 SimpleX 链接的二维码。";
+/* No comment provided by engineer. */
+"The connection reached the limit of undelivered messages, your contact may be offline." = "连接达到了未送达消息上限,你的联系人可能处于离线状态。";
+
/* No comment provided by engineer. */
"The connection you accepted will be cancelled!" = "您接受的连接将被取消!";
@@ -4877,6 +5422,9 @@ chat item action */
/* No comment provided by engineer. */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "下一条消息的 ID 不正确(小于或等于上一条)。\n它可能是由于某些错误或连接被破坏才发生。";
+/* alert message */
+"The link will be short, and group profile will be shared via the link." = "链接不会长,群资料会通过短链接分享。";
+
/* No comment provided by engineer. */
"The message will be deleted for all members." = "将为所有成员删除该消息。";
@@ -4892,6 +5440,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。";
+/* No comment provided by engineer. */
+"The second preset operator in the app!" = "应用中的第二个预设运营方!";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅";
@@ -4904,9 +5455,15 @@ chat item action */
/* No comment provided by engineer. */
"The text you pasted is not a SimpleX link." = "您粘贴的文本不是 SimpleX 链接。";
+/* No comment provided by engineer. */
+"The uploaded database archive will be permanently removed from the servers." = "已上传的数据库归档将会从服务器中永久移除。";
+
/* No comment provided by engineer. */
"Themes" = "主题";
+/* No comment provided by engineer. */
+"These conditions will also apply for: **%@**." = "这些条件将同样适用于: **%@**。";
+
/* No comment provided by engineer. */
"These settings are for your current profile **%@**." = "这些设置适用于您当前的配置文件 **%@**。";
@@ -4919,6 +5476,9 @@ chat item action */
/* No comment provided by engineer. */
"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "此操作无法撤消——早于所选的发送和接收的消息将被删除。 这可能需要几分钟时间。";
+/* alert message */
+"This action cannot be undone - the messages sent and received in this chat earlier than selected will be deleted." = "此操作无法撤销 —— 比此聊天中所选消息更早发出并收到的消息将被删除。";
+
/* No comment provided by engineer. */
"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "此操作无法撤消——您的个人资料、联系人、消息和文件将不可撤回地丢失。";
@@ -4943,12 +5503,24 @@ chat item action */
/* No comment provided by engineer. */
"This group no longer exists." = "该群组已不存在。";
+/* No comment provided by engineer. */
+"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "此链接需要更新的应用版本。请升级应用或请求你的联系人发送相容的链接。";
+
/* No comment provided by engineer. */
"This link was used with another mobile device, please create a new link on the desktop." = "此链接已在其他移动设备上使用,请在桌面上创建新链接。";
+/* No comment provided by engineer. */
+"This message was deleted or not received yet." = "此消息被删除或尚未收到。";
+
/* No comment provided by engineer. */
"This setting applies to messages in your current chat profile **%@**." = "此设置适用于您当前聊天资料 **%@** 中的消息。";
+/* No comment provided by engineer. */
+"This setting is for your current profile **%@**." = "此设置用于当前个人资料 **%@**。";
+
+/* No comment provided by engineer. */
+"Time to disappear is set only for new contacts." = "只为新联系人设置了消失时间。";
+
/* No comment provided by engineer. */
"Title" = "标题";
@@ -4964,6 +5536,9 @@ chat item action */
/* No comment provided by engineer. */
"To make a new connection" = "建立新连接";
+/* No comment provided by engineer. */
+"To protect against your link being replaced, you can compare contact security codes." = "为了防止链接被替换,你可以比较联系人安全代码。";
+
/* No comment provided by engineer. */
"To protect timezone, image/voice files use UTC." = "为了保护时区,图像/语音文件使用 UTC。";
@@ -4976,15 +5551,36 @@ chat item action */
/* No comment provided by engineer. */
"To protect your privacy, SimpleX uses separate IDs for each of your contacts." = "为了保护隐私,SimpleX使用针对消息队列的标识符,而不是所有其他平台使用的用户ID,每个联系人都有独立的标识符。";
+/* No comment provided by engineer. */
+"To receive" = "消息接收";
+
+/* No comment provided by engineer. */
+"To record speech please grant permission to use Microphone." = "为了记录语音请授予使用麦克风权限。";
+
+/* No comment provided by engineer. */
+"To record video please grant permission to use Camera." = "为了录制视频请授予使用相机权限。";
+
/* No comment provided by engineer. */
"To record voice message please grant permission to use Microphone." = "请授权使用麦克风以录制语音消息。";
/* No comment provided by engineer. */
"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "要显示您的隐藏的个人资料,请在**您的聊天个人资料**页面的搜索字段中输入完整密码。";
+/* No comment provided by engineer. */
+"To send" = "发送";
+
+/* alert message */
+"To send commands you must be connected." = "你必须已连接才能发送命令。";
+
/* No comment provided by engineer. */
"To support instant push notifications the chat database has to be migrated." = "为了支持即时推送通知,聊天数据库必须被迁移。";
+/* alert message */
+"To use another profile after connection attempt, delete the chat and use the link again." = "要在连接尝试后使用不同的个人资料,请删除聊天并再次使用该链接。";
+
+/* No comment provided by engineer. */
+"To use the servers of **%@**, accept conditions of use." = "要使用**%@**的服务器,需接受条款。";
+
/* No comment provided by engineer. */
"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "要与您的联系人验证端到端加密,请比较(或扫描)您设备上的代码。";
@@ -5006,6 +5602,9 @@ chat item action */
/* No comment provided by engineer. */
"Transport sessions" = "传输会话";
+/* subscription status explanation */
+"Trying to connect to the server used to receive messages from this connection." = "尝试连接到用于从该连接接收消息的服务器。";
+
/* No comment provided by engineer. */
"Turkish interface" = "土耳其语界面";
@@ -5036,6 +5635,9 @@ chat item action */
/* rcv group event chat item */
"unblocked %@" = "未阻止 %@";
+/* No comment provided by engineer. */
+"Undelivered messages" = "未送达的消息";
+
/* No comment provided by engineer. */
"Unexpected migration state" = "未预料的迁移状态";
@@ -5102,6 +5704,9 @@ chat item action */
/* swipe action */
"Unread" = "未读";
+/* No comment provided by engineer. */
+"Unsupported connection link" = "不支持的连接链接";
+
/* No comment provided by engineer. */
"Up to 100 last messages are sent to new members." = "给新成员发送了最多 100 条历史消息。";
@@ -5117,6 +5722,9 @@ chat item action */
/* No comment provided by engineer. */
"Update settings?" = "更新设置?";
+/* No comment provided by engineer. */
+"Updated conditions" = "条款已更新";
+
/* rcv group event chat item */
"updated group profile" = "已更新的群组资料";
@@ -5126,9 +5734,27 @@ chat item action */
/* No comment provided by engineer. */
"Updating settings will re-connect the client to all servers." = "更新设置会将客户端重新连接到所有服务器。";
+/* alert button */
+"Upgrade" = "升级";
+
+/* No comment provided by engineer. */
+"Upgrade address" = "升级地址";
+
+/* alert message */
+"Upgrade address?" = "升级地址?";
+
/* No comment provided by engineer. */
"Upgrade and open chat" = "升级并打开聊天";
+/* alert message */
+"Upgrade group link?" = "升级群链接?";
+
+/* No comment provided by engineer. */
+"Upgrade link" = "升级链接";
+
+/* No comment provided by engineer. */
+"Upgrade your address" = "升级你的地址";
+
/* No comment provided by engineer. */
"Upload errors" = "上传错误";
@@ -5150,18 +5776,30 @@ chat item action */
/* No comment provided by engineer. */
"Use .onion hosts" = "使用 .onion 主机";
+/* No comment provided by engineer. */
+"Use %@" = "使用 %@";
+
/* No comment provided by engineer. */
"Use chat" = "使用聊天";
/* new chat action */
"Use current profile" = "使用当前配置文件";
+/* No comment provided by engineer. */
+"Use for files" = "用于文件";
+
+/* No comment provided by engineer. */
+"Use for messages" = "用于消息";
+
/* No comment provided by engineer. */
"Use for new connections" = "用于新连接";
/* No comment provided by engineer. */
"Use from desktop" = "从桌面端使用";
+/* No comment provided by engineer. */
+"Use incognito profile" = "使用隐身个人资料";
+
/* No comment provided by engineer. */
"Use iOS call interface" = "使用 iOS 通话界面";
@@ -5180,18 +5818,36 @@ chat item action */
/* No comment provided by engineer. */
"Use server" = "使用服务器";
+/* No comment provided by engineer. */
+"Use servers" = "使用服务器";
+
/* No comment provided by engineer. */
"Use SimpleX Chat servers?" = "使用 SimpleX Chat 服务器?";
+/* No comment provided by engineer. */
+"Use SOCKS proxy" = "使用 SOCKS 代理";
+
+/* No comment provided by engineer. */
+"Use TCP port %@ when no port is specified." = "当未指定端口时使用TCP端口%@。";
+
+/* No comment provided by engineer. */
+"Use TCP port 443 for preset servers only." = "仅预设服务器使用 TCP 协议 443 端口。";
+
/* No comment provided by engineer. */
"Use the app while in the call." = "通话时使用本应用.";
/* No comment provided by engineer. */
"Use the app with one hand." = "用一只手使用应用程序。";
+/* No comment provided by engineer. */
+"Use web port" = "使用 web 端口";
+
/* No comment provided by engineer. */
"User selection" = "用户选择";
+/* No comment provided by engineer. */
+"Username" = "用户名";
+
/* No comment provided by engineer. */
"Using SimpleX Chat servers." = "使用 SimpleX Chat 服务器。";
@@ -5255,12 +5911,21 @@ chat item action */
/* No comment provided by engineer. */
"Video will be received when your contact is online, please wait or check later!" = "视频将在您的联系人在线时收到,请稍等或稍后查看!";
+/* No comment provided by engineer. */
+"Videos" = "视频";
+
/* No comment provided by engineer. */
"Videos and files up to 1gb" = "最大 1gb 的视频和文件";
+/* No comment provided by engineer. */
+"View conditions" = "查看条款";
+
/* No comment provided by engineer. */
"View security code" = "查看安全码";
+/* No comment provided by engineer. */
+"View updated conditions" = "查看更新后的条款";
+
/* chat feature */
"Visible history" = "可见的历史";
@@ -5330,6 +5995,9 @@ chat item action */
/* No comment provided by engineer. */
"Welcome message is too long" = "欢迎消息太大了";
+/* No comment provided by engineer. */
+"Welcome your contacts 👋" = "欢迎联系人👋";
+
/* No comment provided by engineer. */
"What's new" = "更新内容";
@@ -5342,6 +6010,9 @@ chat item action */
/* No comment provided by engineer. */
"when IP hidden" = "当 IP 隐藏时";
+/* No comment provided by engineer. */
+"When more than one operator is enabled, none of them has metadata to learn who communicates with whom." = "当启用了超过一个运营方时,没有一个运营方拥有了解谁和谁联络的元数据。";
+
/* No comment provided by engineer. */
"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。";
@@ -5396,6 +6067,9 @@ chat item action */
/* No comment provided by engineer. */
"You accepted connection" = "您已接受连接";
+/* snd group event chat item */
+"you accepted this member" = "你接受了该成员";
+
/* No comment provided by engineer. */
"You allow" = "您允许";
@@ -5405,6 +6079,9 @@ chat item action */
/* No comment provided by engineer. */
"You are already connected to %@." = "您已经连接到 %@。";
+/* No comment provided by engineer. */
+"You are already connected with %@." = "你已经与%@保持连接。";
+
/* new chat sheet message */
"You are already connecting to %@." = "您已连接到 %@。";
@@ -5423,9 +6100,15 @@ chat item action */
/* new chat sheet title */
"You are already joining the group!\nRepeat join request?" = "您已经加入了这个群组!\n重复加入请求?";
+/* subscription status explanation */
+"You are connected to the server used to receive messages from this connection." = "你已连接到用于接收该连接消息的服务器。";
+
/* No comment provided by engineer. */
"You are invited to group" = "您被邀请加入群组";
+/* subscription status explanation */
+"You are not connected to the server used to receive messages from this connection (no subscription)." = "未连接到用于从该连接接收消息的服务器(无订阅)。";
+
/* No comment provided by engineer. */
"You are not connected to these servers. Private routing is used to deliver messages to them." = "您未连接到这些服务器。私有路由用于向他们发送消息。";
@@ -5441,6 +6124,9 @@ chat item action */
/* No comment provided by engineer. */
"You can change it in Appearance settings." = "您可以在外观设置中更改它。";
+/* No comment provided by engineer. */
+"You can configure servers via settings." = "你可以通过设置配置服务器。";
+
/* No comment provided by engineer. */
"You can create it later" = "您可以以后创建它";
@@ -5465,6 +6151,9 @@ chat item action */
/* No comment provided by engineer. */
"You can send messages to %@ from Archived contacts." = "您可以从存档的联系人向%@发送消息。";
+/* No comment provided by engineer. */
+"You can set connection name, to remember who the link was shared with." = "你可以设置连接名称,用来记住和谁分享了这个链接。";
+
/* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "您可以通过设置来设置锁屏通知预览。";
@@ -5489,6 +6178,9 @@ chat item action */
/* alert message */
"You can view invitation link again in connection details." = "您可以在连接详情中再次查看邀请链接。";
+/* alert message */
+"You can view your reports in Chat with admins." = "你可以在和管理员和聊天中查看你的举报。";
+
/* alert title */
"You can't send messages!" = "您无法发送消息!";
@@ -5561,6 +6253,9 @@ chat item action */
/* snd group event chat item */
"you unblocked %@" = "您解封了 %@";
+/* No comment provided by engineer. */
+"You will be able to send messages **only after your request is accepted**." = "**只有在你的请求被接受后**你才能发送消息。";
+
/* No comment provided by engineer. */
"You will be connected to group when the group host's device is online, please wait or check later!" = "您将在组主设备上线时连接到该群组,请稍等或稍后再检查!";
@@ -5579,6 +6274,9 @@ chat item action */
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。";
+/* No comment provided by engineer. */
+"You will stop receiving messages from this chat. Chat history will be preserved." = "你将停止从这个聊天收到消息。聊天历史将被保留。";
+
/* No comment provided by engineer. */
"You will stop receiving messages from this group. Chat history will be preserved." = "您将停止接收来自该群组的消息。聊天记录将被保留。";
@@ -5594,6 +6292,9 @@ chat item action */
/* No comment provided by engineer. */
"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "您正在为该群组使用隐身个人资料——为防止共享您的主要个人资料,不允许邀请联系人";
+/* No comment provided by engineer. */
+"Your business contact" = "你的企业联系人";
+
/* No comment provided by engineer. */
"Your calls" = "您的通话";
@@ -5603,9 +6304,15 @@ chat item action */
/* No comment provided by engineer. */
"Your chat database is not encrypted - set passphrase to encrypt it." = "您的聊天数据库未加密——设置密码来加密。";
+/* alert title */
+"Your chat preferences" = "你的聊天偏好设置";
+
/* No comment provided by engineer. */
"Your chat profiles" = "您的聊天资料";
+/* No comment provided by engineer. */
+"Your contact" = "你的联系人";
+
/* No comment provided by engineer. */
"Your contact sent a file that is larger than currently supported maximum size (%@)." = "您的联系人发送的文件大于当前支持的最大大小 (%@)。";
@@ -5615,12 +6322,18 @@ chat item action */
/* No comment provided by engineer. */
"Your contacts will remain connected." = "与您的联系人保持连接。";
+/* No comment provided by engineer. */
+"Your credentials may be sent unencrypted." = "你的凭据可能以未经加密的方式被发送。";
+
/* No comment provided by engineer. */
"Your current chat database will be DELETED and REPLACED with the imported one." = "您当前的聊天数据库将被删除并替换为导入的数据库。";
/* No comment provided by engineer. */
"Your current profile" = "您当前的资料";
+/* No comment provided by engineer. */
+"Your group" = "你的群";
+
/* No comment provided by engineer. */
"Your ICE servers" = "您的 ICE 服务器";
@@ -5642,12 +6355,18 @@ chat item action */
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
+/* alert message */
+"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "您的个人资料已修改。如果进行保存,更新后的个人资料将发送到所有联系人。";
+
/* No comment provided by engineer. */
"Your random profile" = "您的随机资料";
/* No comment provided by engineer. */
"Your server address" = "您的服务器地址";
+/* No comment provided by engineer. */
+"Your servers" = "你的服务器";
+
/* No comment provided by engineer. */
"Your settings" = "您的设置";
diff --git a/apps/multiplatform/CODE.md b/apps/multiplatform/CODE.md
new file mode 100644
index 0000000000..26a36e75bb
--- /dev/null
+++ b/apps/multiplatform/CODE.md
@@ -0,0 +1,309 @@
+# Coding and building
+
+You are an expert developer for SimpleX Chat, a privacy-first decentralized messaging platform. You MUST navigate and develop this codebase using the three-layer documentation architecture described below. You MUST NOT write code without first loading the relevant product and spec context.
+
+## Three-Layer Documentation Architecture
+
+### Why this structure exists
+
+LLMs start each session with no persistent understanding of the codebase. Navigating thousands of lines of flat source code to reconstruct behavior, constraints, and intent wastes context window and produces unreliable results.
+
+The `product/`, `spec/`, and source layers form a persistent, structured representation of the system that survives across sessions. Each layer is connected to the next by bidirectional cross-references. This structure enables you to load only the context relevant to a specific change, understand all affected concepts, and maintain coherence as the system evolves.
+
+### The layers
+
+| Layer | Contains | Question it answers |
+|-------|----------|-------------------|
+| `product/` | Capabilities, user flows, views, business rules, glossary | **What** does the system do and why? |
+| `spec/` | Technical design, API contracts, database schema, service internals | **How** is it organized technically? |
+| `common/src/commonMain/` | Shared Kotlin/Compose code (Android + Desktop) | What does it **execute** on both platforms? |
+| `common/src/androidMain/` | Android-specific Kotlin (platform implementations) | What does it execute on **Android**? |
+| `common/src/desktopMain/` | Desktop-specific Kotlin (platform implementations) | What does it execute on **Desktop**? |
+| `android/src/main/` | Android app module (Application, Activity, Services) | What is the **Android entry point**? |
+| `desktop/src/jvmMain/` | Desktop app module (main function) | What is the **Desktop entry point**? |
+| `../../src/Simplex/Chat/` | Haskell core (chat logic, protocol, database) | What does the **core** execute? |
+
+Each layer links to the next:
+- `product/concepts.md` links every concept to its spec docs, source files, and tests in a single table — this is the primary navigation entry point
+- `product/views/*.md` and `product/flows/*.md` each have a **Related spec:** line linking to their most relevant spec documents
+- `product/glossary.md` uses *See: [spec/...]* references and `product/rules.md` uses **Spec:** [spec/...] references to link individual terms and rules down to spec
+- `spec/` documents contain **Source:** headers and inline function links pointing down to source. Line references MUST be clickable by embedding the `#Lxx-Lyy` fragment in the link URL: [`functionName()`](common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt#Lxx-Lyy). You MUST NOT duplicate line numbers in the display text — the URL fragment is sufficient. Why: redundant line numbers in display text create maintenance burden on every line shift.
+- Reverse direction: the Document Map (end of this file) maps source → spec → product
+
+### Navigation workflow
+
+When the user requests any change, you MUST follow these steps before writing any code:
+
+1. **Identify scope.** You MUST read `product/concepts.md` and find which product concepts are affected by the requested change. Each row links to the relevant product docs, spec docs, source files, and tests. Why: concepts.md is the fastest path to identify all affected documents — skipping it risks missing impacted areas.
+
+2. **Load product context.** You MUST read the relevant `product/views/*.md` or `product/flows/*.md` to understand current user-facing behavior. For business constraints, you MUST read `product/rules.md`. Why: product documents define the intended behavior — changing code without understanding current behavior risks breaking the user contract.
+
+3. **Load spec context.** You MUST follow the product → spec links to read the relevant `spec/*.md` or `spec/services/*.md`. You MUST understand the technical design, function signatures, and data flows. Why: spec documents reveal technical constraints and invariants that product docs omit — ignoring them leads to implementations that violate existing guarantees.
+
+4. **Load source context.** You MUST follow the spec → source links (with line numbers) to read the relevant source files. Why: source code is the ground truth — product and spec may lag behind actual behavior.
+
+5. **Identify full impact.** You MUST read `spec/impact.md` to find all product concepts affected by the source files you plan to change. This determines which documents you MUST update after the code change. Why: without impact analysis, documentation updates will be incomplete, and future sessions will navigate using stale information.
+
+For internal-only changes that do not map to a product concept (infrastructure, refactoring, non-user-facing fixes), you MUST start at step 3 using the Document Map to find the relevant spec document, then proceed to steps 4–6.
+
+6. **Implement.** Make the code change in source, then you MUST update all affected documentation as described in the Change Protocol below.
+
+### Key navigation documents
+
+| Document | Purpose | When to read |
+|----------|---------|-------------|
+| `product/concepts.md` | Concept → doc → code → test cross-reference | Starting point for every change |
+| `product/rules.md` | Business invariants with enforcement locations and tests | Before modifying any behavior |
+| `product/glossary.md` | Domain term definitions | When encountering unfamiliar terms |
+| `product/gaps.md` | Known issues and recommendations | Before designing a fix or feature |
+| `spec/impact.md` | Source file → affected product concepts | After identifying which files to change |
+| Document Map (below) | Source ↔ spec ↔ product mapping | When updating documentation |
+
+---
+
+## Code Security
+
+When designing code and planning implementations, you MUST:
+- Apply adversarial thinking, and consider what may happen if one of the communicating parties is malicious. Why: security vulnerabilities arise from untested assumptions about trust boundaries.
+- Formulate an explicit threat model for each change — who can do which undesirable things and under which circumstances. Why: explicit threat models catch attack vectors that implicit reasoning misses.
+
+---
+
+## Code Style
+
+**Follow existing code patterns — you MUST:**
+- Match the style of surrounding code. Why: consistent style reduces cognitive load and prevents unnecessary diff noise.
+- Use Kotlin data classes for value types, regular classes for reference types, and sealed classes/interfaces for variants. Why: correct type choices leverage the type system for compile-time correctness.
+- Prefer exhaustive `when` expressions over `else` branches. Why: `else` branches bypass compiler checks for new sealed subclasses and hide bugs.
+
+**Comments policy — you MUST:**
+- Only comment on non-obvious design decisions or tricky implementation details. Why: redundant comments create maintenance burden and drift from code.
+- Keep function names and type signatures self-documenting. Why: good names eliminate the need for most comments.
+- Assume a competent Kotlin reader. Why: over-explaining trivial Kotlin adds noise without value.
+
+**Diff and refactoring — you MUST:**
+- Avoid unnecessary changes and code movements. Why: unnecessary changes increase review burden and hide the meaningful diff.
+- Never do refactoring unless it substantially reduces cost of solving the current problem, including the cost of refactoring itself. Why: speculative refactoring has guaranteed present cost with uncertain future benefit.
+- Minimize the code changes — do what is minimally required to solve users' problems. Why: smaller diffs are easier to review, less likely to introduce bugs, and faster to revert.
+
+**Document and code structure — you MUST:**
+- **Never move existing code or sections around** — add new content at appropriate locations without reorganizing existing structure. Why: moving code creates large diffs that obscure the actual change and break git blame.
+- When adding new sections to documents, continue the existing numbering scheme. Why: consistent numbering preserves document navigability.
+- Minimize diff size — prefer small, targeted changes over reorganization. Why: large diffs compound review errors and make rollback difficult.
+
+**Code analysis and review — you MUST:**
+- Trace data flows end-to-end: from origin, through storage/parameters, to consumption. Flag values that are discarded and reconstructed from partial data (e.g. extracted from a URI missing original fields) — this is usually a bug. Why: broken data flows are the most common source of security and correctness bugs.
+- Read implementations of called functions, not just signatures — if duplication involves a called function, check whether decomposing it resolves the duplication. Why: function signatures can be misleading about actual behavior.
+- Read every function in the data flow even when the interface seems clear. Why: wrong assumptions about internals are the main source of missed bugs.
+
+---
+
+## Plans
+
+When developing via plans (non-trivial features, multi-step changes, architectural decisions), you MUST store the plan in the `plans/` folder before implementing. Why: plans are the persistent record of design decisions and rationale — without them, future sessions cannot understand why the system was built the way it was.
+
+### Plan requirements
+
+1. **File naming.** You MUST use the format `YYYYMMDD_NN.md` (e.g., `20260211_01.md`). Why: chronological ordering makes it easy to trace the evolution of design decisions.
+
+2. **Plan structure.** Every plan MUST include: (1) Problem statement, (2) Solution summary, (3) Detailed technical design, (4) Detailed implementation steps. Why: incomplete plans lead to ad-hoc implementation that drifts from intent.
+
+3. **Consistency with product/ and spec/.** The plan MUST be consistent with the current state of `product/` and `spec/`. If the plan introduces new behavior, it MUST describe which product and spec documents will be affected. Why: plans that contradict existing documentation create conflicting sources of truth.
+
+4. **Adversarial self-review.** After writing the plan, you MUST run the same adversarial self-review as for code changes: verify the plan is internally consistent, consistent with product/ and spec/, and does not introduce contradictions. You MUST repeat until two consecutive passes find zero issues. Why: an incoherent plan produces incoherent implementation.
+
+---
+
+## Change Protocol
+
+### The rule
+
+Every code change MUST include corresponding updates to `spec/` and `product/`. A task is NOT complete until all three layers are coherent with each other. Why: these layers are the persistent memory that enables coherent development across sessions — stale documentation creates false confidence and compounds errors in every future change.
+
+### What to update
+
+1. **spec/ — on every code change.** You MUST update the corresponding spec document to reflect the change. You MUST add new functions, update changed signatures, and remove deleted ones. Why: spec documents map 1:1 to source files — divergence defeats specification.
+
+2. **product/ — when user-visible behavior changes.** You MUST update the relevant `product/views/*.md` and any affected `product/flows/*.md`. You MUST update `product/rules.md` when business invariants change. Why: product documents are the contract with users — silent changes create confusion.
+
+3. **Line number references — on every code change.** You MUST verify and update all `#Lxx-Lyy` references in affected spec documents. Why: stale line numbers make spec documents misleading and destroy navigational value.
+
+4. **Cross-references — when adding or removing files.** You MUST add corresponding spec documents and update `spec/README.md` document index and reverse index. When adding pages, you MUST add `product/views/` and `spec/client/` documents. You MUST update the Document Map at the end of this file. Why: every source file must be covered for the navigation system to work.
+
+5. **Impact graph — when adding files or changing what a file affects.** You MUST update `spec/impact.md` to reflect the source file → product concept mapping. Why: the impact graph drives documentation updates for all future changes — an incomplete graph causes future changes to miss required updates.
+
+6. **Concept index — when adding or changing product concepts.** You MUST add or update the relevant row in `product/concepts.md` with links to product docs, spec docs, source files, and tests. Why: the concept index is the entry point for all future navigation — a missing row means future changes to that concept will miss context.
+
+7. **[GAP] annotations — when discovering issues.** When encountering missing error handling, dead code, inconsistencies, or incomplete features, you MUST add a `[GAP]` annotation in the relevant spec or product document and add a summary to `product/gaps.md`. Why: this builds institutional knowledge about technical debt.
+
+8. **[REC] annotations — when identifying improvements.** You MUST add a `[REC]` annotation in the relevant document. Why: capturing improvement ideas at discovery time preserves context that is lost later.
+
+9. **Preserve document structure.** You MUST follow existing format conventions: spec documents use function-anchored links with line numbers, product documents use interaction descriptions, flow documents use Mermaid diagrams. Why: consistent structure makes documents predictable and navigable.
+
+### Adversarial self-review
+
+After completing all changes (code + documentation), you MUST run an adversarial self-review. You MUST check coherence both within each layer and across layers.
+
+**Within-layer coherence — you MUST verify:**
+- spec/ is internally consistent — no contradictory descriptions, state machines have no unreachable states, data model is referentially intact
+- product/ is internally consistent — flows match views, rules match behavior descriptions
+
+**Across-layer coherence — you MUST verify:**
+- Every new or changed function in source appears in the corresponding spec/ document
+- Every user-visible behavior change in source appears in the relevant product/ document
+- All `#Lxx-Lyy` line references in affected spec documents point to the correct lines
+- All cross-references resolve — product → spec links, spec → source links
+- `spec/impact.md` covers all affected product concepts for the changed source files
+- `product/concepts.md` rows are current for any affected concepts
+
+**Convergence:** You MUST repeat the review-and-fix cycle until two consecutive passes find zero issues. You MUST fix all issues discovered between passes. Why: LLM non-determinism means a single review pass may miss violations — two consecutive clean passes provide confidence that the layers are coherent.
+
+---
+
+## Multiplatform Architecture Notes
+
+### Kotlin Multiplatform (KMP) + Compose Multiplatform
+
+The app uses Kotlin Multiplatform with Compose Multiplatform for shared UI. The project has three Gradle modules:
+
+- **common/** — Shared library containing all models, views, platform abstractions, and theme system
+- **android/** — Android app module (Application, Activity, Services)
+- **desktop/** — Desktop JVM app module (main entry point)
+
+### expect/actual Pattern
+
+Platform-specific code uses Kotlin's `expect`/`actual` mechanism. The `commonMain` source set declares `expect` functions/classes, and `androidMain`/`desktopMain` provide `actual` implementations. Files follow the naming convention:
+- `commonMain`: `FileName.kt` (contains `expect` declarations)
+- `androidMain`: `FileName.android.kt` (contains `actual` implementations)
+- `desktopMain`: `FileName.desktop.kt` (contains `actual` implementations)
+
+When modifying platform abstractions, you MUST update both `actual` implementations.
+
+### Source Set Structure
+
+```
+common/src/
+├── commonMain/kotlin/chat/simplex/common/ -- Shared code (195 files)
+│ ├── model/ -- ChatModel, SimpleXAPI, CryptoFile
+│ ├── platform/ -- expect/actual platform abstractions
+│ ├── ui/theme/ -- Theme system (ThemeManager, colors, types)
+│ └── views/ -- Compose UI (chat, chatlist, call, settings, etc.)
+├── androidMain/kotlin/chat/simplex/common/ -- Android actuals (55 files)
+│ ├── platform/ -- actual implementations
+│ └── views/ -- Android-specific view variants
+├── desktopMain/kotlin/chat/simplex/common/ -- Desktop actuals (56 files)
+│ ├── platform/ -- actual implementations
+│ └── views/ -- Desktop-specific view variants
+android/src/main/java/chat/simplex/app/ -- Android app (8 files)
+desktop/src/jvmMain/kotlin/chat/simplex/desktop/ -- Desktop app (1 file)
+```
+
+### Platform Differences
+
+| Aspect | Android | Desktop |
+|--------|---------|---------|
+| Layout | 2-column (chat list → chat) | 3-column (sidebar → chat list → details) |
+| Background messaging | SimplexService (foreground service) + MessagesFetcherWorker (WorkManager) | Continuous (always-on process) |
+| Notifications | Android NotificationManager with channels | Desktop system notifications |
+| Calls | CallActivity (separate Activity) + CallService | In-window call view |
+| Video playback | ExoPlayer | VLC (VLCJ) |
+| Authentication | Android BiometricPrompt | Passcode only |
+| Auto-update | Play Store / manual APK | Built-in AppUpdater |
+| Window management | Standard Activity lifecycle | StoreWindowState persistence |
+| Entry point | SimplexApp (Application) + MainActivity | Main.kt → initHaskell() → showApp() |
+
+---
+
+## Document Map
+
+### Shared Sources (commonMain)
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| common/.../common/App.kt | spec/architecture.md | product/views/chat-list.md |
+| common/.../common/AppLock.kt | spec/architecture.md | product/views/settings.md |
+| common/.../common/model/ChatModel.kt | spec/state.md | product/concepts.md |
+| common/.../common/model/SimpleXAPI.kt | spec/api.md, spec/architecture.md | product/concepts.md |
+| common/.../common/model/CryptoFile.kt | spec/services/files.md | product/flows/file-transfer.md |
+| common/.../common/platform/Core.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/platform/AppCommon.kt | spec/architecture.md | product/flows/onboarding.md |
+| common/.../common/platform/Notifications.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/NtfManager.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/Files.kt | spec/services/files.md | product/flows/file-transfer.md |
+| common/.../common/platform/SimplexService.kt | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/platform/Share.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/platform/VideoPlayer.kt | spec/services/files.md | product/views/chat.md |
+| common/.../common/platform/RecAndPlay.kt | spec/services/files.md | product/views/chat.md |
+| common/.../common/platform/UI.kt | spec/architecture.md | product/views/chat.md |
+| common/.../common/platform/Platform.kt | spec/architecture.md | product/concepts.md |
+| common/.../common/ui/theme/ThemeManager.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/ui/theme/Theme.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/ui/theme/Color.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/views/chatlist/ChatListView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/ChatListNavLinkView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/ChatPreviewView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/UserPicker.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chatlist/TagListView.kt | spec/client/chat-list.md | product/views/chat-list.md |
+| common/.../common/views/chat/ChatView.kt | spec/client/chat-view.md | product/views/chat.md |
+| common/.../common/views/chat/ComposeView.kt | spec/client/compose.md | product/views/chat.md |
+| common/.../common/views/chat/SendMsgView.kt | spec/client/compose.md | product/views/chat.md |
+| common/.../common/views/chat/ChatInfoView.kt | spec/client/chat-view.md | product/views/contact-info.md |
+| common/.../common/views/chat/group/ | spec/client/chat-view.md | product/views/group-info.md |
+| common/.../common/views/chat/item/ | spec/client/chat-view.md | product/views/chat.md |
+| common/.../common/views/call/CallView.kt | spec/services/calls.md | product/views/call.md |
+| common/.../common/views/call/IncomingCallAlertView.kt | spec/services/calls.md | product/views/call.md |
+| common/.../common/views/call/WebRTC.kt | spec/services/calls.md | product/flows/calling.md |
+| common/.../common/views/newchat/NewChatView.kt | spec/client/navigation.md | product/views/new-chat.md |
+| common/.../common/views/newchat/AddGroupView.kt | spec/client/navigation.md | product/views/new-chat.md |
+| common/.../common/views/usersettings/SettingsView.kt | spec/client/navigation.md | product/views/settings.md |
+| common/.../common/views/usersettings/Appearance.kt | spec/services/theme.md | product/views/settings.md |
+| common/.../common/views/usersettings/PrivacySettings.kt | spec/client/navigation.md | product/views/settings.md |
+| common/.../common/views/usersettings/networkAndServers/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/usersettings/UserProfilesView.kt | spec/client/navigation.md | product/views/user-profiles.md |
+| common/.../common/views/onboarding/ | spec/client/navigation.md | product/views/onboarding.md |
+| common/.../common/views/localauth/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/database/ | spec/database.md | product/views/settings.md |
+| common/.../common/views/migration/ | spec/database.md | product/flows/onboarding.md |
+| common/.../common/views/remote/ | spec/architecture.md | product/views/settings.md |
+| common/.../common/views/contacts/ | spec/client/chat-view.md | product/views/contact-info.md |
+| common/.../common/views/helpers/ | spec/architecture.md | product/concepts.md |
+
+### Android-Specific Sources
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| android/.../app/SimplexApp.kt | spec/architecture.md | product/flows/onboarding.md |
+| android/.../app/MainActivity.kt | spec/architecture.md | product/views/chat-list.md |
+| android/.../app/SimplexService.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/CallService.kt | spec/services/calls.md | product/flows/calling.md |
+| android/.../app/MessagesFetcherWorker.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/model/NtfManager.android.kt | spec/services/notifications.md | product/flows/messaging.md |
+| android/.../app/views/call/CallActivity.kt | spec/services/calls.md | product/views/call.md |
+
+### Desktop-Specific Sources
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| desktop/.../desktop/Main.kt | spec/architecture.md | product/flows/onboarding.md |
+| common/.../common/DesktopApp.kt (desktopMain) | spec/architecture.md | product/views/chat-list.md |
+| common/.../common/StoreWindowState.kt (desktopMain) | spec/architecture.md | product/views/settings.md |
+| common/.../common/model/NtfManager.desktop.kt (desktopMain) | spec/services/notifications.md | product/flows/messaging.md |
+| common/.../common/views/helpers/AppUpdater.kt (desktopMain) | spec/architecture.md | product/views/settings.md |
+
+### Haskell Core Sources (at `../../src/Simplex/Chat/` relative to `apps/multiplatform/`)
+
+| Source Location | Spec Document | Product Document |
+|----------------|---------------|-----------------|
+| ../../src/Simplex/Chat/Controller.hs | spec/api.md | product/concepts.md |
+| ../../src/Simplex/Chat/Types.hs | spec/api.md | product/glossary.md |
+| ../../src/Simplex/Chat/Core.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Protocol.hs | spec/architecture.md | product/concepts.md |
+| ../../src/Simplex/Chat/Messages.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Messages/CIContent.hs | spec/api.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Call.hs | spec/services/calls.md | product/flows/calling.md |
+| ../../src/Simplex/Chat/Files.hs | spec/services/files.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Messages.hs | spec/database.md | product/flows/messaging.md |
+| ../../src/Simplex/Chat/Store/Groups.hs | spec/database.md | product/flows/group-lifecycle.md |
+| ../../src/Simplex/Chat/Store/Direct.hs | spec/database.md | product/flows/connection.md |
+| ../../src/Simplex/Chat/Store/Files.hs | spec/database.md | product/flows/file-transfer.md |
+| ../../src/Simplex/Chat/Store/Profiles.hs | spec/database.md | product/views/user-profiles.md |
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt
index 4f47fda130..1a3703822d 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt
@@ -21,12 +21,19 @@ import java.net.URI
import kotlin.math.min
import kotlin.math.sqrt
+private const val MAX_IMAGE_DIMENSION = 4320
+
actual fun base64ToBitmap(base64ImageString: String): ImageBitmap {
val imageString = base64ImageString
.removePrefix("data:image/png;base64,")
.removePrefix("data:image/jpg;base64,")
return try {
val imageBytes = Base64.decode(imageString, Base64.NO_WRAP)
+ val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
+ BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size, options)
+ if (options.outWidth <= 0 || options.outHeight <= 0 || options.outWidth > MAX_IMAGE_DIMENSION || options.outHeight > MAX_IMAGE_DIMENSION || options.outHeight > options.outWidth * 256) {
+ return errorBitmap.asImageBitmap()
+ }
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size).asImageBitmap()
} catch (e: Exception) {
Log.e(TAG, "base64ToBitmap error: $e")
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
index 22e53af849..56279a5143 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt
@@ -108,6 +108,7 @@ class ActiveCallState: Closeable {
}
+// Spec: spec/services/calls.md#ActiveCallView
@SuppressLint("SourceLockedOrientationActivity")
@Composable
actual fun ActiveCallView() {
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
index 9d1e0c4e97..a5021ae54c 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt
@@ -182,6 +182,8 @@ private fun spannableStringToAnnotatedString(
actual fun getAppFileUri(fileName: String): URI =
FileProvider.getUriForFile(androidAppContext, "$APPLICATION_ID.provider", if (File(fileName).isAbsolute) File(fileName) else File(getAppFilePath(fileName))).toURI()
+actual fun clearImageCaches() {}
+
// https://developer.android.com/training/data-storage/shared/documents-files#bitmap
actual suspend fun getLoadedImage(file: CIFile?): Pair? {
val filePath = getLoadedFilePath(file)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
index 70e0067260..d9439a5474 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt
@@ -42,6 +42,7 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
+// Spec: spec/client/navigation.md#AppScreen
@Composable
fun AppScreen() {
AppBarHandler.appBarMaxHeightPx = with(LocalDensity.current) { AppBarHeight.roundToPx() }
@@ -78,6 +79,7 @@ fun AppScreen() {
}
}
+// Spec: spec/client/navigation.md#MainScreen
@Composable
fun MainScreen() {
val chatModel = ChatModel
@@ -289,6 +291,7 @@ fun AndroidWrapInCallLayout(content: @Composable () -> Unit) {
}
}
+// Spec: spec/client/navigation.md#AndroidScreen
@Composable
fun AndroidScreen(userPickerState: MutableStateFlow) {
BoxWithConstraints {
@@ -402,6 +405,7 @@ fun EndPartOfScreen() {
ModalManager.end.showInView()
}
+// Spec: spec/client/navigation.md#DesktopScreen
@Composable
fun DesktopScreen(userPickerState: MutableStateFlow) {
Box(Modifier.width(DEFAULT_START_MODAL_WIDTH * fontSizeSqrtMultiplier)) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
index d6f9640cb9..32a5ce1ef1 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt
@@ -13,6 +13,7 @@ import chat.simplex.common.views.usersettings.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
+// Spec: spec/client/navigation.md#AppLock
object AppLock {
/**
* We don't want these values to be bound to Activity lifecycle since activities are changed often, for example, when a user
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index 8db2cc1a76..4e406044e5 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -78,9 +78,33 @@ object ConnectProgressManager {
val connectProgressManager = ConnectProgressManager
+object ChannelRelaysModel {
+ val groupId = mutableStateOf(null)
+ val groupRelays = mutableStateListOf()
+
+ fun set(groupId: Long, groupRelays: List) {
+ this.groupId.value = groupId
+ this.groupRelays.clear()
+ this.groupRelays.addAll(groupRelays)
+ }
+
+ fun updateRelay(groupInfo: GroupInfo, relay: GroupRelay) {
+ if (groupId.value == groupInfo.groupId) {
+ val i = groupRelays.indexOfFirst { it.groupRelayId == relay.groupRelayId }
+ if (i >= 0) groupRelays[i] = relay
+ }
+ }
+
+ fun reset() {
+ groupId.value = null
+ groupRelays.clear()
+ }
+}
+
/*
* Without this annotation an animation from ChatList to ChatView has 1 frame per the whole animation. Don't delete it
* */
+// Spec: spec/state.md#ChatModel
@Stable
object ChatModel {
val controller: ChatController = ChatController
@@ -109,9 +133,13 @@ object ChatModel {
val chats: State> = chatsContext.chats
// rhId, chatId
val deletedChats = mutableStateOf>>(emptyList())
+ val creatingChannelId = mutableStateOf(null)
val groupMembers = mutableStateOf>(emptyList())
val groupMembersIndexes = mutableStateOf