iPad: render git commits as a structured card in the transcript

Port the desktop GitBlockCard's commit rendering (+ CommandDisclosure) to the
remote transcript: a Bash `git commit` now shows the commit subject as a
headline and the message body as Markdown, with the raw command one tap away
under "Show command" -- instead of a raw `git commit -F - <<'EOF' ...` blob.

- GitCommitCard.swift: the card + the CommandDisclosure helper (portable
  SwiftUI, mirroring Sources/NucleicApp/{GitBlockCard,CommandDisclosure}).
- GitCommitSummary.parse (in HostCommandSummary.swift) extracts the message
  from -m/--message args or a `-F -` heredoc body, reusing the file-private
  shell Lexer already used for host-exec summaries; skips env prefixes and git
  global flags, and only fires for real `git commit` segments.
- Hooked into ToolCallCard.details, gated to shell tools so non-shell input is
  never misread as a commit.
- Demo transcript gains a git-commit call so the card is exercisable offline.

SummaryCard is intentionally not ported -- it's the host-only Apple-Intelligence
recap, which a remote has no way to generate.

Verified in the iPad simulator (demo mode); parser covered by a standalone test.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
2026-07-04 00:21:32 -07:00
co-authored by Claude Opus 4.8
parent 34aa01a724
commit 850ee0cd96
4 changed files with 187 additions and 3 deletions
@@ -43,9 +43,17 @@ struct ToolCallCard: View {
.contentShape(Rectangle())
}
/// Only shell tools carry a literal command worth structuring (a git commit); everything else
/// keeps the plain input block, so a non-shell input can't be misread as a commit.
private var isShellTool: Bool { group.name == "Bash" || group.name == "Shell" }
@ViewBuilder private var details: some View {
let input = group.input.approvalDetail
if !input.isEmpty {
// A git commit reads as a structured commit card (subject + Markdown body), like the Mac —
// the raw command stays one tap away under "Show command".
if isShellTool, let commit = GitCommitSummary.parse(input) {
GitCommitCard(commit: commit, rawCommand: input)
} else if !input.isEmpty {
ToolBlock(label: "Input", text: input, mono: true)
}
if let result = group.result {