Skip to content

cppa-cursor-browser: Search UX polish and distinct /api/search error codes #117

Description

@clean6378-max-it

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

  1. Make search-window behavior discoverable in the product UI without changing search semantics.
  2. Return appropriate HTTP status codes from /api/search so 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 — optional searchWindowNote string 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-range since_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 (OSErrorstorage_unavailable)
    • 500 — unexpected internal errors only; log full traceback server-side
  • Each error response: {"error": "<human-readable>", "code": "<machine-readable>"} (no results key 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_WINDOW behavior
  • 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 — only OperationalError for index lock today

PR #126 review notes

  • Breaking change: Error responses use {"error", "code"} only; legacy 500 bodies that included "results": [] are removed.
  • Bonus scope: workspace query param + post-filter (not in search UI).
  • 503 mapping: OSError during workspace path resolution → storage_unavailable (retryable config/discovery failure).
  • Frontend: Error paragraph carries data-error-code for 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_history checkbox behavior unchanged; shared links with all_history=1 / true still work
  • No regression in tests/test_api_search.py / TestSearchWindow

Part B — API errors

  • Empty or whitespace q still 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 pytest and mypy --strict pass
  • 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.

Metadata

Metadata

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions