Conversation
Session metadata is E2EE, so the server cannot index titles; the filter runs client-side over rows the app has already decrypted. Matches the row title with the working path as fallback (untitled sessions all share "New chat"), case-insensitive substring. The search box is extracted into SessionSearchInput and stays mounted in every list state — populated, empty account, no-results — with the query owned by SessionsListWrapper and passed down as a controlled prop. Archive toggle hides while a search is active (its click cannot bring anything back under a filter). Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The search box no longer sits permanently above the list. A search icon next to "New session" (sidebar top controls) and beside "+" (phone/web header) toggles it. State lives in a small zustand store so the icon and the list share it without a prop chain; closing the box clears the query so the list never stays filtered behind a hidden input. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
The boot fetch stops at the server's 150 most recent sessions, so the search silently missed everything older (on a real account: 685 of 835 sessions, anything before the last week). Opening the search now pages /v2/sessions to the end and merges the rows the boot fetch did not bring in. The decrypt+apply path is extracted from fetchSessions into ingestSessions so both callers share it. Runs only on demand and once per app run — the list itself still boots at 150. A spinner sits in the box while history pages in; a failed page lets the next open retry instead of pinning the failure. Generated with [Claude Code](https://claude.ai/code) via [Happy](https://happy.engineering) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: Happy <yesreply@happy.engineering>
f-liva
force-pushed
the
pr/session-search
branch
from
September 16, 2026 08:56
d7ba944 to
6f0f6fb
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Search sessions by title from the home list. A search icon sits next to New session in the sidebar (and beside + in the phone/web header); it toggles a text box that filters the list live. Opening the search also pages in every session older than the 150 the app boots with, so the filter covers the whole account, not just the last week.
Why
The web app had no way to find a session other than scrolling. With a few hundred sessions the list is unusable as a lookup, and the boot fetch's 150-row cap (
GET /v1/sessions,take: 150) hides anything older entirely: on one real account 685 of 835 sessions were unreachable from the UI.Search has to be client-side: session metadata (where the title lives) is end-to-end encrypted — the CLI encrypts it before
update-metadata, the server stores an opaque blob — so the server cannot index or match titles. The filter runs over rows the client has already decrypted.Change
useVisibleSessionListViewData(searchQuery?)— case-insensitive substring match on the row title (session.name: agent summary or bot name) with the working path as fallback, because untitled sessions all render as "New chat" and only the path tells them apart. Applies to flat rows, project cards, bots and the active-sessions group; the existing archive rule still applies; emptied headers and cards drop out.SessionSearchInput+sessionSearchStore(zustand) — the box, and open/query state shared between the icon and the list without a prop chain (same pattern ashomeDockFocus). Closing clears the query so the list never stays filtered behind a hidden input. The archive toggle hides while a query is active (its click cannot bring anything back under a filter).SessionsListWrapperowns the box across every list state (populated, empty account, no results).SessionsListaccepts the query as a controlled prop and falls back to the store for direct mounts (MainViewphone layout).sync.loadAllSessionsForSearch()— on first open, pages/v2/sessions(cursor, 200/page) to the end and ingests only ids not already in the store; once per app run; a failed page lets the next open retry. The decrypt+apply path is extracted fromfetchSessionsintoingestSessionsso both callers share it. The boot fetch is unchanged. A spinner sits in the box while history pages in.sessionsFilter.searchPlaceholder/noResultsPlaceholderin all 10 locales.Three commits: filter + box, header icon, on-demand history.
pnpm typecheckclean.Verified
useVisibleSessionListViewData.test.ts: title/path match, project cards, bots, headers, archive interaction) and 4 on the history loader (sync.searchHistory.test.ts: cursor to the end, skipping known ids, single run, retry after a failed page).Note
Message content search is out of scope for the same E2EE reason and would require decrypting every session's history client-side. Rebases cleanly on top of #1772 (chunked ingest) if that lands first:
ingestSessionsis the natural seam.🤖 Generated with Claude Code