Skip to content

Commit 778ebbb

Browse files
committed
fix(pager): key live-comment bucket off changeset.id
Same anonymous-commit collapse the previous commit fixed for the commit buffer also applied to the live-comment store. The bucket key was the active commit's sha (with an empty-string fallback for non-commit-review), so every commit without a parsed sha shared one bucket — and that bucket was the same one the non-commit-review canvas used as its sentinel. Switch the key to the active changeset's id, which is `commit:<sha>` for normal commits, `commit:anonymous:N` for anonymous ones, and `changeset:...` shapes elsewhere. Buckets now stay distinct across all modes, and the DEFAULT_REVIEW_KEY sentinel is no longer needed. Renamed liveCommentsBySha to liveCommentsByReviewKey to match.
1 parent f40a925 commit 778ebbb

1 file changed

Lines changed: 18 additions & 17 deletions

File tree

src/ui/AppHost.tsx

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,6 @@ import type { HunkSessionBrokerClient, LiveComment } from "../hunk-session/types
1919
import { App } from "./App";
2020
import { useStartupUpdateNotice } from "./hooks/useStartupUpdateNotice";
2121

22-
/** Sentinel review key used when no commit cursor is present. */
23-
const DEFAULT_REVIEW_KEY = "";
24-
2522
/** Result returned by `onMoveCommit` so the caller can detect blocked moves. */
2623
export type MoveCommitResult =
2724
| { kind: "moved"; index: number }
@@ -53,9 +50,9 @@ export function AppHost({
5350
// survive the App remount that fires on every commit-cursor move. App's view-state
5451
// reset on commit nav is intentional for selection / scroll / filter (they don't
5552
// translate across commits), but the user's metadata-visibility preference and the
56-
// notes they've left should follow them. Live comments are bucketed by commit sha so
57-
// each commit keeps its own annotation set; switching back to a previously visited
58-
// commit restores its notes.
53+
// notes they've left should follow them. Live comments are bucketed by the active
54+
// changeset's id so each commit (or each non-commit-review canvas) keeps its own
55+
// annotation set; switching back to a previously visited commit restores its notes.
5956
const [commitDetailsMode, setCommitDetailsMode] = useState<CommitDetailsMode>(
6057
bootstrap.initialCommitDetailsMode ?? "full",
6158
);
@@ -64,31 +61,35 @@ export function AppHost({
6461
current === "full" ? "compact" : current === "compact" ? "hidden" : "full",
6562
);
6663
}, []);
67-
const [liveCommentsBySha, setLiveCommentsBySha] = useState<
64+
const [liveCommentsByReviewKey, setLiveCommentsByReviewKey] = useState<
6865
Record<string, Record<string, LiveComment[]>>
6966
>({});
70-
const currentReviewKey = activeBootstrap.currentCommit?.sha ?? DEFAULT_REVIEW_KEY;
67+
// Bucket key is the active changeset's id rather than the commit sha. The producer
68+
// already builds it as `commit:<sha>` for normal commits and `commit:anonymous:N`
69+
// for commits with no parsed sha, so anonymous commits each get their own bucket
70+
// instead of colliding on an empty-string key.
71+
const currentReviewKey = activeBootstrap.changeset.id;
7172
const liveCommentsByFileId = useMemo<Record<string, LiveComment[]>>(
72-
() => liveCommentsBySha[currentReviewKey] ?? {},
73-
[liveCommentsBySha, currentReviewKey],
73+
() => liveCommentsByReviewKey[currentReviewKey] ?? {},
74+
[liveCommentsByReviewKey, currentReviewKey],
7475
);
7576
// Updater scoped to the current review's slice. Forwarding an updater here keeps the
7677
// controlled-hook pattern in useReviewController (functional and value setters both
77-
// work) while AppHost decides which sha bucket the writes land in.
78+
// work) while AppHost decides which review-key bucket the writes land in.
7879
const setLiveCommentsByFileId = useCallback<
7980
Dispatch<SetStateAction<Record<string, LiveComment[]>>>
8081
>(
8182
(action) => {
82-
setLiveCommentsBySha((bySha) => {
83-
const currentSlice = bySha[currentReviewKey] ?? {};
83+
setLiveCommentsByReviewKey((byKey) => {
84+
const currentSlice = byKey[currentReviewKey] ?? {};
8485
const nextSlice =
8586
typeof action === "function"
8687
? (action as (prev: Record<string, LiveComment[]>) => Record<string, LiveComment[]>)(
8788
currentSlice,
8889
)
8990
: action;
90-
if (nextSlice === currentSlice) return bySha;
91-
return { ...bySha, [currentReviewKey]: nextSlice };
91+
if (nextSlice === currentSlice) return byKey;
92+
return { ...byKey, [currentReviewKey]: nextSlice };
9293
});
9394
},
9495
[currentReviewKey],
@@ -266,9 +267,9 @@ export function AppHost({
266267

267268
setActiveBootstrap(nextBootstrap);
268269
if (options?.resetApp !== false) {
269-
// A full reload is a fresh review canvas — drop any cached per-sha comments
270+
// A full reload is a fresh review canvas — drop any cached per-review comments
270271
// since the file IDs and content no longer correspond to the new bootstrap.
271-
setLiveCommentsBySha({});
272+
setLiveCommentsByReviewKey({});
272273
setAppVersion((current) => current + 1);
273274
}
274275

0 commit comments

Comments
 (0)