An unapproved account only gets the server's default contacts, which are
its way to reach support — list them instead of discarding them, and hide
the search field that cannot return anything.
Xcode 27 ships Swift 6.4, which rejects two things 26.09.1 still contains. The
upstream fix for the app itself is cherry-picked as the commit before this one;
these two are what it does not cover.
Compound's colour overrides now take a KeyPath & Sendable, so the HealthChat
brand arrays have to carry the same constraint.
NotificationContentBuilder is main-actor isolated, so its test suite has to
build the type from an async init. The lines match upstream's own fix, which
only exists inside the larger AccountProvider change (#6103) and so cannot be
cherry-picked on its own.
* Add HealthChat API and extended profile services
Expose OpenID tokens and homeserver credentials from the client proxy, and
add fork-only services for the HealthChat member directory backend
(api.healthchat.ch) plus a service that reads MSC4133 extended profile
fields directly from the homeserver. Groundwork for the members tab and
the profile screens.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Show extended profile details on the profile screens
Add a UserProfileScreenHook that lets the fork inject an extra Form
section below the header of the user profile and room member details
screens. The HealthChat implementation loads the MSC4133 extended
profile fields and renders them with tappable phone/email/website
links, hiding the section entirely for profiles without any fields.
Strings live in a new fork-only HealthChat.strings table (de/en/fr,
generated as HealthChatL10n via SwiftGen) since Untranslated.strings
pins the English bundle.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Add a member directory tab
Add a HomeTabsHook that lets the fork append an extra tab to the home
tab bar, and a HealthChat members flow behind it: a directory of all
approved members from the HealthChat API with server-side search,
pending-approval and error states, chat shortcuts and pushed member
profiles. Chats and calls are handed back to the session flow via the
hook's actions publisher.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Let members edit their profile and invite others
Add a native profile editor for the HealthChat extended profile fields
(13 fields plus avatar, saved as a partial diff through the HealthChat
API so displayname derivation and Zitadel sync stay server-side), an
invitation sheet (email plus personal note) and a profile-completeness
hint on the member directory.
A new UserDetailsEditScreenHook lets the fork substitute the profile
editor behind the settings user row — the stock display-name editor
would let users edit a value the next profile update derives away.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Add HealthChat API and extended profile services
Expose OpenID tokens and homeserver credentials from the client proxy, and
add fork-only services for the HealthChat member directory backend
(api.healthchat.ch) plus a service that reads MSC4133 extended profile
fields directly from the homeserver. Groundwork for the members tab and
the profile screens.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Show extended profile details on the profile screens
Add a UserProfileScreenHook that lets the fork inject an extra Form
section below the header of the user profile and room member details
screens. The HealthChat implementation loads the MSC4133 extended
profile fields and renders them with tappable phone/email/website
links, hiding the section entirely for profiles without any fields.
Strings live in a new fork-only HealthChat.strings table (de/en/fr,
generated as HealthChatL10n via SwiftGen) since Untranslated.strings
pins the English bundle.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Add a member directory tab
Add a HomeTabsHook that lets the fork append an extra tab to the home
tab bar, and a HealthChat members flow behind it: a directory of all
approved members from the HealthChat API with server-side search,
pending-approval and error states, chat shortcuts and pushed member
profiles. Chats and calls are handed back to the session flow via the
hook's actions publisher.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Add HealthChat API and extended profile services
Expose OpenID tokens and homeserver credentials from the client proxy, and
add fork-only services for the HealthChat member directory backend
(api.healthchat.ch) plus a service that reads MSC4133 extended profile
fields directly from the homeserver. Groundwork for the members tab and
the profile screens.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
* Show extended profile details on the profile screens
Add a UserProfileScreenHook that lets the fork inject an extra Form
section below the header of the user profile and room member details
screens. The HealthChat implementation loads the MSC4133 extended
profile fields and renders them with tappable phone/email/website
links, hiding the section entirely for profiles without any fields.
Strings live in a new fork-only HealthChat.strings table (de/en/fr,
generated as HealthChatL10n via SwiftGen) since Untranslated.strings
pins the English bundle.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
Expose OpenID tokens and homeserver credentials from the client proxy, and
add fork-only services for the HealthChat member directory backend
(api.healthchat.ch) plus a service that reads MSC4133 extended profile
fields directly from the homeserver. Groundwork for the members tab and
the profile screens.
Claude-Session: https://claude.ai/code/session_013qdTbAWsZj2quQzEkagXaS
ClassicAppManager migrates an existing user's session and encryption keys by
reading the legacy app's app-group container and keychain. Those identifiers
defaulted to Element's own, which this team cannot provision, so loadAccounts()
threw invalidAppGroupIdentifier — caught and logged, never surfaced. Since both
apps share a bundle id, the result would be every existing user silently logged
out and losing local history when they update.
The legacy identifiers belong to HealthChat and are provisionable under the same
team, so repoint them rather than dropping them.
Also stop sending accounts without key backup to a dead end. Upstream shows those
users a sheet asking them to open Element Classic and set up backup first, but this
update replaces that app and nothing registers its URL scheme, so the sheet's only
button does nothing. Sign in directly instead: the user re-verifies the device and
loses only history that by definition has no backup.
* Add a developer option and selection mode for timeline messages
First step of the multi-select messages feature (element-meta#3255, iOS
counterpart of element-x-android#6918). Everything is gated behind the new
`messageMultiSelectEnabled` developer option and nothing changes while it
is off.
- Adds a "Select" action to the timeline item menu for remote messages in
the room, focused and thread timelines. Choosing it enters selection
mode with that message selected.
- While selecting, every bubbled row shows a checkbox on its leading edge
and tapping or long pressing a row toggles it. State events, redacted
messages, live locations, unsupported events and local echoes cannot be
selected and stay inert. Selected rows get the accent background.
- The navigation bar swaps to a Cancel button and an "N selected" title,
the back button is hidden and the composer is removed until the
selection ends (Cancel or deselecting the last item). This is the hook
where the bulk action bar will land in the next PR.
- The selection is capped at 30 messages, showing a toast when the cap
is hit, and is cleared if the developer option is switched off.
- The selection state lives in `TimelineViewModel` so room and thread
timelines share the behaviour without extra plumbing.
Includes unit tests for the menu action gating and the selection state
transitions, plus RoomScreen and bubble styler previews for the new state.
* Harden the timeline selection mode after review
- Reconcile the selection with timeline updates: selected messages that
become unselectable (e.g. redacted) are dropped, and the selection is
cleared when the timeline is swapped for a focused or live one, so the
count can never refer to items that aren't on screen.
- Keep the composer mounted but collapsed while selecting, instead of
removing it from the hierarchy. This preserves the draft and composer
mode and avoids the disappear side effects. The footer with the identity
warnings stays visible.
- Give the long press modifier an `isEnabled` flag so the recogniser is
disabled while selecting, rather than only skipping the action while
still animating and buzzing.
- Gate the Mac context menu on the selection like the other gestures.
- VoiceOver: a selectable bubble becomes a button carrying the selected
trait whose default action toggles it, non-selectable rows lose their
message actions while selecting, and reactions, thread summaries and the
checkbox are hidden from the accessibility tree in selection mode.
* Stop voice message recordings when entering selection mode
The composer is collapsed rather than removed while selecting, so an
in-progress recording would otherwise keep the microphone open out of
sight. Entering the selection now stops the recording, which moves the
composer to the preview state so the message is still there to play or
send once the selection ends. Playback of voice messages is unaffected.
* Use the translated strings for the message selection mode
The selection strings have been imported to Localazy, so the untranslated
placeholders are dropped in favour of `action_select`,
`screen_room_maximum_messages_selected` and the `screen_room_selection_count`
plural. Only these keys are taken from the download to keep the change
focused.
* Apply review suggestions to the message selection mode
Addresses the first review round on the selection mode:
- Undecryptable messages are no longer selectable, as none of the bulk
actions can be taken on them from the single item menu either.
- Renames for clarity: the menu action is `selectMessages` (leaving room
for a future `selectText`), the view action is `startSelection`, the
state exposes `contains(_:)`, `limit` and `isAtLimit`, and the composer
collapsing modifier is `collapsedInPlace(_:)`.
- `canSelectMessages` uses a switch expression, the selection state doc
comment refers to a feature flag rather than Labs, the recording helper
doc describes only what it does, and `toggleSelection()` moves below
the subviews.
* Keep the accessibility labels of timeline items while selecting
The selection mode used to replace each item's accessibility body with a
plain combined element, dropping the sender name representation and the
gallery label. The per item labelling now always applies, and the selection
only changes the interaction on top of it: the message actions are offered
as before, or the item becomes a button carrying the selected trait whose
default action toggles it. Polls and galleries collapse into a single
element only while selecting, as their children are inert then.
* Keep the timeline types within the lint limits after rebasing
The redaction reason work on develop grew the menu action handler and the
timeline view model right up to SwiftLint's size limits, so the selection
additions tipped them over. The `selectMessages` case now shares the
`downloadMedia` break and the selection flag subscription lives in the
selection extension.
* Fix the snapshot regressions from the selection mode
- Drop `geometryGroup()` from the bubble row: it stopped the avatar
placeholder initial from rendering, which showed up as a mismatch in
every timeline item snapshot.
- Collapse the composer with opacity instead of clipping, as the clip also
removed the composer background from the home indicator area while the
composer was fully visible.
- Keep the timeline item menu preview on the default flag-off state so the
existing menu snapshots stay valid.
- Record the new "Selecting" room screen and "Selection" bubble snapshots
on the same iPhone SE simulator CI uses.
* Only show a spinner when opening the media viewer is slow
* Keep a sent local echo's page in the media viewer
* Fix the caption visibility.
---------
Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
Co-authored-by: Matthew Hodgson <matthew@matrix.org>
* Add a Mentions room list filter
Adds a new filter chip to the room list that shows only rooms with
unread mentions, using the new mentions room list filter from the
Rust SDK (matrix-org/matrix-rust-sdk branch matthew/mentions-filter).
The SDK version needs bumping to a release that includes the new
filter kind before this can build against the released components.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Order the room list filters by expected usage
Move Favourites ahead of People and Rooms so the more commonly used
filters (Unreads, Mentions, Favourites) sit first in the chip row —
Rooms and Invites are used rarely enough that it's fine for them to
wrap off the side.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uk8aPxHn3BHCe52L226jdG
* update the SDK and use the appropriate rust filter types
* preview tests + app settings flag for mentions
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Mauro Romito <mauro.romito@element.io>
The newline to break replacement only matched paragraph to paragraph, so a blank
line after a list was dropped and the text following it glued to the last item.
Only a following paragraph is matched, because without a blank line its text
would have continued the last item instead of starting a paragraph of its own.
* Make sure user profiles are up to date when mentioning.
* Automatically focus the custom status text field.
* Make sure to clear the previous custom status text before input.
* Attempt to fix the user status sheet sometimes disappearing.
The room summary provider subscription only delivers its current state through an
async main queue hop, which can end up queued behind the first render. On a warm
launch, where the cached rooms have already been published before the home screen
exists, that leaves the skeletons up for the whole duration.
Read the current state at the end of init instead, alongside the rooms which were
already being read there.
The room summary provider publishes its loading state and its rooms through two
independent streams, so the state can report loaded before the first summaries
have been applied. Flipping the room list mode to .rooms at that point renders
an empty list until the summaries arrive.
Combine the two publishers into a single subscription so that the mode and the
rooms are always updated from the same pair of values, and keep the skeletons up
until the rooms have published. Only the initial transition is held back, an
empty list later on is a filter or a search result and must not send the mode
back to the skeletons.
The suite intermittently hung on CI, most recently for the full 2 minute time
limit on lifetimeIsCapped(). A spindump from a failed run shows the process
suspended rather than spinning (0.247s of CPU over 190s) with a background
priority worker thread sitting runnable for roughly 145 seconds without ever
being scheduled.
That band is exactly what swift-clocks' TestClock depends on to make progress:
both advance() and checkSuspension() await Task.megaYield(), which serially
awaits 20 detached tasks at background priority. On an oversubscribed runner
those never run, so the test waits forever instead of failing. The same
mechanism, less starved, is why two of the passing tests took 1.0s and 0.77s
for what is otherwise pure logic. checkSuspension() made it worse, since
asking whether a timer was armed cost a megaYield per poll.
ManualClock replaces it. It holds a single pending sleep, since that is all a
test needs to step through: the sleep registers a continuation, advance(by:)
resumes it synchronously once its deadline passes, and waitForScheduledSleep()
suspends until the timer is armed instead of polling for it. Nothing in the
path touches background priority and no loop's iteration count depends on the
scheduler. The suite now runs in 0.019s, and under a locally saturated CPU the
old version took 1.005s against the new one's 0.037s.
Also drops the now unused Clocks package, removes the 2 minute time limit that
was papering over the starvation, re-enables callIsTimingOut() which the same
flakiness had disabled, and has lifetimeIsCapped() assert the 90 second cap
directly rather than inferring it from when the timer fires.
The shim is embedded but not linked, so nothing at build time proves it is
still there. If it stops being copied into the bundle, or the class is renamed,
dlopen simply fails at runtime and the user gets a blank map behind an error
alert. Nothing in CI would notice.
The test asserts the framework loads and the shim resolves, which is exactly
the pair that breaks if either side of the arrangement is disturbed.
* Adjust login flow to exclusively use the ServerSelectionScreen
* Move async code to server selection vm
* ServerSelection supports pro picker options
* Use homeserver address for both confirmation and picker modes
* rename ServerSelectionScreenMode
* remove ServerConfirmationScreen
It's been superseded by the ServerSelectionScreen
* rename userInput
* Update strings with latest design strings
* Add server input autocompletion
* Store previous servers in AppSettings and autofill on server signin
* Audit changes for logging
* Enforce lowercase server history storage
* Update tests
* Update Xcode project
* PR review fixups
* Add HomeserverHistoryManager
* Review fixups 2
* Review fixups 3
* Extract a shared helper for loading a media preview
The four screens that show a fullscreen avatar each had their own copy of the same
load-and-wrap logic. Move it to MediaPreviewItem.load, with the mime type as a
parameter defaulting to JPEG, and leave each screen's loading indicator where it is.
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
* Only dismiss a QuickLook preview that is still presented
UIKit forwards dismiss() up the presentation chain when the receiver has nothing
presented, so dismissing the avatar view was dismissing the profile details sheet
also.
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
* Allow tapping the avatar in the room member bottom sheet to view it fullscreen
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
---------
Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
* Support for waitingForAuth continuation sender during Link new device
* Stop automatically regenerating new QR codes when they expire.
This reverts part of commit e235e3ba81.
* Add a timeout after the Face/TouchID verification when linking a desktop computer.
---------
Co-authored-by: Hugh Nimmo-Smith <hughns@element.io>
* feat: add call icon next to members in call
* feat: use matrix rtc room status for in-call
* chore: revert xcodeproj change
* chore: update preview tests
* chore: Update ElementX.xcscheme
* chore: undo room member status proxy
---------
Co-authored-by: Kai <nankeen@users.noreply.github.com>