From 4bea5f5d28924b963f4a12ed03990aa1cabcaa3b Mon Sep 17 00:00:00 2001 From: Cooper Maruyama Date: Sat, 1 Aug 2026 03:50:48 -0700 Subject: [PATCH] fix(drift): glitchy loader, item grouping bug, show hidden error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drift review actions: - Surface a status message row (Checking…/Building…/Applying…/Check failed) with a CircleAlert icon for the failed state and a teal Loader2 spinner when busy. - Add a shimmer sheen on the Build & Test button while busy so the disabled state still reads as in-progress. - Swap the buildButtonLabel computation for a single source of truth. on diff items, key DriftFileRow by file.hash instead of oldFilename + filename — robust against renames showing as separate entries. chore(review): split up monolith file --- .oxlintrc.json | 6 + .../drift-review.stories.tsx.snap | 14 +- .../drift/drift-discard-confirmation.tsx | 36 +++ .../widget/drift/drift-review-actions.tsx | 175 ++++++++++++ .../widget/drift/drift-review-build-card.tsx | 70 +++++ .../widget/drift/drift-review-content.tsx | 31 +++ .../widget/drift/drift-review-header.tsx | 48 ++++ .../widget/drift/drift-review-types.ts | 1 + .../widget/drift/drift-review.stories.tsx | 26 ++ .../components/widget/drift/drift-review.tsx | 263 ++++-------------- .../widget/drift/drift-utils.test.ts | 18 +- .../components/widget/drift/drift-utils.ts | 54 ++-- .../widget/summaries/summary-or-diff.tsx | 8 +- 13 files changed, 483 insertions(+), 267 deletions(-) create mode 100644 apps/native/src/components/widget/drift/drift-discard-confirmation.tsx create mode 100644 apps/native/src/components/widget/drift/drift-review-actions.tsx create mode 100644 apps/native/src/components/widget/drift/drift-review-build-card.tsx create mode 100644 apps/native/src/components/widget/drift/drift-review-content.tsx create mode 100644 apps/native/src/components/widget/drift/drift-review-header.tsx create mode 100644 apps/native/src/components/widget/drift/drift-review-types.ts diff --git a/.oxlintrc.json b/.oxlintrc.json index 08c7c40f4..40078e68e 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -2,6 +2,12 @@ "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["import", "typescript", "unicorn", "react", "react-perf", "vitest", "oxc"], "overrides": [ + { + "files": ["apps/native/src/components/widget/drift/**/*.tsx"], + "rules": { + "unicorn/no-nested-ternary": "error" + } + }, { "files": [ "**/*.spec.{js,mjs,ts,tsx}", diff --git a/apps/native/src/components/widget/drift/__snapshots__/drift-review.stories.tsx.snap b/apps/native/src/components/widget/drift/__snapshots__/drift-review.stories.tsx.snap index 92194f320..95c5b0649 100644 --- a/apps/native/src/components/widget/drift/__snapshots__/drift-review.stories.tsx.snap +++ b/apps/native/src/components/widget/drift/__snapshots__/drift-review.stories.tsx.snap @@ -1,13 +1,15 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`Ai Session 1`] = `"

3 changes ready to apply

Press Build & Test to activate the changes you asked for. Reviewing the diffs below is optional.

Proposed changes1 added · 1 modified · 1 removed
Also indarwin:
  • hello

    Changed an app's settings

  • manual-new.nix

    Added a new machine setup

  • modules/home/shell.nix

"`; +exports[`Ai Session 1`] = `"

3 changes ready to apply

Press Build & Test to activate the changes you asked for. Reviewing the diffs below is optional.

Proposed changes1 added · 1 modified · 1 removed
Also indarwin:
  • hello

    Changed an app's settings

  • manual-new.nix

    Added a new machine setup

  • modules/home/shell.nix

"`; -exports[`Grouped 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
  • hello, manual-new.nix, +1

    +7-4

    Switched the shell setup to a new host

"`; +exports[`Checking Build 1`] = `"
Checking changes before build
"`; -exports[`Saved Updates Ready 1`] = `"

New configuration updates are available

This Mac isn’t using the latest configuration yet. Apply the updates to bring it up to date.

"`; +exports[`Grouped 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
  • hello, manual-new.nix, +1

    +7-4

    Switched the shell setup to a new host

Build check failed
"`; -exports[`Single Change 1`] = `"

1 manual change detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 modified
Manual Changes found indarwin:
  • flake.nix

"`; +exports[`Saved Updates Ready 1`] = `"

New configuration updates are available

This Mac isn’t using the latest configuration yet. Apply the updates to bring it up to date.

"`; -exports[`Summarized 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
Also indarwin:
  • hello

    Changed an app's settings

  • manual-new.nix

    Added a new machine setup

  • modules/home/shell.nix

"`; +exports[`Single Change 1`] = `"

1 manual change detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 modified
Manual Changes found indarwin:
  • flake.nix

Build check failed
"`; -exports[`Unsummarized 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
Manual Changes found indarwin:
  • hello

  • hosts/manual-new.nix

  • modules/home/shell.nix

"`; +exports[`Summarized 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
Also indarwin:
  • hello

    Changed an app's settings

  • manual-new.nix

    Added a new machine setup

  • modules/home/shell.nix

Build check failed
"`; + +exports[`Unsummarized 1`] = `"

3 manual changes detected since your last build

Your system drifted from the tracked config in darwin. Choose what to do with these changes below.

Detected changes1 added · 1 modified · 1 removed
Manual Changes found indarwin:
  • hello

  • hosts/manual-new.nix

  • modules/home/shell.nix

Build check failed
"`; diff --git a/apps/native/src/components/widget/drift/drift-discard-confirmation.tsx b/apps/native/src/components/widget/drift/drift-discard-confirmation.tsx new file mode 100644 index 000000000..0950df5ca --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-discard-confirmation.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { Check } from "lucide-react"; + +interface DriftDiscardConfirmationProps { + isManualDrift: boolean; + onCancel: () => void; + onConfirm: () => void; + total: number; +} + +export function DriftDiscardConfirmation({ + isManualDrift, + onCancel, + onConfirm, + total, +}: DriftDiscardConfirmationProps) { + return ( +
+

+ Discard all {total} {isManualDrift ? "manual " : ""} + {total === 1 ? "change" : "changes"}? This reverts to the tracked state and cannot be undone. +

+
+ + +
+
+ ); +} diff --git a/apps/native/src/components/widget/drift/drift-review-actions.tsx b/apps/native/src/components/widget/drift/drift-review-actions.tsx new file mode 100644 index 000000000..9aeea1ee6 --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-review-actions.tsx @@ -0,0 +1,175 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { ConfirmButton } from "@/components/widget/controls/confirm-button"; +import { cn } from "@/lib/utils"; +import { + ArrowLeft, + ChevronDown, + CircleAlert, + GitCommitHorizontal, + Loader2, + Sparkles, + Trash2, + Wrench, +} from "lucide-react"; + +const busyButtonSheenClassName = + "relative overflow-hidden disabled:opacity-100 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(110deg,transparent,rgba(255,255,255,0.3),transparent)] after:bg-size-[200%_100%] after:content-[''] motion-safe:after:animate-shimmer"; + +interface DriftReviewActionsProps { + buildChecking: boolean; + buildCheckFailed: boolean; + buildReady: boolean; + isApplyBusy: boolean; + isManualDrift: boolean; + onApply: () => void; + onBackToPrompt: () => void; + onRefineWithAi: () => void; + onRequestDiscard: () => void; + rebuildRunning: boolean; +} + +export function DriftReviewActions({ + buildChecking, + buildCheckFailed, + buildReady, + isApplyBusy, + isManualDrift, + onApply, + onBackToPrompt, + onRefineWithAi, + onRequestDiscard, + rebuildRunning, +}: DriftReviewActionsProps) { + const isBusy = buildChecking || isApplyBusy || rebuildRunning; + let statusMessage: string | null = null; + let buildButtonLabel = "Build & Test"; + + if (buildCheckFailed) { + statusMessage = "Build check failed"; + buildButtonLabel = "Check failed"; + } + + if (isApplyBusy) { + statusMessage = "Applying configuration"; + buildButtonLabel = "Applying…"; + } + + if (rebuildRunning) { + statusMessage = "Building and testing changes"; + buildButtonLabel = "Building…"; + } + + if (buildChecking) { + statusMessage = "Checking changes before build"; + buildButtonLabel = "Checking…"; + } + + return ( + + ); +} diff --git a/apps/native/src/components/widget/drift/drift-review-build-card.tsx b/apps/native/src/components/widget/drift/drift-review-build-card.tsx new file mode 100644 index 000000000..82f10dc78 --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-review-build-card.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { ConfirmButton } from "@/components/widget/controls/confirm-button"; +import { cn } from "@/lib/utils"; +import { Wrench, CircleCheckBig } from "lucide-react"; + +const busyButtonSheenClassName = + "relative overflow-hidden disabled:opacity-100 after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:bg-[linear-gradient(110deg,transparent,rgba(255,255,255,0.3),transparent)] after:bg-size-[200%_100%] after:content-[''] motion-safe:after:animate-shimmer"; + +interface DriftReviewBuildCardProps { + buildReady: boolean; + isApplyBusy: boolean; + rebuildRunning: boolean; + onApply: () => void; +} + +export function DriftReviewBuildCard({ + buildReady, + isApplyBusy, + rebuildRunning, + onApply, +}: DriftReviewBuildCardProps) { + const isBusy = isApplyBusy || rebuildRunning; + let buildButtonLabel = "Build & Test"; + let buildButtonTitle: string | undefined; + + if (isApplyBusy) { + buildButtonLabel = "Applying…"; + buildButtonTitle = "Applying configuration"; + } + + if (rebuildRunning) { + buildButtonLabel = "Building…"; + buildButtonTitle = "Building and testing changes"; + } + + return ( +
+
+
+
+
+

New configuration updates are available

+

+ This Mac isn’t using the latest configuration yet. Apply the updates to bring it up to + date. +

+
+
+ +
+ + +
+
+ ); +} diff --git a/apps/native/src/components/widget/drift/drift-review-content.tsx b/apps/native/src/components/widget/drift/drift-review-content.tsx new file mode 100644 index 000000000..363c0d116 --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-review-content.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { DriftFileRow } from "./drift-file-row"; +import { DriftSummaryView } from "./drift-summary-view"; +import type { DriftFileRowData } from "./drift-utils"; +import type { DriftView } from "./drift-review-types"; + +interface DriftReviewContentProps { + files: DriftFileRowData[]; + view: DriftView; +} + +export function DriftReviewContent({ files, view }: DriftReviewContentProps) { + return ( +
+ {view === "summary" ? ( + + ) : ( +
    + {files.map((file, index) => ( + + ))} +
+ )} +
+ ); +} diff --git a/apps/native/src/components/widget/drift/drift-review-header.tsx b/apps/native/src/components/widget/drift/drift-review-header.tsx new file mode 100644 index 000000000..c2b74ff36 --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-review-header.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { AnimatedTabsList, AnimatedTabsTrigger } from "@/components/ui/animated-tabs"; +import { Badge } from "@/components/ui/badge"; +import { Tabs } from "@/components/ui/tabs"; +import { formatDriftCounts, type DriftSummaryCounts } from "./drift-utils"; +import type { DriftView } from "./drift-review-types"; +import { ListTree, MessageSquareText } from "lucide-react"; + +interface DriftReviewHeaderProps { + counts: DriftSummaryCounts; + isManualDrift: boolean; + onViewChange: (view: DriftView) => void; + view: DriftView; +} + +export function DriftReviewHeader({ + counts, + isManualDrift, + onViewChange, + view, +}: DriftReviewHeaderProps) { + return ( +
+
+ + {isManualDrift ? "Detected changes" : "Proposed changes"} + + + {formatDriftCounts(counts)} + +
+ + onViewChange(value as DriftView)}> + + + + + + + +
+ ); +} diff --git a/apps/native/src/components/widget/drift/drift-review-types.ts b/apps/native/src/components/widget/drift/drift-review-types.ts new file mode 100644 index 000000000..9177d558c --- /dev/null +++ b/apps/native/src/components/widget/drift/drift-review-types.ts @@ -0,0 +1 @@ +export type DriftView = "summary" | "files"; diff --git a/apps/native/src/components/widget/drift/drift-review.stories.tsx b/apps/native/src/components/widget/drift/drift-review.stories.tsx index 6a8be8bee..e4fdd22fe 100644 --- a/apps/native/src/components/widget/drift/drift-review.stories.tsx +++ b/apps/native/src/components/widget/drift/drift-review.stories.tsx @@ -4,6 +4,7 @@ import type { Change, SemanticChangeMap } from "@/ipc/types"; import { makeGlobalPreferences } from "@/utils/test-fixtures"; import { viewModelActions } from "@nixmac/state"; import { useEffect } from "react"; +import { DriftReviewActions } from "./drift-review-actions"; import { DriftReview } from "./drift-review"; // Mock Tauri API for Storybook (buildCheck etc. resolve to a no-op). @@ -154,6 +155,8 @@ function setup({ rebuildNeeded, }, }); + // Story setup intentionally hydrates the store once per story. + // oxlint-disable-next-line react-hooks/exhaustive-deps }, []); return ( @@ -218,3 +221,26 @@ export const SingleChange = meta.story({ changeMap: { groups: [], singles: [], unsummarizedHashes: ["hash-1"] }, }), }); + +/** + * Manual drift while the pre-build check is still running: both halves of the + * build control stay disabled and the checking state remains visible. + */ +export const CheckingBuild = meta.story({ + render: () => ( +
+ {}} + onBackToPrompt={() => {}} + onRefineWithAi={() => {}} + onRequestDiscard={() => {}} + rebuildRunning={false} + /> +
+ ), +}); diff --git a/apps/native/src/components/widget/drift/drift-review.tsx b/apps/native/src/components/widget/drift/drift-review.tsx index c820770f9..5853c092e 100644 --- a/apps/native/src/components/widget/drift/drift-review.tsx +++ b/apps/native/src/components/widget/drift/drift-review.tsx @@ -1,41 +1,20 @@ "use client"; -import { AnimatedTabsList, AnimatedTabsTrigger } from "@/components/ui/animated-tabs"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; -import { Tabs } from "@/components/ui/tabs"; -import { ConfirmButton } from "@/components/widget/controls/confirm-button"; +import { DriftBanner } from "./drift-banner"; +import { DriftDiscardConfirmation } from "./drift-discard-confirmation"; +import { DriftReviewActions } from "./drift-review-actions"; +import { DriftReviewBuildCard } from "./drift-review-build-card"; +import { DriftReviewContent } from "./drift-review-content"; +import { DriftReviewHeader } from "./drift-review-header"; +import type { DriftView } from "./drift-review-types"; +import { deriveDriftFiles, summarizeDriftCounts } from "./drift-utils"; import { useApply } from "@/hooks/use-apply"; import { useEvolve } from "@/hooks/use-evolve"; import { useRollback } from "@/hooks/use-rollback"; import { uiActions, useUiState, useViewModel } from "@nixmac/state"; -import { - ArrowLeft, - Check, - ChevronDown, - GitCommitHorizontal, - ListTree, - Loader2, - MessageSquareText, - Sparkles, - Trash2, - Wrench, - CircleCheckBig, -} from "lucide-react"; import { useEffect, useMemo, useState } from "react"; -import { DriftBanner } from "./drift-banner"; -import { DriftFileRow } from "./drift-file-row"; -import { DriftSummaryView } from "./drift-summary-view"; -import { deriveDriftFiles, formatDriftCounts, summarizeDriftCounts } from "./drift-utils"; type BuildCheckStatus = "checking" | "passed" | "failed"; -type DriftView = "summary" | "files"; /** * Shared review surface for both the AI evolve step and the manual-drift step. @@ -72,7 +51,7 @@ export function DriftReview() { // Re-run the dry build check whenever the set of changes changes. const changeFingerprint = useMemo( - () => changes?.map((c) => c.hash).join(",") ?? "", + () => changes?.map((change) => change.hash).join(",") ?? "", [changes], ); @@ -104,47 +83,22 @@ export function DriftReview() { if (!gitStatus) return null; const buildReady = buildStatus === "passed" && !isApplyBusy && !rebuildRunning; + const buildChecking = isManualDrift && buildStatus === "checking"; if (isSavedBuildPending) { return ( -
-
-
-
-
-

New configuration updates are available

-

- This Mac isn’t using the latest configuration yet. Apply the updates to bring it up to date. -

-
-
- -
- - {rebuildRunning ? ( - -
-
+ ); } if (files.length === 0) return null; const total = files.length; - const buildChecking = isManualDrift && buildStatus === "checking"; return (
@@ -157,161 +111,40 @@ export function DriftReview() { /> )} -
-
- - {isManualDrift ? "Detected changes" : "Proposed changes"} - - - {formatDriftCounts(counts)} - -
- - setView(v as DriftView)}> - - - - - - - -
- -
- {view === "summary" ? ( - - ) : ( -
    - {files.map((file, index) => ( - - ))} -
- )} -
- -
- - -
- {/* AI session: refine by returning to the Describe step, which keeps - the prompt + the live conversation. (Manual drift has no - conversation — it refines via the "Refine with AI" combo item.) */} - {!isManualDrift && ( - - )} -
- - {buildChecking ? ( - - {/* The split dropdown only adopts manual drift into an AI session. - An active AI session refines via the "Refine with AI" button. */} - {isManualDrift && ( - - - - - - { - void evolveFromManual(); - }} - > - - - Refine with AI first - - Adopt these changes into an AI session - - - - - - - Commit without building - - Track as-is, skip rebuild — coming soon - - - - - - )} -
-
-
+ + + + + uiActions.setActiveStepOverride("begin")} + onRefineWithAi={() => { + void evolveFromManual(); + }} + onRequestDiscard={() => setConfirmDiscard(true)} + rebuildRunning={rebuildRunning} + /> {confirmDiscard && ( -
-

- Discard all {total} {isManualDrift ? "manual " : ""} - {total === 1 ? "change" : "changes"}? This reverts to the tracked state and cannot be - undone. -

-
- - -
-
+ setConfirmDiscard(false)} + onConfirm={() => { + setConfirmDiscard(false); + void handleRollback(); + }} + total={total} + /> )}
); diff --git a/apps/native/src/components/widget/drift/drift-utils.test.ts b/apps/native/src/components/widget/drift/drift-utils.test.ts index 3477ee3bf..ba49c055c 100644 --- a/apps/native/src/components/widget/drift/drift-utils.test.ts +++ b/apps/native/src/components/widget/drift/drift-utils.test.ts @@ -53,15 +53,19 @@ describe("deriveDriftFiles", () => { expect(byName("configuration.nix").stats).toEqual({ added: 2, removed: 1 }); }); - it("collapses multiple hunks of the same file into one row and sums stats", () => { + it("keeps multiple changes to the same file as separate rows", () => { const rows = deriveDriftFiles([ change("configuration.nix", EDITED), change("configuration.nix", ADDED), ]); - expect(rows).toHaveLength(1); - expect(rows[0].hunkCount).toBe(2); - expect(rows[0].stats).toEqual({ added: 5, removed: 1 }); + expect(rows).toHaveLength(2); + expect(rows.map((row) => row.filename)).toEqual(["configuration.nix", "configuration.nix"]); + expect(rows.map((row) => row.hunkCount)).toEqual([1, 1]); + expect(rows.map((row) => row.stats)).toEqual([ + { added: 2, removed: 1 }, + { added: 3, removed: 0 }, + ]); }); it("pairs an add + remove of the same basename into a single renamed row", () => { @@ -81,14 +85,14 @@ describe("deriveDriftFiles", () => { expect(rows[0].diffText).toBe(EDITED); }); - it("concatenates every hunk of a file into the row's diff text", () => { + it("keeps each change's diff text with its own row", () => { const rows = deriveDriftFiles([ change("configuration.nix", EDITED), change("configuration.nix", ADDED), ]); - expect(rows).toHaveLength(1); - expect(rows[0].diffText).toBe(`${EDITED}\n${ADDED}`); + expect(rows).toHaveLength(2); + expect(rows.map((row) => row.diffText)).toEqual([EDITED, ADDED]); }); it("returns nothing for an empty change set", () => { diff --git a/apps/native/src/components/widget/drift/drift-utils.ts b/apps/native/src/components/widget/drift/drift-utils.ts index 92adf5346..275574263 100644 --- a/apps/native/src/components/widget/drift/drift-utils.ts +++ b/apps/native/src/components/widget/drift/drift-utils.ts @@ -1,9 +1,4 @@ -import { - type ChangeFileSummary, - categorizeRenamed, - enrichChanges, - summarizeChangesByFile, -} from "@/components/widget/utils"; +import { categorizeRenamed, enrichChanges } from "@/components/widget/utils"; import { type DiffLineStats, countDiffLineStats, @@ -11,13 +6,17 @@ import { import type { Change, ChangeType } from "@/ipc/types"; /** - * A per-file drift row: the collapsed file summary, its summed +/- line stats, - * and the full unified diff (every hunk of the file concatenated) so the row can - * expand to reveal the diff without any further fetching. + * A per-change drift row: one independently detected hunk, its +/- line + * stats, and its unified diff. Multiple rows may legitimately have the same + * filename when a file contains separate semantic changes. */ -export type DriftFileRowData = ChangeFileSummary & { stats: DiffLineStats; diffText: string }; +export type DriftFileRowData = ReturnType[number] & { + hunkCount: number; + stats: DiffLineStats; + diffText: string; +}; -type DriftSummaryCounts = { +export type DriftSummaryCounts = { added: number; modified: number; removed: number; @@ -35,37 +34,22 @@ export const CHANGE_TYPE_GLYPH: Record(); - const diffByFile = new Map(); - for (const change of enriched) { - const prev = statsByFile.get(change.filename) ?? { added: 0, removed: 0 }; - const next = countDiffLineStats(change.diff); - statsByFile.set(change.filename, { - added: prev.added + next.added, - removed: prev.removed + next.removed, - }); - - const hunks = diffByFile.get(change.filename) ?? []; - hunks.push(change.diff); - diffByFile.set(change.filename, hunks); - } - - return summarizeChangesByFile(enriched).map((file) => ({ - ...file, - stats: statsByFile.get(file.filename) ?? { added: 0, removed: 0 }, - diffText: (diffByFile.get(file.filename) ?? [file.diff]).join("\n"), + return enriched.map((change) => ({ + ...change, + hunkCount: 1, + stats: countDiffLineStats(change.diff), + diffText: change.diff, })); } -/** Count files by edit kind. Renamed files are folded into "modified". */ +/** Count detected changes by edit kind. Renamed files are folded into "modified". */ export function summarizeDriftCounts(files: DriftFileRowData[]): DriftSummaryCounts { return files.reduce( (acc, file) => { diff --git a/apps/native/src/components/widget/summaries/summary-or-diff.tsx b/apps/native/src/components/widget/summaries/summary-or-diff.tsx index 50398205d..e3faa8499 100644 --- a/apps/native/src/components/widget/summaries/summary-or-diff.tsx +++ b/apps/native/src/components/widget/summaries/summary-or-diff.tsx @@ -55,7 +55,7 @@ export function SummaryOrDiff({ setIncludedFiles((prev) => { const next: Record = {}; for (const file of files) { - next[file.filename] = prev[file.filename] ?? true; + next[file.hash] = prev[file.hash] ?? true; } return next; }); @@ -108,11 +108,11 @@ export function SummaryOrDiff({
    {files.map((file, index) => ( - setIncludedFiles((prev) => ({ ...prev, [file.filename]: included })) + setIncludedFiles((prev) => ({ ...prev, [file.hash]: included })) } showActions={false} defaultOpen={index === 0}