Repository navigation
cppa-cursor-browser: Search UX polish and distinct /api/search error codes #117
Description
Calendar Day
Week 5 carryover from PR #113 (PR 2 of 2 — deferred review items 12 and 13)
Planned Effort
5 story points (Medium) — sprint items #12 + #13
Problem
Two search follow-ups were deferred from PR #113:
A — Window behavior invisible to users (item 12). Search defaults to a 30-day window (DEFAULT_SEARCH_WINDOW_DAYS, all_history=0). Composers with no parseable timestamp (updated_ms <= 0) remain searchable because _INCLUDE_UNKNOWN_TIMESTAMPS_IN_WINDOW = True in services/search.py — matching index SQL (updated_ms >= ? OR updated_ms <= 0). This is documented in code (resolve_search_since_ms docstring) but not in the UI. Users who expect a strict cutoff may be surprised when old or undated chats still appear. The “Include chats older than 30 days” checkbox exists but does not explain undated chats.
B — Generic 500 on all search failures (item 13). api/search.py wraps the entire handler in except Exception and returns 500 with {"error": "Search failed", "results": []} for all failures. Empty q already returns 400, but infrastructure failures (missing workspace path, DB open errors, unexpected bugs) are indistinguishable from recoverable input problems. API consumers and the frontend cannot tell whether to retry, fix configuration, or show a validation message.
Goal
- Make search-window behavior discoverable in the product UI without changing search semantics.
- Return appropriate HTTP status codes from
/api/searchso clients can branch on error class, without leaking sensitive exception details.
Scope
Part A — Search window UI
templates/search.html— tooltip or helper text on#all-history(and/or near result count) explaining:- default 30-day window
- undated chats may still appear unless excluded by other filters
all_history=1/ checkbox searches full history
- Optional: one line in search result count banner when window is active (e.g. “Showing last 30 days; undated chats included”)
api/search.py— optionalsearchWindowNotestring in JSON payload only if needed for UI; prefer HTML-only if sufficient
Part B — API error status codes
api/search.py— narrow exception handling:- 400 — malformed/empty query, invalid
type, out-of-rangesince_days, max query length (>500 chars) - 404 — workspace-not-found or invalid workspace hash
- 503 — search index locked/rebuilding (
sqlite3.OperationalError); workspace storage discovery failure (OSError→storage_unavailable) - 500 — unexpected internal errors only; log full traceback server-side
- 400 — malformed/empty query, invalid
- Each error response:
{"error": "<human-readable>", "code": "<machine-readable>"}(noresultskey on errors — breaking change vs. legacy 500 bodies) - Optional bonus:
workspace=<hash>query param post-filters results (API-only; search UI does not expose it) - Frontend search handler displays the human-readable error from the response body
tests/test_api_search.py— parametrised cases for all four codes
Out of scope
- Changing
_INCLUDE_UNKNOWN_TIMESTAMPS_IN_WINDOWbehavior - FTS vs live-scan semantics refactor
- Structured errors for all blueprints (repo-wide horizon)
- Search module duplication extraction (T21 — deferred)
- Mapping other SQLite errors (e.g.
sqlite3.DatabaseError) to 503 — onlyOperationalErrorfor index lock today
PR #126 review notes
- Breaking change: Error responses use
{"error", "code"}only; legacy500bodies that included"results": []are removed. - Bonus scope:
workspacequery param + post-filter (not in search UI). - 503 mapping:
OSErrorduring workspace path resolution →storage_unavailable(retryable config/discovery failure). - Frontend: Error paragraph carries
data-error-codefor operator debugging; result-count banner mentions undated chats when windowed.
Acceptance Criteria
Part A — UI
- Search page explains default 30-day window in plain language
- Users can discover why undated conversations may appear in windowed search
-
all_historycheckbox behavior unchanged; shared links withall_history=1/truestill work - No regression in
tests/test_api_search.py/TestSearchWindow
Part B — API errors
- Empty or whitespace
qstill returns 400 - At least one additional distinguishable client error returns 400 (not 500) when provoked in tests
- Storage/path failures return a non-500 code where appropriate (document choice in PR)
- Unexpected exceptions still return 500 and are logged with
exception() - No raw exception strings or stack traces in JSON responses
- New/updated tests in
tests/test_api_search.py
General
- Full
pytestandmypy --strictpass - PR approved by at least 1 reviewer
Verification
cd C:\Users\Jasen\CppAliance\cppa-cursor-browser
.\.venv\Scripts\Activate.ps1
pytest tests/test_api_search.py -q
pytest -q
mypy .Manual: open /search, confirm tooltip/helper text; toggle “all history” and confirm copy updates if applicable.
Activity
Search UX tooltip and distinct /api/search error codes (#117)
fix(#117): address PR #126 review feedback on search UX and errors
Search UX tooltip and distinct /api/search error codes (#117) (#126)