From 4e968f0b9dc169a3ba7c316daa07f4266440eb68 Mon Sep 17 00:00:00 2001 From: maker-nathan Date: Tue, 11 Aug 2026 21:46:24 -0700 Subject: [PATCH] fix: don't hide all manufacturing matches behind default near-miss tolerance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same bug as #355, in the manufacturing MatchView: the near-miss tolerance filter can hide every facility (default tolerance is 1 missing requirement), but the slider that lets a user raise it was only rendered inside the already-filtered results branch — so once everything was filtered out, there was no way to see or reveal the hidden facilities, just a blank "No matches found". Split the empty-state check the same way as the cooking fix: "No matches found" now only fires when the API returned zero solutions. When solutions exist but are all hidden by tolerance, the slider (and hidden-count message) render with a "No matches within tolerance" message instead. Co-authored-by: Cursor --- .../MatchView.nearMissTolerance.test.tsx | 136 ++++++++++++++ frontend/src/features/match/MatchView.tsx | 173 ++++++++++-------- 2 files changed, 228 insertions(+), 81 deletions(-) create mode 100644 frontend/src/features/match/MatchView.nearMissTolerance.test.tsx diff --git a/frontend/src/features/match/MatchView.nearMissTolerance.test.tsx b/frontend/src/features/match/MatchView.nearMissTolerance.test.tsx new file mode 100644 index 00000000..8a5469c1 --- /dev/null +++ b/frontend/src/features/match/MatchView.nearMissTolerance.test.tsx @@ -0,0 +1,136 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { fireEvent, render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { http, HttpResponse } from "msw"; +import { MemoryRouter } from "react-router-dom"; +import { describe, expect, it } from "vitest"; +import { server } from "../../test/msw/server"; +import type { OkhManifest } from "../../types/okh"; +import type { NetworkData } from "../../api/ohm/network"; +import { MatchView } from "./MatchView"; + +const design: OkhManifest = { + id: "design-1", + title: "Widget", + version: null, + repo: null, + function: null, + description: null, + intended_use: null, + keywords: [], + documentation_language: null, + license: null, + licensor: { name: "Acme" } as OkhManifest["licensor"], + contributors: [], + manufacturing_processes: [], + materials: [], + design_files: [], + manufacturing_files: [], + making_instructions: [], + parts: [], + tool_list: [], + image: null, + project_link: null, +}; + +const seededNetwork: NetworkData = { + spaces: [ + { + id: "facility-1", + name: "Alpha Lab", + lat: 45.5, + lon: -122.6, + source: "local", + city: "Portland", + region: "OR", + country: "US", + status: null, + processes: [], + access_type: null, + url: null, + }, + ], + total: 1, + local_count: 1, + mom_count: 0, + dropped_no_coords: 0, + mom_available: false, +}; + +function renderView() { + const client = new QueryClient({ + defaultOptions: { queries: { retry: false, staleTime: Infinity } }, + }); + client.setQueryData(["network", "baseline"], seededNetwork); + client.setQueryData(["okh-list"], { + items: [design], + pagination: { + page: 1, + page_size: 1, + total_items: 1, + total_pages: 1, + has_next: false, + has_previous: false, + }, + }); + return render( + + + + + , + ); +} + +describe("MatchView — near-miss tolerance", () => { + it("surfaces the tolerance slider instead of a blank empty state when every facility is hidden by default tolerance", async () => { + // 5 requirements, 3 missing: exceeds the default tolerance of 1, so the + // one and only solution is hidden by `withinTolerance` — but the API DID + // return a match, so this must not render the "zero solutions" empty state. + server.use( + http.post("*/v1/api/match", () => + HttpResponse.json({ + data: { + solutions: [ + { + facility_id: "facility-1", + facility_name: "Alpha Lab", + confidence: 0.4, + score: 0.4, + rank: 1, + match_type: "manufacturing", + explanation: { + requirement_matches: [ + { requirement_value: "cnc milling", status: "matched" }, + { requirement_value: "welding", status: "matched" }, + { requirement_value: "anodizing", status: "not_matched" }, + { requirement_value: "laser cutting", status: "not_matched" }, + { requirement_value: "3d printing", status: "not_matched" }, + ], + }, + }, + ], + total_solutions: 1, + }, + }), + ), + ); + + const user = userEvent.setup(); + renderView(); + + await user.click(await screen.findByLabelText("Alpha Lab")); + await user.click(screen.getByRole("button", { name: "⚡ Run Match" })); + + expect( + await screen.findByLabelText(/Allow facilities missing up to/), + ).toBeInTheDocument(); + expect(screen.getByText(/1 facility is hidden at this setting/)).toBeInTheDocument(); + expect(screen.queryByText("No matches found")).not.toBeInTheDocument(); + + const slider = screen.getByLabelText(/Allow facilities missing up to/) as HTMLInputElement; + fireEvent.change(slider, { target: { value: "3" } }); + + expect(await screen.findByLabelText("Select Alpha Lab")).toBeInTheDocument(); + }); +}); diff --git a/frontend/src/features/match/MatchView.tsx b/frontend/src/features/match/MatchView.tsx index d7118051..72f7032b 100644 --- a/frontend/src/features/match/MatchView.tsx +++ b/frontend/src/features/match/MatchView.tsx @@ -338,9 +338,10 @@ export function MatchView({ /> )} - {view && + {rawView && + view && !mutation.isPending && - (view.solutions.length === 0 ? ( + (rawView.solutions.length === 0 ? ( )} -
-

- {view.totalSolutions} solution - {view.totalSolutions !== 1 ? "s" : ""} - {selectedSolutionKeys.length > 0 - ? ` · ${selectedSolutionKeys.length} selected` - : ""} -

-
- - - -
-
-

- Select one or more facilities to generate outreach RFQs and - arrange production. Each card also links to that solution’s supply - tree when available. -

- {view.solutions.map((s, i) => { - const key = solutionSelectionKey(s, i); - return ( - - setSelectedSolutionKeys((prev) => - prev.includes(key) - ? prev.filter((k) => k !== key) - : [...prev, key], - ) - } - /> - ); - })} + {view.solutions.length === 0 ? ( + + ) : ( + <> +
+

+ {view.totalSolutions} solution + {view.totalSolutions !== 1 ? "s" : ""} + {selectedSolutionKeys.length > 0 + ? ` · ${selectedSolutionKeys.length} selected` + : ""} +

+
+ + + +
+
+

+ Select one or more facilities to generate outreach RFQs and + arrange production. Each card also links to that solution’s supply + tree when available. +

+ {view.solutions.map((s, i) => { + const key = solutionSelectionKey(s, i); + return ( + + setSelectedSolutionKeys((prev) => + prev.includes(key) + ? prev.filter((k) => k !== key) + : [...prev, key], + ) + } + /> + ); + })} + + )} ))}