feat(chat): numbered shortcuts on permission sheet (Phase 2.3)

Hermes v2026.4.23's TUI rewrite added 1-9 numbered shortcuts on
approval prompts so power users approve/deny without reaching for
the mouse. Mirror the pattern in Scarf:

Mac PermissionApprovalView:
- Each option button gets a "1. ", "2. ", … prefix on its label.
- New private View extension `applyingNumberShortcut(index:)` binds
  the digit `idx + 1` (no modifiers) via .keyboardShortcut. Capped
  at 9; extra options stay tappable but unbound.

iOS PermissionSheet:
- Each row gets a monospaced "1." / "2." prefix as a hierarchy hint.
- No keyboard binding (phones don't have hardware keyboards), but
  the numbering matches the Mac pattern so users transitioning
  between platforms see the same visual structure.

Verified: Mac + iOS builds clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alan Wizemann
2026-04-25 09:04:33 +02:00
parent 70d4c97a6c
commit 1fcd963019
2 changed files with 49 additions and 13 deletions
+11 -1
View File
@@ -1270,7 +1270,12 @@ private struct PermissionSheet: View {
}
Section("Your response") {
ForEach(permission.options, id: \.optionId) { opt in
// Visual numbering 1-9 matches the Mac sheet's
// keyboard shortcuts; on iPhone the numbers serve
// as a hierarchy hint rather than an accelerator
// (no hardware keyboard binding). Mirrors the new
// Hermes v2026.4.23 TUI pattern.
ForEach(Array(permission.options.enumerated()), id: \.element.optionId) { idx, opt in
Button {
Task {
await onRespond(opt.optionId)
@@ -1278,6 +1283,11 @@ private struct PermissionSheet: View {
}
} label: {
HStack {
if idx < 9 {
Text("\(idx + 1).")
.font(.body.monospaced())
.foregroundStyle(.secondary)
}
Text(opt.name)
Spacer()
Image(systemName: "chevron.right")
+29 -3
View File
@@ -444,22 +444,34 @@ struct PermissionApprovalView: View {
.multilineTextAlignment(.center)
.padding(.horizontal)
// Numbered keyboard shortcuts (19) on the option buttons.
// Mirrors the new TUI pattern Hermes v2026.4.23 ships
// power users approve / deny without reaching for the
// mouse. Visible "1." prefixes act as discoverability
// hints; the actual key binding goes through
// `.keyboardShortcut`. Capped at 9 extra options stay
// tappable but unbound (they'd need modifiers to
// disambiguate beyond 9, which isn't worth it).
HStack(spacing: 12) {
ForEach(options, id: \.optionId) { option in
ForEach(Array(options.enumerated()), id: \.element.optionId) { idx, option in
let label = idx < 9 ? "\(idx + 1). \(option.name)" : option.name
Group {
if option.optionId == "deny" {
Button(option.name) {
Button(label) {
onRespond(option.optionId)
dismiss()
}
.buttonStyle(.bordered)
} else {
Button(option.name) {
Button(label) {
onRespond(option.optionId)
dismiss()
}
.buttonStyle(.borderedProminent)
}
}
.applyingNumberShortcut(index: idx)
}
}
}
.padding(24)
@@ -484,3 +496,17 @@ struct PermissionApprovalView: View {
}
}
}
private extension View {
/// Bind the digit `idx + 1` (1-9) to this view as a no-modifier
/// keyboard shortcut. Indices 9 silently skip there are only
/// nine numeric shortcut keys without modifier conflicts.
@ViewBuilder
func applyingNumberShortcut(index idx: Int) -> some View {
if idx < 9, let scalar = Unicode.Scalar(48 + idx + 1) {
self.keyboardShortcut(KeyEquivalent(Character(scalar)), modifiers: [])
} else {
self
}
}
}