From 147b3b1507ebcdf5912fbd4c474c9dfb35265148 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Fri, 6 Nov 2026 03:18:21 +0000 Subject: castle-search: read Brave again, and report genuine zero-hit searches as such Brave: #results no longer exists on the live page; fall back to #main/main. It was reported as 'no results area at all', i.e. a rate limit. DuckDuckGo: its 'No results found' notice is itself a .result, so an empty search looked like broken markup. emptyMarker lets the probe say the engine itself reported no matches, and the error tells the caller to loosen the query instead of switching engines. --- castle-search/src/engines.ts | 13 ++++++++++++- castle-search/src/extract.ts | 9 +++++++++ castle-search/src/search.ts | 12 ++++++++++++ castle-search/test/extraction-config.test.ts | 16 ++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 castle-search/test/extraction-config.test.ts diff --git a/castle-search/src/engines.ts b/castle-search/src/engines.ts index 76716c6..fb07649 100644 --- a/castle-search/src/engines.ts +++ b/castle-search/src/engines.ts @@ -45,6 +45,12 @@ export type ExtractionConfig = { mode: "items" | "headings"; /** Results container candidates, first match wins. */ roots: string[]; + /** + * Present only on the engine's own "nothing matched" page. Without it a page + * that genuinely has zero hits is indistinguishable from broken markup, and + * the error blames the extractor for what is just an over-narrow query. + */ + emptyMarker?: string; /** "items" mode: one result. */ item?: string; /** Anchor carrying the outbound link, relative to the item. */ @@ -124,6 +130,9 @@ const DUCKDUCKGO: EngineDefinition = { extraction: { mode: "items", roots: [".results", "#links"], + // Seen 2026-11-06: `