From ca6d0bf58aaf2c9fbc05c7e74371d3021272c879 Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Mon, 20 Jul 2026 09:21:05 +0300 Subject: [PATCH 1/3] fix(code-review): scroll to next file after viewing Keep review progress aligned by moving the next file to the top after marking the current file as viewed. Generated-By: PostHog Code Task-Id: f9593454-58e4-44a7-9abe-2033ae332f9c --- .../code-review/reviewShellGeometry.test.ts | 13 ++++++++ .../src/code-review/reviewShellGeometry.ts | 14 ++++++++ .../code-review/components/ReviewShell.tsx | 33 ++++++++++++++----- .../features/code-review/reviewShellParts.tsx | 1 + 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/packages/core/src/code-review/reviewShellGeometry.test.ts b/packages/core/src/code-review/reviewShellGeometry.test.ts index 75838ac479..a671fa7fbd 100644 --- a/packages/core/src/code-review/reviewShellGeometry.test.ts +++ b/packages/core/src/code-review/reviewShellGeometry.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { buildItemIndex, + findNextScrollKey, getDeferredMessage, splitFilePath, sumHunkStats, @@ -45,6 +46,18 @@ describe("buildItemIndex", () => { }); }); +describe("findNextScrollKey", () => { + it.each([ + ["a", "b"], + ["b", null], + ["missing", null], + ])("finds the next file after %s", (currentKey, expected) => { + const items = [{ scrollKey: "a" }, {}, { scrollKey: "b" }, {}]; + + expect(findNextScrollKey(items, currentKey)).toBe(expected); + }); +}); + describe("getDeferredMessage", () => { it("returns the line-limit message", () => { expect(getDeferredMessage("line-limit")).toContain("5,000-line"); diff --git a/packages/core/src/code-review/reviewShellGeometry.ts b/packages/core/src/code-review/reviewShellGeometry.ts index 02bcae15bb..77df74087a 100644 --- a/packages/core/src/code-review/reviewShellGeometry.ts +++ b/packages/core/src/code-review/reviewShellGeometry.ts @@ -37,6 +37,20 @@ export function buildItemIndex( return index; } +export function findNextScrollKey( + items: { scrollKey?: string }[], + currentKey: string, +): string | null { + const currentIndex = items.findIndex((item) => item.scrollKey === currentKey); + if (currentIndex === -1) return null; + + for (let i = currentIndex + 1; i < items.length; i++) { + const key = items[i].scrollKey; + if (key) return key; + } + return null; +} + export function getDeferredMessage(reason: DeferredReason): string { switch (reason) { case "line-limit": diff --git a/packages/ui/src/features/code-review/components/ReviewShell.tsx b/packages/ui/src/features/code-review/components/ReviewShell.tsx index 3e03271bfc..5f52dbc1a7 100644 --- a/packages/ui/src/features/code-review/components/ReviewShell.tsx +++ b/packages/ui/src/features/code-review/components/ReviewShell.tsx @@ -17,6 +17,7 @@ import { useReviewNavigationStore } from "../reviewNavigationStore"; import type { ReviewListItem, ReviewShellProps } from "../reviewShellParts"; import { findActiveScrollKey, + findNextScrollKey, findRenderedScrollAnchor, isFileViewed, } from "../reviewShellParts"; @@ -187,26 +188,40 @@ export function ReviewShell({ if (prState === "merged") clearTasks([taskId]); }, [prState, taskId, clearTasks]); - const viewedContextValue = useMemo( - () => ({ - viewedRecord, - currentSignatures, - toggleViewed: onToggleViewed, - }), - [viewedRecord, currentSignatures, onToggleViewed], - ); - const scrollRequest = useReviewNavigationStore( (s) => s.scrollRequests[taskId] ?? null, ); const clearScrollRequest = useReviewNavigationStore( (s) => s.clearScrollRequest, ); + const requestScrollToFile = useReviewNavigationStore( + (s) => s.requestScrollToFile, + ); const setActiveFilePath = useReviewNavigationStore( (s) => s.setActiveFilePath, ); const clearTask = useReviewNavigationStore((s) => s.clearTask); + const toggleViewed = useCallback( + (key: string, signature: string | null) => { + onToggleViewed(key, signature); + if (signature === null) return; + + const nextKey = findNextScrollKey(items, key); + if (nextKey) requestScrollToFile(taskId, nextKey); + }, + [items, onToggleViewed, requestScrollToFile, taskId], + ); + + const viewedContextValue = useMemo( + () => ({ + viewedRecord, + currentSignatures, + toggleViewed, + }), + [viewedRecord, currentSignatures, toggleViewed], + ); + useEffect(() => { return () => { if (navigationFrameRef.current !== null) { diff --git a/packages/ui/src/features/code-review/reviewShellParts.tsx b/packages/ui/src/features/code-review/reviewShellParts.tsx index fa1327522f..d65fa1240a 100644 --- a/packages/ui/src/features/code-review/reviewShellParts.tsx +++ b/packages/ui/src/features/code-review/reviewShellParts.tsx @@ -30,6 +30,7 @@ import { useReviewViewedStore } from "./reviewViewedStore"; export type { DeferredReason } from "@posthog/core/code-review/reviewShellGeometry"; export { buildItemIndex, + findNextScrollKey, splitFilePath, } from "@posthog/core/code-review/reviewShellGeometry"; From 8dbc4078cc6bda2ad00d713edd22c33b1663171f Mon Sep 17 00:00:00 2001 From: Richard Solomou Date: Mon, 20 Jul 2026 09:38:39 +0300 Subject: [PATCH 2/3] fix(code-review): avoid component export warning Import the navigation helper from core directly so React Doctor no longer treats the component module as adding another non-component export. Generated-By: PostHog Code Task-Id: f9593454-58e4-44a7-9abe-2033ae332f9c --- packages/ui/src/features/code-review/components/ReviewShell.tsx | 2 +- packages/ui/src/features/code-review/reviewShellParts.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/ui/src/features/code-review/components/ReviewShell.tsx b/packages/ui/src/features/code-review/components/ReviewShell.tsx index 5f52dbc1a7..45e32b71ef 100644 --- a/packages/ui/src/features/code-review/components/ReviewShell.tsx +++ b/packages/ui/src/features/code-review/components/ReviewShell.tsx @@ -1,4 +1,5 @@ import { WorkerPoolContextProvider } from "@pierre/diffs/react"; +import { findNextScrollKey } from "@posthog/core/code-review/reviewShellGeometry"; import { useService } from "@posthog/di/react"; import type { Task } from "@posthog/shared/domain-types"; import { useArchivedTaskIds } from "@posthog/ui/features/archive/useArchivedTaskIds"; @@ -17,7 +18,6 @@ import { useReviewNavigationStore } from "../reviewNavigationStore"; import type { ReviewListItem, ReviewShellProps } from "../reviewShellParts"; import { findActiveScrollKey, - findNextScrollKey, findRenderedScrollAnchor, isFileViewed, } from "../reviewShellParts"; diff --git a/packages/ui/src/features/code-review/reviewShellParts.tsx b/packages/ui/src/features/code-review/reviewShellParts.tsx index d65fa1240a..fa1327522f 100644 --- a/packages/ui/src/features/code-review/reviewShellParts.tsx +++ b/packages/ui/src/features/code-review/reviewShellParts.tsx @@ -30,7 +30,6 @@ import { useReviewViewedStore } from "./reviewViewedStore"; export type { DeferredReason } from "@posthog/core/code-review/reviewShellGeometry"; export { buildItemIndex, - findNextScrollKey, splitFilePath, } from "@posthog/core/code-review/reviewShellGeometry"; From 10da13a4c2af8456de473b6435d9f04d3c2b1292 Mon Sep 17 00:00:00 2001 From: "posthog[bot]" <206114724+posthog[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 12:11:16 +0000 Subject: [PATCH 3/3] chore(visual): update storybook baselines 1 updated Run: 607b1d03-c58c-464b-a7b0-1cf9f09f774c Co-authored-by: richardsolomou <2622273+richardsolomou@users.noreply.github.com> --- apps/code/snapshots.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/code/snapshots.yml b/apps/code/snapshots.yml index cfbdd290b1..26cb8c4587 100644 --- a/apps/code/snapshots.yml +++ b/apps/code/snapshots.yml @@ -27,7 +27,7 @@ snapshots: archive-archivedtasksview--many-tasks--dark: hash: v1.k4693efd2.40d24f07a24c6400c32ae68b39fd908a056beb5dc6df8bcb237ec2ab2b494f4a.l2vjfSQDalCPRtDNV7pbfTKGlMsQoI81jI-UdJfLHWE archive-archivedtasksview--many-tasks--light: - hash: v1.k4693efd2.52ec9963bfe6f248ffcdaa4c26945d2b7e303115825b65d978aa5aefd4af1007.ZLtOfTZUrznhYeB-N2SIHzQWRzF6TDSaipNkfd8uzjM + hash: v1.k4693efd2.74c25b303262f2d4c0f22890d351e76f482827548548f7c023bc309327c927b8.DP9VVrvBz1naIJMwHORkfqm69BlO785ulOt4o6zFs94 archive-archivedtasksview--mixed-modes--dark: hash: v1.k4693efd2.d94039b8cc17a4ad1b720364f41fee58a4843aa9a901443997ba62602f698794.441LWZhT-2WQZJHni4LoOgQsOSr2O103NZOk1pF8ME4 archive-archivedtasksview--mixed-modes--light: