use xa11y::Error;
pub fn render(tool: &str, ctx: &str, err: &Error) -> String {
let head = format!("{tool}: {ctx}: ");
match err {
Error::PermissionDenied { instructions } => format!(
"{head}permission denied — {instructions} After granting, retry the \
same call; the consent applies to the whole terminal session"
),
Error::AccessibilityNotEnabled { app, instructions } => {
format!("{head}accessibility not enabled for {app} — {instructions}")
}
Error::SelectorNotMatched {
selector,
diagnosis,
} => {
let mut out = format!(
"{head}no element matched `{selector}` — re-capture with \
computer_snapshot and match an element that exists NOW \
(role[name='…'] is stable; a bare :nth index goes stale \
after any UI change)"
);
out.push_str(&diagnosis_suffix(diagnosis.as_deref()));
out
}
Error::ElementStale { selector } => format!(
"{head}element `{selector}` went stale (the UI changed between \
capture and action) — re-snapshot and act on the fresh tree"
),
Error::ActionNotSupported { action, role } => format!(
"{head}action `{action}` is not supported on a {role} element — \
try the action appropriate to its role instead (`press` on \
buttons, `toggle` on check boxes, `show_menu` on menu items), \
or act on a parent container"
),
Error::TextValueNotSupported => format!(
"{head}this element does not accept text input through the \
accessibility bridge — for a slider/spinner use `set_numeric_value` \
with `numeric_value`, otherwise click it first (computer_control, \
the real click is the one reliable focus mover) and type with a \
keyboard step instead"
),
Error::Timeout { elapsed, diagnosis } => {
let mut out = format!(
"{head}timed out after {elapsed:.1?} — if the target is slow \
to appear, raise `timeout_ms` or add a `wait` step before \
this one"
);
out.push_str(&diagnosis_suffix(diagnosis.as_deref()));
out
}
Error::InvalidSelector { selector, message } => {
format!(
"{head}selector `{selector}` is invalid: {message} — fix the selector syntax and retry (no platform call was made)"
)
}
Error::InvalidActionData { message } => {
format!(
"{head}invalid action data: {message} — fix the call arguments and retry (no platform call was made)"
)
}
Error::InvalidConfig { message } => {
format!(
"{head}invalid configuration: {message} — fix the environment setting and restart the session"
)
}
Error::NoElementBounds => format!(
"{head}the element matched but has no on-screen bounds — it may \
sit in a collapsed container or be virtual; scroll it into \
view (computer_act `scroll_into_view`) or act on a visible \
ancestor instead"
),
Error::Unsupported { feature } => format!(
"{head}`{feature}` has no implementation on this platform/session — a \
capability limit, not a fixable call error. Input synthesis and \
screen capture both depend on the session type (native X11, or \
Wayland with the right portal grants), so pick an approach that \
fits what this session exposes"
),
Error::Platform { code, message } => format!(
"{head}platform error ({code}): {message} — a provider-level failure \
whose meaning depends on the operation (an accessibility-backend \
fault, a capture or image-processing failure, or an argument the \
backend rejected); one retry is reasonable, but if unchanged \
arguments fail the same way, stop and report it to the user \
instead of looping"
),
other => format!(
"{head}{} — re-capture with computer_snapshot to check the \
current state before retrying",
other
),
}
}
pub fn render_app_miss(tool: &str, err: &Error) -> String {
let out = render(tool, "resolve application", err);
match err {
Error::SelectorNotMatched { .. } => format!(
"{out} — a name that matches no APPLICATION may belong to a \
transient shell surface (Quick Settings, Notification Center, \
a shell menu): those never appear as applications. Target them \
with `surface` instead (e.g. `\"flyout\"` for an open flyout, \
`\"menu_bar\"` for menus)"
),
_ => out,
}
}
fn diagnosis_suffix(diagnosis: Option<&xa11y::Diagnosis>) -> String {
match diagnosis {
None => String::new(),
Some(d) => {
let rendered = d.to_string();
if rendered.starts_with(';') {
format!(" {rendered}")
} else {
format!("; {rendered}")
}
}
}
}