authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-10-10 22:13:45-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-10 22:13:45-03:00
log0f36a1176921691440e4c5b790c33f439faab98c
treec4cfa43efea7050c789cf7b3bdf2baa09c3a1567
parent75c439f4ecc5431f4b1ae95ddd620245ff57bb27
signature Signed by PGP key B5690EEEBB952194

refactor: format diff line counts through one shared helper (#17948)


13 files changed, 84 insertions(+), 30 deletions(-)

apps/mobile/src/features/review/ReviewSheet.tsx+5-2
......@@ -1,4 +1,5 @@
11import type { EnvironmentId, ThreadId } from "@t3tools/contracts";
2import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
23import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
34import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
45import { ScreenHeader } from "../../components/ScreenHeader";
......@@ -282,9 +283,11 @@ const ReviewFileNavigatorRow = memo(function ReviewFileNavigatorRow(props: {
282283 </Text>
283284 <View className="mt-1 flex-row gap-2">
284285 <Text className="text-2xs font-t3-bold text-adaptive-emerald-700-300">
285 +{file.additions}
286 +{formatDiffCount(file.additions)}
287 </Text>
288 <Text className="text-2xs font-t3-bold text-adaptive-rose-700-300">
289 -{formatDiffCount(file.deletions)}
286290 </Text>
287 <Text className="text-2xs font-t3-bold text-adaptive-rose-700-300">-{file.deletions}</Text>
288291 </View>
289292 </Pressable>
290293 );
apps/mobile/src/features/review/useReviewDiffData.ts+7-3
......@@ -1,4 +1,5 @@
11import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
23
34import { countReviewCommentContexts, parseReviewInlineComments } from "./reviewCommentSelection";
45import { getCachedNativeReviewDiffData } from "./nativeReviewDiffAdapter";
......@@ -109,12 +110,15 @@ export function formatHeaderDiffSummary(
109110} {
110111 if (files) {
111112 return {
112 additions: `+${files.reduce((total, file) => total + file.additions, 0)}`,
113 deletions: `-${files.reduce((total, file) => total + file.deletions, 0)}`,
113 additions: `+${formatDiffCount(files.reduce((total, file) => total + file.additions, 0))}`,
114 deletions: `-${formatDiffCount(files.reduce((total, file) => total + file.deletions, 0))}`,
114115 };
115116 }
116117 if (parsedDiff.kind !== "files") return { additions: null, deletions: null };
117 return { additions: `+${parsedDiff.additions}`, deletions: `-${parsedDiff.deletions}` };
118 return {
119 additions: `+${formatDiffCount(parsedDiff.additions)}`,
120 deletions: `-${formatDiffCount(parsedDiff.deletions)}`,
121 };
118122}
119123
120124export function useReviewDiffData(input: {
apps/mobile/src/features/threads/git/GitCommitSheet.tsx+7-5
......@@ -1,6 +1,7 @@
11import { useNavigation, type StaticScreenProps } from "@react-navigation/native";
22import { useCallback, useState } from "react";
33import { Platform, Pressable, ScrollView, useWindowDimensions, View } from "react-native";
4import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
45import { useSafeAreaInsets } from "react-native-safe-area-context";
56
67import { AndroidSheetHeader } from "../../../components/AndroidScreenHeader";
......@@ -134,7 +135,8 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
134135 Files
135136 </Text>
136137 <Text className="text-foreground-muted text-xs leading-normal">
137 {selectedFiles.length} selected · +{selectedInsertions} / -{selectedDeletions}
138 {selectedFiles.length} selected · +{formatDiffCount(selectedInsertions)} / -
139 {formatDiffCount(selectedDeletions)}
138140 </Text>
139141 </View>
140142 <View className="flex-row items-center gap-2">
......@@ -171,10 +173,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
171173 {file.path}
172174 </Text>
173175 <Text className="text-xs font-t3-bold text-adaptive-emerald-700-300">
174 +{file.insertions}
176 +{formatDiffCount(file.insertions)}
175177 </Text>
176178 <Text className="text-xs font-t3-bold text-adaptive-rose-700-300">
177 -{file.deletions}
179 -{formatDiffCount(file.deletions)}
178180 </Text>
179181 </View>
180182 ))}
......@@ -250,10 +252,10 @@ export function GitCommitSheet(_props: GitCommitSheetProps) {
250252 </View>
251253 <View className="items-end gap-1">
252254 <Text className="text-xs font-t3-bold text-adaptive-emerald-700-300">
253 +{file.insertions}
255 +{formatDiffCount(file.insertions)}
254256 </Text>
255257 <Text className="text-xs font-t3-bold text-adaptive-rose-700-300">
256 -{file.deletions}
258 -{formatDiffCount(file.deletions)}
257259 </Text>
258260 </View>
259261 </View>
apps/mobile/src/lib/threadActivityInspector.ts+6-2
......@@ -1,5 +1,6 @@
11import type { V2ItemSupport } from "@t3tools/client-runtime/state/item-support";
22import { toolItemForDisplay } from "@t3tools/client-runtime/work-log/presentation";
3import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
34import type { ThreadId } from "@t3tools/contracts";
45import { formatDuration } from "@t3tools/shared/orchestrationTiming";
56import * as DateTime from "effect/DateTime";
......@@ -160,7 +161,7 @@ export function buildThreadActivityInspector(
160161 if (item.additions !== undefined || item.deletions !== undefined) {
161162 fields.push({
162163 label: "Changes",
163 value: `+${item.additions ?? 0} −${item.deletions ?? 0}`,
164 value: `+${formatDiffCount(item.additions ?? 0)} −${formatDiffCount(item.deletions ?? 0)}`,
164165 });
165166 }
166167 break;
......@@ -206,7 +207,10 @@ export function buildThreadActivityInspector(
206207 blocks,
207208 "Files",
208209 item.files
209 .map((file) => `${file.path} +${file.additions} −${file.deletions} ${file.kind}`)
210 .map(
211 (file) =>
212 `${file.path} +${formatDiffCount(file.additions)} −${formatDiffCount(file.deletions)} ${file.kind}`,
213 )
210214 .join("\n"),
211215 );
212216 break;
apps/web/src/components/GitActionsControl.test.ts+3-1
......@@ -80,7 +80,9 @@ vi.mock("~/state/query", () => ({
8080 isDefaultRef: false,
8181 hasPrimaryRemote: true,
8282 hasWorkingTreeChanges: true,
83 workingTree: { files: [{ path: "file.ts", status: "modified" }] },
83 workingTree: {
84 files: [{ path: "file.ts", status: "modified", insertions: 1, deletions: 0 }],
85 },
8486 },
8587 error: null,
8688 }),
apps/web/src/components/GitActionsControl.tsx+9-6
......@@ -125,6 +125,7 @@ import {
125125} from "./chat/threadDetailsPanelStyles";
126126import { getSourceControlPresentation, sourceControlIcon } from "~/sourceControlPresentation";
127127import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients";
128import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
128129import { useOpenLink } from "~/browser/useOpenLink";
129130
130131interface GitActionsControlProps {
......@@ -1910,8 +1911,10 @@ export default function GitActionsControl({
19101911 <FileDiffIcon className={THREAD_DETAILS_PANEL_ICON_CLASS} aria-hidden />
19111912 <span className={cn("flex-1 text-left", THREAD_DETAILS_PANEL_LABEL_CLASS)}>Changes</span>
19121913 <span className="flex items-center gap-1 font-mono text-2xs tabular-nums">
1913 <span className="text-success">+{changesTotals?.insertions ?? 0}</span>
1914 <span className="text-destructive">-{changesTotals?.deletions ?? 0}</span>
1914 <span className="text-success">+{formatDiffCount(changesTotals?.insertions ?? 0)}</span>
1915 <span className="text-destructive">
1916 -{formatDiffCount(changesTotals?.deletions ?? 0)}
1917 </span>
19151918 </span>
19161919 </ThreadDetailsControl>
19171920 ) : null}
......@@ -2021,11 +2024,11 @@ export default function GitActionsControl({
20212024 ) : (
20222025 <>
20232026 <span className="text-diff-addition">
2024 +{file.insertions}
2027 +{formatDiffCount(file.insertions)}
20252028 </span>
20262029 <span className="text-muted-foreground"> / </span>
20272030 <span className="text-diff-deletion">
2028 -{file.deletions}
2031 -{formatDiffCount(file.deletions)}
20292032 </span>
20302033 </>
20312034 )}
......@@ -2039,11 +2042,11 @@ export default function GitActionsControl({
20392042 </div>
20402043 <div className="flex justify-end font-mono">
20412044 <span className="text-diff-addition">
2042 +{selectedFiles.reduce((sum, f) => sum + f.insertions, 0)}
2045 +{formatDiffCount(selectedFiles.reduce((sum, f) => sum + f.insertions, 0))}
20432046 </span>
20442047 <span className="text-muted-foreground"> / </span>
20452048 <span className="text-diff-deletion">
2046 -{selectedFiles.reduce((sum, f) => sum + f.deletions, 0)}
2049 -{formatDiffCount(selectedFiles.reduce((sum, f) => sum + f.deletions, 0))}
20472050 </span>
20482051 </div>
20492052 </div>
apps/web/src/components/Sidebar.tsx+7-2
......@@ -33,6 +33,7 @@ import {
3333 threadWokeAt,
3434} from "@t3tools/client-runtime/state/thread-settled";
3535import { createInboxReturnTracker } from "@t3tools/client-runtime/state/thread-inbox";
36import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
3637import {
3738 resolveSettledThreadTimestamp,
3839 sortSettledThreads,
......@@ -2186,8 +2187,12 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: {
21862187 {prBadge}
21872188 {diff ? (
21882189 <span className="shrink-0 font-mono">
2189 <span className="text-diff-addition-foreground">+{diff.insertions}</span>{" "}
2190 <span className="text-diff-deletion-foreground">−{diff.deletions}</span>
2190 <span className="text-diff-addition-foreground">
2191 +{formatDiffCount(diff.insertions)}
2192 </span>{" "}
2193 <span className="text-diff-deletion-foreground">
2194 −{formatDiffCount(diff.deletions)}
2195 </span>
21912196 </span>
21922197 ) : null}
21932198 <span
apps/web/src/components/chat/V2ItemInspector.tsx+3-2
......@@ -11,6 +11,7 @@ import {
1111 turnItemOutputImages,
1212 turnItemOutputText,
1313} from "@t3tools/client-runtime/work-log/item-detail";
14import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
1415import { ExternalLinkIcon, GitBranchIcon, RotateCcwIcon } from "lucide-react";
1516import { memo, Suspense, use, useMemo } from "react";
1617
......@@ -261,8 +262,8 @@ export const V2ItemInspector = memo(function V2ItemInspector(props: V2ItemInspec
261262 </span>
262263 {item.additions !== undefined || item.deletions !== undefined ? (
263264 <span>
264 <span className="text-success">+{item.additions ?? 0}</span>{" "}
265 <span className="text-destructive">-{item.deletions ?? 0}</span>
265 <span className="text-success">+{formatDiffCount(item.additions ?? 0)}</span>{" "}
266 <span className="text-destructive">-{formatDiffCount(item.deletions ?? 0)}</span>
266267 </span>
267268 ) : null}
268269 {item.runId !== null ? (
apps/web/src/components/pullRequest/pullRequestPresentation.tsx+3-2
......@@ -9,6 +9,7 @@ import type {
99 PullRequestReviewDecision,
1010 PullRequestState,
1111} from "@t3tools/contracts";
12import { formatDiffCount } from "@t3tools/client-runtime/diff-count";
1213import {
1314 CircleCheckIcon,
1415 CircleDashedIcon,
......@@ -524,8 +525,8 @@ export function PullRequestDiffStat({
524525 }
525526 return (
526527 <span className={cn("inline-flex items-baseline gap-1 tabular-nums", className)}>
527 <span className="text-diff-addition-foreground">+{additions.toLocaleString()}</span>
528 <span className="text-diff-deletion">-{deletions.toLocaleString()}</span>
528 <span className="text-diff-addition-foreground">+{formatDiffCount(additions)}</span>
529 <span className="text-diff-deletion">-{formatDiffCount(deletions)}</span>
529530 </span>
530531 );
531532}
packages/client-runtime/package.json+4
......@@ -383,6 +383,10 @@
383383 "types": "./src/device/duoControl.ts",
384384 "default": "./src/device/duoControl.ts"
385385 },
386 "./diff-count": {
387 "types": "./src/diffCount.ts",
388 "default": "./src/diffCount.ts"
389 },
386390 "./mcp-apps": {
387391 "types": "./src/mcpApps/host.ts",
388392 "default": "./src/mcpApps/host.ts"
packages/client-runtime/src/diffCount.ts created+6
......@@ -0,0 +1,6 @@
1const DIFF_COUNT_FORMAT = new Intl.NumberFormat();
2
3/** Formats a diff line count with the viewer's digit grouping, so `83065` reads `83,065`. */
4export function formatDiffCount(count: number): string {
5 return DIFF_COUNT_FORMAT.format(count);
6}
patches/@pierre%2Fdiffs@1.5.2.patch+19
......@@ -970,6 +970,25 @@ index 84c7915..d1930bf 100644
970970 if (hasCustomHeader) managedOptions.renderCustomHeader = noopRender;
971971 if (hasGutterRenderer) managedOptions.renderGutterUtility = noopRender;
972972 if (hasCodeViewHeader) managedOptions.renderCodeViewHeader = noopRender;
973diff --git a/dist/utils/createFileHeaderElement.js b/dist/utils/createFileHeaderElement.js
974index 39fe30e..600442e 100644
975--- a/dist/utils/createFileHeaderElement.js
976+++ b/dist/utils/createFileHeaderElement.js
977@@ -73,12 +73,12 @@ function createMetadataElement(fileDiff) {
978 }
979 if (deletions > 0 || additions === 0) children.push(createHastElement({
980 tagName: "span",
981- children: [createTextNodeElement(`-${deletions}`)],
982+ children: [createTextNodeElement(`-${deletions.toLocaleString()}`)],
983 properties: { "data-deletions-count": "" }
984 }));
985 if (additions > 0 || deletions === 0) children.push(createHastElement({
986 tagName: "span",
987- children: [createTextNodeElement(`+${additions}`)],
988+ children: [createTextNodeElement(`+${additions.toLocaleString()}`)],
989 properties: { "data-additions-count": "" }
990 }));
991 }
973992diff --git a/package.json b/package.json
974993index d9dc7b5..3c37bae 100644
975994--- a/package.json
pnpm-lock.yaml+5-5
......@@ -151,7 +151,7 @@ patchedDependencies:
151151 '@opencode/client@2.0.23': f725d6e6d9af0d56dc6ed71b863518fbfc751af1773cff2311209d2c404cf4cd
152152 '@opencode/protocol@2.0.23': 17a82f6c2c98fd0371e786c211a4fbdaaba403577a25733e5e99a0c25a16a685
153153 '@opencode/schema@2.0.23': 650f1b8602789407c010861284272dcb6bd6de01ec6a75bf3c0904a0e7596d59
154 '@pierre/diffs@1.5.2': b33a575eb1382270ed0df956307e4b6ffe4b8e4349cefeb43e14f648c0fdc3b0
154 '@pierre/diffs@1.5.2': 298e94372dbd51846a4e26ece2ecc3e22eb1f94b0450b1f1a9c6fec127809be7
155155 '@react-native-ai/apple@0.12.0': 2d09870c2848d185cb05b53ed823a46e12dba519324d8dd8e584e28731990f9d
156156 '@react-native-menu/menu@2.0.0': a4866ecfcd44f318043de839d20110c17e14909004900f002ffe12050376f2ba
157157 '@react-navigation/native-stack@7.17.6': d2d52f3776e30765519748fd5e177592dc3b9cc778c52fb00e02674dfa9042c5
......@@ -331,7 +331,7 @@ importers:
331331 version: 1.9.1
332332 '@pierre/diffs':
333333 specifier: 'catalog:'
334 version: 1.5.2(patch_hash=b33a575eb1382270ed0df956307e4b6ffe4b8e4349cefeb43e14f648c0fdc3b0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
334 version: 1.5.2(patch_hash=298e94372dbd51846a4e26ece2ecc3e22eb1f94b0450b1f1a9c6fec127809be7)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)
335335 '@react-native-ai/apple':
336336 specifier: 0.12.0
337337 version: 0.12.0(patch_hash=2d09870c2848d185cb05b53ed823a46e12dba519324d8dd8e584e28731990f9d)(react-native@0.88.0-rc.3(@babel/core@7.29.7)(@react-native/metro-config@0.88.0-rc.3(@babel/core@7.29.7)(bufferutil@4.1.0)(utf-8-validate@6.0.6))(@types/react@19.3.0)(bufferutil@4.1.0)(react@19.3.0)(utf-8-validate@6.0.6))
......@@ -783,7 +783,7 @@ importers:
783783 version: 1.8.0
784784 '@pierre/diffs':
785785 specifier: 'catalog:'
786 version: 1.5.2(patch_hash=b33a575eb1382270ed0df956307e4b6ffe4b8e4349cefeb43e14f648c0fdc3b0)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
786 version: 1.5.2(patch_hash=298e94372dbd51846a4e26ece2ecc3e22eb1f94b0450b1f1a9c6fec127809be7)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
787787 '@pierre/trees':
788788 specifier: 1.0.0-beta.6
789789 version: 1.0.0-beta.6(@pierre/theme@1.1.0)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(shiki@4.2.0)
......@@ -15726,7 +15726,7 @@ snapshots:
1572615726 tslib: 2.8.1
1572715727 webcrypto-core: 1.9.2
1572815728
15729 '@pierre/diffs@1.5.2(patch_hash=b33a575eb1382270ed0df956307e4b6ffe4b8e4349cefeb43e14f648c0fdc3b0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
15729 '@pierre/diffs@1.5.2(patch_hash=298e94372dbd51846a4e26ece2ecc3e22eb1f94b0450b1f1a9c6fec127809be7)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)':
1573015730 dependencies:
1573115731 '@pierre/theme': 2.0.0
1573215732 '@pierre/theming': 1.0.1(@pierre/theme@2.0.0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(shiki@4.2.0)
......@@ -15741,7 +15741,7 @@ snapshots:
1574115741 transitivePeerDependencies:
1574215742 - '@shikijs/themes'
1574315743
15744 '@pierre/diffs@1.5.2(patch_hash=b33a575eb1382270ed0df956307e4b6ffe4b8e4349cefeb43e14f648c0fdc3b0)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
15744 '@pierre/diffs@1.5.2(patch_hash=298e94372dbd51846a4e26ece2ecc3e22eb1f94b0450b1f1a9c6fec127809be7)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
1574515745 dependencies:
1574615746 '@pierre/theme': 2.0.0
1574715747 '@pierre/theming': 1.0.1(@pierre/theme@2.0.0)(@shikijs/themes@4.3.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)(shiki@4.2.0)