Fix branch refresh.

This commit is contained in:
Greyson Parrelli
2026-08-27 12:10:32 -04:00
parent 11ebe07e4f
commit 2e3af19d00
3 changed files with 44 additions and 6 deletions
+27 -4
View File
@@ -205,6 +205,9 @@ export default function App() {
const [toast, setToast] = useState<string | null>(null);
const path = repo?.path ?? null;
// What the header currently says the work tree is on. The poll below compares
// against it to notice a branch switch made outside this pane.
const branch = repo?.branch ?? null;
// ctxByRepo holds only the repos whose base ref you've actually changed;
// anything else falls back to HEAD. Deriving rather than seeding state avoids a
@@ -338,6 +341,18 @@ export default function App() {
if (path) loadDiff(ctx);
}, [path, ctx, loadDiff]);
// What the refresh button and the stale banner do. Deliberately more than
// reloading the patch: the reason to press either is that the world moved, and
// everything the header shows — the branch, the ref list the base picker offers,
// the draft counts — is otherwise fetched once when the page loads and never
// again. The poll below catches a branch switch on its own, but it stops once
// the diff is known to be stale, and it can't see a ref that was merely
// created; pressing refresh is the answer to both.
const reload = useCallback(() => {
loadRepo();
loadDiff(ctx);
}, [loadRepo, loadDiff, ctx]);
// Tell the server which diff is on screen, so an agent asked to review it lands
// its comments on the lines you're actually looking at. Best-effort — nothing on
// screen depends on it.
@@ -404,10 +419,18 @@ export default function App() {
busy = true;
const seq = reqRef.current;
try {
const { revision: now } = await api.revision(ctx, { ignoreWhitespace: ignoreWs });
const { revision: now, branch: on } = await api.revision(ctx, {
ignoreWhitespace: ignoreWs,
});
// A load that started while this was in flight has already answered the
// question, with a revision this closure doesn't know about.
if (canceled || seq !== reqRef.current) return;
// A branch switch is news about the repository rather than about the
// diff, and the two don't always arrive together — so it is acted on
// rather than announced. The header is describing the work tree, which
// really is on another branch now; the diff below it is still the one
// you were reading, and the banner is what offers to move that.
if (on && on !== branch) loadRepo();
if (now && now !== revision) setStale(now);
} catch {
// A failed poll says nothing about the diff — the next one will.
@@ -428,7 +451,7 @@ export default function App() {
document.removeEventListener('visibilitychange', onVisible);
window.removeEventListener('focus', onVisible);
};
}, [path, ctx, ignoreWs, revision, stale]);
}, [path, ctx, ignoreWs, revision, stale, branch, loadRepo]);
// dismissStale keeps the diff you are reading and stops pointing at it, but
// takes the newer state as the baseline — so the *next* change says so too,
@@ -723,7 +746,7 @@ export default function App() {
<button
className={`icon-btn${stale ? ' is-attention' : ''}`}
onClick={() => loadDiff(ctx)}
onClick={reload}
title={stale ? 'Reload the diff — the work tree has moved on' : 'Refresh diff'}
aria-label="Refresh diff"
>
@@ -779,7 +802,7 @@ export default function App() {
<span className="banner-text">
This diff is out of date the work tree has changed since it loaded.
</span>
<button className="btn-ghost" onClick={() => loadDiff(ctx)}>
<button className="btn-ghost" onClick={reload}>
<Icon name="sync" size={14} />
Refresh
</button>
+7 -1
View File
@@ -108,13 +108,19 @@ export const api = {
// `revision` that came with the diff on screen. One hash, so it's cheap to ask
// for repeatedly — which is what lets the page notice the diff is out of date
// without pulling it out from under whoever is reading it.
//
// `branch` is the work tree's current branch, answered here because this is the
// one thing the page asks about on a timer. The header names the branch, and
// nothing else would tell the page it had changed: the digest covers HEAD's
// sha, so branching off the commit you're on doesn't move it (see
// Server.handleRevision), and repo info is otherwise fetched once at load.
revision: (ctx: DiffContext, opts: { ignoreWhitespace?: boolean } = {}) =>
fetch(
`${apiBase}/diff/revision${q(
...ctxParams(ctx),
...(opts.ignoreWhitespace ? ['ignoreWhitespace=1'] : []),
)}`,
).then(json<{ revision: string }>),
).then(json<{ revision: string; branch?: string }>),
// Full contents of a file at a ref, for expanding collapsed context. Null when
// the file doesn't exist at that ref (e.g. a newly added file).