mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 00:05:49 +02:00
Coordinate initial conversation reveal, preserve message identity and reading anchors during live updates, and bound transcript reads with recoverable retries. Cover desktop/mobile navigation and rich task loading with actual-route browser tests. Verified all Linux CI gates, 5,575 local UI tests, eight layout browser scenarios, and recorded native Codex walkthroughs. Greptile: 5/5; all review findings resolved. Co-Authored-By: Paperclip <noreply@paperclip.ing>
27 lines
1.0 KiB
TypeScript
27 lines
1.0 KiB
TypeScript
/** Keep a reading position by logical row identity, not total scroll height. */
|
|
export interface ThreadScrollAnchor {
|
|
id: string;
|
|
top: number;
|
|
}
|
|
|
|
export function readThreadScrollAnchor(root: Element, viewportTop: number, viewportBottom: number): ThreadScrollAnchor | null {
|
|
for (const row of root.querySelectorAll<HTMLElement>("[data-thread-anchor]")) {
|
|
const rect = row.getBoundingClientRect();
|
|
if (rect.height > 0 && rect.bottom > viewportTop && rect.top < viewportBottom) {
|
|
return { id: row.dataset.threadAnchor!, top: rect.top - viewportTop };
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function threadScrollAnchorDelta(root: Element, anchor: ThreadScrollAnchor | null, viewportTop: number): number {
|
|
if (!anchor) return 0;
|
|
// IDs are opaque and need not be safe CSS selectors.
|
|
for (const row of root.querySelectorAll<HTMLElement>("[data-thread-anchor]")) {
|
|
if (row.dataset.threadAnchor === anchor.id) {
|
|
return row.getBoundingClientRect().top - viewportTop - anchor.top;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|