Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -618,3 +618,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie
| 2026-08-04 | pull/1588 | d79d19c97a918edf785c5a066e691435a3f31518 | Run PR sweep full changed scope | fixed; exact-head CI required | PASS: conflict resolved from current main; combined lock regenerated with 0 vulnerabilities; prior jsdom exact-head build, coverage, static, Lighthouse, container and scans passed. Final merged-tree CI required. |
| 2026-08-04 | pull/1602 | 9b2c45c3f10ee7440e7450da7179a390dfbdf4c0 | Run PR sweep full changed scope | merged | PASS: dependency audit, build, coverage, static, Lighthouse, provider-free container smoke, HIGH/CRITICAL scan, SAST, Secret Scan and PR required. |
| 2026-08-04 | pull/1603 | dab117d474224964da40af2a1edc016e5dd7ffdf | Run PR sweep full changed scope | merged | PASS: dependency audit, Knip check, typecheck, build, full unit coverage, Lighthouse, provider-free container verification, scans and PR required. |
| 2026-08-04 | claude/top-search-design-mockups-w53znc | b432448e4893a42d07558aff0dc04be797971231 | PR #1611 — results-band shelf Clear filter-only, memo deps, restored tests | Fixed two Qodo findings from merged #1555; mutation-tested guard added | tsc 0; eslint 0; vitest 4 files/59 tests; verify:pr-local blocked by lock parity (node 24.13 vs jsdom@30) |
28 changes: 15 additions & 13 deletions src/components/clinical-dashboard/document-search-results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1111,15 +1111,20 @@ function DocumentSearchResultsPanelImpl({
const recordMatchCount = recordMatches.length;
const shouldShowHome = showHome || !trimmedQuery;

function toggleTagFacet(facet: SmartDocumentTagFacet) {
setActiveFacetState((current) => {
const keys = current.query === query ? current.keys : [];
return {
query,
keys: keys.includes(facet.key) ? keys.filter((key) => key !== facet.key) : [...keys, facet.key],
};
});
}
// Stable per query so the applied-filter shelf can depend on it honestly
// rather than suppressing the dependency check.
const toggleTagFacet = useCallback(
(facet: SmartDocumentTagFacet) => {
setActiveFacetState((current) => {
const keys = current.query === query ? current.keys : [];
return {
query,
keys: keys.includes(facet.key) ? keys.filter((key) => key !== facet.key) : [...keys, facet.key],
};
});
},
[query],
);

const unavailable = deriveDocumentSearchUnavailable({
apiUnavailable,
Expand Down Expand Up @@ -1182,10 +1187,7 @@ function DocumentSearchResultsPanelImpl({
}
}
return chips;
// `toggleTagFacet` is a stable closure over `query`, which is already a
// dependency of `activeFacetKeys`.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tagFacetGroups, activeFacetKeys, effectiveResultType, resultTabs]);
}, [tagFacetGroups, activeFacetKeys, effectiveResultType, resultTabs, toggleTagFacet]);
const clearAllFilters = () => {
setActiveFacetState({ query, keys: [] });
setActiveResultType("all");
Expand Down
5 changes: 5 additions & 0 deletions src/components/therapy-compass/bindings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export type TcBindings = {
toggleSheetOnly: () => void;
toggleReviewedOnly: () => void;
clearSearch: () => void;
clearSearchFilters: () => void;

// ---- compare --------------------------------------------------------
compareSlugs: string[];
Expand Down Expand Up @@ -434,6 +435,10 @@ export function TcProvider({ children }: { children: ReactNode }) {
toggleSheetOnly: () => setSearch((prev) => ({ ...prev, sheetOnly: !prev.sheetOnly })),
toggleReviewedOnly: () => setSearch((prev) => ({ ...prev, reviewedOnly: !prev.reviewedOnly })),
clearSearch: () => setSearch(EMPTY_SEARCH),
// Filter-only clear. The results-band shelf lists filters and says so
// ("Filtered by"), so its Clear must not delete the search term the user
// is reading — that is a control doing more than it advertises.
clearSearchFilters: () => setSearch((prev) => ({ ...EMPTY_SEARCH, query: prev.query })),

compareSlugs,
compareTherapies,
Expand Down
6 changes: 4 additions & 2 deletions src/components/therapy-compass/screens/search-screen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export function SearchScreen() {
const activeFilterCount = b.search.tags.length + availabilityFilterCount;
// Topics and availability both narrow the same list, so both belong on the
// shelf. The query is deliberately absent: it is stated in the composer and
// removing it is not a filter operation.
// removing it is not a filter operation. The shelf's trailing Clear therefore
// uses `clearSearchFilters`, not `clearSearch` — a row labelled "Filtered by"
// must not delete the search term the user is reading.
const appliedFilters = [
...b.search.tags.map((tag) => ({ id: `topic-${tag}`, label: tag, onRemove: () => b.toggleTag(tag) })),
...(b.search.reviewedOnly ? [{ id: "reviewed", label: "Reviewed only", onRemove: b.toggleReviewedOnly }] : []),
Expand All @@ -51,7 +53,7 @@ export function SearchScreen() {
onRetry={b.retryData}
headingLevel={1}
appliedFilters={appliedFilters}
onClearFilters={b.clearSearch}
onClearFilters={b.clearSearchFilters}
filterLabel="Filter therapy results"
mobileControls={
<TherapyFilterTrigger
Expand Down
18 changes: 18 additions & 0 deletions tests/search-command-surface.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { readFileSync } from "node:fs";

import { describe, expect, it } from "vitest";

import {
commandDropdownCanDisplay,
commandDropdownMinimumWidthMediaQuery,
commandDropdownPointerMediaQuery,
differentialRedFlagTerms,
filteredSuggestions,
isFormCodeQuery,
searchCommandSurfaceConfig,
Expand Down Expand Up @@ -60,4 +63,19 @@ describe("search command surface", () => {
);
expect(filteredSuggestions(config, "missing topic")).toEqual([]);
});

it("exposes differential red-flag search terms", () => {
expect(differentialRedFlagTerms).toContain("confusion");
expect(differentialRedFlagTerms.length).toBeGreaterThan(3);
});

it("keeps one sm max-height cap per command dropdown placement", () => {
const source = readFileSync(
new URL("../src/components/clinical-dashboard/universal-search-command-surface.tsx", import.meta.url),
"utf8",
);

expect(source).not.toContain("sm:max-h-[min(55dvh,26rem)]");
expect(source).toContain('opensUpward ? "sm:max-h-[min(38dvh,20rem)]" : "sm:max-h-[min(42dvh,24rem)]"');
});
});
21 changes: 21 additions & 0 deletions tests/therapy-compass-mode-wiring.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,4 +228,25 @@ describe("Therapy Compass production-mode wiring", () => {
expect(shellSrc).toContain("isStandaloneModeHomePath(pathname)");
expect(isStandaloneModeHomePath("/therapy-compass")).toBe(true);
});

it("keeps the results-band shelf Clear filter-only so it cannot delete the query", () => {
// Shipped defect (PR #1555): the shelf is labelled "Filtered by" and its
// trailing Clear was wired to `clearSearch`, which resets EMPTY_SEARCH —
// including `query: ""`. So Clear silently deleted the search term the user
// was reading, while the code comment beside it claimed the query was
// deliberately excluded. The sheet's own "Clear all" still resets
// everything on purpose; only the shelf is filter-only.
const searchScreenSrc = readFileSync(
new URL("../src/components/therapy-compass/screens/search-screen.tsx", import.meta.url),
"utf8",
);
expect(searchScreenSrc).toContain("onClearFilters={b.clearSearchFilters}");
expect(searchScreenSrc).not.toContain("onClearFilters={b.clearSearch}");
// The binding must preserve the query rather than reset the whole shape.
expect(bindingsSrc).toContain(
"clearSearchFilters: () => setSearch((prev) => ({ ...EMPTY_SEARCH, query: prev.query }))",
);
// The sheet keeps the full reset.
expect(searchScreenSrc).toContain("onClear={b.clearSearch}");
});
});
Loading