Skip to content

feat(search): revive unified search - #10639

Open
Rello wants to merge 1 commit into
masterfrom
feature/newSearch
Open

feat(search): revive unified search#10639
Rello wants to merge 1 commit into
masterfrom
feature/newSearch

Conversation

@Rello

@Rello Rello commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator
  • Modernize the account-scoped search window with provider, date, and people filters, connected-service results, and aggregate and provider detail views.
  • Introduce cancellable per-provider search state, stable reveal ordering, stale-response protection, pagination, scoped retries, and persistent keyboard selection.
  • Align the search UI with shared wizard styling and add coverage for the search models, QML behavior, people lookup, and image handling.

Server Reference: nextcloud/server#60241

Assisted-by: Codex:GPT-5

Bildschirmfoto 2026-08-21 um 11 21 09
Bildschirmaufnahme.2026-08-21.um.11.22.02.mov

@Rello Rello added this to the 35.0.0 milestone Aug 21, 2026
@Rello Rello self-assigned this Aug 21, 2026
@Rello Rello added the design Design, UI, UX, etc. label Aug 21, 2026
@Rello

Rello commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

@kra-mo as discussed

@kra-mo kra-mo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is already a fixed-height dialog, there is no reason to show a filter button in the initial state. Instead, the filters should just always be visible.

There should also be some indication that a search is happening through a circular progress indicator like on the web. Right now, the search button seems to disappear while you're searching?

And are you struggling to click some of the buttons in the screen recording? I guess that is known, then :)

@kra-mo

kra-mo commented Aug 21, 2026

Copy link
Copy Markdown
Member

Since this is already a fixed-height dialog, there is no reason to show a filter button in the initial state. Instead, the filters should just always be visible.

Alternatively, we could make it variable-height, like Spotlight, which might also be nice.

@Rello

Rello commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator Author

Since this is already a fixed-height dialog, there is no reason to show a filter button in the initial state. Instead, the filters should just always be visible.

Alternatively, we could make it variable-height, like Spotlight, which might also be nice.

Hi,
you mean type/date/people buttons should be there from the beginning, correct?
I would like to keep it fixed height because this is stable. trying with dynamic and max-heigt is not used yet. Would add some more complexity. now we have the standard for all

@kra-mo

kra-mo commented Aug 21, 2026

Copy link
Copy Markdown
Member

Hi,
you mean type/date/people buttons should be there from the beginning, correct?

Yes, instead of having a funnel button.

I would like to keep it fixed height because this is stable. trying with dynamic and max-heigt is not used yet. Would add some more complexity. now we have the standard for all

Sure, makes sense

@Rello

Rello commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator Author

@kra-mo should be fine now

Bildschirmaufnahme.2026-08-22.um.16.11.20.mov

@Rello
Rello force-pushed the feature/newSearch branch from 69c9e0a to 5cfeaea Compare August 22, 2026 14:13
@Rello
Rello marked this pull request as ready for review August 22, 2026 14:13

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5cfeaeaac6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +38 to +39
else if (event.key === Qt.Key_Home) moveSelection(UnifiedSearchResultsListModel.First)
else if (event.key === Qt.Key_End) moveSelection(UnifiedSearchResultsListModel.Last)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Home and End for editing the query

When the search field contains text, these handlers consume every Home/End key press (including Shift+Home/End) and mark the event accepted, so users can no longer move or extend the text cursor to the beginning or end of the query. Reserve these shortcuts for result navigation only under an explicit modifier or when focus is in the results list.

Useful? React with 👍 / 👎.

@Rello
Rello force-pushed the feature/newSearch branch 4 times, most recently from 860dcd2 to 7920816 Compare August 22, 2026 18:22

@kra-mo kra-mo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Rello the 🔍 icon still disappears and there is still no progress indication.

@Rello

Rello commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

@Rello the 🔍 icon still disappears and there is still no progress indication.

Ah. I think it tries to put the progress indicator where the search icon is.
question is where to put it? always at the bottom of all results? ...because of the "load more" for example...

@kra-mo

kra-mo commented Aug 25, 2026

Copy link
Copy Markdown
Member

Where it is on the web. At the end of the bar, just before the X button:

image

@Rello

Rello commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

Where it is on the web. At the end of the bar, just before the X button:

Hi,
final approval like this?

Bildschirmfoto 2026-08-25 um 13 40 29

@kra-mo kra-mo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. It's an improvement over what there is atm.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08df11544b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

auto *const job = new JsonApiJob(account, QStringLiteral("ocs/v2.php/apps/files_sharing/api/v1/sharees"));
QUrlQuery query;
query.addQueryItem(QStringLiteral("search"), _searchTerm);
query.addQueryItem(QStringLiteral("shareType"), QStringLiteral("0"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Send itemType with people lookups

For every non-empty People query, this bypasses the repository's OcsShareeJob::getSharees contract and omits the itemType parameter that the existing wrapper always adds and ShareeModel::fetch() supplies as either file or folder. Servers that enforce that Sharee API parameter will reject the request, leaving the new People picker in its error state; add an appropriate itemType and make the fake endpoint test reject requests that omit it so the real failure path is covered.

AGENTS.md reference: AGENTS.md:L33-L35

Useful? React with 👍 / 👎.

@Rello

Rello commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved the merge conflicts and pushed the merge commit in 5d490ea.

Comment thread src/gui/search/unifiedsearchpeoplemodel.cpp Outdated
Comment thread src/gui/search/unifiedsearchpeoplemodel.cpp Outdated
Comment thread src/gui/search/unifiedsearchpeoplemodel.h Outdated
Comment thread src/gui/search/unifiedsearchresultslistmodel.h Outdated
Comment thread src/gui/search/unifiedsearchresultslistmodel.cpp Outdated
Comment thread src/gui/search/unifiedsearchresultslistmodel.cpp Outdated
Comment thread src/gui/search/unifiedsearchresultslistmodel.cpp Outdated
Comment thread src/gui/wizard/qml/WizardMenuItem.qml Outdated
Comment thread src/gui/wizard/qml/WizardButton.qml Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cefa5a12d7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


QVariant UnifiedSearchResultsListModel::data(const QModelIndex &index, int role) const
{
Q_ASSERT(!checkIndex(index, QAbstractItemModel::CheckIndexOption::IndexIsValid));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the negation from the model-index assertion

In builds with assertions enabled, every valid QML/model lookup aborts here: checkIndex(...IndexIsValid) returns true for a valid index, and the newly added ! therefore makes Q_ASSERT fail as soon as search results are displayed. The same inverted assertion was also introduced in UnifiedSearchPeopleModel::data() at src/gui/search/unifiedsearchpeoplemodel.cpp:35, so opening populated people results has the same failure mode.

Useful? React with 👍 / 👎.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
78.4% Coverage on New Code (required ≥ 80%)
99 New Code Smells (required ≤ 0)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Comment thread src/gui/activity/qml/ActivityItem.qml Outdated
id: activityContent

adaptiveTextColor: root.activeFocus ? palette.highlightedText : palette.text
adaptiveTextColor: palette.text

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to unified search changes?

Comment thread src/gui/activity/qml/ActivityList.qml Outdated
id: activityHover
anchors.fill: activityList.currentItem
color: palette.highlight
color: Style.listItemHoverBackground

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this related to unified search changes?

Comment thread src/gui/search/SearchWindow.qml Outdated
Comment on lines +116 to +156
Item {
id: detailHeader

objectName: "searchDetailHeader"
Layout.fillWidth: true
Layout.preferredHeight: 40
visible: root.searchModel && !root.aggregateView

ToolButton {
id: backButton

objectName: "searchDetailBackButton"
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
text: qsTr("Back")
icon.source: "image://svgimage-custom-color/"
+ (root.LayoutMirroring.enabled ? "arrow-right.svg/" : "arrow-left.svg/")
+ Style.wizardPrimaryText
icon.width: Style.smallIconSize
icon.height: Style.smallIconSize
display: AbstractButton.TextBesideIcon
Accessible.name: qsTr("Back to all search results")
onClicked: {
root.searchModel.closeProviderDetail()
root.focusSearchInput()
}
}
EnforcedPlainTextLabel {
objectName: "searchDetailProviderTitle"
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: backButton.width + Style.smallSpacing
anchors.rightMargin: backButton.width + Style.smallSpacing
anchors.verticalCenter: parent.verticalCenter
text: root.searchModel ? root.searchModel.detailProviderName : ""
font.bold: true
font.pixelSize: Style.wizardHeaderTitleFontPixelSize
elide: Text.ElideRight
horizontalAlignment: Text.AlignHCenter
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is large enough that it should live in its own file

Comment thread src/gui/search/SearchWindow.qml Outdated
Comment on lines +158 to +254
Flow {
id: filterFlow
objectName: "categoryFilterFlow"
Layout.fillWidth: true
Layout.preferredHeight: visible ? childrenRect.height : 0
visible: root.filtersVisible
spacing: Style.smallSpacing

WizardButton {
id: typeFilterButton

objectName: "typeFilterButton"
width: Math.max(140, (filterFlow.width - 2 * filterFlow.spacing) / 3)
text: qsTr("Type")
trailingIconSource: "image://svgimage-custom-color/caret-down.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
iconBeforeText: true
iconSource: "image://svgimage-custom-color/folder.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
primary: root.hasActiveFilter("provider")
Accessible.name: qsTr("Filter by type")
onClicked: typeMenu.toggle()
WizardMenu {
id: typeMenu
objectName: "typeFilterMenu"
anchorItem: typeFilterButton
width: anchorItem.width * 1.5
Repeater {
model: root.searchModel ? root.searchModel.providers : []
delegate: WizardMenuItem {
required property var modelData
text: (modelData.selected ? "✓ " : "") + modelData.name
icon.source: modelData.icon ? "image://tray-image-provider/" + modelData.icon : ""
tintIcon: true
iconTintColor: Style.wizardPrimaryText
onTriggered: root.searchModel.toggleProviderFilter(modelData.id)
}
}
}
}
onClearText: {
if (root.searchModel) {
root.searchModel.searchTerm = ""
WizardButton {
id: dateFilterButton

objectName: "dateFilterButton"
width: Math.max(140, (filterFlow.width - 2 * filterFlow.spacing) / 3)
text: qsTr("Date")
trailingIconSource: "image://svgimage-custom-color/caret-down.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
iconBeforeText: true
iconSource: "image://svgimage-custom-color/calendar.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
primary: root.hasActiveFilter("date")
enabled: root.searchModel && root.searchModel.dateFilterAvailable
Accessible.name: qsTr("Filter by date")
Accessible.description: enabled ? "" : qsTr("No search source supports date filtering")
onClicked: dateMenu.toggle()
WizardMenu {
id: dateMenu
objectName: "dateFilterMenu"
anchorItem: dateFilterButton
WizardMenuItem {
objectName: "dateTodayMenuItem"
text: qsTr("Today")
onTriggered: root.searchModel.setDatePreset("today")
}
WizardMenuItem { text: qsTr("Last 7 days"); onTriggered: root.searchModel.setDatePreset("last7days") }
WizardMenuItem { text: qsTr("Last 30 days"); onTriggered: root.searchModel.setDatePreset("last30days") }
WizardMenuItem { text: qsTr("This year"); onTriggered: root.searchModel.setDatePreset("thisyear") }
WizardMenuItem { text: qsTr("Last year"); onTriggered: root.searchModel.setDatePreset("lastyear") }
MenuSeparator {}
WizardMenuItem {
text: qsTr("Custom range …")
onTriggered: {
customRangeDialog.validationError = false
customRangeDialog.open()
}
}
WizardMenuItem { text: qsTr("Clear date"); onTriggered: root.searchModel.clearDateFilter() }
}
}
WizardButton {
id: peopleButton
objectName: "peopleFilterButton"
width: Math.max(140, (filterFlow.width - 2 * filterFlow.spacing) / 3)
text: qsTr("People")
trailingIconSource: "image://svgimage-custom-color/caret-down.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
iconBeforeText: true
iconSource: "image://svgimage-custom-color/account-group.svg/"
+ (primary ? Style.wizardSelectedText : Style.wizardPrimaryText)
primary: root.hasActiveFilter("person")
enabled: root.searchModel && root.searchModel.peopleFilterAvailable
Accessible.name: qsTr("Filter by person")
Accessible.description: enabled ? "" : qsTr("No search source supports people filtering")
onClicked: peoplePopup.open()
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same her

Comment on lines +333 to 384
delegate: Item {
id: delegateData

required property string providerName
required property string providerId
required property string providerIcon
required property string resultTitle
required property string subline
required property url resourceUrlRole
required property string darkIcons
required property string lightIcons
required property bool darkIconsIsThumbnail
required property bool lightIconsIsThumbnail
required property string darkImagePlaceholder
required property string lightImagePlaceholder
required property bool isRounded
required property int type
required property bool isSelected
required property bool isPartialMatch
required property bool hasOverflow
required property bool isLoading

readonly property alias loadedItem: resultDelegate.loadedItem

width: resultsList.width
height: resultDelegate.implicitHeight

UnifiedSearchResultDelegate {
id: resultDelegate

anchors.fill: parent
searchModel: root.searchModel
providerName: delegateData.providerName
providerId: delegateData.providerId
providerIcon: delegateData.providerIcon
resultTitle: delegateData.resultTitle
subline: delegateData.subline
resourceUrlRole: delegateData.resourceUrlRole
darkIcons: delegateData.darkIcons
lightIcons: delegateData.lightIcons
darkIconsIsThumbnail: delegateData.darkIconsIsThumbnail
lightIconsIsThumbnail: delegateData.lightIconsIsThumbnail
darkImagePlaceholder: delegateData.darkImagePlaceholder
lightImagePlaceholder: delegateData.lightImagePlaceholder
isRounded: delegateData.isRounded
resultType: delegateData.type
isSelected: delegateData.isSelected
isPartialMatch: delegateData.isPartialMatch
hasOverflow: delegateData.hasOverflow
isLoading: delegateData.isLoading
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we having a delegate within a delegate?

Comment thread src/gui/search/SearchWindow.qml Outdated
Comment on lines +426 to +540
Popup {
id: peoplePopup
parent: Overlay.overlay
width: Math.min(root.width - 40, 420)
height: 340
x: (root.width - width) / 2
y: 150
modal: true
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onOpened: peopleSearch.forceActiveFocus()
ColumnLayout {
anchors.fill: parent
anchors.margins: Style.smallSpacing
TextField {
id: peopleSearch
Layout.fillWidth: true
placeholderText: qsTr("Search people")
onTextEdited: peopleModel.searchTerm = text
}
EnforcedPlainTextLabel { visible: peopleModel.errorString.length > 0; text: peopleModel.errorString; wrapMode: Text.Wrap }
WizardButton {
visible: peopleModel.errorString.length > 0
text: qsTr("Retry")
onClicked: peopleModel.retry()
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
model: peopleModel
delegate: ItemDelegate {
id: personDelegate
required property string userId
required property string displayName
required property string avatarUrl
width: ListView.view.width
height: 44
text: displayName
hoverEnabled: true
Accessible.description: userId
background: Rectangle {
color: personDelegate.hovered || personDelegate.down
? Style.listItemHoverBackground
: "transparent"
radius: Style.mediumRoundedButtonRadius
}
HoverHandler {
cursorShape: Qt.PointingHandCursor
}
contentItem: RowLayout {
Image {
Layout.preferredWidth: 32
Layout.preferredHeight: 32
sourceSize.width: 32
sourceSize.height: 32
asynchronous: true
source: personDelegate.avatarUrl.length > 0
? "image://tray-image-provider/" + personDelegate.avatarUrl : ""
Accessible.ignored: true
}
EnforcedPlainTextLabel { Layout.fillWidth: true; text: personDelegate.displayName; elide: Text.ElideRight }
}
onClicked: {
root.searchModel.setPersonFilter(userId, displayName, avatarUrl)
peoplePopup.close()
root.focusSearchInput()
}
}
}
}
}

Dialog {
id: customRangeDialog
property bool validationError: false
anchors.centerIn: parent
title: qsTr("Custom date range")
modal: true

footer: RowLayout {
spacing: Style.wizardFooterSpacing

Item {
Layout.fillWidth: true
}

WizardButton {
text: qsTr("Cancel")
onClicked: customRangeDialog.close()
}

WizardButton {
primary: true
text: qsTr("Apply")
onClicked: {
customRangeDialog.validationError = !root.searchModel.setCustomDateRange(customSince.text, customUntil.text)
if (!customRangeDialog.validationError) {
customRangeDialog.close()
}
}
}
}

ColumnLayout {
EnforcedPlainTextLabel { text: qsTr("Start date (YYYY-MM-DD)") }
TextField { id: customSince; Layout.fillWidth: true; placeholderText: "YYYY-MM-DD" }
EnforcedPlainTextLabel { text: qsTr("End date (YYYY-MM-DD)") }
TextField { id: customUntil; Layout.fillWidth: true; placeholderText: "YYYY-MM-DD" }
EnforcedPlainTextLabel {
visible: customRangeDialog.validationError
text: qsTr("Enter valid dates with the start date before the end date.")
color: palette.accent
}
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These dialogs and popups should be moved into their own files too

Comment on lines +37 to +42
switch (role) {
case UserIdRole: return person.id;
case DisplayNameRole: return person.displayName;
case AvatarUrlRole: return person.avatarUrl;
}
return {};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is clang-format happy with this?

Comment thread src/gui/wizard/qml/WizardButton.qml Outdated
implicitWidth: contentRow.implicitWidth
implicitHeight: contentRow.implicitHeight
contentItem: RowLayout {
spacing: 6

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please no magic numbers

Comment thread src/gui/wizard/qml/WizardButton.qml Outdated
Comment on lines +75 to +77
color: root.enabled
? (root.primary ? Style.wizardSelectedText : root.palette.buttonText)
: Style.wizardDisabledText

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ternary chains are readability nightmares, let's just use a conventional if/else

Comment thread src/gui/wizard/qml/WizardChipButton.qml Outdated
Comment on lines +10 to +11
leftPadding: Style.standardSpacing
rightPadding: Style.standardSpacing

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really want to hardcode padding into a button type?

Comment thread src/gui/wizard/qml/WizardMenuItem.qml Outdated
Comment on lines +21 to +22
leftPadding: 12
rightPadding: 12

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No magic numbers please

Comment thread src/gui/systray.cpp Outdated
}

auto *const searchModel = new UnifiedSearchResultsListModel(accountState.data(), accountState.data());
auto *const searchModel = new UnifiedSearchResultsListModel(accountState.data());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto *const searchModel = new UnifiedSearchResultsListModel(accountState.data());
const auto searchModel = new UnifiedSearchResultsListModel(accountState.data());

@Rello
Rello force-pushed the feature/newSearch branch from e5e8dda to 8fbc4d6 Compare August 29, 2026 08:58
Modernize the account-scoped search window with provider, date, and people filters, connected-service results, and aggregate and provider detail views.

Introduce per-provider search state, pagination, scoped retries, keyboard selection, shared wizard styling, and model and QML coverage.

Co-authored-by: Matthieu Gallien <matthieu.gallien@nextcloud.com>
Signed-off-by: Rello <github@scherello.de>
Assisted-by: Codex:GPT-5
@Rello
Rello force-pushed the feature/newSearch branch from 8fbc4d6 to 717974c Compare August 29, 2026 10:46

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 717974c845

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

{
const auto encodedUserId = QString::fromUtf8(QUrl::toPercentEncoding(userId));
const auto avatarPath = QStringLiteral("index.php/avatar/%1/64").arg(encodedUserId);
return account->url().resolved(QUrl(avatarPath)).toString();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Build avatar URLs without replacing the server webroot

For accounts hosted below the domain root whose URL lacks a trailing slash, such as https://host/nextcloud, QUrl::resolved() treats nextcloud as a file and produces https://host/index.php/avatar/.... Consequently, every person avatar fails to load on these deployments; use the existing Utility::concatUrlPath() helper and add a subdirectory-account regression case.

AGENTS.md reference: AGENTS.md:L33-L35

Useful? React with 👍 / 👎.

QVector<UnifiedSearchResult> projection;
if (_viewMode == ViewMode::ProviderDetail && _providers.contains(_detailProviderId)) {
const auto &provider = _providers[_detailProviderId];
projection = provider.entries;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve partial-match state in provider detail

When a date or person filter is active and a provider does not support it, the aggregate projection marks that provider's rows as partial, but entering its detail view copies the raw entries whose _isPartialMatch remains false. Because the filter controls and partial-match header are also hidden in detail view, the unfiltered results are presented as ordinary matches; carry the provider's partial state into this projection or retain an equivalent warning.

Useful? React with 👍 / 👎.

Comment on lines +114 to +116
onPressed: {
if (root.hasOverflow) {
root.searchModel.openProviderDetail(root.providerId)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Activate result controls only after a completed click

On touch input, a drag beginning on an overflow provider header emits pressed before the ListView can take over the gesture, so attempting to scroll immediately opens the provider detail even if the press is subsequently canceled. The paging row has the same issue and can start a network request during scrolling; handle these actions with onClicked so canceled presses do not activate them.

Useful? React with 👍 / 👎.

@github-actions

Copy link
Copy Markdown
Contributor

Artifact containing the AppImage: nextcloud-appimage-pr-10639.zip

Digest: sha256:29d878f7474bc0283a6ce020ff11cfc8d908c0342e7473f92bd35d45291a3ec4

To test this change/fix you can download the above artifact file, unzip it, and run it.

Please make sure to quit your existing Nextcloud app and backup your data.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

design Design, UI, UX, etc.

Projects

Status: NC35

Development

Successfully merging this pull request may close these issues.

5 participants