Show full untruncated content in approval requests
Permission requests only ever displayed a summary that was hard-capped: RiskClassifier.title() slices to 80 chars, and the iOS card used a lossy compactSummary clipped to 8 lines. The full tool input was present on the request but never shown, so a long Bash command, file path, or URL was cut off with "…" and the user couldn't see what they were granting. - Add RiskClassifier.detail(toolName:input:): the full, untruncated content (full command/path/url/query), falling back to the pretty-printed input so nothing about an unrecognized tool is hidden. - Add JSONValue.prettyString() for indented, multi-line display. - macOS ApprovalBar: render the detail in a bounded, scrollable, selectable monospaced block (shown only when it adds beyond the already-shown title). - iOS ApprovalCardView: replace lossy compactSummary/lineLimit(8) with the full content in a bounded scroll view via JSONValue.approvalDetail. - Tests: assert detail is full and untruncated, with pretty-input fallback. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -33,13 +33,19 @@ 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)
|
||||||
}
|
}
|
||||||
Text(approval.input.compactSummary)
|
// Full, untruncated content in a bounded scroll view — the user must be
|
||||||
.font(.caption.monospaced())
|
// able to read exactly what they are granting, so long commands/paths/inputs
|
||||||
.lineLimit(8)
|
// scroll instead of being clipped to a few lines.
|
||||||
.textSelection(.enabled)
|
if !approval.input.approvalDetail.isEmpty {
|
||||||
.padding(8)
|
ScrollView([.horizontal, .vertical]) {
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
Text(approval.input.approvalDetail)
|
||||||
|
.font(.caption.monospaced())
|
||||||
|
.textSelection(.enabled)
|
||||||
|
.padding(8)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: 200, alignment: .leading)
|
||||||
.background(Color(.secondarySystemBackground), in: RoundedRectangle(cornerRadius: 8))
|
.background(Color(.secondarySystemBackground), in: RoundedRectangle(cornerRadius: 8))
|
||||||
|
}
|
||||||
|
|
||||||
if requiresBiometric && !biometricPassed {
|
if requiresBiometric && !biometricPassed {
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@@ -82,4 +82,27 @@ extension JSONValue {
|
|||||||
case .null: return "null"
|
case .null: return "null"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The full, **untruncated** content of a tool input for the approval card —
|
||||||
|
/// the user must see exactly what they are granting before allowing. Unlike
|
||||||
|
/// `compactSummary` (a one-line gist that drops values for complex objects) this
|
||||||
|
/// returns the primary field verbatim, falling back to the pretty-printed input
|
||||||
|
/// so nothing is hidden. The caller scrolls; it never clips.
|
||||||
|
var approvalDetail: String {
|
||||||
|
switch self {
|
||||||
|
case .string(let s): return s
|
||||||
|
case .object(let o):
|
||||||
|
if let primary = o["command"]?.stringValue
|
||||||
|
?? o["file_path"]?.stringValue
|
||||||
|
?? o["path"]?.stringValue
|
||||||
|
?? o["notebook_path"]?.stringValue
|
||||||
|
?? o["url"]?.stringValue
|
||||||
|
?? o["query"]?.stringValue {
|
||||||
|
return primary
|
||||||
|
}
|
||||||
|
return o.isEmpty ? "" : prettyString()
|
||||||
|
case .null: return ""
|
||||||
|
default: return prettyString()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user