From 313365ad835ae7f14a651dc6c0f9c637689f2727 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 29 Sep 2026 14:53:02 +0300 Subject: [PATCH] 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. --- .../SearchScreen/SearchScreenModels.swift | 1 + .../SearchScreen/SearchScreenViewModel.swift | 20 +++++++++---------- .../SearchScreen/View/SearchScreen.swift | 1 + .../Sources/SearchScreenViewModelTests.swift | 12 ++++++++++- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/ElementX/Sources/Screens/SearchScreen/SearchScreenModels.swift b/ElementX/Sources/Screens/SearchScreen/SearchScreenModels.swift index a657ba111..fe17a27c7 100644 --- a/ElementX/Sources/Screens/SearchScreen/SearchScreenModels.swift +++ b/ElementX/Sources/Screens/SearchScreen/SearchScreenModels.swift @@ -48,6 +48,7 @@ struct SearchScreenViewStateBindings { enum SearchScreenViewAction { case appeared + case submitQuery case selectRoom(roomID: String) case selectMessage(roomID: String, eventID: String) case reachedTop diff --git a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift index 7a3dd15d2..e022e7e32 100644 --- a/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift +++ b/ElementX/Sources/Screens/SearchScreen/SearchScreenViewModel.swift @@ -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. // Re-apply ours on every appearance to keep the displayed results in sync with the query. 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): - recordBreadcrumbs(roomID: roomID) + recordBreadcrumb(.room(roomID: roomID)) actionsSubject.send(.presentRoom(roomID: roomID, eventID: nil)) case .selectMessage(let roomID, let eventID): - recordBreadcrumbs(roomID: roomID) + recordBreadcrumb(.room(roomID: roomID)) actionsSubject.send(.presentRoom(roomID: roomID, eventID: eventID)) case .reachedTop: 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. - private func recordBreadcrumbs(roomID: String) { - let searchQuery = state.bindings.searchQuery - let newBreadcrumbs: [SearchBreadcrumb] = searchQuery.isEmpty ? [.room(roomID: roomID)] : [.query(searchQuery), .room(roomID: roomID)] - + /// Moves the breadcrumb to the top of the history, adding it if needed. + private func recordBreadcrumb(_ breadcrumb: SearchBreadcrumb) { var breadcrumbs = userSettings.searchBreadcrumbs - for breadcrumb in newBreadcrumbs { - breadcrumbs.removeAll { $0 == breadcrumb } - breadcrumbs.insert(breadcrumb, at: 0) - } + breadcrumbs.removeAll { $0 == breadcrumb } + breadcrumbs.insert(breadcrumb, at: 0) userSettings.searchBreadcrumbs = Array(breadcrumbs.prefix(Self.maximumBreadcrumbCount)) } diff --git a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift index 523bdb446..8c14cf989 100644 --- a/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift +++ b/ElementX/Sources/Screens/SearchScreen/View/SearchScreen.swift @@ -91,6 +91,7 @@ struct SearchScreen: View { .scrollDismissesKeyboard(.immediately) .background(tabShortcuts) .onSubmit(of: .search) { + context.send(viewAction: .submitQuery) // A software keyboard's submit/search button just dismisses; only a hardware return selects. if isHardwareKeyboardConnected, selectedID != nil { selectCurrent() diff --git a/UnitTests/Sources/SearchScreenViewModelTests.swift b/UnitTests/Sources/SearchScreenViewModelTests.swift index 671dc7508..22c2230f0 100644 --- a/UnitTests/Sources/SearchScreenViewModelTests.swift +++ b/UnitTests/Sources/SearchScreenViewModelTests.swift @@ -127,16 +127,26 @@ struct SearchScreenViewModelTests { @Test func selectionRecordsBreadcrumbs() { + // Typing alone doesn't record the query, only submitting it does. context.searchQuery = "Second" 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. context.searchQuery = "" context.send(viewAction: .selectRoom(roomID: "2")) #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