"""HTML views for gated action previews (#434).""" from __future__ import annotations import html import json from webui.gated_actions import ActionRegistry, GatedAction, preview_action from webui.layout import render_page def _escape(text: str) -> str: return html.escape(text, quote=True) def _ledger_block(action: GatedAction) -> str: sample_params: dict[str, object] if action.action_id == "create_issue": sample_params = {"title": "Example issue"} elif action.action_id == "create_pr": sample_params = {"head": "feat/issue-99-example", "base": "master"} elif action.action_id == "delete_branch": sample_params = {"branch_name": "feat/issue-99-example"} elif action.action_id in {"review_pr", "merge_pr", "comment_pr", "close_pr"}: sample_params = {"pr_number": 99} else: sample_params = {"issue_number": 99} preview = preview_action(action.action_id, **sample_params) ledger_json = json.dumps(preview.get("mutation_ledger", []), indent=2) return ( "
" "Preview mutation ledger" f"
{_escape(ledger_json)}
" f"

Sample params: " f"{_escape(json.dumps(sample_params))}

" f"

JSON preview

" "
" ) def _preview_query(params: dict[str, object]) -> str: parts = [] for key, value in params.items(): parts.append(f"{key}={_escape(str(value))}") return "&".join(parts) def _action_card(action: GatedAction) -> str: disabled_attr = " disabled" if not action.enabled else "" return ( "
" f"

{_escape(action.label)}

" f"

{_escape(action.description)}

" "

" f"Task {_escape(action.task_key)} · " f"MCP {_escape(action.mcp_tool)}
" f"Requires {_escape(action.required_permission)} " f"({_escape(action.required_role)} profile)

" f"" f"{_ledger_block(action)}" "
" ) ACTION_PAGE_STYLES = """ """ def render_actions_page(registry: ActionRegistry) -> str: cards = "".join(_action_card(action) for action in registry.actions) body = ( "

Gated actions

" "

Future write actions are registered here but remain " "disabled in the read-only MVP. Each action " "declares the MCP capability and profile role from " "task_capability_map; previews show the mutation " "ledger before any execution path ships.

" f"

{len(registry.actions)} registered actions · " "execution: fail-closed

" f"{cards}" "

JSON registry

" f"{ACTION_PAGE_STYLES}" ) return render_page(title="Actions", body_html=body)