- Delete a group chat from GroupInfoView or via a swipe in the chat
list: remove the conversation and its messages from SwiftData,
clean per-chat state, and leave the room on the server first when
joined.
- Keep "Покинуть комнату" as leave-without-deleting and suppress
deleted rooms for the session so roomState events cannot recreate
them; an explicit rejoin or a fresh invitation clears the
suppression.
- Add verify.sh invariants and document the behaviour in
ARCHITECTURE.md.
- Normalize SCRAM passwords with RFC 4013 SASLprep before the
challenge response, so they match SASLprep-compliant servers
(Martin hashes raw UTF-8) and PLAIN still sends the password
untouched.
- Capture the raw <failure/> condition with LumaSaslFailureModule and
map SASL errors to actionable Russian messages instead of the
cryptic OS-localized "Martin.SaslError, error 5".
- Cover SASLprep and failure message mapping with unit tests and new
verify.sh invariants.
- Drive chat list, timeline and forward picker from SwiftData @Query
instead of AppModel's in-memory arrays; inject the per-account
ModelContext from RootView with an in-memory fallback.
- Physically delete locally deleted messages (context.delete) and
purge rows marked deleted by older builds on store open, so @Query
views never resurrect them.
- Delete the legacy JSON snapshot only after a successful import save
and restore the completeUntilFirstUserAuthentication data protection
attribute on the store file.
- Mirror the old ChatArchive tolerant decoding in LegacyArchiveImporter
so snapshots from older schema versions (missing reactions,
isGroupMessage, roster fields) still import.
- Cover the new behaviour in ArchiveStoreTests and guard it with new
verify.sh invariants; update AGENTS.md, ARCHITECTURE.md and
SECURITY.md.
Models:
- Convert Conversation and ChatMessage to SwiftData @Model classes with
a
one-to-many relationship and cascade delete.
- Rename ChatMessage.id to clientID and drop Conversation.id in favor of
jid
(the string id would clash with PersistentIdentifier).
- Keep MessageReaction as a Codable value stored in an array attribute.
Persistence:
- Add ArchiveStore, a per-account ModelContainer/ModelContext that
replaces
the JSON ChatArchive with load/save/erase.
- Add ArchiveMetadataRecord for the durable MAM checkpoints, cursor,
locally
deleted message IDs and roster contact JIDs.
- Add LegacyArchiveImporter to import the old JSON snapshot once and
delete
the file afterwards, preserving existing history.
AppModel:
- Replace ChatArchive with ArchiveStore and persist via context.save();
insert new models and delete the replaced object on upsert merges so
SwiftData never keeps an orphaned duplicate.
Tests:
- Replace ChatArchiveTests with ArchiveStoreTests (store round-trip and
legacy import) and drop the now-obsolete Codable round-trip
assertions.
Server information (Monal-style):
- Add ServerInformation model and ServerInfoView reachable from
Settings,
showing server software (XEP-0092), disco#info identities and
features,
conference (MUC) services via disco#items, and STUN/TURN via XEP-0215.
- Add a curated XEP capability list with per-XEP success/error status
derived
from server/account disco features and connection flags.
- Detect PEP (0163) from account disco pubsub#* features, HTTP Upload
(0363)
from the main domain plus its components, and add Roster Versioning
(0237),
Pre-Authenticated Roster Subscription (0379), and SASL SCRAM Downgrade
Protection (0474, neutral because Martin does not expose SSDP).
Connection statistics:
- Add LumaConnectionStatsModule, an XmppStanzaFilter registered before
stream
management, to count sent / acknowledged / received stanzas.
- Track last login and SMACKS session timestamps, and read SASL
mechanisms
and Client State Indication from the raw stream features.
MAM / OMEMO handling:
- Prefer a plaintext <body> fallback when OMEMO decryption fails instead
of
showing "failed to decrypt".
- Keep the optimistic text of our own outgoing message when its echo
cannot
be decrypted back, instead of replacing it with a placeholder.
Emoji picker:
- Add EmojiCatalog and a cross-platform EmojiPickerView.
- Open the picker for reactions (replacing the static quick list) and
from
the composer's smiley button, inserting the chosen emoji into the
draft.