On a cold launch the home screen appeared just before the cached rooms
published, briefly showing skeletons that were immediately replaced.
The home screen now starts in a new awaitingCachedRooms mode, keeping
the list blank while a cached room list is about to publish and falling
back to the skeletons after 500ms. Without a cache the skeletons show
straight away as before.
* Initial plan
* Offer only joined rooms in destination pickers
Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com>
* Clean whitespace in share picker test
Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com>
* Restrict the shared picker room list to joined rooms
The alternate room summary provider, which backs the forward, share and
Add rooms pickers and global search, now always includes the SDK's
joined filter in its base filter. This replaces the per-call joinedOnly
flags, so Add rooms no longer offers invited rooms or spaces and no
screen can forget the flag.
A filter set before the provider has its room list is now applied once
the list is set, instead of being reset to the default. The share picker
hit this on a cold launch from the share extension.
The forward and share pickers also hide upgraded rooms, matching the
Suggestions section. The SDK has no filter for these.
Also registers the share picker's test file in the project, fixes the
blank-line indentation SwiftFormat flagged, and re-records the picker
snapshots without the upgraded room.
* Check room membership for forwarding suggestions and search breadcrumbs
RoomSummary now has isJoined, built from the room's membership. The
forwarding suggestions and the search breadcrumbs used a missing
joinRequestType to mean joined, which is also the case for rooms that
have been left or banned from. It only worked because the room list's
base filter already drops those.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com>
Co-authored-by: Mauro Romito <mauro.romito@element.io>
The room list keeps showing skeletons while the provider reports loaded
but no summaries have published yet, to avoid flashing an empty list
before the first batch lands. That guard also fired when a filter
(chips, space or search) legitimately matched nothing, leaving the home
screen on placeholders forever instead of showing the empty filter
state.
* Announce toasts with VoiceOver
Toasts are posted from a separate overlay window and never take
VoiceOver focus, so their text was never spoken. UserIndicatorController
now posts a high priority announcement with the title whenever a toast
with readable text is submitted, including re-submissions of an already
visible toast with the same ID. Modal indicators are not announced.
* Remove empty-title toast announcement check
Co-authored-by: Velin92 <34335419+Velin92@users.noreply.github.com>
* Hide toasts from VoiceOver
The toast title is now announced by UserIndicatorController when the
toast is submitted, so letting VoiceOver also focus the toast itself
would only read the same text twice.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
* Suggest recently visited rooms when forwarding messages
The Forward to screen now shows up to 5 recently visited rooms in a
Suggestions section, above a Chats section that lists every room. The
suggestions are never searched: while searching, only the Chats section
is shown and filtered. The source room, invites, spaces and upgraded
rooms are never suggested, and the section is hidden when there is
nothing to suggest.
The suggestions are resolved from the room summaries rather than room
proxies, so they show up without holding back the list, and they are
re-resolved as the room list loads after a cold start. ClientProxy now
exposes the recently visited room IDs for this.
The recently visited rooms filter is now async, so callers choose its
isolation instead of asserting it at runtime.
* Apply review suggestions to the forwarding suggestions
Rooms:
- Each room is listed once. Suggested rooms are left out of Chats unless
searching, where Chats lists every result. This also removes the
duplicated row whose selection changed without the tapped row's
animation.
- The Chats header is hidden when the section has no rooms.
Suggestions:
- Resolve them once when the screen opens instead of on every room list
update.
The count was only on the forward button at the bottom, so VoiceOver
users had to go and find it after every tap. While selecting, each
selectable message now starts its label with "N messages selected".
The label changes on every toggle, so VoiceOver reads the new count
followed by the message, as in other messaging apps.
Unselecting the last message used to end the selection, closing it
without the user asking. Selection mode is now its own state: Forward
starts it and it only ends with the close button, once the messages are
forwarded, when the timeline changes or the feature is turned off. With
nothing selected the forward button stays visible but disabled.
swift-snapshot-testing is pinned in both compound-ios/Package.swift and
project.yml. Renovate updated them from different groups, so one side got
bumped without the other and the Xcode build broke on the mismatched
Package.resolved. A dedicated "Shared Dependencies" group updates both
together, and since that PR touches project.yml the XcodeGen workflow
regenerates the project as well.
Tool and Compound dependencies had no schedule while project dependencies
were only updated every 2 weeks, so their PRs landed at unrelated times.
Put them all on the same biweekly Wednesday slot and rename the Compound
group to "Compound Dependencies" for consistency with the others.
`managers` and `matchPaths` are deprecated in favour of `matchManagers`
and `matchFileNames`. Renovate was migrating them on the fly, this makes
the config match what it actually runs.
Replace the 100ms polling loop in the background app refresh with a
subject that the state machine's transition handler sets as soon as the
app is no longer restoring a session, whatever the outcome. The refresh
now awaits it once, with the same 5 second timeout, instead of waking
up repeatedly.
Move the wait back into the class body and silence type_body_length for
AppCoordinator rather than keeping a single-function extension around
to stay under the limit.
When iOS relaunches a terminated app for the task, the session is still
being restored asynchronously when the refresh starts. Wait up to 5
seconds for the restore to finish, bailing out early if the app isn't
restoring a session (e.g. signed out or the restore failed).
# Conflicts:
# ElementX/Sources/Application/AppCoordinator.swift
Components of a formatted body were identified by their plain characters, so an edit
that only added formatting (e.g. wrapping a word in inline code) kept the same ID.
SwiftUI then reused the existing MessageText, whose @State string ignores new input,
and the old unformatted text kept rendering.
Identify components by the hash of their attributed string so formatting changes
produce a new ID.
QuickLook requests every page up front when the viewer opens. At that point the
media timeline is still empty, so every page after the tapped media is cached as
a forward pagination placeholder. Once the timeline loads it reports forward as
end reached, so no pagination happens and nothing ever replaces those cached
placeholders, leaving the viewer stuck when swiping forward.
When landing on a placeholder, or when the timeline updates, the controller now
checks whether the data source holds a different item at that index and if so
refreshes the current page. The refresh waits for any swipe to settle, as
refreshing mid-swipe breaks QLPreviewController.
UIWindow.init() is deprecated in iOS 26. AuthenticationStartScreenViewModelTests
now creates its fake window from the test host's connected scene, matching
ServerSelectionScreenViewModelTests.
With Swift 6.4, the default value for AlertInfo.primaryButton warned that
T's Hashable conformance may be isolated and can't be used in a main
actor-isolated context. The nested AlertButton/AlertTextField types
don't depend on T, so move them to top level as AlertInfoButton and
AlertInfoTextField, and keep typealiases so existing call sites compile.
Constraining T to SendableMetatype isn't possible as the synthesized
Hashable conformances of our main actor-isolated alert enums are isolated.
The forwarding screen now picks destinations for one or several
messages, so "Forward message" no longer fits. Use the common_forward_to
string from Localazy, as in the designs, and re-record its snapshots.
Whether to animate a route was decided after resuming the client
services, by which time a notification tap or an external deep link
had already made the app active, so the user watched a navigation
animation from the room list. Read the app state as soon as the route
arrives instead, so these land directly on their target while routes
handled in the foreground keep animating.
A reaction that fails to send can now block the room's send queue, but the
timeline only surfaced failures for the item itself, its edit or its
redaction. EventTimelineItemProxy.pendingSend now also falls back to our
first failed reaction, targeting it with SendTarget.reaction(key:), so the
message shows the red exclamation mark and the sending failed alert can
retry or remove it.
In-flight reactions are deliberately ignored so reacting doesn't flash a
sending indicator on the message.
Tapping a message notification always opened the room on a timeline
focussed on that event, even when it was the newest message and the
live timeline already ends with it.
On tap, the notification manager now checks whether any other delivered
notification for the same room is more recent. If none is, the room is
opened live instead of focussed. Older notifications in a room keep
focussing on their event.
Query and room breadcrumbs reserved slightly different heights, and
rooms without a description collapsed further. Both now reserve the
space of a title and subtitle so every row lines up.
Each recent search now has a trailing close button to remove it from
the history. The view action is logged without its payload so removed
queries don't end up in the logs.
Queries were previously stored whenever a result was selected, which
meant half-typed queries ended up in the history. Now a query is only
recorded when the user submits the search field, while selecting a
result only records the room.
The translations update removed screen_room_confirm_removal_message, as the redaction confirmation now asks the question in its title ("Remove message?"), matching Element X Android. Drop the message from RedactConfirmationView and re-record the previews whose copy changed.
* Run the tests on iOS 27.0
Bump the required simulator OS in the preview, UI and Compound tests, and the CI tool's default OS, from 26.5 to 27.0. Test devices stay the same.
iOS 27 writes transcoded videos 146 bytes larger, so update the expected sizes in MediaUploadingPreprocessorTests.
* Build and test on CI with Xcode 27
Select Xcode 27 in ci_common.sh and move every macOS workflow to the xcode-27 runner image, as macos-26 doesn't include Xcode 27. The Compound workflow now also creates its simulator on the iOS 27.0 runtime.
The xcode-27 runners are slower, so give the unit, Enterprise, Compound and snapshot recording jobs longer timeouts.
* Fix the preview snapshot layout on iOS 27
On iOS 27 the hosting controller applies the window's safe area when rendering, but the snapshot size comes from sizeThatFits, which ignores it. Component previews were shifted down and clipped at the bottom. Disable the hosting controller's safe area regions for these snapshots so the measured and rendered layouts match.
swift-snapshot-testing also renders into a window without a scene, and on iOS 27 a navigation stack in such a window reserves space for a large title it never shows, leaving a gap below the navigation bar. Attach the render window to the app's scene until swift-snapshot-testing handles it.
The invite users and add rooms screens sized their selected items row from a measured width stored in state, which is zero on the first layout pass, so snapshots could draw the row empty. Use containerRelativeFrame instead. The add rooms search previews now also wait for their results, and the search previews that show a clear button get a small precision tolerance, as it renders inconsistently in snapshots.
* Re-record the preview snapshots on iOS 27.0
Recorded on an iPhone SE (3rd generation) simulator running iOS 27.0 with Xcode 27.0 (27A266a). The changes come from iOS 27 rendering: text anti-aliasing, hyphenation and line breaking, navigation bars and toggles that now render in snapshots, lazy stacks that were blank on iOS 26, and pseudo-language locale formatting.
* Fix the UI tests on iOS 27
iOS 27 no longer exposes accessibility identifiers on SwiftUI menu items, so the space exploration test now finds them by label within the menu. On iPad, the start chat search field keeps focus after submitting and can swallow the next tap, so retry the tap if the create room screen doesn't appear.
* Re-record the UI test snapshots on iOS 27.0
Recorded on iPhone 17 and iPad (A16) simulators running iOS 27.0 with Xcode 27.0. The changes come from iOS 27 styling, like the keyboard height and glass borders. This also removes the iPad app lock setup snapshots, which have been orphaned since those tests stopped running on iPad.
* Fix the accessibility tests crashing on iOS 27
The accessibility tests' previews wrapper kept its ImageRenderer alive across the awaits that follow the render. On iOS 27, releasing it at that point tears down its view graph outside of a SwiftUI update, which hits an assertion and crashes the app. Release the renderer as soon as the image has been rendered.
* Keep the accessibility tests' image renderers alive on iOS 27
Releasing the ImageRenderer straight away wasn't enough: on iOS 27, releasing one that rendered a preview crashes SwiftUI whichever context it happens in. Keep the renderers alive instead. Each test launches a fresh app, so they only live for the one test.
* Share the UI tests' app settings with their mock user session
Since the user session flow reads its settings from UserSettings, the UI tests' mock user session was wrapping a fresh volatile AppSettings instead of the one the tests configure. The flags that skip onboarding weren't picked up, so every user session UI test got stuck on the notifications permission screen.
* Re-record the Compound snapshots on iOS 27.0
Apply the same safe area fix as the app's preview tests, so size-that-fits snapshots aren't clipped on iOS 27, and re-record the snapshots on an iPhone SE (3rd generation) simulator running iOS 27.0 in English (US).
* Fix a race reading the keyboard in the accessibility tests
On iOS 27 the QR code login screen's keyboard can be dismissing between checking that it exists and reading its frame, which fails the test. Snapshot it in a single query instead.
Remove some redundant injections when passed alongside a user session too.
# Conflicts:
# ElementX/Sources/Screens/PinnedEventsTimelineScreen/View/PinnedEventsTimelineScreen.swift
# ElementX/Sources/Screens/Timeline/TimelineViewModel.swift