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/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 (OSError → storage_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
Part B — API errors
General
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.
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 = Trueinservices/search.py— matching index SQL(updated_ms >= ? OR updated_ms <= 0). This is documented in code (resolve_search_since_msdocstring) 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.pywraps the entire handler inexcept Exceptionand returns500with{"error": "Search failed", "results": []}for all failures. Emptyqalready 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
/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:all_history=1/ checkbox searches full historyapi/search.py— optionalsearchWindowNotestring in JSON payload only if needed for UI; prefer HTML-only if sufficientPart B — API error status codes
api/search.py— narrow exception handling:type, out-of-rangesince_days, max query length (>500 chars)sqlite3.OperationalError); workspace storage discovery failure (OSError→storage_unavailable){"error": "<human-readable>", "code": "<machine-readable>"}(noresultskey on errors — breaking change vs. legacy 500 bodies)workspace=<hash>query param post-filters results (API-only; search UI does not expose it)tests/test_api_search.py— parametrised cases for all four codesOut of scope
_INCLUDE_UNKNOWN_TIMESTAMPS_IN_WINDOWbehaviorsqlite3.DatabaseError) to 503 — onlyOperationalErrorfor index lock todayPR #126 review notes
{"error", "code"}only; legacy500bodies that included"results": []are removed.workspacequery param + post-filter (not in search UI).OSErrorduring workspace path resolution →storage_unavailable(retryable config/discovery failure).data-error-codefor operator debugging; result-count banner mentions undated chats when windowed.Acceptance Criteria
Part A — UI
all_historycheckbox behavior unchanged; shared links withall_history=1/truestill worktests/test_api_search.py/TestSearchWindowPart B — API errors
qstill returns 400exception()tests/test_api_search.pyGeneral
pytestandmypy --strictpassVerification
Manual: open
/search, confirm tooltip/helper text; toggle “all history” and confirm copy updates if applicable.