From 44798ef17aefba6283f4ae9045425c5116289efa Mon Sep 17 00:00:00 2001 From: Chris Bongers Date: Fri, 10 Jul 2026 14:40:03 +0200 Subject: [PATCH 1/2] feat(squads): add in-squad post search Lets members and public visitors filter a squad's feed by text query via a search field in the squad heading, mirroring the bookmarks in-feed search pattern. Submitting writes `?q=` to the URL (shallow) and swaps the feed to the new searchSourcePosts query; clearing restores the default sourceFeed. - Add SEARCH_SOURCE_POSTS_QUERY (mirrors SEARCH_BOOKMARKS_QUERY) and the search-squad feed page key - Add opt-in `enableSuggestions` prop to PostsSearch so this source-scoped surface doesn't show wrong-scope Mimir suggestions - Add opt-in `searchChildren` slot to SquadFeedHeading for the search field - Wire the squad page to swap query/variables/feedQueryKey/emptyScreen based on `router.query.q`, and build the submit/clear handlers from `asPath` instead of `router.pathname` (the latter is the dynamic route pattern `/squads/[handle]` and breaks Next's router.replace when the `handle` param isn't in the query object) --- .../shared/src/components/PostsSearch.tsx | 15 +- .../components/squads/SquadFeedHeading.tsx | 14 +- packages/shared/src/graphql/feed.ts | 24 +++ packages/shared/src/lib/query.ts | 1 + .../webapp/pages/squads/[handle]/index.tsx | 150 ++++++++++++++++-- 5 files changed, 189 insertions(+), 15 deletions(-) diff --git a/packages/shared/src/components/PostsSearch.tsx b/packages/shared/src/components/PostsSearch.tsx index 221f1464c25..01272e07ffc 100644 --- a/packages/shared/src/components/PostsSearch.tsx +++ b/packages/shared/src/components/PostsSearch.tsx @@ -35,6 +35,13 @@ export type PostsSearchProps = { }, ) => Promise; onClearQuery?: () => Promise; + /** + * Autocomplete suggestions (Mimir-backed) are not scoped to the current + * feed/source. Surfaces that search within a narrower scope (e.g. a single + * squad) should opt out rather than show suggestions from the wrong scope. + * Defaults to `true` to preserve existing behavior. + */ + enableSuggestions?: boolean; } & Pick, 'onFocus'>; const SEARCH_TYPES = { @@ -52,6 +59,7 @@ export default function PostsSearch({ suggestionType = 'searchPostSuggestions', onFocus, onClearQuery, + enableSuggestions = true, }: PostsSearchProps): ReactElement { const { time, contentCurationFilter } = useSearchContextProvider(); const searchBoxRef = useRef(); @@ -59,7 +67,10 @@ export default function PostsSearch({ const [items, setItems] = useState([]); const { value: searchVersion } = useConditionalFeature({ feature: feature.searchVersion, - shouldEvaluate: !!query && suggestionType === 'searchPostSuggestions', + shouldEvaluate: + !!query && + suggestionType === 'searchPostSuggestions' && + enableSuggestions, }); const SEARCH_URL = SEARCH_TYPES[suggestionType]; const purify = useDomPurify(); @@ -72,7 +83,7 @@ export default function PostsSearch({ queryKey: [suggestionType, query], queryFn: () => gqlClient.request(SEARCH_URL, { query, version: searchVersion }), - enabled: !!query, + enabled: !!query && enableSuggestions, staleTime: StaleTime.Default, }); diff --git a/packages/shared/src/components/squads/SquadFeedHeading.tsx b/packages/shared/src/components/squads/SquadFeedHeading.tsx index 0a3d60ebfd2..a0344accb95 100644 --- a/packages/shared/src/components/squads/SquadFeedHeading.tsx +++ b/packages/shared/src/components/squads/SquadFeedHeading.tsx @@ -1,4 +1,4 @@ -import type { ReactElement } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import React, { useContext, useMemo } from 'react'; import { Button, ButtonVariant } from '../buttons/Button'; import { PinIcon } from '../icons'; @@ -8,9 +8,14 @@ import { useSquadActions } from '../../hooks'; interface SquadFeedHeadingProps { squad: Squad; + /** Optional search field rendered at the start of the heading row. */ + searchChildren?: ReactNode; } -function SquadFeedHeading({ squad }: SquadFeedHeadingProps): ReactElement { +function SquadFeedHeading({ + squad, + searchChildren, +}: SquadFeedHeadingProps): ReactElement { const { items } = useContext(ActiveFeedContext); const { collapseSquadPinnedPosts, expandSquadPinnedPosts } = useSquadActions({ squad, @@ -38,6 +43,11 @@ function SquadFeedHeading({ squad }: SquadFeedHeadingProps): ReactElement { return (
+ {searchChildren && ( +
+ {searchChildren} +
+ )} {isSquadMember && (
); }