iPad: show the commit card for host-run and pending-approval commits too

Extend the git commit card beyond the Bash transcript path to the other two
surfaces a commit appears on, so all three read like the Mac:

- ApprovalCardView: a pending `git commit` approval (Bash or host_exec) renders
  the structured commit card (subject + Markdown body) with the literal command
  under "Show command", instead of a raw blob or the generic host breakdown --
  so you see exactly the message you're granting.
- HostExecToolCard: a host-run `git commit` surfaces the commit card in its
  expanded body (the `$ command` already sits in the header), rather than only
  the generic "Commit changes" purpose.

Both reuse GitCommitSummary.parse + GitCommitCard from the previous commit.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-04 00:27:20 -07:00
co-authored by Claude Opus 4.8
parent 850ee0cd96
commit 149af312dc
2 changed files with 18 additions and 8 deletions
@@ -33,11 +33,14 @@ struct ApprovalCardView: View {
.background(approval.risk.color.opacity(0.2), in: Capsule()) .background(approval.risk.color.opacity(0.2), in: Capsule())
.foregroundStyle(approval.risk.color) .foregroundStyle(approval.risk.color)
} }
// A host_exec gate escapes the sandbox onto the macOS host, so lay out what it's // A git commit reads as a structured commit card (subject + Markdown body) so you can
// about to run — the inferred purpose and the program/actions/flags, parsed from the // see exactly what you're granting; the literal command stays under "Show command".
// command itself (never from the agent) — then the exact command beneath it. Every // Otherwise a host_exec gate lays out its parsed breakdown + exact command, and every
// other tool shows its untruncated detail in a single scrollable box. // other tool shows its untruncated detail in a single scrollable box.
if approval.toolName == HostCommandSummary.hostExecToolName, if let command = approval.input["command"]?.stringValue,
let commit = GitCommitSummary.parse(command) {
GitCommitCard(commit: commit, rawCommand: command)
} else if approval.toolName == HostCommandSummary.hostExecToolName,
let command = approval.input["command"]?.stringValue, let command = approval.input["command"]?.stringValue,
let parsed = HostCommandSummary.summary(for: command) { let parsed = HostCommandSummary.summary(for: command) {
HostCommandBreakdown(summary: parsed) HostCommandBreakdown(summary: parsed)
@@ -29,7 +29,10 @@ struct HostExecToolCard: View {
!$0.flags.isEmpty || !$0.arguments.isEmpty || !$0.env.isEmpty !$0.flags.isEmpty || !$0.arguments.isEmpty || !$0.env.isEmpty
} }
} }
private var canExpand: Bool { hasOutput || hasBreakdown } /// A host-run `git commit` — surfaced as a structured commit card (subject + Markdown body),
/// like the Mac, instead of only the generic "Commit changes" purpose.
private var commit: GitCommitSummary.Commit? { GitCommitSummary.parse(command) }
private var canExpand: Bool { hasOutput || hasBreakdown || commit != nil }
var body: some View { var body: some View {
VStack(alignment: .leading, spacing: 8) { VStack(alignment: .leading, spacing: 8) {
@@ -37,7 +40,11 @@ struct HostExecToolCard: View {
label: { header } label: { header }
.buttonStyle(.plain) .buttonStyle(.plain)
if expanded { if expanded {
if hasBreakdown, let parsed { if let commit {
// The `$ command` is already shown in the header, so omit the disclosure here.
Divider().overlay(Color.primary.opacity(0.06))
GitCommitCard(commit: commit)
} else if hasBreakdown, let parsed {
Divider().overlay(Color.primary.opacity(0.06)) Divider().overlay(Color.primary.opacity(0.06))
HostCommandBreakdown(summary: parsed, showPurpose: false) HostCommandBreakdown(summary: parsed, showPurpose: false)
} }