From 7c7db4e24d6cbe9d8b5a84dad08433513b8c7a52 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 06:07:39 +0000 Subject: [PATCH 1/9] feat(mockups): three runnable directions for the Tools search results state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `/tools?q=…` is rated an urgent redesign in the outstanding-issues ledger (`#162`). The July 2026 study produced three static PNG comps and picked direction A, but nothing runnable was ever built, so the pick had never been read against live catalogue data. Adds `/mockups/tools-search-directions`: one route, three stacked directions for the submitted results state, each shown at phone 390 and desktop 1280. - 01 Compact Results Instrument — query-as-h1, hero and quick-action grid dropped on submit, filters folded onto the title row, dense rows whose primary button launches the tool. - 02 Ranked Clinical Brief — adopts `rankToolRecords` (already exported by the catalogue, never called by this page) and promotes the top match into an inline brief built from `bestFor` / `checkFirst` / `neededInput` / `output`. - 03 Launch-First Triage Deck — no dialog at all; native `
` rows, and the area filter and count fuse into one segmented band. Frames are built from the real 13-record `src/lib/tools-catalog.ts`, so match counts, orderings, badges and per-tool copy are genuine rather than fixtures. Both demo queries are real: `monitoring` (3 substring matches vs 4 ranked — today's matcher puts the exactly-named tool last) and `compare` (one match, the reported screenshot). Match-reason chips deliberately use categorical `--type-*` tones rather than the `relevance.tsx` badges, whose `EvidenceRelevance` verdicts would print a source-grounding claim where only a keyword hit exists. Shared app chrome is suppressed for this route because every frame draws its own top bar, composer and band; per `mockups/README.md` shared chrome is inherited or suppressed, never forked into the page. Scope is the results state only — no production surface changes, and the Tools home redesign stays out per `#162`'s own stop rule. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CCSWnNvh73Y3FG3oDj6rRi --- docs/site-map.md | 1 + mockups/README.md | 27 + src/app/mockups/mockups-layout-client.tsx | 5 + .../mockups/tools-search-directions/page.tsx | 12 + .../tools-search-directions-mockups.tsx | 853 ++++++++++++++++++ 5 files changed, 898 insertions(+) create mode 100644 src/app/mockups/tools-search-directions/page.tsx create mode 100644 src/components/tools-search-directions-mockups.tsx diff --git a/docs/site-map.md b/docs/site-map.md index 4a7df112bc..2d94c8e439 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -1039,6 +1039,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir - `/mockups/tools-action-workbench` - Route discovered from app directory Source: `src/app/mockups/tools-action-workbench/page.tsx`. - `/mockups/tools-clinical-lanes` - Route discovered from app directory Source: `src/app/mockups/tools-clinical-lanes/page.tsx`. - `/mockups/tools-command-center` - Route discovered from app directory Source: `src/app/mockups/tools-command-center/page.tsx`. +- `/mockups/tools-search-directions` - Route discovered from app directory Source: `src/app/mockups/tools-search-directions/page.tsx`. - `/mockups/tools-split-clinical-brief` - Route discovered from app directory Source: `src/app/mockups/tools-split-clinical-brief/page.tsx`. - `/mockups/tools-split-compact-sheet` - Route discovered from app directory Source: `src/app/mockups/tools-split-compact-sheet/page.tsx`. - `/mockups/tools-split-pane` - Route discovered from app directory Source: `src/app/mockups/tools-split-pane/page.tsx`. diff --git a/mockups/README.md b/mockups/README.md index ea223959a4..4646ec3079 100644 --- a/mockups/README.md +++ b/mockups/README.md @@ -61,6 +61,33 @@ These are PNGs for design review only. Runnable `/mockups/*` routes are a separa **Perfected combined comps** (desktop + phone in one image, recommended directions only) live in [`public/mockups/mode-page-redesign-2026-07/perfected-combined/`](../public/mockups/mode-page-redesign-2026-07/perfected-combined/README.md). +## Tools search results directions (2026-08-12) + +Runnable study at [`/mockups/tools-search-directions`](../src/app/mockups/tools-search-directions/page.tsx): +three directions for the **results state** of `/tools?q=…` (ledger `#162`). Scope is the submitted +state only — not the Tools home, per that row's own stop rule. + +This is the runnable successor to the static July comps in +[`public/mockups/mode-page-redesign-2026-07/tools-search/`](../public/mockups/mode-page-redesign-2026-07/tools-search/), +whose direction A was the product pick but was never built, so the pick had never been read +against live catalogue data. These directions are fresh, informed by that study rather than +tracing it. + +| Direction | Adds | Cost | +| ----------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------- | +| 01 Compact Results Instrument | Query-as-h1, hero dropped, filters on the title row, dense launch rows | Leaves the substring matcher — and its ordering — as is | +| 02 Ranked Clinical Brief | `rankToolRecords` plus an inline brief from the unused catalogue fields | Tallest frame; ranking change needs its own PR scrutiny | +| 03 Launch-First Triage Deck | No dialog at all — `
` rows; filter and count fuse into one band | Tall rows at desktop width, and no room for long detail | + +**Recommendation: 02**, on 01's row grammar. Every frame is built from the real 13-record +`src/lib/tools-catalog.ts`, so the counts, orderings, badges and per-tool copy are genuine. +Two queries are shown per direction: `monitoring` (3 substring matches vs 4 ranked — the current +page puts the exactly-named tool **last**) and `compare` (one match: the reported screenshot). + +Frames use role tokens, so **set the app to Clinical White before judging** — the study was +designed and reviewed in light mode. Shared app chrome is suppressed for this route because +every frame draws its own top bar, composer and band. + ## Breadcrumb header study (2026-08-09) — shipped, route retired Three sticky header directions for record pages that use `InformationPageBreadcrumbs` and have **no in-page section index** — factsheets, services, forms, DSM, specifiers, formulation, medications. `InPageNavHeader` stays the default for in-page navigation per [`docs/search-chrome-behaviour.md`](../docs/search-chrome-behaviour.md); it is the wrong shape for those pages, because with no sections its disclosure opens a one-item sheet and its weighted track renders one full-width segment. Every direction kept that header's row grammar (back, title, ellipsis, one scroll owner) and dropped the section machinery: diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx index 4a2c9d166f..d3c9c89876 100644 --- a/src/app/mockups/mockups-layout-client.tsx +++ b/src/app/mockups/mockups-layout-client.tsx @@ -54,6 +54,10 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { // at the bottom on phones), so the shared universal composer is suppressed here // to avoid a second, floating search bar. const isCalculatorsSearchPageMockup = pathname === "/mockups/calculators-search-page"; + // Draws its own top bar, composer and results band inside every device frame, because two of + // the three directions restructure that band. Shared chrome above them would read as a second, + // real header and a second real composer over the study. + const isToolsSearchDirectionsMockup = pathname === "/mockups/tools-search-directions"; return ( ; +} diff --git a/src/components/tools-search-directions-mockups.tsx b/src/components/tools-search-directions-mockups.tsx new file mode 100644 index 0000000000..21c77988d5 --- /dev/null +++ b/src/components/tools-search-directions-mockups.tsx @@ -0,0 +1,853 @@ +"use client"; + +// Design-scratch: the Tools search RESULTS state (`/tools?q=…`), three directions. +// +// Grounds `docs/outstanding-issues.md` `#162`, which rates this page an urgent redesign and +// picked "Compact Results Instrument" from the static comps in +// `public/mockups/mode-page-redesign-2026-07/tools-search/`. Those comps were never built, so +// the pick had never been read against the real 13-record catalogue. These directions are +// fresh, informed by that study rather than tracing it. +// +// Every frame draws its own top bar, composer and results band, so +// `src/app/mockups/mockups-layout-client.tsx` suppresses the shared app chrome for this route +// (`chromeVisible={false}`) — otherwise the real header reads as a second, live header sitting +// over the study. Per `mockups/README.md`, shared chrome is inherited or suppressed, never +// forked into the page, which is why the in-frame band here is a deliberate replica and the +// real `SearchResultsHeaderBand` is not mounted: directions 01 and 03 exist to restructure +// that band, and it reads its count noun from the live mode registry. +// +// Results state only. The pre-query home state and the Tools launcher redesign are out of +// scope (`#162`'s own stop rule). + +import { + ArrowLeft, + Brain, + Calculator, + ChevronDown, + ClipboardCheck, + ClipboardList, + FileCheck2, + FileText, + Menu, + Pill, + Plus, + Search, + Send, + ShieldAlert, + ShieldCheck, + Sparkles, + Star, + TriangleAlert, + Users, + Waves, + Wrench, + X, + type LucideIcon, +} from "lucide-react"; + +import { cn } from "@/components/ui-primitives"; +import { + rankToolRecords, + toolCatalogRecordsForSession, + type ToolCatalogArea, + type ToolCatalogRecord, +} from "@/lib/tools-catalog"; + +type DirectionId = "instrument" | "brief" | "deck"; +type FrameQuery = "monitoring" | "compare"; +type FrameDevice = "phone" | "desktop"; + +/* ------------------------------------------------------------------ * + * Data — the real catalogue, not fixtures. + * ------------------------------------------------------------------ */ + +const areaLabels: Record = { + assessment: "Assess", + reference: "Evidence", + care: "Treat", + coordination: "Coordinate", + saved: "Saved", +}; + +// Presentation-only mapping, mirroring `applications-launcher-page.tsx`: the shared catalogue +// is icon-free so server code (universal search) can import it. +const iconById: Record = { + "clinical-kb-search": Search, + differentials: Brain, + documents: FileText, + guidelines: ShieldCheck, + "risk-safety": ShieldCheck, + "medication-prescribing": Pill, + services: Users, + forms: FileCheck2, + "care-plans": ClipboardCheck, + "safety-plan": ClipboardList, + calculators: Calculator, + monitoring: Waves, + favourites: Star, +}; + +/** Catalogue record with its icon already attached, so no component is resolved during render. */ +type MockupTool = ToolCatalogRecord & { icon: LucideIcon }; + +const withIcon = (record: ToolCatalogRecord): MockupTool => ({ + ...record, + icon: iconById[record.id] ?? Sparkles, +}); + +const session = { authenticated: true, demoMode: false } as const; +const catalogue: MockupTool[] = toolCatalogRecordsForSession(session).map(withIcon); + +/** + * The matcher the page ships today: one lowercase `.includes()` over a joined string + * (`applications-launcher-page.tsx:769-788`). Reproduced verbatim so directions 01 and 03 show + * the real current result set and ordering, and 02's ranked list can be read against it. + */ +function substringMatches(query: string): MockupTool[] { + const needle = query.trim().toLowerCase(); + if (!needle) return catalogue; + return catalogue.filter((tool) => + [tool.title, tool.mobileTitle, tool.description, tool.bestFor, tool.detail, areaLabels[tool.area], ...tool.keywords] + .filter(Boolean) + .join(" ") + .toLowerCase() + .includes(needle), + ); +} + +/** Reason labels as `rankToolRecords` emits them, mapped to reader-facing copy. */ +const reasonLabels: Record = { + title: "Name", + keywords: "Keywords", + description: "Description", + content: "Detail", + phrase: "Exact phrase", +}; + +const rankedMatches = (query: string) => + rankToolRecords(query, 8, [], session).map((match) => ({ ...match, tool: withIcon(match.tool) })); + +const queryCopy: Record = { + monitoring: { + label: "monitoring", + note: "Substring finds 3 and ranks the exactly-named tool last. Ranked finds 4, Monitoring first.", + }, + compare: { + label: "compare", + note: "One match — the state in the reported screenshot, where the only result fell below the fold.", + }, +}; + +/* ------------------------------------------------------------------ * + * Direction annotations + * ------------------------------------------------------------------ */ + +const directions: Array<{ + id: DirectionId; + title: string; + verdict: string; + description: string; + changes: string[]; +}> = [ + { + id: "instrument", + title: "Compact Results Instrument", + verdict: "Smallest change that fixes it", + description: + "The results state stops pretending to be the home page. The mode hero drops on submit, the band becomes the page title with the query as the only h1, and the filter row collapses onto that same line so one control no longer occupies a whole shelf. Results become dense rows whose primary button launches the tool.", + changes: [ + "Mode hero and the 7-chip quick-action grid are gone once a query exists — one h1, and it is the query.", + "Count, mode and area filters share the title row; the second band shelf disappears. Phone lists only the areas the result set contains, so the row stays one line.", + "Each row's primary button is the tool's own actionLabel (Ask / Compare / Prescribe / Open); Details demotes to a desktop-only secondary, and the row itself opens detail on phone.", + "A filter is only drawn when there is something to filter — at one match the row disappears entirely rather than offering All 1.", + "Cross-mode escape added as a collapsed disclosure at the foot, so a miss is recoverable without retyping.", + "The colour-coding reference link leaves the results flow — it is settings, not a result.", + ], + }, + { + id: "brief", + title: "Ranked Clinical Brief", + verdict: "Best decision support", + description: + "Everything in 01, plus the page finally calls rankToolRecords — which the catalogue already exports and this page has never used. The top match is promoted into an inline brief built from the four catalogue fields the results state currently hides, so the likely answer needs no dialog at all.", + changes: [ + "rankToolRecords replaces the substring filter: `monitoring` now returns Monitoring first, not last, and surfaces a fourth match the substring misses.", + "Rows carry why they matched, from the ranker's own reasons array, as neutral metadata chips.", + "The top match expands inline into Best for / Check first / You'll need / Output — real per-tool catalogue fields, authored for all 13 tools and currently invisible.", + "Match-reason chips deliberately use categorical --type-* tones, never the semantic source-backed treatment: a keyword hit must not be able to read as a grounding claim.", + "Remaining matches stay as 01's dense rows, so density is only spent where it buys a decision.", + ], + }, + { + id: "deck", + title: "Launch-First Triage Deck", + verdict: "Fewest taps to the tool", + description: + "An interaction-model change: no dialog anywhere. Every match is a disclosure that opens its detail inline, so nothing is hidden behind a modal, and the area filter folds into the band as a segmented control that doubles as the count. Safety-first tools are marked permanently rather than one tap deep.", + changes: [ + "The detail dialog is removed. Rows are native
, so detail costs no modal and no JavaScript.", + "Filter and count become one segmented control; the phone filter sheet and its trigger both disappear.", + "Areas with no matches are present but inert, so the filter still reports the shape of the catalogue.", + "safetyFirst tools (Risk & Safety, Medication Prescribing, Safety plan) carry a persistent warning edge and chip.", + "Trade-off to judge: at desktop width the inline detail makes rows tall, and no modal means no room for anything longer.", + ], + }, +]; + +/* ------------------------------------------------------------------ * + * Shared in-frame chrome (replicas — see the header note) + * ------------------------------------------------------------------ */ + +const focusRing = + "focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus)]"; + +const iconTone: Record = { + assessment: + "border-[color:var(--type-service-border)] bg-[color:var(--type-service-soft)] text-[color:var(--type-service)]", + reference: "border-[color:var(--type-table-border)] bg-[color:var(--type-table-soft)] text-[color:var(--type-table)]", + care: "border-[color:var(--type-document-border)] bg-[color:var(--type-document-soft)] text-[color:var(--type-document)]", + coordination: + "border-[color:var(--clinical-accent-border)] bg-[color:var(--clinical-accent-soft)] text-[color:var(--clinical-accent)]", + saved: "border-[color:var(--type-search-border)] bg-[color:var(--type-search-soft)] text-[color:var(--type-search)]", + safety: "border-[color:var(--danger-border)] bg-[color:var(--danger-soft)] text-[color:var(--danger)]", + medication: "border-[color:var(--type-form-border)] bg-[color:var(--type-form-soft)] text-[color:var(--type-form)]", + differentials: + "border-[color:var(--type-source-border)] bg-[color:var(--type-source-soft)] text-[color:var(--type-source)]", +}; + +function toneFor(tool: MockupTool) { + if (tool.safetyFirst && tool.id !== "medication-prescribing") return iconTone.safety; + if (tool.id === "medication-prescribing") return iconTone.medication; + if (tool.id === "differentials") return iconTone.differentials; + return iconTone[tool.area]; +} + +function ToolGlyph({ tool, size = "md" }: { tool: MockupTool; size?: "sm" | "md" | "lg" }) { + return ( + + + ); +} + +function FrameTopBar({ device }: { device: FrameDevice }) { + return ( +
+ + KB + + + + + Tools + + +
+ ); +} + +/** Back affordance to the Tools home — every direction keeps a single return path. */ +function BackRow({ label = "Tools" }: { label?: string }) { + return ( +

+

+ ); +} + +function FrameComposer({ query, device }: { query: FrameQuery; device: FrameDevice }) { + return ( +
+
+ + + + {queryCopy[query].label} + + + + + +
+ {/* Governance copy (APP-5 / PIA-5) — reproduced verbatim, never reworded. */} +

+

+
+ ); +} + +/** The query as the page's only heading — the change every direction shares. */ +function QueryTitle({ + query, + countLabel, + device, + trailing, +}: { + query: FrameQuery; + countLabel: string; + device: FrameDevice; + trailing?: React.ReactNode; +}) { + return ( +
+
+

+ {queryCopy[query].label} +

+

{countLabel}

+
+ {trailing} +
+ ); +} + +/** + * Phone shows only the areas the result set actually contains, so the row stays one line at + * 340 px; desktop has room to show every area with its count, empty ones included. + */ +function AreaFilterRow({ tools, device }: { tools: MockupTool[]; device: FrameDevice }) { + const present = new Set(tools.map((tool) => tool.area)); + const allAreas: ToolCatalogArea[] = ["assessment", "reference", "care", "coordination"]; + const areas = device === "phone" ? allAreas.filter((area) => present.has(area)) : allAreas; + return ( +
+ + All {tools.length} + + {areas.map((area) => ( + + {areaLabels[area]} + {device === "desktop" ? ` ${tools.filter((tool) => tool.area === area).length}` : null} + + ))} +
+ ); +} + +function SourceChip({ tool }: { tool: MockupTool }) { + return tool.sourceBacked ? ( + + + ) : ( + + Private + + ); +} + +function SafetyChip() { + return ( + + + ); +} + +function PrimaryAction({ label, device }: { label: string; device: FrameDevice }) { + return ( + + ); +} + +/** Collapsed cross-mode escape. Modes are named rather than counted — nothing here is live. */ +function CrossModeFoot() { + return ( +

+ Also in Documents · Medications · Services +

+ ); +} + +/* ------------------------------------------------------------------ * + * 01 — Compact Results Instrument + * ------------------------------------------------------------------ */ + +function InstrumentRow({ tool, device }: { tool: MockupTool; device: FrameDevice }) { + return ( +
  • + + + {/* The tool name wraps rather than truncating — a half-read tool name is not a result. */} + {tool.title} + {tool.bestFor} + + {tool.safetyFirst ? : null} + {device === "desktop" ? : null} + + + + + {/* Details is a desktop-only secondary; on phone the row itself opens the detail. */} + {device === "desktop" ? ( + + ) : null} + +
  • + ); +} + +function InstrumentDirection({ query, device }: { query: FrameQuery; device: FrameDevice }) { + const tools = substringMatches(queryCopy[query].label); + return ( +
    + + 1 ? : undefined + } + /> + + {device === "phone" && tools.length > 1 ? : null} +
      + {tools.map((tool) => ( + + ))} +
    + +
    + ); +} + +/* ------------------------------------------------------------------ * + * 02 — Ranked Clinical Brief + * ------------------------------------------------------------------ */ + +function ReasonChips({ reasons }: { reasons: string[] }) { + return ( + + {reasons.map((reason) => ( + + {reasonLabels[reason] ?? reason} + + ))} + + ); +} + +function BriefField({ term, children }: { term: string; children: React.ReactNode }) { + return ( +
    +
    {term}
    +
    {children}
    +
    + ); +} + +function BriefDirection({ query, device }: { query: FrameQuery; device: FrameDevice }) { + const matches = rankedMatches(queryCopy[query].label); + const [top, ...rest] = matches; + if (!top) return null; + return ( +
    + + m.tool)} device={device} /> : undefined + } + /> + + + {/* Promoted top match — the inline brief that removes the dialog for the likely answer. */} +
    +

    + Best match +

    +
    + +
    +

    + {top.tool.title} +

    +

    {top.tool.description}

    +
    + + {top.tool.safetyFirst ? : null} + +
    +
    +
    +
    + {top.tool.bestFor} + {top.tool.checkFirst.slice(0, 2).join(" · ")} + {top.tool.neededInput.slice(0, 2).join(" · ")} + {top.tool.output} +
    +
    + +
    +
    + + {rest.length > 0 ? ( +
    +

    + {rest.length} more {rest.length === 1 ? "match" : "matches"} +

    +
      + {rest.map((match) => ( +
    • + + + + {match.tool.title} + + + {match.tool.bestFor} + + + {match.tool.safetyFirst ? : null} + + + + +
    • + ))} +
    +
    + ) : null} + +
    + ); +} + +/* ------------------------------------------------------------------ * + * 03 — Launch-First Triage Deck + * ------------------------------------------------------------------ */ + +function SegmentedCountBand({ tools }: { tools: MockupTool[] }) { + const areas: ToolCatalogArea[] = ["assessment", "reference", "care", "coordination"]; + return ( +
    + + {tools.length} + + All + + + {areas.map((area) => { + const count = tools.filter((tool) => tool.area === area).length; + return ( + + + {count === 0 ? "—" : count} + + + {areaLabels[area]} + + + ); + })} +
    + ); +} + +function DeckRow({ tool, device, open }: { tool: MockupTool; device: FrameDevice; open: boolean }) { + return ( +
  • +
    +
    + + + + + {tool.title} + + {tool.bestFor} + {tool.safetyFirst ? ( + + + + ) : null} + + +
    + {tool.checkFirst.join(" · ")} + {tool.neededInput.join(" · ")} + {tool.output} +
    +

    + +

    +
    + +
    +
  • + ); +} + +function DeckDirection({ query, device }: { query: FrameQuery; device: FrameDevice }) { + const tools = substringMatches(queryCopy[query].label); + return ( +
    + + + + +
      + {tools.map((tool, index) => ( + + ))} +
    + +
    + ); +} + +/* ------------------------------------------------------------------ * + * Frames and showcase + * ------------------------------------------------------------------ */ + +const directionRenderers: Record React.ReactNode> = + { + instrument: InstrumentDirection, + brief: BriefDirection, + deck: DeckDirection, + }; + +function DeviceFrame({ direction, query, device }: { direction: DirectionId; query: FrameQuery; device: FrameDevice }) { + const Render = directionRenderers[direction]; + return ( +
    +
    + + +
    +
    + {device === "phone" ? "Phone 390" : "Desktop 1280"} · “{queryCopy[query].label}” + + {queryCopy[query].note} + +
    +
    + ); +} + +function DirectionShowcase({ direction, number }: { direction: (typeof directions)[number]; number: number }) { + return ( +
    +
    +
    +

    + Direction {String(number).padStart(2, "0")} · {direction.verdict} +

    +

    + {direction.title} +

    +
    +

    + {direction.description} +

    +
    + +
      + {direction.changes.map((change) => ( +
    • + {change} +
    • + ))} +
    + +
    + + + +
    +
    + ); +} + +const currentDefects: string[] = [ + "Two competing page titles — the mode hero’s “Tools” h1 and the band’s query heading.", + "The home hero and the 7-chip quick-action grid survive submission, pushing a single result below the phone fold.", + "Those chips read as refinements beside results, but each one opens a detail dialog instead of re-searching.", + "A two-row band shelf carrying one lone Filter control.", + "Card-grid density for a 13-item catalogue where most queries return 1–4 matches.", + "The primary affordance on a result is “Details” — a dialog — not launching the tool you searched for.", + "A “Colour coding reference” link rendered inside the results flow as though it were a result.", + "No cross-mode escape when the 13-tool catalogue misses the query.", + "Relevance thrown away: a plain substring filter, while rankToolRecords already computes scores and match reasons.", +]; + +export function ToolsSearchDirectionsMockups() { + return ( +
    +
    +
    +

    + Tools · Search results state +

    +

    + Three directions for the Tools results page +

    +

    + All three fix the same nine defects in today’s /tools?q=…; they differ in how far they go. + Every frame is built from the real 13-record catalogue in src/lib/tools-catalog.ts, so the + match counts, orderings, badges and per-tool copy below are the genuine data — not fixtures. Design scratch: + this route 404s in production and changes no production surface. +

    +
    + {[ + [ + "Shared by all three", + "Query is the only h1; hero and quick-action grid drop on submit; launch beats dialog", + ], + [ + "Queries shown", + "“monitoring” (multi-match, exposes the ranking defect) and “compare” (the reported screenshot)", + ], + ["Read in light theme", "Frames use role tokens, so set the app to Clinical White before judging"], + ].map(([term, detail]) => ( +
    +
    + {term} +
    +
    {detail}
    +
    + ))} +
    +
    + +
    +

    + What every direction is fixing +

    +
      + {currentDefects.map((defect, index) => ( +
    1. + {index + 1} + {defect} +
    2. + ))} +
    +
    + +
    + {directions.map((direction, index) => ( + + ))} +
    +
    +
    + ); +} From f8a14ab443e2aa98f06ac32c4c8604310e99579c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 06:11:53 +0000 Subject: [PATCH 2/9] docs(ledger): record the tools search mockups review Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01CCSWnNvh73Y3FG3oDj6rRi --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index b1c6f2b5ce..6c8422748a 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -896,3 +896,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-11 | 1820 | 897ff11a4cdb13ae1c01f5eb149007847028f5aa | review-and-fix | fixed | Semgrep:IN_PROGRESS, Gitleaks:IN_PROGRESS, Semgrep ingestion gate:IN_PROGRESS, Static PR checks:QUEUED, Safety and config checks:QUEUED, Unit coverage:QUEUED, Build:QUEUED, Production UI critical:QUEUED, Lighthouse budget:QUEUED | | 2026-08-12 | PR #1815 / claude/spacing-icon-design-review-rxwh28 | 9f266210f02081be54d407c70a85f52fed436128 | babysit | no remaining actionable findings; one pre-existing thread resolved as no-change (Dockerfile.worker follow-up needed) | required checks: Gitleaks PR policy PR required (all pass); targeted vitest passed: tests/document-frame-contract.test.ts + tests/in-page-nav-header.dom.test.tsx | | 2026-08-12 | 1815 | 27ce96e1755055ceee2eeae02d6efdf11259fcde | babysit | fixed | Unit coverage: targeted vitest passed: tests/shared-home-empty-state.dom.test.tsx (17 passed). PR required still blocked on pre-existing check failure at old remote head before sync. | +| 2026-08-12 | claude/tools-search-mockups-kmzxf1 | 7c7db4e24d6cbe9d8b5a84dad08433513b8c7a52 | tools search results mockups | PR #1839 opened: new /mockups/tools-search-directions route with three directions for the /tools?q= results state (#162), built from the real tools catalogue; mockups-layout-client chrome suppression added; no production surface touched | lint, typecheck, test (1 pre-existing unrelated failure: pr-handoff-stop), build, format:changed, sitemap:check, docs:check-*, ledger + outstanding-issues guards, check:rag:fixtures, check:bundle-budget (production and mockups within tolerance), Chromium 200 + zero console errors | From 25889b2e3140bbb237b2691c8ceee0466b43e2da Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 12 Aug 2026 21:22:11 +0800 Subject: [PATCH 3/9] fix(mockups): align tools search comparison evidence --- AGENTS.md | 4 +++- docs/branch-review-ledger.md | 1 + .../tools-search-directions-mockups.tsx | 5 +++-- tests/tools-search-directions-mockups.test.ts | 21 +++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 tests/tools-search-directions-mockups.test.ts diff --git a/AGENTS.md b/AGENTS.md index 88ec727f80..483df4b4c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,9 @@ # This is NOT the Next.js you know -This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices. +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 6c8422748a..d4bdc29459 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -897,3 +897,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-12 | PR #1815 / claude/spacing-icon-design-review-rxwh28 | 9f266210f02081be54d407c70a85f52fed436128 | babysit | no remaining actionable findings; one pre-existing thread resolved as no-change (Dockerfile.worker follow-up needed) | required checks: Gitleaks PR policy PR required (all pass); targeted vitest passed: tests/document-frame-contract.test.ts + tests/in-page-nav-header.dom.test.tsx | | 2026-08-12 | 1815 | 27ce96e1755055ceee2eeae02d6efdf11259fcde | babysit | fixed | Unit coverage: targeted vitest passed: tests/shared-home-empty-state.dom.test.tsx (17 passed). PR required still blocked on pre-existing check failure at old remote head before sync. | | 2026-08-12 | claude/tools-search-mockups-kmzxf1 | 7c7db4e24d6cbe9d8b5a84dad08433513b8c7a52 | tools search results mockups | PR #1839 opened: new /mockups/tools-search-directions route with three directions for the /tools?q= results state (#162), built from the real tools catalogue; mockups-layout-client chrome suppression added; no production surface touched | lint, typecheck, test (1 pre-existing unrelated failure: pr-handoff-stop), build, format:changed, sitemap:check, docs:check-*, ledger + outstanding-issues guards, check:rag:fixtures, check:bundle-budget (production and mockups within tolerance), Chromium 200 + zero console errors | +| 2026-08-12 | PR-1839 | f8a14ab443e2aa98f06ac32c4c8604310e99579c | full PR diff and unresolved review feedback | P2 evidence-width and desktop-comparison findings fixed | focused static mockup contract 2/2 pass; task-owned ensure server verified | diff --git a/src/components/tools-search-directions-mockups.tsx b/src/components/tools-search-directions-mockups.tsx index 21c77988d5..d6ca931982 100644 --- a/src/components/tools-search-directions-mockups.tsx +++ b/src/components/tools-search-directions-mockups.tsx @@ -337,7 +337,7 @@ function QueryTitle({ /** * Phone shows only the areas the result set actually contains, so the row stays one line at - * 340 px; desktop has room to show every area with its count, empty ones included. + * 390 px; desktop has room to show every area with its count, empty ones included. */ function AreaFilterRow({ tools, device }: { tools: MockupTool[]; device: FrameDevice }) { const present = new Set(tools.map((tool) => tool.area)); @@ -712,7 +712,7 @@ function DeviceFrame({ direction, query, device }: { direction: DirectionId; que data-direction={direction} data-query={query} data-device={device} - className={cn("m-0 shrink-0", device === "phone" ? "w-[340px]" : "w-full max-w-[900px]")} + className={cn("m-0 shrink-0", device === "phone" ? "w-[390px]" : "w-[1280px]")} >
    @@ -763,6 +763,7 @@ function DirectionShowcase({ direction, number }: { direction: (typeof direction +
    ); diff --git a/tests/tools-search-directions-mockups.test.ts b/tests/tools-search-directions-mockups.test.ts new file mode 100644 index 0000000000..de3a1a79c6 --- /dev/null +++ b/tests/tools-search-directions-mockups.test.ts @@ -0,0 +1,21 @@ +import { readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; + +const source = readFileSync(new URL("../src/components/tools-search-directions-mockups.tsx", import.meta.url), "utf8"); + +describe("tools search direction mockup evidence", () => { + it("renders frames at the widths named by their captions", () => { + expect(source).toContain('device === "phone" ? "w-[390px]" : "w-[1280px]"'); + expect(source).toContain('device === "phone" ? "Phone 390" : "Desktop 1280"'); + expect(source).not.toContain('device === "phone" ? "w-[340px]" : "w-full max-w-[900px]"'); + }); + + it("shows both representative queries at both widths for every direction", () => { + const showcase = source.slice(source.indexOf("function DirectionShowcase"), source.indexOf("const currentDefects")); + expect(showcase).toContain(''); + expect(showcase).toContain(''); + expect(showcase).toContain(''); + expect(showcase).toContain(''); + }); +}); From ea4fa5c86a6a20754a8ae0b24373b674739d1e6c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:46:20 +0800 Subject: [PATCH 4/9] fix(mockups): update generated site map --- docs/site-map.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/site-map.md b/docs/site-map.md index b8098c8514..55ea7e389e 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -1042,6 +1042,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir - `/mockups/tools-action-workbench` - Route discovered from app directory Source: `src/app/mockups/tools-action-workbench/page.tsx`. - `/mockups/tools-clinical-lanes` - Route discovered from app directory Source: `src/app/mockups/tools-clinical-lanes/page.tsx`. - `/mockups/tools-command-center` - Route discovered from app directory Source: `src/app/mockups/tools-command-center/page.tsx`. +- `/mockups/tools-search-directions` - Route discovered from app directory Source: `src/app/mockups/tools-search-directions/page.tsx`. - `/mockups/tools-search-mode` - Route discovered from app directory Source: `src/app/mockups/tools-search-mode/page.tsx`. - `/mockups/tools-split-clinical-brief` - Route discovered from app directory Source: `src/app/mockups/tools-split-clinical-brief/page.tsx`. - `/mockups/tools-split-compact-sheet` - Route discovered from app directory Source: `src/app/mockups/tools-split-compact-sheet/page.tsx`. From 89efbe1fedae1fc215d037f37b34346d1aa99216 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Thu, 13 Aug 2026 18:46:21 +0800 Subject: [PATCH 5/9] fix(mockups): format layout client --- src/app/mockups/mockups-layout-client.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/mockups/mockups-layout-client.tsx b/src/app/mockups/mockups-layout-client.tsx index 46f5d12928..7c4b8ac0f6 100644 --- a/src/app/mockups/mockups-layout-client.tsx +++ b/src/app/mockups/mockups-layout-client.tsx @@ -138,4 +138,4 @@ export function MockupsLayoutClient({ children }: { children: ReactNode }) { {children} ); -} \ No newline at end of file +} From df60dcb6e0c68343247d67bd4e77f4a4743dde05 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:02:30 +0800 Subject: [PATCH 6/9] fix(mockups): align tools study with submitted search --- .../tools-search-directions-mockups.tsx | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/components/tools-search-directions-mockups.tsx b/src/components/tools-search-directions-mockups.tsx index d6ca931982..253b459ae0 100644 --- a/src/components/tools-search-directions-mockups.tsx +++ b/src/components/tools-search-directions-mockups.tsx @@ -46,9 +46,11 @@ import { } from "lucide-react"; import { cn } from "@/components/ui-primitives"; +import { normalizeSearchText } from "@/lib/catalog-search"; import { rankToolRecords, toolCatalogRecordsForSession, + toolSearchText, type ToolCatalogArea, type ToolCatalogRecord, } from "@/lib/tools-catalog"; @@ -99,22 +101,21 @@ const session = { authenticated: true, demoMode: false } as const; const catalogue: MockupTool[] = toolCatalogRecordsForSession(session).map(withIcon); /** - * The matcher the page ships today: one lowercase `.includes()` over a joined string - * (`applications-launcher-page.tsx:769-788`). Reproduced verbatim so directions 01 and 03 show - * the real current result set and ordering, and 02's ranked list can be read against it. + * The matcher used by the submitted /tools results route. Reuse its normalizer and shared + * catalogue search text so the study cannot drift back to the retired launcher matcher. */ -function substringMatches(query: string): MockupTool[] { - const needle = query.trim().toLowerCase(); - if (!needle) return catalogue; - return catalogue.filter((tool) => - [tool.title, tool.mobileTitle, tool.description, tool.bestFor, tool.detail, areaLabels[tool.area], ...tool.keywords] - .filter(Boolean) - .join(" ") - .toLowerCase() - .includes(needle), +function submittedMatches(query: string): MockupTool[] { + const normalized = normalizeSearchText(query); + return catalogue.filter( + (tool) => (!normalized || toolSearchText(tool).includes(normalized)) && tool.id !== "favourites", ); } +/** Focused contract surface: demo-query IDs must stay aligned with the production results route. */ +export function submittedToolIdsForMockup(query: string): string[] { + return submittedMatches(query).map((tool) => tool.id); +} + /** Reason labels as `rankToolRecords` emits them, mapped to reader-facing copy. */ const reasonLabels: Record = { title: "Name", @@ -130,7 +131,7 @@ const rankedMatches = (query: string) => const queryCopy: Record = { monitoring: { label: "monitoring", - note: "Substring finds 3 and ranks the exactly-named tool last. Ranked finds 4, Monitoring first.", + note: "Submitted search finds 4 and puts the exactly-named tool last. Ranked keeps 4, Monitoring first.", }, compare: { label: "compare", @@ -171,7 +172,7 @@ const directions: Array<{ description: "Everything in 01, plus the page finally calls rankToolRecords — which the catalogue already exports and this page has never used. The top match is promoted into an inline brief built from the four catalogue fields the results state currently hides, so the likely answer needs no dialog at all.", changes: [ - "rankToolRecords replaces the substring filter: `monitoring` now returns Monitoring first, not last, and surfaces a fourth match the substring misses.", + "rankToolRecords replaces submitted-order ranking: `monitoring` keeps all four matches and moves Monitoring from last to first.", "Rows carry why they matched, from the ranker's own reasons array, as neutral metadata chips.", "The top match expands inline into Best for / Check first / You'll need / Output — real per-tool catalogue fields, authored for all 13 tools and currently invisible.", "Match-reason chips deliberately use categorical --type-* tones, never the semantic source-backed treatment: a keyword hit must not be able to read as a grounding claim.", @@ -450,7 +451,7 @@ function InstrumentRow({ tool, device }: { tool: MockupTool; device: FrameDevice } function InstrumentDirection({ query, device }: { query: FrameQuery; device: FrameDevice }) { - const tools = substringMatches(queryCopy[query].label); + const tools = submittedMatches(queryCopy[query].label); return (
    @@ -516,10 +517,15 @@ function BriefDirection({ query, device }: { query: FrameQuery; device: FrameDev countLabel={`${matches.length} ${matches.length === 1 ? "tool" : "tools"} · ranked`} device={device} trailing={ - device === "desktop" ? m.tool)} device={device} /> : undefined + device === "desktop" && matches.length > 1 ? ( + match.tool)} device={device} /> + ) : undefined } /> + {device === "phone" && matches.length > 1 ? ( + match.tool)} device={device} /> + ) : null} {/* Promoted top match — the inline brief that removes the dialog for the likely answer. */}
    @@ -677,7 +683,7 @@ function DeckRow({ tool, device, open }: { tool: MockupTool; device: FrameDevice } function DeckDirection({ query, device }: { query: FrameQuery; device: FrameDevice }) { - const tools = substringMatches(queryCopy[query].label); + const tools = submittedMatches(queryCopy[query].label); return (
    From 8dc7ce9037deba42ee3f4a121e8dcb69b4907e70 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Fri, 14 Aug 2026 01:02:31 +0800 Subject: [PATCH 7/9] test(mockups): pin submitted matcher and ranked filters --- tests/tools-search-directions-mockups.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/tools-search-directions-mockups.test.ts b/tests/tools-search-directions-mockups.test.ts index de3a1a79c6..cf602e2041 100644 --- a/tests/tools-search-directions-mockups.test.ts +++ b/tests/tools-search-directions-mockups.test.ts @@ -2,8 +2,19 @@ import { readFileSync } from "node:fs"; import { describe, expect, it } from "vitest"; +import { submittedToolIdsForMockup } from "../src/components/tools-search-directions-mockups"; +import { normalizeSearchText } from "../src/lib/catalog-search"; +import { toolCatalogRecords, toolSearchText } from "../src/lib/tools-catalog"; + const source = readFileSync(new URL("../src/components/tools-search-directions-mockups.tsx", import.meta.url), "utf8"); +function productionSubmittedToolIds(query: string): string[] { + const normalized = normalizeSearchText(query); + return toolCatalogRecords + .filter((tool) => (!normalized || toolSearchText(tool).includes(normalized)) && tool.id !== "favourites") + .map((tool) => tool.id); +} + describe("tools search direction mockup evidence", () => { it("renders frames at the widths named by their captions", () => { expect(source).toContain('device === "phone" ? "w-[390px]" : "w-[1280px]"'); @@ -18,4 +29,15 @@ describe("tools search direction mockup evidence", () => { expect(showcase).toContain(''); expect(showcase).toContain(''); }); + + it.each(["monitoring", "compare"])("uses the production submitted matcher for %s", (query) => { + expect(submittedToolIdsForMockup(query)).toEqual(productionSubmittedToolIds(query)); + }); + + it("keeps the ranked filter only for multi-match frames at both widths", () => { + const brief = source.slice(source.indexOf("function BriefDirection"), source.indexOf("function SegmentedCountBand")); + expect(brief).toContain('device === "desktop" && matches.length > 1'); + expect(brief).toContain('device === "phone" && matches.length > 1'); + expect(brief).not.toContain('device === "desktop" ? Date: Fri, 14 Aug 2026 01:05:51 +0800 Subject: [PATCH 8/9] style(mockups): format search direction regression test --- tests/tools-search-directions-mockups.test.ts | 59 +++++++++++++++---- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/tests/tools-search-directions-mockups.test.ts b/tests/tools-search-directions-mockups.test.ts index cf602e2041..51cf0adc8c 100644 --- a/tests/tools-search-directions-mockups.test.ts +++ b/tests/tools-search-directions-mockups.test.ts @@ -6,36 +6,69 @@ import { submittedToolIdsForMockup } from "../src/components/tools-search-direct import { normalizeSearchText } from "../src/lib/catalog-search"; import { toolCatalogRecords, toolSearchText } from "../src/lib/tools-catalog"; -const source = readFileSync(new URL("../src/components/tools-search-directions-mockups.tsx", import.meta.url), "utf8"); +const source = readFileSync( + new URL( + "../src/components/tools-search-directions-mockups.tsx", + import.meta.url, + ), + "utf8", +); function productionSubmittedToolIds(query: string): string[] { const normalized = normalizeSearchText(query); return toolCatalogRecords - .filter((tool) => (!normalized || toolSearchText(tool).includes(normalized)) && tool.id !== "favourites") + .filter( + (tool) => + (!normalized || toolSearchText(tool).includes(normalized)) && + tool.id !== "favourites", + ) .map((tool) => tool.id); } describe("tools search direction mockup evidence", () => { it("renders frames at the widths named by their captions", () => { expect(source).toContain('device === "phone" ? "w-[390px]" : "w-[1280px]"'); - expect(source).toContain('device === "phone" ? "Phone 390" : "Desktop 1280"'); - expect(source).not.toContain('device === "phone" ? "w-[340px]" : "w-full max-w-[900px]"'); + expect(source).toContain( + 'device === "phone" ? "Phone 390" : "Desktop 1280"', + ); + expect(source).not.toContain( + 'device === "phone" ? "w-[340px]" : "w-full max-w-[900px]"', + ); }); it("shows both representative queries at both widths for every direction", () => { - const showcase = source.slice(source.indexOf("function DirectionShowcase"), source.indexOf("const currentDefects")); - expect(showcase).toContain(''); - expect(showcase).toContain(''); - expect(showcase).toContain(''); - expect(showcase).toContain(''); + const showcase = source.slice( + source.indexOf("function DirectionShowcase"), + source.indexOf("const currentDefects"), + ); + expect(showcase).toContain( + '', + ); + expect(showcase).toContain( + '', + ); + expect(showcase).toContain( + '', + ); + expect(showcase).toContain( + '', + ); }); - it.each(["monitoring", "compare"])("uses the production submitted matcher for %s", (query) => { - expect(submittedToolIdsForMockup(query)).toEqual(productionSubmittedToolIds(query)); - }); + it.each(["monitoring", "compare"])( + "uses the production submitted matcher for %s", + (query) => { + expect(submittedToolIdsForMockup(query)).toEqual( + productionSubmittedToolIds(query), + ); + }, + ); it("keeps the ranked filter only for multi-match frames at both widths", () => { - const brief = source.slice(source.indexOf("function BriefDirection"), source.indexOf("function SegmentedCountBand")); + const brief = source.slice( + source.indexOf("function BriefDirection"), + source.indexOf("function SegmentedCountBand"), + ); expect(brief).toContain('device === "desktop" && matches.length > 1'); expect(brief).toContain('device === "phone" && matches.length > 1'); expect(brief).not.toContain('device === "desktop" ? Date: Fri, 14 Aug 2026 01:09:54 +0800 Subject: [PATCH 9/9] style(mockups): apply repository Prettier width --- tests/tools-search-directions-mockups.test.ts | 54 +++++-------------- 1 file changed, 12 insertions(+), 42 deletions(-) diff --git a/tests/tools-search-directions-mockups.test.ts b/tests/tools-search-directions-mockups.test.ts index 51cf0adc8c..5f09bfffa7 100644 --- a/tests/tools-search-directions-mockups.test.ts +++ b/tests/tools-search-directions-mockups.test.ts @@ -6,63 +6,33 @@ import { submittedToolIdsForMockup } from "../src/components/tools-search-direct import { normalizeSearchText } from "../src/lib/catalog-search"; import { toolCatalogRecords, toolSearchText } from "../src/lib/tools-catalog"; -const source = readFileSync( - new URL( - "../src/components/tools-search-directions-mockups.tsx", - import.meta.url, - ), - "utf8", -); +const source = readFileSync(new URL("../src/components/tools-search-directions-mockups.tsx", import.meta.url), "utf8"); function productionSubmittedToolIds(query: string): string[] { const normalized = normalizeSearchText(query); return toolCatalogRecords - .filter( - (tool) => - (!normalized || toolSearchText(tool).includes(normalized)) && - tool.id !== "favourites", - ) + .filter((tool) => (!normalized || toolSearchText(tool).includes(normalized)) && tool.id !== "favourites") .map((tool) => tool.id); } describe("tools search direction mockup evidence", () => { it("renders frames at the widths named by their captions", () => { expect(source).toContain('device === "phone" ? "w-[390px]" : "w-[1280px]"'); - expect(source).toContain( - 'device === "phone" ? "Phone 390" : "Desktop 1280"', - ); - expect(source).not.toContain( - 'device === "phone" ? "w-[340px]" : "w-full max-w-[900px]"', - ); + expect(source).toContain('device === "phone" ? "Phone 390" : "Desktop 1280"'); + expect(source).not.toContain('device === "phone" ? "w-[340px]" : "w-full max-w-[900px]"'); }); it("shows both representative queries at both widths for every direction", () => { - const showcase = source.slice( - source.indexOf("function DirectionShowcase"), - source.indexOf("const currentDefects"), - ); - expect(showcase).toContain( - '', - ); - expect(showcase).toContain( - '', - ); - expect(showcase).toContain( - '', - ); - expect(showcase).toContain( - '', - ); + const showcase = source.slice(source.indexOf("function DirectionShowcase"), source.indexOf("const currentDefects")); + expect(showcase).toContain(''); + expect(showcase).toContain(''); + expect(showcase).toContain(''); + expect(showcase).toContain(''); }); - it.each(["monitoring", "compare"])( - "uses the production submitted matcher for %s", - (query) => { - expect(submittedToolIdsForMockup(query)).toEqual( - productionSubmittedToolIds(query), - ); - }, - ); + it.each(["monitoring", "compare"])("uses the production submitted matcher for %s", (query) => { + expect(submittedToolIdsForMockup(query)).toEqual(productionSubmittedToolIds(query)); + }); it("keeps the ranked filter only for multi-match frames at both widths", () => { const brief = source.slice(