export interface Source { service: string; mode: 'public' | 'authenticated'; viewerDid?: string; } export interface Author { did: string; handle: string; displayName: string; avatar?: string; pronouns?: string; following?: boolean; followedBy?: boolean; } export interface FeedReason { kind: 'repost' | 'pin' | 'unknown'; by?: Author; indexedAt?: string; type?: string; } export interface FeedOccurrence { id: string; postUri: string; reason?: FeedReason; parentUri?: string; rootUri?: string; } export interface FeedOptions { limit?: number; cursor?: string; } export interface FeedEncounter { feedViewId: string; occurrenceId: string; postUri: string; reason?: FeedReason; } export type TargetKind = 'post' | 'profile' | 'link' | 'tag' | 'media' | 'occurrence' | 'author-feed'; export interface Target { kind: TargetKind; value: string; label: string; ownerUri?: string; index?: number; } export interface Facet { byteStart: number; byteEnd: number; text: string; targets: Target[]; unsupported: string[]; } export interface Media { kind: 'image' | 'video'; url?: string; thumbnail?: string; alt?: string; width?: number; height?: number; } export interface LinkCard { uri: string; title: string; description: string; thumbnail?: string; } export interface PostNode { uri: string; cid?: string; availability: 'available' | 'not-found' | 'blocked' | 'unsupported'; author?: Author; text?: string; createdAt?: string; indexedAt?: string; parentUri?: string; rootUri?: string; replies: string[]; repliesFetched: boolean; facets: Facet[]; media: Media[]; quotes: string[]; cards: LinkCard[]; labels: string[]; unsupported: string[]; counts: { replies?: number; likes?: number; reposts?: number; quotes?: number }; } export interface FeedSnapshot { kind: 'feed'; feed: 'following' | 'author'; fetchedAt: string; source: Source; actor?: Author; entries: FeedOccurrence[]; nodes: PostNode[]; limit: number; cursor?: string; requestCursor?: string; maxNodes: number; locallyTruncated: boolean; warnings: string[]; } export interface ThreadSnapshot { kind: 'thread'; requested: string; focusUri: string; fetchedAt: string; source: Source; nodes: PostNode[]; /** Oldest available ancestor first; excludes focus. */ ancestors: string[]; replyOrder: 'source'; limits: { depth: number; parentHeight: number; maxNodes: number }; /** Local node omission is distinct from unknown remote coverage. */ locallyTruncated: boolean; warnings: string[]; encounter?: FeedEncounter; } export interface ProfileLabel { value: string; issuerDid: string; uri: string; cid?: string; negated?: boolean; createdAt?: string; expiresAt?: string; signature?: string; } export interface ProfileEnrichment { id: string; title: string; status: 'available' | 'absent' | 'unavailable' | 'unsupported'; items: Array<{ label: string; value: string; uri?: string }>; source: { service: string; actorDid: string; fetchedAt: string; uri?: string; cid?: string; }; note?: string; } export interface ProfileSnapshot { kind: 'profile'; requested: string; fetchedAt: string; source: Source; author: Author; description?: string; banner?: string; website?: string; createdAt?: string; indexedAt?: string; associated?: Record; verification?: Record; pinnedPost?: { uri: string; cid: string }; counts: { followers?: number; follows?: number; posts?: number }; labels: Array; warnings?: string[]; enrichments?: ProfileEnrichment[]; } export interface TargetSnapshot { kind: 'target'; fetchedAt: string; source: Source; target: Target; /** Link/tag/media inspection is not an external article fetch. */ note: string; } export type Snapshot = ThreadSnapshot | ProfileSnapshot | TargetSnapshot | FeedSnapshot; export interface ReadingView { /** Monotonically allocated by the local store, never reused. */ id: string; snapshot: Snapshot; refs: Record; } export interface SessionInfo { name: string; viewId: string | null; history: string[]; } export interface ThreadOptions { depth?: number; parentHeight?: number; maxNodes?: number; } export interface ReaderOptions { service?: string; /** Explicit opt-in to existing OMP atproto env, never persisted. */ authConfig?: string; pds?: string; } export interface Reader { readonly source: Source; thread(input: string, options?: ThreadOptions): Promise; profile(actor: string): Promise; following(options?: FeedOptions): Promise; authorFeed(actor: string, options?: FeedOptions): Promise; } export interface RenderFiles { html: string; text: string; json: string; images: string[]; } export function targetKey(target: Target): string { return JSON.stringify([target.kind, target.value, target.ownerUri ?? null, target.index ?? null]); } export function refFor(view: ReadingView, kind: TargetKind, value: string): string | undefined { return Object.entries(view.refs).find(([, target]) => target.kind === kind && target.value === value)?.[0]; }