# `Cairnloop.Web.ToolProposalPresenter`
[🔗](https://github.com/szTheory/cairnloop/blob/main/lib/cairnloop/web/tool_proposal_presenter.ex#L1)

Pure, total presenter for `Cairnloop.Governance.ToolProposal` structs.

Mirrors `ReviewTaskPresenter` and `GapCandidatePresenter` exactly:
- Total functions with safe fallbacks — no crashes on unexpected input
- Pattern-match on struct and bare atoms
- Returns strings and atoms only — never markup, never raw Elixir terms
- Never re-reads live config at render time (snapshots are truth for trust fields)

D-22 masking choke point: `input_rows/1` is the ONLY place raw `input_snapshot`
values are converted to display rows; it allowlists known scalar fields and
applies the "Unsupported value" posture to nested/unknown/sensitive values.

D-14: `reason_label/1` humanizes all reason shapes (including `{:missing_scopes, _}`
tuples and atoms) — never passes `inspect/1` output to operators.

D-13 / brand §7.5: `risk_tier_tone/1` returns an atom (:info/:warning/:danger)
that LiveView maps to color — always paired with text, never state-by-color-alone.

# `approval_mode_label`

Human label for an approval mode atom.

# `approval_outlook`

Future-tense operator copy describing the approval gate — D-12 honesty seam.

Returns:
- `nil`    for :auto (no gate to describe)
- `String` for :requires_approval (gate exists — future-tense)
- `String` for :always_block (terminal — "cannot be approved or run")

# `approval_outlook_for_approval`

Present-tense operator copy for an active approval record (D15-16 real copy).

Takes a `%ToolApproval{}` struct or a map with a `:status` key (and optionally `:reason`).
Returns a calm, present-tense string for operators — or `nil` for unknown states.

Replaces the future-tense `approval_outlook/1` honesty seam when an active approval exists.
Never re-reads live config (brand §5.3/§5.6 reason-forward, no raw terms).

# `block_reason_copy`

Returns operator copy explaining why a proposal was blocked, or nil/empty for non-blocked.

# `event_timestamp_label`

Human-readable relative timestamp for an event datetime.

# `history_line`

Returns a human-readable line for a ToolActionEvent.

Catch-all → "Workflow updated" for any unrecognized event_type (D-24).

# `input_rows`

Converts an `input_snapshot` map to a list of `{label, value}` display rows.

MASKING RULES (D-22):
- Iterates ALL keys (both atom and string-keyed JSONB snapshots are supported)
- Scalar values (string, number, boolean) are humanized and displayed
- Lists of scalars are joined with ", "
- Nested maps, tuples, and other complex values → "Unsupported value" sentinel
- Sensitive field names (containing "password", "token", "secret", "key") → "••••••"
- Never dumps raw nested structures to operators

# `policy_explanation`

Returns a calm one-sentence explanation from the policy_snapshot.

Uses dual-key lookup (atom + string) to survive the JSONB string-key
round-trip (mirrors ReviewTaskPresenter.metadata_value/2). Raw map is NOT
returned — only a humanized sentence. (D-14 / brand §5.6)

# `reason_label`

Humanizes a reason value — never passes raw inspect output to operators (D-14).

Handles:
- nil → nil
- {:missing_scopes, scopes} → "Missing scopes: scope1, scope2"
- known atom → human-readable label from @reason_labels
- unknown atom → humanize via capitalize-and-replace
- string → pass through
- any tuple → generic human fallback (no raw inspect)

# `risk_tier_label`

Human label for a risk tier atom.

# `risk_tier_tone`

Semantic tone for the risk tier — returns an atom only (brand §7.5 / D-13).
LiveView MUST pair this with a text label — never rely on color alone.

:info    — :read_only (low risk, informational)
:warning — :low_write (moderate risk)
:danger  — :high_write, :destructive (high risk)

# `scope_summary`

Human summary of required scopes from a scope_snapshot map.

# `status_group`

Groups proposal status into display buckets.

- :awaiting — :proposed, :needs_input (action needed but not yet blocked)
- :blocked  — :scope_invalid, :policy_denied (terminal blocked state in Phase 14)
- :active   — declared for future Phase 15/16 running/approved states (unreachable now)
- :done     — declared for future completed/executed states (unreachable now)

# `status_group_label`

Human label for the status group.

# `status_label`

Human label for a proposal status atom or ToolProposal struct.

# `status_meaning`

Status meaning — one calm sentence explaining what this state means for operators.

# `trace_metadata`

Returns a map of de-emphasized trace fields for a proposal.
Safe for display; all values are strings. Shows a short idempotency key suffix.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
