Raxol. UI. Components. Harness. ApprovalPrompt
(Raxol v2.6.1)
View Source
The agent-harness approval gate: shows what an action wants to do, its blast radius, and a keyboard-driven choice of how far to trust it.
Render-dual of the harness protocol's approval exchange (see
docs/proposals/in-flight/harness-spec-frontend.md, ยง4 row A6):
- Payload in โ
approval_requested{action, blast_radius, options}, read byinit/1as the:action,:blast_radius,:optionsprops. - Emitted out โ
approval_decision{decision, scope}, returned as a command fromhandle_event/3in the shape{:approval_decision, %{decision: :allow | :deny, scope: :once | :session | :root}}once the user confirms with Enter.
Built on Raxol.UI.Components.Modal.Rendering.dialog_surface/4 -- the
same one-box overlay shell Modal itself renders into -- rather than a
second modal implementation. render/2 embeds a
Raxol.UI.Components.Harness.BlastRadiusPreview for the safety-critical
part of the surface. The overlay positioning (centering, dimming the
background) is the caller's job, same as Modal's own demo: wrap this
component's render/2 result with
Raxol.UI.Components.AbsoluteLayer.dialog_overlay/3, sized with
estimate_height/1.
Keyboard
Up/Down arrows and digit keys (1-9) move the selection; digits jump
straight to that option's index rather than acting as a shortcut, so
Enter is always the one and only way to confirm (a mis-timed digit key
can't fire off a decision by itself). Enter emits the decision for
whichever option is currently selected.
Default options
Four choices, matching the harness spec's :once/:session/:root scopes
(Devin's three-button pattern) plus deny:
- Allow once ->
%{decision: :allow, scope: :once} - Allow session ->
%{decision: :allow, scope: :session} - Allow subtree ->
%{decision: :allow, scope: :root}--:rootcovers this whole agent spawn subtree, not just the current agent. - Deny ->
%{decision: :deny, scope: :once}-- denial is inherently a single-decision act; there is no persistent "deny for session/subtree" concept in this gate, so it always carries the narrowest scope.
None of the options carry inherent color-by-danger -- the only visual
differentiator between rows is which one currently holds keyboard focus
(reverse video, one row, per the tui-design-science skill's "one
anchor per screen" rule). Danger lives entirely in the embedded
BlastRadiusPreview, not in how "Deny" or "Allow" are painted, so the
safe default is never visually fighting the destructive one.
Summary
Functions
Structural line-count estimate of the prompt's total footprint height
(frame + content), used to size the dialog_surface/4 box this
component renders into and, standalone, by callers positioning the
overlay before calling render/2 (e.g. AbsoluteLayer.dialog_overlay/3
needs a matching height to center correctly). Generous by design, like
Raxol.UI.Components.Modal.Rendering.estimate_height/1.
Callback implementation for Raxol.UI.Components.Base.Component.mount/1.
Callback implementation for Raxol.UI.Components.Base.Component.unmount/1.
Callback implementation for Raxol.UI.Components.Base.Component.update/2.
Types
@type scope() :: :once | :session | :root
@type t() :: %{ id: String.t() | atom(), action: term(), blast_radius: Raxol.UI.Components.Harness.BlastRadiusPreview.blast_radius(), options: [option()], selected_index: non_neg_integer(), width: pos_integer(), style: map(), theme: map() }
Functions
@spec estimate_height(t()) :: pos_integer()
Structural line-count estimate of the prompt's total footprint height
(frame + content), used to size the dialog_surface/4 box this
component renders into and, standalone, by callers positioning the
overlay before calling render/2 (e.g. AbsoluteLayer.dialog_overlay/3
needs a matching height to center correctly). Generous by design, like
Raxol.UI.Components.Modal.Rendering.estimate_height/1.
Callback implementation for Raxol.UI.Components.Base.Component.mount/1.
Callback implementation for Raxol.UI.Components.Base.Component.unmount/1.
Callback implementation for Raxol.UI.Components.Base.Component.update/2.