Skip to content

feat(frontend): search and paginate saved documents - #50

Merged
johnnyhuy merged 1 commit into
mainfrom
agent/issue-49-document-search-dashboard
Nov 15, 2026
Merged

johnnyhuy merged 1 commit into
mainfrom
agent/issue-49-document-search-dashboard

Conversation

@johnnyhuy

Copy link
Copy Markdown
Contributor

The Documents tab now searches saved titles and full content through the existing literal-search API, with 20-result pages and a clear action that returns to the first page. Search and pagination clear selected details, failed requests can be retried, and cancelled requests cannot overwrite newer results.

Part of #5. Closes #49.

Verification

  • UV_PYTHON=3.13 mise run install passed for backend, frontend and runtime. The first attempt selected Python 3.14, which lacks a wheel for pinned onnxruntime; selecting the repo's Python 3.13 resolved it. Dependency lock files are unchanged.
  • mise run test-backend passed, 196 tests, 731 warnings. The command used UV_PYTHON=3.13, fixture Slack credentials, blank provider keys and DATABASE_URL=sqlite:///.tmp-tests.db.
  • mise run test-frontend passed, 22 tests including 7 new document tests. The new tests exercise form submission, literal query encoding, source-filter compatibility, paging with the applied query, clearing, loading, empty states, retries, rendering saved markup as text, stale list/detail responses and tab cleanup.
  • mise run test-runtime passed, 7 offline tests.
  • mise exec -- bun ./node_modules/typescript/bin/tsc --noEmit from frontend/ passed.
  • mise run dev-backend reached Application startup complete and served the transcript below, then was stopped. It used Python 3.13, a disposable local SQLite database and a sitecustomize.py hook that mocked Slack authentication and blocked Slack API calls and Socket Mode startup. No live token or real .env was read or changed.
  • mise run dev-frontend started, served the changed dashboard and was stopped after Playwright capture. The dashboard used the real local API with 23 fixture documents, including 21 VPN matches. No Slack, embedding or model request was needed.
  • git diff --check passed. The UI detector returned no findings. Desktop and 390-pixel mobile screenshots were inspected.

Proof

Capture method (b) succeeded with screenshots and the complete frontend test output below. Method (a) also succeeded with backend tests and a curl transcript against the local backend. No capture path remains blocked. This is a dashboard slice; retrieval scoring and RAG behaviour are unchanged, so a before/after retrieval transcript does not apply.

Desktop screenshot and mobile screenshot show the applied VPN search, the last result on page two, a disabled Next button and the selected document's full content. Both files are committed under .tmp/proof/ so the evidence survives this worktree.

Desktop document search

Mobile document search

Mobile document search

Frontend test output including the new test names
[test-frontend] $ bun run test
$ bun test --preload ./tests/setup.ts
bun test v1.3.5 (1e86cebd)

tests/documents.test.tsx:
(pass) documents load on opening the tab, search only on submit, and retain the applied query while paging [155.54ms]
(pass) document client encodes literal search text, keeps source filters, and omits blank queries [0.10ms]
(pass) documents show loading, retry the submitted query after a list error, and distinguish empty searches [14.18ms]
(pass) documents render saved content as text and retry detail failures without losing results [23.69ms]
(pass) documents ignore stale successful and failed list responses after a newer search [9.77ms]
(pass) documents ignore late details after another selection, a page change, and clearing a search [33.78ms]
(pass) documents abort requests on leaving the tab and ignore late responses when reopened [6.82ms]

tests/retrieval.test.tsx:
(pass) retrieval waits for a non-blank question and submits trimmed optional scope [11.55ms]
(pass) retrieval omits blank scope, preserves rank and raw distances, and shows an unset cutoff [8.73ms]
(pass) retrieval disables inputs and duplicate submissions while loading [5.61ms]
(pass) retrieval shows an empty result and a zero distance cutoff [8.69ms]
(pass) retrieval replaces a failed request with a successful retry [9.07ms]
(pass) retrieval clears old results when the question or channel changes [15.20ms]
(pass) retrieval aborts on leaving the tab and ignores late responses after reopening [10.48ms]
(pass) retrieval renders chunk and metadata markup as text [6.54ms]

tests/conversations.test.tsx:
(pass) loads only on opening the tab and paginates summaries and chronological turns independently [123.27ms]
(pass) shows loading, an empty page and a list error with a working retry [6.83ms]
(pass) retries missing conversation details without discarding the list [19.53ms]
(pass) ignores stale detail responses after selecting another conversation [13.78ms]
(pass) ignores stale errors after leaving and reopening the tab [11.89ms]
(pass) renders stored markup as text and supports missing metadata [9.68ms]
(pass) encodes conversation IDs in detail request paths [1.65ms]

 22 pass
 0 fail
 187 expect() calls
Ran 22 tests across 3 files. [842.00ms]
Backend search regression tests and suite result
tests/test_documents.py::TestSearchDocuments::test_matches_title_case_insensitively_and_trims_query[vpn] PASSED [ 35%]
tests/test_documents.py::TestSearchDocuments::test_matches_title_case_insensitively_and_trims_query[VpN] PASSED [ 36%]
tests/test_documents.py::TestSearchDocuments::test_matches_title_case_insensitively_and_trims_query[ \tVPN\n ] PASSED [ 36%]
tests/test_documents.py::TestSearchDocuments::test_matches_content_beyond_the_preview PASSED [ 37%]
tests/test_documents.py::TestSearchDocuments::test_filters_before_counting_and_paginating PASSED [ 37%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-%] PASSED [ 38%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-_] PASSED [ 38%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-/] PASSED [ 39%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-100%] PASSED [ 39%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-setup_notes] PASSED [ 40%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-a/b] PASSED [ 40%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[title-' OR 1=1 --] PASSED [ 41%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-%] PASSED [ 41%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-_] PASSED [ 42%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-/] PASSED [ 42%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-100%] PASSED [ 43%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-setup_notes] PASSED [ 43%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-a/b] PASSED [ 44%]
tests/test_documents.py::TestSearchDocuments::test_treats_sql_wildcards_and_escape_characters_literally[content-' OR 1=1 --] PASSED [ 44%]
tests/test_documents.py::TestSearchDocuments::test_blank_query_preserves_source_filter_and_existing_list[] PASSED [ 45%]
tests/test_documents.py::TestSearchDocuments::test_blank_query_preserves_source_filter_and_existing_list[ \t\n ] PASSED [ 45%]
tests/test_documents.py::TestSearchDocuments::test_blank_query_preserves_source_filter_and_existing_list[\u2003\xa0] PASSED [ 46%]
tests/test_documents.py::TestSearchDocuments::test_no_matches_return_an_empty_page[params0] PASSED [ 46%]
tests/test_documents.py::TestSearchDocuments::test_no_matches_return_an_empty_page[params1] PASSED [ 47%]
====================== 196 passed, 731 warnings in 7.51s =======================
Local API transcript with fixture records

The submitted whitespace is trimmed, matching documents are counted before pagination, and a missing search returns an empty page.

$ curl -sS -i --get http://127.0.0.1:8000/api/documents --data-urlencode 'q=  VPN  ' --data-urlencode limit=20 --data-urlencode offset=20
HTTP/1.1 200 OK
date: Sat, 14 Nov 2026 22:22:58 GMT
server: uvicorn
content-length: 268
content-type: application/json

{"items":[{"id":1,"title":"VPN guide 01","source":"fixture","created_at":"2026-11-14T09:01:00","content_preview":"Fixture document 1. Reset VPN access through the IT portal.\n\nUse your staff account and follow the recovery steps."}],"total":21,"limit":20,"offset":20}

$ curl -sS -i --get http://127.0.0.1:8000/api/documents --data-urlencode q=missing --data-urlencode limit=20 --data-urlencode offset=0
HTTP/1.1 200 OK
date: Sat, 14 Nov 2026 22:22:58 GMT
server: uvicorn
content-length: 44
content-type: application/json

{"items":[],"total":0,"limit":20,"offset":0}

Review notes

The existing API contract and database schema are unchanged. Search is applied on form submission; editing the input does not send requests or change the query used by pagination and retry. Leaving the Documents tab cancels pending requests and reopening starts with the unfiltered first page.

All checks and screenshots used fixtures or mocks. No live Slack connection is required to verify this slice, and none was made. PostgreSQL-specific collation and large-dataset performance were not measured. The parent remains open for the remaining management dashboard work; #49 stays open until this PR merges.

Co-authored-by: OpenAI Codex <noreply@openai.com>
@johnnyhuy
johnnyhuy merged commit b1e0b7b into main Nov 15, 2026
10 checks passed
@johnnyhuy
johnnyhuy deleted the agent/issue-49-document-search-dashboard branch November 15, 2026 02:18
@github-actions github-actions Bot mentioned this pull request Nov 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Search and paginate saved documents in the dashboard

1 participant