1diff --git a/react.js b/react.js
2index 5155454..2885128 100644
3--- a/react.js
4+++ b/react.js
5@@ -3489,6 +3489,12 @@ function abortBootstrapInitialScroll(ctx) {
7 // src/core/containerLayoutBaseline.ts
8 var containerLayoutBaselines = /* @__PURE__ */ new WeakMap();
9+// Offset sizes stay in layout pixels throughout ancestor transforms, including
10+// the last frames of an overview zoom. The list rounds measured sizes too.
11+function measureLayoutRect(element) {
12+ const rect = element.getBoundingClientRect();
13+ return { height: element.offsetHeight, left: rect.left, top: rect.top, width: element.offsetWidth, x: rect.x, y: rect.y };
14+}
15 function getContainerLayoutBaseline(element) {
16 return containerLayoutBaselines.get(element);
17 }
18@@ -5646,7 +5652,7 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
19 const element = viewRef == null ? void 0 : viewRef.current;
20 if (itemKey !== void 0 && element && isContainerItemCurrent(ctx, containerId, itemKey)) {
21 const generation = (_b = ctx.state.containerItemGenerations[containerId]) != null ? _b : 0;
22- const rect = element.getBoundingClientRect();
23+ const rect = measureLayoutRect(element);
24 setContainerLayoutBaseline(element, rect);
25 measurements.push({
26 containerId,
27@@ -6214,7 +6220,7 @@ function useOnLayoutSync({
28 };
29 let prevRect;
30 if (measureInLayoutEffect) {
31- const rect = element.getBoundingClientRect();
32+ const rect = measureLayoutRect(element);
33 emit(toLayout(rect), true);
34 prevRect = rect;
35 }
36@@ -6227,7 +6233,7 @@ function useOnLayoutSync({
37 left: entry.contentRect.left,
38 top: entry.contentRect.top,
39 width: borderBoxSize.inlineSize
40- } : (_a4 = target == null ? void 0 : target.getBoundingClientRect()) != null ? _a4 : entry.contentRect;
41+ } : (_a4 = target == null ? void 0 : measureLayoutRect(target)) != null ? _a4 : entry.contentRect;
42 const previousRect = prevRect != null ? prevRect : getContainerLayoutBaseline(element);
43 const didSizeChange = previousRect === void 0 || rectObserved.width !== previousRect.width || rectObserved.height !== previousRect.height;
44 prevRect = rectObserved;
45@@ -6296,7 +6302,7 @@ function processContainerLayout({ containerId, ctx, rectangle, ref, state }) {
46 var _a4;
47 if (state.itemKey === currentItemKey) {
48 const element = ref.current;
49- const rect = (_a4 = element == null ? void 0 : element.getBoundingClientRect) == null ? void 0 : _a4.call(element);
50+ const rect = element == null ? void 0 : measureLayoutRect(element);
51 if (rect) {
52 layout = { height: rect.height, width: rect.width };
53 }
54diff --git a/react.mjs b/react.mjs
55index ce08abe..2c764cb 100644
56--- a/react.mjs
57+++ b/react.mjs
58@@ -3468,6 +3468,12 @@ function abortBootstrapInitialScroll(ctx) {
60 // src/core/containerLayoutBaseline.ts
61 var containerLayoutBaselines = /* @__PURE__ */ new WeakMap();
62+// Offset sizes stay in layout pixels throughout ancestor transforms, including
63+// the last frames of an overview zoom. The list rounds measured sizes too.
64+function measureLayoutRect(element) {
65+ const rect = element.getBoundingClientRect();
66+ return { height: element.offsetHeight, left: rect.left, top: rect.top, width: element.offsetWidth, x: rect.x, y: rect.y };
67+}
68 function getContainerLayoutBaseline(element) {
69 return containerLayoutBaselines.get(element);
70 }
71@@ -5625,7 +5631,7 @@ function measureContainersInLayoutEffect(ctx, targetContainerIds = null) {
72 const element = viewRef == null ? void 0 : viewRef.current;
73 if (itemKey !== void 0 && element && isContainerItemCurrent(ctx, containerId, itemKey)) {
74 const generation = (_b = ctx.state.containerItemGenerations[containerId]) != null ? _b : 0;
75- const rect = element.getBoundingClientRect();
76+ const rect = measureLayoutRect(element);
77 setContainerLayoutBaseline(element, rect);
78 measurements.push({
79 containerId,
80@@ -6193,7 +6199,7 @@ function useOnLayoutSync({
81 };
82 let prevRect;
83 if (measureInLayoutEffect) {
84- const rect = element.getBoundingClientRect();
85+ const rect = measureLayoutRect(element);
86 emit(toLayout(rect), true);
87 prevRect = rect;
88 }
89@@ -6206,7 +6212,7 @@ function useOnLayoutSync({
90 left: entry.contentRect.left,
91 top: entry.contentRect.top,
92 width: borderBoxSize.inlineSize
93- } : (_a4 = target == null ? void 0 : target.getBoundingClientRect()) != null ? _a4 : entry.contentRect;
94+ } : (_a4 = target == null ? void 0 : measureLayoutRect(target)) != null ? _a4 : entry.contentRect;
95 const previousRect = prevRect != null ? prevRect : getContainerLayoutBaseline(element);
96 const didSizeChange = previousRect === void 0 || rectObserved.width !== previousRect.width || rectObserved.height !== previousRect.height;
97 prevRect = rectObserved;
98@@ -6275,7 +6281,7 @@ function processContainerLayout({ containerId, ctx, rectangle, ref, state }) {
99 var _a4;
100 if (state.itemKey === currentItemKey) {
101 const element = ref.current;
102- const rect = (_a4 = element == null ? void 0 : element.getBoundingClientRect) == null ? void 0 : _a4.call(element);
103+ const rect = element == null ? void 0 : measureLayoutRect(element);
104 if (rect) {
105 layout = { height: rect.height, width: rect.width };
106 }
107diff --git a/react-native.js b/react-native.js
108index 4af685dfb961eb0c8a4bf1ae9132c9304cf4fa4d..df4ead7ae4f03cd0fbcd99a3df7d95e15b1852b1 100644
109--- a/react-native.js
110+++ b/react-native.js
111@@ -1795,7 +1795,35 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
112 }
113 return false;
114 }
115+// T3 patch: while following the end, a size-only change keeps the end in the same pass that
116+// repositions the items. Native end following otherwise runs a frame later, after MVCP has
117+// anchored the top row, so the feed visibly jumps twice.
118+function shouldHoldEndOnSizeChange(ctx, dataChanged) {
119+ const state = ctx.state;
120+ return !dataChanged && !!state.props.maintainScrollAtEnd && !!state.didFinishInitialScroll && !state.endFollowInterrupted && !state.visibleContentAnchor && state.scrollingTo === void 0 && !state.isDragging && state.userScrollContentSize === void 0 && !!peek$(ctx, "isFollowingEnd");
121+}
122 function prepareMVCP(ctx, dataChanged, adjustmentSource) {
123+ const restore = prepareDefaultMVCP(ctx, dataChanged, adjustmentSource);
124+ if (!shouldHoldEndOnSizeChange(ctx, dataChanged)) {
125+ return restore;
126+ }
127+ return () => {
128+ if (!shouldHoldEndOnSizeChange(ctx, dataChanged)) {
129+ restore == null ? void 0 : restore();
130+ return;
131+ }
132+ // Content that shrinks under an offset past its new end pulls the native offset back by itself.
133+ const endOffset = getScrollRangeEnd(ctx);
134+ if (ctx.state.scroll > endOffset) {
135+ ctx.state.scroll = endOffset;
136+ }
137+ const positionDiff = endOffset - ctx.state.scroll;
138+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
139+ requestAdjust(ctx, positionDiff, adjustmentSource);
140+ }
141+ };
142+}
143+function prepareDefaultMVCP(ctx, dataChanged, adjustmentSource) {
144 var _a3, _b, _c;
145 const state = ctx.state;
146 const { idsInView, positions, props } = state;
147diff --git a/react-native.mjs b/react-native.mjs
148index 9ebfddc183a9b202d35c990a387c9b628feeb185..c414a49e866ecab1393645101fb67d55530a7d00 100644
149--- a/react-native.mjs
150+++ b/react-native.mjs
151@@ -1774,7 +1774,35 @@ function resolvePendingNativeMVCPAdjust(ctx, newScroll) {
152 }
153 return false;
154 }
155+// T3 patch: while following the end, a size-only change keeps the end in the same pass that
156+// repositions the items. Native end following otherwise runs a frame later, after MVCP has
157+// anchored the top row, so the feed visibly jumps twice.
158+function shouldHoldEndOnSizeChange(ctx, dataChanged) {
159+ const state = ctx.state;
160+ return !dataChanged && !!state.props.maintainScrollAtEnd && !!state.didFinishInitialScroll && !state.endFollowInterrupted && !state.visibleContentAnchor && state.scrollingTo === void 0 && !state.isDragging && state.userScrollContentSize === void 0 && !!peek$(ctx, "isFollowingEnd");
161+}
162 function prepareMVCP(ctx, dataChanged, adjustmentSource) {
163+ const restore = prepareDefaultMVCP(ctx, dataChanged, adjustmentSource);
164+ if (!shouldHoldEndOnSizeChange(ctx, dataChanged)) {
165+ return restore;
166+ }
167+ return () => {
168+ if (!shouldHoldEndOnSizeChange(ctx, dataChanged)) {
169+ restore == null ? void 0 : restore();
170+ return;
171+ }
172+ // Content that shrinks under an offset past its new end pulls the native offset back by itself.
173+ const endOffset = getScrollRangeEnd(ctx);
174+ if (ctx.state.scroll > endOffset) {
175+ ctx.state.scroll = endOffset;
176+ }
177+ const positionDiff = endOffset - ctx.state.scroll;
178+ if (Math.abs(positionDiff) > MVCP_POSITION_EPSILON) {
179+ requestAdjust(ctx, positionDiff, adjustmentSource);
180+ }
181+ };
182+}
183+function prepareDefaultMVCP(ctx, dataChanged, adjustmentSource) {
184 var _a3, _b, _c;
185 const state = ctx.state;
186 const { idsInView, positions, props } = state;