Files
hapi/web/src/lib/sharePickerSessions.ts
T
0d12035674 feat(web): searchable session picker on Android share target (#986)
* feat(web): searchable session picker on share target (#980)

Add search to the Android share-target picker so operators can attach
shared content to inactive or older sessions, not just recent actives.
Reuses sidebar search helpers; caps default active list with a search hint.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): defer share pending consume until session is active

ShareSeedConsumer was consuming the sessionStorage transfer id on first
mount even when the target session was inactive. Reopening into a new
session id remounted the chat with the key already gone, dropping the
shared payload. Consume and seed only after sessionActive is true.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): bind share pending transfer to target session id

Deferring consume until active left a global pending slot that the next
unrelated active SessionChat could claim. Bind the pending transfer to
the picked session id, retarget on reopen/spawn id-swap, and only
consume when the mounting session matches.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): wire SessionListSearch date-range props on share picker

Upstream added required customStart/customEnd/onDateRangeChange to
SessionListSearch; share /share must pass them and honor the range when
filtering sessions after rebase onto v0.23.1.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): pass sessionActivityDates to share SessionListSearch

Upstream SessionListSearch now requires activity dates for the date
picker; without them CI typecheck fails and the PR chip stays needs_work.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): use cached machine labels in share picker search

Share picker now uses useMachineLabels like SessionList so stale
machine ids still match search by display name.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): align share picker with sidebar session prep and preview limit

Snapshot via prepareSidebarSessions and pass useSessionPreviewLimit
so search targets and fold cap match the main session list.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): wire SessionListSearch expanded props on share picker

Upstream search now requires expanded/onExpandedChange; default open
on the share picker so the field is available immediately.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): unblock typecheck after SessionSummary and RawSendError fields

Fill new required SessionSummary watermarks in the share picker test
helper, and set deliveryMode on abort-restore send errors.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): mock machines hooks in share page missing-state test

SharePage now calls useMachines for picker search labels; the missing-
share unit test needs those hooks stubbed without a QueryClient.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(web): retarget pending share on automatic session supersession

When SessionDetailRoute follows supersededBySessionId A→B, rewrite the
share pending target so ShareSeedConsumer on B can still claim it.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Debian <heavygee@oos-linux.in.lockhouse>
2026-08-11 14:25:00 +01:00

55 lines
2.0 KiB
TypeScript

import type { SessionSummary } from '@/types/api'
import { DEFAULT_SESSION_PREVIEW_LIMIT } from '@/hooks/useSessionPreviewLimit'
import {
normalizeSearch,
sessionMatchesQuery,
sessionMatchesTimeRange,
type SessionTimeRange,
} from '@/components/SessionList'
export type SharePickerMachineLabelResolver = (machineId: string | null) => string
function sortByUpdatedAtDesc(sessions: SessionSummary[]): SessionSummary[] {
return [...sessions].sort((a, b) => b.updatedAt - a.updatedAt)
}
/**
* Share-target session picker filter.
* Empty query (and no date range): recent active sessions (capped).
* Non-empty query and/or date range: all sessions matching those filters.
*/
export function filterSharePickerSessions(
sessions: SessionSummary[],
query: string,
resolveMachineLabel: SharePickerMachineLabelResolver,
timeRange: SessionTimeRange | null = null,
previewLimit: number = DEFAULT_SESSION_PREVIEW_LIMIT,
): SessionSummary[] {
const normalizedQuery = normalizeSearch(query)
const sorted = sortByUpdatedAtDesc(sessions)
const inRange = (session: SessionSummary) => sessionMatchesTimeRange(session, timeRange)
if (normalizedQuery || timeRange) {
return sorted.filter((session) => {
if (!inRange(session)) return false
if (!normalizedQuery) return true
return sessionMatchesQuery(
session,
normalizedQuery,
resolveMachineLabel(session.metadata?.machineId ?? null),
)
})
}
return sorted.filter((session) => session.active).slice(0, previewLimit)
}
/** Active sessions hidden by the empty-query cap (for "search for more" hint). */
export function countHiddenActiveSharePickerSessions(
sessions: SessionSummary[],
previewLimit: number = DEFAULT_SESSION_PREVIEW_LIMIT,
): number {
const activeCount = sessions.filter((session) => session.active).length
return Math.max(0, activeCount - previewLimit)
}