Only record query breadcrumbs when the search is submitted

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.
This commit is contained in:
Stefan Ceriu
2026-09-29 14:53:02 +03:00
parent bc018251c7
commit 313365ad83
4 changed files with 22 additions and 12 deletions
@@ -48,6 +48,7 @@ struct SearchScreenViewStateBindings {
enum SearchScreenViewAction { enum SearchScreenViewAction {
case appeared case appeared
case submitQuery
case selectRoom(roomID: String) case selectRoom(roomID: String)
case selectMessage(roomID: String, eventID: String) case selectMessage(roomID: String, eventID: String)
case reachedTop case reachedTop
@@ -138,11 +138,14 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
// The provider is shared, so other consumers may have changed its filter while we were off-screen. // The provider is shared, so other consumers may have changed its filter while we were off-screen.
// Re-apply ours on every appearance to keep the displayed results in sync with the query. // Re-apply ours on every appearance to keep the displayed results in sync with the query.
updateFilter(for: state.bindings.searchQuery, forced: true) updateFilter(for: state.bindings.searchQuery, forced: true)
case .submitQuery:
guard !state.bindings.searchQuery.isEmpty else { return }
recordBreadcrumb(.query(state.bindings.searchQuery))
case .selectRoom(let roomID): case .selectRoom(let roomID):
recordBreadcrumbs(roomID: roomID) recordBreadcrumb(.room(roomID: roomID))
actionsSubject.send(.presentRoom(roomID: roomID, eventID: nil)) actionsSubject.send(.presentRoom(roomID: roomID, eventID: nil))
case .selectMessage(let roomID, let eventID): case .selectMessage(let roomID, let eventID):
recordBreadcrumbs(roomID: roomID) recordBreadcrumb(.room(roomID: roomID))
actionsSubject.send(.presentRoom(roomID: roomID, eventID: eventID)) actionsSubject.send(.presentRoom(roomID: roomID, eventID: eventID))
case .reachedTop: case .reachedTop:
if state.bindings.searchMode == .rooms { if state.bindings.searchMode == .rooms {
@@ -210,16 +213,11 @@ class SearchScreenViewModel: SearchScreenViewModelType, SearchScreenViewModelPro
} }
} }
/// Stores the query that led to the selected result, along with the room it belongs to. /// Moves the breadcrumb to the top of the history, adding it if needed.
private func recordBreadcrumbs(roomID: String) { private func recordBreadcrumb(_ breadcrumb: SearchBreadcrumb) {
let searchQuery = state.bindings.searchQuery
let newBreadcrumbs: [SearchBreadcrumb] = searchQuery.isEmpty ? [.room(roomID: roomID)] : [.query(searchQuery), .room(roomID: roomID)]
var breadcrumbs = userSettings.searchBreadcrumbs var breadcrumbs = userSettings.searchBreadcrumbs
for breadcrumb in newBreadcrumbs { breadcrumbs.removeAll { $0 == breadcrumb }
breadcrumbs.removeAll { $0 == breadcrumb } breadcrumbs.insert(breadcrumb, at: 0)
breadcrumbs.insert(breadcrumb, at: 0)
}
userSettings.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount)) userSettings.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount))
} }
@@ -91,6 +91,7 @@ struct SearchScreen: View {
.scrollDismissesKeyboard(.immediately) .scrollDismissesKeyboard(.immediately)
.background(tabShortcuts) .background(tabShortcuts)
.onSubmit(of: .search) { .onSubmit(of: .search) {
context.send(viewAction: .submitQuery)
// A software keyboard's submit/search button just dismisses; only a hardware return selects. // A software keyboard's submit/search button just dismisses; only a hardware return selects.
if isHardwareKeyboardConnected, selectedID != nil { if isHardwareKeyboardConnected, selectedID != nil {
selectCurrent() selectCurrent()
@@ -127,16 +127,26 @@ struct SearchScreenViewModelTests {
@Test @Test
func selectionRecordsBreadcrumbs() { func selectionRecordsBreadcrumbs() {
// Typing alone doesn't record the query, only submitting it does.
context.searchQuery = "Second" context.searchQuery = "Second"
context.send(viewAction: .selectRoom(roomID: "2")) context.send(viewAction: .selectRoom(roomID: "2"))
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2")])
context.send(viewAction: .submitQuery)
#expect(userSettings.searchBreadcrumbs == [.query("Second"), .room(roomID: "2")])
// Selecting the same room again from the empty state only bumps it back to the top. // Selecting the same room again from the empty state only bumps it back to the top.
context.searchQuery = "" context.searchQuery = ""
context.send(viewAction: .selectRoom(roomID: "2")) context.send(viewAction: .selectRoom(roomID: "2"))
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")]) #expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
// Submitting an empty query records nothing.
context.send(viewAction: .submitQuery)
#expect(userSettings.searchBreadcrumbs == [.room(roomID: "2"), .query("Second")])
} }
@Test @Test