<%!-- ── Header ──────────────────────────────────────────────────────────────── --%>

Execution history for every API call routed through this flow.

Open Builder
<%!-- ── Stats strip ─────────────────────────────────────────────────────────── --%>

Total

<%= @summary.total %>

Success

<%= @summary.success %>

Failed

<%= @summary.error %>

Replayed

<%= @summary.replayed %>

Avg Latency

<%= format_ms(@summary.avg_ms) %>

<%!-- ── Search + filter ────────────────────────────────────────────────────── --%>
<%= for {label, val} <- [{"All", "all"}, {"Success", "success"}, {"Failed", "error"}] do %> <% end %>
<%= @total_count %> result<%= if @total_count != 1, do: "s", else: "" %>
<%!-- ── Event table (always full width) ──────────────────────────────────── --%>
<%= if @events == [] do %>

No events found

<%= if @search != "" or @filter_status != "all", do: "Try clearing the search or filter.", else: "Publish this flow and make an API call to see events here." %>

<% else %> <%= for event <- @events do %> if @selected_event && @selected_event.id == event.id, do: "bg-indigo-50 border-l-2 border-indigo-500", else: "hover:bg-slate-50/60"} phx-click="select_event" phx-value-trace_id={event.trace_id}> <% end %>
Trace ID Status Idempotency Key Duration When
<%= short_trace(event.trace_id) %> status_badge(event.status)}> status_dot(event.status)}> <%= event.status %> <%= event.idempotency_key || "—" %> <%= format_ms(event.duration_ms) %> <%= format_ts(event.inserted_at) %> if @selected_event && @selected_event.id == event.id, do: "text-indigo-500 rotate-90", else: "text-slate-300 group-hover:text-slate-400"} fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<%!-- Pagination --%> <% total_p = total_pages(@total_count, FlowAudit.page_size()) %> <%= if total_p > 1 do %>

Page <%= @page %> of <%= total_p %><%= @total_count %> total

<%= if @page > 1 do %> <% end %> <%= if @page < total_p do %> <% end %>
<% end %> <% end %>
<%!-- ── Right slide-over drawer ────────────────────────────────────────────── --%> <%= if @panel_open and @selected_event do %> <% e = @selected_event %> <% trace = @trace_data %> <% nodes = Map.get(trace, "nodes", []) %> <% skipped = Map.get(trace, "skipped", []) %> <%!-- Backdrop --%>
<%!-- Drawer panel --%>
<%!-- Drawer header --%>
<%!-- Status badge + duration --%>
status_badge(e.status)}> status_dot(e.status)}> <%= e.status %> <%= format_ms(e.duration_ms) %> <%= if e.idempotency_replayed do %> replayed <% end %>
<%!-- Full trace ID --%> <%= e.trace_id %>
<%!-- Scrollable body --%>
<%!-- Metadata --%>

Event Details

<%= if e.tenant_id do %>
Tenant
<%= e.tenant_id %>
<% end %>
Message type
<%= e.message_type %>
<%= if e.idempotency_key do %>
Idempotency key
<%= e.idempotency_key %>
<% end %> <%= if e.adapter do %>
Adapter
<%= e.adapter %>
<% end %>
Timestamp
<%= format_ts(e.inserted_at) %>
<%!-- Error block --%> <%= if e.error do %>

Error

<%= e.error %>

<% end %> <%!-- Tab bar: Timeline (inline) + Canvas (opens full-screen modal) --%>
<%!-- Timeline tab (inline, always shown unless explicitly toggled off) --%> <%= if @canvas_tab == "timeline" do %> <%!-- Node timeline --%>

Node Timeline

<%= if nodes != [] do %> <%= length(nodes) %> nodes <% end %>
<%= if nodes == [] do %>

No node trace available.

<% else %>
<%= for node <- nodes do %> <% nid = Map.get(node, "node_id", "?") %> <% ntype = Map.get(node, "type", "?") %> <% nstatus = Map.get(node, "status", "unknown") %> <% nms = Map.get(node, "duration_ms") %> <% nreason = Map.get(node, "reason") %> <% expanded = MapSet.member?(@expanded_nodes, nid) %> <% has_detail = not is_nil(nreason) or nstatus == "error" %>
if expanded, do: "border-slate-200 bg-slate-50", else: "border-transparent"}> <%!-- Node row --%>
if has_detail, do: "cursor-pointer hover:bg-slate-50", else: ""} phx-click={if has_detail, do: "toggle_node"} phx-value-node_id={if has_detail, do: nid}> <%!-- Status indicator --%>
case nstatus do "ok" -> "bg-green-100 text-green-700" "success" -> "bg-green-100 text-green-700" "error" -> "bg-red-100 text-red-600" "skipped" -> "bg-slate-100 text-slate-400" _ -> "bg-slate-100 text-slate-400" end}> <%= case nstatus do "ok" -> "✓" "success" -> "✓" "error" -> "✗" "skipped" -> "—" _ -> "○" end %>
<%!-- Node label + ID --%>
<%= node_label(nid, @node_labels) %> <%= if node_label(nid, @node_labels) != nid do %> <%= nid %> <% end %>
<%!-- Type chip --%> node_type_chip(ntype)}> <%= ntype %> <%!-- Duration --%> <%= if nms, do: "#{nms}ms", else: "—" %> <%!-- Chevron --%> <%= if has_detail do %> if expanded, do: "rotate-90", else: ""} fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5"> <% end %>
<%!-- Expanded reason --%> <%= if expanded and has_detail do %>

<%= nreason || "Execution error" %>

<% end %>
<% end %>
<% end %>
<% end %> <%!-- Skipped nodes --%> <%= if skipped != [] do %>

Skipped (<%= length(skipped) %>)

<%= for sid <- skipped do %> <%= sid %> <% end %>
<% end %> <%!-- Request / Response payloads --%>
<%= if e.request_snapshot do %>
Request Payload
<%= Jason.encode!(e.request_snapshot, pretty: true) %>
<% end %> <%= if e.response_snapshot do %>
Response Payload
<%= Jason.encode!(e.response_snapshot, pretty: true) %>
<% end %>
<%!-- end scrollable body --%>
<%!-- end drawer --%> <% end %> <%!-- ═══════════════════════════════════════════════════════════════════ Full-screen Canvas Modal Opens when user clicks "Canvas ↗" in the audit drawer ════════════════════════════════════════════════════════════════════ --%> <%= if @canvas_modal and @selected_event do %> <% e = @selected_event %> <%!-- Backdrop --%>
<%!-- Modal panel --%>
<%!-- Modal header --%>
case e.status do "success" -> "bg-green-100 text-green-700" "error" -> "bg-red-100 text-red-700" _ -> "bg-slate-100 text-slate-600" end}> case e.status do "success" -> "bg-green-500" "error" -> "bg-red-500" _ -> "bg-slate-400" end}> <%= e.status %> <%= if e.duration_ms do %> <%= e.duration_ms %>ms <% end %>

<%= e.trace_id %>

<%= @flow.name %> · <%= format_ts(e.inserted_at) %>

<%!-- Legend + close --%>
<%!-- Canvas area — full remaining space, scrollable --%>
<%!-- Status summary footer --%>
<% stats = @node_stats %> <% ok_count = stats |> Map.values() |> Enum.count(&(&1.status in ["ok","success"])) %> <% error_count = stats |> Map.values() |> Enum.count(&(&1.status == "error")) %> <% skipped_count = stats |> Map.values() |> Enum.count(&(&1.status == "skipped")) %> <%= ok_count %> ok <%= error_count %> error <%= skipped_count %> skipped <%= if e.duration_ms do %> Total: <%= e.duration_ms %>ms <% end %>
<% end %>