# Route C Timeline Pagination

P017 restores Cinny's raw Matrix event pagination contract:

```text
one raw Matrix event index
= one virtual paginator index
= one stable event-id position
```

## Authority

`RoomTimeline` obtains linked `EventTimeline` objects from the Matrix SDK.
`getTimelinesEventsCount()` is the virtual paginator count. The paginator range,
mounted item attributes, `getItems()`, event focus, and scroll targets all use
raw absolute Matrix indexes.

There is no Route C display index. Tool groups, placeholders, day dividers, and
new-message dividers cannot become independent paginator items. A divider is
rendered in the same fragment as its following Matrix event.

Events Cinny normally hides retain their raw index and may render `null`.
Reactions, edits, redactions, and unsupported events are not replaced with
synthetic rows.

## Native Pagination

The accepted Cinny `useVirtualPaginator` implementation owns local range
expansion and anchor restoration. Backward and forward sentinels are observed
with `IntersectionObserver`. When a local range changes, the paginator records
a mounted raw event and its offset, renders the new range, then restores the
same event offset.

When the local range reaches a linked-timeline boundary, `RoomTimeline` asks
the Matrix SDK to paginate backward or forward. Matrix pagination tokens and
event IDs remain authoritative. Route C does not add one-scroll/one-page gates,
suppressed retry timers, remote-end locks as viewport authority, or a second
viewport ownership controller.

### Capacitor iOS remote restore transaction

Capacitor iOS/WKWebView can overwrite Cinny's one-shot local offset restore
while a touch or momentum scroll is still active. If the restore is lost, the
new remote page head replaces the visible position and the still-visible
sentinel can request more pages.

P017 therefore permits one opt-in adapter only when both conditions are true:

- the page is a clean Route C chat route;
- the runtime is Capacitor iOS.

Before a remote Matrix page fetch, the adapter records the mounted raw event
nearest the viewport center as `{ event_id, viewport_offset }`. It does not
store a display index or become range authority. The Matrix SDK fetch, native
range recalibration, raw local expansion, event-id restore, and measured
verification form one transaction.

The adapter waits for touch release and layout frames before committing the
remote range. It then measures the same event ID and reapplies only the missing
scroll offset. A correction is accepted only after the anchor remains within
two pixels for consecutive checks. Corrections are bounded; a failed
verification leaves remote pagination blocked instead of allowing an unbounded
sentinel loop.

While the transaction is pending, neither remote direction may start another
request. Local raw range expansion remains native and unrestricted. After a
verified restore, another remote page is requested automatically only when the
rendered content still cannot fill one viewport. Backward and forward use the
same transaction; there is no direction-specific index, page quota, debounce,
or bottom-follow writer.

Generic Cinny rooms and non-iOS Route C clients keep the upstream paginator
path with this adapter disabled.

## Focus And Live Behavior

Search, pin, reply, hyperlink return-to, unread focus, and Host Owner navigation
resolve an event ID through `getEventTimeline()` and
`getEventIdAbsoluteIndex()`. Host Owner navigation chooses the mounted event
nearest the current viewport as its anchor, not the previous button target.

A focused historical timeline is not the live end. Provider arrivals cannot
force Jump to Latest while the reader is away from bottom. Explicit Jump to
Latest restores live-bottom behavior. A new local user send follows native
Cinny `atBottom` behavior and does not force a historical viewport to latest.

### Live-bottom post-commit transaction

When a live event arrives while the reader already owns the authoritative live
bottom, Route C queues one revision-owned post-commit transaction. On the next
animation frame, after the raw range and DOM commit, it verifies that the same
live timeline is still linked and at its end, then positions the committed
scroll container at its new legal bottom with `behavior: instant`.

Live arrival never starts a smooth scroll. A newer event supersedes the pending
revision, leaving the live timeline cancels it, and unmount cancels its frame.
There is no delayed second correction, fixed timer, one-pixel nudge, user-agent
CSS workaround, or independent range/index owner. This transaction does not
change Cinny sentinels, Matrix pagination, local anchor restoration, or focused
history behavior.

The internal Fake provider command `P017_LIVE_TAIL_3S` emits 20 deterministic
short, medium, and tall assistant rows at three-second intervals for this
contract. Geometry diagnostics are disabled by default and require both
`debug_host_artifact_writes_enabled` and
`debug_routec_viewport_geometry_diagnostics_enabled`. When enabled, sanitized
geometry-only records are written under the Host `operation_logs` folder; they
contain event IDs and viewport measurements, not message bodies or secrets.

## Tool Events

Every tool event that exists in Matrix is a normal raw event row. Stage 1 keeps
all currently retained Matrix tool events as individual raw rows. Stage 2
changes new continuous runs to retain only the first event in Matrix; event 2+
belongs to the separate SQLite continuation store and never participates in
timeline pagination. Existing P142 retained events remain individual raw rows.

## Verification

Use the deterministic 20,000+ fixture for repeated PageUp/PageDown, wheel,
direction reversal, focus, Owner navigation, live arrival, and Jump to Latest.
The visible raw event must remain stable when either direction loads a page,
and no permanent placeholder gap may appear.
