Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/quiet-row-facade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
4 changes: 2 additions & 2 deletions docs/browser-review-seam-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,13 @@ whether a client needs more than that is Phase 5's first question.
_Repaid (Phase 1 PR 2, model side)_: `splitLineCount`/`unifiedLineCount` carried on
`ReviewFileV1` by `core/review/document.ts`; the browser consumes them in Phase 5.
- **A8. Empty-diff explanation — 3 variants with different precedence.** Terminal
`renderRows.tsx` `diffMessage` (rename-pure first), web `ReviewStream.tsx` (binary first),
`plannedRowText.ts` `diffMessage` (rename-pure first), web `ReviewStream.tsx` (binary first),
`staticDiffPager.ts` (extra cases). Same file can explain itself differently per client.
Fix: `reviewEmptyDiffReason(file)` in core.
_Repaid (Phase 1 PR 2)_: `reviewEmptyDiffReason` in `core/review/document.ts` with one canonical
precedence — what the change _is_ outranks how it is stored (`rename-only` -> `binary` ->
`too-large` -> `new-file` -> `deleted-file` -> `no-hunks`), the review stream's existing order.
`renderRows.tsx` and `staticDiffPager.ts` keep their own wording and share the reason; fixture
`plannedRowText.ts` and `staticDiffPager.ts` keep their own wording and share the reason; fixture
`binary-rename-with-no-rows`. The static pager's own order put storage first, so a renamed
binary or oversized rename now reports as a rename there too.
- **A9. STML tag vocabulary — parse shared, tag semantics forked.** Terminal
Expand Down
7 changes: 5 additions & 2 deletions docs/extension-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,11 @@ text. `src/ui/diff/rowStyle.ts` resolves tones against the actual line
background with the word-diff minimum-contrast guarantee.
`src/ui/diff/CodeRowView.tsx` applies the transform through the cell painter,
which keeps highlights out of `buildDiffSectionRowPlan`, its caches, and every
geometry measurement: a highlight change is a repaint, never a re-plan. The
static pager never runs extension code, so highlights are interactive-only.
geometry measurement: a highlight change is a repaint, never a re-plan.
`src/ui/diff/DiffRowView.tsx` remains only the memoized dispatch facade; raw-row
adaptation there supports the public OpenTUI and extension current-line surfaces, while
`src/ui/diff/cursorHighlight.ts` owns stable-key cursor matching. The static pager never
runs extension code, so highlights are interactive-only.

Agent attention marks (`hunk session highlight add` / `clear`) join this same
pipeline rather than growing a second one: `useTerminalReview.ts` validates
Expand Down
2 changes: 1 addition & 1 deletion src/opentui/HunkDiffBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DEFAULT_TAB_WIDTH } from "../core/run/tabWidth";
import { findMaxLineNumber } from "../ui/diff/codeColumns";
import { buildSplitRows, buildStackRows } from "../ui/diff/diffRows";
import { diffMessage, fitText } from "../ui/diff/plannedRowText";
import { DiffRowView } from "../ui/diff/renderRows";
import { DiffRowView } from "../ui/diff/DiffRowView";
import { useHighlightedDiff } from "../ui/diff/useHighlightedDiff";
import { resolveTheme } from "../ui/themes";
import { toInternalDiffFile } from "./model";
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/panes/DiffPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
} from "../../../core/review/state";
import type { FileSourceStatus } from "../../diff/expandCollapsedRows";
import type { ActiveAddNoteAffordance } from "../../diff/DiffSectionBody";
import type { CursorHighlight } from "../../diff/renderRows";
import type { CursorHighlight } from "../../diff/cursorHighlight";
import { isNestedRowMouseAction } from "../../diff/rowMouseActions";
import type { DraftReviewNote } from "../../lib/reviewNoteMapping";
import {
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/panes/DiffSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { LayoutMode } from "../../../core/run/commandInputs";
import type { UserNoteLineTarget } from "../../../core/liveComments";
import type { FileSourceStatus } from "../../diff/expandCollapsedRows";
import { DiffSectionBody, type ActiveAddNoteAffordance } from "../../diff/DiffSectionBody";
import type { CursorHighlight } from "../../diff/renderRows";
import type { CursorHighlight } from "../../diff/cursorHighlight";
import type { VisibleBodyBounds } from "../../diff/rowWindowing";
import type { DiffSectionGeometry } from "../../diff/diffSectionGeometry";
import type { DiffSectionRowPlan } from "../../diff/diffSectionRowPlan";
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/panes/FileView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {
} from "../../../extension-api/types";
import type { AppTheme } from "../../themes";
import type { DiffSectionGeometry } from "../../diff/diffSectionGeometry";
import { plannedRowMatchesCursor, type CursorHighlight } from "../../diff/renderRows";
import { plannedRowMatchesCursor, type CursorHighlight } from "../../diff/cursorHighlight";
import { cursorLineHighlightBg } from "../../diff/rowStyle";
import { resolveVisibleRowIndexWindow, type VisibleBodyBounds } from "../../diff/rowWindowing";
import { reviewRowId } from "../../lib/ids";
Expand Down
2 changes: 1 addition & 1 deletion src/ui/components/ui-components.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const { DiffFileHeaderRow } = await import("./panes/DiffFileHeaderRow");
const { DiffSectionBody } = await import("../diff/DiffSectionBody");
const { measurePlannedRenderedRowHeight, measureRenderedRowHeight } =
await import("../diff/codeRowLayout");
const { DiffRowView } = await import("../diff/renderRows");
const { DiffRowView } = await import("../diff/DiffRowView");

function createTestDiffFile(
id: string,
Expand Down
2 changes: 1 addition & 1 deletion src/ui/diff/CodeCellView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { legacyPlannedDiffRow, planCodeRowLayout } from "./codeRowLayout";
import type { DiffRow } from "./diffRows";
import { lineHighlightPaintKey, type LineHighlightPaintIndex } from "./lineHighlightPaint";
import { DiffRowView } from "./renderRows";
import { DiffRowView } from "./DiffRowView";
import { resolveTheme, withTransparentSurfaces } from "../themes";

/** Capture one code-row component and always release its OpenTUI renderer. */
Expand Down
11 changes: 2 additions & 9 deletions src/ui/diff/CodeRowView.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/** Mounts split and stack code rows from the canonical code-row layout and paint plans. */
import type { UserNoteLineTarget } from "../../core/liveComments";
import type { CursorLine } from "../../core/run/commandInputs";
import type { CopySelectedRowRange } from "../lib/diffSpatial";
import type { AppTheme } from "../themes";
import { CODE_ROW_ADD_NOTE_BADGE_TEXT, CODE_ROW_ADD_NOTE_BADGE_WIDTH } from "./codeRowAffordance";
Expand All @@ -10,6 +9,7 @@ import {
type PlannedDiffReviewRow,
} from "./codeRowLayout";
import { codeCellView, FULL_CODE_CELL_COL_RANGE, type CodeCellHighlight } from "./CodeCellView";
import type { CursorHighlight } from "./cursorHighlight";
import type { DiffRow } from "./diffRows";
import type { LineHighlightPaintIndex } from "./lineHighlightPaint";
import {
Expand All @@ -29,13 +29,6 @@ export type PlannedCodeReviewRow = Omit<PlannedDiffReviewRow, "row"> & {
row: CodeDiffRow;
};

/** Cursor paint inputs consumed by mounted code rows. */
export interface CodeRowCursorHighlight {
stableKey: string;
style: Exclude<CursorLine, "off">;
side: "old" | "new";
}

export interface CodeRowViewProps {
plannedRow: PlannedCodeReviewRow;
width: number;
Expand All @@ -47,7 +40,7 @@ export interface CodeRowViewProps {
selected: boolean;
copySelectedRowRange?: CopySelectedRowRange;
copySelectedSide?: "left" | "right";
cursorHighlight?: CodeRowCursorHighlight;
cursorHighlight?: CursorHighlight;
lineHighlights?: LineHighlightPaintIndex;
showAddNoteBadge?: boolean;
onHoverRow?: (rowKey: string) => void;
Expand Down
27 changes: 4 additions & 23 deletions src/ui/diff/renderRows.tsx → src/ui/diff/DiffRowView.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,15 @@
/** Adapts and dispatches diff rows to their focused mounted row views. */
import { memo } from "react";
import type { UserNoteLineTarget } from "../../core/liveComments";
import type { CursorLine } from "../../core/run/commandInputs";
import type { CopySelectedRowRange } from "../lib/diffSpatial";
import type { AppTheme } from "../themes";
import { CodeRowView, type PlannedCodeReviewRow } from "./CodeRowView";
import { legacyPlannedDiffRow, type PlannedDiffReviewRow } from "./codeRowLayout";
import type { CursorHighlight } from "./cursorHighlight";
import { DiffMetaRowView, type PlannedDiffMetaReviewRow } from "./DiffMetaRowView";
import type { DiffRow } from "./diffRows";
import type { LineHighlightPaintIndex } from "./lineHighlightPaint";

export { isNestedRowMouseAction, markNestedRowMouseAction } from "./rowMouseActions";

export interface CursorHighlight {
/** The render plan anchor of the row the cursor rests on, shared with reveal lookups. */
stableKey: string;
style: Exclude<CursorLine, "off">;
/** Which half of a split row the cursor sits on, and where a note would anchor. */
side: "old" | "new";
}

/** Report whether one planned row carries the anchor the cursor rests on. */
export function plannedRowMatchesCursor(
row: { stableKey: string; stableAliasKeys?: readonly string[] },
cursor: CursorHighlight | undefined,
) {
return (
cursor !== undefined &&
(row.stableKey === cursor.stableKey || row.stableAliasKeys?.includes(cursor.stableKey) === true)
);
}

/** Dispatch one planned diff row to its focused metadata or code view. */
function renderRow(
plannedRow: PlannedDiffReviewRow,
Expand Down Expand Up @@ -95,7 +75,8 @@ function renderRow(
);
}

interface DiffRowViewProps {
/** Inputs accepted by the memoized diff-row facade. */
export interface DiffRowViewProps {
/** Complete review-stream row; preferred when the caller owns the shared render plan. */
plannedRow?: PlannedDiffReviewRow;
/** Raw row fallback for renderer-only surfaces outside the shared review stream. */
Expand Down
3 changes: 2 additions & 1 deletion src/ui/diff/DiffSectionBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import { plannedReviewRowVisible } from "./reviewRowGeometry";
import { buildDiffSectionRowPlan, type DiffSectionRowPlan } from "./diffSectionRowPlan";
import { resolveVisiblePlannedRowWindow, type VisibleBodyBounds } from "./rowWindowing";
import { diffMessage, fitText } from "./plannedRowText";
import { DiffRowView, plannedRowMatchesCursor, type CursorHighlight } from "./renderRows";
import { DiffRowView } from "./DiffRowView";
import { plannedRowMatchesCursor, type CursorHighlight } from "./cursorHighlight";
import { useHighlightedDiff } from "./useHighlightedDiff";
import { useHighlightedSource } from "./useHighlightedSource";

Expand Down
25 changes: 25 additions & 0 deletions src/ui/diff/cursorHighlight.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, test } from "bun:test";
import { plannedRowMatchesCursor, type CursorHighlight } from "./cursorHighlight";

const CURSOR: CursorHighlight = {
stableKey: "line:new:2",
style: "row",
side: "new",
};

describe("plannedRowMatchesCursor", () => {
test("matches canonical and alias stable keys", () => {
expect(plannedRowMatchesCursor({ stableKey: CURSOR.stableKey }, CURSOR)).toBe(true);
expect(
plannedRowMatchesCursor(
{ stableKey: "line:old:2", stableAliasKeys: [CURSOR.stableKey] },
CURSOR,
),
).toBe(true);
});

test("rejects absent and unrelated cursors", () => {
expect(plannedRowMatchesCursor({ stableKey: "line:new:3" }, CURSOR)).toBe(false);
expect(plannedRowMatchesCursor({ stableKey: CURSOR.stableKey }, undefined)).toBe(false);
});
});
22 changes: 22 additions & 0 deletions src/ui/diff/cursorHighlight.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/** Defines cursor paint inputs and matches them to stable planned-row identities. */
import type { CursorLine } from "../../core/run/commandInputs";

/** Cursor paint inputs shared by review planning and mounted code rows. */
export interface CursorHighlight {
/** The render plan anchor of the row the cursor rests on, shared with reveal lookups. */
stableKey: string;
style: Exclude<CursorLine, "off">;
/** Which half of a split row the cursor sits on, and where a note would anchor. */
side: "old" | "new";
}

/** Report whether one planned row carries the anchor the cursor rests on. */
export function plannedRowMatchesCursor(
row: { stableKey: string; stableAliasKeys?: readonly string[] },
cursor: CursorHighlight | undefined,
) {
return (
cursor !== undefined &&
(row.stableKey === cursor.stableKey || row.stableAliasKeys?.includes(cursor.stableKey) === true)
);
}
2 changes: 1 addition & 1 deletion src/ui/lib/extensionCurrentLine.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ExtensionCurrentLinePaint } from "../../extension-api/types";
import type { DiffRow, SplitLineCell, StackLineCell } from "../diff/diffRows";
import { DiffRowView } from "../diff/renderRows";
import { DiffRowView } from "../diff/DiffRowView";
import type { DiffSectionRowPlan } from "../diff/diffSectionRowPlan";
import type { LineCursor } from "./lineCursors";
import type { AppTheme } from "../themes";
Expand Down
Loading