From cfb18f16d409a2bc925660fcfdc3340c98a5a8dc Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 06:53:37 +0200 Subject: [PATCH] fix(app): find starting refs by change request descriptions (#6558) Preserve returned change request bodies as search-only option text so client filtering keeps description matches. Keep title and ID matches ranked ahead of secondary text. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- .../components/ui/combobox-options.test.ts | 13 ++++++ .../app/src/components/ui/combobox-options.ts | 14 ++++-- .../screens/new-workspace-picker-item.test.ts | 46 +++++++++++++++++++ .../src/screens/new-workspace-picker-item.ts | 13 +++++- 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/packages/app/src/components/ui/combobox-options.test.ts b/packages/app/src/components/ui/combobox-options.test.ts index 6129af9463..e3e98633b3 100644 --- a/packages/app/src/components/ui/combobox-options.test.ts +++ b/packages/app/src/components/ui/combobox-options.test.ts @@ -160,3 +160,16 @@ describe("combobox above-search ordering", () => { expect(getComboboxFallbackIndex(ordered.length, "below-search")).toBe(0); }); }); + +describe("search-only option text", () => { + it("finds hidden search text while keeping title matches first", () => { + const bodyMatch = { id: "pr:7", label: "Fix parsing", searchText: "Release notes for 2024" }; + const titleMatch = { id: "pr:8", label: "2024 release" }; + expect(filterAndRankComboboxOptions([bodyMatch, titleMatch], "2024")).toEqual([ + titleMatch, + bodyMatch, + ]); + expect(filterAndRankComboboxOptions([bodyMatch], "unrelated")).toEqual([]); + expect(filterAndRankComboboxOptions([bodyMatch], "parsing 2024")).toEqual([bodyMatch]); + }); +}); diff --git a/packages/app/src/components/ui/combobox-options.ts b/packages/app/src/components/ui/combobox-options.ts index e23f2315ef..006eb1a21e 100644 --- a/packages/app/src/components/ui/combobox-options.ts +++ b/packages/app/src/components/ui/combobox-options.ts @@ -10,18 +10,22 @@ export interface ComboboxOptionModel { id: string; label: string; description?: string; + /** Additional text to match without displaying it in the option row. */ + searchText?: string; kind?: ComboboxOptionKind; } -const DESCRIPTION_FALLBACK_TIER = 99; +const SECONDARY_TEXT_FALLBACK_TIER = 99; function scoreOption(opt: ComboboxOptionModel, search: string): MatchScore | null { const best = scoreTextFields(search, [opt.label, opt.id]); if (best) return best; - if (!opt.description) return null; - const descriptionScore = scoreTextFields(search, [opt.description]); - if (!descriptionScore) return null; - return { ...descriptionScore, tier: descriptionScore.tier + DESCRIPTION_FALLBACK_TIER }; + const secondaryFields = opt.searchText + ? [opt.label, opt.id, opt.description ?? "", opt.searchText] + : [opt.description ?? ""]; + const secondaryScore = scoreTextFields(search, secondaryFields); + if (!secondaryScore) return null; + return { ...secondaryScore, tier: secondaryScore.tier + SECONDARY_TEXT_FALLBACK_TIER }; } export interface BuildVisibleComboboxOptionsInput { diff --git a/packages/app/src/screens/new-workspace-picker-item.test.ts b/packages/app/src/screens/new-workspace-picker-item.test.ts index 739738e0f9..a6c1e81845 100644 --- a/packages/app/src/screens/new-workspace-picker-item.test.ts +++ b/packages/app/src/screens/new-workspace-picker-item.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from "vitest"; +import { buildVisibleComboboxOptions } from "@/components/ui/combobox-options"; import type { ForgeSearchItem } from "@getpaseo/protocol/messages"; import { type BranchPickerDetail, @@ -340,3 +341,48 @@ describe("defaultBasePickerItem", () => { expect(defaultBasePickerItem({ currentBranch: null })).toBeNull(); }); }); + +describe("starting-ref search", () => { + it.each(["gitlab", "github"] as const)( + "keeps a %s change request returned for a body-only match", + (forge) => { + const item: ForgeSearchItem = { + ...prItem, + forge, + number: 3896, + title: "fix(config): send environment credentials only to trusted hosts", + body: "This mirrors the fix shipped for CVE-2024-53858.", + headRefName: "8545-duo-test", + }; + const { options } = buildPickerOptionData({ + branchDetails: [], + prItems: [item], + baseItem: null, + }); + const visible = buildVisibleComboboxOptions({ + options, + searchQuery: "2024", + searchable: true, + allowCustomValue: false, + customValuePrefix: "Use", + }); + expect(visible.map((option) => option.label)).toEqual([ + `${forge === "gitlab" ? "!" : "#"}3896 fix(config): send environment credentials only to trusted hosts`, + ]); + const selected = buildPickerOptionData({ + branchDetails: [], + prItems: [], + baseItem: { kind: "github-pr", item }, + }); + expect( + buildVisibleComboboxOptions({ + options: selected.options, + searchQuery: "2024", + searchable: true, + allowCustomValue: false, + customValuePrefix: "Use", + }), + ).toEqual(visible); + }, + ); +}); diff --git a/packages/app/src/screens/new-workspace-picker-item.ts b/packages/app/src/screens/new-workspace-picker-item.ts index 0cb0680605..d02c7a0bf2 100644 --- a/packages/app/src/screens/new-workspace-picker-item.ts +++ b/packages/app/src/screens/new-workspace-picker-item.ts @@ -264,7 +264,10 @@ export function buildPickerOptionData(input: { itemById.set(id, { kind: "github-pr", item: pr }); const updatedAtMs = pr.updatedAt ? Date.parse(pr.updatedAt) : 0; const timestamp = Number.isNaN(updatedAtMs) ? 0 : Math.floor(updatedAtMs / 1000); - timedOptions.push({ option: { id, label: formatPrLabel(pr) }, timestamp }); + timedOptions.push({ + option: { id, label: formatPrLabel(pr), searchText: pr.body ?? undefined }, + timestamp, + }); } const baseItem = input.baseItem; @@ -279,7 +282,13 @@ export function buildPickerOptionData(input: { const labeledItem = disambiguate(baseItem, timedOptions); itemById.set(selectedOptionId, labeledItem); timedOptions.push({ - option: { id: selectedOptionId, label: pickerItemLabel(labeledItem) }, + option: { + id: selectedOptionId, + label: pickerItemLabel(labeledItem), + ...(labeledItem.kind === "github-pr" + ? { searchText: labeledItem.item.body ?? undefined } + : {}), + }, // The base sorts first: it is what a workspace is created from unless you pick something. timestamp: Number.POSITIVE_INFINITY, });