PostgreSQL FTS supports !term but the /api/search endpoint
sanitises away anything that looks like punctuation — making "NOT
microglia" impossible. Researchers routinely want to find "alpha-syn
hypotheses NOT involving propagation models" or "TREM2 papers NOT
about Alzheimer's". Build first-class boolean negation, plus
field-scoped negation (-target_gene:APOE) and entity-aware negation
(-entity:microglia resolves to all microglia synonyms via
canonical_entity_links).
scidex/atlas/search_query_parser.py::parse(q) -> ParsedQuery supporting tokens: bare term, +term, -term, field:term, -field:term, entity:NAME, -entity:NAME.not microglia parsed as -entity:microglia.-(syn1 | syn2 | ...) via canonical lookup (expand_entity_synonyms())./api/search (all 6 FTS tables) and /api/search/fts endpoint (fixed from broken SQLite FTS5 to PostgreSQL search_vector)./search UI gets a "Refine" panel showing parsed terms and lets user click × to remove a clause."alpha-synuclein -entity:microglia" returns 10 hypotheses, all verified microglia-free._tokenize() handles all prefix/field/entity variants.canonical_entities.canonical_name + aliases (PostgreSQL JSONB).WHERE field NOT ILIKE %s; exposed via build_field_conditions().api.py calls it with try/except to fall back to plain plainto_tsquery on any error./api/search/parse endpoint for the UI to fetch parsed tokens without running the full search.q-srch-hybrid-rerank (rerank operates on the parsed result list).scidex/atlas/search_query_parser.py with QueryToken, ParsedQuery, parse(), expand_entity_synonyms(), build_pg_tsquery_sql(), build_field_conditions().api.py (/api/search): replaced hard-coded plainto_tsquery in 6 FTS tables (hypotheses, analyses, wiki_pages, papers, knowledge_gaps, notebooks) with the parser's tsquery expression. Added parsed_tokens to response.api.py (/api/search/fts): rewrote from broken SQLite FTS5 (bm25, MATCH ?) to PostgreSQL search_vector @@ tsquery with parser support./api/search/parse endpoint (no rate limit, read-only).site/search.html: added Refine panel with chip CSS, renderRefinePanel() function, and chip-remove event handler.plainto_tsquery.