Skip to content

feat(app): persist search history with a clickable recent-searches dropdown#222

Open
zengtianli wants to merge 1 commit into
cardisoft:masterfrom
zengtianli:pr/persist-search-history
Open

feat(app): persist search history with a clickable recent-searches dropdown#222
zengtianli wants to merge 1 commit into
cardisoft:masterfrom
zengtianli:pr/persist-search-history

Conversation

@zengtianli

Copy link
Copy Markdown

Intro

This PR adds a persistent search history to the file-search bar: past queries are saved across app restarts and can be re-run with a single click from a small clock-icon dropdown next to the search options.

It builds directly on top of the in-memory ArrowUp/ArrowDown history from #31 rather than replacing it — see How it relates to #31 below.

What & why

The ArrowUp/ArrowDown history added in #31 is great for cycling through queries within a session, but it lives entirely in memory and disappears on restart, and it's only reachable via the keyboard. This PR complements it with:

  • Persistence — queries survive an app restart (stored in localStorage under cardinal.recentSearches, newest-first, deduped, capped at 50 entries).
  • Discoverability — a clock-icon toggle opens a dropdown listing recent queries; clicking one re-runs it immediately. Includes a "clear history" action, and closes on Esc or an outside click.
  • Smarter recording — history is recorded not only when a query is submitted, but also when a result is actually opened (double-click, Cmd+O, or the context menu), on the reasoning that a search which led to an open is a search worth remembering.

Implementation

Key files:

File Change
src/hooks/useRecentSearches.ts New hook. Wraps the existing useStoredState to persist a newest-first, deduped, capped list; exposes recordSearch / clearRecentSearches. Trims blanks and ignores malformed persisted payloads.
src/utils/openResultPath.ts Adds a tiny subscribeResultOpened pub/sub so any result-open path (double-click / Cmd+O / context menu) can notify subscribers without prop-drilling.
src/hooks/useFilesTabState.ts New optional onQueryCommitted callback, fired when a query is submitted, so committed queries flow into history.
src/components/SearchBar.tsx New history toggle button + dropdown panel (list, empty state, clear action), with Esc / outside-click handling and ARIA (aria-haspopup / aria-expanded on the toggle, labelled panel, native <button> semantics for entries). All strings are props.
src/App.tsx Wires the hook to the SearchBar, subscribes to subscribeResultOpened, and passes i18n'd labels.
src/App.css Styles for the toggle and panel, using the existing CSS variables so it themes correctly in light/dark.
src/i18n/resources/*.json New search.history.{toggle,empty,clear} keys across all 15 locales.
src/hooks/__tests__/useRecentSearches.test.ts 6 unit tests (see Testing).

Notes:

  • Reuses the existing useStoredState helper rather than hand-rolling storage access.
  • The history UI is gated to the Files tab (historyEnabled) so it doesn't appear where it wouldn't make sense.
  • Styling relies entirely on existing theme variables (--color-accent, --color-elevated-bg, etc.), so light/dark both work with no extra tokens.

How it relates to #31

This is complementary, not a replacement:

  • feat: navigate search history using ArrowUp/ArrowDown #31 (useSearchHistory.ts) gives in-memory ArrowUp/ArrowDown cycling within a session — fast keyboard recall while you work.
  • This PR persists queries to localStorage across sessions and surfaces them in a clickable dropdown — durable recall and mouse discoverability.

The two coexist: the keyboard history from #31 is untouched (a separate hook file), and this PR layers persistence + a visible UI on top.

Testing

Ran from cardinal/:

  • npm run format:check — clean.
  • npm run typecheck (tsc --noEmit) — clean, no type errors.
  • npm test -- --run (vitest) — all green (278 passed), including 6 new unit tests for useRecentSearches covering: newest-first ordering + persistence, blank/whitespace trimming, dedup-to-front, cap at max entries, restore-on-mount + clear, and rejecting malformed persisted payloads.
  • npm run build (vite) — succeeds.
  • Built the Tauri app and manually verified end-to-end: ran several searches, opened results, confirmed the dropdown re-runs past queries, then restarted the app and confirmed history persisted.

No Rust was touched.

Screenshots

A screenshot / GIF of the dropdown in action can be added here on request.

…opdown

Complements the in-memory ArrowUp/ArrowDown history from cardisoft#31 (useSearchHistory.ts)
by persisting queries across app restarts and surfacing them in a clickable panel.

- New useRecentSearches hook wraps useStoredState to persist a newest-first,
  deduped, capped-at-50 list under localStorage key cardinal.recentSearches.
- History records on query submit AND on opening a result (double-click / Cmd+O /
  context menu) via a small subscribeResultOpened pub/sub in openResultPath.
- SearchBar gains a clock-icon toggle + dropdown (re-run a query, clear history,
  Esc / outside-click to close); native button semantics, aria-haspopup on toggle.
- Fully i18n'd across all 15 locales; light/dark via existing CSS variables.
- 6 unit tests for ordering, trimming, dedup, cap, restore/clear, malformed payload.
zengtianli added a commit to zengtianli/cardinal that referenced this pull request Jul 19, 2026
@ldm0
ldm0 self-requested a review July 19, 2026 13:54
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.

1 participant