(i: number, wantLast: boolean)
| 607 | // Scroll to message i's top, arm scanPending. scan-effect reads fresh |
| 608 | // screen next tick. wantLast: N-into-message — screenOrd = length-1. |
| 609 | function jump(i: number, wantLast: boolean): void { |
| 610 | const s = scrollRef.current; |
| 611 | if (!s) return; |
| 612 | const js = jumpState.current; |
| 613 | const { |
| 614 | getItemElement, |
| 615 | scrollToIndex |
| 616 | } = js; |
| 617 | // offsets is a Float64Array whose .length is the allocated buffer (only |
| 618 | // grows) — messages.length is the logical item count. |
| 619 | if (i < 0 || i >= js.messages.length) return; |
| 620 | // Clear stale highlight before scroll. Between now and the seek |
| 621 | // effect's highlight, inverse-only from scan-highlight shows. |
| 622 | setPositions?.(null); |
| 623 | elementPositions.current = { |
| 624 | msgIdx: -1, |
| 625 | positions: [] |
| 626 | }; |
| 627 | scanRequestRef.current = { |
| 628 | idx: i, |
| 629 | wantLast, |
| 630 | tries: 0 |
| 631 | }; |
| 632 | const el = getItemElement(i); |
| 633 | const h = el?.yogaNode?.getComputedHeight() ?? 0; |
| 634 | // Mounted → precise scrollTo. Unmounted → scrollToIndex mounts it |
| 635 | // (scrollTop and topSpacer agree via the same offsets value — exact |
| 636 | // by construction, no estimation). Seek effect does the precise |
| 637 | // scrollTo after paint either way. |
| 638 | if (el && h > 0) { |
| 639 | s.scrollTo(targetFor(i)); |
| 640 | } else { |
| 641 | scrollToIndex(i); |
| 642 | } |
| 643 | bumpSeek(); |
| 644 | } |
| 645 | |
| 646 | // Advance screenOrd within elementPositions. Exhausted → ptr advances, |
| 647 | // jump to next matches[ptr], re-scan. Phantom (scan found 0 after |
no test coverage detected