fix(approve): clearer panel focus indicator and unhandled key feedback (#19)
- Footer now shows '● this panel has focus' so users know keypresses are going to the panel, not the editor - Prompt line uses '› approve this action? [y/n]' cursor glyph - Unhandled keys re-render with '› unrecognised key — press y or n' instead of silently swallowing the input
- commit
- e9f98b35780adc1e8ebf3db2079e1d9961a285ee
- author
- Raymond Gasper · · 2026-06-08 16:35 UTC
- parent
- 5ffdafa
- signature
- unsigned
1 file · +22 -9
M examples/extensions/approve/main.go +22 -9
| @@ -83,6 +83,22 @@ func main() { | |||
| 83 | 83 | pid := nextPID() | |
| 84 | 84 | decision := make(chan bool, 1) | |
| 85 | 85 | ||
| 86 | + | buildLines := func(hint string) []string { | |
| 87 | + | lines := []string{ | |
| 88 | + | " Action: " + in.Action, | |
| 89 | + | } | |
| 90 | + | if strings.TrimSpace(in.Reason) != "" { | |
| 91 | + | lines = append(lines, " Reason: "+in.Reason) | |
| 92 | + | } | |
| 93 | + | lines = append(lines, "") | |
| 94 | + | lines = append(lines, " "+hint) | |
| 95 | + | return lines | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | const footer = "● this panel has focus — y approve n deny esc cancel" | |
| 99 | + | const prompt = "› approve this action? [y/n]" | |
| 100 | + | const badKey = "› unrecognised key — press y to approve or n to deny" | |
| 101 | + | ||
| 86 | 102 | // Register key handler before opening the panel so there | |
| 87 | 103 | // is no window where a key could arrive unhandled. | |
| 88 | 104 | e.OnPanelKey(pid, func(key, text string) { | |
| @@ -94,6 +110,11 @@ func main() { | |||
| 94 | 110 | key == "esc": | |
| 95 | 111 | e.ClosePanel(pid) | |
| 96 | 112 | decision <- false | |
| 113 | + | default: | |
| 114 | + | // Unknown key: re-render with a nudge so the user | |
| 115 | + | // knows the panel is alive and what to press. | |
| 116 | + | e.RenderPanel(pid, "Approval required", | |
| 117 | + | buildLines(badKey), footer) | |
| 97 | 118 | } | |
| 98 | 119 | }, func() { | |
| 99 | 120 | // Host closed the panel (e.g. user navigated away). | |
| @@ -103,16 +124,8 @@ func main() { | |||
| 103 | 124 | } | |
| 104 | 125 | }) | |
| 105 | 126 | ||
| 106 | - | lines := []string{ | |
| 107 | - | " Action: " + in.Action, | |
| 108 | - | } | |
| 109 | - | if strings.TrimSpace(in.Reason) != "" { | |
| 110 | - | lines = append(lines, " Reason: "+in.Reason) | |
| 111 | - | } | |
| 112 | - | lines = append(lines, "", " y approve n / esc deny") | |
| 113 | - | ||
| 114 | 127 | // Open the panel spontaneously from inside the tool handler. | |
| 115 | - | e.OpenPanel(pid, "Approval required", lines, "y approve n deny esc cancel") | |
| 128 | + | e.OpenPanel(pid, "Approval required", buildLines(prompt), footer) | |
| 116 | 129 | ||
| 117 | 130 | // Block until the user responds. | |
| 118 | 131 | if <-decision { | |
| @@ -83,6 +83,22 @@ func main() { | |||||
| 83 | pid := nextPID() | 83 | pid := nextPID() | ||
| 84 | decision := make(chan bool, 1) | 84 | decision := make(chan bool, 1) | ||
| 85 | 85 | ||||
| 86 | + | buildLines := func(hint string) []string { | |||
| 87 | + | lines := []string{ | |||
| 88 | + | " Action: " + in.Action, | |||
| 89 | + | } | |||
| 90 | + | if strings.TrimSpace(in.Reason) != "" { | |||
| 91 | + | lines = append(lines, " Reason: "+in.Reason) | |||
| 92 | + | } | |||
| 93 | + | lines = append(lines, "") | |||
| 94 | + | lines = append(lines, " "+hint) | |||
| 95 | + | return lines | |||
| 96 | + | } | |||
| 97 | + | ||||
| 98 | + | const footer = "● this panel has focus — y approve n deny esc cancel" | |||
| 99 | + | const prompt = "› approve this action? [y/n]" | |||
| 100 | + | const badKey = "› unrecognised key — press y to approve or n to deny" | |||
| 101 | + | ||||
| 86 | // Register key handler before opening the panel so there | 102 | // Register key handler before opening the panel so there | ||
| 87 | // is no window where a key could arrive unhandled. | 103 | // is no window where a key could arrive unhandled. | ||
| 88 | e.OnPanelKey(pid, func(key, text string) { | 104 | e.OnPanelKey(pid, func(key, text string) { | ||
| @@ -94,6 +110,11 @@ func main() { | |||||
| 94 | key == "esc": | 110 | key == "esc": | ||
| 95 | e.ClosePanel(pid) | 111 | e.ClosePanel(pid) | ||
| 96 | decision <- false | 112 | decision <- false | ||
| 113 | + | default: | |||
| 114 | + | // Unknown key: re-render with a nudge so the user | |||
| 115 | + | // knows the panel is alive and what to press. | |||
| 116 | + | e.RenderPanel(pid, "Approval required", | |||
| 117 | + | buildLines(badKey), footer) | |||
| 97 | } | 118 | } | ||
| 98 | }, func() { | 119 | }, func() { | ||
| 99 | // Host closed the panel (e.g. user navigated away). | 120 | // Host closed the panel (e.g. user navigated away). | ||
| @@ -103,16 +124,8 @@ func main() { | |||||
| 103 | } | 124 | } | ||
| 104 | }) | 125 | }) | ||
| 105 | 126 | ||||
| 106 | - | lines := []string{ | |||
| 107 | - | " Action: " + in.Action, | |||
| 108 | - | } | |||
| 109 | - | if strings.TrimSpace(in.Reason) != "" { | |||
| 110 | - | lines = append(lines, " Reason: "+in.Reason) | |||
| 111 | - | } | |||
| 112 | - | lines = append(lines, "", " y approve n / esc deny") | |||
| 113 | - | ||||
| 114 | // Open the panel spontaneously from inside the tool handler. | 127 | // Open the panel spontaneously from inside the tool handler. | ||
| 115 | - | e.OpenPanel(pid, "Approval required", lines, "y approve n deny esc cancel") | 128 | + | e.OpenPanel(pid, "Approval required", buildLines(prompt), footer) |
| 116 | 129 | ||||
| 117 | // Block until the user responds. | 130 | // Block until the user responds. | ||
| 118 | if <-decision { | 131 | if <-decision { | ||