/** * Prepend bookkeeping for the message thread. * * The message page route returns whole, non-overlapping turns: an older page * never splits a message and never repeats one already loaded. Loading such a * page inserts rows *above* the viewport. * * LegendList owns the scroll offset for that insertion — its * `maintainVisibleContentPosition` anchoring keeps the visible content in place * across a data change. Nothing in the app may measure or write `scrollTop`, so * this module holds only the two things the list cannot know: * - which cursor has already been requested, so the `onStartReached` burst the * list emits while the user sits at the top collapses into one fetch, and * - whether end-following must stand down, so a prepend commit is never * mistaken for "new content at the bottom". * * Both are plain logic so they can be unit tested without mounting a list. */ /** * `idle` → no prepend in flight; end-following behaves normally. * `fetching` → from the moment a page is requested until the fetch settles. * End-following is suspended for that window and released *without* scrolling. */ export type PrependPhase = 'idle' | 'fetching'; export interface PrependRequestState { phase: PrependPhase; /** Cursor of the request currently in flight, if any. */ inFlightToken: string | null; /** Cursors already requested; a cursor is never requested twice. */ requestedTokens: Set; /** Diagnostics: how many fetches this state has dispatched. */ requestCount: number; } export declare function createPrependRequestState(): PrependRequestState; /** Forgets every request, e.g. when the session changes. */ export declare function resetPrependRequests(state: PrependRequestState): void; export interface PrependRequestInput { /** Identifies the page about to be requested (the next cursor). */ token: string | null; hasOlder: boolean; isLoading: boolean; } /** * True when a prepend should actually be dispatched. Repeated calls for the * same cursor — which `onStartReached` produces continuously while the user * stays inside the start threshold — are collapsed into one request. The list * owns the threshold hysteresis; this owns the cursor latch. */ export declare function shouldRequestPrepend(state: PrependRequestState, { token, hasOlder, isLoading }: PrependRequestInput): boolean; /** Records that a prepend request was dispatched for `token`. */ export declare function markPrependRequested(state: PrependRequestState, token: string | null): void; /** * Releases the prepend once the fetch settled — successfully or not. The cursor * stays in `requestedTokens` so a stale `onStartReached` burst cannot * re-request it; a *new* page advances the cursor, which unlatches naturally. * Releasing never scrolls: the list has already anchored the inserted rows. */ export declare function markPrependSettled(state: PrependRequestState): void; export interface PrependFrameScheduler { request(callback: () => void): number; cancel(frame: number): void; } /** * Runs a history request only after the browser has had one complete paint. * This lets a virtual list fill a newly jumped-to top range before a fast page * response starts the much heavier prepend render. */ export declare function schedulePrependAfterViewportPaint(dispatch: () => void, scheduler: PrependFrameScheduler): () => void; /** * True while end-following must stay suspended: from the request until the * fetch has settled and its rows have been committed. */ export declare function isEndFollowSuspended(state: PrependRequestState): boolean; /** * Native end-follow configuration. LegendList only ever scrolls to the end * through this; the app never issues follow scrolls of its own. * * `animated: false` keeps a streaming turn from queueing overlapping animated * scrolls, and the explicit trigger set documents that following reacts to real * content growth (data, item layout, footer layout, list layout) only. */ export declare const END_FOLLOW_OPTIONS: { readonly animated: false; readonly on: { readonly dataChange: true; readonly itemLayout: true; readonly footerLayout: true; readonly layout: true; }; }; export interface EndFollowInput { /** Caller disabled auto-scroll entirely (embedded/preview threads). */ disabled: boolean; /** * The reader has scrolled away from the live edge. Follow must stay off * for the whole detached window — flipping it back on after a prepend is * what snaps the viewport back to the bottom. */ detached: boolean; } /** * `maintainScrollAtEnd` configuration for the current frame. The caller owns * the reader-intent latch; this maps that latch to Legend List's native follow * feature. A prepend of older history must not toggle it: at the tail, follow * should keep the live edge on screen; away from it, follow must stay off so * the insert cannot be mistaken for new content at the bottom. */ export declare function resolveEndFollow(input: EndFollowInput): typeof END_FOLLOW_OPTIONS | false; //# sourceMappingURL=threadPrepend.d.ts.map