fix(terminal): the history notice waits for the pull already in flight (#583 merge-time fix)

_smoothScrollBy and the touch paths call _maybeLoadMoreHistoryOnScroll on
every frame. Once the viewport sat at row 0, the next frame hit the real
pull's in-flight early return, so its reveal ran at once and the notice
showed the pre-pull tail state for the whole pull. A later frame now leaves
the reveal to the call that started the pull.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-10-13 17:48:11 +02:00
parent f2d5af2b4c
commit ee038ad83c
2 changed files with 29 additions and 0 deletions
+3
View File
@@ -3681,6 +3681,9 @@ Object.assign(CodemanApp.prototype, {
return;
}
if (lines === 0 || this.terminal?.buffer?.active?.viewportY !== 0) return;
// An earlier frame of this gesture already started the pull; its own
// completion reveals the notice, so do not flash the pre-pull state now.
if (this._fullHistoryRepullInFlight || this._isLoadingBuffer) return;
const sessionId = this.activeSessionId;
Promise.resolve(this._maybeRefetchFullHistory?.())
.catch(() => {})
+26
View File
@@ -184,6 +184,32 @@ describe('partial-history notice: lazy reveal', () => {
expect(loadButton(bar)?.textContent).toBe('Load full history');
});
it('a later frame of the same gesture does not flash the pre-pull state while the pull runs', async () => {
const { app, bar, buffer } = makeApp();
app._setHistoryTruncation('s1', TAIL);
// Like the real pull: it marks itself in flight and returns at once to every later caller.
const pull = { release: () => {} };
app._maybeRefetchFullHistory.mockImplementation(() => {
if (app._fullHistoryRepullInFlight) return Promise.resolve();
app._fullHistoryRepullInFlight = true;
return new Promise<void>((res) => {
pull.release = () => {
app._fullHistoryRepullInFlight = false;
res();
};
});
});
buffer.viewportY = 0;
app._maybeLoadMoreHistoryOnScroll(-500);
app._maybeLoadMoreHistoryOnScroll(-1); // the next animation frame, still at row 0
for (let i = 0; i < 5; i++) await Promise.resolve();
expect(bar.hidden).toBe(true);
pull.release();
for (let i = 0; i < 5; i++) await Promise.resolve();
expect(bar.hidden).toBe(false);
});
it('shows what the pull left: nothing, when the pull brought everything back', async () => {
const { app, bar, buffer } = makeApp();
app._setHistoryTruncation('s1', TAIL);