Skip to content

Commit e6ec114

Browse files
refactor(web): pull request page reacts to turns and sweep Escape without effects (#25523)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 3f94487 commit e6ec114

1 file changed

Lines changed: 48 additions & 36 deletions

File tree

‎apps/web/src/components/pullRequest/PullRequestsPage.tsx‎

Lines changed: 48 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ import { RightPanelTabs } from "../RightPanelTabs";
7878
import { isCommandPaletteOpen } from "~/commandPaletteBus";
7979
import { isElectron } from "~/env";
8080
import { resolveShortcutCommand, shortcutLabelForCommand } from "~/keybindings";
81+
import { registerCommands } from "~/shortcuts/dispatcher";
8182
import { isTerminalFocused } from "~/lib/terminalFocus";
8283
import { PanelLayoutControls, RightPanelMaximizeControl } from "../chat/PanelLayoutControls";
8384
import { Button } from "../ui/button";
@@ -286,13 +287,21 @@ export function PullRequestsPage() {
286287
* host forever.
287288
*/
288289
regrown: ReadonlyArray<string>;
289-
}>({ key: filterKey, size: PAGE_SIZE, cursors: null, regrown: [] });
290+
/** The turn refreshes a continuation was read under; a later turn makes its cursors stale. */
291+
turn: string;
292+
}>({ key: filterKey, size: PAGE_SIZE, cursors: null, regrown: [], turn: turnRefreshToken });
290293
const pageSize = page.key === filterKey ? page.size : PAGE_SIZE;
291294
const sentCursors = page.key === filterKey ? page.cursors : null;
292295
const sentRegrown = page.key === filterKey ? page.regrown : [];
293296

294297
if (page.key !== filterKey) {
295-
setPage({ key: filterKey, size: PAGE_SIZE, cursors: null, regrown: [] });
298+
setPage({
299+
key: filterKey,
300+
size: PAGE_SIZE,
301+
cursors: null,
302+
regrown: [],
303+
turn: turnRefreshToken,
304+
});
296305
}
297306

298307
const listTargets = environmentQueries.flatMap(({ environmentId, projectIds }) => {
@@ -591,6 +600,7 @@ export function PullRequestsPage() {
591600
size: regrown.length === 0 ? pageSize : Math.min(pageSize + PAGE_SIZE, MAX_PAGE_SIZE),
592601
cursors: nextCursors,
593602
regrown,
603+
turn: turnRefreshToken,
594604
});
595605
return;
596606
}
@@ -599,9 +609,28 @@ export function PullRequestsPage() {
599609
size: Math.min(pageSize + PAGE_SIZE, MAX_PAGE_SIZE),
600610
cursors: null,
601611
regrown: [],
612+
turn: turnRefreshToken,
602613
});
603614
};
604615

616+
const collapsedPage = () => {
617+
const loadedCount =
618+
ordered?.key === filterKey ? largestPullRequestReadCount(ordered.entries) : pageSize;
619+
return {
620+
key: filterKey,
621+
regrown: [],
622+
size: Math.min(
623+
Math.max(pageSize, Math.ceil(loadedCount / PAGE_SIZE) * PAGE_SIZE),
624+
MAX_PAGE_SIZE,
625+
),
626+
cursors: null,
627+
turn: turnRefreshToken,
628+
};
629+
};
630+
// Every list read refetches itself when a turn completes, but a continuation would refetch only
631+
// its later page and strand the rows before it, so the whole loaded list is asked for again.
632+
if (sentCursors !== null && page.turn !== turnRefreshToken) setPage(collapsedPage());
633+
605634
const refreshList = (includeRelated = false, actedEnvironmentId?: EnvironmentId) => {
606635
const related = (
607636
includeRelated
@@ -627,31 +656,9 @@ export function PullRequestsPage() {
627656
return;
628657
}
629658
if (related.length > 0) listQuery.refresh(related);
630-
const loadedCount =
631-
ordered?.key === filterKey ? largestPullRequestReadCount(ordered.entries) : pageSize;
632-
setPage({
633-
key: filterKey,
634-
regrown: [],
635-
size: Math.min(
636-
Math.max(pageSize, Math.ceil(loadedCount / PAGE_SIZE) * PAGE_SIZE),
637-
MAX_PAGE_SIZE,
638-
),
639-
cursors: null,
640-
});
659+
setPage(collapsedPage());
641660
};
642661

643-
const appliedTurnRefreshToken = useRef("");
644-
const refreshAfterTurn = useEffectEvent(() => {
645-
if (sentCursors !== null) refreshList();
646-
});
647-
useEffect(() => {
648-
if (turnRefreshToken.length === 0 || appliedTurnRefreshToken.current === turnRefreshToken) {
649-
return;
650-
}
651-
appliedTurnRefreshToken.current = turnRefreshToken;
652-
refreshAfterTurn();
653-
}, [turnRefreshToken]);
654-
655662
useLiveRefresh(
656663
() => {
657664
refreshList(true);
@@ -917,15 +924,6 @@ export function PullRequestsPage() {
917924
// oxlint-disable-next-line react/exhaustive-effect-dependencies -- The pointer sensor lives outside React; a new list under a sweep in progress must end it.
918925
[filterKey, search.q, sort],
919926
);
920-
useEffect(() => {
921-
const onKeyDown = (event: KeyboardEvent) => {
922-
if (event.key !== "Escape" || closeSensorRef.current === null) return;
923-
event.preventDefault();
924-
closeSensorRef.current.cancel();
925-
};
926-
document.addEventListener("keydown", onKeyDown, true);
927-
return () => document.removeEventListener("keydown", onKeyDown, true);
928-
}, []);
929927
const startCloseSweep = useStableCallback(
930928
(entry: EnvironmentPullRequestEntry, event: PointerEvent) => {
931929
closeSensorRef.current?.cancel();
@@ -957,14 +955,27 @@ export function PullRequestsPage() {
957955
sweptKeys = resolveSidebarSweepKeys(orderedKeys, originKey, key, canClose);
958956
setCloseSweepKeys(new Set(sweptKeys));
959957
};
960-
closeSensorRef.current = new SidebarPointerSensor({
958+
// Only while a sweep runs: Escape ends it, ahead of any other scope that would also take it.
959+
const releaseEscape = registerCommands(
960+
{},
961+
{
962+
capture: true,
963+
onKeyDown: (keyEvent) => {
964+
if (keyEvent.key !== "Escape") return false;
965+
keyEvent.preventDefault();
966+
sensor.cancel();
967+
},
968+
},
969+
);
970+
const sensor = new SidebarPointerSensor({
961971
active: originKey,
962972
event,
963973
options: {
964974
distance: 6,
965975
onAttach: () => {},
966976
onFinish: () => {
967-
closeSensorRef.current = null;
977+
releaseEscape();
978+
if (closeSensorRef.current === sensor) closeSensorRef.current = null;
968979
setCloseSweepKeys(new Set());
969980
},
970981
},
@@ -992,6 +1003,7 @@ export function PullRequestsPage() {
9921003
onCancel: () => {},
9931004
onAbort: () => {},
9941005
});
1006+
closeSensorRef.current = sensor;
9951007
},
9961008
);
9971009
const rightPanelAvailable = selectedPullRequestSurface !== null;

0 commit comments

Comments
 (0)