Add quick review button.

This commit is contained in:
Greyson Parrelli committed 2026-11-02 15:56:21 -05:00
1 parent 3db4123319
commit c550169c83
4 files changed
+66

No files matched your search

+1
View File
@@ -7,6 +7,7 @@
-->
<gresources>
<gresource prefix="/dev/greyson/playpen/icons">
<file compressed="true">scalable/actions/chat-message-new-symbolic.svg</file>
<file compressed="true">scalable/actions/document-edit-symbolic.svg</file>
<file compressed="true">scalable/actions/edit-undo-symbolic.svg</file>
<file compressed="true">scalable/actions/emblem-system-symbolic.svg</file>
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg">
<path d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 6 c 0 1.644531 1.355469 3 3 3 h 1 v 3 l 3 -3 v -1 c 0 -0.550781 -0.449219 -1 -1 -1 h -3 c -0.570312 0 -1 -0.429688 -1 -1 v -6 c 0 -0.554688 0.445312 -1 1 -1 h 10 c 0.554688 0 1 0.445312 1 1 v 4 c 0 0.550781 0.449219 1 1 1 s 1 -0.449219 1 -1 v -4 c 0 -1.644531 -1.355469 -3 -3 -3 z m 8 7 v 3 h -3 v 2 h 3 v 3 h 2 v -3 h 3 v -2 h -3 v -3 z m 0 0" fill="#2e3436"/>
</svg>

After

Width:  |  Height:  |  Size: 548 B

+11
View File
@@ -506,6 +506,13 @@ fn buildHeader(self: *Pane) void {
_ = gtk.Button.signals.clicked.connect(review_button, *Pane, &onReviewClicked, self, .{});
header.append(review_button.as(gtk.Widget));
const agent_review = gtk.Button.newFromIconName("chat-message-new-symbolic");
agent_review.as(gtk.Widget).addCssClass("flat");
agent_review.as(gtk.Widget).addCssClass("playpen-pane-button");
agent_review.as(gtk.Widget).setTooltipText("Have Claude review this tab's changes in the review pane");
_ = gtk.Button.signals.clicked.connect(agent_review, *Pane, &onAgentReviewClicked, self, .{});
header.append(agent_review.as(gtk.Widget));
// Last before close, so the destructive button stays on the end where it
// is expected and the zoom toggle sits with the other view controls.
self.zoom.as(gtk.Widget).addCssClass("flat");
@@ -765,6 +772,10 @@ fn onReviewClicked(_: *gtk.Button, self: *Pane) callconv(.c) void {
self.view.requestReview();
}
fn onAgentReviewClicked(_: *gtk.Button, self: *Pane) callconv(.c) void {
self.view.startAgentReview(self);
}
fn onZoomClicked(_: *gtk.Button, self: *Pane) callconv(.c) void {
self.view.toggleZoom(self);
}
+50
View File
@@ -355,6 +355,56 @@ pub fn requestReview(self: *View) void {
self.on_review(self.ctx);
}
/// What the pane header's agent-review button types into the new shell.
const agent_review_command = "claude '/pr-review using /leave-review'";
/// Open a Claude Code session beside `from`, in the directory `from` is working
/// in, and have it review the tab's diff into the review pane.
///
/// `/leave-review` posts to this tab's review pane and stops if there isn't
/// one, so a missing pane is opened first rather than leaving the session to
/// fail on arrival. `from` is focused before that, so the review is bound to
/// the same directory the session starts in.
pub fn startAgentReview(self: *View, from: *Pane) void {
var buf: [std.fs.max_path_bytes]u8 = undefined;
const cwd = self.paneDirectory(from, &buf);
if (self.reviewPane() == null) {
self.setFocused(from);
self.requestReview();
}
self.addPane(.{ .terminal = .{
.cwd = cwd,
.command = agent_review_command,
} }) catch |err| {
std.log.err("failed to open the agent review: {s}", .{@errorName(err)});
};
}
/// The directory `pane` is working in, copied into `buf`, or empty to leave it
/// to the shell's default.
///
/// A terminal answers for itself, a review pane with the repository it is bound
/// to; a web pane has no directory, so it borrows the first terminal's in the
/// tab.
fn paneDirectory(self: *View, pane: *Pane, buf: []u8) []const u8 {
switch (pane.content) {
.terminal => |t| if (t.session.cwd(buf)) |dir| return dir,
.review => if (self.review_spec.repo.len > 0) {
const n = @min(buf.len, self.review_spec.repo.len);
@memcpy(buf[0..n], self.review_spec.repo[0..n]);
return buf[0..n];
},
.web => {},
}
for (self.panes.items) |other| {
const t = other.terminal() orelse continue;
if (t.session.cwd(buf)) |dir| return dir;
}
return "";
}
pub fn closePane(self: *View, pane: *Pane) void {
if (self.closing) return;