Commit e3d2504c3acd

Vincent Demeester <vincent@sbr.pm>
2026-03-25 16:01:14
feat(pi/github): add body preview to comment actions
Added approvalGateWithBodyPreview helper that offers a full-content editor preview when body text exceeds 200 characters, matching the existing pattern from issue-create and pr-create. Applied it to issue-comment, issue-edit, pr-comment, pr-review, pr-line-comment, pr-review-edit, and pr-review-comment-edit handlers.
1 parent 32c7266
Changed files (3)
dots
pi
agent
extensions
dots/pi/agent/extensions/github/actions/issue.ts
@@ -18,6 +18,7 @@ import {
 	addSubIssue,
 	removeSubIssue,
 	approvalGate,
+	approvalGateWithBodyPreview,
 	buildModifyResult,
 	buildRejectResult,
 } from "../utils";
@@ -389,7 +390,13 @@ export async function handleIssueComment(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildCommentConfirmation("Issue", params.number, params.body);
-		const approval = await approvalGate(ctx, `Comment on issue #${params.number}?`, confirmMessage);
+		const approval = await approvalGateWithBodyPreview(
+			ctx,
+			`Comment on issue #${params.number}?`,
+			confirmMessage,
+			`Issue #${params.number} Comment Preview (${params.body.length} chars):`,
+			params.body,
+		);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Comment paused for modifications", "info");
 			return buildModifyResult("comment", { action: "issue-comment", issueNumber: params.number });
@@ -460,7 +467,15 @@ export async function handleIssueEdit(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = `Issue: #${params.number}\n\nChanges:\n${changes.join("\n")}\n\nThis will modify the issue.`;
-		const approval = await approvalGate(ctx, `Edit issue #${params.number}?`, confirmMessage);
+		const approval = params.body
+			? await approvalGateWithBodyPreview(
+				ctx,
+				`Edit issue #${params.number}?`,
+				confirmMessage,
+				`Issue #${params.number} New Body Preview (${params.body.length} chars):`,
+				params.body,
+			)
+			: await approvalGate(ctx, `Edit issue #${params.number}?`, confirmMessage);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Edit paused for modifications", "info");
 			return buildModifyResult("issue edit", { action: "issue-edit", issueNumber: params.number });
dots/pi/agent/extensions/github/actions/pr.ts
@@ -28,6 +28,7 @@ import {
 	resolveGitCwd,
 	findPRTemplate,
 	approvalGate,
+	approvalGateWithBodyPreview,
 	buildModifyResult,
 	buildRejectResult,
 } from "../utils";
@@ -523,7 +524,15 @@ export async function handlePRReview(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildReviewConfirmation(params);
-		const approval = await approvalGate(ctx, `Submit review on PR #${params.number}?`, confirmMessage);
+		const approval = params.body
+			? await approvalGateWithBodyPreview(
+				ctx,
+				`Submit review on PR #${params.number}?`,
+				confirmMessage,
+				`PR #${params.number} Review Body Preview (${params.body.length} chars):`,
+				params.body,
+			)
+			: await approvalGate(ctx, `Submit review on PR #${params.number}?`, confirmMessage);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Review paused for modifications", "info");
 			return buildModifyResult("review", { action: "pr-review", prNumber: params.number });
@@ -602,7 +611,13 @@ export async function handlePRComment(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildCommentConfirmation("PR", params.number, params.body);
-		const approval = await approvalGate(ctx, `Comment on PR #${params.number}?`, confirmMessage);
+		const approval = await approvalGateWithBodyPreview(
+			ctx,
+			`Comment on PR #${params.number}?`,
+			confirmMessage,
+			`PR #${params.number} Comment Preview (${params.body.length} chars):`,
+			params.body,
+		);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Comment paused for modifications", "info");
 			return buildModifyResult("comment", { action: "pr-comment", prNumber: params.number });
@@ -816,7 +831,14 @@ export async function handlePRLineComment(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildLineCommentConfirmation(params.number, params.path, params.line, params.body, params.startLine);
-		const approval = await approvalGate(ctx, `Add inline comment on PR #${params.number}?`, confirmMessage);
+		const range = params.startLine ? `${params.path}:${params.startLine}-${params.line}` : `${params.path}:${params.line}`;
+		const approval = await approvalGateWithBodyPreview(
+			ctx,
+			`Add inline comment on PR #${params.number}?`,
+			confirmMessage,
+			`PR #${params.number} Inline Comment Preview at ${range} (${params.body.length} chars):`,
+			params.body,
+		);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Inline comment paused for modifications", "info");
 			return buildModifyResult("inline comment", { action: "pr-line-comment", prNumber: params.number });
@@ -1127,7 +1149,13 @@ export async function handlePRReviewEdit(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildReviewEditConfirmation(params.number, params.reviewId, params.body);
-		const approval = await approvalGate(ctx, `Edit review ${params.reviewId} on PR #${params.number}?`, confirmMessage);
+		const approval = await approvalGateWithBodyPreview(
+			ctx,
+			`Edit review ${params.reviewId} on PR #${params.number}?`,
+			confirmMessage,
+			`PR #${params.number} Review ${params.reviewId} New Body Preview (${params.body.length} chars):`,
+			params.body,
+		);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Review edit paused for modifications", "info");
 			return buildModifyResult("review edit", { action: "pr-review-edit", prNumber: params.number, reviewId: params.reviewId });
@@ -1266,7 +1294,13 @@ export async function handlePRReviewCommentEdit(
 	// APPROVAL GATE
 	if (ctx.hasUI) {
 		const confirmMessage = buildReviewCommentEditConfirmation(params.commentId, params.body);
-		const approval = await approvalGate(ctx, `Edit review comment ${params.commentId}?`, confirmMessage);
+		const approval = await approvalGateWithBodyPreview(
+			ctx,
+			`Edit review comment ${params.commentId}?`,
+			confirmMessage,
+			`Review Comment ${params.commentId} New Body Preview (${params.body.length} chars):`,
+			params.body,
+		);
 		if (approval.outcome === "modify") {
 			ctx.ui.notify("Comment edit paused for modifications", "info");
 			return buildModifyResult("review comment edit", { action: "pr-review-comment-edit", commentId: params.commentId });
dots/pi/agent/extensions/github/utils.ts
@@ -562,6 +562,52 @@ export async function approvalGate(
 	return result;
 }
 
+/**
+ * Approval gate with body preview for actions that carry long text content.
+ *
+ * When `bodyText` exceeds 200 characters a fourth option โ€”
+ * "๐Ÿ“„ Preview body first" โ€” is offered before the standard
+ * Accept / Modify / Reject flow.  Choosing it opens an editor pane so the
+ * user can read the full content, then re-presents the confirmation dialog.
+ *
+ * @param ctx          Extension context
+ * @param title        Confirmation dialog title
+ * @param description  Confirmation dialog description (with truncated preview)
+ * @param previewTitle Label shown at the top of the editor preview pane
+ * @param bodyText     Full body text; only previewed when length > 200
+ */
+export async function approvalGateWithBodyPreview(
+	ctx: ExtensionContext,
+	title: string,
+	description: string,
+	previewTitle: string,
+	bodyText: string,
+): Promise<ApprovalResult> {
+	if (bodyText.length <= 200) {
+		return approvalGate(ctx, title, description);
+	}
+
+	const prompt = description ? `${title}\n\n${description}` : title;
+	const choice = await ctx.ui.select(prompt, [
+		"โœ“ Accept",
+		"๐Ÿ“„ Preview body first",
+		"โœŽ Modify",
+		"โœ— Reject",
+	]);
+
+	if (choice === undefined || choice === "โœ— Reject") return { outcome: "rejected" };
+	if (choice === "โœŽ Modify") return { outcome: "modify" };
+
+	if (choice === "๐Ÿ“„ Preview body first") {
+		await ctx.ui.editor(`${previewTitle}\n\n---\n\n`, bodyText);
+		// Re-present the standard three-way dialog after the preview
+		return approvalGate(ctx, title, description);
+	}
+
+	// "โœ“ Accept"
+	return { outcome: "accepted" };
+}
+
 /**
  * Build a tool result for when the user wants modifications.
  * The message clearly tells the LLM to ask the user what they want changed.