@stigmer/react 3.0.9-dev.20260616060535 → 3.1.0
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.
- package/execution/ApprovalCard.d.ts +47 -3
- package/execution/ApprovalCard.d.ts.map +1 -1
- package/execution/ApprovalCard.js +138 -70
- package/execution/ApprovalCard.js.map +1 -1
- package/execution/ApprovalContext.d.ts +72 -0
- package/execution/ApprovalContext.d.ts.map +1 -0
- package/execution/ApprovalContext.js +41 -0
- package/execution/ApprovalContext.js.map +1 -0
- package/execution/ArtifactCard.d.ts.map +1 -1
- package/execution/ArtifactCard.js +3 -1
- package/execution/ArtifactCard.js.map +1 -1
- package/execution/ArtifactPreviewModal.d.ts.map +1 -1
- package/execution/ArtifactPreviewModal.js +5 -3
- package/execution/ArtifactPreviewModal.js.map +1 -1
- package/execution/EmptyChangeNotice.d.ts +38 -0
- package/execution/EmptyChangeNotice.d.ts.map +1 -0
- package/execution/EmptyChangeNotice.js +27 -0
- package/execution/EmptyChangeNotice.js.map +1 -0
- package/execution/FileChangesView.d.ts +81 -0
- package/execution/FileChangesView.d.ts.map +1 -0
- package/execution/FileChangesView.js +164 -0
- package/execution/FileChangesView.js.map +1 -0
- package/execution/FilePathLink.d.ts +24 -4
- package/execution/FilePathLink.d.ts.map +1 -1
- package/execution/FilePathLink.js +31 -9
- package/execution/FilePathLink.js.map +1 -1
- package/execution/FileReviewCard.d.ts +111 -0
- package/execution/FileReviewCard.d.ts.map +1 -0
- package/execution/FileReviewCard.js +558 -0
- package/execution/FileReviewCard.js.map +1 -0
- package/execution/FileReviewContext.d.ts +35 -0
- package/execution/FileReviewContext.d.ts.map +1 -0
- package/execution/FileReviewContext.js +23 -0
- package/execution/FileReviewContext.js.map +1 -0
- package/execution/FileReviewDock.d.ts +73 -0
- package/execution/FileReviewDock.d.ts.map +1 -0
- package/execution/FileReviewDock.js +65 -0
- package/execution/FileReviewDock.js.map +1 -0
- package/execution/McpToolDetail.d.ts.map +1 -1
- package/execution/McpToolDetail.js +38 -5
- package/execution/McpToolDetail.js.map +1 -1
- package/execution/MessageThread.d.ts +42 -3
- package/execution/MessageThread.d.ts.map +1 -1
- package/execution/MessageThread.js +263 -18
- package/execution/MessageThread.js.map +1 -1
- package/execution/PlanArtifactCard.d.ts.map +1 -1
- package/execution/PlanArtifactCard.js +3 -1
- package/execution/PlanArtifactCard.js.map +1 -1
- package/execution/PlanCompletionCard.d.ts +1 -1
- package/execution/PlanCompletionCard.js +1 -1
- package/execution/ResultView.d.ts +19 -1
- package/execution/ResultView.d.ts.map +1 -1
- package/execution/ResultView.js +141 -40
- package/execution/ResultView.js.map +1 -1
- package/execution/SubAgentSection.d.ts.map +1 -1
- package/execution/SubAgentSection.js +45 -6
- package/execution/SubAgentSection.js.map +1 -1
- package/execution/TerminalSession.d.ts +34 -0
- package/execution/TerminalSession.d.ts.map +1 -0
- package/execution/TerminalSession.js +34 -0
- package/execution/TerminalSession.js.map +1 -0
- package/execution/TodoCard.d.ts +53 -0
- package/execution/TodoCard.d.ts.map +1 -0
- package/execution/TodoCard.js +77 -0
- package/execution/TodoCard.js.map +1 -0
- package/execution/ToolArgsView.d.ts +7 -1
- package/execution/ToolArgsView.d.ts.map +1 -1
- package/execution/ToolArgsView.js +10 -9
- package/execution/ToolArgsView.js.map +1 -1
- package/execution/ToolCallDetail.d.ts +10 -1
- package/execution/ToolCallDetail.d.ts.map +1 -1
- package/execution/ToolCallDetail.js +66 -19
- package/execution/ToolCallDetail.js.map +1 -1
- package/execution/ToolCallGroup.d.ts +28 -18
- package/execution/ToolCallGroup.d.ts.map +1 -1
- package/execution/ToolCallGroup.js +39 -148
- package/execution/ToolCallGroup.js.map +1 -1
- package/execution/ToolCallItem.d.ts +36 -8
- package/execution/ToolCallItem.d.ts.map +1 -1
- package/execution/ToolCallItem.js +140 -21
- package/execution/ToolCallItem.js.map +1 -1
- package/execution/ToolRunGroup.d.ts +48 -0
- package/execution/ToolRunGroup.d.ts.map +1 -0
- package/execution/ToolRunGroup.js +168 -0
- package/execution/ToolRunGroup.js.map +1 -0
- package/execution/approval-provenance.d.ts +2 -0
- package/execution/approval-provenance.d.ts.map +1 -0
- package/execution/approval-provenance.js +6 -0
- package/execution/approval-provenance.js.map +1 -0
- package/execution/artifact-utils.d.ts +0 -10
- package/execution/artifact-utils.d.ts.map +1 -1
- package/execution/artifact-utils.js +0 -17
- package/execution/artifact-utils.js.map +1 -1
- package/execution/execution-phases.d.ts +1 -6
- package/execution/execution-phases.d.ts.map +1 -1
- package/execution/execution-phases.js +4 -14
- package/execution/execution-phases.js.map +1 -1
- package/execution/file-path-resolver.d.ts +28 -0
- package/execution/file-path-resolver.d.ts.map +1 -1
- package/execution/file-path-resolver.js +24 -0
- package/execution/file-path-resolver.js.map +1 -1
- package/execution/file-review-status.d.ts +127 -0
- package/execution/file-review-status.d.ts.map +1 -0
- package/execution/file-review-status.js +135 -0
- package/execution/file-review-status.js.map +1 -0
- package/execution/index.d.ts +39 -5
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js +23 -3
- package/execution/index.js.map +1 -1
- package/execution/segment-tool-calls.d.ts +46 -0
- package/execution/segment-tool-calls.d.ts.map +1 -0
- package/execution/segment-tool-calls.js +71 -0
- package/execution/segment-tool-calls.js.map +1 -0
- package/execution/tool-categories.d.ts +62 -1
- package/execution/tool-categories.d.ts.map +1 -1
- package/execution/tool-categories.js +96 -2
- package/execution/tool-categories.js.map +1 -1
- package/execution/tool-presenter.d.ts +38 -2
- package/execution/tool-presenter.d.ts.map +1 -1
- package/execution/tool-presenter.js +23 -1
- package/execution/tool-presenter.js.map +1 -1
- package/execution/tool-rendering-primitives.d.ts.map +1 -1
- package/execution/tool-rendering-primitives.js +3 -2
- package/execution/tool-rendering-primitives.js.map +1 -1
- package/execution/useArtifactDownload.d.ts +44 -0
- package/execution/useArtifactDownload.d.ts.map +1 -0
- package/execution/useArtifactDownload.js +80 -0
- package/execution/useArtifactDownload.js.map +1 -0
- package/execution/useArtifactDownloadUrl.d.ts +67 -0
- package/execution/useArtifactDownloadUrl.d.ts.map +1 -0
- package/execution/useArtifactDownloadUrl.js +59 -0
- package/execution/useArtifactDownloadUrl.js.map +1 -0
- package/execution/useFileChangeContent.d.ts +61 -0
- package/execution/useFileChangeContent.d.ts.map +1 -0
- package/execution/useFileChangeContent.js +89 -0
- package/execution/useFileChangeContent.js.map +1 -0
- package/execution/useFileReview.d.ts +89 -0
- package/execution/useFileReview.d.ts.map +1 -0
- package/execution/useFileReview.js +106 -0
- package/execution/useFileReview.js.map +1 -0
- package/execution/useSubmitApproval.d.ts +17 -2
- package/execution/useSubmitApproval.d.ts.map +1 -1
- package/execution/useSubmitApproval.js +27 -23
- package/execution/useSubmitApproval.js.map +1 -1
- package/execution/useToolOutputContent.d.ts +44 -0
- package/execution/useToolOutputContent.d.ts.map +1 -0
- package/execution/useToolOutputContent.js +31 -0
- package/execution/useToolOutputContent.js.map +1 -0
- package/index.d.ts +6 -6
- package/index.d.ts.map +1 -1
- package/index.js +3 -3
- package/index.js.map +1 -1
- package/internal/ApprovalPeekBar.d.ts +23 -0
- package/internal/ApprovalPeekBar.d.ts.map +1 -0
- package/internal/ApprovalPeekBar.js +25 -0
- package/internal/ApprovalPeekBar.js.map +1 -0
- package/internal/BoundedContent.d.ts +49 -0
- package/internal/BoundedContent.d.ts.map +1 -0
- package/internal/BoundedContent.js +50 -0
- package/internal/BoundedContent.js.map +1 -0
- package/internal/DecisionButton.d.ts +47 -0
- package/internal/DecisionButton.d.ts.map +1 -0
- package/internal/DecisionButton.js +42 -0
- package/internal/DecisionButton.js.map +1 -0
- package/internal/InCardDecisionError.d.ts +31 -0
- package/internal/InCardDecisionError.d.ts.map +1 -0
- package/internal/InCardDecisionError.js +23 -0
- package/internal/InCardDecisionError.js.map +1 -0
- package/internal/RevealToggle.d.ts +36 -0
- package/internal/RevealToggle.d.ts.map +1 -0
- package/internal/RevealToggle.js +32 -0
- package/internal/RevealToggle.js.map +1 -0
- package/internal/VirtualizedThread.d.ts +7 -1
- package/internal/VirtualizedThread.d.ts.map +1 -1
- package/internal/VirtualizedThread.js +7 -3
- package/internal/VirtualizedThread.js.map +1 -1
- package/internal/useAutoDisclosure.d.ts +34 -0
- package/internal/useAutoDisclosure.d.ts.map +1 -0
- package/internal/useAutoDisclosure.js +53 -0
- package/internal/useAutoDisclosure.js.map +1 -0
- package/internal/useElapsedSince.d.ts +30 -0
- package/internal/useElapsedSince.d.ts.map +1 -0
- package/internal/useElapsedSince.js +70 -0
- package/internal/useElapsedSince.js.map +1 -0
- package/internal/useIsOverflowing.d.ts +30 -0
- package/internal/useIsOverflowing.d.ts.map +1 -0
- package/internal/useIsOverflowing.js +47 -0
- package/internal/useIsOverflowing.js.map +1 -0
- package/internal/useIsTextTruncated.d.ts +24 -0
- package/internal/useIsTextTruncated.d.ts.map +1 -0
- package/internal/useIsTextTruncated.js +41 -0
- package/internal/useIsTextTruncated.js.map +1 -0
- package/internal/useKeyedSubmission.d.ts +49 -0
- package/internal/useKeyedSubmission.d.ts.map +1 -0
- package/internal/useKeyedSubmission.js +67 -0
- package/internal/useKeyedSubmission.js.map +1 -0
- package/package.json +4 -4
- package/session/SessionViewer.d.ts +2 -2
- package/session/SessionViewer.d.ts.map +1 -1
- package/session/SessionViewer.js +10 -4
- package/session/SessionViewer.js.map +1 -1
- package/session/index.d.ts +2 -0
- package/session/index.d.ts.map +1 -1
- package/session/index.js +1 -0
- package/session/index.js.map +1 -1
- package/session/inspector/ChangesTab.d.ts +8 -5
- package/session/inspector/ChangesTab.d.ts.map +1 -1
- package/session/inspector/ChangesTab.js +11 -8
- package/session/inspector/ChangesTab.js.map +1 -1
- package/session/inspector/SessionInspector.d.ts +4 -4
- package/session/inspector/SessionInspector.d.ts.map +1 -1
- package/session/inspector/SessionInspector.js +4 -4
- package/session/inspector/SessionInspector.js.map +1 -1
- package/session/inspector/index.d.ts +0 -2
- package/session/inspector/index.d.ts.map +1 -1
- package/session/inspector/index.js +0 -1
- package/session/inspector/index.js.map +1 -1
- package/session/inspector/useSessionInspector.d.ts +8 -4
- package/session/inspector/useSessionInspector.d.ts.map +1 -1
- package/session/inspector/useSessionInspector.js +27 -20
- package/session/inspector/useSessionInspector.js.map +1 -1
- package/session/useSessionConversation.d.ts +37 -3
- package/session/useSessionConversation.d.ts.map +1 -1
- package/session/useSessionConversation.js +18 -2
- package/session/useSessionConversation.js.map +1 -1
- package/session/useSessionFileChanges.d.ts +74 -0
- package/session/useSessionFileChanges.d.ts.map +1 -0
- package/session/useSessionFileChanges.js +178 -0
- package/session/useSessionFileChanges.js.map +1 -0
- package/src/__tests__/styles-border-layer-invariant.test.ts +105 -0
- package/src/execution/ApprovalCard.tsx +368 -230
- package/src/execution/ApprovalContext.ts +109 -0
- package/src/execution/ArtifactCard.tsx +10 -6
- package/src/execution/ArtifactPreviewModal.tsx +12 -6
- package/src/execution/EmptyChangeNotice.tsx +62 -0
- package/src/execution/FileChangesView.tsx +383 -0
- package/src/execution/FilePathLink.tsx +65 -12
- package/src/execution/FileReviewCard.tsx +1118 -0
- package/src/execution/FileReviewContext.ts +53 -0
- package/src/execution/FileReviewDock.tsx +157 -0
- package/src/execution/McpToolDetail.tsx +98 -32
- package/src/execution/MessageThread.tsx +374 -9
- package/src/execution/PlanArtifactCard.tsx +9 -6
- package/src/execution/PlanCompletionCard.tsx +1 -1
- package/src/execution/ResultView.tsx +333 -162
- package/src/execution/SubAgentSection.tsx +58 -11
- package/src/execution/TerminalSession.tsx +91 -0
- package/src/execution/TodoCard.tsx +186 -0
- package/src/execution/ToolArgsView.tsx +28 -17
- package/src/execution/ToolCallDetail.tsx +137 -26
- package/src/execution/ToolCallGroup.tsx +68 -296
- package/src/execution/ToolCallItem.tsx +265 -67
- package/src/execution/ToolRunGroup.tsx +302 -0
- package/src/execution/__tests__/ApprovalCard.test.tsx +395 -5
- package/src/execution/__tests__/ArtifactPreviewModal.test.tsx +0 -1
- package/src/execution/__tests__/EmptyChangeNotice.test.tsx +24 -0
- package/src/execution/__tests__/FileChangesView.test.tsx +296 -0
- package/src/execution/__tests__/FilePathLink.test.tsx +92 -0
- package/src/execution/__tests__/FileReviewCard.test.tsx +1044 -0
- package/src/execution/__tests__/FileReviewDock.test.tsx +134 -0
- package/src/execution/__tests__/McpToolDetail.test.tsx +79 -0
- package/src/execution/__tests__/MessageThread.test.tsx +324 -0
- package/src/execution/__tests__/PlanArtifactCard.test.tsx +37 -16
- package/src/execution/__tests__/ResultView.test.tsx +280 -2
- package/src/execution/__tests__/SubAgentSection.test.tsx +180 -0
- package/src/execution/__tests__/TerminalSession.test.tsx +72 -0
- package/src/execution/__tests__/TodoCard.test.tsx +102 -0
- package/src/execution/__tests__/ToolArgsView.test.tsx +48 -0
- package/src/execution/__tests__/ToolCallDetail.test.tsx +195 -0
- package/src/execution/__tests__/ToolCallGroup.test.tsx +132 -0
- package/src/execution/__tests__/ToolCallItem.test.tsx +521 -0
- package/src/execution/__tests__/ToolRunGroup.test.tsx +137 -0
- package/src/execution/__tests__/blanked-narration.test.tsx +149 -0
- package/src/execution/__tests__/buildThreadItems-approvals.test.ts +304 -0
- package/src/execution/__tests__/buildThreadItems-file-review.test.ts +331 -0
- package/src/execution/__tests__/file-path-resolver.test.ts +44 -0
- package/src/execution/__tests__/file-review-status.test.ts +343 -0
- package/src/execution/__tests__/segment-tool-calls.test.ts +124 -0
- package/src/execution/__tests__/subagent-file-review-badge.test.tsx +273 -0
- package/src/execution/__tests__/thread-keys.test.ts +195 -0
- package/src/execution/__tests__/tool-presenter.test.tsx +95 -0
- package/src/execution/__tests__/useArtifactDownload.test.tsx +67 -0
- package/src/execution/__tests__/useArtifactDownloadUrl.test.tsx +51 -0
- package/src/execution/__tests__/useFileChangeContent.test.tsx +240 -0
- package/src/execution/__tests__/useFileReview.test.tsx +236 -0
- package/src/execution/__tests__/useSubmitApproval.test.tsx +165 -0
- package/src/execution/__tests__/useToolOutputContent.test.tsx +82 -0
- package/src/execution/approval-provenance.ts +8 -0
- package/src/execution/artifact-utils.ts +0 -18
- package/src/execution/execution-phases.ts +4 -16
- package/src/execution/file-path-resolver.ts +38 -0
- package/src/execution/file-review-status.ts +219 -0
- package/src/execution/index.ts +84 -4
- package/src/execution/segment-tool-calls.ts +101 -0
- package/src/execution/tool-categories.ts +128 -1
- package/src/execution/tool-presenter.ts +58 -3
- package/src/execution/tool-rendering-primitives.tsx +11 -14
- package/src/execution/useArtifactDownload.ts +109 -0
- package/src/execution/useArtifactDownloadUrl.ts +116 -0
- package/src/execution/useFileChangeContent.ts +149 -0
- package/src/execution/useFileReview.ts +197 -0
- package/src/execution/useSubmitApproval.ts +49 -27
- package/src/execution/useToolOutputContent.ts +70 -0
- package/src/index.ts +60 -1
- package/src/internal/ApprovalPeekBar.tsx +73 -0
- package/src/internal/BoundedContent.tsx +97 -0
- package/src/internal/DecisionButton.tsx +127 -0
- package/src/internal/InCardDecisionError.tsx +47 -0
- package/src/internal/RevealToggle.tsx +91 -0
- package/src/internal/VirtualizedThread.tsx +28 -2
- package/src/internal/__tests__/ApprovalPeekBar.test.tsx +34 -0
- package/src/internal/__tests__/BoundedContent.test.tsx +92 -0
- package/src/internal/__tests__/DecisionButton.test.tsx +107 -0
- package/src/internal/__tests__/RevealToggle.test.tsx +61 -0
- package/src/internal/__tests__/useAutoDisclosure.test.ts +74 -0
- package/src/internal/__tests__/useElapsedSince.test.tsx +68 -0
- package/src/internal/__tests__/useIsOverflowing.test.ts +36 -0
- package/src/internal/__tests__/useKeyedSubmission.test.tsx +126 -0
- package/src/internal/useAutoDisclosure.ts +58 -0
- package/src/internal/useElapsedSince.ts +72 -0
- package/src/internal/useIsOverflowing.ts +61 -0
- package/src/internal/useIsTextTruncated.ts +55 -0
- package/src/internal/useKeyedSubmission.ts +101 -0
- package/src/session/SessionViewer.tsx +21 -4
- package/src/session/__tests__/SessionViewer-fileReviewDock.test.tsx +198 -0
- package/src/session/__tests__/audienceWiring.test.tsx +4 -0
- package/src/session/__tests__/useSessionFileChanges.test.ts +303 -0
- package/src/session/index.ts +3 -0
- package/src/session/inspector/ChangesTab.tsx +20 -17
- package/src/session/inspector/SessionInspector.tsx +12 -10
- package/src/session/inspector/__tests__/ArtifactsTab.test.tsx +0 -1
- package/src/session/inspector/__tests__/ChangesTab.test.tsx +38 -0
- package/src/session/inspector/__tests__/useSessionInspector.test.ts +46 -61
- package/src/session/inspector/index.ts +0 -3
- package/src/session/inspector/useSessionInspector.ts +27 -20
- package/src/session/useSessionConversation.ts +80 -3
- package/src/session/useSessionFileChanges.ts +207 -0
- package/src/styles.css +35 -10
- package/src/version-history/DiffViewer.tsx +10 -7
- package/src/version-history/UnifiedDiffView.tsx +66 -0
- package/src/version-history/__tests__/DiffViewer.test.tsx +57 -0
- package/src/version-history/__tests__/UnifiedDiffView.test.tsx +50 -0
- package/src/version-history/__tests__/computeDiff.test.ts +35 -0
- package/src/version-history/__tests__/parseUnifiedDiff.test.ts +82 -0
- package/src/version-history/computeDiff.ts +6 -37
- package/src/version-history/diff-hunks.ts +63 -0
- package/src/version-history/index.ts +1 -0
- package/src/version-history/parseUnifiedDiff.ts +34 -0
- package/src/workflow/WorkflowExecutionApprovalCard.tsx +49 -24
- package/src/workflow/WorkflowExecutionTimeline.tsx +15 -4
- package/src/workflow/WorkflowExecutionTimelineEvent.tsx +8 -3
- package/src/workflow/WorkflowExecutionViewer.tsx +72 -28
- package/src/workflow/WorkflowFileReviewList.tsx +152 -0
- package/src/workflow/WorkflowTaskApprovalCard.tsx +37 -85
- package/src/workflow/__tests__/ExecutionInspector.test.tsx +30 -2
- package/src/workflow/__tests__/WorkflowExecutionApprovalCard.test.tsx +129 -0
- package/src/workflow/__tests__/WorkflowFileReviewList.test.tsx +129 -0
- package/src/workflow/__tests__/WorkflowTaskApprovalCard.test.tsx +43 -11
- package/src/workflow/__tests__/useWorkflowExecutionActions.test.tsx +276 -0
- package/src/workflow/execution-inspector/ExecutionInspector.tsx +42 -18
- package/src/workflow/index.ts +5 -0
- package/src/workflow/useWorkflowExecutionActions.ts +177 -29
- package/styles.css +1 -1
- package/version-history/DiffViewer.d.ts.map +1 -1
- package/version-history/DiffViewer.js +9 -3
- package/version-history/DiffViewer.js.map +1 -1
- package/version-history/UnifiedDiffView.d.ts +29 -0
- package/version-history/UnifiedDiffView.d.ts.map +1 -0
- package/version-history/UnifiedDiffView.js +36 -0
- package/version-history/UnifiedDiffView.js.map +1 -0
- package/version-history/computeDiff.d.ts +3 -1
- package/version-history/computeDiff.d.ts.map +1 -1
- package/version-history/computeDiff.js +5 -35
- package/version-history/computeDiff.js.map +1 -1
- package/version-history/diff-hunks.d.ts +21 -0
- package/version-history/diff-hunks.d.ts.map +1 -0
- package/version-history/diff-hunks.js +58 -0
- package/version-history/diff-hunks.js.map +1 -0
- package/version-history/index.d.ts +1 -0
- package/version-history/index.d.ts.map +1 -1
- package/version-history/index.js +1 -0
- package/version-history/index.js.map +1 -1
- package/version-history/parseUnifiedDiff.d.ts +22 -0
- package/version-history/parseUnifiedDiff.d.ts.map +1 -0
- package/version-history/parseUnifiedDiff.js +34 -0
- package/version-history/parseUnifiedDiff.js.map +1 -0
- package/workflow/WorkflowExecutionApprovalCard.d.ts +8 -1
- package/workflow/WorkflowExecutionApprovalCard.d.ts.map +1 -1
- package/workflow/WorkflowExecutionApprovalCard.js +17 -3
- package/workflow/WorkflowExecutionApprovalCard.js.map +1 -1
- package/workflow/WorkflowExecutionTimeline.d.ts +11 -2
- package/workflow/WorkflowExecutionTimeline.d.ts.map +1 -1
- package/workflow/WorkflowExecutionTimeline.js +2 -2
- package/workflow/WorkflowExecutionTimeline.js.map +1 -1
- package/workflow/WorkflowExecutionTimelineEvent.d.ts +4 -1
- package/workflow/WorkflowExecutionTimelineEvent.d.ts.map +1 -1
- package/workflow/WorkflowExecutionTimelineEvent.js +2 -2
- package/workflow/WorkflowExecutionTimelineEvent.js.map +1 -1
- package/workflow/WorkflowExecutionViewer.d.ts.map +1 -1
- package/workflow/WorkflowExecutionViewer.js +18 -10
- package/workflow/WorkflowExecutionViewer.js.map +1 -1
- package/workflow/WorkflowFileReviewList.d.ts +46 -0
- package/workflow/WorkflowFileReviewList.d.ts.map +1 -0
- package/workflow/WorkflowFileReviewList.js +48 -0
- package/workflow/WorkflowFileReviewList.js.map +1 -0
- package/workflow/WorkflowTaskApprovalCard.d.ts +8 -1
- package/workflow/WorkflowTaskApprovalCard.d.ts.map +1 -1
- package/workflow/WorkflowTaskApprovalCard.js +12 -19
- package/workflow/WorkflowTaskApprovalCard.js.map +1 -1
- package/workflow/execution-inspector/ExecutionInspector.d.ts +21 -4
- package/workflow/execution-inspector/ExecutionInspector.d.ts.map +1 -1
- package/workflow/execution-inspector/ExecutionInspector.js +6 -3
- package/workflow/execution-inspector/ExecutionInspector.js.map +1 -1
- package/workflow/index.d.ts +1 -0
- package/workflow/index.d.ts.map +1 -1
- package/workflow/index.js +1 -0
- package/workflow/index.js.map +1 -1
- package/workflow/useWorkflowExecutionActions.d.ts +62 -4
- package/workflow/useWorkflowExecutionActions.d.ts.map +1 -1
- package/workflow/useWorkflowExecutionActions.js +94 -18
- package/workflow/useWorkflowExecutionActions.js.map +1 -1
- package/session/inspector/PlanTab.d.ts +0 -15
- package/session/inspector/PlanTab.d.ts.map +0 -1
- package/session/inspector/PlanTab.js +0 -19
- package/session/inspector/PlanTab.js.map +0 -1
- package/src/session/inspector/PlanTab.tsx +0 -31
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { create } from "@bufbuild/protobuf";
|
|
5
|
+
import type { AgentExecution } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/api_pb";
|
|
6
|
+
import type { FileChange } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
7
|
+
import { FileChangeSchema } from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/message_pb";
|
|
8
|
+
import {
|
|
9
|
+
FileChangeCaptureLevel,
|
|
10
|
+
FileChangeType,
|
|
11
|
+
} from "@stigmer/protos/ai/stigmer/agentic/agentexecution/v1/enum_pb";
|
|
12
|
+
import { displayFileChangeSets, toDisplayFileChange } from "@stigmer/sdk";
|
|
13
|
+
|
|
14
|
+
/** Return value of {@link useSessionFileChanges}. */
|
|
15
|
+
export interface UseSessionFileChangesReturn {
|
|
16
|
+
/**
|
|
17
|
+
* One {@link FileChange} per file touched in the session — the *net*
|
|
18
|
+
* change for that path (see the net-diff note below) — ordered for a
|
|
19
|
+
* file-list (modified, then created/renamed, then deleted; alphabetical
|
|
20
|
+
* within each group).
|
|
21
|
+
*/
|
|
22
|
+
readonly fileChanges: readonly FileChange[];
|
|
23
|
+
/** `true` when at least one file was changed across all executions. */
|
|
24
|
+
readonly hasFileChanges: boolean;
|
|
25
|
+
/** Number of distinct files changed. */
|
|
26
|
+
readonly fileChangeCount: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Pure derivation hook that aggregates an agent's file changes across every
|
|
31
|
+
* execution in a session into one net change per file. A headless building
|
|
32
|
+
* block for platform builders composing a consolidated session-changes surface
|
|
33
|
+
* (typically paired with {@link FileChangesView}); the Console itself renders
|
|
34
|
+
* file changes in the transcript (stamped edit rows + the per-turn decision
|
|
35
|
+
* bar) and does not consume this hook.
|
|
36
|
+
*
|
|
37
|
+
* **Source (ledger-first).** Under apply-then-review the changes live in the
|
|
38
|
+
* file-review ledger; {@link displayFileChangeSets} reads the server's live
|
|
39
|
+
* projection or folds the durable ledger for a terminal execution (whose
|
|
40
|
+
* projection is nil), and each captured change is projected onto the display
|
|
41
|
+
* {@link FileChange} via {@link toDisplayFileChange}. A pre-capture
|
|
42
|
+
* (legacy) execution has no ledger, so it falls back to the tool-call-coupled
|
|
43
|
+
* `ToolCall.file_changes` (#186; removed in a later slice with that field). The
|
|
44
|
+
* two sources are mutually exclusive per execution, so there is no double count.
|
|
45
|
+
*
|
|
46
|
+
* **Traversal.** The ledger is execution-scoped and already includes sub-agent
|
|
47
|
+
* captures (DD-19), so the primary path is one pass per execution. The legacy
|
|
48
|
+
* fallback walks the same surfaces the server-side approval recompute and
|
|
49
|
+
* `MessageThread` walk: `status.messages[].toolCalls` **and**
|
|
50
|
+
* `status.subAgentExecutions[].messages[].toolCalls`. Executions are visited in
|
|
51
|
+
* chronological order, so each path's change list is chronological.
|
|
52
|
+
*
|
|
53
|
+
* **Net-diff (not latest-wins).** `useSessionWriteBacks`/`useSessionArtifacts`
|
|
54
|
+
* dedup by "latest wins" because each entry is a *snapshot* (a PR; a file's
|
|
55
|
+
* final bytes). A {@link FileChange} is a *delta* — an agent often rewrites the
|
|
56
|
+
* same file several times in a turn, and showing only the last edit's diff
|
|
57
|
+
* would misrepresent what changed. So changes to the same path are reduced to a
|
|
58
|
+
* single net change: `before` from the first change, `after` from the last.
|
|
59
|
+
* Because a WHOLE_FILE `after` is a complete file snapshot, `first.before →
|
|
60
|
+
* last.after` is exactly the net change. A group with one change (the common
|
|
61
|
+
* case) is passed through untouched; multi-edit groups synthesize a net
|
|
62
|
+
* {@link FileChange} so every consumer speaks one proto type (DD-007).
|
|
63
|
+
*
|
|
64
|
+
* Counts and unified diffs for whole-file captures stay derivable by the
|
|
65
|
+
* presentation layer (the runner emits 0/"" sentinels), matching the contract
|
|
66
|
+
* the SDK `tool-view` established.
|
|
67
|
+
*
|
|
68
|
+
* @param executions - All executions for a session, in chronological order.
|
|
69
|
+
* Pass both completed and active-stream executions.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```tsx
|
|
73
|
+
* const allExecutions = [
|
|
74
|
+
* ...conv.completedExecutions,
|
|
75
|
+
* ...(conv.activeStreamExecution ? [conv.activeStreamExecution] : []),
|
|
76
|
+
* ];
|
|
77
|
+
* const { fileChanges, hasFileChanges } = useSessionFileChanges(allExecutions);
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @see useSessionWriteBacks — git-mode (PR) counterpart, which does drive the
|
|
81
|
+
* inspector's Changes tab
|
|
82
|
+
* @see useFileChangeContent — resolves a change's before/after text for diffing
|
|
83
|
+
* @see FileChangesView — component that renders this data
|
|
84
|
+
*/
|
|
85
|
+
export function useSessionFileChanges(
|
|
86
|
+
executions: readonly AgentExecution[],
|
|
87
|
+
): UseSessionFileChangesReturn {
|
|
88
|
+
return useMemo(() => {
|
|
89
|
+
// Path -> chronological list of raw changes to that path.
|
|
90
|
+
const groups = new Map<string, FileChange[]>();
|
|
91
|
+
// Preserve first-seen path order as a stable tiebreaker before sorting.
|
|
92
|
+
const collect = (change: FileChange) => {
|
|
93
|
+
const key = change.path || change.absolutePath;
|
|
94
|
+
if (!key) return;
|
|
95
|
+
const list = groups.get(key);
|
|
96
|
+
if (list) list.push(change);
|
|
97
|
+
else groups.set(key, [change]);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
for (const execution of executions) {
|
|
101
|
+
// The file-review ledger is the single source under apply-then-review: the
|
|
102
|
+
// display seam reads the live projection or folds the durable ledger for a
|
|
103
|
+
// terminal execution, and the execution-scoped ledger already covers
|
|
104
|
+
// sub-agent captures (DD-19), so one pass suffices. Each CapturedFileChange
|
|
105
|
+
// is projected onto the display FileChange the net-collapse + renderer speak.
|
|
106
|
+
const capturedChanges = displayFileChangeSets(execution.status).flatMap(
|
|
107
|
+
(set) => set.changes,
|
|
108
|
+
);
|
|
109
|
+
for (const captured of capturedChanges) collect(toDisplayFileChange(captured));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const fileChanges = Array.from(groups.values(), netChange);
|
|
113
|
+
fileChanges.sort(fileChangeComparator);
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
fileChanges,
|
|
117
|
+
hasFileChanges: fileChanges.length > 0,
|
|
118
|
+
fileChangeCount: fileChanges.length,
|
|
119
|
+
};
|
|
120
|
+
}, [executions]);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reduces a path's chronological change list to a single net change.
|
|
125
|
+
* A one-change group is returned as-is (the common path, no allocation);
|
|
126
|
+
* multi-edit groups synthesize a net {@link FileChange}.
|
|
127
|
+
*/
|
|
128
|
+
function netChange(changes: FileChange[]): FileChange {
|
|
129
|
+
if (changes.length === 1) return changes[0];
|
|
130
|
+
|
|
131
|
+
const first = changes[0];
|
|
132
|
+
const last = changes[changes.length - 1];
|
|
133
|
+
const changeType = netChangeType(first, last);
|
|
134
|
+
|
|
135
|
+
// Whole-file only when both chosen sides come from whole-file captures;
|
|
136
|
+
// otherwise fall back to the last change's hunk diff. A path edited by a
|
|
137
|
+
// single harness won't mix levels — this just stays honest if it ever does.
|
|
138
|
+
const wholeFile =
|
|
139
|
+
first.captureLevel === FileChangeCaptureLevel.WHOLE_FILE &&
|
|
140
|
+
last.captureLevel === FileChangeCaptureLevel.WHOLE_FILE;
|
|
141
|
+
|
|
142
|
+
if (wholeFile) {
|
|
143
|
+
return create(FileChangeSchema, {
|
|
144
|
+
path: last.path,
|
|
145
|
+
absolutePath: last.absolutePath,
|
|
146
|
+
changeType,
|
|
147
|
+
captureLevel: FileChangeCaptureLevel.WHOLE_FILE,
|
|
148
|
+
before: first.before,
|
|
149
|
+
after: last.after,
|
|
150
|
+
renameFrom: changeType === FileChangeType.RENAME ? first.renameFrom : "",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return create(FileChangeSchema, {
|
|
155
|
+
path: last.path,
|
|
156
|
+
absolutePath: last.absolutePath,
|
|
157
|
+
changeType,
|
|
158
|
+
captureLevel: FileChangeCaptureLevel.HUNK_ONLY,
|
|
159
|
+
unifiedDiff: last.unifiedDiff,
|
|
160
|
+
linesAdded: last.linesAdded,
|
|
161
|
+
linesRemoved: last.linesRemoved,
|
|
162
|
+
renameFrom: changeType === FileChangeType.RENAME ? first.renameFrom : "",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Reconciles the net `change_type` of a path from its first and last change.
|
|
168
|
+
*
|
|
169
|
+
* Intent: the badge should describe the file's journey across the *whole*
|
|
170
|
+
* session, anchored on its two endpoints, in this precedence:
|
|
171
|
+
*
|
|
172
|
+
* 1. Ends deleted (`last` is DELETE) → DELETE. The file is gone, regardless of
|
|
173
|
+
* how it got there — this includes create-then-delete, which we deliberately
|
|
174
|
+
* surface as DELETE rather than hiding the path. (`before` then comes from
|
|
175
|
+
* the first change, which for a created-then-deleted file is empty, so the
|
|
176
|
+
* diff reads as the removal of whatever briefly existed.)
|
|
177
|
+
* 2. Started created (`first` is CREATE) and still exists → CREATE. A file born
|
|
178
|
+
* this session and later modified is, on net, a new file.
|
|
179
|
+
* 3. Started renamed (`first` is RENAME) and still exists → RENAME, carrying the
|
|
180
|
+
* original `rename_from` so the move is preserved across later edits.
|
|
181
|
+
* 4. Otherwise → MODIFY (the common multi-edit case).
|
|
182
|
+
*/
|
|
183
|
+
function netChangeType(first: FileChange, last: FileChange): FileChangeType {
|
|
184
|
+
if (last.changeType === FileChangeType.DELETE) return FileChangeType.DELETE;
|
|
185
|
+
if (first.changeType === FileChangeType.CREATE) return FileChangeType.CREATE;
|
|
186
|
+
if (first.changeType === FileChangeType.RENAME) return FileChangeType.RENAME;
|
|
187
|
+
return FileChangeType.MODIFY;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Most-actionable first (modified), then additions (create/rename), then
|
|
191
|
+
// deletions — mirroring GitHub's file-list convention — alphabetical within
|
|
192
|
+
// each group so the list is stable and scannable.
|
|
193
|
+
const CHANGE_TYPE_ORDER: Readonly<Record<FileChangeType, number>> = {
|
|
194
|
+
[FileChangeType.MODIFY]: 0,
|
|
195
|
+
[FileChangeType.CREATE]: 1,
|
|
196
|
+
[FileChangeType.RENAME]: 1,
|
|
197
|
+
[FileChangeType.DELETE]: 2,
|
|
198
|
+
[FileChangeType.UNSPECIFIED]: 3,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
function fileChangeComparator(a: FileChange, b: FileChange): number {
|
|
202
|
+
const order =
|
|
203
|
+
(CHANGE_TYPE_ORDER[a.changeType] ?? 3) -
|
|
204
|
+
(CHANGE_TYPE_ORDER[b.changeType] ?? 3);
|
|
205
|
+
if (order !== 0) return order;
|
|
206
|
+
return a.path.localeCompare(b.path, undefined, { sensitivity: "base" });
|
|
207
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -1,7 +1,21 @@
|
|
|
1
1
|
@source "./**/*.{ts,tsx}";
|
|
2
2
|
@source "../node_modules/streamdown/dist/*.js";
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
/* Declare the standard Tailwind layer order plus our `stgm` utilities layer
|
|
5
|
+
LAST. In a host app the host's `@import "tailwindcss"` already declares
|
|
6
|
+
`theme, base, components, utilities` first, so re-stating them here is a
|
|
7
|
+
harmless no-op that simply pins `stgm` after them; in the standalone SDK build
|
|
8
|
+
it establishes the full order explicitly.
|
|
9
|
+
|
|
10
|
+
Why this matters: the preflight-style reset below (which zeroes `border-width`)
|
|
11
|
+
MUST live in `base`, not in a layer above `utilities`. When this stylesheet is
|
|
12
|
+
recompiled by a host's Tailwind v4, the SDK's `border-*` utilities are emitted
|
|
13
|
+
into the host's `utilities` layer (Tailwind v4 emits each utility once) — NOT
|
|
14
|
+
into `stgm`. A reset placed in any layer after `utilities` therefore wins and
|
|
15
|
+
silently zeroes every `border` inside `.stgm`. Keeping the reset in `base`
|
|
16
|
+
(exactly where stock Tailwind keeps preflight) guarantees the utilities win in
|
|
17
|
+
the host build, the standalone build, and a third-party embed alike. */
|
|
18
|
+
@layer theme, base, components, utilities, stgm;
|
|
5
19
|
|
|
6
20
|
@import "tailwindcss/theme.css" layer(stgm);
|
|
7
21
|
@import "tailwindcss/utilities.css" layer(stgm);
|
|
@@ -66,6 +80,7 @@
|
|
|
66
80
|
--color-muted-foreground-subtle: var(--stgm-muted-foreground-subtle);
|
|
67
81
|
--color-muted-foreground-faint: var(--stgm-muted-foreground-faint);
|
|
68
82
|
--color-border-muted: var(--stgm-border-muted);
|
|
83
|
+
--color-border-prominent: var(--stgm-border-prominent);
|
|
69
84
|
--color-backdrop: var(--stgm-backdrop);
|
|
70
85
|
--color-sidebar: var(--stgm-sidebar);
|
|
71
86
|
--color-sidebar-foreground: var(--stgm-sidebar-foreground);
|
|
@@ -115,15 +130,15 @@
|
|
|
115
130
|
z-index: var(--stgm-z-popover);
|
|
116
131
|
}
|
|
117
132
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
133
|
+
/* Preflight-style reset, scoped to `.stgm`. Lives in the standard `base` layer —
|
|
134
|
+
exactly where stock Tailwind keeps its preflight — so it ranks BELOW the
|
|
135
|
+
`utilities` layer where `border-*` utilities are emitted (see the layer
|
|
136
|
+
declaration at the top of this file). It supplies only the DEFAULTS
|
|
137
|
+
(`border-style: solid`, width 0, themed color) that a `border` utility then
|
|
138
|
+
builds on; because `utilities` outranks `base`, the utility's width always
|
|
139
|
+
wins. Putting this in a layer after `utilities` (the previous `stgm-base`) is
|
|
140
|
+
what silently zeroed every border inside `.stgm`. */
|
|
141
|
+
@layer base {
|
|
127
142
|
.stgm *,
|
|
128
143
|
.stgm *::before,
|
|
129
144
|
.stgm *::after {
|
|
@@ -132,6 +147,16 @@
|
|
|
132
147
|
border-style: solid;
|
|
133
148
|
border-color: var(--stgm-border, currentColor);
|
|
134
149
|
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
@layer stgm {
|
|
153
|
+
.stgm {
|
|
154
|
+
--stgm-motion-duration: 150ms;
|
|
155
|
+
--stgm-motion-easing: var(--stgm-transition-timing, ease-out);
|
|
156
|
+
line-height: 1.5;
|
|
157
|
+
-webkit-font-smoothing: antialiased;
|
|
158
|
+
-moz-osx-font-smoothing: grayscale;
|
|
159
|
+
}
|
|
135
160
|
|
|
136
161
|
@keyframes stgm-fade-in-up {
|
|
137
162
|
from {
|
|
@@ -63,8 +63,7 @@ export function DiffViewer({ hunks, filePath, className }: DiffViewerProps) {
|
|
|
63
63
|
<table className="w-full border-collapse" role="table">
|
|
64
64
|
<thead className="sr-only">
|
|
65
65
|
<tr>
|
|
66
|
-
<th scope="col">
|
|
67
|
-
<th scope="col">New line</th>
|
|
66
|
+
<th scope="col">Line</th>
|
|
68
67
|
<th scope="col">Change</th>
|
|
69
68
|
<th scope="col">Content</th>
|
|
70
69
|
</tr>
|
|
@@ -97,7 +96,7 @@ function HunkRows({
|
|
|
97
96
|
{!isFirst && (
|
|
98
97
|
<tr>
|
|
99
98
|
<td
|
|
100
|
-
colSpan={
|
|
99
|
+
colSpan={3}
|
|
101
100
|
className="select-none bg-diff-hunk-header-bg px-3 py-1 text-[11px] text-diff-hunk-header-fg"
|
|
102
101
|
aria-label={`Hunk: ${header}`}
|
|
103
102
|
>
|
|
@@ -144,13 +143,17 @@ function DiffLineRow({ line }: { readonly line: DiffLine }) {
|
|
|
144
143
|
? `Removed line ${line.oldLineNumber}: ${line.content}`
|
|
145
144
|
: undefined;
|
|
146
145
|
|
|
146
|
+
// A single line-number gutter — the Cursor/VS Code inline-diff convention,
|
|
147
|
+
// more compact than GitHub's old+new pair. Each line shows the number from the
|
|
148
|
+
// side it belongs to: the new-file number for added/context lines, the
|
|
149
|
+
// old-file number for a removed line (which has no new-side number). The
|
|
150
|
+
// marker (+/-) and row color carry the change type.
|
|
151
|
+
const lineNumber = line.newLineNumber ?? line.oldLineNumber;
|
|
152
|
+
|
|
147
153
|
return (
|
|
148
154
|
<tr className={rowClass} aria-label={ariaLabel}>
|
|
149
155
|
<td className={LINE_NUM_CLASSES}>
|
|
150
|
-
{
|
|
151
|
-
</td>
|
|
152
|
-
<td className={LINE_NUM_CLASSES}>
|
|
153
|
-
{line.newLineNumber ?? ""}
|
|
156
|
+
{lineNumber ?? ""}
|
|
154
157
|
</td>
|
|
155
158
|
<td className={cn(MARKER_CLASSES, markerColor)} aria-hidden="true">
|
|
156
159
|
{marker}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import { cn } from "@stigmer/theme";
|
|
5
|
+
import { DiffViewer } from "./DiffViewer";
|
|
6
|
+
import { parseUnifiedDiff } from "./parseUnifiedDiff";
|
|
7
|
+
|
|
8
|
+
/** Props for {@link UnifiedDiffView}. */
|
|
9
|
+
export interface UnifiedDiffViewProps {
|
|
10
|
+
/** A unified-diff patch string (e.g. Cursor's hunk-only `diffString`). */
|
|
11
|
+
readonly patch: string;
|
|
12
|
+
/** Additional CSS classes for the root container (e.g. a height cap). */
|
|
13
|
+
readonly className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The single canonical renderer for a unified-diff patch *string*.
|
|
18
|
+
*
|
|
19
|
+
* It parses the patch into hunks and renders them through {@link DiffViewer} —
|
|
20
|
+
* the same accessible table (line numbers, colored row backgrounds, a narrow
|
|
21
|
+
* `+`/`-` gutter column, `--stgm-diff-*` tokens) that a computed before/after
|
|
22
|
+
* diff uses — so a harness that only emits a ready unified diff (the Cursor
|
|
23
|
+
* harness's `diffString`) looks line-for-line identical to a native whole-file
|
|
24
|
+
* diff. The `---`/`+++`/`@@` preamble parses into metadata and never reaches the
|
|
25
|
+
* screen; a single-hunk diff shows no `@@` separator at all.
|
|
26
|
+
*
|
|
27
|
+
* Fallback: if the patch cannot be parsed into any hunk (a malformed or
|
|
28
|
+
* non-standard payload) it renders verbatim in a `<pre>` so we show something
|
|
29
|
+
* truthful rather than a misleading "No changes". Color is never the sole
|
|
30
|
+
* channel in either path — the gutter marker / literal prefix carries the change
|
|
31
|
+
* type for non-color readers.
|
|
32
|
+
*
|
|
33
|
+
* Internal to the SDK (not barrel-exported); consumed by {@link ResultView} and
|
|
34
|
+
* {@link FileChangeDiff}.
|
|
35
|
+
*/
|
|
36
|
+
export function UnifiedDiffView({ patch, className }: UnifiedDiffViewProps) {
|
|
37
|
+
const hunks = useMemo(() => parseUnifiedDiff(patch), [patch]);
|
|
38
|
+
|
|
39
|
+
if (hunks.length > 0) {
|
|
40
|
+
return <DiffViewer hunks={hunks} className={className} />;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Unparseable but non-empty: show the raw patch rather than nothing.
|
|
44
|
+
const lines = patch.split("\n");
|
|
45
|
+
return (
|
|
46
|
+
<pre
|
|
47
|
+
className={cn(
|
|
48
|
+
"max-h-96 overflow-auto whitespace-pre-wrap break-words rounded-md border border-border bg-muted-subtle p-2 font-mono text-xs",
|
|
49
|
+
className,
|
|
50
|
+
)}
|
|
51
|
+
>
|
|
52
|
+
{lines.map((line, i) => (
|
|
53
|
+
<div
|
|
54
|
+
key={i}
|
|
55
|
+
className={cn(
|
|
56
|
+
line.startsWith("+") && !line.startsWith("+++") && "text-diff-added-fg",
|
|
57
|
+
line.startsWith("-") && !line.startsWith("---") && "text-diff-removed-fg",
|
|
58
|
+
line.startsWith("@@") && "text-diff-hunk-header-fg",
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
{line || " "}
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</pre>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { render, cleanup } from "@testing-library/react";
|
|
3
|
+
import { DiffViewer } from "../DiffViewer";
|
|
4
|
+
import type { DiffHunk } from "../types";
|
|
5
|
+
|
|
6
|
+
afterEach(cleanup);
|
|
7
|
+
|
|
8
|
+
// A hunk exercising all three line types: an unchanged line, a removal, and an
|
|
9
|
+
// addition — so the single-gutter numbering can be checked per type.
|
|
10
|
+
const hunk: DiffHunk = {
|
|
11
|
+
oldStart: 1,
|
|
12
|
+
oldLines: 2,
|
|
13
|
+
newStart: 1,
|
|
14
|
+
newLines: 2,
|
|
15
|
+
lines: [
|
|
16
|
+
{ type: "context", content: "keep", oldLineNumber: 1, newLineNumber: 1 },
|
|
17
|
+
{ type: "removed", content: "gone", oldLineNumber: 2 },
|
|
18
|
+
{ type: "added", content: "fresh", newLineNumber: 2 },
|
|
19
|
+
],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/** The first cell of a row is the (single) line-number gutter. */
|
|
23
|
+
function gutter(row: Element): string {
|
|
24
|
+
return row.querySelectorAll("td")[0]?.textContent ?? "";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("DiffViewer — single-column gutter (Cursor-style)", () => {
|
|
28
|
+
it("renders exactly one line-number column (3 cells per row, not 4)", () => {
|
|
29
|
+
const { container } = render(<DiffViewer hunks={[hunk]} />);
|
|
30
|
+
const firstRow = container.querySelector("tbody tr")!;
|
|
31
|
+
// gutter + marker + content — the old+new pair is collapsed to one.
|
|
32
|
+
expect(firstRow.querySelectorAll("td")).toHaveLength(3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("numbers each line from the side it belongs to (new for added/context, old for removed)", () => {
|
|
36
|
+
const { container } = render(<DiffViewer hunks={[hunk]} />);
|
|
37
|
+
const rows = Array.from(container.querySelectorAll("tbody tr"));
|
|
38
|
+
|
|
39
|
+
// context "keep" → its new-file number
|
|
40
|
+
expect(gutter(rows[0])).toBe("1");
|
|
41
|
+
expect(rows[0].textContent).toContain("keep");
|
|
42
|
+
|
|
43
|
+
// removed "gone" → its old-file number (it has no new-side number)
|
|
44
|
+
expect(gutter(rows[1])).toBe("2");
|
|
45
|
+
expect(rows[1].textContent).toContain("gone");
|
|
46
|
+
|
|
47
|
+
// added "fresh" → its new-file number
|
|
48
|
+
expect(gutter(rows[2])).toBe("2");
|
|
49
|
+
expect(rows[2].textContent).toContain("fresh");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it("renders a 'No changes' status (no table) for empty hunks", () => {
|
|
53
|
+
const { container } = render(<DiffViewer hunks={[]} />);
|
|
54
|
+
expect(container.querySelector("table")).toBeNull();
|
|
55
|
+
expect(container.textContent).toContain("No changes");
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
2
|
+
import { render, cleanup } from "@testing-library/react";
|
|
3
|
+
import { UnifiedDiffView } from "../UnifiedDiffView";
|
|
4
|
+
|
|
5
|
+
afterEach(cleanup);
|
|
6
|
+
|
|
7
|
+
describe("UnifiedDiffView", () => {
|
|
8
|
+
it("renders a parseable patch through the DiffViewer table, not a raw dump", () => {
|
|
9
|
+
const patch =
|
|
10
|
+
"--- /dev/null\n+++ b/notes.md\n@@ -0,0 +1,2 @@\n+# Title\n+body\n";
|
|
11
|
+
|
|
12
|
+
const { container } = render(<UnifiedDiffView patch={patch} />);
|
|
13
|
+
|
|
14
|
+
// The accessible table is the renderer — not the old raw <pre>.
|
|
15
|
+
expect(container.querySelector("table")).not.toBeNull();
|
|
16
|
+
// Content is present...
|
|
17
|
+
expect(container.textContent).toContain("# Title");
|
|
18
|
+
expect(container.textContent).toContain("body");
|
|
19
|
+
// ...but the preamble never reaches the screen.
|
|
20
|
+
expect(container.textContent).not.toContain("/dev/null");
|
|
21
|
+
expect(container.textContent).not.toContain("+++");
|
|
22
|
+
// A single hunk shows no @@ separator (DiffViewer omits the first header).
|
|
23
|
+
expect(container.textContent).not.toContain("@@");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("shows the subtle @@ separator only between hunks of a multi-hunk patch", () => {
|
|
27
|
+
const patch =
|
|
28
|
+
"--- a/f\n+++ b/f\n@@ -1,2 +1,2 @@\n ctx1\n-a\n+A\n@@ -10,2 +10,3 @@\n ctx2\n+added\n ctx3\n";
|
|
29
|
+
|
|
30
|
+
const { container } = render(<UnifiedDiffView patch={patch} />);
|
|
31
|
+
|
|
32
|
+
expect(container.querySelector("table")).not.toBeNull();
|
|
33
|
+
// The second hunk's header renders as a separator row.
|
|
34
|
+
expect(container.textContent).toContain("@@ -10,2 +10,3 @@");
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("falls back to a raw <pre> for an unparseable but non-empty patch", () => {
|
|
38
|
+
const { container } = render(<UnifiedDiffView patch="totally not a diff" />);
|
|
39
|
+
|
|
40
|
+
expect(container.querySelector("table")).toBeNull();
|
|
41
|
+
expect(container.querySelector("pre")).not.toBeNull();
|
|
42
|
+
expect(container.textContent).toContain("totally not a diff");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("forwards className to the rendered container (e.g. a height cap)", () => {
|
|
46
|
+
const patch = "@@ -1 +1 @@\n-a\n+b\n";
|
|
47
|
+
const { container } = render(<UnifiedDiffView patch={patch} className="max-h-80" />);
|
|
48
|
+
expect(container.querySelector(".max-h-80")).not.toBeNull();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { computeDiff } from "../computeDiff";
|
|
3
|
+
|
|
4
|
+
describe("computeDiff", () => {
|
|
5
|
+
it("returns no hunks for identical text", () => {
|
|
6
|
+
expect(computeDiff("same\n", "same\n")).toEqual([]);
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it("maps an addition with a new-side line number and no old-side number", () => {
|
|
10
|
+
const hunks = computeDiff("hello\n", "hello\nworld\n");
|
|
11
|
+
expect(hunks).toHaveLength(1);
|
|
12
|
+
const added = hunks[0].lines.find((l) => l.type === "added");
|
|
13
|
+
expect(added).toMatchObject({ type: "added", content: "world" });
|
|
14
|
+
expect(added?.newLineNumber).toBe(2);
|
|
15
|
+
expect(added?.oldLineNumber).toBeUndefined();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("maps a replacement into a removed + added pair with correct numbers", () => {
|
|
19
|
+
const hunks = computeDiff("alpha\n", "beta\n");
|
|
20
|
+
expect(hunks[0].lines).toEqual([
|
|
21
|
+
{ type: "removed", content: "alpha", oldLineNumber: 1 },
|
|
22
|
+
{ type: "added", content: "beta", newLineNumber: 1 },
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("skips the no-newline marker when a side lacks a trailing newline", () => {
|
|
27
|
+
// structuredPatch emits "" here; the shared
|
|
28
|
+
// mapper must drop it rather than render it as a spurious context line.
|
|
29
|
+
const hunks = computeDiff("alpha", "beta");
|
|
30
|
+
expect(hunks[0].lines).toEqual([
|
|
31
|
+
{ type: "removed", content: "alpha", oldLineNumber: 1 },
|
|
32
|
+
{ type: "added", content: "beta", newLineNumber: 1 },
|
|
33
|
+
]);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parseUnifiedDiff } from "../parseUnifiedDiff";
|
|
3
|
+
import { computeDiff } from "../computeDiff";
|
|
4
|
+
|
|
5
|
+
describe("parseUnifiedDiff", () => {
|
|
6
|
+
it("parses a /dev/null create, discarding the ---/+++ preamble", () => {
|
|
7
|
+
// The exact shape the Cursor harness emits for a new file (the screenshot
|
|
8
|
+
// case): the file headers must not survive into the rendered hunks.
|
|
9
|
+
const patch =
|
|
10
|
+
"--- /dev/null\n+++ b/Users/x/notes.md\n@@ -0,0 +1,3 @@\n+# Project Notes\n+\n+Minimal service\n";
|
|
11
|
+
|
|
12
|
+
const hunks = parseUnifiedDiff(patch);
|
|
13
|
+
|
|
14
|
+
expect(hunks).toHaveLength(1);
|
|
15
|
+
const lines = hunks[0].lines;
|
|
16
|
+
expect(lines.map((l) => l.type)).toEqual(["added", "added", "added"]);
|
|
17
|
+
expect(lines.map((l) => l.content)).toEqual(["# Project Notes", "", "Minimal service"]);
|
|
18
|
+
// Added lines carry a new-side number and no old-side number.
|
|
19
|
+
expect(lines.map((l) => l.newLineNumber)).toEqual([1, 2, 3]);
|
|
20
|
+
expect(lines.every((l) => l.oldLineNumber === undefined)).toBe(true);
|
|
21
|
+
// No preamble leaked into any content.
|
|
22
|
+
const allContent = lines.map((l) => l.content).join("\n");
|
|
23
|
+
expect(allContent).not.toContain("/dev/null");
|
|
24
|
+
expect(allContent).not.toContain("+++");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("parses a header-less hunk-only edit with correct line numbers", () => {
|
|
28
|
+
const patch = "@@ -1,3 +1,3 @@\n line one\n-old middle\n+new middle\n line three\n";
|
|
29
|
+
|
|
30
|
+
const hunks = parseUnifiedDiff(patch);
|
|
31
|
+
|
|
32
|
+
expect(hunks).toHaveLength(1);
|
|
33
|
+
expect(hunks[0].lines).toEqual([
|
|
34
|
+
{ type: "context", content: "line one", oldLineNumber: 1, newLineNumber: 1 },
|
|
35
|
+
{ type: "removed", content: "old middle", oldLineNumber: 2 },
|
|
36
|
+
{ type: "added", content: "new middle", newLineNumber: 2 },
|
|
37
|
+
{ type: "context", content: "line three", oldLineNumber: 3, newLineNumber: 3 },
|
|
38
|
+
]);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("skips '\' markers", () => {
|
|
42
|
+
const patch =
|
|
43
|
+
"--- a/f\n+++ b/f\n@@ -1 +1 @@\n-alpha\n\\n+beta\n\\n";
|
|
44
|
+
|
|
45
|
+
const hunks = parseUnifiedDiff(patch);
|
|
46
|
+
|
|
47
|
+
expect(hunks).toHaveLength(1);
|
|
48
|
+
// The two metadata markers are gone; only the real change lines remain.
|
|
49
|
+
expect(hunks[0].lines).toEqual([
|
|
50
|
+
{ type: "removed", content: "alpha", oldLineNumber: 1 },
|
|
51
|
+
{ type: "added", content: "beta", newLineNumber: 1 },
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("parses a multi-hunk patch into independent, correctly-numbered hunks", () => {
|
|
56
|
+
const patch =
|
|
57
|
+
"--- a/f\n+++ b/f\n@@ -1,2 +1,2 @@\n ctx1\n-a\n+A\n@@ -10,2 +10,3 @@\n ctx2\n+added\n ctx3\n";
|
|
58
|
+
|
|
59
|
+
const hunks = parseUnifiedDiff(patch);
|
|
60
|
+
|
|
61
|
+
expect(hunks).toHaveLength(2);
|
|
62
|
+
expect(hunks[1].lines).toEqual([
|
|
63
|
+
{ type: "context", content: "ctx2", oldLineNumber: 10, newLineNumber: 10 },
|
|
64
|
+
{ type: "added", content: "added", newLineNumber: 11 },
|
|
65
|
+
{ type: "context", content: "ctx3", oldLineNumber: 11, newLineNumber: 12 },
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns [] for an empty or unparseable patch (caller renders a raw fallback)", () => {
|
|
70
|
+
expect(parseUnifiedDiff("")).toEqual([]);
|
|
71
|
+
expect(parseUnifiedDiff("this is not a diff at all")).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("produces the same line shape as computeDiff for an equivalent change", () => {
|
|
75
|
+
// computeDiff (before/after text) and parseUnifiedDiff (a patch string) must
|
|
76
|
+
// map a change to identical DiffLines — they share mapPatchHunks.
|
|
77
|
+
const computed = computeDiff("alpha\n", "beta\n");
|
|
78
|
+
const parsed = parseUnifiedDiff("@@ -1 +1 @@\n-alpha\n+beta\n");
|
|
79
|
+
|
|
80
|
+
expect(parsed[0].lines).toEqual(computed[0].lines);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { structuredPatch } from "diff";
|
|
2
|
-
import
|
|
2
|
+
import { mapPatchHunks } from "./diff-hunks";
|
|
3
|
+
import type { DiffHunk } from "./types";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Compute a unified diff between two text strings.
|
|
6
7
|
*
|
|
7
8
|
* Returns an array of {@link DiffHunk} objects, each containing the
|
|
8
9
|
* changed and context lines with line numbers. Uses the `diff` library's
|
|
9
|
-
* Myers algorithm internally
|
|
10
|
+
* Myers algorithm internally, then maps the structured hunks through the
|
|
11
|
+
* shared {@link mapPatchHunks} (the same mapper `parseUnifiedDiff` uses), so a
|
|
12
|
+
* computed diff and a parsed unified diff render line-for-line identically.
|
|
10
13
|
*
|
|
11
14
|
* Pure function — no side effects, safe to call outside React.
|
|
12
15
|
*
|
|
@@ -23,39 +26,5 @@ export function computeDiff(
|
|
|
23
26
|
context: contextLines,
|
|
24
27
|
});
|
|
25
28
|
|
|
26
|
-
return patch.hunks
|
|
27
|
-
const lines: DiffLine[] = [];
|
|
28
|
-
let oldLine = hunk.oldStart;
|
|
29
|
-
let newLine = hunk.newStart;
|
|
30
|
-
|
|
31
|
-
for (const raw of hunk.lines) {
|
|
32
|
-
const prefix = raw[0];
|
|
33
|
-
const content = raw.slice(1);
|
|
34
|
-
|
|
35
|
-
if (prefix === "+") {
|
|
36
|
-
lines.push({ type: "added", content, newLineNumber: newLine });
|
|
37
|
-
newLine++;
|
|
38
|
-
} else if (prefix === "-") {
|
|
39
|
-
lines.push({ type: "removed", content, oldLineNumber: oldLine });
|
|
40
|
-
oldLine++;
|
|
41
|
-
} else {
|
|
42
|
-
lines.push({
|
|
43
|
-
type: "context",
|
|
44
|
-
content,
|
|
45
|
-
oldLineNumber: oldLine,
|
|
46
|
-
newLineNumber: newLine,
|
|
47
|
-
});
|
|
48
|
-
oldLine++;
|
|
49
|
-
newLine++;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {
|
|
54
|
-
oldStart: hunk.oldStart,
|
|
55
|
-
oldLines: hunk.oldLines,
|
|
56
|
-
newStart: hunk.newStart,
|
|
57
|
-
newLines: hunk.newLines,
|
|
58
|
-
lines,
|
|
59
|
-
};
|
|
60
|
-
});
|
|
29
|
+
return mapPatchHunks(patch.hunks);
|
|
61
30
|
}
|