Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,11 @@ Sticky pin floors are not reduced from a transient target rect. Positive
effective content growth first enters a short settlement ledger (currently
300 ms) instead of immediately removing physical bottom range. An unsignaled
negative height correction cancels matching unsettled growth; a known collapse
does not. Stable growth then consumes the pin floor in one synchronous Footer
does not. Growth that reaches the complete remaining pin floor settles
immediately because the sticky viewport has reached its tail-follow handoff
boundary; if a collapse transaction is still active, that settlement resumes
as soon as the transaction finishes. Sub-threshold growth still waits for the
quiet window. Stable growth consumes the pin floor in one synchronous Footer
update. Live pin reconciliation may increase a floor immediately, but cannot
shrink it while Virtuoso item measurements are still moving. Stream end performs
one final pin measurement when the target is available, transfers all remaining
Expand Down Expand Up @@ -314,16 +318,19 @@ manual or otherwise unsignaled intents use the TTL timer. The scroll handler kee
timer fallback for browsers that delay timers. While the intent is alive, the
grow branch of `measureHeightChange` protects the collapse reservation, but it
may still consume measured content growth from the sticky pin reservation.
Once the intent settles, residual collapse space enters retained-provisional
settlement even when the sticky pinned item still owns the viewport. This is
required for first-turn `scrollTop === 0`: provisional full-card estimates must
not accumulate merely to fill an otherwise short viewport.
If a collapsing header owns the viewport, the footer is instead reduced
atomically to the minimum range that can retain the current `scrollTop`, then
that settled range is promoted to a protected collapse floor before the
semantic anchor is restored. This prevents a clear-and-reacquire frame without
retaining the full provisional estimate; later content growth can still drain
the protected range. Any deferred follow is then replayed.
Intent settlement follows the current semantic viewport owner. A sticky pinned
turn always reconciles provisional collapse space back into a freshly measured
pin reservation, even when the active transaction established a non-zero
collapse floor. That floor protects the pin only while layout is moving; it
must not cause the full-card estimate to survive into the next collapse. If the
pinned target is temporarily unavailable, settlement retries without dropping
the current range. A following tail instead enters retained-provisional quiet
settlement, while a collapsing header that owns `preserving-element` reduces
the footer atomically to the minimum range needed by its captured `scrollTop`.
A detached protected viewport uses the same geometric settlement against its
current `scrollTop`, without retaining provisional pixels above that range.
These owner-specific transactions prevent both clear-and-reacquire frames and
cumulative provisional whitespace. Any deferred follow is then replayed.

## C. Follow-Output Mode (continuous tail)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ import {
reconcileUnsignaledShrinkReservation,
releasePinReservationForUserNavigation,
resolveAutoCollapseAnchorScrollTop,
resolveCollapseIntentSettlementStrategy,
resolveProvisionalStickyPinReservationPx,
resolveStickyPinGrowthSettlementStrategy,
settleRetainedCollapseReservationForAnchor,
settleCollapseReservationForPreservedViewport,
settleCollapseReservationForViewport,
shouldBypassShrinkCompensationInTailFollow,
shouldPreserveCollapseReservationAfterIntent,
shouldClearExpiredProvisionalStickyPin,
shouldSyncPhysicalBottom,
shouldSuppressFollowingTailNegativeScrollBy,
Expand Down Expand Up @@ -439,7 +440,7 @@ describe('VirtualMessageList session boundary', () => {

it('transfers collapse space to a sticky pin in one reservation state', () => {
const currentState = {
collapse: { kind: 'collapse' as const, px: 1_583, floorPx: 0 },
collapse: { kind: 'collapse' as const, px: 1_583, floorPx: 181 },
pin: {
kind: 'pin' as const,
px: 0,
Expand All @@ -450,8 +451,8 @@ describe('VirtualMessageList session boundary', () => {
};
const nextPin = {
...currentState.pin,
px: 378,
floorPx: 378,
px: 559,
floorPx: 559,
};

expect(transferCollapseReservationToPin(currentState, nextPin)).toEqual({
Expand Down Expand Up @@ -603,7 +604,7 @@ describe('VirtualMessageList session boundary', () => {
});

it('protects a settled element range from later unsignaled shrink reconciliation', () => {
const settledState = settleCollapseReservationForPreservedViewport({
const settledState = settleCollapseReservationForViewport({
collapse: { kind: 'collapse', px: 1_022, floorPx: 670 },
pin: {
kind: 'pin',
Expand All @@ -623,6 +624,23 @@ describe('VirtualMessageList session boundary', () => {
floorPx: 702,
});

const settledPinnedRange = settleCollapseReservationForViewport({
collapse: { kind: 'collapse', px: 152.7, floorPx: 67.7 },
pin: {
kind: 'pin',
px: 674.6,
floorPx: 674.6,
mode: 'sticky-latest',
targetTurnId: 'turn-a',
},
}, {
scrollTop: 5_122,
scrollHeight: 6_235,
clientHeight: 1_027,
});
expect(settledPinnedRange.collapse.px).toBeCloseTo(67.7, 1);
expect(settledPinnedRange.collapse.floorPx).toBeCloseTo(67.7, 1);

const protectedState = protectCurrentCollapseReservation({
collapse: { kind: 'collapse', px: 784, floorPx: 670 },
pin: {
Expand Down Expand Up @@ -858,41 +876,87 @@ describe('VirtualMessageList session boundary', () => {
})).toBe(0);
});

it('lets known streaming collapses reconcile while preserving their reservation', () => {
expect(shouldBypassShrinkCompensationInTailFollow({
isFollowingOutput: true,
isStreamingOutput: true,
it('settles sticky pin growth as soon as it exhausts the remaining pin floor', () => {
expect(resolveStickyPinGrowthSettlementStrategy({
pendingGrowthPx: 0,
pinFloorPx: 11.77,
hasActiveCollapseIntent: false,
})).toBe(true);
expect(shouldBypassShrinkCompensationInTailFollow({
isFollowingOutput: true,
isStreamingOutput: true,
})).toBe('none');
expect(resolveStickyPinGrowthSettlementStrategy({
pendingGrowthPx: 10,
pinFloorPx: 11.77,
hasActiveCollapseIntent: false,
})).toBe('wait-for-quiet');
expect(resolveStickyPinGrowthSettlementStrategy({
pendingGrowthPx: 126,
pinFloorPx: 11.77,
hasActiveCollapseIntent: true,
})).toBe(false);
expect(shouldPreserveCollapseReservationAfterIntent({
})).toBe('wait-for-collapse');
expect(resolveStickyPinGrowthSettlementStrategy({
pendingGrowthPx: 126,
pinFloorPx: 11.77,
hasActiveCollapseIntent: false,
})).toBe('settle-now');
});

it('selects an explicit settlement strategy for each collapse viewport owner', () => {
const stickyReservation = {
collapse: { kind: 'collapse' as const, px: 152.7, floorPx: 67.7 },
pin: {
kind: 'pin' as const,
px: 674.6,
floorPx: 674.6,
mode: 'sticky-latest' as const,
targetTurnId: 'turn-a',
},
};

expect(resolveCollapseIntentSettlementStrategy({
coordinatorMode: 'pinned-item',
isFollowingOutput: false,
isStreamingOutput: true,
reservation: stickyReservation,
})).toBe('reconcile-sticky-pin');
expect(resolveCollapseIntentSettlementStrategy({
coordinatorMode: 'following-tail',
isFollowingOutput: true,
isStreamingOutput: true,
isPreservingElement: false,
hasProtectedCollapseRange: false,
})).toBe(true);
expect(shouldPreserveCollapseReservationAfterIntent({
reservation: stickyReservation,
})).toBe('retain-following-tail');
expect(resolveCollapseIntentSettlementStrategy({
coordinatorMode: 'preserving-element',
isFollowingOutput: false,
isStreamingOutput: true,
isPreservingElement: false,
hasProtectedCollapseRange: false,
})).toBe(false);
expect(shouldPreserveCollapseReservationAfterIntent({
reservation: stickyReservation,
})).toBe('settle-preserved-element');
expect(resolveCollapseIntentSettlementStrategy({
coordinatorMode: 'idle',
isFollowingOutput: false,
isStreamingOutput: true,
isPreservingElement: true,
hasProtectedCollapseRange: false,
})).toBe(true);
expect(shouldPreserveCollapseReservationAfterIntent({
reservation: stickyReservation,
})).toBe('settle-protected-viewport');
expect(resolveCollapseIntentSettlementStrategy({
coordinatorMode: 'idle',
isFollowingOutput: false,
isStreamingOutput: true,
isPreservingElement: false,
hasProtectedCollapseRange: true,
reservation: {
...stickyReservation,
collapse: { ...stickyReservation.collapse, floorPx: 0 },
},
})).toBe('drain');
});

it('lets known streaming collapses reconcile without bypassing active intents', () => {
expect(shouldBypassShrinkCompensationInTailFollow({
isFollowingOutput: true,
isStreamingOutput: true,
hasActiveCollapseIntent: false,
})).toBe(true);
expect(shouldBypassShrinkCompensationInTailFollow({
isFollowingOutput: true,
isStreamingOutput: true,
hasActiveCollapseIntent: true,
})).toBe(false);
});

it('recovers the last stable scroll position when an auto collapse arrives after clamp', () => {
Expand Down
Loading