pocket-es Search UX Spec: Dual-Input Redesign

Table of Contents

1. Context

The input layer is broken in both directions. The placeholder advertises a raw ES payload, but typing plain text produces no results, and typing valid JSON also fails silently. The single box conflates two incompatible interaction models. This spec replaces it with two explicit modes.

2. Goals

  • Common case (casual visitor typing a word) works by default
  • Full ES DSL power for advanced users behind an explicit toggle
  • Both paths feed a single shared result renderer
  • No changes to the engine API (window.pocketES)

3. Component Structure

Two mutually exclusive input modes plus shared output:

3.1. Simple mode (default)

Single-line <input class"pes-input–simple">= for plain-text queries. Live autocomplete wired to .pes-suggestions.

3.2. Advanced mode

Multi-line <textarea class"pes-input–advanced">= for hand-written ES DSL payloads. Revealed by toggle (e.g. "Advanced: raw ES query").

3.3. Shared

Single #pes-results panel and one render function consumed by both modes.

4. Behavior

4.1. Simple mode

On input (debounced ~150ms):

{:query {:multi_match {:query <text>
                       :fields ["title^3" "keywords^2" "description" "headings"]}}}
  • Empty input clears results to neutral placeholder
  • .pes-suggestions populated from pocketES.suggest({prefix: <text>})
  • Selecting a suggestion fills input and runs search
  • Suggestions cap at ~8 items

4.2. Advanced mode

  • Textarea contents parsed as JSON, passed to pocketES.search(...)
  • Debounced ~300ms or explicit "Run" action
  • Placeholder shows representative payload
  • "Copy as console call" affordance (nice-to-have)

4.3. Placeholder copy

  • Simple: "Search the site..."
  • Advanced: ES DSL template
  • Current ES-DSL placeholder on a plain search box must not survive

5. Result Rendering

Single render function, three states:

State Behavior
Hits Title, path link, visible BM25 _score, ordered descending
Empty "No matches for <query>" — not the idle placeholder
Idle "Type to search..." when input is empty

6. Error Handling (Advanced Mode)

Silent failure is eliminated:

  • Invalid JSON: inline message Invalid JSON: <reason>
  • Malformed query: Could not run query: <reason>
  • Users must distinguish "no results" from "query didn't execute"

7. Acceptance Criteria

  1. Default to simple mode with plain-language placeholder
  2. Typing clojure renders BM25-ordered hits
  3. Prefix typing populates suggestions; selecting one runs search
  4. Zero-match query shows explicit empty-state message
  5. Advanced mode toggle reveals textarea; valid payloads render results
  6. Invalid JSON or unsupported clause renders visible error
  7. Each hit shows or is ordered by BM25 score
  8. No changes to window.pocketES engine API

8. Out of Scope

Engine/algorithm changes, index build pipeline, data-source loading paths. Confined to ClojureScript input rendering and result-display layer.

9. Status

Tracked as www.wal.sh-nf1s. Blocked on stable Clojure e2e in prod.