Files
kandev/apps/web/components/diff/inline-review-finding.tsx
T
João Salavisa a127e2dffd feat(review): navigate to code-review findings from the Changes panel (#1963)
* feat(review): navigate to code-review findings from the Changes panel

The findings count in the review top bar was a passive badge, so a user
had to scroll the diff to hunt for each inline finding. Turn it into a
navigator: it opens a popover listing every open finding grouped by file
(most-severe first), and clicking one jumps to the finding's card in the
diff, scrolls it into view, and briefly flashes it.

- lib/review/navigation.ts: navigateToFinding selects the finding's file,
  emits an event, then polls across frames (files render lazily) for the
  card and scrolls + flashes it. Reuses the composite <repo>\x00<path>
  key so cross-repo same-named files stay distinct.
- review-findings-overview + review-findings-button: the grouped list and
  its click-to-open popover trigger, mirroring the FixCommentsButton /
  ReviewCommentsOverview pattern. The badge moves out of ReviewRunButton
  into the top bar, keeping the review-open-count testid and copy.
- inline-review-finding: stamps data-review-finding-id + scroll margin so
  navigation can target the card.
- unanchored-findings-banner: auto-expands when a navigation targets one
  of its stale findings so that card renders and can be scrolled to.
- globals.css: review-finding-flash highlight, respecting reduced motion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(review): harden finding navigation from code-review feedback

Address automated review of the findings navigator:

- navigation.ts: scope the finding lookup to the open review dialog so a
  duplicate card rendered by the Changes panel behind it is not scrolled
  to instead; re-emit the navigate event each poll frame so a lazily
  mounted stale-findings banner still expands (a one-shot event fired
  before the section rendered was missed); honor prefers-reduced-motion
  for the scroll, matching the flash animation.
- review-dialog-surface: clear the sidebar file filter before selecting a
  finding's file, so a finding in a filtered-out file still mounts.
- review-findings-button: surface a warning when the card can't be reached
  instead of discarding the promise silently.
- review-findings-overview: compute the file directory once per row.
- Tests: unit coverage for the banner auto-expand and the findings button
  (null render + navigate), plus a mobile-* e2e that taps the navigator
  and verifies the target card is reached.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 18:17:49 +01:00

44 lines
1.6 KiB
TypeScript

"use client";
import { useAppStore } from "@/components/state-provider";
import { useFindingActions } from "@/hooks/domains/review/use-finding-actions";
import { useSendFindingToAgent } from "@/hooks/domains/review/use-send-finding-to-agent";
import { FINDING_DOM_ATTR } from "@/lib/review/navigation";
import type { TaskReviewFinding } from "@/lib/types/review";
import { ReviewFindingCard } from "./review-finding-card";
/**
* One review finding rendered inline at its anchored diff line.
*
* This container wires its own actions from the store rather than taking them as
* props: an inline finding annotation only exists when the active task has
* findings, so the store is always present, and threading four callbacks through
* FileDiffViewer → DiffViewer → the annotation renderer would add plumbing that
* every other diff consumer would have to carry.
*/
export function InlineReviewFinding({
finding,
staleReason,
}: {
finding: TaskReviewFinding;
staleReason?: string;
}) {
const taskId = useAppStore((s) => s.tasks.activeTaskId);
const sessionId = useAppStore((s) => s.tasks.activeSessionId);
const { resolveFinding, dismissFinding, reopenFinding } = useFindingActions(taskId);
const sendToAgent = useSendFindingToAgent({ taskId, sessionId });
return (
<div className="my-1 scroll-mt-24 px-2 rounded-md" {...{ [FINDING_DOM_ATTR]: finding.id }}>
<ReviewFindingCard
finding={finding}
staleReason={staleReason}
onResolve={resolveFinding}
onDismiss={dismissFinding}
onReopen={reopenFinding}
onSendToAgent={sessionId ? sendToAgent : undefined}
/>
</div>
);
}