Fix #276: explore search results sections, landing page centering and search reset - #335
Conversation
…jects Two regressions on the signed-out landing page. 1. The logo, heading and search bar were no longer vertically centered. They were centered with "height: 100%" on the landing <main>, which only works when every ancestor has a definite height. #316 relaxed "body" from "height: 100%" to "min-height: 100%" so that pages taller than the viewport stop pushing the footer out of view (#242). With an indefinite body height, "height: 100%" on <main> resolves to "auto", the element shrinks to its content, "justify-content: center" has no free space left to distribute, and the block ends up stuck directly under the navbar. Replace the percentage height with flexbox, the same way the auth pages were fixed in #316: ".full.height" already grows to fill the viewport via "flex: 1 0 auto", so making it a flex column and giving the landing "flex: 1 0 auto" hands <main> a real height to center inside. "flex-shrink: 0" keeps the old top-aligned behavior on viewports too short for the content, so the page scrolls instead of clipping the logo. The 64px "--page-space-bottom" that ".full.height" reserves is dropped here: with one centered block it is dead space that pulls the content 32px above the true center. Verified at 1456x836 against the running instance: the gap above and below the block is 187px each (was 0px / 373px) and the page still does not overflow the viewport. 2. The landing search submitted to "/explore/articles" while the search on /explore submits to "/explore/subjects" -- the tab the explore navbar defaults to, and the one "/explore" itself redirects to. So the same query produced a different page depending on where it was typed, and the landing one arrived on a page whose navbar shows no active tab and which renders no "Search results for ..." section (#276 cases 1 and 2). Point the form at "/explore/subjects" and drop the three hidden inputs ("only_show_relevant", "sort=score", "fork=0"): they are repo-search parameters that explore.Subjects does not read ("sort=score" is not a valid subject sort type and silently falls back to "recentupdate"). Removing them makes a landing search produce exactly the "?q=..." URL that the explore subject search produces. Case 3 of the issue -- no "Search results for ..." / "Similar" sections on the /explore/users tab -- is a separate gap in the users template and is not addressed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Case 3 of the issue: searching on /explore/users returned a bare list (or a
bare "No matching results found."), while the subjects tab splits the same
search into the subject the keyword names and a "Similar" section below it.
The users tab now does the same, so a search reads the same way on both tabs.
models: add SearchUserOptions.ExactMatchOnly, mirroring
repo.FindSubjectsOptions.ExactMatchOnly. It swaps the keyword's LIKE for an Eq
on both lower_name and LOWER(full_name), so "user1" stops dragging in
user10..user18. The email condition is deliberately left as a substring match:
an email is never the thing a visitor typed the name of.
routers: splitExactUserMatch runs that exact lookup as its own query rather
than picking the match out of the current page. The list is paginated and
ordered by name or sign-up date, not relevance, so the user named exactly like
the keyword can sit on any page -- someone searching "anastasia" should find
her on the first one. Reusing the page's own options makes the lookup inherit
every filter already applied (user type, active, visibility to the viewer,
repo role), so it cannot surface a user the paginated list would have hidden.
When the exact match also falls on the current page it is dropped from the
"Similar" list so it is not rendered twice; the pagination total is left alone
so page boundaries stay stable while browsing.
templates: custom/templates/explore/user_list.tmpl overrides the stock list
with the three sections. The row markup moves to
custom/templates/shared/user/explore_item.tmpl so all sections render a user
identically. It takes a dict rather than a bare user because Go rebinds "$"
to whatever each {{template}} call passes, so a sub-template cannot reach the
page data the way the markup did while inlined.
When there is neither an exact match nor a similar user, only "No matching
results found." is shown -- no dangling "no user named exactly" note above it.
templates/admin/user/view.tmpl shares this template but sets no search data, so
HasSearchKeyword is false there and it renders the plain list exactly as before.
Verified against a throwaway instance seeded from the dev database:
- "pierre" -> exact "pierre" + Similar "pierre_user"
- "PIERRE" -> same (case insensitive)
- "Ada Lorne" -> exact match on full name, no Similar section
- "bubble" -> no exact match note + 11 similar
- "anastasia" -> heading + "No matching results found." only
- no keyword -> unchanged plain list
TestSearchUsers covers ExactMatchOnly on username, full name, case folding, and
a substring-only keyword matching nobody.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The bordered box on /explore/users comes from ".explore-users-list-container", which wraps everything the list template emits. Once the "Search results for ..." heading, the "Similar" label and the no-exact-match note were added inside that template, the border swallowed them too. Move the box (border, radius, overflow, background) off the outer container and onto a ".explore-users-rows" wrapper that only ever holds user rows. The headings and the note become siblings of those wrappers, so they sit outside the frame. A search now renders two boxes, the exact match and the similar list, each framed on its own with the heading above it. "flex-list" moves onto the same element as the rows rather than staying on an outer wrapper: the row separator rule is ".flex-list > .flex-item + .flex-item" (web_src/css/shared/flex-list.css:80), a direct-child selector, so the rows have to be direct children of the element carrying the class or they lose their dividers. The ".flex-item" padding rules are rescoped to ".explore-users-rows" for the same reason -- ":first-child" / ":last-child" now resolve within each box. The empty-search result keeps its border: "No matching results found." was framed before this feature existed, and it stands in for the rows rather than labelling them. Verified on a throwaway instance that every box contains only ".flex-item" children, with the headings as siblings outside: - "pierre" -> heading, box(1 row), "Similar", box(1 row) - "bubble" -> heading, note, "Similar", box(11 rows) - "anastasia" -> heading, box(1 row: no matching results) - no keyword -> single box of 16 rows, unchanged Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Emptying the keyword field left the results on screen next to an empty search box. Clicking the field's own clear button -- the "x" that <input type="search"> renders -- did nothing at all: it produces an "input" event indistinguishable from typing, and no "change" until the field is left, so no handler noticed. Clearing by keyboard and pressing Enter did reload the list, but submitted "q=" and landed on "?q=&sort=alphabetically": the right page behind a URL that says it is searching for nothing. Reset the page on both gestures: - listen for "search", which Blink and WebKit fire when the clear button or Escape empties the field. It bubbles, so one listener per form is enough (verified). - intercept "submit" while the field is empty. This is the path Enter and the search button take, and the only one Firefox has: it renders no clear button and does not implement the "search" event. Both only intervene while the field is empty -- a real keyword still submits natively -- and both drop only "q". Sort and filters are separate controls and survive: clearing the search clears the search, not the way results are arranged. Dropping an empty "q" moved into buildSearchParams, shared with the existing filter and sort handler, so those navigations stop carrying a stray "q=" too. Deliberately not hooked: "input" turning empty. Retyping a query starts by deleting the old one (this is step 2 of the issue's own repro), and navigating away mid-edit on the last backspace would make the field unusable. Only the explicit clear gestures reset; typing still just updates the tab links, so the keyword carry-over from #273 is untouched. Verified on a throwaway instance, users and subjects tabs: - clear button -> "?sort=..." , no "q", full list, search heading gone - clear + Enter -> same - real keyword -> still submits natively ("?q=bubble&sort=alphabetically") - type then clear without submitting -> no navigation; tab links gain then lose "q" - changing sort -> still navigates, now without a stray empty "q=" Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
/review AI review started. |
Advanced AI Review
Click to expand reviewReviewI read the full files touched by the diff ( 1. 🟡 The split (and its user-specific wording) also renders on the
|
The organizations page does not just share the handler, it shares the template:
explore.Organizations calls RenderUserSearch with tplExploreUsers, and
templates/explore/users.tmpl includes the overridden explore/user_list. The route
is registered and enabled by default; only the navbar tab is commented out. So
"GET /explore/organizations?q=acme" answered a search with "No user named exactly
'acme'." on a listing of organizations. Reproduced against a throwaway instance
with two seeded orgs. The claim to the contrary in the code comment, the template
comment and the PR description was simply wrong.
Gate the split on PageIsExploreUsers, so the organizations page falls back to the
plain list it rendered before and does not pay for the extra exact-match query
either. TestExploreOrganizationsKeepsPlainList pins it, and fails without the gate.
Also from the review:
- Return every exact match, not just the first. "lower_name" is unique but
"full_name" is not, so a name two accounts share used to promote one of them
arbitrarily and file its twin under "Similar" as though it were a near miss.
Note this is not the fix the review proposed: excluding all exact matches from
"Similar" while still rendering only exactMatches[0] would have dropped the twin
from the page entirely. Rendering all of them keeps every result visible.
Bounded at 50 by maxExactUserMatches.
- Derive both name columns from one matchName helper in toSearchQueryBase, so the
exact and fuzzy variants cannot drift apart.
- Drop the "{{if .Keyword}}" nested inside "{{if .HasSearchKeyword}}": the latter
is set to "Keyword != \"\"", so the inner test could never be false.
- Fold the ".flex-list { margin: 0 }" reset into the rows-box rule, now that both
classes are on the same element by design.
- Cover the rendered split in tests/integration/explore_user_test.go: exact user
promoted into its own box, "Similar" populated, the note for a substring-only
keyword, and no split without a keyword.
Not taken: merging "user.similar" into a shared "explore.similar". Per-context
locale keys are better for translators, not worse -- "Similar" can need different
agreement or gender for users and for subjects -- and collapsing them locks both
to one string.
The integration tests assert on the parsed document rather than resp.Body.String():
NewHTMLParser drains the buffer, so body assertions made after it read "" and pass
no matter what. Two of them did exactly that before this was caught.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Approved |
PR #335 (#276: explore search sections, landing centering, search reset) landed on master and touches the same two landing-page files as this branch. Both conflicts are additive; both sides are kept. custom/templates/home.tmpl -- most of the file merged on its own: the "home-landing" class on <main> and the removal of the three dead hidden inputs came from master, the combobox input id and aria attributes and the suggestions container from this branch. The one conflicting hunk was the wrapper and the opening form tag: - wrapper: took "home-search" over master's "dropdown-anchor". "home-search" carries "position: relative", which is what anchors the absolutely positioned dropdown; "dropdown-anchor" has no CSS and no JS anywhere and was dead markup. - form: kept this branch's "ignore-dirty" (and the comment explaining it), and took master's action of "/explore/subjects" over "/explore/articles". That change is the point of #276 cases 1 and 2, and it is what the suggestions already assume: home-search.ts queries /explore/subjects/suggestions and each suggestion links to /subject/..., so submitting the form now lands on the same kind of page. web_src/css/home.css -- both sides appended a block after ".home .logo" and shared the next closing brace. Master's landing-centering block is kept first, then this branch's search-suggestion styles. Verified on the merged tree against a throwaway instance: - the landing block is still vertically centered (gap 430px above and below, no viewport overflow) and <main> still has flex-grow 1, so #276 survives - ".home-search" resolves to position: relative and the suggestions box to absolute - typing "sub" returns 4 suggestions, the box unhides, aria-expanded flips to true, and the first entry links to /subject/Test%20Subject - form action is /explore/subjects and still carries ignore-dirty - go build, webpack, eslint and stylelint clean; home-search vitest passes; TestExploreSubjectSuggestions, TestExploreSubjectsListMarkup, TestExploreUserSearchSplit and TestExploreOrganizationsKeepsPlainList all pass
PR #335 (#276) landed on master and touches the same two landing-page files. Both conflicts are additive and both sides are kept. custom/templates/home.tmpl -- the only conflicting hunk was the opening form tag. Kept this branch's id="home-search-form" and "ignore-dirty", took master's action of "/explore/subjects" over "/explore/articles": that redirect is the point of #276 cases 1 and 2, and nothing here depends on the action, since home-search.ts only guards the submit. The rest merged on its own -- the "home-landing" class on <main> and the removal of the three dead hidden inputs from master, the error element from here. web_src/css/home.css -- both sides appended a block after ".home .logo" and shared the next closing brace. Master's landing-centering block first, then the search-validation styles. Verified on the merged tree against a throwaway instance: - the landing block is still centered (187px above and below, no overflow), so #276 survives - submitting blank stays on the landing page, shows "Please type a subject to search", sets aria-invalid and aria-describedby, and turns the border red (rgb(207, 34, 46)) - typing clears the message again, and a real search submits through to /explore/subjects?q=forest - go build, webpack, eslint and stylelint clean; home-search vitest (3 tests), ./models/repo, TestExploreRepos, TestExploreReposBlankKeyword and TestExploreUserSearchSplit all pass
The Subjects tab was reported as neither highlighted nor underlined in the brand colour. Most of that report no longer applies. The tab-activation half is already fixed on master: /explore/subjects sets PageIsExploreSubjects and the custom navbar keys the Subjects tab on it. The reporter landed on /explore/articles, which sets PageIsExploreRepositories, a flag the custom navbar never tests, so no tab was active there at all; PR #335 retargeted the landing form at /explore/subjects. The underline colour was already correct too: menu.custom.css has carried `.ui.secondary.pointing.menu .active.item { border-color: var(--color-primary) }` for a while, and custom/header is included after base/head_style, so it already beat upstream's `currentcolor`. What was actually broken is the alignment. custom/templates/explore/navbar.tmpl had no <div class="overflow-menu-items"> wrapper, which upstream's navbar has. The <overflow-menu> web component's connectedCallback parks on a MutationObserver waiting for that element, so init() never ran and the menu never collapsed responsively or grew an overflow button. It also meant base.css's `overflow-menu .overflow-menu-items .item { margin-bottom: 0 !important }` never applied, so the tabs kept fomantic's `margin: 0 0 -2px` while <overflow-menu>'s own `border-bottom: 1px solid` won over the menu's 2px rule — the active tab's underline sat a pixel below the rail, which is what the screenshot shows. Restoring the wrapper makes the component initialise for the first time, and that exposes a second, previously unreachable colour bug: once a tab can collapse into the kebab button, base.css draws that button's active underline with `border-bottom: 2px solid currentcolor` — the near-black this issue is about, now at narrow viewports. menu.custom.css brands it to match the tab it stands in for, using base.css's own selector so the specificities match and mere sheet order decides it. TestExploreNavbarSubjectsTabActive covers the markup: it asserts exactly one tab is active inside .overflow-menu-items on /explore/subjects and /explore/users, that it is the tab matching the page, and that the sibling tabs still render unmarked. The colours are CSS only and are not asserted. Co-authored-by: Pierre Schweiger <schweiger.pierre@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On the explore tabs the sort radios and the filter radios are rendered inside the same form as the keyword field, checked to match the results on screen. Typing a new keyword and pressing Enter submitted them along with it, so a brand new set of results came back arranged the way the previous one had been left, with the previously picked button still active. The submit listener now compares the typed keyword with the one the page was rendered for (the field's defaultValue, so no new markup is needed). A changed keyword takes over the submit and navigates with the keyword alone, dropping the sort, the filters and the page. Re-submitting the same keyword still submits natively: there the form is the only record of a sort or a filter just picked, and dropping it would undo that choice. Clearing the field is unchanged -- clearing the search clears the search, not the way the results are arranged (#335). Co-authored-by: Pierre Schweiger <schweiger.pierre@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Subjects tab was reported as neither highlighted nor underlined in the brand colour. Most of that report no longer applies. The tab-activation half is already fixed on master: /explore/subjects sets PageIsExploreSubjects and the custom navbar keys the Subjects tab on it. The reporter landed on /explore/articles, which sets PageIsExploreRepositories, a flag the custom navbar never tests, so no tab was active there at all; PR #335 retargeted the landing form at /explore/subjects. The underline colour was already correct too: menu.custom.css has carried `.ui.secondary.pointing.menu .active.item { border-color: var(--color-primary) }` for a while, and custom/header is included after base/head_style, so it already beat upstream's `currentcolor`. What was actually broken is the alignment. custom/templates/explore/navbar.tmpl had no <div class="overflow-menu-items"> wrapper, which upstream's navbar has. The <overflow-menu> web component's connectedCallback parks on a MutationObserver waiting for that element, so init() never ran and the menu never collapsed responsively or grew an overflow button. It also meant base.css's `overflow-menu .overflow-menu-items .item { margin-bottom: 0 !important }` never applied, so the tabs kept fomantic's `margin: 0 0 -2px` while <overflow-menu>'s own `border-bottom: 1px solid` won over the menu's 2px rule — the active tab's underline sat a pixel below the rail, which is what the screenshot shows. Restoring the wrapper makes the component initialise for the first time, and that exposes a second, previously unreachable colour bug: once a tab can collapse into the kebab button, base.css draws that button's active underline with `border-bottom: 2px solid currentcolor` — the near-black this issue is about, now at narrow viewports. menu.custom.css brands it to match the tab it stands in for, using base.css's own selector so the specificities match and mere sheet order decides it. TestExploreNavbarSubjectsTabActive covers the markup: it asserts exactly one tab is active inside .overflow-menu-items on /explore/subjects and /explore/users, that it is the tab matching the page, and that the sibling tabs still render unmarked. The colours are CSS only and are not asserted. Co-authored-by: Pierre Schweiger <schweiger.pierre@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
On the explore tabs the sort radios and the filter radios are rendered inside the same form as the keyword field, checked to match the results on screen. Typing a new keyword and pressing Enter submitted them along with it, so a brand new set of results came back arranged the way the previous one had been left, with the previously picked button still active. The submit listener now compares the typed keyword with the one the page was rendered for (the field's defaultValue, so no new markup is needed). A changed keyword takes over the submit and navigates with the keyword alone, dropping the sort, the filters and the page. Re-submitting the same keyword still submits natively: there the form is the only record of a sort or a filter just picked, and dropping it would undo that choice. Clearing the field is unchanged -- clearing the search clears the search, not the way the results are arranged (#335). Co-authored-by: Pierre Schweiger <schweiger.pierre@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The Subjects tab was reported as neither highlighted nor underlined in the brand colour. Most of that report no longer applies. The tab-activation half is already fixed on master: /explore/subjects sets PageIsExploreSubjects and the custom navbar keys the Subjects tab on it. The reporter landed on /explore/articles, which sets PageIsExploreRepositories, a flag the custom navbar never tests, so no tab was active there at all; PR #335 retargeted the landing form at /explore/subjects. The underline colour was already correct too: menu.custom.css has carried `.ui.secondary.pointing.menu .active.item { border-color: var(--color-primary) }` for a while, and custom/header is included after base/head_style, so it already beat upstream's `currentcolor`. What was actually broken is the alignment. custom/templates/explore/navbar.tmpl had no <div class="overflow-menu-items"> wrapper, which upstream's navbar has. The <overflow-menu> web component's connectedCallback parks on a MutationObserver waiting for that element, so init() never ran and the menu never collapsed responsively or grew an overflow button. It also meant base.css's `overflow-menu .overflow-menu-items .item { margin-bottom: 0 !important }` never applied, so the tabs kept fomantic's `margin: 0 0 -2px` while <overflow-menu>'s own `border-bottom: 1px solid` won over the menu's 2px rule — the active tab's underline sat a pixel below the rail, which is what the screenshot shows. Restoring the wrapper makes the component initialise for the first time, and that exposes a second, previously unreachable colour bug: once a tab can collapse into the kebab button, base.css draws that button's active underline with `border-bottom: 2px solid currentcolor` — the near-black this issue is about, now at narrow viewports. menu.custom.css brands it to match the tab it stands in for, using base.css's own selector so the specificities match and mere sheet order decides it. TestExploreNavbarSubjectsTabActive covers the markup: it asserts exactly one tab is active inside .overflow-menu-items on /explore/subjects and /explore/users, that it is the tab matching the page, and that the sibling tabs still render unmarked. The colours are CSS only and are not asserted. Co-authored-by: Pierre Schweiger <schweiger.pierre@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes #276.
Fixes the three cases in the issue, plus a landing-page regression found while reproducing them.
1. The landing page was no longer vertically centered
The logo, heading and search bar were centered with
height: 100%on the landing<main>, which only works when every ancestor has a definite height. #316 relaxedbodyfromheight: 100%tomin-height: 100%so tall pages stop pushing the footer out of view (#242). With an indefinite body height,height: 100%resolved toauto,<main>shrank to its content,justify-content: centerhad no free space to distribute, and the block sat directly under the navbar.Replaced the percentage height with flexbox, the same way #316 fixed the auth pages:
.full.heightalready grows to fill the viewport viaflex: 1 0 auto, so making it a flex column and giving the landingflex: 1 0 autohands<main>a real height to center inside.flex-shrink: 0keeps the old top-aligned behaviour on viewports too short for the content, so the page scrolls instead of clipping the logo.Measured at 1456x836: the gap above and below the block went from 0px / 373px to 187px / 187px, with no viewport overflow.
2. Landing search went to the wrong tab (issue cases 1 and 2)
The landing form submitted to
/explore/articles, while the search on /explore submits to/explore/subjects— the tab the navbar defaults to, and the one/exploreitself redirects to. The same query produced a different page depending on where it was typed, and the landing one arrived on a page whose navbar shows no active tab and which renders no "Search results for ..." section.Pointed the form at
/explore/subjectsand dropped three hidden inputs (only_show_relevant,sort=score,fork=0) thatexplore.Subjectsnever reads —sort=scoreisn't a valid subject sort and silently fell back torecentupdate. A landing search now produces exactly the?q=...URL the explore subject search produces.3. No "Search results for ..." / "Similar" on the users tab (issue case 3)
The users tab returned a bare list, or a bare "No matching results found.". It now splits results the way the subjects tab does.
SearchUserOptions.ExactMatchOnly, mirroringrepo.FindSubjectsOptions.ExactMatchOnly. It swaps the keyword'sLIKEforEqon bothlower_nameandLOWER(full_name), souser1stops dragging inuser10..user18. The email condition stays a substring match on purpose — an email is never the thing a visitor typed the name of.splitExactUserMatchruns the exact lookup as its own query rather than picking the match out of the current page. The list is paginated and ordered by name or sign-up date, not relevance, so the exactly-named user can sit on any page — someone searching "anastasia" should find her on the first one. Reusing the page's own options makes the lookup inherit every filter already applied (user type, active, visibility to the viewer, repo role), so it cannot surface a user the paginated list would have hidden. When the exact match also falls on the current page it is dropped from the "Similar" list; the pagination total is left alone so page boundaries stay stable.custom/templates/explore/user_list.tmploverrides the stock list. Row markup moved tocustom/templates/shared/user/explore_item.tmpl, taking a dict rather than a bare user because Go rebinds$to whatever each{{template}}call passes.The split is gated on
PageIsExploreUsers./explore/organizationsshares not just the handler but the template (explore.OrganizationscallsRenderUserSearchwithtplExploreUsers), so without the gate an org listing would answer a search with "No user named exactly ..." — caught in review, fixed, and pinned byTestExploreOrganizationsKeepsPlainList.templates/admin/user/view.tmplshares the handler too but sets no search data, so it renders the plain list exactly as before.All exact matches are rendered, not just the first:
lower_nameis unique butfull_nameis not, so a name two accounts share would otherwise promote one arbitrarily and file its twin under "Similar".4. Border framed the headings, not just the rows
The box came from
.explore-users-list-container, which wraps everything the list template emits, so the new headings ended up inside it. Moved the border, radius, overflow and background onto an.explore-users-rowswrapper that only ever holds user rows; the headings and the no-exact-match note are siblings of those wrappers. A search renders two boxes — exact match and similar list — each framed on its own.flex-listhad to move onto the same element as the rows: the divider rule is.flex-list > .flex-item + .flex-item, a direct-child selector.5. Clearing the search box now resets the page
Emptying the keyword field left the results on screen next to an empty search box. Clicking the field's own clear button (the
xthat<input type="search">renders) did nothing at all — it produces aninputevent indistinguishable from typing, and nochangeuntil the field is left. Clearing by keyboard and pressing Enter did reload the list, but submittedq=and landed on?q=&sort=alphabetically.Now reset on both gestures:
search, which Blink and WebKit fire when the clear button or Escape empties the field (it bubbles, so one listener per form).submitwhile the field is empty — the path Enter and the search button take, and the only one Firefox has, since it renders no clear button and does not implementsearch.Both only intervene while the field is empty, so a real keyword still submits natively, and both drop only
q— sort and filters are separate controls and survive. Dropping an emptyqmoved into a shared helper, so the existing filter/sort handler stops emitting a strayq=too.Deliberately not hooked:
inputturning empty. Retyping a query starts by deleting the old one (step 2 of the issue's own repro), and navigating away on the last backspace would make the field unusable. Typing still just updates the tab links, so the keyword carry-over from #273 is untouched.Testing
Verified in a browser against a throwaway instance seeded from a dev database:
/explore/subjects?q=forest, Subjects tab active, "Search results for forest" renderspierrepierre+ Similarpierre_userPIERREAda Lornebubbleanastasia?sort=..., noq, full list, heading gone (users and subjects)qq=Also checked every rows box contains only
.flex-itemchildren, with headings as siblings outside.TestSearchUsersgains four cases forExactMatchOnly: username, full name, case folding, and a substring-only keyword matching nobody.tests/integration/explore_user_test.gogainsTestExploreUserSearchSplit(exact user promoted into its own box, "Similar" populated, the note for a substring-only keyword, no split without a keyword) andTestExploreOrganizationsKeepsPlainList(verified to fail without the users-tab gate).eslint,vue-tscandstylelintare clean.Note
The branch is based on a
masterthat is 5 commits behindorigin/master. It merges cleanly — the two overlapping files (models/user/search.go,custom/options/locale/locale_en-US.ini) received purely additive upstream changes in different regions, andExactMatchOnlycomposes with the newExcludeUserIDs/ExcludeOwnersOfSubjectIDconditions as independent AND-ed clauses.🤖 Generated with Claude Code