From de84e72a3d4f5380bf05624b3dc6d75808a63158 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 18 Aug 2026 10:54:06 -0400 Subject: [PATCH 1/3] fix(web): the timeline scrolls only as far as its content A thread that stays open is the normal case here, and the dead space below the last message compounded with every turn, so the surface people never leave was the one that degraded the most. Signed-off-by: Yordis Prieto --- ...line-scrolls-only-as-far-as-its-content.md | 42 ++ docs/fork/README.md | 19 +- patches/@legendapp__list@3.3.5.patch | 360 ++++++++++++++++++ pnpm-lock.yaml | 10 +- 4 files changed, 417 insertions(+), 14 deletions(-) create mode 100644 docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md diff --git a/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md b/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md new file mode 100644 index 000000000000..58c4179ca12a --- /dev/null +++ b/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md @@ -0,0 +1,42 @@ +# 0012: The timeline scrolls only as far as its content + +- PR: [TrogonStack/t3code#NN](https://github.com/TrogonStack/t3code/pull/NN) +- Status: active + +## What you can do now + +- Drag the timeline scrollbar and land where the thumb says you will. A long + thread no longer grows an empty region below the last message that the + scrollbar still counts, so the thumb stays proportional to the conversation + instead of shrinking to a sliver. +- Keep a thread open all day without its scroll range drifting. The dead space + used to compound with every turn, several times over, until scrolling to the + bottom meant travelling multiples of the thread's real height through + nothing. +- Flick to the end on a phone and stop at the last message rather than + somewhere in the blank area past it. + +## Why + +The timeline is the one surface a user never leaves, and its scrollbar is how +they judge how much thread there is. When the scroll range says a thread is +eight times longer than it is, every estimate the user makes from it is wrong, +and the flick that should reach the newest message lands nowhere. + +It also degraded the longer someone stayed, which is the worst shape for this +kind of bug: the threads worst affected are exactly the ones people care most +about, and closing and reopening the thread was the only way out. That is not +something we can ask of people who drive agents all day. + +## Upstream considerations + +This one is not going upstream, by our choice: it lives in a patch against a +third-party list dependency rather than in T3 Code's own code, so there is no +upstream T3 Code change to submit. The fix belongs to the list library, and we +are carrying it locally instead of taking on that project's release cycle. + +The rebase burden is small and self-contained. It survives a sync untouched +unless the dependency version moves; a version bump means regenerating the +patch, and the affected code was unchanged in the next upstream release, so it +should reapply. Anyone bumping that dependency needs to confirm the fix is +still present rather than assume the new version carries it. diff --git a/docs/fork/README.md b/docs/fork/README.md index 324bc69819ed..aa5eab142759 100644 --- a/docs/fork/README.md +++ b/docs/fork/README.md @@ -27,12 +27,13 @@ Each entry uses these sections: ## Ledger -| # | Divergence | PR | Status | -| ---- | ----------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------ | -| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active | -| 0006 | [Fork schema on its own migration ledger](./0006-fork-migration-ledger.md) | [#13](https://github.com/TrogonStack/t3code/pull/13), [#16](https://github.com/TrogonStack/t3code/pull/16) | active | -| 0007 | [API-key Codex installs are not reported as broken](./0007-codex-api-key-auth-is-supported.md) | [#15](https://github.com/TrogonStack/t3code/pull/15) | active | -| 0008 | [Drop a folder on the sidebar to add a project](./0008-drop-a-folder-to-add-a-project.md) | [#17](https://github.com/TrogonStack/t3code/pull/17) | active | -| 0009 | [Grok reports the account it is signed in as](./0009-grok-reports-its-authenticated-account.md) | [#18](https://github.com/TrogonStack/t3code/pull/18) | active | -| 0010 | [Pull request conventions of our own](./0010-fork-pull-request-conventions.md) | [#19](https://github.com/TrogonStack/t3code/pull/19) | active | -| 0011 | [Follow the background work a thread left running](./0011-follow-background-work.md) | [#20](https://github.com/TrogonStack/t3code/pull/20) | active | +| # | Divergence | PR | Status | +| ---- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------ | +| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active | +| 0006 | [Fork schema on its own migration ledger](./0006-fork-migration-ledger.md) | [#13](https://github.com/TrogonStack/t3code/pull/13), [#16](https://github.com/TrogonStack/t3code/pull/16) | active | +| 0007 | [API-key Codex installs are not reported as broken](./0007-codex-api-key-auth-is-supported.md) | [#15](https://github.com/TrogonStack/t3code/pull/15) | active | +| 0008 | [Drop a folder on the sidebar to add a project](./0008-drop-a-folder-to-add-a-project.md) | [#17](https://github.com/TrogonStack/t3code/pull/17) | active | +| 0009 | [Grok reports the account it is signed in as](./0009-grok-reports-its-authenticated-account.md) | [#18](https://github.com/TrogonStack/t3code/pull/18) | active | +| 0010 | [Pull request conventions of our own](./0010-fork-pull-request-conventions.md) | [#19](https://github.com/TrogonStack/t3code/pull/19) | active | +| 0011 | [Follow the background work a thread left running](./0011-follow-background-work.md) | [#20](https://github.com/TrogonStack/t3code/pull/20) | active | +| 0012 | [The timeline scrolls only as far as its content](./0012-timeline-scrolls-only-as-far-as-its-content.md) | [#NN](https://github.com/TrogonStack/t3code/pull/NN) | active | diff --git a/patches/@legendapp__list@3.3.5.patch b/patches/@legendapp__list@3.3.5.patch index 60a5954d8f28..79050f556357 100644 --- a/patches/@legendapp__list@3.3.5.patch +++ b/patches/@legendapp__list@3.3.5.patch @@ -925,6 +925,366 @@ index 40e87cda8c9bc79a889e5542f29af429a24b24d4..2d556eecb0bafe2f54084c809f82144c onScroll: onScrollHandler, recycleItems, refreshControl: refreshControlElement ? stylePaddingTopState > 0 ? React2.cloneElement(refreshControlElement, { +diff --git a/react-native.web.js b/react-native.web.js +index 914d2dafaafa001c9ab6791a0a0581659295e333..fb0d4157bea1bd2314b98955d2daf3ae8847b121 100644 +--- a/react-native.web.js ++++ b/react-native.web.js +@@ -6407,6 +6407,25 @@ function getScrollAdjustTarget(ctx, contentNode) { + function scrollAdjustBy(el, left, top) { + el.scrollBy({ behavior: "auto", left, top }); + } ++var SCROLL_ADJUST_PADDING_TOLERANCE = 1e-4; ++function isScrollAdjustPaddingOurs(temporaryPadding) { ++ const current = temporaryPadding.node.style[temporaryPadding.prop]; ++ if (current === temporaryPadding.value) { ++ return true; ++ } ++ const currentValue = Number.parseFloat(current); ++ if (!Number.isFinite(currentValue)) { ++ return false; ++ } ++ const writtenValue = Number.parseFloat(temporaryPadding.value); ++ const tolerance = Math.max(1, Math.abs(writtenValue) * SCROLL_ADJUST_PADDING_TOLERANCE); ++ return Math.abs(currentValue - writtenValue) <= tolerance; ++} ++function restoreScrollAdjustPadding(temporaryPadding) { ++ if (temporaryPadding && isScrollAdjustPaddingOurs(temporaryPadding)) { ++ temporaryPadding.node.style[temporaryPadding.prop] = temporaryPadding.baseline; ++ } ++} + function ScrollAdjust() { + const ctx = useStateContext(); + const lastScrollOffsetRef = React3__namespace.useRef(0); +@@ -6438,15 +6457,24 @@ function ScrollAdjust() { + const nextScroll = currentScroll + scrollDelta; + const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize; + if (needsTemporaryPadding) { +- const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp]; + const previousTemporaryPadding = temporaryPaddingRef.current; +- const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd; ++ const isStackedInflation = previousTemporaryPadding !== void 0 && previousTemporaryPadding.node === contentNode && previousTemporaryPadding.prop === axis.paddingEndProp; ++ if (!isStackedInflation) { ++ restoreScrollAdjustPadding(previousTemporaryPadding); ++ } ++ const baselinePaddingEnd = isStackedInflation ? previousTemporaryPadding.baseline : contentNode.style[axis.paddingEndProp]; + const pad = (nextScroll + viewportSize - totalSize) * 2; + const currentPaddingEnd = Number.parseFloat( + window.getComputedStyle(contentNode)[axis.paddingEndProp] + ); + const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; +- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd }; ++ const temporaryPadding = { ++ baseline: baselinePaddingEnd, ++ node: contentNode, ++ prop: axis.paddingEndProp, ++ value: temporaryPaddingEnd ++ }; ++ temporaryPaddingRef.current = temporaryPadding; + contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd; + void contentNode.offsetHeight; + scrollBy(); +@@ -6454,12 +6482,11 @@ function ScrollAdjust() { + cancelAnimationFrame(resetPaddingRafRef.current); + } + resetPaddingRafRef.current = requestAnimationFrame(() => { +- const temporaryPadding = temporaryPaddingRef.current; + resetPaddingRafRef.current = void 0; +- temporaryPaddingRef.current = void 0; +- if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) { +- contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline; ++ if (temporaryPaddingRef.current === temporaryPadding) { ++ temporaryPaddingRef.current = void 0; + } ++ restoreScrollAdjustPadding(temporaryPadding); + }); + } else { + scrollBy(); +@@ -6472,6 +6499,15 @@ function ScrollAdjust() { + lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0; + } + }, [ctx]); ++ React3__namespace.useEffect(() => () => { ++ if (resetPaddingRafRef.current !== void 0) { ++ cancelAnimationFrame(resetPaddingRafRef.current); ++ resetPaddingRafRef.current = void 0; ++ } ++ const temporaryPadding = temporaryPaddingRef.current; ++ temporaryPaddingRef.current = void 0; ++ restoreScrollAdjustPadding(temporaryPadding); ++ }, []); + useValueListener$("scrollAdjust", callback); + useValueListener$("scrollAdjustUserOffset", callback); + return null; +diff --git a/react-native.web.mjs b/react-native.web.mjs +index 95465f2ab89ce41a10553f58af83618f7310e83c..b27eecb6096b531f523df8204384ab58017cd04d 100644 +--- a/react-native.web.mjs ++++ b/react-native.web.mjs +@@ -6386,6 +6386,25 @@ function getScrollAdjustTarget(ctx, contentNode) { + function scrollAdjustBy(el, left, top) { + el.scrollBy({ behavior: "auto", left, top }); + } ++var SCROLL_ADJUST_PADDING_TOLERANCE = 1e-4; ++function isScrollAdjustPaddingOurs(temporaryPadding) { ++ const current = temporaryPadding.node.style[temporaryPadding.prop]; ++ if (current === temporaryPadding.value) { ++ return true; ++ } ++ const currentValue = Number.parseFloat(current); ++ if (!Number.isFinite(currentValue)) { ++ return false; ++ } ++ const writtenValue = Number.parseFloat(temporaryPadding.value); ++ const tolerance = Math.max(1, Math.abs(writtenValue) * SCROLL_ADJUST_PADDING_TOLERANCE); ++ return Math.abs(currentValue - writtenValue) <= tolerance; ++} ++function restoreScrollAdjustPadding(temporaryPadding) { ++ if (temporaryPadding && isScrollAdjustPaddingOurs(temporaryPadding)) { ++ temporaryPadding.node.style[temporaryPadding.prop] = temporaryPadding.baseline; ++ } ++} + function ScrollAdjust() { + const ctx = useStateContext(); + const lastScrollOffsetRef = React3.useRef(0); +@@ -6417,15 +6436,24 @@ function ScrollAdjust() { + const nextScroll = currentScroll + scrollDelta; + const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize; + if (needsTemporaryPadding) { +- const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp]; + const previousTemporaryPadding = temporaryPaddingRef.current; +- const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd; ++ const isStackedInflation = previousTemporaryPadding !== void 0 && previousTemporaryPadding.node === contentNode && previousTemporaryPadding.prop === axis.paddingEndProp; ++ if (!isStackedInflation) { ++ restoreScrollAdjustPadding(previousTemporaryPadding); ++ } ++ const baselinePaddingEnd = isStackedInflation ? previousTemporaryPadding.baseline : contentNode.style[axis.paddingEndProp]; + const pad = (nextScroll + viewportSize - totalSize) * 2; + const currentPaddingEnd = Number.parseFloat( + window.getComputedStyle(contentNode)[axis.paddingEndProp] + ); + const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; +- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd }; ++ const temporaryPadding = { ++ baseline: baselinePaddingEnd, ++ node: contentNode, ++ prop: axis.paddingEndProp, ++ value: temporaryPaddingEnd ++ }; ++ temporaryPaddingRef.current = temporaryPadding; + contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd; + void contentNode.offsetHeight; + scrollBy(); +@@ -6433,12 +6461,11 @@ function ScrollAdjust() { + cancelAnimationFrame(resetPaddingRafRef.current); + } + resetPaddingRafRef.current = requestAnimationFrame(() => { +- const temporaryPadding = temporaryPaddingRef.current; + resetPaddingRafRef.current = void 0; +- temporaryPaddingRef.current = void 0; +- if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) { +- contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline; ++ if (temporaryPaddingRef.current === temporaryPadding) { ++ temporaryPaddingRef.current = void 0; + } ++ restoreScrollAdjustPadding(temporaryPadding); + }); + } else { + scrollBy(); +@@ -6451,6 +6478,15 @@ function ScrollAdjust() { + lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0; + } + }, [ctx]); ++ React3.useEffect(() => () => { ++ if (resetPaddingRafRef.current !== void 0) { ++ cancelAnimationFrame(resetPaddingRafRef.current); ++ resetPaddingRafRef.current = void 0; ++ } ++ const temporaryPadding = temporaryPaddingRef.current; ++ temporaryPaddingRef.current = void 0; ++ restoreScrollAdjustPadding(temporaryPadding); ++ }, []); + useValueListener$("scrollAdjust", callback); + useValueListener$("scrollAdjustUserOffset", callback); + return null; +diff --git a/react.js b/react.js +index 914d2dafaafa001c9ab6791a0a0581659295e333..fb0d4157bea1bd2314b98955d2daf3ae8847b121 100644 +--- a/react.js ++++ b/react.js +@@ -6407,6 +6407,25 @@ function getScrollAdjustTarget(ctx, contentNode) { + function scrollAdjustBy(el, left, top) { + el.scrollBy({ behavior: "auto", left, top }); + } ++var SCROLL_ADJUST_PADDING_TOLERANCE = 1e-4; ++function isScrollAdjustPaddingOurs(temporaryPadding) { ++ const current = temporaryPadding.node.style[temporaryPadding.prop]; ++ if (current === temporaryPadding.value) { ++ return true; ++ } ++ const currentValue = Number.parseFloat(current); ++ if (!Number.isFinite(currentValue)) { ++ return false; ++ } ++ const writtenValue = Number.parseFloat(temporaryPadding.value); ++ const tolerance = Math.max(1, Math.abs(writtenValue) * SCROLL_ADJUST_PADDING_TOLERANCE); ++ return Math.abs(currentValue - writtenValue) <= tolerance; ++} ++function restoreScrollAdjustPadding(temporaryPadding) { ++ if (temporaryPadding && isScrollAdjustPaddingOurs(temporaryPadding)) { ++ temporaryPadding.node.style[temporaryPadding.prop] = temporaryPadding.baseline; ++ } ++} + function ScrollAdjust() { + const ctx = useStateContext(); + const lastScrollOffsetRef = React3__namespace.useRef(0); +@@ -6438,15 +6457,24 @@ function ScrollAdjust() { + const nextScroll = currentScroll + scrollDelta; + const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize; + if (needsTemporaryPadding) { +- const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp]; + const previousTemporaryPadding = temporaryPaddingRef.current; +- const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd; ++ const isStackedInflation = previousTemporaryPadding !== void 0 && previousTemporaryPadding.node === contentNode && previousTemporaryPadding.prop === axis.paddingEndProp; ++ if (!isStackedInflation) { ++ restoreScrollAdjustPadding(previousTemporaryPadding); ++ } ++ const baselinePaddingEnd = isStackedInflation ? previousTemporaryPadding.baseline : contentNode.style[axis.paddingEndProp]; + const pad = (nextScroll + viewportSize - totalSize) * 2; + const currentPaddingEnd = Number.parseFloat( + window.getComputedStyle(contentNode)[axis.paddingEndProp] + ); + const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; +- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd }; ++ const temporaryPadding = { ++ baseline: baselinePaddingEnd, ++ node: contentNode, ++ prop: axis.paddingEndProp, ++ value: temporaryPaddingEnd ++ }; ++ temporaryPaddingRef.current = temporaryPadding; + contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd; + void contentNode.offsetHeight; + scrollBy(); +@@ -6454,12 +6482,11 @@ function ScrollAdjust() { + cancelAnimationFrame(resetPaddingRafRef.current); + } + resetPaddingRafRef.current = requestAnimationFrame(() => { +- const temporaryPadding = temporaryPaddingRef.current; + resetPaddingRafRef.current = void 0; +- temporaryPaddingRef.current = void 0; +- if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) { +- contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline; ++ if (temporaryPaddingRef.current === temporaryPadding) { ++ temporaryPaddingRef.current = void 0; + } ++ restoreScrollAdjustPadding(temporaryPadding); + }); + } else { + scrollBy(); +@@ -6472,6 +6499,15 @@ function ScrollAdjust() { + lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0; + } + }, [ctx]); ++ React3__namespace.useEffect(() => () => { ++ if (resetPaddingRafRef.current !== void 0) { ++ cancelAnimationFrame(resetPaddingRafRef.current); ++ resetPaddingRafRef.current = void 0; ++ } ++ const temporaryPadding = temporaryPaddingRef.current; ++ temporaryPaddingRef.current = void 0; ++ restoreScrollAdjustPadding(temporaryPadding); ++ }, []); + useValueListener$("scrollAdjust", callback); + useValueListener$("scrollAdjustUserOffset", callback); + return null; +diff --git a/react.mjs b/react.mjs +index 95465f2ab89ce41a10553f58af83618f7310e83c..b27eecb6096b531f523df8204384ab58017cd04d 100644 +--- a/react.mjs ++++ b/react.mjs +@@ -6386,6 +6386,25 @@ function getScrollAdjustTarget(ctx, contentNode) { + function scrollAdjustBy(el, left, top) { + el.scrollBy({ behavior: "auto", left, top }); + } ++var SCROLL_ADJUST_PADDING_TOLERANCE = 1e-4; ++function isScrollAdjustPaddingOurs(temporaryPadding) { ++ const current = temporaryPadding.node.style[temporaryPadding.prop]; ++ if (current === temporaryPadding.value) { ++ return true; ++ } ++ const currentValue = Number.parseFloat(current); ++ if (!Number.isFinite(currentValue)) { ++ return false; ++ } ++ const writtenValue = Number.parseFloat(temporaryPadding.value); ++ const tolerance = Math.max(1, Math.abs(writtenValue) * SCROLL_ADJUST_PADDING_TOLERANCE); ++ return Math.abs(currentValue - writtenValue) <= tolerance; ++} ++function restoreScrollAdjustPadding(temporaryPadding) { ++ if (temporaryPadding && isScrollAdjustPaddingOurs(temporaryPadding)) { ++ temporaryPadding.node.style[temporaryPadding.prop] = temporaryPadding.baseline; ++ } ++} + function ScrollAdjust() { + const ctx = useStateContext(); + const lastScrollOffsetRef = React3.useRef(0); +@@ -6417,15 +6436,24 @@ function ScrollAdjust() { + const nextScroll = currentScroll + scrollDelta; + const needsTemporaryPadding = scrollDelta > 0 && !ctx.state.adjustingFromInitialMount && totalSize < nextScroll + viewportSize; + if (needsTemporaryPadding) { +- const currentInlinePaddingEnd = contentNode.style[axis.paddingEndProp]; + const previousTemporaryPadding = temporaryPaddingRef.current; +- const baselinePaddingEnd = (previousTemporaryPadding == null ? void 0 : previousTemporaryPadding.value) === currentInlinePaddingEnd ? previousTemporaryPadding.baseline : currentInlinePaddingEnd; ++ const isStackedInflation = previousTemporaryPadding !== void 0 && previousTemporaryPadding.node === contentNode && previousTemporaryPadding.prop === axis.paddingEndProp; ++ if (!isStackedInflation) { ++ restoreScrollAdjustPadding(previousTemporaryPadding); ++ } ++ const baselinePaddingEnd = isStackedInflation ? previousTemporaryPadding.baseline : contentNode.style[axis.paddingEndProp]; + const pad = (nextScroll + viewportSize - totalSize) * 2; + const currentPaddingEnd = Number.parseFloat( + window.getComputedStyle(contentNode)[axis.paddingEndProp] + ); + const temporaryPaddingEnd = `${(currentPaddingEnd || 0) + pad}px`; +- temporaryPaddingRef.current = { baseline: baselinePaddingEnd, value: temporaryPaddingEnd }; ++ const temporaryPadding = { ++ baseline: baselinePaddingEnd, ++ node: contentNode, ++ prop: axis.paddingEndProp, ++ value: temporaryPaddingEnd ++ }; ++ temporaryPaddingRef.current = temporaryPadding; + contentNode.style[axis.paddingEndProp] = temporaryPaddingEnd; + void contentNode.offsetHeight; + scrollBy(); +@@ -6433,12 +6461,11 @@ function ScrollAdjust() { + cancelAnimationFrame(resetPaddingRafRef.current); + } + resetPaddingRafRef.current = requestAnimationFrame(() => { +- const temporaryPadding = temporaryPaddingRef.current; + resetPaddingRafRef.current = void 0; +- temporaryPaddingRef.current = void 0; +- if (contentNode.style[axis.paddingEndProp] === (temporaryPadding == null ? void 0 : temporaryPadding.value)) { +- contentNode.style[axis.paddingEndProp] = temporaryPadding.baseline; ++ if (temporaryPaddingRef.current === temporaryPadding) { ++ temporaryPaddingRef.current = void 0; + } ++ restoreScrollAdjustPadding(temporaryPadding); + }); + } else { + scrollBy(); +@@ -6451,6 +6478,15 @@ function ScrollAdjust() { + lastScrollAdjustUserOffsetRef.current = scrollAdjustUserOffset || 0; + } + }, [ctx]); ++ React3.useEffect(() => () => { ++ if (resetPaddingRafRef.current !== void 0) { ++ cancelAnimationFrame(resetPaddingRafRef.current); ++ resetPaddingRafRef.current = void 0; ++ } ++ const temporaryPadding = temporaryPaddingRef.current; ++ temporaryPaddingRef.current = void 0; ++ restoreScrollAdjustPadding(temporaryPadding); ++ }, []); + useValueListener$("scrollAdjust", callback); + useValueListener$("scrollAdjustUserOffset", callback); + return null; diff --git a/reanimated.d.ts b/reanimated.d.ts index e5043320700b12f34f4c0babbc341f85ca8135c1..2ce63830a28636b21937a0741fd0e613dae950fe 100644 --- a/reanimated.d.ts diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2c79aea36a0e..97980591ed8b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,7 +76,7 @@ patchedDependencies: '@effect/vitest@4.0.0-beta.103': a16b1e870d8c29e4a98b17cc4c638a4ff471753d8ca3487f78a22b759caf951b '@expo/metro-config@56.0.14': 8cb08b5bb7051ed9d2dbe46a2c293c5a1e17f1bd6ddf30de27909e18c921ff46 '@ff-labs/fff-node@0.9.4': ab9ff544009e1891cfe3930105862d3699007f38922a79f3c98d90018deca368 - '@legendapp/list@3.3.5': 6befc76c7f590a0b0915b531386ce7e3bbb364612868e1f04e4ac84f60a39ab5 + '@legendapp/list@3.3.5': 897638cce5599b31551c2a3169d4bcbaf88ed27db0b0af66ea01972cc0370813 '@pierre/diffs@1.3.0-beta.10': 7ef7cb0cbabb17c15cdb137554068b36f15f6f5265e73fb51452aa3380db91aa '@react-native-menu/menu@2.0.0': c7f66d121c726ade4f5c4e1aed11a691e5711d244c544084e289ac26132a0045 '@react-native/gradle-plugin@0.85.3': c1b594a16e682d621b6a960926f8ce13fc92edfb397884cfe7fcb0518996a784 @@ -216,7 +216,7 @@ importers: version: 56.0.18(3cdc0dde9f93166d952f1e1bd0cb25c0) '@legendapp/list': specifier: 'catalog:' - version: 3.3.5(patch_hash=6befc76c7f590a0b0915b531386ce7e3bbb364612868e1f04e4ac84f60a39ab5)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) + version: 3.3.5(patch_hash=897638cce5599b31551c2a3169d4bcbaf88ed27db0b0af66ea01972cc0370813)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3) '@noble/curves': specifier: 'catalog:' version: 1.9.1 @@ -545,7 +545,7 @@ importers: version: 0.9.0 '@legendapp/list': specifier: 'catalog:' - version: 3.3.5(patch_hash=6befc76c7f590a0b0915b531386ce7e3bbb364612868e1f04e4ac84f60a39ab5)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + version: 3.3.5(patch_hash=897638cce5599b31551c2a3169d4bcbaf88ed27db0b0af66ea01972cc0370813)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@lexical/react': specifier: ^0.41.0 version: 0.41.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(yjs@13.6.31) @@ -13015,7 +13015,7 @@ snapshots: dependencies: jsbi: 4.3.2 - '@legendapp/list@3.3.5(patch_hash=6befc76c7f590a0b0915b531386ce7e3bbb364612868e1f04e4ac84f60a39ab5)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)': + '@legendapp/list@3.3.5(patch_hash=897638cce5599b31551c2a3169d4bcbaf88ed27db0b0af66ea01972cc0370813)(react-dom@19.2.3(react@19.2.3))(react-native@0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6))(react@19.2.3)': dependencies: react: 19.2.3 use-sync-external-store: 1.6.0(react@19.2.3) @@ -13023,7 +13023,7 @@ snapshots: react-dom: 19.2.3(react@19.2.3) react-native: 0.85.3(@babel/core@7.29.7)(@react-native/metro-config@0.85.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.2.16)(bufferutil@4.1.0)(react@19.2.3)(utf-8-validate@6.0.6) - '@legendapp/list@3.3.5(patch_hash=6befc76c7f590a0b0915b531386ce7e3bbb364612868e1f04e4ac84f60a39ab5)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + '@legendapp/list@3.3.5(patch_hash=897638cce5599b31551c2a3169d4bcbaf88ed27db0b0af66ea01972cc0370813)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: react: 19.2.6 use-sync-external-store: 1.6.0(react@19.2.6) From f21ca1e6e5fef4953ec847db9f6c6cb25eb641dc Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 18 Aug 2026 10:55:09 -0400 Subject: [PATCH 2/3] docs(fork): point divergence 0012 at its pull request Signed-off-by: Yordis Prieto --- docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md | 2 +- docs/fork/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md b/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md index 58c4179ca12a..9dd888596ad7 100644 --- a/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md +++ b/docs/fork/0012-timeline-scrolls-only-as-far-as-its-content.md @@ -1,6 +1,6 @@ # 0012: The timeline scrolls only as far as its content -- PR: [TrogonStack/t3code#NN](https://github.com/TrogonStack/t3code/pull/NN) +- PR: [TrogonStack/t3code#21](https://github.com/TrogonStack/t3code/pull/21) - Status: active ## What you can do now diff --git a/docs/fork/README.md b/docs/fork/README.md index aa5eab142759..a7409e636b86 100644 --- a/docs/fork/README.md +++ b/docs/fork/README.md @@ -36,4 +36,4 @@ Each entry uses these sections: | 0009 | [Grok reports the account it is signed in as](./0009-grok-reports-its-authenticated-account.md) | [#18](https://github.com/TrogonStack/t3code/pull/18) | active | | 0010 | [Pull request conventions of our own](./0010-fork-pull-request-conventions.md) | [#19](https://github.com/TrogonStack/t3code/pull/19) | active | | 0011 | [Follow the background work a thread left running](./0011-follow-background-work.md) | [#20](https://github.com/TrogonStack/t3code/pull/20) | active | -| 0012 | [The timeline scrolls only as far as its content](./0012-timeline-scrolls-only-as-far-as-its-content.md) | [#NN](https://github.com/TrogonStack/t3code/pull/NN) | active | +| 0012 | [The timeline scrolls only as far as its content](./0012-timeline-scrolls-only-as-far-as-its-content.md) | [#21](https://github.com/TrogonStack/t3code/pull/21) | active | From bda45f048bb0d00ad7ca1ec10f76f79193c2c561 Mon Sep 17 00:00:00 2001 From: Yordis Prieto Date: Tue, 18 Aug 2026 11:15:17 -0400 Subject: [PATCH 3/3] docs(fork): the ledger survives a new entry without reformatting Table columns are padded to the widest row, so an entry whose title ran past the current column width rewrote every other row with it, and the real change was buried in whitespace. Signed-off-by: Yordis Prieto --- docs/fork/README.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/docs/fork/README.md b/docs/fork/README.md index a7409e636b86..e65ccd798b48 100644 --- a/docs/fork/README.md +++ b/docs/fork/README.md @@ -27,13 +27,19 @@ Each entry uses these sections: ## Ledger -| # | Divergence | PR | Status | -| ---- | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | ------ | -| 0003 | [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) | [#3](https://github.com/TrogonStack/t3code/pull/3) | active | -| 0006 | [Fork schema on its own migration ledger](./0006-fork-migration-ledger.md) | [#13](https://github.com/TrogonStack/t3code/pull/13), [#16](https://github.com/TrogonStack/t3code/pull/16) | active | -| 0007 | [API-key Codex installs are not reported as broken](./0007-codex-api-key-auth-is-supported.md) | [#15](https://github.com/TrogonStack/t3code/pull/15) | active | -| 0008 | [Drop a folder on the sidebar to add a project](./0008-drop-a-folder-to-add-a-project.md) | [#17](https://github.com/TrogonStack/t3code/pull/17) | active | -| 0009 | [Grok reports the account it is signed in as](./0009-grok-reports-its-authenticated-account.md) | [#18](https://github.com/TrogonStack/t3code/pull/18) | active | -| 0010 | [Pull request conventions of our own](./0010-fork-pull-request-conventions.md) | [#19](https://github.com/TrogonStack/t3code/pull/19) | active | -| 0011 | [Follow the background work a thread left running](./0011-follow-background-work.md) | [#20](https://github.com/TrogonStack/t3code/pull/20) | active | -| 0012 | [The timeline scrolls only as far as its content](./0012-timeline-scrolls-only-as-far-as-its-content.md) | [#21](https://github.com/TrogonStack/t3code/pull/21) | active | +- **0003** [Native subagent threads for Claude orchestrators](./0003-native-subagent-threads.md) + active, [#3](https://github.com/TrogonStack/t3code/pull/3) +- **0006** [Fork schema on its own migration ledger](./0006-fork-migration-ledger.md) + active, [#13](https://github.com/TrogonStack/t3code/pull/13), [#16](https://github.com/TrogonStack/t3code/pull/16) +- **0007** [API-key Codex installs are not reported as broken](./0007-codex-api-key-auth-is-supported.md) + active, [#15](https://github.com/TrogonStack/t3code/pull/15) +- **0008** [Drop a folder on the sidebar to add a project](./0008-drop-a-folder-to-add-a-project.md) + active, [#17](https://github.com/TrogonStack/t3code/pull/17) +- **0009** [Grok reports the account it is signed in as](./0009-grok-reports-its-authenticated-account.md) + active, [#18](https://github.com/TrogonStack/t3code/pull/18) +- **0010** [Pull request conventions of our own](./0010-fork-pull-request-conventions.md) + active, [#19](https://github.com/TrogonStack/t3code/pull/19) +- **0011** [Follow the background work a thread left running](./0011-follow-background-work.md) + active, [#20](https://github.com/TrogonStack/t3code/pull/20) +- **0012** [The timeline scrolls only as far as its content](./0012-timeline-scrolls-only-as-far-as-its-content.md) + active, [#21](https://github.com/TrogonStack/t3code/pull/21)