Merging keepActiveUser into apiCreateActiveUser changed a signature that two Kotlin
docs quote. spec/api.md still described the old parameters and said the command sets
the profile as active, full stop; product/flows/onboarding.md quoted the old signature
one step above the line this branch had already updated to mention the flag. The iOS
spec table was fixed in the previous commit and these were missed - the same drift on
the other platform.
Both iOS call sites now use listUsersAsync, which is what changeActiveUserAsync_ uses
a few lines later; the synchronous listUsers blocks whatever executor the Task is on,
and SwiftUI infers that as the main actor.
Not compiled: the Kotlin changes here are markdown only, and the Swift ones cannot be
built on this machine.
Correctness.
The one-time link picker's profile list was keyed on the active user as well as the
count. That defeats the "don't change order after a user was selected" behaviour its own
comment protects - changeActiveUser_ sets currentUser first and only then reloads chats,
so the list visibly re-sorted and stayed re-sorted for the whole of getUserChatData - and
it was redundant: the count alone covers a profile being created from the picker.
iOS, compose picker: the old-core fallback relied on the user switch tearing this view
down, which only happens when the switch succeeded. If it threw, the form was left open
over a profile that already existed, and the only way out was to swipe it away. It is now
dismissed unconditionally.
iOS: alerts raised right after a sheet is dismissed are presented on a controller that is
going away and are dropped. The 0.5s wait was applied only to the old-core path, while the
ordinary reassignment failures - the ones that actually happen - went unguarded.
alertAfterDismissal now covers all of them, in one place.
iOS: dropped the chatId write that Kotlin removed for the same reason two commits ago. It
is a no-op on the happy path, but not if something legitimately closed the chat meanwhile
- SimpleX lock re-auth clears it, and this would reopen the chat behind the lock screen.
iOS: the create-profile row is now gated on profileChangeProhibited, as every other row on
both platforms already was.
Surface.
apiCreateActiveUser takes keepActiveUser as a defaulted argument, exactly as it already
takes pastTimestamp, instead of a public wrapper plus a private helper. Two symbols fewer
on each platform, and every existing call site passes its arguments by name, so none of
them changes.
Reverted the activeOrder sort flip in the compose picker: it changes the order of an
existing screen for every user, is not needed for this feature, and with active_order 0
would push a newly created profile to the bottom. It deserves its own commit if wanted.
Also: apps/ios/spec/api.md tracks the command enum and had gone stale; the plan records
the two limits of the flag the review surfaced - it is ignored when there is no active
user, and active_order 0 ties rather than sorts last on migrated databases.
No behaviour change. The review rounds left the flow carrying their own reasoning:
267 of the 759 added lines were comments, and the fixes had accreted structure that
was no longer needed.
createProfileForInvitation runs on Main via withApi, as the pickers' own handlers
always did, instead of withBGApi with three withContext(Dispatchers.Main) blocks
inside it. Every call it makes suspends into IO - sendCmd hops there itself - so Main
is never blocked, and the whole class of off-main model mutation that two rounds of
review found simply cannot arise. That removes the two coroutine imports as well.
hasModalOpenNotClosing is gone: while the form is open it is necessarily the last
modal on the fullscreen stack, so the entry guard uses isLastModalOpenNotClosing and
one function covers both call sites.
The rest is comments. Kept the ones that record why something non-obvious is the way
it is - the placement, the main-thread choice, the guards - and dropped the narration
of what the earlier attempts got wrong, which belongs in the history and the plan.
One comment described code that is not there at all; it is the commit message's job
to say why the chatId write was removed.
759 -> 667 added lines, 267 -> 189 of them comments.
Third review pass. Still not compiled - no Swift toolchain here.
The compose picker's fallback had moved the chatId clear out of the do block, so it
fired when the switch had FAILED - closing a prepared chat that was still perfectly
valid and still owned by the active profile. It is now conditional on the switch having
happened.
Dismissing the form before the switch did not help either: the dismissal animates for
about a third of a second, getTopViewController() keeps returning the sheet until it
ends, and the local database work finishes sooner - so the alert was presented on a
controller being dismissed and dropped, on the common path. The explicit dismissal is
gone (the switch removes this view and its sheet anyway) and the alert is deferred past
the transition.
The one-time link picker's rows had no guard at all: allowsHitTesting keyed on
switchingProfileByTimeout, which latches half a second late, so a second row was
tappable for that whole window right after the form closed - starting a second
connection change on a pccConnId the first was recreating. Both pickers now block input
from the moment the work starts.
In the compose picker the guard is on the branches that start work, not on the whole
Button, so expanding and collapsing the list - which is local - still works, and a tap
in a chat where the profile cannot be changed still explains itself.
Also: that picker's other-profiles list sorted activeOrder ascending while every other
list on both platforms sorts descending, which the core change made visible by giving a
never-activated profile order 0; the one-time link picker's fallback now dismisses and
resyncs its selection as the compose picker does; and the incognito handler's new else
no longer writes incognitoEnabled, which is bound to the app-wide default and marks the
invitation as used.
Follow-up to the previous iOS commit. Still not compiled - no Swift toolchain here.
The "core ignored keepActiveUser" path in the compose picker rested on a false
premise. It claimed the form stays open and the alert draws over it, but the resync it
performs replaces the chat list with the new profile's, and the prepared chat stayed
with the previous one - so getChat returns nothing, the pushed chat view renders
nothing, and this picker, which is the sheet's presenter, is removed by the switch
itself. The alert was then raised into a hierarchy being torn down and discarded. The
form is now dismissed first, and the chat id cleared only if it is still the chat this
picker belongs to, since a notification tap may have navigated elsewhere meanwhile.
Rows stay live during the reassignment. creatingProfile is cleared as soon as
createProfileForChat returns, which is the moment it hands off to changeProfile's
fire-and-forget Task, and none of the rows had a disabled state at all - so a second
change could be started on top of the first, two concurrent reassignments of the same
prepared contact and two racing user switches. This is what the Kotlin side made
onCreated suspending for.
The one-time link picker's new else branch reset the switch status but not the
selection, so after a cancelled retry the profile that was not switched to kept the
checkmark and tapping it did nothing - the view thought it was already selected. Its
"core ignored the flag" path likewise left the picker pointing at a profile that is no
longer active. The incognito handler has the identical latch the else branch was added
for, and hardening the row's disabled state on profileSwitchStatus made that stuck path
disable the row permanently, so it gets the same treatment - and its two state writes
move onto the main actor, where the rest of that block already was.
Both swallowed errors are now logged, and the comment claiming two .sheet modifiers on
one view conflict is dropped - it contradicted the sibling file and the deployment
target.
Mirrors the Android/desktop fixes where iOS has the same defects, and fixes the ones
that are specific to SwiftUI. Still not compiled - no Swift toolchain here, same
status as the rest of the iOS port in this branch.
Swallowed alert. The one-time link picker reported "the core ignored keepActiveUser"
by setting its own `alert` @State, consumed by .alert(item:) on the very view that is
presenting the create-profile sheet. UIKit refuses an alert on a controller that
already has a presentation, so it was never shown: the active profile silently
switched, the connection stayed where it was, and the user was left looking at an
unchanged form. Uses the global showAlert, which targets the top view controller, as
the compose picker already does.
Stale picker list. The compose picker fills its own `users` in onAppear and never
again, so the profile just created was missing from the list whenever the
reassignment failed - tapping "Add profile" again then hit "Duplicate display name!"
- and the frame height, which is computed from that count, was one row short. The
local from listUsers also shadowed the property, which is what hid this.
Resync failure reported as a creation failure. changeActiveUserAsync_ throws, and on
the "core ignored the flag" path that propagated into the form's catch, which says
"Error creating profile!" although the profile had been created. On the compose
picker that path also left chatId pointing at a prepared chat that had stayed with
the previous profile, i.e. at a chat absent from the list, which renders blank.
Sheet owner destroyed mid-dismissal. The compose picker's sheet was attached inside
profilePicker(), which is swapped out for currentSelection() the moment listExpanded
flips - which changeProfile does while the sheet is still dismissing, and which an
incoming event can do at any time via profileChangeProhibited. Moved to the Group in
viewBody(), which survives both; stacking sheets is supported from iOS 14.5 and the
app targets 15.
Dismissal during creation. The submit runs in an unstructured Task that SwiftUI does
not cancel, so a swipe-to-dismiss still created the profile and switched to it while
every state write, including the one clearing the in-flight flag, landed on a view
that was gone. Both sheets now block interactive dismissal while creating.
Latched picker. If apiChangeConnectionUser returns nothing rather than throwing,
profileSwitchStatus was never reset, leaving the picker dimmed behind a spinner with
hit testing off. Pre-existing, but the new row routes through the same handler.
Re-tappable row. switchingProfileByTimeout only latches half a second after the
switch starts, and creatingProfile is cleared as soon as the switch is handed over,
so the row was live in between.
Adds "Add profile" to the picker reached from "Share profile" on a one-time
link. The new profile is routed through the existing selectedProfile path, so
the connection change and the switch cannot drift from picking an existing one.
The row sits outside the active-profile branch: inside it, it would disappear
whenever the active profile is filtered out by the search text.
* deps: bump simplexmq for ConnectTarget
* chat: migration adds simplex_name to contacts, groups, connections
Nullable TEXT column on all three tables, with partial indexes on
contacts(user_id, simplex_name) and groups(user_id, simplex_name)
for the upcoming connectPlanName lookup. connections.simplex_name
is the transient carrier from APIConnect -> XInfo handler, where
the value is copied to contacts.simplex_name at delayed create.
No reads or writes yet - column threading lands in subsequent commits.
* tests: provide namesConfig = Nothing in smpServerCfg
Follow-up to the simplexmq pin bump (ee0a45e9). The new
namesConfig :: Maybe NamesConfig field on ServerConfig (introduced
in simplexmq's namespace branch) needs to appear in the test
fixture's record literal, otherwise the test suite fails to compile
under -Werror. Disabled by default (Nothing).
* chat: thread simplexName through Contact/GroupInfo/Connection records
Adds `simplexName :: Maybe SimplexNameInfo` to the three records and
extends every SELECT path that reconstructs them to read the new
column. Decoded via eitherToMaybe . strDecode . encodeUtf8 (the
codebase's established pattern for Maybe Text -> typed-decode fields),
extracted as decodeSimplexName helper since the chain appears in
toContact / toContact' / toGroupInfo / toConnection. INSERT paths
still write Nothing - the write-side wiring lands in the next commit
(Task 7).
* deps: bump simplexmq for SimplexNameInfo FromField/ToField
* chat: cross-reference groupInfoQueryFields and getGroupAndMember_
Two helpers redundantly maintain the same g.* column list. A future
g.* addition must be applied to both sites; the cross-reference
comments flag this for maintainers. A proper refactor (reusing
groupInfoQueryFields from Connections.hs's inline SELECT) is out of
scope for this branch.
* chat: persist simplexName on prepare and connect-via-plan write paths
createPreparedContact/createPreparedGroup gain a Maybe SimplexNameInfo
parameter that they write to contacts.simplex_name / groups.simplex_name
directly. createConnection_ writes to connections.simplex_name as a
transient carrier for the connect-via-plan path. The XInfo handler
in Library/Subscriber.hs reads the connection's simplexName and passes
it to createDirectContact so the final contact row captures the name.
All current callers pass Nothing; the actual flow lights up when
APIConnectPlan accepts ConnectTarget and connectPlanName threads the
name through (later commits in this branch).
Uses the upstream ToField SimplexNameInfo (simplexmq 0b334b66) for
writes; reads continue to go via the soft-degradation helper.
* chat: APIConnectPlan accepts ConnectTarget; connectPlanName looks up by name
APIConnectPlan/Connect flip from Maybe AConnectionLink to Maybe ConnectTarget.
connectPlan dispatches CTLink -> connectPlanLink (the prior body, renamed)
and CTName -> connectPlanName (new) which looks the name up against
contacts.simplex_name and groups.simplex_name via the new
getContactBySimplexName / getGroupInfoBySimplexName store helpers.
The hit path returns the contact's / group's stored conn link from
preparedContact / preparedGroup; missing prepared state or unknown
names return CEInvalidConnReq. RSLV on-chain resolution is out of
scope for this branch -- known-name lookup is enough for conversation
display, search, and external-link share.
connLinkP_ parser is unchanged: APIConnect's preparedLink_ stays
ACreatedConnLink-shaped, and the Connect / APIConnectPlan parsers
already use inline strP for ConnectTarget without going through the
helper.
Directory.Service call sites updated to wrap their AConnectionLink in
CTLink when invoking APIConnectPlan.
* chat: surface simplexName in conversation view + JSON output
viewConnectionPlan now shows the simplex name beneath known
contacts/groups (ILPKnown, CAPKnown, CAPContactViaAddress, GLPKnown
active/prepared/deleted). The TH-derived Contact / GroupInfo JSON
instances automatically expose `simplexName` (omitted when Nothing
per defaultJSON's omitNothingFields), which unblocks client-side
display and search.
No JSON test added: there is no Contact-level JSON test module in
this codebase; coverage is already provided by defaultJSON's
omitNothingFields = True behaviour.
Server-side substring search has no existing pattern in this
codebase; client renderers index simplexName themselves once it
appears in the JSON shape.
External-link share (preferring simplex:/name… form over the raw
link when the contact has a simplexName) lands in the next commit.
* chat: share/copy output prefers simplexName when present
When a contact or group has a simplex_name stored, the share-link
render path emits the canonical simplex:/name... URI (via strEncode)
instead of the underlying connection link. Falls back to the existing
link rendering when simplexName is Nothing.
Final commit of the ConnectTarget plumbing chain: end-to-end users
can now (a) connect via @alice.simplex / #group.simplex with the
agent layer carrying the name, (b) see the simplex name on the
contact/group records and in viewConnectionPlan, (c) share the
contact using the namespace-canonical form rather than the raw URI.
* deps: bump simplexmq for boundedNonSpace + drop unused FromField
* chat: simplex_name partial indexes are UNIQUE
A simplex name is a stable, per-user identity (one name → one contact
or group). Without a unique constraint, a later writer that populates
the column twice for the same name would silently produce two matching
rows, and getContactBySimplexName/getGroupInfoBySimplexName would
return whichever the planner picks first.
Promote the partial indexes added in M20260603 to UNIQUE before any
caller wires the writes. Predicate (WHERE simplex_name IS NOT NULL)
already scopes the constraint to rows that opted in.
* chat: regen Postgres schema dump for UNIQUE simplex_name indexes
Follow-up to f71c579c. The SchemaDump test runs against SQLite only;
the parallel PostgresSchemaDump suite gates on -fclient_postgres and
a running localhost PG instance, which this environment doesn't have.
Updated the Postgres schema dump by hand to mirror the migration
change (two lines: CREATE INDEX → CREATE UNIQUE INDEX).
* chat: CESimplexNameNotFound for name lookup misses
connectPlanName now distinguishes "name not found" from "connection
link is invalid". CEInvalidConnReq's message ("Connection link is
invalid, possibly it was created in a previous version") was
misleading when a user typed @alice.simplex against a database that
simply has no contact by that name.
The two "missing prepared link" cases stay on CEInvalidConnReq —
the lookup found a row but the stored link is unusable, which is
closer to the existing semantics. The two truly-missing cases
(no contact found / no group found) move to CESimplexNameNotFound,
which also surfaces the name back to the client for a precise UX.
* chat: exclude soft-deleted contacts from idx_contacts_simplex_name
The lookup `getContactBySimplexName` (Store/Direct.hs:781) filters
`AND deleted = 0`, but the index predicate `WHERE simplex_name IS NOT
NULL` covered tombstoned rows too. Forward-compat trap: once writers
land a non-Nothing simplex_name, soft-deleting a contact would block
re-claiming its name (UNIQUE conflict) even though the lookup reports
the slot as free.
Tighten the partial-index predicate to also require deleted = 0 so the
constraint scope matches the live-lookup scope. Groups have no soft-
delete column, so their index stays as-is.
* chat: connectPlanName dispatches on nameType first
Previously the function always probed contacts.simplex_name first and
fell through to groups for NTPublicGroup misses. But the discriminator
(`@`/`#`) is embedded in the stored bytes via strEncode, so an
`#group.simplex` lookup can never match a contact row. Reorder to
case on nameType up front, saving one DB query and one withFastStore
transaction acquire on the group path.
* chat: CESimplexNameUnprepared for found-but-no-link cases + member-removed filter
connectPlanName previously threw CEInvalidConnReq when a name lookup
hit a contact / group row whose preparedContact / preparedGroup was
NULL. The error message ("Connection link is invalid, possibly it was
created in a previous version") was wrong: the name resolved fine,
the device just has no link material to reconnect via (typical for a
contact created via the XInfo handler rather than the prepare path).
Introduce CESimplexNameUnprepared SimplexNameInfo for this case.
Also mirror the link-based path's gPlan (Commands.hs:4133) for groups
whose membership state is GSMemRemoved — return CESimplexNameNotFound
rather than GLPKnown for a removed-member group, since GLPKnown for
removed members would be inconsistent with how /_connect plan over a
short link handles the same situation.
* chat: fix stale CRITICAL comment in saveConnInfo
Comment claimed SEDBException is re-thrown as CRITICAL but only
SEDBBusyError is (via the `critical` helper at Subscriber.hs:136
and the showCritical branch at :1695). Updated to describe the actual
behaviour.
* chat: fix misleading decodeSimplexName docstring
The comment described "@alice.simplex" as the column's surface form,
but ToField SimplexNameInfo writes the canonical strEncode output
("simplex:/name@alice.simplex"). Aligns the docstring with what the
column actually holds.
* chat: drop redundant T.unpack in CESimplexName* error rendering
plain has a Text instance (verified by sibling simplexNameLine at
View.hs:2176 which uses it directly). The T.unpack in the new error
renderings was inconsistent with the same-feature helper. Cosmetic
cleanup.
* deps: bump simplexmq for resolveSimplexName
* chat: simplexName field on Profile, GroupProfile, LocalProfile
Adds a Maybe SimplexNameInfo field to the wire-level Profile and
GroupProfile (and their DB sibling LocalProfile). JSON instances are
TH-derived with omitNothingFields = True, so the new optional field is
auto-handled and old peers / old JSON without the key decode as Nothing.
Existing record-construction sites are set to simplexName = Nothing as
a placeholder. Outgoing dissemination (userProfileDirect /
userProfileInGroup) and incoming persistence wire-up land in follow-up
commits. redactedMemberProfile passes the field through, matching how
peerType is preserved.
* chat: load LocalProfile.simplexName from simplex_name column
Populate the embedded LocalProfile.simplexName field for the user's own
profile and for peer Contact / GroupInfo from the existing simplex_name
columns on contacts and groups. Previously every DB read set this field
to Nothing (Task 1 placeholder), so downstream consumers that work off
LocalProfile / GroupProfile (e.g., userProfileDirect / userProfileInGroup
that build outgoing XInfo / XGrpInfo via fromLocalProfile) saw Nothing
unconditionally.
Scope is limited to the rows where the simplex_name column actually
exists: contacts (per-user) and groups (per-user). Sites that only read
contact_profiles / group_profiles (toContactRequest, toContactProfile,
toGroupProfile, rowToLocalProfile) remain Nothing; Task 3 adds the
profile-table columns and wires them up.
* chat: test outgoing Profile carries simplexName from User profile
userProfileDirect, userProfileInGroup' and redactedMemberProfile already
pass simplexName through via fromLocalProfile (Task 1) once the embedded
LocalProfile field is populated (previous commit). Lock that behavior in
with focused unit tests:
- userProfileDirect with Just simplexName -> wire Profile.simplexName Just
- userProfileDirect with Nothing -> wire Nothing
- userProfileDirect with an incognito Profile overlay -> wire Nothing
(incognito identity must not leak the user's registered name)
- userProfileInGroup' pass-through
- redactedMemberProfile pass-through (forwarded member profiles)
* chat: clarify groups.simplex_name stopgap comment
Drop the asymmetric toContact comment (the mirror is now obvious post-Task-1)
and rewrite the toGroupInfo stopgap comment to reflect the actual semantics:
groups.simplex_name is per-user locally-known, mirrored into groupProfile as a
stopgap until group_profiles.simplex_name lands.
* chat: add simplex_name to contact_profiles and group_profiles
Adds nullable simplex_name TEXT column and partial UNIQUE
(user_id, simplex_name) index to both contact_profiles and group_profiles
tables. Distinct from contacts.simplex_name / groups.simplex_name
(M20260603), which carry the user's locally-known label set by the
prepare-via-name path; the new columns will carry the peer's broadcast
claim received via XInfo / XGrpInfo (wired up in following commits).
* chat: persist peer-claimed simplexName from incoming profiles
Write paths:
- updateContactProfile_' / updateGroupProfile_ now set the new
contact_profiles.simplex_name / group_profiles.simplex_name columns
from Profile.simplexName / GroupProfile.simplexName respectively.
- createContact_ INSERT writes Profile.simplexName to the new
contact_profiles column (separate from the existing simplexName arg,
which still writes contacts.simplex_name — the user's locally-known
label).
Read paths (closing Task 2's deferred sites):
- toContact splits simplex_name reads: Contact.simplexName from
contacts.simplex_name (existing); LocalProfile.simplexName from
contact_profiles.simplex_name (new column).
- toGroupInfo similarly splits: GroupInfo.simplexName from
groups.simplex_name; groupProfile.simplexName from
group_profiles.simplex_name.
- ProfileRow / rowToLocalProfile, toContactRequest, getUserContactProfiles,
toGroupProfile, getProfileById, groupMemberQuery, getGroupAndMember_,
saveRcvChatItem-related quotes — all extended to read p.simplex_name
and decode it into LocalProfile.simplexName / GroupProfile.simplexName.
Conflict handling (Decision B):
- clearConflictingContactProfileSimplexName_ / *Group* helpers do an
atomic UPDATE-with-RETURNING that NULLs simplex_name on any other
row in the same user that would collide on the partial UNIQUE index,
returning the displaced row's display_name.
- updateContactProfileWithConflict / updateGroupProfileWithConflict
bundle clear+update in one transaction.
- processContactProfileUpdate / xGrpInfo invoke the *WithConflict
variants and emit CEvtSimplexNameConflict when a displacement
happened (with the claiming and displaced display names).
Adds ChatEvent CEvtSimplexNameConflict and SimplexNameConflictEntity
(SNCEContact / SNCEGroup) with JSON instances and View.hs rendering.
* chat: fix review findings on simplex_name persistence
- updateUserProfile no longer writes contact_profiles.simplex_name on
the user's own row (the column is reserved for peer claims; the user's
broadcastable name lives on contacts.simplex_name via uct.simplex_name).
- updateMemberContactProfile_'/Reset_' now write simplex_name; new
updateMemberProfileWithConflict / updateContactMemberProfileWithConflict
variants run conflict-clear and return the displaced name, with
processMemberProfileUpdate emitting CEvtSimplexNameConflict.
- createContact_ runs conflict-clear before INSERT to avoid UNIQUE
constraint violations on first-write peer collisions, returning the
displaced name; createPreparedContact / createDirectContact thread it
through to APIPrepareContact and saveConnInfo XInfo for event emission.
- groups conflict-clear takes ProfileId directly (avoids the NOT IN (NULL)
silent-noop edge case when groups.group_profile_id is ON DELETE SET NULL).
- Moves clearConflictingContactProfileSimplexName_ to Shared.hs so
createContact_ can call it without inducing a circular import.
* chat: resolveOnUserServers iterates user SMP servers for RSLV
* chat: connectPlanName falls back to RSLV when local lookup misses
* chat: RSLV-resolved NameRecord dispatched through prepared row
dispatchResolvedRecord now picks the first nrContactLinks (NTContact) or
nrChannelLinks (NTPublicGroup) entry from the resolved record, decodes it
as AConnShortLink, fetches the short-link data, and eagerly calls
createPreparedContact / createPreparedGroup with the simplex_name set.
Returning CPContactAddress (CAPKnown ct) / CPGroupLink (GLPKnown g ...)
mirrors the local-store-hit branch of connectPlanName: hit and miss
converge on the same plan shape, so the connectWithPlan caller cannot
distinguish where the prepared row came from. Threading uses the
existing Maybe SimplexNameInfo parameter added in c6f26150 for the
local-prepare path -- no new write path or transient carrier.
Pure helper firstNameLink is extracted and exported so the link-picker
contract is testable without a DB / agent. ResolveNameTests gains five
cases covering the per-type selection, the first-link policy, and the
empty-list to CESimplexNameNotFound collapse.
* chat: regen query plans after simplex_name plumbing
* chat: register ConnectTarget + CEvtSimplexNameConflict in bot docs
Bot API docs generator failed with "Undefined type: ConnectTarget"
since f2394d121 (prior plan) flipped APIConnectPlan/Connect from
Maybe AConnectionLink to Maybe ConnectTarget without updating
bots/src/API/Docs/*. Also adds SimplexNameConflictEntity (new in
cd0de9659) and documents the CEvtSimplexNameConflict event for
peer-name displacement notifications. Regenerates the affected
markdown / TypeScript / Python artefacts.
* deps: bump simplexmq for NameRecord reshape; update consumers
simplexmq 5ee014dd reshaped NameRecord to align with the Python resolver
JSON: nrChannelLinks/nrContactLinks (lists of NameLink) became
nrSimplexChannel/nrSimplexContact (Maybe Text); nrDisplayName became
nrName; nrResolver was added; the NameLink wrapper type and nrIsTest/
nrExpiry/nrAdminAddress/nrAdminEmail fields were dropped.
Update dispatchResolvedRecord destructure and firstNameLink signature
to the new Maybe Text shape, and refresh the ResolveNameTests fixtures
and assertions accordingly.
* chat: resolveOnUserServers iterates only on transport errors
Privacy: every miss previously broadcast the candidate name to every
enabled SMP server. Now only NETWORK / TIMEOUT failures fall through
to the next server; definite resolver answers (NAME / AUTH / CMD
PROHIBITED / other ERR) stop iteration.
* chat: document why groups simplex_name index has no soft-delete filter
The contacts simplex_name index filters on (deleted = 0); the groups
index has no analogous filter because the groups table has no `deleted`
column. Groups are hard-deleted by deleteGroup, so the asymmetry is
intentional. The remaining "removed member, row retained" edge case is
flagged in the lookup comment for follow-up.
* chat: document connections.simplex_name as transient carrier
Audit flagged the column as "INSERTed but never UPDATEd". This is by
design per the prior plan's connect-via-plan flow: the column is a
transient carrier between connection-creation and contact-creation.
After the Contact row is created via XInfo handling, contacts.simplex_name
is the source of truth and the connections value is a historical snapshot.
Documents the intent so future readers don't reflag it.
* chat: extract surfaceSimplexNameConflict helper
Six call sites duplicated the same forM_ ((,) <$> claim <*> displaced)
shape emitting CEvtSimplexNameConflict. Extract to a single helper so
future call sites don't drift on whether to emit, and so the conflict
event shape change (post-Task-3 SimplexNameConflictEntity split into
SNCEContact / SNCEGroup) propagates through one site.
* chat: APIVerifySimplexName command + CEvtSimplexNameUnverified warning
Addresses the TOFU vulnerability where peer-claimed simplex_name was
accepted unverified. Adds:
- contacts.simplex_name_verified_at + groups.simplex_name_verified_at
(M20260606_simplex_name_verified)
- APIVerifySimplexName ChatRef command: RSLV-resolves the claimed name
and compares the resolved link to the peer's stored connection link;
on match writes verified_at and emits CEvtSimplexNameVerified;
on mismatch emits CEvtSimplexNameVerifyFailed
- CEvtSimplexNameUnverified passive warning emitted on incoming XInfo /
XGrpInfo when a name claim arrives without a current verification
- updateContactProfileWithConflict / updateGroupProfileWithConflict
clear simplex_name_verified_at whenever the peer's claim transitions
(any value change including Nothing<->Just): the prior verification
was bound to the prior claim.
UI can surface the unverified indicator next to a contact / group's
name, and prompt the user to invoke the verify command. This shifts
the security model from "TOFU + last-writer-wins" to "TOFU + on-demand
RSLV verification".
* chat: register APIVerifySimplexName + verify events in bot docs
ebe90f716 added the verify command + events + SimplexNameVerifyFailReason
type without touching bots/src/API/Docs/. Mirrors commit 0d7ea8061 which
addressed the same gap for ConnectTarget. Regenerates the affected
markdown / TypeScript / Python artefacts.
* chat: bump simplexmq pin + document cross-table simplex_name discriminator
Pin bump 5ee014dd -> c9c2d19 picks up the 8 simplexmq commits since the
last bump (parseBare lowercase fix, forwarded-param cleanup, ServerTests
+ agent end-to-end tests, TldRegistries removal, SNRC ABI decoder,
NameRecord/NameOwner module extraction).
Adds a brief comment on clearConflictingContactProfileSimplexName_
explaining why the audit's flagged cross-table collision (between
contact_profiles.simplex_name and group_profiles.simplex_name) is
structurally impossible: SimplexNameInfo's strEncode prefixes contact
names with '@' and group names with '#', so the stored bytes never
overlap between the two tables.
Query-plan regen deferred (the test is non-deterministic in CI / dev
sandbox — see prior 6c990696c).
* deps: bump simplexmq for HTTP resolver; adapt NameRecord consumers
simplexmq 92b3d049 reshaped NameRecord text fields from Maybe Text to
Text (empty string sentinel). Adapt firstNameLink to take Text directly
and treat T.null as "absent". dispatchResolvedRecord destructure
unchanged; passes the text values straight through. apiVerifySimplexName
switches from Just/Nothing pattern to a T.null guard with the same UX.
Test fixtures updated.
* deps: bump simplexmq for multi-link NameRecord; adapt consumers
* core: treat RSLV CMD UNKNOWN as no name-resolution support
* core: fix contact-by-connection query missing simplex_name_verified_at
* deps: update sha256map for simplexmq f555e9af pin
* core: iterate past RSLV-unsupported name servers
* core: filter RSLV servers by operator enablement
* core: align resolver docs/tests with RSLV errors
* deps: bump simplexmq to df1aa24c
* refactor(names): agent resolution + one error type
Adopt the simplexmq names rework (PR #7045): name resolution is now
owned by the agent (resolveSimplexName picks a names-role server), so
the chat-side iteration is removed - delete ResolveError,
iterateResolvers, resolveOnUserServers, enabledSMPServersForUser and
resolveErrorToChatError.
One error type: resolver/agent failures flow through ChatErrorAgent;
remove the CEvtSimplexName* events, SimplexNameVerifyFailReason,
SimplexNameConflictEntity and CESimplexNameResolverUnavailable.
APIVerifySimplexName returns CRSimplexNameVerified (verified::Bool),
mirroring CRConnectionVerified. connectPlan handles the name target
directly; updateProfile WithConflict aliases collapsed into the plain
functions.
Add the per-operator "names" SMP server role (migration
20260612_smp_role_names, official operator on by default) feeding
ServerRoles.names -> UserServers.nameSrvs.
Bump simplexmq pin to ce69adfd and regenerate sha256map.nix.
* fix(store): match chat_schema.sql to sqlite 3.46+ indent
The schema-dump test renders the partial-index WHERE via the sqlite3
CLI; sqlite >=3.46 wraps a multi-condition WHERE onto two lines
("IS NOT NULL" + indented "AND ...") where 3.45 kept it on one. The
committed schema was generated with 3.45, so CI (newer sqlite) failed
the comparison on idx_contacts_simplex_name. Regenerated with the
newer formatter; only that one WHERE clause changes.
* feat(operators): warn when no server resolves names
Mirror USWNoChatRelays: validateUserServers emits USWNoNamesServers
when no enabled server of an enabled operator carries the SMP names
role. noNamesServersWarns is self-contained with local predicates,
matching the sibling noChatRelaysWarns; noServersErrs is untouched.
* test(operators): expect USWNoNamesServers warning in no-servers cases
* fix(store): single-line simplex_name WHERE to match CI sqlite (<=3.45)
* chore: bump simplexmq pin to 6843b14c
* refactor(store): consolidate names migrations into one
Unshipped feature - merge the four incremental simplex_name migrations
(0603/0604/0606/0612) into a single M20260603_simplex_name. The combined
UP applies the ALTERs/indexes in the same order, so the resulting schema
is byte-identical (verified by SchemaDump on SQLite and pg_dump on Postgres).
* update simplexmq
* plan for name resolution
* update types and schema
* simpler resolution, name proofs
* simplexmq
* generate bot types, schema, unStrJSON, fix tests
* ad hoc link comparison, create short link
* update simplexmq
* remove same link, use simplexmq instead
* split verify API for contacts and public groups
* remove comment
* simplify warnings
* test: remove unused
* remove cute language
* type name
* remove spurious comments
* refactor setting user name
* refactor setting user name
* remove trivial tests
* refactor
* remove tests using pre-short-link addresses
* rename
* move names
* bots api
* refactor more
* refactor
* refactor to another type
* load own names
* read short links when looking up by name
* renames
* refactor verification
* mapM_
* update api types
* change field for name
* rename columns
* api types, schema
* renames
* fix links
* simplify
* remove comments
* rename fields
* simplify
* remove proof from channel addres
* refactor
* name resolution test
* change tests
* fix tests
* fix tests
* fix plan for names
* test
* test verification status
* bot api
* fix tests
* update bot api types
* query plan
* add api for setting public group access
* android, desktop, ios: connect via SimpleX name (#7068)
* android, desktop, ios: connect via SimpleX name
* android, desktop, ios: open known contact on name lookup; surface prepared contact
Name search opens the contact (not list-filter); resolved/prepared contacts and groups are added to the chat list so they're visible and openable. Kotlin compile-verified; iOS edits pattern-matched, pending Xcode build.
* feat(names): UI names role + agent NAME error
Parity with the core names rework (#7045):
- Add `names` to ServerRoles (Android + iOS) and a per-operator
"To resolve names" toggle under the SMP section (xftp has no names
role; the shared ServerRoles field stays false there).
- Mirror the new agent error: NameErrorType + a NAME case on both
AgentErrorType and ProtocolErrorType (the SMP ErrorType mirror), so
the new SMP/agent NAME errors decode instead of crashing the decoder.
- Remove ChatErrorType.SimplexNameResolverUnavailable (deleted in core)
and repoint its "name resolution unavailable" alert to the agent
NAME NO_SERVERS error, reusing the existing strings.
Android (multiplatform) compiles clean; iOS mirrors the same changes
(builds in Xcode).
* feat(names): UI warning when no server resolves names
Mirror core USWNoNamesServers: add the NoNamesServers variant to
UserServersWarning (Kotlin sealed class + Swift enum) and its
globalWarning / globalServersWarning branch, rendered by the existing
ServersWarningFooter / ServersWarningView. Matches the noChatRelays
warning exactly.
* fix(servers): show all validation errors and warnings, not just the first
globalServersError/Warning returned only the first entry, so a second
warning (e.g. no names servers behind no chat relays) or a second error
(e.g. no XFTP servers behind no SMP servers) was never displayed. Make
them return all entries (globalServersErrors/Warnings) and render one
footer row each, across the three combined-footer views. Per-protocol
SMP/XFTP footers are unchanged.
* docs(names): add SimpleX name UI plan
* feat(names): add name model fields + SimplexName helpers
* feat(names): verify + set-name API & responses
* docs(names): bump core sync to 5008b4e62
* feat(names): show name + verification on chat info
* feat(names): add Verify SimpleX names privacy toggle
* feat(names): add set-name screens (user + channel)
* update ui
* fix kotlin
* fix codable
* fix ios
* fix errors
* api in UI
* send name as string in protocol
* update simplexmq, capitalize
* verify that name is in profile for own and known contacts and channels as condition of name resolution
* update simplexmq
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* add log
* bot types
* finalize renames, ui alerts
* more renames
* kotlin alerts
* show set name
* alerts
* texts, icons, footers
* move JSON parsing to a persistent thread with large stack
* simplexmq
* use uikit alerts
* remove comment
* move name verification to more privacy
* change icon on verify names setting
* verify name proof
* nix shas
* show verified names
* better alert when saving names
* revert breaking field name change
* simplexmq
* clean up
* remove unused
* remove empty line
* fix error
* simplify
* use domain without prefix in profiles and in database, rename fields and types
* rename types
* fix JSON name
* pass verified domain to prepare API
* fix prepare api
* fix ios encoding
* enable name resolution via Flux servers
* fix test
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* core: test support chats in channels, CLI defaults to sending as member in support chat
* ui: enable support chats in channels
* use correct scope when sending from UI
* more readable
* remove test output
* show member support chat in channels
* preference for support chats
* ios: types for support preference
* mp: support preference types
* show support preference in UI
* fix ios
* make support preference optional in JSON parser
* update string
* change strings, pass parameters to prefs
* refactor kotlin
* take support preference into account
* refactor core
* do not show broadcast placeholder in support scope
* move role check, add pref check on update
* support preference test (failing)
* fix version
* fix tests
* warning alert when enabling chats with admins
* revert on dismiss
* update text and icons
* query plans
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* ios: sharing channel links/cards
* update nix shas
* improve
* fix preview
* change condition
* move button for owner
* refactor
* refactor 2
* fix sheets
* MsgChatLink JSON encoding
* correct default icon when editing group profile
* drop link from card
* card layout
* strip link from text
* remove file ref
* share via chat when created
* rename file, do not show text when there is no text
* better card layout
* padding, info string
* add log
* padding
* text layout
* warning emoji if signature verification failed
* chat link preview in chat list
* description
* alert information
* tappable preview
* better
* conditional border color
* sending and forwarding views
* small link icons for forwarding
* strip link in one place
* forwarded context
* quote view for chat links
* reduce diff, remove unnecessary changes
* simplify
* trim description
* diff
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* rfc: bot messages and buttons
* update
* update bot rfc
* core: add bot commands to chat preferences and peer type to profile
* update postgresql schema
* update query plans
* chat sessions preference
* markdown for bot commands
* schema
* core: file preference, options to create bot from CLI
* core: different command type
* ios: commands menu
* update types
* update ios
* improve command markdown
* core, ios: update types
* android, desktop: clickable commands in messages in chats with bots
* android, desktop: commands menu
* command menu button, bot icon
* ios: connect flow for bots
* android, desktop: connect flow for bots
* icon
* CLI commands to view and set commands, remove "hidden" property of command, bot api docs
* corrections
* fix inheriting profile preferences to business groups
* note on business address
* ios: export localizations
* fix test
* commands to set file preference on user/contact, tidy up layout and display of command and attachment buttons
* core: use longer timeouts for background network requests, support retries with increased timeouts
* ios: update types
* ios: allow user retry actions with failed network requests
* build: add exporting API functions with retry
* android, desktop: update types and C APIs
* android, desktop: alert to retry action on network error
* simplexmq
* ios: alerts to open known/prepared contact and groups
* android, desktop: alerts for knonw chats; all ui: fix prepared contacts misinterpreted as contact cards