',
esc_attr( $extra ),
$id_attr,
$head,
$desc_html,
$body
),
aafm_admin_allowed_html()
);
}
/**
* Echo the toggle control for one ability row: a lock icon when a floor is holding the ability
* down, or a checkbox otherwise.
*
* Shared by aafm_render_ability_row() (the Abilities tab) and
* aafm_render_integration_ability_row() (the Integrations tab) so both surfaces render the same
* lock treatment from one place. This exists because they didn't: the Integrations renderer never
* learned about the lock at all and kept emitting a live checkbox for a locked ability, which is
* exactly how a high-risk ability got enabled, saved, and logged from a card whose master switch
* was off.
*
* A locked row emits NO at all, rather than a disabled one. The bulk enable-all controls
* query `input[type="checkbox"][name="aafm_abilities[]"]` inside the card, so a greyed-out but
* present checkbox would still be swept in by them. That is the 1.5.0 bug and it must not come back.
*
* STRUCTURAL ONLY, same convention as the rest of this file: `$name` and `$title_id` are escaped
* here; the caller owns everything else about the row.
*
* @param string $name Ability name.
* @param string $title_id Id of the row's
, referenced via aria-labelledby.
* @param array $enabled Enabled ability names.
* @param bool $host_disabled True when the row's host integration is inactive - an
* unlocked checkbox then renders disabled so it never
* submits. Ignored when the ability is locked, since a
* locked row never renders a checkbox at all.
* @return string|null The lock reason ('read_only' or 'high_risk') when the row is locked, so the
* caller can render the matching note, or null when it is freely toggleable.
*/
function aafm_render_ability_toggle_control( string $name, string $title_id, array $enabled, bool $host_disabled = false ): ?string {
$reason = aafm_ability_lock_reason( $name );
if ( null !== $reason ) {
printf(
'%2$s',
esc_attr__( 'Locked', 'agent-abilities-for-mcp' ),
wp_kses( aafm_icon( 'lock' ), aafm_svg_allowed_html() )
);
return $reason;
}
printf(
'',
esc_attr( $name ),
esc_attr( $title_id ),
$host_disabled ? '' : checked( in_array( $name, $enabled, true ), true, false ),
$host_disabled ? ' disabled' : ''
);
return null;
}
/**
* Echo a labelled set-row: a `.aafm-set-label` (label text + optional `.opt`
* sub-label) beside a `.aafm-set-control` (the pre-built control markup), with an
* optional `.help` element below.
*
* STRUCTURAL ONLY: `control` and `pill` are echoed verbatim - the caller owns their
* escaping. `label`/`opt`/`help` are escaped here with esc_html.
*
* Recognised $args keys: `label` (row label, escaped), `opt` (optional sub-label
* rendered in a ``, escaped), `class` (extra wrapper classes,
* sanitized per class name), `pill` (pre-built pill HTML for the label cell, echoed
* verbatim), `control` (pre-built control HTML, echoed verbatim), and `help` (optional
* help text in a `.help` element, escaped).
*
* @param array $args Row args (see the recognised keys above).
* @return void
*/
function aafm_render_set_row( array $args ): void {
$label = isset( $args['label'] ) ? (string) $args['label'] : '';
$opt = isset( $args['opt'] ) ? (string) $args['opt'] : '';
$extra_class = isset( $args['class'] ) ? (string) $args['class'] : '';
$pill = isset( $args['pill'] ) ? (string) $args['pill'] : '';
$control = isset( $args['control'] ) ? (string) $args['control'] : '';
$help = isset( $args['help'] ) ? (string) $args['help'] : '';
$extra = aafm_sanitize_html_classes( $extra_class );
$opt_html = '' === $opt
? ''
: sprintf( ' %s', esc_html( $opt ) );
$help_html = '' === $help
? ''
: sprintf( '
%s
', esc_html( $help ) );
echo wp_kses(
sprintf(
'
%2$s%3$s%4$s
%5$s%6$s
',
esc_attr( $extra ),
esc_html( $label ),
$opt_html,
$pill,
$control,
$help_html
),
aafm_admin_allowed_html()
);
}
/**
* Build a "See more" disclosure for the tail of a long set-row description.
*
* A native ``/``, the same affordance the integration cards, the abilities
* list, and the Help accordion already use - so it needs no JavaScript, survives a page with
* scripts off, and gets its keyboard and screen-reader behaviour from the browser. Only the
* three rows whose copy runs much longer than the rest of the tab use it; the point is to
* keep the visual rhythm of the tab, not to hide anything, so nothing here is truncated -
* every sentence stays in the markup either side of the fold.
*
* STRUCTURAL ONLY, same convention as the rest of this file: `$body` is echoed verbatim and
* the caller owns its escaping. `$label` is the accessible name and IS escaped here.
*
* @param string $label Accessible name for the summary, e.g. "See more about read-only mode".
* The visible text stays the short shared "See more".
* @param string $body Pre-escaped HTML for the collapsed remainder.
* @return string
*/
function aafm_get_set_more_html( string $label, string $body ): string {
return sprintf(
'%2$s
%3$s
',
esc_attr( $label ),
esc_html__( 'See more', 'agent-abilities-for-mcp' ),
$body
);
}
/**
* Render the search box and All / Read Only / Write buttons shared by the integration cards and
* the bridge directory. The caller prints the wrapping .aafm-integration-filter element.
*
* @param string $input_id Id of the search input, which its visually-hidden label points at.
* @return void
*/
function aafm_render_filter_controls( string $input_id ): void {
// Visually-hidden label keeps the search input accessible without adding visible chrome.
printf(
'',
esc_attr( $input_id ),
esc_html__( 'Search abilities', 'agent-abilities-for-mcp' )
);
printf(
'',
esc_attr( $input_id ),
esc_attr__( 'Search abilities…', 'agent-abilities-for-mcp' )
);
// All / Read Only / Write toggle group. "All" starts selected. Each button is type="button".
echo '
';
}
/**
* Render the head of a stat card: the card's opening element, its label and its icon. The caller
* prints the value and any sub line, then closes the card.
*
* @param string $modifier Modifier class, e.g. 'aafm-stat-abilities'.
* @param string $label Translated label, escaped here.
* @param string $icon Icon name passed to aafm_icon().
* @return void
*/
function aafm_render_stat_head( string $modifier, string $label, string $icon ): void {
echo '