Repository navigation
Diff viewer: review comments with per-repo persistence and TextBox attach #5768
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
053f3cb
a2982d1
954ec0d
710e455
ed02a99
098bc33
026cb93
dc198fa
297c537
b236900
feecfad
80149ac
02dab1d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| import CryptoKit | ||
| import Darwin | ||
| import Foundation | ||
|
|
||
|
|
@@ -400,7 +401,22 @@ extension CMUXCLI { | |
| static func localized() -> DiffViewerLabels { | ||
| DiffViewerLabels(values: [ | ||
| "additions": CMUXDiffViewerLocalization.string("diffViewer.additions", defaultValue: "Additions"), | ||
| "addComment": CMUXDiffViewerLocalization.string("diffViewer.addComment", defaultValue: "Add comment"), | ||
| "attachComment": CMUXDiffViewerLocalization.string("diffViewer.attachComment", defaultValue: "Attach"), | ||
| "attachFailed": CMUXDiffViewerLocalization.string("diffViewer.attachFailed", defaultValue: "Attach failed"), | ||
| "attachOnSave": CMUXDiffViewerLocalization.string("diffViewer.attachOnSave", defaultValue: "Attach to terminal TextBox"), | ||
| "attachedComment": CMUXDiffViewerLocalization.string("diffViewer.attachedComment", defaultValue: "Attached"), | ||
| "bars": CMUXDiffViewerLocalization.string("diffViewer.bars", defaultValue: "Bars"), | ||
| "cancelComment": CMUXDiffViewerLocalization.string("diffViewer.cancelComment", defaultValue: "Cancel"), | ||
| "chooseTerminal": CMUXDiffViewerLocalization.string("diffViewer.chooseTerminal", defaultValue: "Choose a terminal"), | ||
| "comments": CMUXDiffViewerLocalization.string("diffViewer.comments", defaultValue: "Comments"), | ||
| "commentPlaceholder": CMUXDiffViewerLocalization.string("diffViewer.commentPlaceholder", defaultValue: "Leave a comment"), | ||
| "deleteComment": CMUXDiffViewerLocalization.string("diffViewer.deleteComment", defaultValue: "Delete"), | ||
| "editComment": CMUXDiffViewerLocalization.string("diffViewer.editComment", defaultValue: "Edit"), | ||
| "noComments": CMUXDiffViewerLocalization.string("diffViewer.noComments", defaultValue: "No comments yet"), | ||
| "noTerminalForAttach": CMUXDiffViewerLocalization.string("diffViewer.noTerminalForAttach", defaultValue: "No terminal available"), | ||
| "outdatedComment": CMUXDiffViewerLocalization.string("diffViewer.outdatedComment", defaultValue: "Outdated"), | ||
| "saveComment": CMUXDiffViewerLocalization.string("diffViewer.saveComment", defaultValue: "Comment"), | ||
| "changedFiles": CMUXDiffViewerLocalization.string("diffViewer.changedFiles", defaultValue: "Changed files"), | ||
| "classic": CMUXDiffViewerLocalization.string("diffViewer.classic", defaultValue: "Classic"), | ||
| "commit": CMUXDiffViewerLocalization.string("about.commit", defaultValue: "Commit"), | ||
|
|
@@ -833,21 +849,28 @@ extension CMUXCLI { | |
| ) | ||
| if let cwd = parsedArgs.cwd { | ||
| diffSourceContext.repoRoot = try gitRepoRoot(startingAt: resolvePath(cwd)) | ||
| } | ||
| if parsedArgs.source != nil { | ||
| try resolveTargetIfNeeded() | ||
| var sourceContext = try canonicalDiffSourceContext( | ||
| workspaceHandle: workspaceHandle, | ||
| surfaceHandle: surfaceHandle, | ||
| windowHandle: windowHandle, | ||
| client: try connectedClient() | ||
| } else if parsedArgs.source == nil { | ||
| // Piped patches get a best-effort repo root from the CLI's cwd so | ||
| // diff comments can persist per repository. | ||
| diffSourceContext.repoRoot = try? gitRepoRoot( | ||
| startingAt: FileManager.default.currentDirectoryPath | ||
| ) | ||
| sourceContext.repoRoot = diffSourceContext.repoRoot | ||
| sourceContext.branchBaseRef = diffSourceContext.branchBaseRef | ||
| diffSourceContext = sourceContext | ||
| workspaceHandle = sourceContext.workspaceId ?? workspaceHandle | ||
| surfaceHandle = sourceContext.surfaceId ?? surfaceHandle | ||
| } | ||
| // Resolve the opener workspace/surface before writing the viewer HTML | ||
| // (for piped patches too), so the payload's commentTarget always points | ||
| // at the terminal the diff was opened from. | ||
| try resolveTargetIfNeeded() | ||
| var sourceContext = try canonicalDiffSourceContext( | ||
| workspaceHandle: workspaceHandle, | ||
| surfaceHandle: surfaceHandle, | ||
| windowHandle: windowHandle, | ||
| client: try connectedClient() | ||
| ) | ||
| sourceContext.repoRoot = diffSourceContext.repoRoot | ||
| sourceContext.branchBaseRef = diffSourceContext.branchBaseRef | ||
| diffSourceContext = sourceContext | ||
| workspaceHandle = sourceContext.workspaceId ?? workspaceHandle | ||
| surfaceHandle = sourceContext.surfaceId ?? surfaceHandle | ||
|
|
||
| let appearance = diffViewerAppearance( | ||
| socketPath: socketPath, | ||
|
|
@@ -3086,6 +3109,9 @@ extension CMUXCLI { | |
| layoutSource: layoutSource, | ||
| appearance: appearance, | ||
| sourceOptions: [], | ||
| repoRoot: context.repoRoot, | ||
| commentTargetWorkspaceId: context.workspaceId, | ||
| commentTargetSurfaceId: context.surfaceId, | ||
| runtime: runtime | ||
| ) | ||
| let assets = try ensureDiffViewerAssets(nextTo: viewerFileURL, runtime: runtime) | ||
|
|
@@ -3668,6 +3694,8 @@ extension CMUXCLI { | |
| baseOptions: selectedSource == .branch ? baseOptions : [], | ||
| repoRoot: repoRoot, | ||
| branchBaseRef: selectedSource == .branch ? selectedContext.branchBaseRef : nil, | ||
| commentTargetWorkspaceId: selectedContext.workspaceId, | ||
| commentTargetSurfaceId: selectedContext.surfaceId, | ||
| runtime: target.runtime | ||
| ) | ||
| } else if let selectedEmptyMessage { | ||
|
|
@@ -3973,6 +4001,8 @@ extension CMUXCLI { | |
| baseOptions: baseOptions, | ||
| repoRoot: pageContext.repoRoot, | ||
| branchBaseRef: source == .branch ? pageContext.branchBaseRef : nil, | ||
| commentTargetWorkspaceId: pageContext.workspaceId, | ||
| commentTargetSurfaceId: pageContext.surfaceId, | ||
| runtime: sourceSet.runtime | ||
| ) | ||
| return DiffViewerDeferredCompletion( | ||
|
|
@@ -5593,6 +5623,8 @@ extension CMUXCLI { | |
| baseOptions: [DiffViewerSourceOption] = [], | ||
| repoRoot: String? = nil, | ||
| branchBaseRef: String? = nil, | ||
| commentTargetWorkspaceId: String? = nil, | ||
| commentTargetSurfaceId: String? = nil, | ||
| statusMessage: String? = nil, | ||
| statusIsError: Bool = false, | ||
| pollForReplacement: Bool = false, | ||
|
|
@@ -5632,6 +5664,16 @@ extension CMUXCLI { | |
| if let branchBaseRef { | ||
| payload["branchBaseRef"] = branchBaseRef | ||
| } | ||
| if commentTargetWorkspaceId != nil || commentTargetSurfaceId != nil { | ||
| var commentTarget: [String: Any] = [:] | ||
| if let commentTargetWorkspaceId { | ||
| commentTarget["workspaceId"] = commentTargetWorkspaceId | ||
| } | ||
| if let commentTargetSurfaceId { | ||
| commentTarget["surfaceId"] = commentTargetSurfaceId | ||
| } | ||
| payload["commentTarget"] = commentTarget | ||
| } | ||
| let assets = try ensureDiffViewerAssets(nextTo: viewerURL, runtime: runtime) | ||
| let config: [String: Any] = [ | ||
| "payload": payload, | ||
|
|
@@ -5801,12 +5843,29 @@ extension CMUXCLI { | |
| if FileManager.default.fileExists(atPath: appDirectory.path, isDirectory: &isDirectory), | ||
| isDirectory.boolValue, | ||
| FileManager.default.fileExists(atPath: entry.path) { | ||
| return (sourceDirectory: appDirectory, targetDirectoryName: candidate.targetName) | ||
| // The shared /tmp asset cache is written by every running cmux | ||
| // build (stable, nightly, each tagged dev app). Content-key the | ||
| // directory so builds with different webview bundles coexist | ||
| // instead of clobbering each other's chunks, which broke pages | ||
| // whose per-token allowlist no longer matched the files on disk. | ||
| let targetName = "\(candidate.targetName)-\(try diffViewerAppAssetContentKey(directory: appDirectory))" | ||
| return (sourceDirectory: appDirectory, targetDirectoryName: targetName) | ||
|
Comment on lines
+5821
to
+5827
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prune stale hashed asset directories as part of diff-viewer cleanup. This change starts creating a new 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| throw CLIError(message: "Bundled cmux diff viewer app assets not found") | ||
| } | ||
|
|
||
| private func diffViewerAppAssetContentKey(directory: URL) throws -> String { | ||
| var hasher = SHA256() | ||
| for relativePath in try diffViewerBundledAssetRelativePaths(in: directory).sorted() { | ||
| hasher.update(data: Data(relativePath.utf8)) | ||
| let fileURL = directory.appendingPathComponent(relativePath, isDirectory: false) | ||
| hasher.update(data: try Data(contentsOf: fileURL, options: .mappedIfSafe)) | ||
| } | ||
| let digest = hasher.finalize() | ||
| return digest.map { String(format: "%02x", $0) }.joined().prefix(12).lowercased() | ||
|
Comment on lines
+5833
to
+5841
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win Memoize the app-bundle hash instead of rehashing on every page write.
🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
|
|
||
| private func copyDiffViewerAsset(relativePath: String, from sourceDirectory: URL, to targetDirectory: URL) throws { | ||
| let fileManager = FileManager.default | ||
| let sourceURL = sourceDirectory.appendingPathComponent(relativePath, isDirectory: false) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.