@stigmer/ink 3.1.1 → 3.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/app/SessionView.d.ts.map +1 -1
  2. package/app/SessionView.js +12 -1
  3. package/app/SessionView.js.map +1 -1
  4. package/components/FileChangeProgressBar.d.ts +46 -0
  5. package/components/FileChangeProgressBar.d.ts.map +1 -0
  6. package/components/FileChangeProgressBar.js +55 -0
  7. package/components/FileChangeProgressBar.js.map +1 -0
  8. package/components/FileReviewPrompt.d.ts +43 -0
  9. package/components/FileReviewPrompt.d.ts.map +1 -0
  10. package/components/FileReviewPrompt.js +172 -0
  11. package/components/FileReviewPrompt.js.map +1 -0
  12. package/components/FileReviewRecord.d.ts +18 -0
  13. package/components/FileReviewRecord.d.ts.map +1 -0
  14. package/components/FileReviewRecord.js +32 -0
  15. package/components/FileReviewRecord.js.map +1 -0
  16. package/components/MessageThread.d.ts +19 -7
  17. package/components/MessageThread.d.ts.map +1 -1
  18. package/components/MessageThread.js +145 -77
  19. package/components/MessageThread.js.map +1 -1
  20. package/components/ToolCallGroup.d.ts.map +1 -1
  21. package/components/ToolCallGroup.js +42 -2
  22. package/components/ToolCallGroup.js.map +1 -1
  23. package/components/ToolCallItem.d.ts.map +1 -1
  24. package/components/ToolCallItem.js +16 -1
  25. package/components/ToolCallItem.js.map +1 -1
  26. package/file-review.d.ts +48 -0
  27. package/file-review.d.ts.map +1 -0
  28. package/file-review.js +113 -0
  29. package/file-review.js.map +1 -0
  30. package/index.d.ts +4 -0
  31. package/index.d.ts.map +1 -1
  32. package/index.js +5 -0
  33. package/index.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/__tests__/file-change-progress.test.tsx +295 -0
  36. package/src/__tests__/file-review.test.tsx +496 -0
  37. package/src/app/SessionView.tsx +51 -3
  38. package/src/components/FileChangeProgressBar.tsx +122 -0
  39. package/src/components/FileReviewPrompt.tsx +328 -0
  40. package/src/components/FileReviewRecord.tsx +63 -0
  41. package/src/components/MessageThread.tsx +199 -96
  42. package/src/components/ToolCallGroup.tsx +50 -1
  43. package/src/components/ToolCallItem.tsx +25 -0
  44. package/src/file-review.ts +135 -0
  45. package/src/index.ts +17 -0
package/file-review.js ADDED
@@ -0,0 +1,113 @@
1
+ import { FileChangeKind, FileDecisionAction, } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
2
+ import { deriveEffectiveVerdicts, fileReviewability } from "@stigmer/react";
3
+ /**
4
+ * Single-letter badge for a file change's kind, matching the web review card:
5
+ * `A` added, `M` modified, `D` deleted, `R` renamed. A binary change reads as
6
+ * `M` — "binary" is a review-completeness property (from `FileContent.is_binary`
7
+ * via `fileReviewability`), not a distinct edit kind.
8
+ */
9
+ export function kindLetter(kind) {
10
+ switch (kind) {
11
+ case FileChangeKind.ADD:
12
+ return "A";
13
+ case FileChangeKind.DELETE:
14
+ return "D";
15
+ case FileChangeKind.RENAME:
16
+ return "R";
17
+ default:
18
+ return "M";
19
+ }
20
+ }
21
+ /**
22
+ * The one rename-aware path-display rule for the terminal, over the raw
23
+ * before/after/kind triple shared by every file-change shape (reviewable
24
+ * `CapturedFileChange` and slim `FileChangeProgressEntry` alike). Kept private
25
+ * and fed by the typed adapters below so the two surfaces can never grow
26
+ * divergent rename formatting.
27
+ */
28
+ function displayChangePath(pathBefore, pathAfter, kind) {
29
+ if (kind === FileChangeKind.RENAME && pathBefore && pathAfter) {
30
+ return `${pathBefore} → ${pathAfter}`;
31
+ }
32
+ return pathAfter || pathBefore;
33
+ }
34
+ /**
35
+ * The path to show for a reviewable change: the post-edit path, or the pre-edit
36
+ * path for a deletion. A rename shows `before → after` so both sides are visible.
37
+ */
38
+ export function changeDisplayPath(change) {
39
+ return displayChangePath(change.pathBefore, change.pathAfter, change.kind);
40
+ }
41
+ /**
42
+ * The path to show for a mid-run progress entry (DD-32), using the same
43
+ * rename-aware rule as {@link changeDisplayPath}. The slim entry carries no
44
+ * bodies or digests, only the same before/after/kind triple, so the terminal
45
+ * renders its path identically to the reviewable surfaces.
46
+ */
47
+ export function progressEntryDisplayPath(entry) {
48
+ return displayChangePath(entry.pathBefore, entry.pathAfter, entry.kind);
49
+ }
50
+ /**
51
+ * The honest, one-line reason a change cannot be kept as-is, or `null` when it
52
+ * is fully reviewable. Mirrors the web card's per-file block-reason copy so the
53
+ * terminal reviewer sees the same explanation.
54
+ */
55
+ export function blockReasonNote(change) {
56
+ const reviewability = fileReviewability(change);
57
+ switch (reviewability.kind) {
58
+ case "reviewable":
59
+ return null;
60
+ case "binary":
61
+ return "binary — no text diff; keep as-is or discard";
62
+ case "unavailable":
63
+ switch (reviewability.reason) {
64
+ case "secret":
65
+ return "contents withheld (looks like a secret) — discard only";
66
+ case "size":
67
+ return "diff too large to display — discard only";
68
+ default:
69
+ return "diff unavailable — discard only";
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Folds a settled change set's decisions into kept / discarded / not-reviewed
75
+ * counts, using the same `deriveEffectiveVerdicts` fold the web uses so a
76
+ * "Keep all" set correctly reads every file as kept.
77
+ */
78
+ export function settledCounts(set) {
79
+ const verdicts = deriveEffectiveVerdicts(set);
80
+ let kept = 0;
81
+ let discarded = 0;
82
+ for (const change of set.changes) {
83
+ const verdict = verdicts.get(change.id);
84
+ if (verdict === FileDecisionAction.APPROVE)
85
+ kept++;
86
+ else if (verdict === FileDecisionAction.REJECT)
87
+ discarded++;
88
+ }
89
+ return {
90
+ kept,
91
+ discarded,
92
+ notReviewed: set.changes.length - kept - discarded,
93
+ };
94
+ }
95
+ /**
96
+ * A one-line summary of a settled change set's outcome, e.g. `2 kept · 1
97
+ * discarded`, or `3 files changed` when nothing was individually decided.
98
+ */
99
+ export function settledSummary(set) {
100
+ const { kept, discarded, notReviewed } = settledCounts(set);
101
+ const parts = [];
102
+ if (kept)
103
+ parts.push(`${kept} kept`);
104
+ if (discarded)
105
+ parts.push(`${discarded} discarded`);
106
+ if (notReviewed)
107
+ parts.push(`${notReviewed} not reviewed`);
108
+ if (parts.length > 0)
109
+ return parts.join(" · ");
110
+ const n = set.changes.length;
111
+ return `${n} file${n === 1 ? "" : "s"} changed`;
112
+ }
113
+ //# sourceMappingURL=file-review.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-review.js","sourceRoot":"","sources":["../src/file-review.ts"],"names":[],"mappings":"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"}
package/index.d.ts CHANGED
@@ -8,6 +8,10 @@ export { ToolCallGroup, type ToolCallGroupProps } from "./components/ToolCallGro
8
8
  export { SubAgentBlock, type SubAgentBlockProps } from "./components/SubAgentBlock.js";
9
9
  export { TodoList, type TodoListProps } from "./components/TodoList.js";
10
10
  export { ApprovalPrompt, type ApprovalPromptProps } from "./components/ApprovalPrompt.js";
11
+ export { FileReviewPrompt, type FileReviewPromptProps } from "./components/FileReviewPrompt.js";
12
+ export { FileReviewRecord, type FileReviewRecordProps } from "./components/FileReviewRecord.js";
13
+ export { FileChangeProgressBar, type FileChangeProgressBarProps, } from "./components/FileChangeProgressBar.js";
14
+ export { kindLetter, changeDisplayPath, progressEntryDisplayPath, blockReasonNote, settledCounts, settledSummary, type SettledCounts, } from "./file-review.js";
11
15
  export { ExecutionProgress, type ExecutionProgressProps } from "./components/ExecutionProgress.js";
12
16
  export { FollowUpInput, type FollowUpInputProps } from "./components/FollowUpInput.js";
13
17
  export { UsageWidget, type UsageWidgetProps } from "./components/UsageWidget.js";
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGjF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,KAAK,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGjF,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,gBAAgB,GACtB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAG/C,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAAE,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAChG,OAAO,EACL,qBAAqB,EACrB,KAAK,0BAA0B,GAChC,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,aAAa,EACb,cAAc,EACd,KAAK,aAAa,GACnB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAAE,KAAK,sBAAsB,EAAE,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGpF,OAAO,EAAE,cAAc,EAAE,KAAK,mBAAmB,EAAE,KAAK,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAGvF,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
package/index.js CHANGED
@@ -12,6 +12,11 @@ export { ToolCallGroup } from "./components/ToolCallGroup.js";
12
12
  export { SubAgentBlock } from "./components/SubAgentBlock.js";
13
13
  export { TodoList } from "./components/TodoList.js";
14
14
  export { ApprovalPrompt } from "./components/ApprovalPrompt.js";
15
+ export { FileReviewPrompt } from "./components/FileReviewPrompt.js";
16
+ export { FileReviewRecord } from "./components/FileReviewRecord.js";
17
+ export { FileChangeProgressBar, } from "./components/FileChangeProgressBar.js";
18
+ // File-review pure helpers (kind letters, paths, block reasons, settled counts)
19
+ export { kindLetter, changeDisplayPath, progressEntryDisplayPath, blockReasonNote, settledCounts, settledSummary, } from "./file-review.js";
15
20
  export { ExecutionProgress } from "./components/ExecutionProgress.js";
16
21
  export { FollowUpInput } from "./components/FollowUpInput.js";
17
22
  export { UsageWidget } from "./components/UsageWidget.js";
package/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAgC,MAAM,eAAe,CAAC;AAEjF,iDAAiD;AACjD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAE3B,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,aAAa;AACb,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AAEpF,0CAA0C;AAC1C,OAAO,EAAE,cAAc,EAA6C,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AAEvF,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAA+C,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAwB,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAgC,MAAM,eAAe,CAAC;AAEjF,iDAAiD;AACjD,OAAO,EACL,gBAAgB,EAChB,mBAAmB,GAEpB,MAAM,mBAAmB,CAAC;AAE3B,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAE/C,aAAa;AACb,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AACpF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAsB,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,cAAc,EAA4B,MAAM,gCAAgC,CAAC;AAC1F,OAAO,EAAE,gBAAgB,EAA8B,MAAM,kCAAkC,CAAC;AAChG,OAAO,EAAE,gBAAgB,EAA8B,MAAM,kCAAkC,CAAC;AAChG,OAAO,EACL,qBAAqB,GAEtB,MAAM,uCAAuC,CAAC;AAE/C,gFAAgF;AAChF,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,wBAAwB,EACxB,eAAe,EACf,aAAa,EACb,cAAc,GAEf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,iBAAiB,EAA+B,MAAM,mCAAmC,CAAC;AACnG,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AACvF,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,YAAY,EAA0B,MAAM,8BAA8B,CAAC;AAEpF,0CAA0C;AAC1C,OAAO,EAAE,cAAc,EAA6C,MAAM,gCAAgC,CAAC;AAC3G,OAAO,EAAE,WAAW,EAAyB,MAAM,6BAA6B,CAAC;AACjF,OAAO,EAAE,aAAa,EAA2B,MAAM,+BAA+B,CAAC;AAEvF,iBAAiB;AACjB,OAAO,EAAE,WAAW,EAA+C,MAAM,sBAAsB,CAAC;AAChG,OAAO,EAAE,UAAU,EAAwB,MAAM,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stigmer/ink",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "description": "Ink (React for terminals) components for rendering Stigmer agent sessions in the terminal",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@connectrpc/connect-node": "^2.1.1",
38
- "@stigmer/react": "3.1.1",
38
+ "@stigmer/react": "3.1.3",
39
39
  "ink-spinner": "^5.0.0",
40
40
  "ink-text-input": "^6.0.0",
41
41
  "marked": "^15.0.7",
@@ -43,8 +43,8 @@
43
43
  },
44
44
  "peerDependencies": {
45
45
  "@bufbuild/protobuf": "^2.0.0",
46
- "@stigmer/protos": "3.1.1",
47
- "@stigmer/sdk": "3.1.1",
46
+ "@stigmer/protos": "3.1.3",
47
+ "@stigmer/sdk": "3.1.3",
48
48
  "ink": "^7.0.0",
49
49
  "react": ">=19.0.0"
50
50
  }
@@ -0,0 +1,295 @@
1
+ import React from "react";
2
+ import { describe, it, expect } from "vitest";
3
+ import { render } from "ink-testing-library";
4
+ import { create } from "@bufbuild/protobuf";
5
+ import {
6
+ FileChangeProgressSchema,
7
+ FileChangeProgressEntrySchema,
8
+ CapturedFileChangeSchema,
9
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
10
+ import type {
11
+ FileChangeProgress,
12
+ FileChangeProgressEntry,
13
+ } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/filereview_pb";
14
+ import { FileChangeKind } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
15
+ import { FileChangeProgressBar } from "../components/FileChangeProgressBar.js";
16
+ import { changeDisplayPath, progressEntryDisplayPath } from "../file-review.js";
17
+
18
+ // --- fixtures -------------------------------------------------------------
19
+
20
+ interface EntryOpts {
21
+ readonly pathBefore?: string;
22
+ readonly pathAfter?: string;
23
+ readonly kind?: FileChangeKind;
24
+ readonly linesAdded?: number;
25
+ readonly linesRemoved?: number;
26
+ }
27
+
28
+ function makeEntry(opts: EntryOpts): FileChangeProgressEntry {
29
+ return create(FileChangeProgressEntrySchema, {
30
+ pathBefore: opts.pathBefore ?? "",
31
+ pathAfter: opts.pathAfter ?? "",
32
+ kind: opts.kind ?? FileChangeKind.MODIFY,
33
+ linesAdded: opts.linesAdded ?? 0,
34
+ linesRemoved: opts.linesRemoved ?? 0,
35
+ });
36
+ }
37
+
38
+ interface ProgressOpts {
39
+ readonly filesChanged?: number;
40
+ readonly linesAdded?: number;
41
+ readonly linesRemoved?: number;
42
+ readonly entries?: FileChangeProgressEntry[];
43
+ }
44
+
45
+ function makeProgress(opts: ProgressOpts): FileChangeProgress {
46
+ const entries = opts.entries ?? [];
47
+ return create(FileChangeProgressSchema, {
48
+ changeSetId: "cs-1",
49
+ filesChanged: opts.filesChanged ?? entries.length,
50
+ linesAdded: opts.linesAdded ?? 0,
51
+ linesRemoved: opts.linesRemoved ?? 0,
52
+ entries,
53
+ capturedAt: "2026-07-05T00:00:00Z",
54
+ });
55
+ }
56
+
57
+ // --- FileChangeProgressBar: visibility ------------------------------------
58
+
59
+ describe("FileChangeProgressBar — visibility", () => {
60
+ it("renders nothing when progress is undefined", () => {
61
+ const { lastFrame } = render(<FileChangeProgressBar progress={undefined} />);
62
+ expect((lastFrame() ?? "").trim()).toBe("");
63
+ });
64
+
65
+ it("renders nothing when no files have changed yet", () => {
66
+ const { lastFrame } = render(
67
+ <FileChangeProgressBar progress={makeProgress({ filesChanged: 0 })} />,
68
+ );
69
+ expect((lastFrame() ?? "").trim()).toBe("");
70
+ });
71
+ });
72
+
73
+ // --- FileChangeProgressBar: compact summary -------------------------------
74
+
75
+ describe("FileChangeProgressBar — compact summary", () => {
76
+ it("uses the singular noun for a single file", () => {
77
+ const { lastFrame } = render(
78
+ <FileChangeProgressBar
79
+ progress={makeProgress({
80
+ filesChanged: 1,
81
+ entries: [makeEntry({ pathAfter: "one.ts" })],
82
+ })}
83
+ />,
84
+ );
85
+ expect(lastFrame() ?? "").toContain("1 file changing");
86
+ });
87
+
88
+ it("uses the plural noun for multiple files", () => {
89
+ const { lastFrame } = render(
90
+ <FileChangeProgressBar
91
+ progress={makeProgress({
92
+ filesChanged: 3,
93
+ entries: [makeEntry({ pathAfter: "one.ts" })],
94
+ })}
95
+ />,
96
+ );
97
+ expect(lastFrame() ?? "").toContain("3 files changing");
98
+ });
99
+
100
+ it("shows the aggregate +N -M when counts exist", () => {
101
+ const { lastFrame } = render(
102
+ <FileChangeProgressBar
103
+ progress={makeProgress({
104
+ filesChanged: 2,
105
+ linesAdded: 12,
106
+ linesRemoved: 4,
107
+ entries: [makeEntry({ pathAfter: "one.ts" })],
108
+ })}
109
+ />,
110
+ );
111
+ const out = lastFrame() ?? "";
112
+ expect(out).toContain("+12");
113
+ expect(out).toContain("-4");
114
+ });
115
+
116
+ it("hides the aggregate stat when both counts are zero (never +0 -0)", () => {
117
+ const { lastFrame } = render(
118
+ <FileChangeProgressBar
119
+ progress={makeProgress({
120
+ filesChanged: 1,
121
+ linesAdded: 0,
122
+ linesRemoved: 0,
123
+ entries: [makeEntry({ pathAfter: "one.ts" })],
124
+ })}
125
+ />,
126
+ );
127
+ // No "+" anywhere means no stat rendered; the count copy carries none.
128
+ expect(lastFrame() ?? "").not.toContain("+");
129
+ });
130
+ });
131
+
132
+ // --- FileChangeProgressBar: expansion -------------------------------------
133
+
134
+ describe("FileChangeProgressBar — expansion", () => {
135
+ const progress = makeProgress({
136
+ filesChanged: 2,
137
+ linesAdded: 8,
138
+ linesRemoved: 1,
139
+ entries: [
140
+ makeEntry({ pathAfter: "src/app.ts", linesAdded: 5, linesRemoved: 1 }),
141
+ makeEntry({ pathAfter: "src/util.ts", linesAdded: 3, linesRemoved: 0 }),
142
+ ],
143
+ });
144
+
145
+ it("hides the per-file list when collapsed (default)", () => {
146
+ const { lastFrame } = render(<FileChangeProgressBar progress={progress} />);
147
+ const out = lastFrame() ?? "";
148
+ expect(out).toContain("2 files changing");
149
+ expect(out).not.toContain("src/app.ts");
150
+ expect(out).not.toContain("src/util.ts");
151
+ });
152
+
153
+ it("shows the per-file list with kind letter, path, and stats when expanded", () => {
154
+ const { lastFrame } = render(
155
+ <FileChangeProgressBar progress={progress} expanded />,
156
+ );
157
+ const out = lastFrame() ?? "";
158
+ expect(out).toContain("src/app.ts");
159
+ expect(out).toContain("src/util.ts");
160
+ expect(out).toContain("M"); // MODIFY kind letter
161
+ expect(out).toContain("+5");
162
+ expect(out).toContain("+3");
163
+ });
164
+
165
+ it("shows '… and K more' when the entry list is capped below the true total", () => {
166
+ const capped = makeProgress({
167
+ filesChanged: 5,
168
+ entries: [
169
+ makeEntry({ pathAfter: "a.ts" }),
170
+ makeEntry({ pathAfter: "b.ts" }),
171
+ ],
172
+ });
173
+ const { lastFrame } = render(
174
+ <FileChangeProgressBar progress={capped} expanded />,
175
+ );
176
+ expect(lastFrame() ?? "").toContain("and 3 more");
177
+ });
178
+
179
+ it("renders a rename entry as 'before → after'", () => {
180
+ const renamed = makeProgress({
181
+ filesChanged: 1,
182
+ entries: [
183
+ makeEntry({
184
+ pathBefore: "old/name.ts",
185
+ pathAfter: "new/name.ts",
186
+ kind: FileChangeKind.RENAME,
187
+ }),
188
+ ],
189
+ });
190
+ const { lastFrame } = render(
191
+ <FileChangeProgressBar progress={renamed} expanded />,
192
+ );
193
+ expect(lastFrame() ?? "").toContain("old/name.ts → new/name.ts");
194
+ });
195
+
196
+ it("hides per-file stats for a withheld entry (zero counts)", () => {
197
+ const secretish = makeProgress({
198
+ filesChanged: 1,
199
+ entries: [makeEntry({ pathAfter: ".env", linesAdded: 0, linesRemoved: 0 })],
200
+ });
201
+ const { lastFrame } = render(
202
+ <FileChangeProgressBar progress={secretish} expanded />,
203
+ );
204
+ const out = lastFrame() ?? "";
205
+ expect(out).toContain(".env");
206
+ expect(out).not.toContain("+");
207
+ });
208
+ });
209
+
210
+ // --- path helpers (direct unit coverage) ----------------------------------
211
+
212
+ interface ChangeOpts {
213
+ readonly pathBefore?: string;
214
+ readonly pathAfter?: string;
215
+ readonly kind: FileChangeKind;
216
+ }
217
+
218
+ function makeChange(opts: ChangeOpts) {
219
+ return create(CapturedFileChangeSchema, {
220
+ id: "c-1",
221
+ pathBefore: opts.pathBefore ?? "",
222
+ pathAfter: opts.pathAfter ?? "",
223
+ kind: opts.kind,
224
+ });
225
+ }
226
+
227
+ describe("changeDisplayPath", () => {
228
+ it("shows the after-path for an add", () => {
229
+ expect(
230
+ changeDisplayPath(makeChange({ pathAfter: "new.ts", kind: FileChangeKind.ADD })),
231
+ ).toBe("new.ts");
232
+ });
233
+
234
+ it("shows the after-path for a modify", () => {
235
+ expect(
236
+ changeDisplayPath(
237
+ makeChange({ pathAfter: "app.ts", kind: FileChangeKind.MODIFY }),
238
+ ),
239
+ ).toBe("app.ts");
240
+ });
241
+
242
+ it("falls back to the before-path for a delete", () => {
243
+ expect(
244
+ changeDisplayPath(
245
+ makeChange({ pathBefore: "gone.ts", kind: FileChangeKind.DELETE }),
246
+ ),
247
+ ).toBe("gone.ts");
248
+ });
249
+
250
+ it("shows 'before → after' for a rename", () => {
251
+ expect(
252
+ changeDisplayPath(
253
+ makeChange({
254
+ pathBefore: "old.ts",
255
+ pathAfter: "new.ts",
256
+ kind: FileChangeKind.RENAME,
257
+ }),
258
+ ),
259
+ ).toBe("old.ts → new.ts");
260
+ });
261
+ });
262
+
263
+ describe("progressEntryDisplayPath", () => {
264
+ it("shows the after-path for an add", () => {
265
+ expect(
266
+ progressEntryDisplayPath(makeEntry({ pathAfter: "new.ts", kind: FileChangeKind.ADD })),
267
+ ).toBe("new.ts");
268
+ });
269
+
270
+ it("shows the after-path for a modify", () => {
271
+ expect(
272
+ progressEntryDisplayPath(makeEntry({ pathAfter: "app.ts" })),
273
+ ).toBe("app.ts");
274
+ });
275
+
276
+ it("falls back to the before-path for a delete", () => {
277
+ expect(
278
+ progressEntryDisplayPath(
279
+ makeEntry({ pathBefore: "gone.ts", kind: FileChangeKind.DELETE }),
280
+ ),
281
+ ).toBe("gone.ts");
282
+ });
283
+
284
+ it("shows 'before → after' for a rename — identical to changeDisplayPath", () => {
285
+ expect(
286
+ progressEntryDisplayPath(
287
+ makeEntry({
288
+ pathBefore: "old.ts",
289
+ pathAfter: "new.ts",
290
+ kind: FileChangeKind.RENAME,
291
+ }),
292
+ ),
293
+ ).toBe("old.ts → new.ts");
294
+ });
295
+ });