mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-08 01:43:07 +00:00
* 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>
55 lines
2.0 KiB
TypeScript
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)
|
|
}
|